Learning ERC-7208

Data Object

The IDataObject interface defines methods for managing data in Data Objects linked to Data Points. It provides functionality for managing fungible fraction data objects, such as ERC20Fractions and ERC1155WithERC20Fractions Data Managers.

Core functions include balanceOf(), totalSupply(), exists(), transferFrom(), mint(), burn(), and their batch variants. Designed for integration with a DataManager contract, it may also offer features like approvals, access control, and metadata management, often aligning with ERC1155 token standards.

Fungible Fractions DO

Contract Explanation

Let’s begin with how this Data Object manages fungible token information. The following code shows the DataPoint storage structure, which saves the reference to its DataIndex, content, and additional user-specific data:

    /**
     * @notice Data structure to store DataPoint data
     * @param dataIndexImplementation The DataIndex implementation set for the DataPoint
     * @param dpData The DataPoint data
     * @param dataIndexData Mapping of diid to user data
     */
    struct DataPointStorage {
        IDataIndex dataIndexImplementation;
        DpData dpData;
        mapping(bytes32 diid => DiidData) diidData;
    }

The diidData variable stores user-specific data, with the user ID retrievable via the _diid() method from the Data Index implementation.

Here, DpData contains token-specific information, while DiidData handles user balances.

struct DpData {
    uint256 totalSupplyAll;
    mapping(uint256 id => uint256 totalSupplyOfId) totalSupply;
}
// ...
struct DiidData {
    EnumerableSet.UintSet ids;
    mapping(uint256 id => uint256 value) balances;
}

Let’s look at how the Data Object, specifically the minimalistic fungible implementation, handles a user request that passes through the Data Index.

The Data Index contract is the first to act, forwarding the write() operation from the Data Manager (e.g., ERC20FractionsDataManager).

    function write(DataPoint dp, bytes4 operation, bytes calldata data) external onlyDataIndex(dp) returns (bytes memory) {
        return _dispatchWrite(dp, operation, data);
    }

After passing through the modifier, the internal method _dispatchWrite() decodes the operation using the standard fungible operations interface.

    function _dispatchWrite(DataPoint dp, bytes4 operation, bytes calldata data) internal virtual returns (bytes memory) {
        if (operation == IFungibleFractionsOperations.transferFrom.selector) {
            (address from, address to, uint256 id, uint256 value) = abi.decode(data, (address, address, uint256, uint256));
            _transferFrom(dp, from, to, id, value);
            return "";
        }
    }

Next, the storage modification step occurs. Using methods like _diid() and _diidData(), we retrieve specific user information for the request, such as the balance of the ERC1155 token ID.

    function _transferFrom(DataPoint dp, address from, address to, uint256 id, uint256 value) internal virtual {
        bytes32 diidFrom = _diid(dp, from);
        bytes32 diidTo = _diid(dp, to);
        DiidData storage diiddFrom = _diidData(dp, diidFrom);
        DiidData storage diiddTo = _diidData(dp, diidTo);
        _decreaseBalance(diiddFrom, id, value, dp, diidFrom);
        _increaseBalance(diiddTo, id, value, dp, diidTo);
    }

Finally, _decreaseBalance() and _increaseBalance() update the balances for the transfer's origin and destination. Other Data Managers (ERC20, ERC1155, etc.) will access the updated user information on the next read() operation.

On this page