Skip to main content

ERC20 DataManager

Data Managers are independent Smart Contracts that implement the business logic for data management. It acts as the main entry point for user interactions, exposing functions to ensure the underlying storage follows the required use-case logic.

You can find several different implementation of a Data Manager, as it can integrate ANY logic, thus the possibilities are limitless. As we have defined the basic points each Data Manager should have at the Core Concepts, let's dive deeper into specific minimalistic implementations.

Data Managers can communicate between each other and we know that ERC1155 and ERC20 are necessary for this example, so two Data Managers are deployed:

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 you may have noticed we used an alternative method instead of a constructor and there are several reasons why we opted for this solution, in this case, as we want to link our ERC1155 Data Manager with the ERC20 DataManager in order to get the ERC1155WithERC20FractionsDataManager. Because we don't know all the necessary fields for the ERC20 Data Manager at the moment of the deployment (f.e. the ERC1155 DataManager address). Other reasons are because we want to support upgradeability and Clones patterns 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 can observe some ERC20 standard methods, reading storage from the DataObject and making the ERC1155 tokens compatible with this interface. This method will return the supply of all the ERC20 in circulation. If you remember the 1155 Data Manager, this is a collection identified by an ID but now treated as an entire 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 exposes also ERC20 approval() method, which is not tight to the token storage and specific to the standard implementation, that's why we'll find the variable and the proper _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 the ERC20 transfer:

It's important to highlight the examples of composability this standard allows. With the following example, we enable gating mechanisms on transfer methods, as the _beforeTokenTransfer() hook, this is potentially the way of following necessary compliance checks if necessary:

    /*
* @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'll forward the write() operation passing the DataObject, DataPoin, ERC20 transfer selector and the necessary arguments for the Data Object to perform the operation. Additionally, you can follow 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