1.功能描述
该合约主要功能描述如下:
用户A发起盲拍项目,即提出拍卖申请。
竞标原则
1.所有人都可以对任何一个盲拍项目发出拍卖申请。
2.竞拍用户每次出价都需要给出一个对外可见的价格(显式价格),同时给出一个自己心里认为可以接受的价格(隐式价格)。显式价格在竞拍阶段其他人是可以看见的,但是隐式价格别人看不见。因为这个隐式价格被该用户利用自己定的密码加密。即每个人竞标时输入两个数字和一个密码,隐式价格和密码直接在remix编辑器中以参数的形式输入,显式价格以value的形式将以太坊输入合约内。也就是说如果我想买这个东西准备花10eth,但是我想放出假话我要用12eth来买,我就要先将12eth打入这个合约内。
3.用户可以重复对一个商品进行多次竞拍(隐式价格可以改,但是你需要记住你最高的一次竞拍的密码和隐式价格),如果由于误操作导致隐式价格和密码两次都输入一样,那么第一次输入合约的以太坊将无法返还。这点需要注意!
4.显式价格必须大于隐式价格,否则在揭标的时候会被认作无效标。无效竞标可以提交,用于迷惑竞争者,揭标时系统会自动退款。
5.竞标成功的人最终会以合约内所有揭标后的次高价购买到商品(买家花费的钱比心理预期少,卖家至少会得到起拍价)。
揭标原则
1.只能在竞标和仲裁之间进行
2.竞标者在揭标的相关函数中输入每个发起过的竞标的隐式价格和密码。这样每一次发起的竞标会由合约进行判断处理,如果不符合获胜条件会自动返回给竞标发起人。(如果两次竞标同一个隐式价格和密码,只能对最后一次的标进行揭标,前面标的钱将丢失在合约中无法退回)
3.揭标的时候会一直动态更新当前产品的最高价,次高价和出价人。这时候的所有价格都是隐式价格。由于这个时候已经无法竞标,所以公开隐式价格已经无所谓了,同时也可以让竞标成功者知道次高价为多少。
4.竞标失败的人会得到系统发回来的退款。
5.最高价的人得到显式价格与隐式价格的差额。剩余在合约内的前为隐式价格,到仲裁投票后会将与次高位出价的差额退回。
6.如果参与竞标但是未及时揭标,无法自动退款。所以揭标是必须要做的!
仲裁原则
1.仲裁者不允许是买家或竞标者,会有require()进行判断。
- 创建一个合约,将次高价金额、买家、卖家、仲裁人信息转到第该合约内。
- 仲裁结束后,将差额(最高价-次高价的)退还给竞标获胜的买家。
仲裁投票原则
1.只有卖家,竞标获胜者和仲裁者可以参与投票。每个人只能投票一次,投票有两个选择,一个是支持付款给卖家,一个是付款给买家。这个过程是为了防止卖家不发货,或者买家拿到东西不愿意付钱设置的。2.两个选择得到票数先到达两票时,合约会执行对应的付款行为。
我所使用的solidity编译器版本为0.5.0版本
代码实现
1.定义竞标商品结构
pragma solidity ^0.5.0;
contract Auction {
uint productIndex;
struct Product{
uint id;
string name;
string category;
//图片的链接
string imageLink;
//商品描述信息的链接
string descLink;
uint startPrice;
uint revealStartTime;
uint arbitrateStartTime;
//商品的状态:竞标中,卖出成功,卖出失败
ProductStatus status;
//新的,或者二手的
ProductCondition condition;
//最高价格
uint highestBid;
//次高价格
uint secondHighestBid;
//最高出价者
address payable highestBidder;
//所有的竞标人数
uint totalBids;
}
enum ProductStatus {OPEN,SOLD,UNSOLD}
enum ProductCondition {USED,NEW}
}
2.卖家添加竞拍商品至商城
在合约中需要添加两个mapping结构,用于卖家-商品-商品id之间相互查找
mapping(address=>mapping(uint=>Product)) stores;
mapping(uint=>address payable) public productIdToOwmer;
添加函数
function addProductToStore(
string memory _name,
string memory _category,
string memory _imageLink,
string memory _descLink,
uint _startPrice,
uint _revealStartTime,
uint _arbitrateStartTime,
ProductCondition _condition) public{
productIndex++;
Product memory pro = Product({
id:productIndex,
name:_name,
category:_category,
imageLink:_imageLink,
descLink:_descLink,
startPrice:_startPrice,
revealStartTime:_revealStartTime,
arbitrateStartTime:_arbitrateStartTime,
status:ProductStatus.OPEN,
condition:_condition,
highestBid:0,
secondHighestBid:0,
highestBidder:address(0),
totalBids:0
});
stores[msg.sender][productIndex]=pro;
productIdToOwmer[productIndex]=msg.sender;
}
3.竞标
定义标结构
struct Bid {
//对应拍卖品的ID
uint productId;
//该标中的显式价格
uint price2Show;
//是否揭标标志位,为了保证每个标只能被揭一次
bool isRevealed;
//本次投标的投标人地址
address bidder;
}
由于每个人都可以对任何产品发起投标,所以我们需要为每个商品添加一个管理竞标的数据结构。
设计思路:由于不想对心里的隐式价格保密,同时还希望能找到自己能找到该标,别人找不到,设计数据结构如下:
mapping(address=>mapping(bytes32=>Bid)) bids;
address是竞标人地址,byte32是隐式价格和密码字符串packed后生成的哈希。这样,这有自己能找到该标。
注意:如果多次竞标时,输入与上次相同的隐式价格和密码,所对应的hash一样。这样导致key被覆盖,上一次的标就找不到了。这也是前面所提的,为什么输入相同的隐式价格和密码字符串时,上一次的竞标将丢失,里面的以太坊也将丢失在合约内无法退出的原因。
将该数据结构加入Product结构体
struct Product{
...
mapping(address=>mapping(bytes32=>Bid)) bids;
...
}
竞标函数
function bid(uint _productIndex,uint _idealPrice,string memory _password) public payable{
//对隐式价格和密码打包并求哈希
bytes memory bidInfo = abi.encodePacked(_idealPrice,_password);
bytes32 bytesInfo = keccak256(bidInfo);
//通过商品编号找到该拍卖商品的实例
address owner = productIdToOwmer[_productIndex];
Product storage product = stores[owner][_productIndex];
//确保只能在揭标前发起竞标
require(now<product.revealStartTime);
product.totalBids++;
//添加标进管理数据结构
Bid memory b = Bid(_productIndex, msg.value, false, msg.sender);
product.bids[msg.sender][bytesInfo]=b;
}
此时可以写一些辅助函数来进行代码阶段性测试
function getBidById(uint _productIndex,uint _idealPrice,string memory _password) public view returns(uint,uint,bool,address){
address owner = productIdToOwmer[_productIndex];
bytes memory bidInfo = abi.encodePacked(_idealPrice,_password);
bytes32 bidBytes = keccak256(bidInfo);
Product storage pro = stores[owner][_productIndex];
Bid memory b = pro.bids[msg.sender][bidBytes];
return (b.productId,b.price2Show,b.isRevealed,b.bidder);
}
function getBalance() public view returns(uint){
return address(this).balance;
}
ps:
本人热爱图灵,热爱中本聪,热爱V神,热爱一切被梨花照过的姑娘。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!