Skip to main content

ERC1155 Data Manager

Data Managers are independent Smart Contracts that implement the business logic for data management. They act 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 implementations 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 Minimalistic specific 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:

ERC1155 With ERC20Fractions DataManager

Contract Explanation

The MinimalisticERC1155WithERC20FractionsDataManager exposes an ERC1155 interface and is coded to handle each token from the collection as an ERC20.

The contract inherits several functionalities, most of them related to ERC1155 extensive functionalities. Additionally, to help emit events on multiple Data Managers at the same time we import IFractionTransferEventEmitter. Remember that changes made in one DM will be reflected in the other DM storage-wise, but we will also emit events across Data Managers.

contract MinimalisticERC1155WithERC20FractionsDataManager is
IFractionTransferEventEmitter, IERC1155, IERC1155Errors,
IERC1155MetadataURI, ERC165, Ownable

State Variables

Generic Data Manager
Variable NameDescriptionTypeVisibility
_datapointDataPoint for fungible fractionsDataPointinternal
fungibleFractionsDOFungible Fractions Data Object contractIDataObjectpublic
dataIndexData Index implementationIDataIndexpublic
ERC1155 Specific
Variable NameDescriptionTypeVisibility
_nameName of the ERC1155 tokenstringprivate
_symbolSymbol of the ERC1155 tokenstringprivate
_defaultURIDefault URI for token typesstringprivate
_operatorApprovalsApprovals state for operators per addressmapping(address => mapping(address => bool))private
ERC20 Fractions Specific

This part is not mandatory, but we incorporate this information to help retrieve and managing the fractions more easily.

Variable NameDescriptionTypeVisibility
erc20FractionsDMFactoryFactory for ERC20FractionDataManagerMinimalisticERC20FractionDataManagerFactorypublic
fractionManagersByIdERC20FractionDataManager by token IDmapping(uint256 => address)public
fractionManagersByAddressToken ID by ERC20FractionDataManager addressmapping(address => uint256)public

Following an Standard Interface and forwarding methods

In this part of the section, we will see how to perform a write operation to a DataPoint, following the ERC1155 Standard Interface, let's take the mint() operation as an example:

    /**
* @notice Mint new tokens
* @param to The address to mint tokens to
* @param id The token ID
* @param value The amount of tokens to mint
* @param data Additional data with no specified format
*/
function mint(address to, uint256 id, uint256 value, bytes memory data) public virtual onlyMinter {
_mint(to, id, value, data);
}_

For an external user allowed to mint operations, the public method is completely standard but it should follow a proper internal management:

    function _mint(address to, uint256 id, uint256 value, bytes memory data) internal virtual {
if (id == 0) revert IncorrectId(id);

if (to == address(0)) {
revert ERC1155InvalidReceiver(address(0));
}

_deployERC20DMIfNotDeployed(id, data);
_updateWithAcceptanceCheck(address(0), to, id, value, data);
}

In this case we perform a couple valdation checks on the arguments passed as the Id of the token or if the receiver is not a zero address. Since we decided we wanted to support and deploy the ERC20Fractions DM we check if this deployment has been performed before calling this method. We continue with the logic with the proper storage references:

With _updateWithAcceptanceCheck() we want to perform the desired user updates and to follow the IERC1155 standard requirement of checking if destination implements IERC1155Receiver.onERC1155Received, that's why after the update() you'll see a call to _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, values, data);.

If you keep following the internal calls, you can see how it'll notify ERC20 Data Manager and also will forward the write operation to the Data Index like this:

    function _writeTransfer(address from, address to, uint256 id, uint256 value) internal virtual {
if (from == address(0)) {
dataIndex.write(address(fungibleFractionsDO), _datapoint, IFungibleFractionsOperations.mint.selector, abi.encode(to, id, value));
} //Other conditions are omitted for readibility
}

Check that variables as fungibleFractionsDO and _datapoint where set at constructor and we pass the operation selector encoded together with the values of the operation.

This flow can be replicated for any write() operation for this DataManager, as such burning, transfering, etc. However, for read() user operations we can call directly the DataObject like we did for the following method:

    /**
* @notice Total supply of the ERC1155 token
* @return The amount of tokens in existence
* @dev This function is used to get the total supply of the ERC1155 token considering all token IDs
*/
function totalSupply() public view returns (uint256) {
return abi.decode(fungibleFractionsDO.read(_datapoint, IFungibleFractionsOperations.totalSupplyAll.selector, ""), (uint256));
}