Learning ERC-7208Data Managers

ERC20 DataManager

Data Managers are independent smart contracts that implement business logic for data management, serving as the primary entry point for user interactions and ensuring the underlying storage aligns with the required use-case logic.

There are various implementations of Data Managers, as they can integrate any logic, offering limitless possibilities. Having covered the core concepts, let's explore specific minimal implementations.

Data Managers can interact with each other. In this example, two Data Managers are deployed, with ERC1155 and ERC20 being essential for communication.

ERC20Fractions DataManager

Contract Explanation

The MinimalisticERC20FractionDataManager contract manages ERC20 fractions of an ERC1155 token, exposing ERC20 functionalities and emitting Transfer events.

State Variables

Generic Data Manager
NameTypeDescription
_datapointDataPointDataPoint used in the fungibleFractions data object
fungibleFractionsDOIDataObjectFungible Fractions Data Object contract
dataIndexIDataIndexData Index implementation
ERC20 Data Manager Specific
NameTypeDescription
_namestringERC20 token name
_symbolstringERC20 token symbol
_allowancesmappingMapping of allowances from user to spender to amount
ERC1155 Data Manager-Compatibility
NameTypeDescription
erc1155dmaddressERC1155 data manager contract
erc1155IDuint256ERC1155 token ID

In this contract, we use an alternative to a constructor for several reasons. Primarily, we need to link the ERC1155 Data Manager with the ERC20 Data Manager to create the ERC1155WithERC20FractionsDataManager. Since we don’t know all the required fields (e.g., the ERC1155 DataManager address) at deployment, this approach is necessary. Additionally, it supports upgradeability and uses the Clones pattern instead of factories. See the example below:

    /// @dev Initializes the ERC20 Fraction Data Manager
    function initialize(
        bytes32 datapoint_,
        address dataIndex_,
        address fungibleFractionsDO_,
        address erc1155dm_,
        uint256 erc1155ID_,
        string memory name_,
        string memory symbol_
    ) external initializer {
        __Ownable_init_unchained(_msgSender());
        __MinimalisticERC20FractionDataManager_init_unchained(datapoint_, dataIndex_, fungibleFractionsDO_, erc1155dm_, erc1155ID_, name_, symbol_);
    }

We observe ERC20 standard methods that read storage from the DataObject, making ERC1155 tokens compatible with the ERC20 interface. This method returns the total supply of ERC20 tokens in circulation. Unlike the 1155 Data Manager, which uses an ID to identify collections, here the entire ERC1155 collection is treated as an ERC20 token.

    /**
     * @notice Total supply of the ERC20 token
     * @return The amount of ERC20 tokens in circulation
     * @dev This function reads the total supply from the fungible fractions data object
     *      NOTE: This total supply is equal to the amount of fractions of the ERC1155 token with the id `erc1155ID`
     */
    function totalSupply() external view override returns (uint256) {
        return abi.decode(fungibleFractionsDO.read(_datapoint, IFungibleFractionsOperations.totalSupply.selector, abi.encode(erc1155ID)), (uint256));
    }

