繼2019年業界首次提出NFT圖片存儲解決方案(被Uniswap、CryptoPunks等項目采用)並向以太坊官方提交其接口標準EIP-2569之後,道易程團隊又向以太坊官方提交了一個新的通證標準,即《EIP-3712:多種批量同質化通證標準》(英文)。

專業討論:

Pull Request

Ethereum Magicians

歡迎點擊鏈接參與提案討論!

以下是該提案的中英文混合版(請註意這是初始版本,後續的英文修訂版請通過鏈接看原文):

EIP: 3712

Title: 多種批量同質化通證標準

(title: Standard for Multiple Types of Fungible-Tokens)

Author: Zhao Li(@1999321), Derek Zhou (@zhous), Yuefei Tan(@whtyfhas)

discussions-to:https://ethereum-magicians.org/t/eip-3712-multiple-fungible-token-standard/7005

status: Draft

type: Standards Track

category: ERC

created: 2021-08-01

Simple Summary

多重同質化通證標準是允許一個合約同時管理多個同質化通證的標準。

A standard that manages multiple types of fungible tokens in a single contract.

Abstract

多重同質化通證標準彌補了ERC20和ERC1155的不足之處。在交易多個同質化通證時gas消耗減少,支持多個接收方對多個通證的交易transferBatchMul與及多個發送方、多個接收方、多個通證一一對應的交易transferFromBatchMul。在授權方面分兩種授權:單一同質化通證的數量授權approve(uint256 id,address spender, uint256 amount)、全局授權approve(address spender,bool _status)

This standard covers some situations that the ERC-20 standard and ERC-1155 standard can hardly handle. It allows multiple types of fungible tokens to be sent to multiple receiving addresses via our proposed interface transferBatchMul and allows multip le types of fungible tokens to be sent from multiple sending addresses to multiple receiving addresses via our proposed interface transferFromBatchMul. This standard greatl y reduces the gas consumption in transactions of multiple types of fungible tokens. In addition the standard allows both setting of an approval for a single token via an interface approve(uint256 id,address spender, uint256 amount) and setting of approvals uniformly for all types of tokens via an interface approve(address spender,bool _status).

Motivation

ERC20是單一同質化通證標準,當交易涉及多個同質化通證時候,需要加載數量等同的合約。ERC1155把同質性同質化通證和非同質化通證結合在一起,在授權方面缺乏數量方面的授權,對於某一個id的授權與及多個地址同時授權。在交易方面缺乏多個地址對多個通證的轉賬。本提案彌補ERC20和ERC1155的不足之處,使得其適合多同質化通證進行授權與交易等應用場景。這也就是說本通證標準能夠通過一個合約同時管理多個同質化通證,能夠讓多個發送方對多個接收方的多個通證的交易一次性完成,因此未來dApp數量越多,本標準就越能節省它們所消耗的內存和gas等資源、越能提升合約的綜合交互效率——很顯然,如果我們能使用一個基於該標準的智能合約為整個行業提供通證發行的通用的無需許可的解決方案,那將給區塊鏈的發展帶來很大的啟迪,同時對於凸顯區塊鏈的效率也將起到非常好的示範作用。

The ERC-20 token standard defines a single fungible token’s attributes and interfaces. When interacting with multiple types of fungible tokens, multiple token contracts each of which defines a single token need to be developed and deployed. The ERC-1155 token standard combines both fungible tokens and non-fungible tokens but lacks both an interface to set an approval for a spender for an id specified type of fungible token and an interface to set an approval for multiple spenders for an id specified type of fungible token. In addition both the ERC-20 and the ERC-1155 token standards lack interfaces to transact from multiple sending addresses to multiple receiving addresses. This proposed standard covers these use cases and allows multiple types of fungible tokens to be transacted from multiple sending addresses to multiple receiving addresses.   

Specification

提案的接口:

Interfaces:

pragma solidity ^0.8.0;
pragma abicoder v2;


interface IEIP {
    
    function name(uint256 id) external view returns (string memory);
    
    function symbol(uint256 id) external view returns (string memory);
    
    function decimals(uint256 id) external view returns (uint8);
    
    function totalSupply(uint256 id) external view returns (uint256);


    function balanceOf(uint256 id,address account) external view returns (uint256);


    function transfer(uint256 id,address recipient, uint256 amount) external returns (bool);
    
    function transferBatch(uint256[] memory ids,address recipient,uint256[] memory amounts)external returns(bool);
    
    function transferBatchMul(uint256[] memory ids,address[]memory recipient,uint256[] memory amounts)external returns(bool);


    function allowance(uint256 id,address owner, address spender) external view returns (uint256);


    function approve(uint256 id,address spender, uint256 amount) external returns (bool);
    
    function approve(address spender,bool status) external returns(bool);
    
