Explore how to realize the functions of homogenization and non homogenization in a contract of Wuhan chain (based on ETH)

Time:2022-5-10

id:BSN_2021
Official account: BSN Institute

Objective: To study the cases provided in the 1155 standard with you
Chapter process:

Core document

Core method

Summary

In eip-1155, we can see a paragraph as shown in the figure below and provide a case. Therefore, we will study the internal implementation details with you today.
Explore how to realize the functions of homogenization and non homogenization in a contract of Wuhan chain (based on ETH)
1、 Core document
It mainly involves two files, erc1155mixedfungiblemintable Sol and erc1155mixedfungible Sol, as follows:

File erc1155mixedfungible Sol contents are as follows:

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..
    //Decimal system: 115792089237316195423570985008687907852929702298719625575994209400481361428480
    //Hex: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
    //Binary:
    // 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    // 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    uint256 public constant TYPE_MASK = uint256(uint128(~0)) << 128;


    // ..and the non-fungible index in the lower 128
    //Decimal system: 340282366920938463374607431768211455
    //Hex: ffffffffffffffffffffffffffffffffffffffffff
    //Binary: 
    // 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    // 11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    uint256 public constant NF_INDEX_MASK = uint128(~0);


    // The top bit is a flag to tell if this is a NFI.
    //Decimal system: 578960446186580977117854925043939539926634992332820282019728792003956564819968
    //Hex: 800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    //Binary:
    // 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. File erc1155mixedfungiblemintable Sol contents are as follows:

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, '');
            }
        }
    }
}

2、 Core method
Let’s take a look at several built-in state variables, as follows:
TYPE_NF_BIT= 100xxx000 000xxx000
TYPE_MASK= 111xxx111 000xxx000
NF_INDEX_MASK=000xxx000 111xxx111

The main methods are as follows:

Create type (string calldata _uri, bool _isnf):

Key logic: first, it is generated by nonce self increment and then 128 bits left shift_ Type category unique identifier (tokenid). Next, judge whether it is non homogenization. If yes, it is related to type_ NF_ Bit by bit or (Note: the highest mark of the result starts with 1), otherwise it will be returned directly_ type。

Example: first 128 bits, last 128 bits.
nft_type:100xxx001 000xxx000;
ft_type: 000xxx001 000xxx000;

Mintnonfungible (uint256 _type, address [] calldata _to)

Key logic: first, requirements_ Type meets the non homogeneity condition, i.e. judgment_ Type bitwise and type_ NF_ Is bit equal to type_ NF_ BIT。 Next, get the latest index value index, and then generate a unique ID according to the rule (bitwise or between _typeand index + I) and map it with the corresponding address.

Example:
Non homogenization conditions: 100xxx001 000xxx000 & type_ NF_ BIT == TYPE_ NF_ BIT ;
Rule for generating tokenid: id =_ type | index +i。 I.e. 100xxx001 000xxx000 | 001 = > 100xxx001 000xxx001;

Mintfungible (uint256 _id, address [] calldata _to, uint256 [] calldata _quantities)

Key logic: first of all, it is required to meet the quality conditions of the contract, that is, judgment_ Type bitwise and type_ NF_ Whether bit is equal to 0; Next, calculate the latest quantity and map it with the corresponding address.

Example:
Homogenization condition: 000xxx001 000xxx000 & type_ NF_ BIT == 0;

Transfer safetransferfrom (address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data)

Key logic: according to_ ID determines whether it is a non homogenous identification, that is, judgment_ Type bitwise and type_ NF_ Is bit equal to type_ NF_ Bit, and then return the corresponding result.

Obtain balance of (address _owner, uint256 _id) external view returns (uint256)

Key logic: according to_ ID determines whether it is a non homogenous identification, that is, judgment_ ID bitwise and type_ NF_ Is bit equal to type_ NF_ Bit, and_ ID bitwise and NF_ INDEX_ Mask equals zero. Then return the corresponding result. Note: the quantity of non homogeneity is 0 or 1;

3、 Summary
After reading the key methods, we can find that there is not much difficulty in technology. It is mainly the ingenious design by using split ID bits and bit operation. The tokenid uint256 is divided into two parts (the first 128 bits and the last 128 bits). In case of non homogeneity, the first 128 bits identify the type, and the last 128 represents the index or ID, that is < uint128: base token ID > < uint128: index of non fungible >. When homogenizing, the first 128 bits identify the ID, and the last 128 bits are zero, that is < uint128: base token ID > < uint128: Zero >;

Generally speaking, when the Create method is called, the non homogenization generates the category (or series) id, and then generates the unique ID under the category, that is, NF tokenid. The generated by homogenization is the unique identifier, i.e. f-tokenid.

Reference resource address:
https://eips.ethereum.org/EIP…
https://github.com/enjin/erc-…