Alert Source Discuss
Standards Track: ERC

ERC-5528: 可退款的同质化代币

允许通过托管智能合约退还 EIP-20 代币

Authors StartfundInc (@StartfundInc)
Created 2022-08-16
Requires EIP-20

摘要

本标准是 EIP-20 的扩展。本规范定义了一种具有以下流程的托管服务:

  • 卖方发行代币。
  • 卖方创建一个包含详细托管信息的托管智能合约,例如合约地址、锁定期限、汇率、其他托管成功条件等。
  • 卖方将卖方代币提供给 Escrow Contract(托管合约)。
  • 买方提供买方代币,这些代币在 Escrow Contract 中预先定义。
  • 当托管状态满足成功条件时,卖方可以提取买方代币,买方可以根据汇率提取卖方代币。
  • 如果托管过程失败或处于托管过程中,买方可以提取(或退还)他们提供的代币。

动机

由于加密货币的假名性质,没有自动追索权来收回已经支付的资金。

在传统金融中,受信任的托管服务解决了这个问题。然而,在去中心化加密货币的世界中,有可能在没有第三方仲裁者的情况下实施托管服务。本标准定义了一个智能合约的接口,该接口充当托管服务,具有一个功能,如果托管未完成,代币将发送回原始钱包。

规范

托管过程有两种类型的合约:

  • Payable Contract(可支付合约):卖方和买方使用此代币为 Escrow Contract 提供资金。此合约必须覆盖 EIP-20 接口。
  • Escrow Contract(托管合约):定义托管策略并在一段时间内持有 Payable Contract 的代币。此合约不需要覆盖 EIP-20 接口。

方法

constructor

Escrow Contract 在构造函数实现中,以不可变的方式展示了托管策略的详细信息。

Escrow Contract 必须定义以下策略:

  • 卖方代币合约地址
  • 买方代币合约地址

Escrow Contract 可以定义以下策略:

  • 托管期限
  • 投资者最大(或最小)数量
  • 要提供的代币的最大(或最小)数量
  • 卖方/买方代币的汇率
  • 用户的 KYC 验证

escrowFund

_value 数量的代币提供给地址 _to

Escrow Contract 的情况下:

  • _to 必须是用户地址。
  • msg.sender 必须是 Payable Contract 地址。
  • 必须检查策略验证。

Payable Contract 的情况下:

  • 地址 _to 必须是 Escrow Contract 地址。
  • 必须调用 Escrow Contract 接口的相同功能。参数 _to 必须是 msg.sender 以识别 Escrow Contract 中的用户地址。
function escrowFund(address _to, uint256 _value) public returns (bool)

escrowRefund

从地址 _from 退还 _value 数量的代币。

Escrow Contract 的情况下:

  • _from 必须是用户地址。
  • msg.sender 必须是 Payable Contract 地址。
  • 必须检查策略验证。

Payable Contract 的情况下:

  • 地址 _from 必须是 Escrow Contract 地址。
  • 必须调用 Escrow Contract 接口的相同功能。参数 _from 必须是 msg.sender 以识别 Escrow Contract 中的用户地址。
function escrowRefund(address _from, uint256 _value) public returns (bool)

escrowWithdraw

从托管帐户提取资金。

Escrow Contract 的情况下:

  • 必须检查托管过程是否完成。
  • 必须将剩余的卖方和买方代币余额发送到 msg.sender 的卖方和买方合约钱包。

Payable Contract 的情况下,它是可选的。

function escrowWithdraw() public returns (bool)

接口示例

此示例演示了一个卖方和一个买方以一对一汇率进行的简单交换。

pragma solidity ^0.4.20;

interface IERC5528 {

    function escrowFund(address _to, uint256 _value) public returns (bool);

    function escrowRefund(address _from, uint256 _value) public returns (bool);

    function escrowWithdraw() public returns (bool);

}