This contract also exposes the ERC20 approval() method, which is not tied to token storage and follows the standard implementation. Therefore, we include the variable and the appropriate _allowance check:

    /**
     * @notice Approve a spender to spend a certain amount of tokens on behalf of the caller
     * @param spender The address of the spender
     * @param value The amount of tokens the spender is allowed to spend
     * @return True if the approval was successful
     */
    function approve(address spender, uint256 value) public returns (bool) {
        if (_msgSender() == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[_msgSender()][spender] = value;
        emit Approval(_msgSender(), spender, value);
        return true;
    }

Here’s an interesting approach to ERC20 transfers:

This standard enables composability, allowing gating mechanisms on transfer methods via the _beforeTokenTransfer() hook. This can be used for compliance checks when needed:

    /*
     * @notice Transfer tokens to a specified recipient
     * @param to The recipient of the tokens
     * @param amount The amount of tokens to transfer
     * @return True if the transfer was successful
     * @dev This function performs a transfer operation executing the transfer in the fungible fractions data object
     *      NOTE: This function does not allow transfers to the zero address
     */
    function transfer(address to, uint256 amount) external virtual override returns (bool) {
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _beforeTokenTransfer(_msgSender(), to, amount);
 
        _writeTransfer(_msgSender(), to, amount);
 
        emit Transfer(_msgSender(), to, amount);
        return true;
    }

Finally, it forwards the write() operation with the DataObject, DataPoint, ERC20 transfer selector, and necessary arguments. It also shows how the event is transmitted to the ERC1155 Data Manager.

    function _writeTransfer(address from, address to, uint256 amount) internal {
        // Simplified conditional logic for legibility
        dataIndex.write(
            address(fungibleFractionsDO),
            _datapoint,
            IFungibleFractionsOperations.transferFrom.selector,
            abi.encode(from, to, erc1155ID, amount)
        );
        IFractionTransferEventEmitter(erc1155dm).fractionTransferredNotify(from, to, amount);
    }

Methods

1. initialize()
function initialize(
    bytes32 datapoint_,
    address dataIndex_,
    address fungibleFractionsDO_,
    address erc1155dm_,
    uint256 erc1155ID_,
    string memory name_,
    string memory symbol_
) external initializer
DescriptionParameters
Initializes the ERC20 Fraction Data Managerdatapoint_: DataPoint identifier
dataIndex_: Address of the Data Index
fungibleFractionsDO_: Address of the Fungible Fractions Data Object
erc1155dm_: Address of the ERC1155 data manager
erc1155ID_: ERC1155 token ID
name_: ERC20 token name
symbol_: ERC20 token symbol
2. decimals()
function decimals() external pure returns (uint8)
DescriptionReturns
Returns the number of decimals for the tokenNumber of decimals
3. name()
function name() external view returns (string memory)
DescriptionReturns
Returns the name of the tokenToken name
4. symbol()
function symbol() external view returns (string memory)
DescriptionReturns
Returns the symbol of the tokenToken symbol
5. totalSupply()
function totalSupply() external view override returns (uint256)
DescriptionReturns
Returns the total supply of the ERC20 tokenTotal supply
6. balanceOf()
function balanceOf(address account) external view override returns (uint256)
DescriptionParametersReturns
Returns the balance of an accountaccount: The account to checkBalance of the account
7. fractionTransferredNotify()
function fractionTransferredNotify(address from, address to, uint256 amount) external onlyTransferNotifier
DescriptionParameters
Notifies a fraction transfer (only callable by ERC1155 data manager)from: Sender address
to: Recipient address
amount: Amount transferred
8. allowance()
function allowance(address owner_, address spender) public view returns (uint256)
DescriptionParametersReturns
Returns the allowance of a spenderowner_: Token owner
spender: Spender address
Allowance amount
9. approve()
function approve(address spender, uint256 value) public returns (bool)
DescriptionParametersReturns
Approves a spender to spend tokensspender: Spender address
value: Amount to approve
True if successful
10. transfer()
function transfer(address to, uint256 amount) external virtual override returns (bool)
DescriptionParametersReturns
Transfers tokens to a recipientto: Recipient address
amount: Amount to transfer
True if successful
11. transferFrom()
function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool)
DescriptionParametersReturns
Transfers tokens from one account to anotherfrom: Sender address
to: Recipient address
amount: Amount to transfer
True if successful

Internal Functions

1. _spendAllowance()
function _spendAllowance(address owner_, address spender, uint256 amount) internal
DescriptionParameters
Spends allowanceowner_: Token owner
spender: Spender address
amount: Amount to spend
2. _writeTransfer()
function _writeTransfer(address from, address to, uint256 amount) internal
DescriptionParameters
Writes transfer to the fungible fractions data objectfrom: Sender address
to: Recipient address
amount: Amount to transfer
3. _beforeTokenTransfer()
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual
DescriptionParameters
Hook that is called before any transfer of tokensfrom: Sender address
to: Recipient address
amount: Amount to transfer