    function transferFromBatch(uint256[] memory ids,address sender,address recipient,uint256[] memory amounts)external returns(bool);
    
    function transferFromBatchMul(uint256[] memory ids,address[]memory sender,address[]memory recipient,uint256[] memory amounts)external returns(bool);


    function transferFrom(uint256 id,address sender, address recipient, uint256 amount) external returns (bool);


    event Transfer(uint256 id,address indexed from, address indexed to, uint256 value);


    event Approval(uint256 id,address indexed owner, address indexed spender, uint256 value);
    
    event TransferBatch(uint256[] ids,address indexed from,address indexed to,uint256[] value);
    
    event TransferBatchNoIndexed(uint256[] ids,address[] from,address[] to,uint256[] value);
    
    event ApproveBatch(uint256[] ids,address indexed from,address indexed to,uint256[] value);
    
    event ApproveBatchNoIndexed(uint256[] ids,address[] from,address to,uint256[] value);
}

 

totalSupply,balanceOf,transfer,allowance,transferFrom,approve(uint256 id,address spender, uint256 amount)這幾個函數在ERC20的標準基礎上增加了第一個參數id來表明同質化通證。

Each of the interfaces totalSupply, balanceOf, transfer, allowance, transferFrom, approve(uint256 id,address spender, uint256 amount) introduces a new parameter id in the ERC-20's corresponding interface's parameter list to specify a fungible token's type. 

approve(address spender,bool status)納用ERC1155的全局授權,方便授權於平台方。

approve(address spender,bool status) is similar to ERC-1155's interface. I t allows an approval to be set uniformly for all types of tokens.

transferBatch,transferFromBatch去掉了ERC1155的bytes memory data這個參數,實現捆綁式的轉賬。

transferBatch and transferFromBatch remove the parameter bytes memory data that the ERC-1155 standard uses and implement batch transactions.

transferBatchMul實現了多個接收方對應多種同質化通證的轉賬。

transferBatchMul implements transactions of multiple types of fungible tokens to multiple receiving addresses.

transferFromBatchMul實現了多個發送方、多個接收方、多個同質化通證一一對應的授權轉賬。

transferFromBatchMul implements transactions of multiple types of fungible tokens from multiple sending addresses to multiple receiving addresses.

Rationale

ERC20用地址來標識每一個同質化通證,ERC1155使用id來標識通證,ERC1155相對於ERC20來說,在多個通證轉賬的過程中節約了gas的費用。本提案使用id來標識每一個同質化通證,使得在轉賬消耗gas方面優於ERC20,因為可以實現打包交易transferBatch,transferFromBatch。由於引入了id,且每一個id都在同一個合約裏面管理,所以基於同質化通證的性質,增加了數量授權approve(uint256 id,address spender, uint256 amount)。對於平台方而言,假定其合約可信,如果通過數量授權,那麽需要授權的次數,和需要授權的通證的數量一致,為提高效率故而引入全局狀態授權approve(address spender,bool _status),使得平台方可以一次性獲得全部授權。同時在一些場景之下,考慮到一個發送方、多個接收方、多個通證的情況與及多個發送方、多個接收方、多個通證的情況分別添加了transferBatchMultransferFromBatchMul,擴大本提案可支持的交易類型。

The ERC-20 token standard defines a single fungible token. The ERC-1155 token standard defines multiple types of tokens and specifies each token type by using a parameter id. By using this parameter id, the ERC-1155 token standard greatly reduces the gas consumption in transactions of multiple types of tokens, compared with the ERC-20 token standard. Therefore our proposed standard uses a parameter id as well to specify a token type in order to reduce the gas consumption for  transactions of multiple types of tokens in one call operation via transferBatch or transferFromBatch. In our proposed st andard, each type of fungible token is specified by an id, therefore we add an interface approve(uint256 id,address spender, uint256 amount) for applications to set an approval for the id specified type of fungible token. In order to set approvals for multiple types of fungible tokens in minimum operations we introduce a function approve(address spender,bool _status)  to set approvals uniformly for all types of fungible tokens in one call operation. To handle a transaction of multiple types of fungible tokens from one sending address to multiple receiving addresses we introduce an interface transferBatchMul . To handle a transaction of multiple types of fungible tokens from multiple sending addresses to multiple receiving addresses we introduce an interface transferFromBatchMul .     

Backwards Compatibility

該標準嚴格按照規格的接口進行擴展,允許舊版標準可以在新版標準的合約上順利調用。

Our proposed standard strictly adheres to the EIP rules and specifications, therefore our standard is backwork compatible with existing EIPs. 

Reference Implementation

Example implementation of a signing contract:

pragma solidity ^0.8.0;
pragma abicoder v2;
import "./eip.sol";