contract PayableContract is IERC5528, IERC20 {
    /*
      General ERC20 implementations
      一般的 ERC20 实现
    */

    function _transfer(address from, address to, uint256 amount) internal {
        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        _balances[from] = fromBalance - amount;
        _balances[to] += amount;
    }

    function transfer(address to, uint256 amount) public returns (bool) {
        address owner = msg.sender;
        _transfer(owner, to, amount);
        return true;
    }

    function escrowFund(address _to, uint256 _value) public returns (bool){
        bool res = IERC5528(to).escrowFund(msg.sender, amount);
        require(res, "Fund Failed");
        _transfer(msg.sender, to, amount);
        return true;
    }

    function escrowRefund(address _from, uint256 _value) public returns (bool){
        bool res = IERC5528(_from).escrowRefund(msg.sender, _value);
        require(res, "Refund Failed");
        _transfer(_from, msg.sender, _value);
        return true;
    }
}

contract EscrowContract is IERC5528 {

    enum State { Inited, Running, Success, Closed }
    struct BalanceData {
        address addr;
        uint256 amount;
    }

    address _addrSeller;
    address _addrBuyer;
    BalanceData _fundSeller;
    BalanceData _fundBuyer;
    EscrowStatus _status;

    constructor(address sellerContract, address buyerContract){
        _addrSeller = sellerContract;
        _addrBuyer = buyerContract;
        _status = State.Inited;
    }

    function escrowFund(address _to, uint256 _value) public returns (bool){
        if(msg.sender == _addrSeller){
            require(_status.state == State.Running, "must be running state");
            _fundSeller.addr = _to;
            _fundSeller.amount = _value;
            _status = State.Success;
        }else if(msg.sender == _addrBuyer){
            require(_status.state == State.Inited, "must be init state");
            _fundBuyer.addr = _to;
            _fundBuyer.amount = _value;
            _status = State.Running;
        }else{
            require(false, "Invalid to address");
        }
        return true;
    }

    function escrowRefund(address _from, uint256 amount) public returns (bool){
        require(_status.state == State.Running, "refund is only available on running state");
        require(msg.sender == _addrBuyer, "invalid caller for refund");
        require(_fundBuyer.addr == _from, "only buyer can refund");
        require(_fundBuyer.amount >= amount, "buyer fund is not enough to refund");
        _fundBuyer.amount = _fundBuyer.amount - amount
        return true;
    }

    function escrowWithdraw() public returns (bool){
        require(_status.state == State.Success, "withdraw is only available on success state");
        uint256 common = MIN(_fundBuyer.amount, _fundSeller.amount);

        if(common > 0){
            _fundBuyer.amount = _fundBuyer.amount - common;
            _fundSeller.amount = _fundSeller.amount - common;

            // Exchange
            IERC5528(_addrSeller).transfer(_fundBuyer.addr, common);
            IERC5528(_addrBuyer).transfer(_fundSeller.addr, common);

            // send back the remaining balances
            // 发送回剩余的余额
            if(_fundBuyer.amount > 0){
                IERC5528(_addrBuyer).transfer(_fundBuyer.addr, _fundBuyer.amount);
            }
            if(_fundSeller.amount > 0){
                IERC5528(_addrSeller).transfer(_fundSeller.addr, _fundSeller.amount);
            }
        }

        _status = State.Closed;
    }

}

理由

这些接口涵盖了托管操作的可退款问题。

建议的 3 个函数(escrowFundescrowRefundescrowWithdraw)基于 EIP-20 中的 transfer 函数。

escrowFund 将代币发送到 Escrow Contract。 如果策略不符合,Escrow Contract 可以在托管过程中持有合约或拒绝代币。

escrowRefund 可以在托管过程中或托管过程失败时调用。

escrowWithdraw 允许用户(卖方和买方)从托管帐户转移代币。 当托管过程完成时,卖方可以获得买方的代币,买方可以获得卖方的代币。

向后兼容性

实现此 EIP 的 Payable ContractEIP-20 规范完全向后兼容。

测试用例

Truffle 单元测试示例

此测试用例演示了交换卖方/买方代币的以下条件。

  • 汇率是一对一的。
  • 如果买家的数量达到 2 个,则托管过程将终止(成功)。
  • 否则(尚未满足成功条件),买方可以退还(或提取)他们提供的代币。

安全注意事项

由于 Escrow Contract 控制卖方和买方的权利,Escrow Contract 中的缺陷将直接导致意外行为和潜在的资金损失。

版权

CC0 下放弃版权和相关权利。

Citation

Please cite this document as:

StartfundInc (@StartfundInc), "ERC-5528: 可退款的同质化代币," Ethereum Improvement Proposals, no. 5528, August 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5528.