探索如何在武汉链(基于ETH)的一个合约中实现同质化与非同质化功能

249 阅读4分钟

id:BSN_2021

公众号:BSN研习社

目标:跟大家一块去研究下1155标准中提供的案例

章节流程:

1.核心文件

2.核心方法

3.汇总 在eip-1155中看到如下图所示一段内容,并提供了一个案例,因此今天跟大家去研究一下内部的实现细节。

1.png

一、核心文件

其主要涉及两个文件ERC1155MixedFungibleMintable.solERC1155MixedFungible .sol,如下:

  1. 文件ERC1155MixedFungible .sol内容如下:
pragma solidity ^0.5.0;
import "./ERC1155.sol";
/**
@dev Extension to ERC1155 for Mixed Fungible and Non-Fungible Items support    
The main benefit is sharing of common type information, just like you do when    
creating a fungible id.
*/
contract ERC1155MixedFungible is ERC1155 {

// Use a split bit implementation. Store the type in the upper 128 bits..
// 十进制:115792089237316195423570985008687907852929702298719625575994209400481361428480
// 十六进制:ffffffffffffffffffffffffffffffff00000000000000000000000000000000    
// 二进制:
// 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111    
// 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
uint256 public constant TYPE_MASK = uint256(uint128(~0)) << 128;



// ..and the non-fungible index in the lower 128    
// 十进制:340282366920938463463374607431768211455    
// 十六进制:ffffffffffffffffffffffffffffffff    
// 二进制: 

// 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    
// 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111    
uint256 public constant NF_INDEX_MASK = uint128(~0);


// The top bit is a flag to tell if this is a NFI.    
// 十进制: 57896044618658097711785492504343953926634992332820282019728792003956564819968  // 十六进制: 8000000000000000000000000000000000000000000000000000000000000000    
// 二进制:    
// 10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    
// 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000    
uint256 public constant TYPE_NF_BIT = 1 << 255;


mapping (uint256 => address) nfOwners;


 // Only to make code clearer. Should not be functions    
 function isNonFungible(uint256 _id) public pure returns(bool) {
 return _id & TYPE_NF_BIT == TYPE_NF_BIT;    
 }

function isFungible(uint256 _id) public pure returns(bool) {        
return _id & TYPE_NF_BIT == 0;
}

function getNonFungibleIndex(uint256 _id) public pure returns(uint256) { 
return _id & NF_INDEX_MASK;

 }    
 function getNonFungibleBaseType(uint256 _id) public pure returns(uint256) {        return _id & TYPE_MASK;    
 }

function isNonFungibleBaseType(uint256 _id) public pure returns(bool) {        
// A base type has the NF bit but does not have an index.        
return (_id & TYPE_NF_BIT == TYPE_NF_BIT) && (_id & NF_INDEX_MASK == 0);    
}

function isNonFungibleItem(uint256 _id) public pure returns(bool) {        
// A base type has the NF bit but does has an index.        
return (_id & TYPE_NF_BIT == TYPE_NF_BIT) && (_id & NF_INDEX_MASK != 0);   
}

function ownerOf(uint256 _id) public view returns (address) {        
return nfOwners[_id];
}

// override    
function safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external {
        
        require(_to != address(0x0), "cannot send to zero address");        
        require(_from == msg.sender || operatorApproval[_from][msg.sender] == true, "Need operator approval for 3rd party transfers.");

if (isNonFungible(_id)) {            
require(nfOwners[_id] == _from); 
nfOwners[_id] = _to;           
// You could keep balance of NF type in base type id like so:            
// uint256 baseType = getNonFungibleBaseType(_id);            
// balances[baseType][_from] = balances[baseType][_from].sub(_value);           
// balances[baseType][_to]   = balances[baseType][_to].add(_value);       
} else {

balances[_id][_from] = balances[_id][_from].sub(_value);            
balances[_id][_to]   = balances[_id][_to].add(_value);        
}

 emit TransferSingle(msg.sender, _from, _to, _id, _value);
 
        if (_to.isContract()) {
        

 _doSafeTransferAcceptanceCheck(msg.sender, _from, _to, _id, _value, _data);        
 }   
 }

 // override    
 function safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external {

 require(_to != address(0x0), "cannot send to zero address");        
 require(_ids.length == _values.length, "Array length must match");
 
        // Only supporting a global operator approval allows us to do only 1 check and not to touch storage to handle allowances.        
        require(_from == msg.sender || operatorApproval[_from][msg.sender] == true, "Need operator approval for 3rd party transfers.");
      
 for (uint256 i = 0; i < _ids.length; ++i) {            
 // Cache value to local variable to reduce read costs.            
 uint256 id = _ids[i];            
 uint256 value = _values[i];
 

if (isNonFungible(id)) {               
require(nfOwners[id] == _from);                
nfOwners[id] = _to;            
} else {                
balances[id][_from] = balances[id][_from].sub(value);                
balances[id][_to]   = value.add(balances[id][_to]);           
}       
}


emit TransferBatch(msg.sender, _from, _to, _ids, _values);

        if (_to.isContract()) {
 _doSafeBatchTransferAcceptanceCheck(msg.sender, _from, _to, _ids, _values, _data);       
 }    
 }

function balanceOf(address _owner, uint256 _id) external view returns (uint256) {        
if (isNonFungibleItem(_id))            
return nfOwners[_id] == _owner ? 1 : 0;        
return balances[_id][_owner];    
}


function balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids) external view returns (uint256[] memory) {

        require(_owners.length == _ids.length);
     
uint256[] memory balances_ = new uint256[](_owners.length);
        
        for (uint256 i = 0; i < _owners.length; ++i) {
  
uint256 id = _ids[i];            
if (isNonFungibleItem(id)) {                
balances_[i] = nfOwners[id] == _owners[i] ? 1 : 0;           
} else {

balances_[i] = balances[id][_owners[i]];            
}        
}

 return balances_;    
 }
 }
 
 
  1. 文件ERC1155MixedFungibleMintable.sol内容如下:
