solidity入門——購物案例

這裏再寫solidity官方文檔第三個案例《Safe Remote Purchase》的分析與註釋。對於這個case,文檔中並無任何說明,一個簡單的標題告訴你這個合約用來安全地購買商品。

背景

這個例子好玩的點也是在於運行機制,如果你要通過合約買東西,例如網購。那麼怎麼樣確保交易順利進行了。案例裏將整個過程劃設定爲3個狀態,分別對應3個事件:

  • 一是abort(),這個是針對商家,當商家認爲需要修改定價時調用來撤回資金,並重新部署合約;
  • 二是confirmPurchase(),這個留給買家確認下單,此時設置合約狀態爲Locked(鎖定狀態),只有在買家收貨後才能解鎖;
  • 三是confirmReceived(),這個發生在買家確認收貨後,由他來打開合約,完成交易轉賬。

那麼,問題就來了,在交易發生爭議時,雙方的權益該如何保障呢?

  • 買家的權益可以通過封鎖合約來保證——當他沒有收到貨物或對貨物不滿意時可以,合約將一直處於locked狀態,商家無法收到轉賬,迫於壓力必須妥善處理;
  • 商家的權益又如何保證呢?合約用了租房情形下很常見的“押一付一”方式,買家在購物時轉賬金額是實際商品價格的2倍,所以如果買家抵賴不做轉賬,他所轉的錢將永遠被所在合約內。

代碼

pragma solidity >=0.4.22 <0.6.0;

contract Purchase {
    uint public value;
    address payable public seller;
    address payable public buyer;
    enum State { Created, Locked, Inactive }
    State public state;

    // `msg.value`必須是偶數,如果是奇數會發生截斷,可以用乘法檢查奇偶
    // 這樣設計的理由如上文所述
    constructor() public payable {
        seller = msg.sender;
        value = msg.value / 2;
        require((2 * value) == msg.value, "Value has to be even.");
    }

	// 修改器1,判斷條件是否成立
    modifier condition(bool _condition) {
        require(_condition);
        _;
    }

	// 修改器2,限定只有買家有權限操作
    modifier onlyBuyer() {
        require(
            msg.sender == buyer,
            "Only buyer can call this."
        );
        _;
    }

	// 修改器3,限定只有商家有權限操作
    modifier onlySeller() {
        require(
            msg.sender == seller,
            "Only seller can call this."
        );
        _;
    }

	// 修改器4,檢查合約狀態
    modifier inState(State _state) {
        require(
            state == _state,
            "Invalid state."
        );
        _;
    }

	// 事件接口,對應輸出到區塊鏈日誌中,可通過web3j回調接口監聽
    event Aborted();
    event PurchaseConfirmed();
    event ItemReceived();

    /// 交易被鎖前賣家可以放棄交易並重新定價
    function abort() public onlySeller inState(State.Created)
    {
        emit Aborted();
        state = State.Inactive;
    // 將合約裏的前轉回賣家
        seller.transfer(address(this).balance);
    }

    /// 買家確認下單購買後,封鎖合約。直到收貨解除封鎖
    function confirmPurchase() public inState(State.Created) 
        condition(msg.value == (2 * value))
        payable
    {
        emit PurchaseConfirmed();
        buyer = msg.sender;
        state = State.Locked;
    }

    /// 在收貨後解鎖賬戶,這將會轉移合約中的以太幣。
    function confirmReceived() public onlyBuyer
        inState(State.Locked)
    {
        emit ItemReceived();
        // It is important to change the state first because
        // otherwise, the contracts called using `send` below
        // can call in again here.
        state = State.Inactive;

        // NOTE: This actually allows both the buyer and the seller to
        // block the refund - the withdraw pattern should be used.
        buyer.transfer(value);
        seller.transfer(address(this).balance);
    }
}

思考

這個合約可以說設計的十分精巧簡單,但畢竟只是demo。若要投入實際使用,還要解決不少問題。我想到的不妥當之處有:

  1. 併發情況下的購物交易,在這個案例中合約由第一個發起購買請求的用戶鎖定,直到交易結束後釋放合約。這無疑造成了時間的浪費,其他用戶必須等他們交易結束才能繼續進行。最簡單的辦法就是設置一個mapping(address=>State),讓每個用戶對應的合約狀態不同。這樣做同時也實現了合約的複用,當然,大家都想得到;
  2. 在這個案例設計中,一種商品(或一個價格)對應一個合約。在商城業務中,商品的種類繁多,用這種模式需要創建大量的合約賬戶。如何編寫一個更全面的合約或劃歸產品分類來優化合約數目,可以好好思考一下。

暫時先寫到這兒,這些問題可以在自己寫類似業務時認真思考一番。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章