// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; import "@openzeppelin/contracts/interfaces/IERC721.sol"; import "../../Assets.sol"; import "../../utils/DelegateContext.sol"; import "./ERC721AssetVault.sol"; import "../../AssetController.sol"; import "../ERC721AssetUtils.sol"; /** * @title Asset controller for the ERC721 tokens */ contract ERC721AssetController is AssetController, ERC721AssetUtils, DelegateContext { using Assets for Assets.AssetId; using Assets for Assets.Asset[]; using Address for address; /** * @dev Thrown when the asset value is invalid for ERC721 token standard. */ error InvalidERC721Value(uint256 value); /** * @inheritdoc IAssetController */ function assetClass() external pure returns (bytes4) { return _assetClass(); } /** * @inheritdoc IAssetController */ function transferAssetToVault( Assets.Asset memory asset, address assetOwner, address vault ) external onlyDelegatecall { _transferAsset(asset, assetOwner, vault, ""); } /** * @inheritdoc IAssetController */ function returnAssetFromVault(Assets.Asset calldata asset, address vault) external onlyDelegatecall { _validateAsset(asset); // Decode asset ID to extract identification data. (address token, uint256 tokenId) = _decodeAssetId(asset.id); IERC721AssetVault(vault).returnToOwner(token, tokenId); } /** * @inheritdoc IAssetController */ function transfer( Assets.Asset memory asset, address from, address to, bytes memory data ) external onlyDelegatecall { _transferAsset(asset, from, to, data); } /** * @inheritdoc IAssetController */ function collectionId(Assets.AssetId memory assetId) external pure returns (bytes32) { if (assetId.class != _assetClass()) revert AssetClassMismatch(assetId.class, _assetClass()); return _collectionId(assetId.token()); } /// @dev Ensures asset array is sorted function ensureSorted(Assets.AssetId[] calldata assetIds) external pure { if (assetIds.length < 2) return; address tokenAddress; uint256 currentId; (address previousAddress, uint256 previousId) = _tokenWithId(assetIds[0]); for (uint256 i = 1; i < assetIds.length; i++) { (tokenAddress, currentId) = _tokenWithId(assetIds[i]); if (previousAddress > tokenAddress) revert AssetCollectionMismatch(previousAddress, tokenAddress); if (previousAddress == tokenAddress && previousId >= currentId) { revert AssetOrderMismatch(tokenAddress, previousId, currentId); } previousAddress = tokenAddress; previousId = currentId; } } /** * @dev Executes asset transfer. */ function _transferAsset( Assets.Asset memory asset, address from, address to, bytes memory data ) internal { // Make user the asset is valid before decoding and transferring. _validateAsset(asset); // Decode asset ID to extract identification data, required for transfer. (address token, uint256 tokenId) = _decodeAssetId(asset.id); // Execute safe transfer. IERC721(token).safeTransferFrom(from, to, tokenId, data); emit AssetTransfer(asset, from, to, data); } /** * @dev Reverts if the asset params are not valid. * @param asset Asset structure. */ function _validateAsset(Assets.Asset memory asset) internal pure { // Ensure correct class. if (asset.id.class != _assetClass()) revert AssetClassMismatch(asset.id.class, _assetClass()); // Ensure correct value, must be 1 for NFT. if (asset.value != 1) revert InvalidERC721Value(asset.value); } }