1 場景
在盲拍的應(yīng)用場景中,我們定義如下幾個關(guān)鍵要素:
- 受益人,最終的錢款接收方
- 競拍者胆剧,任意持有合法賬戶的人都可以參與競拍
- 競拍時間旱物,只能在指定時間內(nèi)完成出價
- 明牌時間遥缕,參與者亮出出價
2 邏輯
- 所有參與者持有一個區(qū)塊鏈賬戶
- 發(fā)起人創(chuàng)建盲拍合約,創(chuàng)建時指定受益人宵呛,競拍時間单匣,揭曉時間
- 競拍者在競拍時間結(jié)束前,可以進(jìn)行出價
- 競拍者可以隨時撤回自己的出價宝穗,并回收抵押資金
- 競拍結(jié)束户秤,價高者得
3 完整代碼
源代碼地址 https://solidity.readthedocs.io/en/v0.5.1/solidity-by-example.html
pragma solidity >0.4.23 <0.6.0;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address payable public beneficiary;
uint public biddingEnd;
uint public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
mapping(address => uint) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
constructor(
uint _biddingTime,
uint _revealTime,
address payable _beneficiary
) public {
beneficiary = _beneficiary;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
function bid(bytes32 _blindedBid)
public
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}
function reveal(
uint[] memory _values,
bool[] memory _fake,
bytes32[] memory _secret
)
public
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(_values.length == length);
require(_fake.length == length);
require(_secret.length == length);
uint refund;
for (uint i = 0; i < length; i++) {
Bid storage bidToCheck = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(_values[i], _fake[i], _secret[i]);
if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
continue;
}
refund += bidToCheck.deposit;
if (!fake && bidToCheck.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
bidToCheck.blindedBid = bytes32(0);
}
msg.sender.transfer(refund);
}
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
function withdraw() public {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;
msg.sender.transfer(amount);
}
}
function auctionEnd()
public
onlyAfter(revealEnd)
{
require(!ended);
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
}
4 解析
4.1 數(shù)據(jù)結(jié)構(gòu)
一筆盲拍競價數(shù)據(jù)由兩個關(guān)鍵要素構(gòu)成:加密出價,保證金逮矛。
struct Bid {
bytes32 blindedBid; // 加密出價
uint deposit; // 保證金
}
這個合約中的出現(xiàn)了兩類地址鸡号,受益人賬戶,比普通 address
多了一個 payable
修飾關(guān)鍵字须鼎,這表明這個賬戶能進(jìn)行代幣的相關(guān)操作鲸伴。買家賬戶,只作為顯示使用莉兰,不需要 payable
挑围。
address payable public beneficiary;
address public highestBidder;
幾個關(guān)于競拍時間、流程控制的變量糖荒。
uint public biddingEnd;
uint public revealEnd;
bool public ended;
uint public highestBid;
將賬戶與競價信息和抵押保證金相關(guān)聯(lián)杉辙。
mapping(address => Bid[]) public bids;
mapping(address => uint) pendingReturns;
4.2 構(gòu)造函數(shù)
solidity 的內(nèi)置變量 now
將返回當(dāng)前的unix時間戳(自1970年1月1日以來經(jīng)過的秒數(shù))。在這里捶朵,以秒為單位蜘矢,因此 _biddingTime
, _revealTime
都是從當(dāng)前開始經(jīng)過XXX秒后。
constructor(
uint _biddingTime,
uint _revealTime,
address payable _beneficiary
) public {
beneficiary = _beneficiary;
biddingEnd = now + _biddingTime;
revealEnd = biddingEnd + _revealTime;
}
4.3 修改器
modifier
指示函數(shù)修改器综看。本示例中品腹,這種修改器以嵌入的方式加到被作用函數(shù)上。運行時红碑,_;
部分會用被作用函數(shù)的原有代碼替代舞吭。
modifier onlyBefore(uint _time) { require(now < _time); _; }
modifier onlyAfter(uint _time) { require(now > _time); _; }
4.4 競價函數(shù)
該函數(shù)使用了修改器 onlyBefore
泡垃,函數(shù)執(zhí)行的實際代碼為
require(now > _time);
... // bid函數(shù)代碼
payable
關(guān)鍵字修飾該函數(shù),表明涉及資金操作(msg.value
)羡鸥。其中蔑穴,
_blindedBid
= keccak256(abi.encodePacked(value, fake, secret)).
是加密編碼后的競價信息,value
是出價惧浴,不得小于出價人持有的代幣存和;fake
為 false
時競價才有效;secret
可以視為密鑰衷旅。一個賬戶可以多次出價捐腿,通常,競拍者會多次把 fake
設(shè)置為 true
并且給出一個無效 value
提交柿顶,用以隱藏真正出價茄袖。
function bid(bytes32 _blindedBid)
public payable onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: _blindedBid,
deposit: msg.value
}));
}
4.5 明牌函數(shù)
該函數(shù)限制為在競價結(jié)束后,明牌結(jié)束前執(zhí)行嘁锯。用到了修改器 onlyAfter
绞佩,onlyBefore
。此處競拍者需要傳入自己所有的歷史出價的三要素猪钮,并且以出價先后順序排序。在明牌校驗時胆建,用到了多變量賦值語句:
(x,y,z) = (a,b,c)
明牌的具體邏輯如下烤低。
function reveal(
uint[] memory _values,
bool[] memory _fake,
bytes32[] memory _secret
)
public onlyAfter(biddingEnd) onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(_values.length == length);
require(_fake.length == length);
require(_secret.length == length);
uint refund; // 應(yīng)退資金
for (uint i = 0; i < length; i++) {
// [][]不是二維數(shù)組,第一層是map解出value笆载,第二層是訪問一維數(shù)組
Bid storage bidToCheck = bids[msg.sender][i];
// 多變量賦值語句
(uint value, bool fake, bytes32 secret) =
(_values[i], _fake[i], _secret[i]);
if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
continue;
}
refund += bidToCheck.deposit;
if (!fake && bidToCheck.deposit >= value) {
// 嘗試納入有效競價
if (placeBid(msg.sender, value))
refund -= value;
}
// 避免重復(fù)退款
bidToCheck.blindedBid = bytes32(0);
}
msg.sender.transfer(refund);
}
4.6 出價函數(shù)
該函數(shù)用 internal
關(guān)鍵字修飾扑馁,表示只能合約內(nèi)部調(diào)用。類似于 Java 中的 private
關(guān)鍵字凉驻。入?yún)⑿枰谡{(diào)用者的代碼中進(jìn)行校驗腻要。
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
4.7 退款函數(shù)
用戶可以調(diào)用該函數(shù)以退回資金。此處邏輯遵循 條件(condition)--結(jié)果(effect)--交互(interact)
的標(biāo)準(zhǔn)化過程涝登,防止重復(fù)退款的事件發(fā)生雄家。
function withdraw() public {
uint amount = pendingReturns[msg.sender];
// 條件
if (amount > 0) {
// 先設(shè)置結(jié)果
pendingReturns[msg.sender] = 0;
// 再執(zhí)行交互流程
msg.sender.transfer(amount);
}
}
4.8 結(jié)束函數(shù)
這個函數(shù)出發(fā)了一個 event
,它是以太坊虛擬機(jī)(EVM)日志基礎(chǔ)設(shè)施提供的一個便利接口胀滚。當(dāng)被發(fā)送事件(調(diào)用)時趟济,會觸發(fā)參數(shù)存儲到交易的日志中(一種區(qū)塊鏈上的特殊數(shù)據(jù)結(jié)構(gòu))。這些日志與合約的地址關(guān)聯(lián)咽笼,并記錄到區(qū)塊鏈中顷编。在DAPP的應(yīng)用中,如果監(jiān)聽了某事件剑刑,當(dāng)事件發(fā)生時媳纬,會進(jìn)行回調(diào)。
event AuctionEnded(address winner, uint highestBid);
function auctionEnd()
public onlyAfter(revealEnd)
{
require(!ended);
// 事件觸發(fā)
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
(完)