struct ERC20Info{
    uint32 id;
    address manager;
    string name;
    string symbol;
    uint256 totalSupply;
    uint8 decimals;
}


contract ERC20s is IEIP{
    struct ERC20{
        uint32 id;
        address manager;
        string name;
        string symbol;
        uint256 totalSupply;
        uint8 decimals;
        mapping(address => uint256) balances;
        mapping(address => mapping(address => uint256)) allowances;
    }
    mapping(uint256 => ERC20) private allERC20;
    mapping(address => mapping(address => bool)) public approveAll;
    uint32 public nextId = 1;
    
    modifier idInUse(uint256 id) {
        require(bytes(allERC20[id].name).length != 0 || id == 0,"ERC20s:id not in use");
        _;
    }
    // function supportsInterface(bytes4 interfaceId) public view virtual  returns (bool) {
    //     return interfaceId == type(IERC20s).interfaceId
    //         || super.supportsInterface(interfaceId);
    // }
    
    function name(uint256 id) external override view idInUse(id) returns(string memory){
        return allERC20[id].name;
    }
    
    function symbol(uint256 id) external override view idInUse(id) returns(string memory){
        return allERC20[id].symbol;
    }
    
    function decimals(uint256 id) external override view idInUse(id) returns(uint8){
        return allERC20[id].decimals;
    }
    
    function totalSupply(uint256 id) external override view idInUse(id) returns (uint256){
        return allERC20[id].totalSupply;
    }


    function balanceOf(uint256 id,address account) external override view idInUse(id) returns (uint256){
        return allERC20[id].balances[account];
    }


    function transfer(uint256 id,address recipient, uint256 amount) external override idInUse(id) returns (bool){
        _transfer(id,msg.sender,recipient,amount);
        return true;
    }
    
    function transferBatch(uint256[] memory ids,address recipient,uint256[] memory amounts)external override returns(bool){
        _transferBatch(ids,msg.sender,recipient,amounts);
        return true;
    }
    
    function transferBatchMul(uint256[] memory ids,address[]memory recipient,uint256[] memory amounts)external override returns(bool){
        _transferBatchMul(ids,createMsg(msg.sender,ids.length),recipient,amounts);
        return true;
    }


    function allowance(uint256 id,address owner, address spender) external view override returns (uint256){
        return allERC20[id].allowances[owner][spender];
    }


    function approve(uint256 id,address spender, uint256 amount) external override idInUse(id) returns (bool){
        _approve(id,msg.sender,spender,amount);
        return true;
    }
    
    function approve(address spender,bool _status) external override returns(bool){
        approveAll[msg.sender][spender] = _status;
        return true;
    }


    function increaseAllowance(uint256 id,address spender, uint256 addedValue) external idInUse(id) {
        _approve(id,msg.sender,spender,allERC20[id].allowances[msg.sender][spender] + addedValue);
    }
    
    function decreaseAllowance(uint256 id,address spender, uint256 subtractedValue) external idInUse(id) {
        require(allERC20[id].allowances[msg.sender][spender] > subtractedValue,"not enough");
        _approve(id,msg.sender, spender, allERC20[id].allowances[msg.sender][spender] - subtractedValue);
    }
    
    function  transferFrom(uint256 id,address sender, address recipient, uint256 amount) external override idInUse(id) returns(bool) {
        if(approveAll[sender][msg.sender]){
            _transfer(id,sender,recipient,amount);
        }
        else{
            require(allERC20[id].allowances[sender][msg.sender] >= amount,"ERC20:approve not enough");
            _transfer(id,sender,recipient,amount);
            _approve(id,sender,msg.sender,allERC20[id].allowances[sender][msg.sender] - amount);
        }
        return true;
    }
    
    function transferFromBatch(uint256[] memory ids,address sender,address recipient,uint256[] memory amounts)external override returns(bool){
        if(approveAll[sender][msg.sender]){
            _transferBatch(ids,sender,recipient,amounts);
        }
        else{
            for(uint256 i = 0;i < ids.length;i++){
                require(allERC20[ids[i]].allowances[sender][msg.sender] >= amounts[i],"ERC20:approve not enough");
            }
            _transferBatch(ids,sender,recipient,amounts);
            _approveBatch(ids,sender,msg.sender,amounts);
        }
        return true;
    }
    
    function transferFromBatchMul(uint256[] memory ids,address[]memory sender,address[]memory recipient,uint256[] memory amounts)external override returns(bool){
        bool _status = true;
        for(uint256 i = 0;i < ids.length;i++){
            if(!approveAll[sender[i]][msg.sender])
                _status = false;
        }
        if(_status){
            _transferBatchMul(ids,sender,recipient,amounts);
        }
        else{
            for(uint256 i = 0;i < ids.length;i++){
                require(allERC20[ids[i]].allowances[sender[i]][msg.sender] >= amounts[i],"ERC20:approve not enough");
            }
            _transferBatchMul(ids,sender,recipient,amounts);
            _approveBatch(ids,sender,msg.sender,amounts);
        }
        return true;
    }
    
    function _transfer(uint256 id,address sender, address recipient, uint256 amount) internal virtual {
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        
        allERC20[id].balances[sender] = allERC20[id].balances[sender] - amount;
        allERC20[id].balances[recipient] = allERC20[id].balances[recipient] + amount;
        emit Transfer(id,sender, recipient, amount);
    }
    
    function _transferBatch(uint256[] memory ids,address sender,address recipient,uint256[] memory amounts) internal virtual{
        require(sender != address(0), "ERC20: transfer from the zero address");
        require(recipient != address(0), "ERC20: transfer to the zero address");
        
        require(ids.length == amounts.length,"ERC20s: length not equal");
        for(uint256 i = 0;i < ids.length;i++){
            if(amounts[i] == 0){
                continue;
            }
            require(bytes(allERC20[ids[i]].name).length != 0 || ids[i] == 0,"ERC20s: ERC20 not in use");
            allERC20[ids[i]].balances[sender] = allERC20[ids[i]].balances[sender] - amounts[i];
            allERC20[ids[i]].balances[recipient] = allERC20[ids[i]].balances[recipient] + amounts[i];
        }
        emit TransferBatch(ids,sender,recipient,amounts);
    }
    
    function _transferBatchMul(uint256[] memory ids,address[]memory sender,address[]memory recipient,uint256[] memory amounts) internal virtual{
        
        require(ids.length == amounts.length && sender.length == recipient.length && sender.length == amounts.length,"ERC20s: length not equal");
        for(uint256 i = 0;i < ids.length;i++){
            require(sender[i] != address(0), "ERC20: transfer from the zero address");
            require(recipient[i] != address(0), "ERC20: transfer to the zero address");
            require(bytes(allERC20[ids[i]].name).length != 0 || ids[i] == 0,"ERC20s: ERC20 not in use");
            if(amounts[i] == 0){
                continue;
            }
            allERC20[ids[i]].balances[sender[i]] = allERC20[ids[i]].balances[sender[i]] - amounts[i];
            allERC20[ids[i]].balances[recipient[i]] = allERC20[ids[i]].balances[recipient[i]] + amounts[i];
        }
        emit TransferBatchNoIndexed(ids,sender,recipient,amounts);
    }
    
    function _approve(uint256 id,address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");


        allERC20[id].allowances[owner][spender] = amount;
        emit Approval(id,owner, spender, amount);
    }
    
    function _approveBatch(uint256[] memory ids,address owner,address spender,uint256[] memory amounts)internal virtual{
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        
        require(ids.length == amounts.length,"ERC20s: length not equal");
        for(uint256 i = 0;i < ids.length;i++){
            require(bytes(allERC20[ids[i]].name).length != 0 || ids[i] == 0,"ERC20s: ERC20 not in use");
            if(amounts[i] == 0){
                continue;
            }
            allERC20[ids[i]].allowances[owner][spender] = allERC20[ids[i]].allowances[owner][spender] - amounts[i];
            amounts[i] = allERC20[ids[i]].allowances[owner][spender];
        }
        
        emit ApproveBatch(ids,owner,spender,amounts);
    }
    
    function _approveBatch(uint256[] memory ids,address[] memory owner,address spender,uint256[] memory amounts)internal virtual{
        require(ids.length == amounts.length  && owner.length == amounts.length,"ERC20s: length not equal");
        
        for(uint256 i = 0;i < ids.length;i++){
            require(bytes(allERC20[ids[i]].name).length != 0 || ids[i] == 0,"ERC20s: ERC20 not in use");
            if(amounts[i] == 0){
                continue;
            }
            allERC20[ids[i]].allowances[owner[i]][spender] = allERC20[ids[i]].allowances[owner[i]][spender] - amounts[i];
            amounts[i] = allERC20[ids[i]].allowances[owner[i]][spender];
        }
        
        emit ApproveBatchNoIndexed(ids,owner,spender,amounts);
    }
    
    function createMsg(address _msg,uint256 len) internal view returns(address[] memory data){
        data = new address[](len);
        for(uint256 i = 0;i < len;i++){
            data[i] = _msg;
        }
    }
    
    function _createERC20(uint256 id,string memory name,string memory symbol,uint8 _decimals) internal {
        require(bytes(allERC20[id].name).length == 0,"ERC20s: ERC20 in use");
        allERC20[id].name = name;
        allERC20[id].symbol = symbol;
        allERC20[id].decimals = _decimals;
    }
}

Copyright

Copyright and related rights waived via CC0.