這裏再寫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。若要投入實際使用,還要解決不少問題。我想到的不妥當之處有:
- 併發情況下的購物交易,在這個案例中合約由第一個發起購買請求的用戶鎖定,直到交易結束後釋放合約。這無疑造成了時間的浪費,其他用戶必須等他們交易結束才能繼續進行。最簡單的辦法就是設置一個
mapping(address=>State)
,讓每個用戶對應的合約狀態不同。這樣做同時也實現了合約的複用,當然,大家都想得到; - 在這個案例設計中,一種商品(或一個價格)對應一個合約。在商城業務中,商品的種類繁多,用這種模式需要創建大量的合約賬戶。如何編寫一個更全面的合約或劃歸產品分類來優化合約數目,可以好好思考一下。
暫時先寫到這兒,這些問題可以在自己寫類似業務時認真思考一番。