Solidity 智能合約實例分析——盲拍

1 場景

在盲拍的應(yīng)用場景中,我們定義如下幾個關(guān)鍵要素:

  • 受益人,最終的錢款接收方
  • 競拍者胆剧,任意持有合法賬戶的人都可以參與競拍
  • 競拍時間旱物,只能在指定時間內(nèi)完成出價
  • 明牌時間遥缕,參與者亮出出價
bid.jpg

2 邏輯

  1. 所有參與者持有一個區(qū)塊鏈賬戶
  2. 發(fā)起人創(chuàng)建盲拍合約,創(chuàng)建時指定受益人宵呛,競拍時間单匣,揭曉時間
  3. 競拍者在競拍時間結(jié)束前,可以進(jìn)行出價
  4. 競拍者可以隨時撤回自己的出價宝穗,并回收抵押資金
  5. 競拍結(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 是出價惧浴,不得小于出價人持有的代幣存和;fakefalse 時競價才有效;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)

明牌的具體邏輯如下烤低。

auction_flow.jpg
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);
}

(完)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市钮惠,隨后出現(xiàn)的幾起案子茅糜,更是在濱河造成了極大的恐慌,老刑警劉巖萌腿,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件限匣,死亡現(xiàn)場離奇詭異,居然都是意外死亡毁菱,警方通過查閱死者的電腦和手機(jī)米死,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來贮庞,“玉大人峦筒,你說我怎么就攤上這事〈吧鳎” “怎么了物喷?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長遮斥。 經(jīng)常有香客問我峦失,道長,這世上最難降的妖魔是什么术吗? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任尉辑,我火速辦了婚禮,結(jié)果婚禮上较屿,老公的妹妹穿的比我還像新娘隧魄。我一直安慰自己,他們只是感情好隘蝎,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布购啄。 她就那樣靜靜地躺著,像睡著了一般嘱么。 火紅的嫁衣襯著肌膚如雪狮含。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天曼振,我揣著相機(jī)與錄音辉川,去河邊找鬼。 笑死拴测,一個胖子當(dāng)著我的面吹牛乓旗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播集索,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屿愚,長吁一口氣:“原來是場噩夢啊……” “哼汇跨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妆距,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤穷遂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后娱据,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚪黑,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年中剩,在試婚紗的時候發(fā)現(xiàn)自己被綠了忌穿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡结啼,死狀恐怖掠剑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情郊愧,我是刑警寧澤朴译,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站属铁,受9級特大地震影響眠寿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜焦蘑,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一澜公、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喇肋,春花似錦、人聲如沸迹辐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽明吩。三九已至间学,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間印荔,已是汗流浹背低葫。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留仍律,地道東北人嘿悬。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像水泉,于是被迫代替她去往敵國和親善涨。 傳聞我的和親對象是個殘疾皇子窒盐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容