探索如何在武漢鏈(基於ETH)的一個合約中實現同質化與非同質化功能

BSN研習社發表於2022-04-13

id:BSN_2021
公眾號:BSN研習社

目標:跟大家一塊去研究下1155標準中提供的案例
章節流程:

  1. 核心檔案

  2. 核心方法

  3. 彙總

在eip-1155中看到如下圖所示一段內容,並提供了一個案例,因此今天跟大家去研究一下內部的實現細節。


一、核心檔案

其主要涉及兩個檔案 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_;
        }
    }

    2. 檔案 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;

  • 鑄造非同質化 mintNonFungible(uint256 _type, address[] calldata _to)

    • 關鍵邏輯:首先,要求_type符合非同質化條件,即判斷_type按位與TYPE_NF_BIT是否等於TYPE_NF_BIT。接下來,獲取最新的索引值index,然後根據規則(將_type與index+i按位或)生成唯一標識,並與對應的address進行對映。

    • 示例:
      非同質化條件: 100xxx00 1 000xxx000 & TYPE_NF_BIT == TYPE_NF_BIT ;
      生成tokenId的規則:id = _type | index +i。即  100xxx00 1 000xxx000 | 001 =>  100xxx00 1 000xxx00 1 ;

    1. 鑄造同質化mintFungible(uint256 _id, address[] calldata _to, uint256[] calldata _quantities)

    • 關鍵邏輯:首先,要求符合同質化條件,即判斷_type按位與TYPE_NF_BIT是否等於0;接下來,計算最新的數量,並與對應的address進行對映。

    • 示例:
      同質化條件: 000xxx001 000xxx000 & TYPE_NF_BIT == 0;

  • 轉移 safeTransferFrom(address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data)

    • 關鍵邏輯:根據_id判斷是否是非同質化標識,即判斷_type按位與TYPE_NF_BIT是否等於TYPE_NF_BIT,然後返回對應的結果。

  • 獲取餘額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。


    引用資源地址:

    來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70012206/viewspace-2886924/,如需轉載,請註明出處,否則將追究法律責任。

    相關文章