pragma solidity ^0.5.0;

import "./ERC1155MixedFungible.sol";

/**    
@dev Mintable form of ERC1155    
Shows how easy it is to mint new items
*/
contract ERC1155MixedFungibleMintable is ERC1155MixedFungible {

    uint256 nonce;  
   
    mapping (uint256 => address) public creators;    
    mapping (uint256 => uint256) public maxIndex;
    
    modifier creatorOnly(uint256 _id) {       
    require(creators[_id] == msg.sender);
    _;
    }
    // This function only creates the type.
    function create(
    string calldata _uri,
    bool   _isNF)
    external returns(uint256 _type) {
    
        // Store the type in the upper 128 bits
        _type = (++nonce << 128);
        
        // Set a flag if this is an NFI.
        if (_isNF)
        _type = _type | TYPE_NF_BIT;
        
        // This will allow restricted access to creators.
        creators[_type] = msg.sender;
        
        // emit a Transfer event with Create semantic to help with discovery.        
        emit TransferSingle(msg.sender, address(0x0), address(0x0), _type, 0);
        
        if (bytes(_uri).length > 0)            
        emit URI(_uri, _type);
        }
    function mintNonFungible(uint256 _type, address[] calldata _to) external creatorOnly(_type) {
        
        // No need to check this is a nf type rather than an id since        
        // creatorOnly() will only let a type pass through.        
        require(isNonFungible(_type));
        
        // Index are 1-based.        
        uint256 index = maxIndex[_type] + 1;        
        maxIndex[_type] = _to.length.add(maxIndex[_type]);
        
        for (uint256 i = 0; i < _to.length; ++i) {
        address dst = _to[i];
        uint256 id  = _type | index + i;
        
            nfOwners[id] = dst;
            
            // You could use base-type id to store NF type balances if you wish.            
            // balances[_type][dst] = quantity.add(balances[_type][dst]);
           
           emit TransferSingle(msg.sender, address(0x0), dst, id, 1);
           
            if (dst.isContract()) {
            _doSafeTransferAcceptanceCheck(msg.sender, msg.sender, dst, id, 1, '');            
            }        
            } 
            }
    function mintFungible(uint256 _id, address[] calldata _to, uint256[] calldata _quantities) external creatorOnly(_id) {
        
        require(isFungible(_id));
        
        for (uint256 i = 0; i < _to.length; ++i) {
        
            address to = _to[i];
            uint256 quantity = _quantities[i];
            
            // Grant the items to the caller            
            balances[_id][to] = quantity.add(balances[_id][to]);
            
            // Emit the Transfer/Mint event.
            // the 0x0 source address implies a mint 
            // It will also provide the circulating supply info. 
            emit TransferSingle(msg.sender, address(0x0), to, _id, quantity);
            
            if (to.isContract()) { 
            _doSafeTransferAcceptanceCheck(msg.sender, msg.sender, to, _id, quantity, '');            
            }        
            }   
            }
            }

