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
Name | Type | Description |
---|
_datapoint | DataPoint | DataPoint used in the fungibleFractions data object |
fungibleFractionsDO | IDataObject | Fungible Fractions Data Object contract |
dataIndex | IDataIndex | Data Index implementation |
ERC20 Data Manager Specific
Name | Type | Description |
---|
_name | string | ERC20 token name |
_symbol | string | ERC20 token symbol |
_allowances | mapping | Mapping of allowances from user to spender to amount |
ERC1155 Data Manager-Compatibility
Name | Type | Description |
---|
erc1155dm | address | ERC1155 data manager contract |
erc1155ID | uint256 | ERC1155 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
Description | Parameters |
---|
Initializes the ERC20 Fraction Data Manager | datapoint_ : 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)
Description | Returns |
---|
Returns the number of decimals for the token | Number of decimals |
3. name()
function name() external view returns (string memory)
Description | Returns |
---|
Returns the name of the token | Token name |
4. symbol()
function symbol() external view returns (string memory)
Description | Returns |
---|
Returns the symbol of the token | Token symbol |
5. totalSupply()
function totalSupply() external view override returns (uint256)
Description | Returns |
---|
Returns the total supply of the ERC20 token | Total supply |
6. balanceOf()
function balanceOf(address account) external view override returns (uint256)
Description | Parameters | Returns |
---|
Returns the balance of an account | account : The account to check | Balance of the account |
7. fractionTransferredNotify()
function fractionTransferredNotify(address from, address to, uint256 amount) external onlyTransferNotifier
Description | Parameters |
---|
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)
Description | Parameters | Returns |
---|
Returns the allowance of a spender | owner_ : Token owner
spender : Spender address | Allowance amount |
9. approve()
function approve(address spender, uint256 value) public returns (bool)
Description | Parameters | Returns |
---|
Approves a spender to spend tokens | spender : Spender address
value : Amount to approve | True if successful |
10. transfer()
function transfer(address to, uint256 amount) external virtual override returns (bool)
Description | Parameters | Returns |
---|
Transfers tokens to a recipient | to : Recipient address
amount : Amount to transfer | True if successful |
11. transferFrom()
function transferFrom(address from, address to, uint256 amount) external virtual override returns (bool)
Description | Parameters | Returns |
---|
Transfers tokens from one account to another | from : 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
Description | Parameters |
---|
Spends allowance | owner_ : Token owner
spender : Spender address
amount : Amount to spend |
2. _writeTransfer()
function _writeTransfer(address from, address to, uint256 amount) internal
Description | Parameters |
---|
Writes transfer to the fungible fractions data object | from : Sender address
to : Recipient address
amount : Amount to transfer |
3. _beforeTokenTransfer()
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual
Description | Parameters |
---|
Hook that is called before any transfer of tokens | from : Sender address
to : Recipient address
amount : Amount to transfer |