[solidity] 简单的远程购买合约

2022-08-22  本文已影响0人  朱慢慢
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.7;

/*
此合约是一个类似”网购“的功能,通常我们使用的网购存在一个可信任的第三方中介,当买家收货确认后,卖家才收到钱
但是此合约由于没有第三方中介,所以使用了押金的方式,当双方都有押金在合约中锁定,可有意愿去解决出现的末知问题
由卖家发起网购合约,设定商品的价钱value,然后双方都支付两倍value的价钱作为押金,并锁定。
完成交易收到货后,卖家可以退回3倍value(押金+货款),买家可以退回1倍value(押金)
部署地址 https://rinkeby.etherscan.io/tx/0x641a0f38bf11b377cd7fe926a4ed508769e7d643bf1cb9a4f4268f1757e66558
 */

contract Purchase {

    uint public value; // 约定的商品价钱
    uint public value2; // 约定的商品价钱
    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修饰符:判断是否买家在调用函数
    modifier onlyBuyer() {
        if (msg.sender != buyer) {
            revert OnlyBuyer(); // 不是则回滚状态、向外报错
        }
        _;
    }

    // modifier修饰符:判断是否买家在调用函数
    modifier onlySeller() {
        if(msg.sender != seller) {
            revert OnlySeller(); // 不是则回滚状态、向外报错
        }
        _;
    }

    // modifier修饰符:判断是否在`state`状态
    modifier inState(State state_) {
        if (state != state_) {
            revert InvalidState(); // 不是则回滚状态、向外报错
        }
        _;
    }

    event Aborted(); // 交易结束
    event PurchaseConfirmed(); // 交易确定
    event ItemReceived(); // 已收到货
    event SellerRefunded(); // 卖家退款
    
    /// 需要确定`msg.value`是一个偶数
    /// 因为solidity在除法计数中,小数会直接截断(如 99 / 2 = 49.5 会截断为49)
    /// 使用乘法 * 2去判断传入的value是否偶数
    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;// 更改合约状态为Inactive

        // 我们直接在这里使用转账。它是重入安全的,
        // 因为我们是最后一行调用transfer的,并且已经在之前修改了State状态。
        seller.transfer(address(this).balance);
    }

    /// 以买家身份确定交易
    /// 交易必须包含`2 * value`的以太币(value是constructor方法卖家确定的)
    /// 这些以太币会锁定直到交易完成(confirmReceived函数被调用)
    function confirmPurchase() 
        external
        inState(State.Created)
        condition(msg.value == (2 * value))
        payable
    {
        emit PurchaseConfirmed(); // 告知交易确认
        buyer = payable(msg.sender); // 记录买家
        state = State.Locked; // 更改合约状态为Locked
    }

    /// 确认收货,这将会解锁以太币
    function confirmReceived() 
        external
        onlyBuyer
        inState(State.Locked)
    {
        emit ItemReceived(); // 告知已收货
        // 首先更改状态是很重要的,否则再次调用此函数,会再次发起transfer转账
        state = State.Release;
        // 留下货款,取回押金
        buyer.transfer(value);
    }

    /// 此函数给卖家退回押金和货款
    function refundSeller() 
        external
        onlySeller
        inState(State.Release)
    {
        emit SellerRefunded();
        // 首先更改状态是很重要的,否则再次调用此函数,会再次发起transfer转账
        state = State.Inactive;
        // 取回押金和货款
        seller.transfer(3 * value);
    }
}
上一篇下一篇

猜你喜欢

热点阅读