二、核心方法

先看一下几个内置的状态变量,如下:

TYPE_NF_BIT= 100xxx000 000xxx000
TYPE_MASK= 111xxx111 000xxx000
NF_INDEX_MASK=000xxx000 111xxx111

主要的几个方法,详解如下:

  1. 创建类型 create(string calldata _uri,bool _isNF):
    • 关键逻辑:首先,通过nonce自增然后左移128位生成_type 类别唯一标识(tokenid)。接下来,判断是否是非同质化,是则与TYPE_NF_BIT按位或(注:其结果最高位标识为1开头),否则直接返回_type。
    • 示例:前128位,后128位。
      nft_type:100xxx001 000xxx000;
      ft_type: 000xxx001 000xxx000;
  2. 铸造非同质化 mintNonFungible(uint256 _type, address[] calldata _to)
    • 关键逻辑:首先,要求_type符合非同质化条件,即判断_type按位与TYPE_NF_BIT是否等于TYPE_NF_BIT。接下来,获取最新的索引值index,然后根据规则(将_type与index+i按位或)生成唯一标识,并与对应的address进行映射。
    • 示例:
      非同质化条件:100xxx001 000xxx000 & TYPE_NF_BIT == TYPE_NF_BIT ;
      生成tokenId的规则:id = _type | index +i。即 100xxx001 000xxx000 | 001 => 100xxx001 000xxx001 ;
  1. 铸造同质化mintFungible(uint256 _id, address[] calldata _to, uint256[] calldata _quantities)
    • 关键逻辑:首先,要求符合同质化条件,即判断_type按位与TYPE_NF_BIT是否等于0;接下来,计算最新的数量,并与对应的address进行映射。
    • 示例:
      同质化条件:000xxx001 000xxx000 & TYPE_NF_BIT == 0;
  2. 转移 safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data)
    • 关键逻辑:根据_id判断是否是非同质化标识,即判断_type按位与TYPE_NF_BIT是否等于TYPE_NF_BIT,然后返回对应的结果。
  3. 获取余额balanceOf(address _owner, uint256 _id) external view returns (uint256)
    • 关键逻辑:根据_id判断是否是非同质化标识,即判断_id按位与TYPE_NF_BIT是否等于TYPE_NF_BIT,并且_id按位与NF_INDEX_MASK 等于零。然后返回对应的结果。注:非同质化的数量为0或1;

三、汇总

关键的几个方法看完后,可以发现技术上没有太大的难度,主要是运用了标识位拆分(Split ID bits)以及位运算进行巧妙的设计。即将tokenid uint256分为两部分(前128位和后128位)。当非同质化时前128位标识类型,后面128为代表索引或id,即<uint128: base token id><uint128: index of non-fungible>。当同质化时前128位标识id,后128位为零,即<uint128: base token id><uint128: zero>

通俗点的来说,当调用create方法时,非同质化生成的为类别(或系列)标识,然后再生成该类别下的唯一标识,即nf-tokenID。同质化生成的即为唯一标识,即f-tokenID。


引用资源地址:
eips.ethereum.org/EIPS/eip-11… github.com/enjin/erc-1…