Solidity 示例 - 安全的远程购买

192 阅读7分钟

5/5 - (1票)

YouTube Video

本文继续Solidity智能合约实例系列,它实现了一个简单但有用的安全远程购买过程。

在这里,我们将走过一个盲目拍卖的例子(docs )。

  • 为了可读性和开发的目的,我们将首先列出整个智能合约的例子,不加注释。
  • 然后,我们将逐一剖析它的部分,分析和解释它。
  • 按照这个路径,我们将获得智能合约的实践经验,以及编码、理解和调试智能合约的良好实践。

智能合约--安全远程购买

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Purchase {
    uint public value;
    address payable public seller;
    address payable public buyer;

    enum State { Created, Locked, Release, Inactive }
    State public state;

    modifier condition(bool condition_) {
        require(condition_);
        _;
    }

    error OnlyBuyer();
    error OnlySeller();
    error InvalidState();
    error ValueNotEven();

    modifier onlyBuyer() {
        if (msg.sender != buyer)
            revert OnlyBuyer();
        _;
    }

    modifier onlySeller() {
        if (msg.sender != seller)
            revert OnlySeller();
        _;
    }

    modifier inState(State state_) {
        if (state != state_)
            revert InvalidState();
        _;
    }

    event Aborted();
    event PurchaseConfirmed();
    event ItemReceived();
    event SellerRefunded();

    constructor() payable {
        seller = payable(msg.sender);
        value = msg.value / 2;
        if ((2 * value) != msg.value)
            revert ValueNotEven();
    }

    function abort()
        external
        onlySeller
        inState(State.Created)
    {
        emit Aborted();
        state = State.Inactive;
        seller.transfer(address(this).balance);
    }

    function confirmPurchase()
        external
        inState(State.Created)
        condition(msg.value == (2 * value))
        payable
    {
        emit PurchaseConfirmed();
        buyer = payable(msg.sender);
        state = State.Locked;
    }

    function confirmReceived()
        external
        onlyBuyer
        inState(State.Locked)
    {
        emit ItemReceived();
        state = State.Release;

        buyer.transfer(value);
    }

    function refundSeller()
        external
        onlySeller
        inState(State.Release)
    {
        emit SellerRefunded();
        state = State.Inactive;

        seller.transfer(3 * value);
    }
}

