# EIP学习-ERC3525（半均质化通证标准）

By [rbtree](https://paragraph.com/@rbtree) · 2022-09-13

---

1 ERC20，ERC721，ERC1155
----------------------

### 1.1 ERC20

同质化代币，我们可以看一下它转移代币的接口，可以看到只需要给定三个变量：from（支付方地址）、to（接收方地址）、value（转移数额）。

    function transfer(address _to, uint256 _value) public returns (bool success)
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
    

在具体的实现里，我们需要一个map来存储每个地址拥有多少token

    contract ERC20 is Context, IERC20, IERC20Metadata {
        mapping(address => uint256) private _balances;
        ......
    }
    

关于ERC20，我认为还有一个属性值得一提——decimals

    function decimals() public view returns (uint8)
    

这是一个可选接口，不过大部分ERC20代币都选择实现它。这个数字实际上规定了token的最小分割单位。例如decimals=18，那么该代币的最小单位就是0.000000000000000001.

这是一个大家很少会注意到的属性，不过我觉得它很重要，这让ERC20代币近乎可以做到无限细分，赋予了ERC20代币非常大的流动性。ERC721和ERC1155都不具备这个属性，因此它们的最小分割单位就是1.

### 1.2 ERC721

非同质化代币，同样看一下转移代币的接口，可以看到和ERC20代币相比，value被替换成了tokenId。value不再需要，因为ERC721代币只能一个一个转。需要一个tokenId，因为不同tokenId的代币是不一样的。

    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    

在具体的实现里，我们需要两个map，一个是和ERC20类似的balance，记录每个地址拥有多少数量的代币；此外还需要反过来记录每个tokenID被哪个地址拥有。

    contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
        // Mapping from token ID to owner address
        mapping(uint256 => address) private _owners;
    
        // Mapping owner address to token count
        mapping(address => uint256) private _balances;
        ......
    }
    

ERC721和ERC20有2个显著的不同点。第一，非同质化，不同ID的token是不一样的；第二，token不可分割，最小分割单位就是1，不能转0.5个token给别人。这让ERC721代币流动性远不如ERC20.

### 1.3 ERC1155

同时支持同质化和非同质化的代币。

它是对ERC721的升级，在ERC721中，每个tokenId只可以有一个token，而ERC1155的每个tokenId却可以有多个token。

对于同一个ERC1155下不同tokenID的token，它们是非同质化不可相互替代的；对于相同tokenID的token，它们是同质化可替代的。

可以看一个opeasea上ERC1155的例子

[https://opensea.io/collection/noox-badge](https://opensea.io/collection/noox-badge)

游戏装备、徽章是比较适合采用这种模式的。需要区分不同类型的装备（不同类型对应不同的tokenID），但是每个人拥有的相同类型的装备是不需要区分的（对应相同tokenID下多个同质化token）。

其实，对ERC1155而言，如果每个tokenID下只有一个token，那么它就相当于变成了ERC721。

不过，如果只有一个tokenID，这个tokenID下有很多token，那么它可以相当于ERC20吗？并不能，因为它没有decimals属性，不支持转移非整数个token，它依然不会具备ERC20那样的流动性。

我们还是可以看一下它的代币转移接口。有一个data参数，这个参数是用来作为合约账户接受代币时回调用的，我们不必关注。可以看到除了from和to地址参数外，它同时具备ERC20的value参数和ERC721的id参数。

    function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;
    

在具体的实现里，我们需要一个“二维的”map。\_balances\[id\]\[addr\]表示账户addr拥有的tokenID为id的token数目。

    contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
        // Mapping from token ID to account balances
        mapping(uint256 => mapping(address => uint256)) private _balances;
        ......
    }
    

2 ERC3525
---------

刚才说ERC1155是对ERC721的改进，它允许每个tokenID拥有多个实例。ERC3525是对ERC721的另一种改进，如果要实现ERC3525，必须实现所有ERC721接口，因此上面所说的ERC721的代币转移方法在ERC3525上依然是适用的。这个方法应该会把from账户拥有的所有tokenId为特定值的token转移给to账户。

    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
    

### 2.1 value-支持非整数单位

和ERC1155一样，ERC3525也允许同一个tokenId之下有多个实例。但不同的是，ERC3525还具有一个叫`valueDecimals()`的属性，类似于ERC20的decimals()

“Token also contains a `value`property, representing the quantitative nature of the token. The meaning of the ‘value’ property is quite like that of the ‘balance’ property of an [EIP-20](https://eips.ethereum.org/EIPS/eip-20) token. ”

    /**
         * @notice Get the number of decimals the token uses for value - e.g. 6, means the user
         *  representation of the value of a token can be calculated by dividing it by 1,000,000.
    */
    function valueDecimals() external view returns (uint8);
    

这意味着每个tokenID之下的token不但是同质化的，并且是可以细分为非整数单位的。举个例子，我拥有1个单位的tokenID为10的token，那么我可以给你转0.5个单位。这是ERC1155做不到的，ERC1155只能转整数个单位。

所以ERC3525里相同tokenID的代币，就相当于ERC20代币。

下面是ERC3525的其中一个代币转移方法，咋一看和ERC1155接近（有tokenId和value），不过因为valueDecimals的存在，使得这里value的含义截然不同。

    function transferFrom(
            uint256 _fromTokenId,
            address _to,
            uint256 _value
        ) external payable returns (uint256);
    

### 2.2 slot-支持不同id但性质相同的token互转

上面说到，token具有value属性，此外，token还具有一个slot属性！

“Each token has a ‘slot’ attribute, ensuring that the value of two tokens with the same slot be treated as fungible, adding fungibility to the value property of the tokens.”

如果两个token的slot相同，即使它们的tokenId不一样，它们依然是可以被看作是同质化的。举个例子，游戏里面有2个装备，tokenID分别是0和1，如果是ERC1155，它们是不一样的东西；而如果是ERC3525，还需要继续看tokenId的slot属性。

    function slotOf(uint256 _tokenId) external view returns (uint256);
    

如果0和1具有相同的slot值，那么它们是同质化的。ERC3525有下面这样一样方法，用于相同slot但不同tokenId的token互转。使用这个方法，我可以把我的tokenID为1的装备转成tokenID为2的装备。

    function transferFrom(
            uint256 _fromTokenId,
            uint256 _toTokenId,
            uint256 _value
        ) external payable;
    

如果tokenId的slot属性被设计为可以被管理员账户修改，那么就赋予了token非常大的灵活性，我们可以让原本同质化的token变得非同质化，也可以反过来。

在游戏里，装备升级似乎可以完美对应这一场景，slot对应装备的等级。我拿到一个低级装备，一开始slot为0，那么它和其他所有slot为0的装备等效。现在我升级，slot变成1，那么它不再和slot为0的低级装备等效，而开始和slot为1的装备等效。

不过在游戏装备的场景中，最小单位都是整数，这里发挥不出valueDecimals的作用。举个更一般的例子，如果是积分会员卡，有不同等级的会员（对应slot不同），卡积分可以为小数。此时，同等级会员的积分是等效的，而不同等级的会员积分是非等效的，用户可以花钱升级会员等级。在这样的例子里，ERC3525的作用就可以非常明显地体现出来。

---

*Originally published on [rbtree](https://paragraph.com/@rbtree/eip-erc3525)*
