拍得到智能合約流程
先把邏輯捋順再看代碼
競标規則
- 僅可在競标時間段内競标,競标結束後⽆法再次競标,競标⻚⾯關閉
- 每個⼈都可以參與競标。(賣家⾃⼰也可以參與競标)
- 每個⼈可以多次對同⼀産品競标。(我可以看到别⼈⽐我⾼的時候,再次出價;可以使⽤戰略, 多次出價迷惑敵⼈)
-
流程:
每個⼈競标時輸⼊兩個數字和⼀個密碼:
迷惑價格: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);
}
}