Skip to main content

Data Object

The IDataObject interface defines the methods established to manage the data stored in Data Objects associated with Data Points. This contract exposes functionalities for managing fungible fraction data objects, in this case for the mentioned ERC20Fractions and ERC1155WithERC20Fractions Data Managers.

It provides the core functionality for managing Fungible Fraction tokens, including methods like balanceOf(), totalSupply(), exists(), transferFrom(), mint(), burn(), and their batch variants. It is designed for integration with a DataManager contract, potentially offering additional features like approvals, access control, and metadata management, often aligning with ERC1155 token standards.

Fungible Fractions DO

Contract Explanation

Let's start with how this Data Object handles the information associated with a fungible token functionality. In the next piece of code, you'll find the DataPoint storage structure that saves the reference to it's DataIndex, its content and more data associated specifically with a user.

    /**
* @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 variable diidData is a user-specific storage whose identifier can be retrieved with the method _diid() and retrieves the user Id from the Data Index implementation of the DataPoint.

Here you'd see DpData information generic to the token and DiidData specific to user balances.

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

Now, let's see how a Data Object and, in particular, the minimalistic fungible implementation handles a request that has been generated from the user and has passed through the Data Index.

It is the Data Index smart contract call the first to appear in this scenario, when it starts forwarding the write() operation coming from the Data Manager (f.e. ERC20FractionsDataManager).

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

After going through the modifier, the internal method _dispatchWrite() will decode the operation using the standard interface for fungible operations.

    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 "";
}
}

Here comes the storage modification step. If you remember past mentions in this section to _diid() you can guess these methods and _diidData() will help retrieve specific user information of this request, in this case 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() will update balances associated with the origin and destination of the transfer function. Any other Data Manager (ERC20, ERC1155, etc) will access to this updated user information on the next read() operation.