代码分解和分析

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Purchase {

用于记录价值、卖方和买方地址的状态变量。

    uint public value;
    address payable public seller;
    address payable public buyer;

我们首次引入了enum 数据结构,它象征性地定义了我们合约的四种可能状态。这些状态的内部索引是从0enum_length - 1

    enum State { Created, Locked, Release, Inactive }

变量state记录了当前的状态。我们的合约默认从创建状态开始,可以过渡到锁定、释放和不活动状态。

    State public state;

condition 修饰符保护一个函数在没有事先满足条件的情况下执行,也就是在函数定义旁边给出的表达式。

    modifier condition(bool condition_) {
        require(condition_);
        _;
    }

错误定义与适当的、同名的修饰语一起使用。

    error OnlyBuyer();
    error OnlySeller();
    error InvalidState();
    error ValueNotEven();

当函数调用者不是买方时,onlyBuyer 修饰符防止函数的执行。

    modifier onlyBuyer() {
        if (msg.sender != buyer)
            revert OnlyBuyer();
        _;
    }

onlySeller 修饰符在函数调用者与卖方不同的情况下防止函数的执行。

    modifier onlySeller() {
        if (msg.sender != seller)
            revert OnlySeller();
        _;
    }

inState 修改器在合同状态与所需的state_ 不同时防止函数执行。

    modifier inState(State state_) {
        if (state != state_)
            revert InvalidState();
        _;
    }

合同发出的确认函数abort(),confirmPurchase(),confirmReceived(), 和refundSeller() 被执行的事件。

    event Aborted();
    event PurchaseConfirmed();
    event ItemReceived();
    event SellerRefunded();

构造函数被声明为 [payable](https://blog.finxter.com/what-is-payable-in-solidity/),这意味着合同部署(同义词创建实例化)需要与合同创建事务一起发送一个值(msg.value )。

    constructor() payable {

seller 状态变量被设置为msg.sender 地址,投(转换)为应付。

        seller = payable(msg.sender);

价值状态变量被设置为msg.value 的一半,因为卖方和买方都必须把被出售/购买的物品的两倍价值作为托管协议放入合同。

💡 信息"托管是一种法律安排,由第三方暂时保管金钱或财产,直到某一特定条件得到满足(如购买协议的履行)。"(来源

在我们的案例中,我们的托管是我们的智能合约。

        value = msg.value / 2;

如果价值没有平分,即msg.value ,不是偶数,该功能将终止。由于卖家总是会

        if ((2 * value) != msg.value)
            revert ValueNotEven();
    }

终止远程安全购买,只有在Created 状态下才允许,而且只有卖家才可以。

external 关键字使得该函数只能被其他账户/智能合约调用。从商业角度来看,只有卖方可以调用abort() ,而且只能在买方决定购买之前,即在合约进入Locked 状态之前。

    function abort()
        external
        onlySeller
        inState(State.Created)
    {

发出Aborted 事件,合约状态过渡到非活动状态,余额转给卖方。

        emit Aborted();
        state = State.Inactive;

💡 注意"在0.5.0版本之前,Solidity允许地址成员被合同实例访问,例如,this.balance。现在这被禁止了,必须做一个明确的地址转换:address(this).balance."(docs)。

换句话说,这个关键字让我们访问合同的继承成员。

每个合约都从地址类型继承其成员,并且可以通过address(this).<a member> (文档)访问这些成员。

        seller.transfer(address(this).balance);
    }

confirmPurchase() 函数只有在Created 状态下才可以执行。

它执行的规则是:msg.value 必须是购买值的两倍。

confirmPurchase() 函数也被声明为payable ,这意味着调用者,即买方必须在函数调用时发送货币 (msg.value)。

    function confirmPurchase()
        external
        inState(State.Created)
        condition(msg.value == (2 * value))
        payable
    {

PurchaseConfirmed() 事件被发出,以标记购买确认。

        emit PurchaseConfirmed();

msg.sender 的值被转换为应付,并分配给买方变量。

💡 信息。地址在设计上是不可支付的,以防止意外的支付;这就是为什么我们必须在能够转移支付之前将地址投给一个可支付的。

        buyer = payable(msg.sender);

状态被设置为Locked ,因为卖方和买方签订了合同,也就是说,我们的数字版托管协议。

        state = State.Locked;
    }

confirmReceived() 功能只有在Locked 状态下才可以执行,而且只对买方有效。

由于买方存入了两倍的价值量,而只提取了一个价值量,所以第二个价值量与卖方的存款一起留在合同余额中。

    function confirmReceived()
        external
        onlyBuyer
        inState(State.Locked)
    {

发出ItemReceived() 事件。

        emit ItemReceived();

改变状态为释放。

        state = State.Release;

将定金转移给买方。

        buyer.transfer(value);
    }

refundSeller() 函数只有在Release 状态下才可以执行,而且只对卖方有效。

由于卖方存入了两倍的价值量,并从购买中获得了一个价值量,合同从合同余额中转移了三个价值量给卖方。

    function refundSeller()
        external
        onlySeller
        inState(State.Release)
    {

发出SellerRefunded() 事件。

        emit SellerRefunded();

将状态改为Inactive

        state = State.Inactive;

将两个价值量的存款和一个赚取的价值量转移给卖方。

        seller.transfer(3 * value);
    }
}

我们的安全远程购买的智能合约例子是一个很好的简单例子,展示了如何在以太坊区块链网络上进行购买。

安全远程购买的例子显示了两方,一个是卖方,一个是买方,他们都以向合约余额存款的方式进入交易关系。

每笔存款的金额是购买价值的两倍,这意味着合约余额在最高点,即在Locked ,将持有购买价值的四倍。

定金的高度是为了刺激双方解决任何可能的争端,因为否则,他们的定金将在合同余额中保持锁定,无法使用。

当买方确认他收到他所购买的货物时,合同将过渡到Release 状态,购买价值将被释放给买方。

卖方现在可以用定金提取他赚取的购买价值,合同余额下降到0威,合同过渡到Inactive 状态,安全远程购买结束执行。

契约参数

本节包含运行合约的额外信息。我们应该想到,我们的例子账户可能会随着Remix的每次刷新/重新加载而改变。

我们的合同创建参数是存款 (购买价值的两倍)。我们假设购买价值为5威,使合同创建参数非常简单。

10

合同测试场景

  1. 帐户0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 ,用10魏的定金部署合同,有效地成为卖方。
  2. 帐户0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2 通过调用confirmPurchase() 函数确认购买,并以10魏的定金进入交易,实际上成为买方。
  3. 买方通过调用confirmReceived() 函数来确认收到订单。
  4. 卖方通过调用refundSeller() 函数来结束交易。

总结

我们继续我们的智能合约例子系列,这篇文章实现了一个安全的远程购买。

首先,为了可读性,我们铺设了干净的源代码(没有任何注释)。

其次,我们剖析了代码,对其进行了分析,并解释了每个可能的非琐碎部分。