// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "./IAssetController.sol"; import "./IAssetVault.sol"; import "./asset-class-registry/IAssetClassRegistry.sol"; library Assets { using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet; using Address for address; using Assets for Registry; using Assets for Asset; using Assets for AssetId; /* * @dev This is the list of asset class identifiers to be used across the system. */ bytes4 public constant ERC721 = bytes4(keccak256("ERC721")); bytes4 public constant ERC1155 = bytes4(keccak256("ERC1155")); bytes32 public constant ASSET_ID_TYPEHASH = keccak256("AssetId(bytes4 class,bytes data)"); bytes32 public constant ASSET_TYPEHASH = keccak256("Asset(AssetId id,uint256 value)AssetId(bytes4 class,bytes data)"); /** * @dev Thrown upon attempting to register an asset twice. * @param asset Duplicate asset address. */ error AssetIsAlreadyRegistered(address asset); /** * @dev Thrown when target for operation asset is not registered * @param asset Asset address, which is not registered. */ error AssetIsNotRegistered(address asset); /** * @dev Communicates asset identification information. * The structure designed to be token-standard agnostic, * so the layout of `data` might vary for different token standards. * For example, in case of ERC721 token, the `data` will contain contract address and tokenId. * @param class Asset class ID * @param data Asset identification data. */ struct AssetId { bytes4 class; bytes data; } /** * @dev Calculates Asset ID hash */ function hash(AssetId memory assetId) internal pure returns (bytes32) { return keccak256(abi.encode(ASSET_ID_TYPEHASH, assetId.class, keccak256(assetId.data))); } /** * @dev Extracts token contract address from the Asset ID structure. * The address is the common attribute for all assets regardless of their asset class. */ function token(AssetId memory self) internal pure returns (address) { return abi.decode(self.data, (address)); } function hash(Assets.AssetId[] memory assetIds) internal pure returns (bytes32) { return keccak256(abi.encode(assetIds)); } /** * @dev Uniformed structure to describe arbitrary asset (token) and its value. * @param id Asset ID structure. * @param value Asset value (amount). */ struct Asset { AssetId id; uint256 value; } /** * @dev Calculates Asset hash */ function hash(Asset memory asset) internal pure returns (bytes32) { return keccak256(abi.encode(ASSET_TYPEHASH, hash(asset.id), asset.value)); } /** * @dev Extracts token contract address from the Asset structure. * The address is the common attribute for all assets regardless of their asset class. */ function token(Asset memory self) internal pure returns (address) { return abi.decode(self.id.data, (address)); } function toIds(Assets.Asset[] memory assets) internal pure returns (Assets.AssetId[] memory result) { result = new Assets.AssetId[](assets.length); for (uint256 i = 0; i < assets.length; i++) { result[i] = assets[i].id; } } function hashIds(Assets.Asset[] memory assets) internal pure returns (bytes32) { return hash(toIds(assets)); } /** * @dev Original asset data. * @param controller Asset controller. * @param assetClass The asset class identifier. * @param vault Asset vault. */ struct AssetConfig { IAssetController controller; bytes4 assetClass; IAssetVault vault; } /** * @dev Asset registry. * @param classRegistry Asset class registry contract. * @param assetIndex Set of registered asset addresses. * @param assets Mapping from asset address to the asset configuration. */ struct Registry { IAssetClassRegistry classRegistry; EnumerableSetUpgradeable.AddressSet assetIndex; mapping(address => AssetConfig) assets; } /** * @dev Registers new asset. */ function registerAsset( Registry storage self, bytes4 assetClass, address asset ) external { if (self.assetIndex.add(asset)) { IAssetClassRegistry.ClassConfig memory assetClassConfig = self.classRegistry.assetClassConfig(assetClass); self.assets[asset] = AssetConfig({ controller: IAssetController(assetClassConfig.controller), assetClass: assetClass, vault: IAssetVault(assetClassConfig.vault) }); } } /** * @dev Returns the paginated list of currently registered asset configs. */ function supportedAssets( Registry storage self, uint256 offset, uint256 limit ) external view returns (address[] memory, AssetConfig[] memory) { uint256 indexSize = self.assetIndex.length(); if (offset >= indexSize) return (new address[](0), new AssetConfig[](0)); if (limit > indexSize - offset) { limit = indexSize - offset; } AssetConfig[] memory assetConfigs = new AssetConfig[](limit); address[] memory assetAddresses = new address[](limit); for (uint256 i = 0; i < limit; i++) { assetAddresses[i] = self.assetIndex.at(offset + i); assetConfigs[i] = self.assets[assetAddresses[i]]; } return (assetAddresses, assetConfigs); } /** * @dev Transfers an asset to the vault using associated controller. */ function transferAssetToVault( Registry storage self, Assets.Asset memory asset, address from ) external { // Extract token address from asset struct and check whether the asset is supported. address assetToken = asset.token(); if (!isRegisteredAsset(self, assetToken)) revert AssetIsNotRegistered(assetToken); // Transfer asset to the class asset specific vault. AssetConfig memory assetConfig = self.assets[assetToken]; address assetController = address(assetConfig.controller); address assetVault = address(assetConfig.vault); assetController.functionDelegateCall( abi.encodeWithSelector(IAssetController.transferAssetToVault.selector, asset, from, assetVault) ); } /** * @dev Transfers an asset from the vault using associated controller. */ function returnAssetFromVault(Registry storage self, Assets.Asset calldata asset) external { address assetToken = asset.token(); AssetConfig memory assetConfig = self.assets[assetToken]; address assetController = address(assetConfig.controller); address assetVault = address(assetConfig.vault); assetController.functionDelegateCall( abi.encodeWithSelector(IAssetController.returnAssetFromVault.selector, asset, assetVault) ); } function assetCount(Registry storage self) internal view returns (uint256) { return self.assetIndex.length(); } /** * @dev Checks asset registration by address. */ function isRegisteredAsset(Registry storage self, address asset) internal view returns (bool) { return self.assetIndex.contains(asset); } /** * @dev Returns controller for asset class. * @param assetClass Asset class ID. */ function assetClassController(Registry storage self, bytes4 assetClass) internal view returns (address) { return self.classRegistry.assetClassConfig(assetClass).controller; } }