拍得到-智能合约
拍得到智能合约流程
先把逻辑捋顺再看代码
竞标规则
-
仅可在竞标时间段内竞标,竞标结束后⽆法再次竞标,竞标⻚⾯关闭
-
每个⼈都可以参与竞标。(卖家⾃⼰也可以参与竞标)
-
每个⼈可以多次对同⼀产品竞标。(我可以看到别⼈⽐我⾼的时候,再次出价;可以使⽤战略, 多次出价迷惑敌⼈)
-
流程:
每个⼈竞标时输⼊两个数字和⼀个密码:
迷惑价格:30元(打到合约的⾦额,后⾯揭标时会退还差价,公开)
理想出价:10元(隐秘,不会在竞标函数中作为显示参数传⼊)
密码字符串(隐秘)
系统记录相关信息
迷惑价格必须⼤于理想价格时,否则视为⽆效竞标,⽆效竞标可以提交,⽤于迷惑竞争者,揭标 时⾃动退款。
竞标成功的⼈最终会以 次⾼价 购买商品(买家花费的钱⽐⼼理预期少,卖家⾄少会得到起拍 价)。
-
揭标规则
-
仅限于 竞标结束 和 终结拍卖 之前这段时间内完成
-
流程: i. 每个⼈输⼊⼀个数字和⼀个密码: i. 理想出价(同竞标时的数字) ii. 密码(同竞标时的字符串) ii. 揭标时会⼀直动态更新产品的最⾼的出价⼈、最⾼价、次⾼价格。 iii. 淘汰的⼈得到退款(当初转⼊的 迷惑价格 )。 iv. 最⾼价的⼈得到迷惑价格与理想出价的差额。
-
如果参与竞标但是未及时揭标,⽆法⾃动退款
-
终结竞拍规则
-
由任意⼀个第三⽅的⼈来执⾏终结。
-
流程: i. 创建⼀个 第三⽅合约 ,将次⾼价⾦额、买家、卖家、仲裁⼈信息转⼊到第三⽅合约。 ii. 转差额(最⾼价-次⾼价的)给买家。
-
对第三⽅合约投票
-
合约中含有买家,卖家,仲裁⼈,次⾼价⾦额。
-
仅这三个⼈可以进⾏投票,且每⼈⼀票。
-
票数达到两票的⼈获得合约中的⾦额,拍卖结束
1.定义商品结构
struct Product {
//
uint id; //商品唯一id
string name;//商品名称
string category;//商品类别
string imageLink;//ipfs-->图片链接
string descLink;//ipfs-->商品描述信息链接
uint startPrice;//竞拍起始价格
uint auctionStartTime;//竞拍开始时间
uint auctionEndTime;//竞拍结束时间
ProductStatus status;
ProductCondition condition;
//竞标信息
uint highestBid; //最高出价, 50,理想价格
address highestBidder; //最高出价人
uint secondHighestBid; //次高价,40
uint totalBids; //所有的竞标数量
}
enum ProductStatus {Open, Sold, Unsold} //竞标中,卖了,没卖出
enum ProductCondition {Used, New} ////新的,旧的
2.存储商品结构
mapping(卖家地址address => mapping(产品id => 产品Product)) stores
mapping(产品id => 卖家地址address) productToOwner
3.添加商品函数
function addProductToStore(string _name, string _category, string _imageLink, string _descLink, uint _startTime, uint _endTime, uint _startPrice, uint condition) public {
productIndex++; //全局唯一
Product memory product = Product({
id : productIndex,
name : _name,
category : _category,
imageLink : _imageLink,
descLink : _descLink,
startPrice : _startPrice,
auctionStartTime : _startTime,
auctionEndTime : _endTime,
status : ProductStatus.Open,
condition : ProductCondition(condition)
highestBid: 0,
highestBidder : 0,
secondHighestBid : 0,
totalBids : 0
});
//把创建好的商品添加到我们的商店结构中
stores[msg.sender][productIndex] = product;
productIdToOwner[productIndex] = msg.sender;
}
}
4.通过商品id获取商品信息
function getProductById(uint _productId) public view returns (uint, string, string, string, string, uint, uint, uint, uint){
//通过id向定义的两个mapping结构中查询并返回
address owner = productIdToOwner[_productId];
Product memory product = stores[owner][_productId];
return (
product.id, product.name, product.category, product.imageLink, product.descLink,
product.auctionStartTime, product.auctionEndTime, product.startPrice, uint(product.status)
);
5.定义竞标结构
struct Bid {
uint productId;//产品id
uint price; //迷惑价格,不是理想价格
bool isRevealed;//是否揭标
address bidder;//竞标人
}
6.在Product中增加存储所有合约自己相关竞标的结构
// 存储所有的竞标人对这个商品的竞标(与stroes类似)
mapping(address => mapping(bytes32 => Bid)) bids;
7.添加竞标函数bid
function bid(uint _productId, uint _idealPrice, string _secret) public payable {
//将传入的理想价格和明文进行keccak256加密成密文
bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
bytes32 bytesHash = keccak256(bytesInfo);
//通过id获取商品信息
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
//每一个竞标必须大于等于起始价格
require(msg.value >= product.startPrice);
//竞标总数+1
product.totalBids++;
//存储竞标人对这个商品的竞标
Bid memory bidLocal = Bid(_productId, msg.value, false, msg.sender);
product.bids[msg.sender][bytesHash] = bidLocal;
}
8.验证竞标
//获取指定的bid信息
function getBidById(uint _productId, uint _idealPrice, string _secret) public view returns (uint, uint, bool, address) {
//通过id获取商品信息
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
//通过参数生成密文
bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
bytes32 bytesHash = keccak256(bytesInfo);
//从竞标mapping取出竞标信息并返回
Bid memory bidLocal = product.bids[msg.sender][bytesHash];
return (bidLocal.productId, bidLocal.price, bidLocal.isRevealed, bidLocal.bidder);
}
9.揭标
//揭标事件,做log日志返回给客户端
event revealEvent(uint productid, bytes32 bidId, uint idealPrice, uint price, uint refund);
function revealBid(uint _productId, uint _idealPrice, string _secret) public {
//通过id取商品信息
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
//生成密文
bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
bytes32 bidId = keccak256(bytesInfo);
//mapping(address => mapping(bytes32 => Bid)) bids;
//一个人可以对同一个商品竞标多次,揭标的时候也要揭标多次, storage类型
//获取当前id的bid信息
Bid storage currBid = product.bids[msg.sender][bidId];
//当前时间要大于竞拍时间,测试的时候可以关闭
require(now > product.auctionStartTime);
//每个标只能揭标一次,判断是否揭过
require(!currBid.isRevealed);
//说明找到了这个标, 找到了正确的标
require(currBid.bidder != address(0));
currBid.isRevealed = true;
//bid中的是迷惑价格,真实价格揭标时传递进来
uint confusePrice = currBid.price;
//退款金额, 程序最后,统一退款
uint refund = 0;
//理想价格
uint idealPrice = _idealPrice;
//迷惑价格小于理想价格
if (confusePrice < idealPrice) {
//路径1:无效交易,退还金额
refund = confusePrice;
} else {
//理想价格大于最高价
if (idealPrice > product.highestBid) {
if (product.highestBidder == address(0)) {
//当前账户是第一个揭标人
//路径2:
product.highestBidder = msg.sender;
product.highestBid = idealPrice;
product.secondHighestBid = product.startPrice;
refund = confusePrice - idealPrice;
} else {
//路径3:不是第一个,但是出价是目前最高的,更新最高竞标人,最高价格,次高价格
product.highestBidder.transfer(product.highestBid);
product.secondHighestBid = product.highestBid;
product.highestBid = idealPrice; //wangwu 40
product.highestBidder = msg.sender; //wangwu
refund = confusePrice - idealPrice; //10
}
} else {
//路径4:价格低于最高价,但是高于次高价
if (idealPrice > product.secondHighestBid) {
//路径4:更新次高价,然后拿回自己的钱
product.secondHighestBid = idealPrice;
refund = confusePrice; //40
} else {
//路径5:路人甲,价格低于次高价,直接退款
refund = confusePrice;
}
}
}
//提交事件
emit revealEvent(_productId, bidId, confusePrice, currBid.price, refund);
//退款
if (refund > 0) {
msg.sender.transfer(refund);
}
}
10.终结函数
//key是产品id,value:是第三方合约
//全局唯一, 用于投票时,找到这个第三方合约。
mapping(uint => address) public productToEscrow;
function finalaizeAuction(uint _productId) public {
//获取商品信息
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
address buyer = product.highestBidder; //买家
address seller = owner;//卖家
address arbiter = msg.sender; //仲裁人
//仲裁人不允许是买家或者卖家
require(arbiter != buyer && arbiter != seller);
//限定仅在揭标之后才可以进行仲裁
//require(now > product.auctionEndTime);
require(product.status == ProductStatus.Open); //Open, Sold, Unsold
//如果竞标了,但是没有揭标,那么也是没有卖出去(自行拓展)
if (product.totalBids == 0) {
product.status = ProductStatus.Unsold;
} else {
product.status = ProductStatus.Sold;
}
//.value()方式进行外部调用时转钱
//类比feed.info.value(10).gas(800)();
//这是构造的时候传钱,constructor加上payable关键字
//address escrow = (new Escrow).value(25)(buyer, seller, arbiter)
address escrow = (new Escrow).value(product.secondHighestBid)(buyer, seller, arbiter);
productToEscrow[_productId] = escrow;
//退还差价 30- 25 = 5 , 30是理想出价,25是次高
buyer.transfer(product.highestBid - product.secondHighestBid);
}
11.三方合约(另起合约)
contract Escrow {
// 属性:
// 1. 买家
address public buyer;
// 2. 卖家
address public seller;
// 3. 仲裁人
address public arbiter;
//构造函数
constructor(address _buyer, address _seller, address _arbiter) public payable {
buyer = _buyer;
seller = _seller;
arbiter = _arbiter;
}
function getBalance() public view returns(uint256) {
return address(this).balance;
}
}
12.在Escrow中实现两个投票函数
// 4. 卖家获得的票数
uint sellerVotesCount;
// 5. 买家获得的票数
uint buyerVotesCount;
// 6. 标记某个地址是否已经投票
mapping(address => bool) addressVotedMap;
// 7. 是否已经完成付款了
bool isSpent = false;
// 方法:
//向卖家投票方法
function giveMoneyToSeller(address caller) callerRestrict(caller) public {
require(!isSpent);
//记录已经投票的状态,如果投过票,就设置为true
//不是mapping中的成员不可以投
require(!addressVotedMap[caller]);
addressVotedMap[caller] = true; //address => bool
//sellerVotesCount++;
if (++sellerVotesCount == 2 ) {
isSpent = true;
//转钱
seller.transfer(address(this).balance);
}
}
//向买家投票方法
function giveMoneyToBuyer(address caller) callerRestrict(caller) public {
require(!isSpent);
require(!addressVotedMap[caller]);
addressVotedMap[caller] = true;
if (++buyerVotesCount == 2) {
isSpent = true;
buyer.transfer(address(this).balance);
}
}
13.添加修饰器
modifier callerRestrict(address caller ) {
require(caller == seller || caller == buyer || caller == arbiter);
_;
}//必须是三方中一员才可以投票
14.获取仲裁信息
function escrowInfo() public view returns(address, address, address, uint, uint, uint256) {
//获取合约余额
uint256 balance = getEscrowBalance();
return (buyer, seller, arbiter, buyerVotesCount, sellerVotesCount, balance);
}
function getEscrowBalance() public view returns(uint256) {
return address(this).balance;
}
15.更新EcommerceStore,对投票合约进行调用
function getEscrowInfo(uint _productId) public view returns (address, address, address, uint, uint, uint256) {
//获取仲裁商品对应的地址
address escrow = productToEscrow[_productId];
Escrow instanceContract = Escrow(escrow);
return instanceContract.escrowInfo();
}
function giveToSeller(uint _productId) public {
address contract1 = productToEscrow[_productId];
Escrow(contract1).giveMoneyToSeller(msg.sender); //把调用人传给Escrow合约
}
function giveToBuyer(uint _productId) public {
Escrow(productToEscrow[_productId]).giveMoneyToBuyer(msg.sender);
}
16.完整的Escrow合约
contract Escrow {
// 属性:
// 1. 买家
address buyer;
// 2. 卖家
address seller;
// 3. 仲裁人
address arbiter;
// 4. 卖家获得的票数
uint sellerVotesCount;
// 5. 买家获得的票数
uint buyerVotesCount;
// 6. 标记某个地址是否已经投票
mapping(address => bool) addressVotedMap;
// 7. 是否已经完成付款了
bool isSpent = false;
constructor(address _buyer, address _seller, address _arbiter) public payable {
buyer = _buyer;
seller = _seller;
arbiter = _arbiter;
}
// 方法:
//向卖家投票方法
function giveMoneyToSeller(address caller) callerRestrict(caller) public {
require(!isSpent);
//记录已经投票的状态,如果投过票,就设置为true
require(!addressVotedMap[caller]);
addressVotedMap[caller] = true; //address => bool
//sellerVotesCount++;
if (++sellerVotesCount == 2 ) {
isSpent = true;
seller.transfer(address(this).balance);
}
}
//向买家投票方法
function giveMoneyToBuyer(address caller) callerRestrict(caller) public {
require(!isSpent);
require(!addressVotedMap[caller]);
addressVotedMap[caller] = true;
if (++buyerVotesCount == 2) {
isSpent = true;
buyer.transfer(address(this).balance);
}
}
function escrowInfo() public view returns(address, address, address, uint, uint, uint256) {
uint256 balance = getEscrowBalance();
return (buyer, seller, arbiter, buyerVotesCount, sellerVotesCount, balance);
}
modifier callerRestrict(address caller ) {
require(caller == seller || caller == buyer || caller == arbiter);
_;
}
function getEscrowBalance() public view returns(uint256) {
return address(this).balance;
}
}
17.完整的EcommerceStore合约
pragma solidity ^0.4.24;
contract EcommerceStore {
//把商品添加到商店的业务分析:
// 1. 定义商品结构
// 2. 每个商品有一个唯一的id,数字,每添加一个商品,id++
// 3. 需要有一个存储所有商品的结构, 通过id可以的到对应的商品
// 4. 添加商品的方法
uint public productIndex; //每一个产品都有自己的id
struct Product {
//基础信息
uint id;
string name;
string category;
string imageLink;
string descLink;
uint startPrice;
uint auctionStartTime;
uint auctionEndTime;
ProductStatus status;
ProductCondition condition;
//竞标信息
uint highestBid; //最高出价, 50,理想价格
address highestBidder; //最高出价人
uint secondHighestBid; //次高价,40
uint totalBids; //所有的竞标数量
// 存储所有的竞标人对这个商品的竞标(与stroes类似)
mapping(address => mapping(bytes32 => Bid)) bids;
}
enum ProductStatus {Open, Sold, Unsold} //竞标中,卖了,没卖出
enum ProductCondition {Used, New} ////新的,旧的
mapping(address => mapping(uint => Product)) stores;
mapping(uint => address) productIdToOwner;
function addProductToStore(string _name, string _category, string _imageLink, string _descLink, uint _startTime, uint _endTime, uint _startPrice, uint condition) public {
productIndex++;
Product memory product = Product({
id : productIndex,
name : _name,
category : _category,
imageLink : _imageLink,
descLink : _descLink,
startPrice : _startPrice,
auctionStartTime : _startTime,
auctionEndTime : _endTime,
status : ProductStatus.Open,
condition : ProductCondition(condition),
//++++++++++++
highestBid: 0,
highestBidder : 0,
secondHighestBid : 0,
totalBids : 0
});
//把创建好的商品添加到我们的商店结构中
stores[msg.sender][productIndex] = product;
productIdToOwner[productIndex] = msg.sender;
}
function getProductById(uint _productId) public view returns (uint, string, string, string, string, uint, uint, uint, uint){
address owner = productIdToOwner[_productId];
Product memory product = stores[owner][_productId];
return (
product.id, product.name, product.category, product.imageLink, product.descLink,
product.auctionStartTime, product.auctionEndTime, product.startPrice, uint(product.status)
);
}
// 竞标的结构:
// 1. 产品ID
// 2. 转账(迷惑)价格,注意,不是理想价格
// 3. 揭标与否
// 4. 竞标人
struct Bid {
uint productId;
uint price;
bool isRevealed;
address bidder;
}
function bid(uint _productId, uint _idealPrice, string _secret) public payable {
bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
bytes32 bytesHash = keccak256(bytesInfo);
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
//每一个竞标必须大于等于起始价格
require(msg.value >= product.startPrice);
product.totalBids++;
Bid memory bidLocal = Bid(_productId, msg.value, false, msg.sender);
product.bids[msg.sender][bytesHash] = bidLocal;
}
//获取指定的bid信息
function getBidById(uint _productId, uint _idealPrice, string _secret) public view returns (uint, uint, bool, address) {
//Product storage product = stores[productIdToOwner[_productId]][_productId];
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
bytes32 bytesHash = keccak256(bytesInfo);
Bid memory bidLocal = product.bids[msg.sender][bytesHash];
return (bidLocal.productId, bidLocal.price, bidLocal.isRevealed, bidLocal.bidder);
}
function getBalance() public view returns (uint){
return address(this).balance;
}
event revealEvent(uint productid, bytes32 bidId, uint idealPrice, uint price, uint refund);
function revealBid(uint _productId, uint _idealPrice, string _secret) public {
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
bytes memory bytesInfo = abi.encodePacked(_idealPrice, _secret);
bytes32 bidId = keccak256(bytesInfo);
//mapping(address => mapping(bytes32 => Bid)) bids;
//一个人可以对同一个商品竞标多次,揭标的时候也要揭标多次, storage类型
Bid storage currBid = product.bids[msg.sender][bidId];
//require(now > product.auctionStartTime);
//每个标只能揭标一次
require(!currBid.isRevealed);
require(currBid.bidder != address(0)); //说明找到了这个标, 找到了正确的标
currBid.isRevealed = true;
//bid中的是迷惑价格,真实价格揭标时传递进来
uint confusePrice = currBid.price;
//退款金额, 程序最后,统一退款
uint refund = 0;
uint idealPrice = _idealPrice;
if (confusePrice < idealPrice) {
//路径1:无效交易
refund = confusePrice;
} else {
if (idealPrice > product.highestBid) {
if (product.highestBidder == address(0)) {
//当前账户是第一个揭标人
//路径2:
product.highestBidder = msg.sender;
product.highestBid = idealPrice;
product.secondHighestBid = product.startPrice;
refund = confusePrice - idealPrice;
} else {
//路径3:不是第一个,但是出价是目前最高的,更新最高竞标人,最高价格,次高价格
product.highestBidder.transfer(product.highestBid);
product.secondHighestBid = product.highestBid;
product.highestBid = idealPrice; //wangwu 40
product.highestBidder = msg.sender; //wangwu
refund = confusePrice - idealPrice; //10
}
} else {
//路径4:价格低于最高价,但是高于次高价
if (idealPrice > product.secondHighestBid) {
//路径4:更新次高价,然后拿回自己的钱
product.secondHighestBid = idealPrice;
refund = confusePrice; //40
} else {
//路径5:路人甲,价格低于次高价,直接退款
refund = confusePrice;
}
}
}
emit revealEvent(_productId, bidId, confusePrice, currBid.price, refund);
if (refund > 0) {
msg.sender.transfer(refund);
}
}
function getHighestBidInfo(uint _productId) public view returns(address, uint, uint, uint) {
address owner = productIdToOwner[_productId];
Product memory product = stores[owner][_productId];
return (product.highestBidder, product.highestBid, product.secondHighestBid, product.totalBids);
}
//key是产品id,value:是第三方合约
//全局唯一, 用于投票时,找到这个第三方合约。
mapping(uint => address) public productToEscrow;
function finalaizeAuction(uint _productId) public {
address owner = productIdToOwner[_productId];
Product storage product = stores[owner][_productId];
address buyer = product.highestBidder; //买家
address seller = owner;//卖家
address arbiter = msg.sender; //仲裁人
//仲裁人不允许是买家或者卖家
require(arbiter != buyer && arbiter != seller);
//限定仅在揭标之后才可以进行仲裁
//require(now > product.auctionEndTime);
require(product.status == ProductStatus.Open); //Open, Sold, Unsold
//如果竞标了,但是没有揭标,那么也是没有卖出去(自行拓展)
if (product.totalBids == 0) {
product.status = ProductStatus.Unsold;
} else {
product.status = ProductStatus.Sold;
}
//.value()方式进行外部调用时转钱
//类比feed.info.value(10).gas(800)();
//这是构造的时候传钱,constructor加上payable关键字
//address escrow = (new Escrow).value(25)(buyer, seller, arbiter)
address escrow = (new Escrow).value(product.secondHighestBid)(buyer, seller, arbiter);
productToEscrow[_productId] = escrow;
//退还差价 30- 25 = 5 , 30是理想出价,25是次高
buyer.transfer(product.highestBid - product.secondHighestBid);
}
function getEscrowInfo(uint _productId) public view returns (address, address, address, uint, uint, uint256) {
address escrow = productToEscrow[_productId];
Escrow instanceContract = Escrow(escrow);
return instanceContract.escrowInfo();
}
function giveToSeller(uint _productId) public {
address contract1 = productToEscrow[_productId];
Escrow(contract1).giveMoneyToSeller(msg.sender); //把调用人传给Escrow合约
}
function giveToBuyer(uint _productId) public {
Escrow(productToEscrow[_productId]).giveMoneyToBuyer(msg.sender);
}
}