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. 在这个案例设计中,一种商品(或一个价格)对应一个合约。在商城业务中,商品的种类繁多,用这种模式需要创建大量的合约账户。如何编写一个更全面的合约,或通过划分产品来优化合约数目,可以好好思考一下。例如添加一个mapping(uint=>uint)的数组,其key-value关系是ID=>Price,对商品赋予id,让合约通过id号查找对应的价格,这种写法是否会有别的弊端;
  3. 规则本身的问题,这里合约要求买方支付商品价格2倍的价钱。对于小件商品自然问题不大,但对于大件商品,比如说汽车、股票,也许买方付不起两倍价钱,那这套机制应用场景很受限。

暂时先写到这儿,这些问题可以在自己写类似业务时认真思考一番。

你可能感兴趣的:(智能合约开发)