Skip to main content

Data Manager

What is a Data Manager?

Data Manager is a user-facing smart contract that contains 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.

Data Manager

Fig 1. Interaction of a user with a Data Manager, which forwards any request to the Data Index.

When a user wants to perform an operation, such as issueIdentity(), wrap(), verifyTrustedParty(), transfer(), or any other method defined by the developer, the Data Manager applies the necessary business logic but forwards any operation that involves storage manipulation (write access control) of the operation itself. The Data Index validates the user's permissions before allowing the Data Object to modify the data.

Data Managers can make any contract interoperable and bring any logic to an existing or new asset or information. This enables complex compliance mechanisms or adherence to existing standards such as ERC-20, ERC-1155, ERC-721, ERC-3643, ERC-1400, etc., with each implementing its own business logic while interacting with the same Data Object.

note

The Nexera Standard does not impose any restriction of the design of a Data Manager, it's completely a developer decision. We provide the following example and basic concepts:

Initialization

The core variables of the component are:

/// @dev DataPoint used in the Data object
DataPoint internal immutable _datapoint;

/// @dev Data Object contract
IDataObject public immutable fungibleFractionsDO;

/// @dev Data Index implementation
IDataIndex public immutable dataIndex;

We pass these arguments in the constructor, which can also include additional fields related to the contract's logic.

contract DataManager is IFractionTransferEventEmitter, Ownable {
constructor(
bytes32 _dp,
address _dataIndex,
address _dataObject
) Ownable(msg.sender) {
if (_dp == bytes32(0) || _dataIndex == address(0) || _dataObject == address(0)) {
revert WrongParameters();
}

_datapoint = DataPoint.wrap(_dp);
dataIndex = IDataIndex(_dataIndex);
_dataObject = IDataObject(_dataObject);
//Additional initializations
}
}

As you may have noticed, we can enable a Data Manager to inherit third-party smart contracts, such as IFractionTransferEventEmitter, to emit events or even call methods from other contracts whose logic we want to use.

Building Logic for the Data Manager Component

After initializing a Data Manager, method calls advance through the Data Index, which identifies the necessary checks to execute the required operation with the Data Object and Data Point.

The correct way to forward information through this component is by encoding or decoding operations during any write() or read() function, which will ultimately reach the Data Object. This process may look like this:

    /**
* @dev Exposed IERC Method that reads() from storage
* @param arg1 Argument 1
* @param arg2 Argument 2
* @param argN Argument N
* @return response Expected returned value from the method.
*/
function publicViewMethod1(address arg1, uint256 arg2, bytes argN) public view returns (bool) {
return
abi.decode(
dataObject.read(_datapoint, operation.selector, abi.encode(arg1, arg2, argN)),
(bool)
);
}
note

Read() operations do not modify storage. They can retrieve data either from the Data Index or directly from the Data Object.

For write() operations, which modify storage:

    /**
* @dev Exposed IERC Method that writes() to storage
* @param arg1 Argument 1
* @param arg2 Argument 2
* @param argN Argument N
*/
function publicMethod1(address arg1, uint256 arg2, bytes calldata data) public virtual {
// add here additional logic that doesn't modify storage of DM
dataIndex.write(address(dataObject), _datapoint, operation.selector,
abi.encode(arg1,arg2)
);
additionalLogic2(data);
}

An interesting aspect from a user or application's perspective is that when calling publicMethod1() on address A, it performs two actions internally: writing to internal storage and calling another method afterward. If, for any reason, the developer wants to adapt the logic, they can simply deploy a new Data Manager with the same constructor arguments and implement the following method:

    /**
* @dev Exposed IERC Method that writes() to storage
* @param arg1 Argument 1
* @param arg2 Argument 2
* @param argN Argument N
* @dev gatingMechanism modifier performs additional checks before
*/
function publicMethod1(address arg1, uint256 arg2, bytes calldata data) gatingMechanism
public virtual {
// add here additional logic that doesn't modify storage of DM
dataIndex.write(address(dataObject), _datapoint, operation.selector,
abi.encode(arg1,arg2)
);
//additionalLogic2(data);
}

Now, with this new Data Manager, calling publicMethod1() follows a different logic but still performs the same operation. The caller is invoking the same method but is now targeting Address B instead.

Data Manager

Fig 2. Interaction of a user switching to a different Data Manager, which forwards all requests to the same Data Object through the Data Index.

note

For detailed information on the functions and events within the Data Manager, visit the ERC20 Data Manager Section or ERC1155 Data Manager Section.