diff --git a/packages/asset/contracts/Asset.sol b/packages/asset/contracts/Asset.sol index 1c2de609f2..eb784d07c0 100644 --- a/packages/asset/contracts/Asset.sol +++ b/packages/asset/contracts/Asset.sol @@ -1,35 +1,23 @@ //SPDX-License-Identifier: MIT pragma solidity 0.8.18; -import { - AccessControlUpgradeable, - ContextUpgradeable -} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; -import { - ERC1155BurnableUpgradeable, - ERC1155Upgradeable -} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155BurnableUpgradeable.sol"; -import { - ERC1155SupplyUpgradeable -} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol"; -import { - ERC1155URIStorageUpgradeable -} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155URIStorageUpgradeable.sol"; +import {AccessControlUpgradeable, ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import {ERC1155BurnableUpgradeable, ERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155BurnableUpgradeable.sol"; +import {ERC1155SupplyUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155SupplyUpgradeable.sol"; +import {ERC1155URIStorageUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/extensions/ERC1155URIStorageUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import { - ERC2771HandlerUpgradeable -} from "@sandbox-smart-contracts/dependency-metatx/contracts/ERC2771HandlerUpgradeable.sol"; -import { - MultiRoyaltyDistributor -} from "@sandbox-smart-contracts/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol"; -import { - OperatorFiltererUpgradeable, - IOperatorFilterRegistry -} from "@sandbox-smart-contracts/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol"; +import {ERC2771HandlerUpgradeable} from "@sandbox-smart-contracts/dependency-metatx/contracts/ERC2771HandlerUpgradeable.sol"; +import {MultiRoyaltyDistributor} from "@sandbox-smart-contracts/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol"; +import {OperatorFiltererUpgradeable, IOperatorFilterRegistry} from "@sandbox-smart-contracts/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol"; import {TokenIdUtils} from "./libraries/TokenIdUtils.sol"; import {IAsset} from "./interfaces/IAsset.sol"; import {ITokenUtils, IRoyaltyUGC} from "./interfaces/ITokenUtils.sol"; +/// @title Asset +/// @author The Sandbox +/// @notice ERC1155 asset token contract +/// @notice Minting and burning tokens is only allowed through separate authorized contracts +/// @dev This contract is final and should not be inherited contract Asset is IAsset, Initializable, @@ -56,6 +44,12 @@ contract Asset is _disableInitializers(); } + /// @notice Initialize the contract + /// @param forwarder The address of the trusted forwarder + /// @param assetAdmin The address of the asset admin + /// @param baseUri The base URI for the token metadata + /// @param commonSubscription The address of the operator filter subscription + /// @param _manager The address of the royalty manager function initialize( address forwarder, address assetAdmin, @@ -78,12 +72,8 @@ contract Asset is /// @param to The address of the recipient /// @param id The id of the token to mint /// @param amount The amount of the token to mint - function mint( - address to, - uint256 id, - uint256 amount, - string memory metadataHash - ) external onlyRole(MINTER_ROLE) { + /// @param metadataHash The metadata hash of the token to mint + function mint(address to, uint256 id, uint256 amount, string memory metadataHash) external onlyRole(MINTER_ROLE) { _setMetadataHash(id, metadataHash); _mint(to, id, amount, ""); address creator = id.getCreatorAddress(); @@ -95,6 +85,7 @@ contract Asset is /// @param to The address of the recipient /// @param ids The ids of the tokens to mint /// @param amounts The amounts of the tokens to mint + /// @param metadataHashes The metadata hashes of the tokens to mint function mintBatch( address to, uint256[] memory ids, @@ -119,11 +110,7 @@ contract Asset is /// @param account The account to burn tokens from /// @param id The token id to burn /// @param amount The amount of tokens to burn - function burnFrom( - address account, - uint256 id, - uint256 amount - ) external onlyRole(BURNER_ROLE) { + function burnFrom(address account, uint256 id, uint256 amount) external onlyRole(BURNER_ROLE) { _burn(account, id, amount); } @@ -159,19 +146,22 @@ contract Asset is /// @notice returns full token URI, including baseURI and token metadata URI /// @param tokenId The token id to get URI for /// @return tokenURI the URI of the token - function uri(uint256 tokenId) - public - view - override(ERC1155Upgradeable, ERC1155URIStorageUpgradeable) - returns (string memory tokenURI) - { + function uri( + uint256 tokenId + ) public view override(ERC1155Upgradeable, ERC1155URIStorageUpgradeable) returns (string memory tokenURI) { return ERC1155URIStorageUpgradeable.uri(tokenId); } + /// @notice returns the tokenId associated with provided metadata hash + /// @param metadataHash The metadata hash to get tokenId for + /// @return tokenId the tokenId associated with the metadata hash function getTokenIdByMetadataHash(string memory metadataHash) public view returns (uint256 tokenId) { return hashUsed[metadataHash]; } + /// @notice sets the metadata hash for a given tokenId + /// @param tokenId The tokenId to set metadata hash for + /// @param metadataHash The metadata hash to set function _setMetadataHash(uint256 tokenId, string memory metadataHash) internal { if (hashUsed[metadataHash] != 0) { require(hashUsed[metadataHash] == tokenId, "Asset: not allowed to reuse metadata hash"); @@ -192,7 +182,9 @@ contract Asset is /// @notice Query if a contract implements interface `id`. /// @param id the interface identifier, as specified in ERC-165. /// @return supported `true` if the contract implements `id`. - function supportsInterface(bytes4 id) + function supportsInterface( + bytes4 id + ) public view virtual @@ -253,12 +245,10 @@ contract Asset is /// @notice Enable or disable approval for `operator` to manage all of the caller's tokens. /// @param operator address which will be granted rights to transfer all tokens of the caller. /// @param approved whether to approve or revoke - function setApprovalForAll(address operator, bool approved) - public - virtual - override - onlyAllowedOperatorApproval(operator) - { + function setApprovalForAll( + address operator, + bool approved + ) public virtual override onlyAllowedOperatorApproval(operator) { _setApprovalForAll(_msgSender(), operator, approved); } @@ -336,22 +326,24 @@ contract Asset is return TokenIdUtils.isBridged(tokenId); } - /// @notice This function is used to register Asset contract on the Operator Filterer Registry of Opensea.can only be called by admin. + /// @notice This function is used to register Asset contract on the Operator Filterer Registry of OpenSea.can only be called by admin. /// @dev used to register contract and subscribe to the subscriptionOrRegistrantToCopy's black list. /// @param subscriptionOrRegistrantToCopy registration address of the list to subscribe. /// @param subscribe bool to signify subscription "true"" or to copy the list "false". - function registerAndSubscribe(address subscriptionOrRegistrantToCopy, bool subscribe) - external - onlyRole(DEFAULT_ADMIN_ROLE) - { + function registerAndSubscribe( + address subscriptionOrRegistrantToCopy, + bool subscribe + ) external onlyRole(DEFAULT_ADMIN_ROLE) { require(subscriptionOrRegistrantToCopy != address(0), "Asset: subscription can't be zero address"); _registerAndSubscribe(subscriptionOrRegistrantToCopy, subscribe); } - /// @notice sets filter registry address deployed in test + /// @notice sets the operator filter registry address /// @param registry the address of the registry function setOperatorRegistry(address registry) external onlyRole(DEFAULT_ADMIN_ROLE) { require(registry != address(0), "Asset: registry can't be zero address"); - operatorFilterRegistry = IOperatorFilterRegistry(registry); + OperatorFiltererUpgradeable._setOperatorFilterRegistry(registry); } + + uint256[49] private __gap; } diff --git a/packages/asset/contracts/AssetCreate.sol b/packages/asset/contracts/AssetCreate.sol index 0173f18bd9..ae96bf7433 100644 --- a/packages/asset/contracts/AssetCreate.sol +++ b/packages/asset/contracts/AssetCreate.sol @@ -54,9 +54,13 @@ contract AssetCreate is } /// @notice Initialize the contract + /// @param _name The name of the contract (for EIP712) + /// @param _version The version of the contract (for EIP712) /// @param _assetContract The address of the asset contract + /// @param _catalystContract The address of the catalyst contract /// @param _authValidator The address of the AuthSuperValidator contract /// @param _forwarder The address of the forwarder contract + /// @param _defaultAdmin The address of the default admin function initialize( string memory _name, string memory _version, @@ -80,7 +84,9 @@ contract AssetCreate is /// @param signature A signature generated by TSB /// @param tier The tier of the asset to mint /// @param amount The amount of the asset to mint + /// @param revealed Whether the asset is revealed or not /// @param metadataHash The metadata hash of the asset to mint + /// @param creator The address of the creator function createAsset( bytes memory signature, uint8 tier, @@ -94,7 +100,7 @@ contract AssetCreate is signature, _hashMint(creator, signatureNonces[_msgSender()]++, tier, amount, revealed, metadataHash) ), - "Invalid signature" + "AssetCreate: Invalid signature" ); uint256 tokenId = @@ -110,7 +116,9 @@ contract AssetCreate is /// @param signature A signature generated by TSB /// @param tiers The tiers of the assets to mint /// @param amounts The amounts of the assets to mint + /// @param revealed Whether the assets are revealed or not /// @param metadataHashes The metadata hashes of the assets to mint + /// @param creator The address of the creator function createMultipleAssets( bytes memory signature, uint8[] calldata tiers, @@ -124,12 +132,12 @@ contract AssetCreate is signature, _hashBatchMint(creator, signatureNonces[_msgSender()]++, tiers, amounts, revealed, metadataHashes) ), - "Invalid signature" + "AssetCreate: Invalid signature" ); - require(tiers.length == amounts.length, "Arrays must be same length"); - require(amounts.length == metadataHashes.length, "Arrays must be same length"); - require(metadataHashes.length == revealed.length, "Arrays must be same length"); + require(tiers.length == amounts.length, "AssetCreate: Arrays must be same length"); + require(amounts.length == metadataHashes.length, "AssetCreate: Arrays must be same length"); + require(metadataHashes.length == revealed.length, "AssetCreate: Arrays must be same length"); uint256[] memory tokenIds = new uint256[](tiers.length); uint256[] memory tiersToBurn = new uint256[](tiers.length); @@ -167,7 +175,7 @@ contract AssetCreate is signature, _hashMint(creator, signatureNonces[_msgSender()]++, 0, amount, true, metadataHash) ), - "Invalid signature" + "AssetCreate: Invalid signature" ); uint256 tokenId = TokenIdUtils.generateTokenId(creator, 0, ++creatorNonces[creator], 1, false); @@ -206,8 +214,10 @@ contract AssetCreate is /// @notice Creates a hash of the mint data /// @param creator The address of the creator + /// @param nonce The nonce of the creator /// @param tier The tier of the asset /// @param amount The amount of copies to mint + /// @param revealed Whether the asset is revealed or not /// @param metadataHash The metadata hash of the asset /// @return digest The hash of the mint data function _hashMint( @@ -235,8 +245,10 @@ contract AssetCreate is /// @notice Creates a hash of the mint batch data /// @param creator The address of the creator + /// @param nonce The nonce of the creator /// @param tiers The tiers of the assets /// @param amounts The amounts of copies to mint + /// @param revealed Whether the assets are revealed or not /// @param metadataHashes The metadata hashes of the assets /// @return digest The hash of the mint batch data function _hashBatchMint( @@ -301,4 +313,6 @@ contract AssetCreate is { return ERC2771HandlerUpgradeable._msgData(); } + + uint256[45] private __gap; } diff --git a/packages/asset/contracts/AuthSuperValidator.sol b/packages/asset/contracts/AuthSuperValidator.sol index 49044c9162..b35f022f2a 100644 --- a/packages/asset/contracts/AuthSuperValidator.sol +++ b/packages/asset/contracts/AuthSuperValidator.sol @@ -42,4 +42,13 @@ contract AuthSuperValidator is AccessControl { address recoveredSigner = ECDSA.recover(digest, signature); return recoveredSigner == signer; } + + /// @notice Prevents the DEFAULT_ADMIN_ROLE from being renounced + /// @dev This function overrides the default renounceRole function to prevent the DEFAULT_ADMIN_ROLE from being renounced + /// @param role Role to renounce + /// @param account Account to renounce the role for + function renounceRole(bytes32 role, address account) public override { + require(role != DEFAULT_ADMIN_ROLE, "AuthSuperValidator: can't renounce admin role"); + super.renounceRole(role, account); + } } diff --git a/packages/asset/contracts/Catalyst.sol b/packages/asset/contracts/Catalyst.sol index 50ffeb4323..fd6f23e3fa 100644 --- a/packages/asset/contracts/Catalyst.sol +++ b/packages/asset/contracts/Catalyst.sol @@ -30,7 +30,7 @@ import {ICatalyst} from "./interfaces/ICatalyst.sol"; /// @title Catalyst /// @author The Sandbox -/// @notice THis contract manages catalysts which are used to mint new assets. +/// @notice This contract manages catalysts which are used to mint new assets. /// @dev An ERC1155 contract that manages catalysts, extends multiple OpenZeppelin contracts to /// provide a variety of features including, AccessControl, URIStorage, Burnable and more. /// The contract includes support for meta transactions. @@ -77,7 +77,7 @@ contract Catalyst is address _defaultMinter, string[] memory _catalystIpfsCID, address _royaltyManager - ) public initializer { + ) external initializer { require(bytes(_baseUri).length != 0, "Catalyst: base uri can't be empty"); require(_trustedForwarder != address(0), "Catalyst: trusted forwarder can't be zero"); require(_subscription != address(0), "Catalyst: subscription can't be zero"); @@ -154,7 +154,7 @@ contract Catalyst is } /// @notice Add a new catalyst type, limited to DEFAULT_ADMIN_ROLE only - /// @param ipfsCID The royalty bps for the catalyst + /// @param ipfsCID The IPFS content identifiers for the catalyst function addNewCatalystType(string memory ipfsCID) external onlyRole(DEFAULT_ADMIN_ROLE) { require(bytes(ipfsCID).length != 0, "Catalyst: CID can't be empty"); uint256 newCatId = ++highestTierIndex; @@ -187,6 +187,7 @@ contract Catalyst is function setBaseURI(string memory baseURI) external onlyRole(DEFAULT_ADMIN_ROLE) { require(bytes(baseURI).length != 0, "Catalyst: base uri can't be empty"); _setBaseURI(baseURI); + emit BaseURISet(baseURI); } /// @notice returns full token URI, including baseURI and token metadata URI @@ -223,12 +224,18 @@ contract Catalyst is return ERC2771HandlerUpgradeable._msgData(); } + /// @dev Sets `baseURI` as the `_baseURI` for all tokens + function _setBaseURI(string memory baseURI) internal virtual override { + super._setBaseURI(baseURI); + emit BaseURISet(baseURI); + } + /// @notice Transfers `value` tokens of type `id` from `from` to `to` (with safety call). /// @param from address from which tokens are transfered. /// @param to address to which the token will be transfered. /// @param id the token type transfered. /// @param value amount of token transfered. - /// @param data aditional data accompanying the transfer. + /// @param data additional data accompanying the transfer. function safeTransferFrom( address from, address to, @@ -245,7 +252,7 @@ contract Catalyst is /// @param to address to which the token will be transfered. /// @param ids ids of each token type transfered. /// @param values amount of each token type transfered. - /// @param data aditional data accompanying the transfer. + /// @param data additional data accompanying the transfer. function safeBatchTransferFrom( address from, address to, @@ -286,7 +293,7 @@ contract Catalyst is return super.supportsInterface(interfaceId); } - /// @notice This function is used to register Catalyst contract on the Operator Filterer Registry of Opensea.can only be called by admin. + /// @notice This function is used to register Catalyst contract on the Operator Filterer Registry of OpenSea. Can only be called by admin. /// @dev used to register contract and subscribe to the subscriptionOrRegistrantToCopy's black list. /// @param subscriptionOrRegistrantToCopy registration address of the list to subscribe. /// @param subscribe bool to signify subscription "true"" or to copy the list "false". @@ -298,10 +305,13 @@ contract Catalyst is _registerAndSubscribe(subscriptionOrRegistrantToCopy, subscribe); } - /// @notice sets filter registry address deployed in test + /// @notice sets filter registry address /// @param registry the address of the registry function setOperatorRegistry(address registry) external onlyRole(DEFAULT_ADMIN_ROLE) { require(registry != address(0), "Catalyst: registry can't be zero address"); - operatorFilterRegistry = IOperatorFilterRegistry(registry); + OperatorFiltererUpgradeable._setOperatorFilterRegistry(registry); + emit OperatorRegistrySet(registry); } + + uint256[49] private __gap; } diff --git a/packages/asset/contracts/interfaces/IAsset.sol b/packages/asset/contracts/interfaces/IAsset.sol index 7ff9f20a23..19f9a6a538 100644 --- a/packages/asset/contracts/interfaces/IAsset.sol +++ b/packages/asset/contracts/interfaces/IAsset.sol @@ -1,6 +1,8 @@ //SPDX-License-Identifier: MIT pragma solidity 0.8.18; +/// @title Asset interface +/// @author The Sandbox interface IAsset { // AssetData reflects the asset tokenId structure // Refer to TokenIdUtils.sol @@ -17,14 +19,20 @@ interface IAsset { event TrustedForwarderChanged(address indexed newTrustedForwarderAddress); - // Functions - function mint( - address to, - uint256 id, - uint256 amount, - string memory metadataHash - ) external; + /// @notice Mint new tokens + /// @dev Only callable by the minter role + /// @param to The address of the recipient + /// @param id The id of the token to mint + /// @param amount The amount of the token to mint + /// @param metadataHash The metadata hash of the token to mint + function mint(address to, uint256 id, uint256 amount, string memory metadataHash) external; + /// @notice Mint new tokens with catalyst tier chosen by the creator + /// @dev Only callable by the minter role + /// @param to The address of the recipient + /// @param ids The ids of the tokens to mint + /// @param amounts The amounts of the tokens to mint + /// @param metadataHashes The metadata hashes of the tokens to mint function mintBatch( address to, uint256[] memory ids, @@ -32,17 +40,25 @@ interface IAsset { string[] memory metadataHashes ) external; - function burnFrom( - address account, - uint256 id, - uint256 amount - ) external; + /// @notice Burn a token from a given account + /// @dev Only the minter role can burn tokens + /// @dev This function was added with token recycling and bridging in mind but may have other use cases + /// @param account The account to burn tokens from + /// @param id The token id to burn + /// @param amount The amount of tokens to burn + function burnFrom(address account, uint256 id, uint256 amount) external; - function burnBatchFrom( - address account, - uint256[] memory ids, - uint256[] memory amounts - ) external; + /// @notice Burn a batch of tokens from a given account + /// @dev Only the minter role can burn tokens + /// @dev This function was added with token recycling and bridging in mind but may have other use cases + /// @dev The length of the ids and amounts arrays must be the same + /// @param account The account to burn tokens from + /// @param ids An array of token ids to burn + /// @param amounts An array of amounts of tokens to burn + function burnBatchFrom(address account, uint256[] memory ids, uint256[] memory amounts) external; + /// @notice returns the tokenId associated with provided metadata hash + /// @param metadataHash The metadata hash to get tokenId for + /// @return tokenId the tokenId associated with the metadata hash function getTokenIdByMetadataHash(string memory metadataHash) external view returns (uint256 tokenId); } diff --git a/packages/asset/contracts/interfaces/IAssetCreate.sol b/packages/asset/contracts/interfaces/IAssetCreate.sol index 8062faa3c9..75b83f3e1f 100644 --- a/packages/asset/contracts/interfaces/IAssetCreate.sol +++ b/packages/asset/contracts/interfaces/IAssetCreate.sol @@ -1,6 +1,8 @@ //SPDX-License-Identifier: MIT pragma solidity 0.8.18; +/// @title AssetCreate interface +/// @author The Sandbox interface IAssetCreate { event TrustedForwarderChanged(address indexed newTrustedForwarderAddress); event AssetMinted( diff --git a/packages/asset/contracts/interfaces/IAssetReveal.sol b/packages/asset/contracts/interfaces/IAssetReveal.sol index d956a87c3b..0f75a8aa0c 100644 --- a/packages/asset/contracts/interfaces/IAssetReveal.sol +++ b/packages/asset/contracts/interfaces/IAssetReveal.sol @@ -1,19 +1,21 @@ //SPDX-License-Identifier: MIT pragma solidity 0.8.18; +/// @title AssetReveal interface +/// @author The Sandbox interface IAssetReveal { event TrustedForwarderChanged(address indexed newTrustedForwarderAddress); - event AssetRevealBurn(address revealer, uint256 unrevealedTokenId, uint256 amount); - event AssetRevealBatchBurn(address revealer, uint256[] unrevealedTokenIds, uint256[] amounts); + event AssetRevealBurn(address indexed revealer, uint256 unrevealedTokenId, uint256 amount); + event AssetRevealBatchBurn(address indexed revealer, uint256[] unrevealedTokenIds, uint256[] amounts); event AssetRevealMint( - address recipient, + address indexed recipient, uint256 unrevealedTokenId, uint256[] amounts, uint256[] newTokenIds, bytes32[] revealHashes ); event AssetRevealBatchMint( - address recipient, + address indexed recipient, uint256[] unrevealedTokenIds, uint256[][] amounts, uint256[][] newTokenIds, diff --git a/packages/asset/contracts/interfaces/ICatalyst.sol b/packages/asset/contracts/interfaces/ICatalyst.sol index 9443651d75..eb1d3e564e 100644 --- a/packages/asset/contracts/interfaces/ICatalyst.sol +++ b/packages/asset/contracts/interfaces/ICatalyst.sol @@ -7,6 +7,8 @@ interface ICatalyst { event TrustedForwarderChanged(address indexed newTrustedForwarderAddress); event NewCatalystTypeAdded(uint256 catalystId); event DefaultRoyaltyChanged(address indexed newDefaultRoyaltyRecipient, uint256 newDefaultRoyaltyAmount); + event BaseURISet(string baseURI); + event OperatorRegistrySet(address indexed registry); /// @notice Mints a new token, limited to MINTER_ROLE only /// @param to The address that will own the minted token diff --git a/packages/asset/contracts/interfaces/ITokenUtils.sol b/packages/asset/contracts/interfaces/ITokenUtils.sol index 83240551db..37b75e9074 100644 --- a/packages/asset/contracts/interfaces/ITokenUtils.sol +++ b/packages/asset/contracts/interfaces/ITokenUtils.sol @@ -3,14 +3,36 @@ pragma solidity 0.8.18; import {IRoyaltyUGC} from "@sandbox-smart-contracts/dependency-royalty-management/contracts/interfaces/IRoyaltyUGC.sol"; +/// @title TokenUtils interface +/// @author The Sandbox interface ITokenUtils is IRoyaltyUGC { + /// @notice Extracts the tier from a given token id + /// @param tokenId The token id to extract the tier from + /// @return tier The asset tier, determined by the catalyst used to create it function getTier(uint256 tokenId) external pure returns (uint8 tier); - function isRevealed(uint256 tokenId) external pure returns (bool); + /// @notice Extracts the revealed flag from a given token id + /// @param tokenId The token id to extract the revealed flag from + /// @return revealed Whether the asset is revealed or not + function isRevealed(uint256 tokenId) external pure returns (bool revealed); - function getCreatorNonce(uint256 tokenId) external pure returns (uint16); + /// @notice Extracts the asset nonce from a given token id + /// @param tokenId The token id to extract the asset nonce from + /// @return creatorNonce The asset creator nonce + function getCreatorNonce(uint256 tokenId) external pure returns (uint16 creatorNonce); - function getRevealNonce(uint256 tokenId) external pure returns (uint16); + /// @notice Extracts the abilities and enhancements hash from a given token id + /// @param tokenId The token id to extract reveal nonce from + /// @return revealNonce The reveal nonce of the asset + function getRevealNonce(uint256 tokenId) external pure returns (uint16 revealNonce); - function isBridged(uint256 tokenId) external pure returns (bool); + /// @notice Extracts the bridged flag from a given token id + /// @param tokenId The token id to extract the bridged flag from + /// @return bridged Whether the asset is bridged or not + function isBridged(uint256 tokenId) external pure returns (bool bridged); + + /// @notice Extracts the creator address from a given token id + /// @param tokenId The token id to extract the creator address from + /// @return creator The asset creator address + function getCreatorAddress(uint256 tokenId) external pure returns (address creator); } diff --git a/packages/asset/contracts/libraries/TokenIdUtils.sol b/packages/asset/contracts/libraries/TokenIdUtils.sol index 4faedb6b51..946ea1bf47 100644 --- a/packages/asset/contracts/libraries/TokenIdUtils.sol +++ b/packages/asset/contracts/libraries/TokenIdUtils.sol @@ -3,6 +3,9 @@ pragma solidity ^0.8.0; import {IAsset} from "../interfaces/IAsset.sol"; +/// @title TokenIdUtils library +/// @author The Sandbox +/// @notice Contains utility functions for token ids library TokenIdUtils { // Layer masks uint256 public constant TIER_MASK = 0xFF; @@ -19,12 +22,12 @@ library TokenIdUtils { /// @notice Generates a token id for a given asset /// @dev The token id is generated by concatenating the following fields: - /// @dev creator address, chain index, tier, asset nonce, reveal nonce and bridged boolean + /// @dev creator address, tier, creator nonce, reveal nonce and bridged boolean /// @dev The first 160 bits are the creator address - /// @dev The next 8 bits are the chain index /// @dev The next 8 bits are the tier - /// @dev The next 16 bits are the asset nonce - /// @dev The next 16 bits are assets reveal nonce. + /// @dev The next 16 bits are the creator nonce + /// @dev The next 16 bits are for reveal nonce. + /// @dev The last bit is for bridged boolean /// @param creator The address of the creator of the asset /// @param tier The tier of the asset determined by the catalyst used to create it /// @param creatorNonce The nonce of the asset creator @@ -101,6 +104,7 @@ library TokenIdUtils { /// @notice Extracts the asset data from a given token id /// @dev Created to limit the number of functions that need to be called when revealing an asset /// @param tokenId The token id to extract the asset data from + /// @return data The asset data struct function getData(uint256 tokenId) internal pure returns (IAsset.AssetData memory data) { data.creator = getCreatorAddress(tokenId); data.tier = getTier(tokenId); diff --git a/packages/asset/contracts/mock/MockAsset.sol b/packages/asset/contracts/mock/MockAsset.sol index c0b42d137a..7c17db6a59 100644 --- a/packages/asset/contracts/mock/MockAsset.sol +++ b/packages/asset/contracts/mock/MockAsset.sol @@ -13,6 +13,8 @@ contract MockAsset is Asset { /// @param registry address of registry /// @param subscription address to subscribe function setRegistryAndSubscribe(address registry, address subscription) external { + _setOperatorFilterRegistry(registry); + IOperatorFilterRegistry operatorFilterRegistry = _getOperatorFilterRegistry(); operatorFilterRegistry = IOperatorFilterRegistry(registry); operatorFilterRegistry.registerAndSubscribe(address(this), subscription); } diff --git a/packages/asset/contracts/mock/MockCatalyst.sol b/packages/asset/contracts/mock/MockCatalyst.sol index 28fb0146a6..acbd616904 100644 --- a/packages/asset/contracts/mock/MockCatalyst.sol +++ b/packages/asset/contracts/mock/MockCatalyst.sol @@ -9,6 +9,8 @@ contract MockCatalyst is Catalyst { /// @param registry address of registry /// @param subscription address to subscribe function setRegistryAndSubscribe(address registry, address subscription) external { + _setOperatorFilterRegistry(registry); + IOperatorFilterRegistry operatorFilterRegistry = _getOperatorFilterRegistry(); operatorFilterRegistry = IOperatorFilterRegistry(registry); operatorFilterRegistry.registerAndSubscribe(address(this), subscription); } diff --git a/packages/asset/docs/Asset.md b/packages/asset/docs/Asset.md index 678f8249de..aede9fb989 100644 --- a/packages/asset/docs/Asset.md +++ b/packages/asset/docs/Asset.md @@ -176,6 +176,7 @@ Sets a new trusted forwarder for meta-transactions. Parameters: - `trustedForwarder` - The new trusted forwarder. + ### setTokenRoyalties ```solidity @@ -183,7 +184,7 @@ function setTokenRoyalties( uint256 tokenId, address payable recipient, address creator -) external override onlyRole(DEFAULT_ADMIN_ROLE) +) external override onlyRole(DEFAULT_ADMIN_ROLE) ``` Sets token royalty i.e. the creator splitter address as EIP2981 royalty recipient. deploys a splitter if there is none deployed for a creator. Only admin can call it. @@ -254,7 +255,6 @@ Parameters: - `tokenId` - the id of the token. - ### isBridged ```solidity @@ -275,7 +275,7 @@ function registerAndSubscribe(address subscriptionOrRegistrantToCopy, bool subsc onlyRole(DEFAULT_ADMIN_ROLE) ``` -Used to register and subscribe on the operator filter registry of Opensea +Used to register and subscribe on the operator filter registry of OpenSea Parameters: @@ -286,7 +286,7 @@ Parameters: ```solidity function setOperatorRegistry(address registry) external onlyRole(DEFAULT_ADMIN_ROLE) -```fu +``` Used to the address of the operator filter registry diff --git a/packages/asset/test/Asset.test.ts b/packages/asset/test/Asset.test.ts index 79197938a8..57a5bea541 100644 --- a/packages/asset/test/Asset.test.ts +++ b/packages/asset/test/Asset.test.ts @@ -1025,7 +1025,7 @@ describe('Base Asset Contract (/packages/asset/contracts/Asset.sol)', function ( await Asset.connect(assetAdmin).setOperatorRegistry( operatorFilterRegistry.address ); - expect(await Asset.operatorFilterRegistry()).to.be.equals( + expect(await Asset.getOperatorFilterRegistry()).to.be.equals( operatorFilterRegistry.address ); @@ -1616,6 +1616,7 @@ describe('Base Asset Contract (/packages/asset/contracts/Asset.sol)', function ( it('it should not setApprovalForAll blacklisted market places', async function () { const {mockMarketPlace1, users} = await setupOperatorFilter(); + await expect( users[0].Asset.setApprovalForAll(mockMarketPlace1.address, true) ).to.be.reverted; diff --git a/packages/asset/test/AssetCreate.test.ts b/packages/asset/test/AssetCreate.test.ts index dd88d1a906..789a42ef84 100644 --- a/packages/asset/test/AssetCreate.test.ts +++ b/packages/asset/test/AssetCreate.test.ts @@ -405,7 +405,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () await expect( mintSingleAsset(signature, 4, 1, true, metadataHashes[0]) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if tier mismatches signed tier', async function () { const { @@ -428,7 +428,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () await expect( mintSingleAsset(signature, txSuppliedTier, 1, true, metadataHashes[0]) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if amount mismatches signed amount', async function () { const { @@ -457,7 +457,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () true, metadataHashes[0] ) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if sender is not the creator for which the signature was generated', async function () { const { @@ -478,7 +478,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () await expect( mintSingleAsset(signature, 4, 1, true, metadataHashes[0]) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if metadataHash mismatches signed metadataHash', async function () { const { @@ -499,7 +499,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () await expect( mintSingleAsset(signature, 4, 1, true, '0x1234') - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if the signature has been used before', async function () { const { @@ -524,7 +524,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () await expect( mintSingleAsset(signature, 4, 1, true, metadataHashes[0]) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it("should revert if user doesn't have enough catalysts", async function () { const { @@ -896,7 +896,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () [true, true], metadataHashes ) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if tiers mismatch signed values', async function () { const { @@ -924,7 +924,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () [true, true], metadataHashes ) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if sender is not the creator for which the signature was generated', async function () { const { @@ -952,7 +952,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () [true, true], metadataHashes ) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if tiers, amounts and metadatahashes are not of the same length', async function () { const { @@ -981,7 +981,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () [true, true], [...metadataHashes, additionalMetadataHash] ) - ).to.be.revertedWith('Arrays must be same length'); + ).to.be.revertedWith('AssetCreate: Arrays must be same length'); }); it('should revert if amounts mismatch signed values', async function () { const { @@ -1009,7 +1009,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () [true, true], metadataHashes ) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if metadataHashes mismatch signed values', async function () { const { @@ -1038,7 +1038,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () [true, true], [metadataHashes[1], additionalMetadataHash] ) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it('should revert if signature has already been used', async function () { const { @@ -1073,7 +1073,7 @@ describe('AssetCreate (/packages/asset/contracts/AssetCreate.sol)', function () [true, true], metadataHashes ) - ).to.be.revertedWith('Invalid signature'); + ).to.be.revertedWith('AssetCreate: Invalid signature'); }); it("should revert if user doesn't have enough catalysts", async function () { const { diff --git a/packages/asset/test/AssetReveal.test.ts b/packages/asset/test/AssetReveal.test.ts index bf02e2f2d2..6f518830ea 100644 --- a/packages/asset/test/AssetReveal.test.ts +++ b/packages/asset/test/AssetReveal.test.ts @@ -2,7 +2,8 @@ import {expect} from 'chai'; import {formatBytes32String} from 'ethers/lib/utils'; import {runRevealTestSetup} from './fixtures/asset/assetRevealFixtures'; import {ethers} from 'hardhat'; -import {BigNumber, Event} from 'ethers'; +import {BigNumber} from 'ethers'; +import {findAllEventsByName, findEventByName} from './utils/events'; const revealHashA = formatBytes32String('revealHashA'); const revealHashB = formatBytes32String('revealHashB'); @@ -66,8 +67,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes1, [revealHashA] ); - expect(result.events[3].event).to.equal('AssetRevealMint'); - const newTokenId = result.events[3].args.newTokenIds[0]; + const event = findEventByName(result.events, 'AssetRevealMint'); + expect(event).to.not.be.undefined; + const newTokenId = event?.args?.newTokenIds[0]; const revealNonce = await TokenIdUtilsContract.getRevealNonce(newTokenId); expect(revealNonce.toString()).to.equal('1'); @@ -86,8 +88,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashB] ); - expect(result2.events[3].event).to.equal('AssetRevealMint'); - const newTokenId2 = result2.events[3].args.newTokenIds[0]; + const event2 = findEventByName(result2.events, 'AssetRevealMint'); + expect(event2).to.not.be.undefined; + const newTokenId2 = event2?.args?.newTokenIds[0]; const revealNonce2 = await TokenIdUtilsContract.getRevealNonce( newTokenId2 ); @@ -122,8 +125,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () sameMetadataHash, [revealHashA] ); - expect(result.events[3].event).to.equal('AssetRevealMint'); - const newTokenId = result.events[3].args.newTokenIds[0]; + const event = findEventByName(result.events, 'AssetRevealMint'); + expect(event).to.not.be.undefined; + const newTokenId = event?.args?.newTokenIds[0]; const revealNonce = await TokenIdUtilsContract.getRevealNonce(newTokenId); expect(revealNonce.toString()).to.equal('1'); @@ -142,8 +146,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashB] ); - expect(result2.events[2].event).to.equal('AssetRevealMint'); - const newTokenId2 = result2.events[2].args.newTokenIds[0]; + const event2 = findEventByName(result2.events, 'AssetRevealMint'); + const newTokenId2 = event2?.args?.newTokenIds[0]; const revealNonce2 = await TokenIdUtilsContract.getRevealNonce( newTokenId2 ); @@ -475,14 +479,14 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () 1 ); const burnResult = await burnTx.wait(); - const burnEvent = burnResult.events[1]; - expect(burnEvent.event).to.equal('AssetRevealBurn'); + const burnEvent = findEventByName(burnResult.events, 'AssetRevealBurn'); + expect(burnEvent).to.not.be.undefined; // revealer - expect(burnEvent.args[0]).to.equal(user.address); + expect(burnEvent?.args?.revealer).to.equal(user.address); // token id that is being revealed - expect(burnEvent.args[1]).to.equal(unrevealedtokenId); + expect(burnEvent?.args?.unrevealedTokenId).to.equal(unrevealedtokenId); // amount - expect(burnEvent.args[2].toString()).to.equal('1'); + expect(burnEvent?.args?.amount.toString()).to.equal('1'); }); it('should emit AssetRevealBatchBurn event with correct data when burning multiple tokens', async function () { const { @@ -496,16 +500,19 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [1, 2] ); const burnResult = await burnTx.wait(); - const burnEvent = burnResult.events[1]; - expect(burnEvent.event).to.equal('AssetRevealBatchBurn'); + const burnEvent = findEventByName( + burnResult.events, + 'AssetRevealBatchBurn' + ); + expect(burnEvent).to.not.be.undefined; // revealer - expect(burnEvent.args[0]).to.equal(user.address); + expect(burnEvent?.args?.revealer).to.equal(user.address); // token ids that are being revealed - expect(burnEvent.args[1].toString()).to.equal( + expect(burnEvent?.args?.unrevealedTokenIds.toString()).to.equal( [unrevealedtokenId, unrevealedtokenId2].toString() ); // amount - expect(burnEvent.args[2].toString()).to.equal([1, 2].toString()); + expect(burnEvent?.args?.amounts.toString()).to.equal([1, 2].toString()); }); }); }); @@ -648,8 +655,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes, [revealHashA] ); - expect(result.events[3].event).to.equal('AssetRevealMint'); - const newTokenId = result.events[3].args.newTokenIds[0]; + const event = findEventByName(result.events, 'AssetRevealMint'); + expect(event).to.not.be.undefined; + const newTokenId = event?.args?.newTokenIds[0]; const balance = await AssetContract.balanceOf( user.address, newTokenId @@ -682,9 +690,10 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes, [revealHashA] ); - expect(result.events[3].event).to.equal('AssetRevealMint'); - expect(result.events[3].args['newTokenIds'].length).to.equal(1); - const newTokenId = result.events[3].args.newTokenIds[0]; + const event = findEventByName(result.events, 'AssetRevealMint'); + expect(event).to.not.be.undefined; + expect(event?.args?.newTokenIds.length).to.equal(1); + const newTokenId = event?.args?.newTokenIds[0]; const balance = await AssetContract.balanceOf( user.address, newTokenId @@ -717,7 +726,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes, [revealHashA] ); - const newTokenId = result.events[3].args.newTokenIds[0]; + const event = findEventByName(result.events, 'AssetRevealMint'); + const newTokenId = event?.args?.newTokenIds[0]; const balance = await AssetContract.balanceOf( user.address, newTokenId @@ -788,8 +798,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () revealHashF, ] ); - expect(result.events[13].event).to.equal('AssetRevealMint'); - expect(result.events[13].args['newTokenIds'].length).to.equal(6); + const event = findEventByName(result.events, 'AssetRevealMint'); + expect(event).to.not.be.undefined; + expect(event?.args?.newTokenIds.length).to.equal(6); }); it('should set the reveal hash as used after successful mint', async function () { const { @@ -954,7 +965,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes, [revealHashA, revealHashB] ); - expect(result.events[5].event).to.equal('AssetRevealMint'); + const event = findEventByName(result.events, 'AssetRevealMint'); + expect(event).to.not.be.undefined; }); it('should emit AssetRevealMint event with correct arguments', async function () { const { @@ -984,8 +996,12 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashA, revealHashB] ); - expect(result.events[5].event).to.equal('AssetRevealMint'); - const args = result.events[5].args; + const event = findEventByName(result.events, 'AssetRevealMint'); + expect(event).to.not.be.undefined; + const args = event?.args; + if (!args) { + expect.fail('Event args are undefined'); + } const { recipient, unrevealedTokenId, @@ -1037,9 +1053,7 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () ); // expect one batch reveal event - const event = result.events.find( - (e: Event) => e.event === 'AssetRevealBatchMint' - ); + const event = findEventByName(result.events, 'AssetRevealBatchMint'); expect(event).to.not.be.undefined; }); it("should allow batch reveal of the same token's copies", async function () { @@ -1082,11 +1096,12 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () ] ); - const batchRevealMintEvent: Event = result.events.find( - (e: Event) => e.event === 'AssetRevealBatchMint' + const batchRevealMintEvent = findEventByName( + result.events, + 'AssetRevealBatchMint' ); - const newTokenIds = batchRevealMintEvent.args?.newTokenIds; + const newTokenIds = batchRevealMintEvent?.args?.newTokenIds; const allNewTokenIds = newTokenIds.flat(); const idsAsStrings = allNewTokenIds.map((id: BigNumber) => @@ -1255,7 +1270,7 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () }); }); describe('Events', function () { - it('should emit multiple AssetRevealMint events when successully revealed multiple tokens', async function () { + it('should emit multiple AssetRevealBatchMint events when successully revealed multiple tokens', async function () { const { user, generateBatchRevealSignature, @@ -1284,12 +1299,13 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [[revealHashA], [revealHashB]] ); - const revealEvents = result.events.filter( - (event: Event) => event.event === 'AssetRevealBatchMint' + const revealEvents = findAllEventsByName( + result.events, + 'AssetRevealBatchMint' ); expect(revealEvents.length).to.equal(1); }); - it('should emit AssetRevealMint events with correct arguments when successully revealed multiple tokens', async function () { + it('should emit AssetRevealBatchMint events with correct arguments when successully revealed multiple tokens', async function () { const { user, generateBatchRevealSignature, @@ -1317,19 +1333,21 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [newMetadataHashes1, newMetadataHashes2], [[revealHashA], [revealHashB]] ); - const revealEvents = result.events.filter( - (event: Event) => event.event === 'AssetRevealBatchMint' + const revealEvents = findAllEventsByName( + result.events, + 'AssetRevealBatchMint' ); - const args1 = revealEvents[0].args; + expect(revealEvents.length).to.equal(1); + const args = revealEvents[0].args; - expect(args1['recipient']).to.equal(user.address); - expect(args1['unrevealedTokenIds']).to.deep.equal([ + expect(args?.recipient).to.equal(user.address); + expect(args?.unrevealedTokenIds).to.deep.equal([ unrevealedtokenId, unrevealedtokenId2, ]); - expect(args1['amounts']).to.deep.equal([amounts1, amounts2]); - expect(args1['newTokenIds'].length).to.equal(2); - expect(args1['revealHashes']).to.deep.equal([ + expect(args?.amounts).to.deep.equal([amounts1, amounts2]); + expect(args?.newTokenIds.length).to.equal(2); + expect(args?.revealHashes).to.deep.equal([ [revealHashA], [revealHashB], ]); @@ -1366,8 +1384,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHash, [revealHashA] ); - const revealMintEvent = result.events.filter( - (event: Event) => event.event === 'AssetRevealMint' + const revealMintEvent = findAllEventsByName( + result.events, + 'AssetRevealMint' )[0]; expect(revealMintEvent).to.not.be.undefined; }); @@ -1498,16 +1517,18 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHash, [revealHashA] ); - const revealMintEvent = result.events.filter( - (event: Event) => event.event === 'AssetRevealMint' + const revealMintEvent = findAllEventsByName( + result.events, + 'AssetRevealMint' )[0]; - expect(revealMintEvent.args['recipient']).to.equal(user.address); - expect(revealMintEvent.args['unrevealedTokenId']).to.equal( + expect(revealMintEvent).to.not.be.undefined; + expect(revealMintEvent?.args?.['recipient']).to.equal(user.address); + expect(revealMintEvent?.args?.['unrevealedTokenId']).to.equal( unrevealedtokenId ); - expect(revealMintEvent.args['amounts']).to.deep.equal(amounts); - expect(revealMintEvent.args['newTokenIds'].length).to.equal(1); - expect(revealMintEvent.args['revealHashes']).to.deep.equal([ + expect(revealMintEvent?.args?.['amounts']).to.deep.equal(amounts); + expect(revealMintEvent?.args?.['newTokenIds'].length).to.equal(1); + expect(revealMintEvent?.args?.['revealHashes']).to.deep.equal([ revealHashA, ]); }); diff --git a/packages/asset/test/AssetRoyalty.test.ts b/packages/asset/test/AssetRoyalty.test.ts index 4027ddff38..4c859d75ec 100644 --- a/packages/asset/test/AssetRoyalty.test.ts +++ b/packages/asset/test/AssetRoyalty.test.ts @@ -39,7 +39,7 @@ describe('Asset Royalties', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( creator.address ); @@ -103,7 +103,7 @@ describe('Asset Royalties', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( creator.address ); @@ -170,7 +170,7 @@ describe('Asset Royalties', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( creator.address ); @@ -416,7 +416,7 @@ describe('Asset Royalties', function () { const id = generateAssetId(creator.address, 1); await assetAsMinter.mint(seller.address, id, 1, '0x'); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( creator.address ); @@ -425,7 +425,7 @@ describe('Asset Royalties', function () { splitter ); - expect(await splitterContract._recipient()).to.be.equal(creator.address); + expect(await splitterContract.recipient()).to.be.equal(creator.address); const tnx = await RoyaltyManagerContract.connect( await ethers.getSigner(creator.address) @@ -433,7 +433,7 @@ describe('Asset Royalties', function () { await tnx.wait(); - expect(await splitterContract._recipient()).to.be.equal( + expect(await splitterContract.recipient()).to.be.equal( royaltyReceiver.address ); @@ -666,7 +666,7 @@ describe('Asset Royalties', function () { const id = generateAssetId(creator.address, 1); await assetAsMinter.mint(seller.address, id, 1, '0x'); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( creator.address ); const splitterContract = await ethers.getContractAt( @@ -674,14 +674,14 @@ describe('Asset Royalties', function () { splitter ); - expect(await splitterContract._recipient()).to.be.equal(creator.address); + expect(await splitterContract.recipient()).to.be.equal(creator.address); const tnx = await RoyaltyManagerContract.connect( await ethers.getSigner(creator.address) ).setRoyaltyRecipient(royaltyReceiver.address); await tnx.wait(); - expect(await splitterContract._recipient()).to.be.equal( + expect(await splitterContract.recipient()).to.be.equal( royaltyReceiver.address ); @@ -828,37 +828,6 @@ describe('Asset Royalties', function () { }); }); - it('Can view all the royalty recipient of each asset', async function () { - const { - Asset, - seller, - commonRoyaltyReceiver, - creator, - deployer, - assetAsMinter, - } = await assetRoyaltyDistribution(); - - const id = generateAssetId(creator.address, 1); - await assetAsMinter.mint(seller.address, id, 1, '0x'); - const id2 = generateAssetId(deployer.address, 1); - await assetAsMinter.mint(seller.address, id2, 1, '0x01'); - const tokenRoyalties = await Asset.getTokenRoyalties(); - expect(tokenRoyalties[0].tokenId).to.be.equal(id); - expect(tokenRoyalties[0].recipients[0].recipient).to.be.equal( - creator.address - ); - expect(tokenRoyalties[0].recipients[1].recipient).to.be.equal( - commonRoyaltyReceiver.address - ); - expect(tokenRoyalties[1].tokenId).to.be.equal(id2); - expect(tokenRoyalties[1].recipients[0].recipient).to.be.equal( - deployer.address - ); - expect(tokenRoyalties[1].recipients[1].recipient).to.be.equal( - commonRoyaltyReceiver.address - ); - }); - describe('Roles on Asset and Manager contract', function () { it('creator could change the recipient for his splitter', async function () { const {seller, RoyaltyManagerContract, creator, assetAsMinter} = @@ -866,7 +835,7 @@ describe('Asset Royalties', function () { const id = generateAssetId(creator.address, 1); await assetAsMinter.mint(seller.address, id, 1, '0x'); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( creator.address ); const splitterContract = await ethers.getContractAt( @@ -874,12 +843,12 @@ describe('Asset Royalties', function () { splitter ); - expect(await splitterContract._recipient()).to.be.equal(creator.address); + expect(await splitterContract.recipient()).to.be.equal(creator.address); const tnx = await RoyaltyManagerContract.connect( await ethers.getSigner(creator.address) ).setRoyaltyRecipient(seller.address); await tnx.wait(); - expect(await splitterContract._recipient()).to.be.equal(seller.address); + expect(await splitterContract.recipient()).to.be.equal(seller.address); }); it('only creator could change the recipient for his splitter', async function () { @@ -942,10 +911,10 @@ describe('Asset Royalties', function () { const id1 = generateAssetId(creator.address, 1); await assetAsMinter.mint(seller.address, id1, 1, '0x'); - const splitter1 = await Asset._tokenRoyaltiesSplitter(id1); + const splitter1 = await Asset.getTokenRoyaltiesSplitter(id1); const id2 = generateAssetId(creator.address, 2); await assetAsMinter.mint(seller.address, id2, 1, '0x01'); - const splitter2 = await Asset._tokenRoyaltiesSplitter(id2); + const splitter2 = await Asset.getTokenRoyaltiesSplitter(id2); expect(splitter1).to.be.equal(splitter2); }); @@ -955,10 +924,10 @@ describe('Asset Royalties', function () { const id1 = generateAssetId(creator.address, 1); await assetAsMinter.mint(seller.address, id1, 1, '0x'); - const splitter1 = await Asset._tokenRoyaltiesSplitter(id1); + const splitter1 = await Asset.getTokenRoyaltiesSplitter(id1); const id2 = generateAssetId(deployer.address, 2); await assetAsMinter.mint(seller.address, id2, 1, '0x01'); - const splitter2 = await Asset._tokenRoyaltiesSplitter(id2); + const splitter2 = await Asset.getTokenRoyaltiesSplitter(id2); expect(splitter1).to.not.be.equal(splitter2); }); @@ -974,8 +943,8 @@ describe('Asset Royalties', function () { [1, 1], ['0x', '0x01'] ); - const splitter2 = await Asset._tokenRoyaltiesSplitter(id2); - const splitter1 = await Asset._tokenRoyaltiesSplitter(id1); + const splitter2 = await Asset.getTokenRoyaltiesSplitter(id2); + const splitter1 = await Asset.getTokenRoyaltiesSplitter(id1); expect(splitter1).to.be.equal(splitter2); }); @@ -991,8 +960,8 @@ describe('Asset Royalties', function () { [1, 1], ['0x', '0x01'] ); - const splitter2 = await Asset._tokenRoyaltiesSplitter(id2); - const splitter1 = await Asset._tokenRoyaltiesSplitter(id1); + const splitter2 = await Asset.getTokenRoyaltiesSplitter(id2); + const splitter1 = await Asset.getTokenRoyaltiesSplitter(id1); expect(splitter1).to.not.be.equal(splitter2); }); @@ -1002,7 +971,7 @@ describe('Asset Royalties', function () { const id = generateAssetId(deployer.address, 2); await assetAsMinter.mint(seller.address, id, 1, '0x'); - const splitter = await Asset._tokenRoyaltiesSplitter(id); + const splitter = await Asset.getTokenRoyaltiesSplitter(id); const royaltyInfo = await Asset.royaltyInfo(id, 10000); expect(splitter).to.be.equal(royaltyInfo[0]); }); diff --git a/packages/asset/test/AuthSuperValidator.test.ts b/packages/asset/test/AuthSuperValidator.test.ts index fab56547c2..5047bf7bd7 100644 --- a/packages/asset/test/AuthSuperValidator.test.ts +++ b/packages/asset/test/AuthSuperValidator.test.ts @@ -14,6 +14,17 @@ describe('AuthSuperValidator, (/packages/asset/contracts/AuthSuperValidator.sol) ); expect(hasRole).to.equal(true); }); + it('should not allow DEFAULT_ADMIN_ROLE to be renounced', async function () { + const {authValidatorAdmin, AuthValidatorContract} = await runSetup(); + const DEFAULT_ADMIN_ROLE = + await AuthValidatorContract.DEFAULT_ADMIN_ROLE(); + await expect( + AuthValidatorContract.renounceRole( + DEFAULT_ADMIN_ROLE, + authValidatorAdmin.address + ) + ).to.be.revertedWith("AuthSuperValidator: can't renounce admin role"); + }); it('should allow admin to set signer for a given contract address', async function () { const {MockContract, AuthValidatorContractAsAdmin, backendSigner} = await runSetup(); diff --git a/packages/asset/test/Catalyst.test.ts b/packages/asset/test/Catalyst.test.ts index 81c14b67aa..ae2efd6f58 100644 --- a/packages/asset/test/Catalyst.test.ts +++ b/packages/asset/test/Catalyst.test.ts @@ -384,6 +384,14 @@ describe('Catalyst (/packages/asset/contracts/Catalyst.sol)', function () { `AccessControl: account ${user1.address.toLocaleLowerCase()} is missing role ${catalystAdminRole}` ); }); + it('emits BaseURISet event on setting base uri', async function () { + const {catalystAsAdmin, catalyst} = await runCatalystSetup(); + + const setBaseURITx = await catalystAsAdmin.setBaseURI('ipfs////'); + await expect(setBaseURITx) + .to.emit(catalyst, 'BaseURISet') + .withArgs(`ipfs////`); + }); it('cant add invalid token uri', async function () { const {catalystAsAdmin} = await runCatalystSetup(); await expect(catalystAsAdmin.addNewCatalystType('')).to.be.revertedWith( @@ -605,6 +613,24 @@ describe('Catalyst (/packages/asset/contracts/Catalyst.sol)', function () { expect(await catalyst.balanceOf(user1.address, 1)).to.be.equal(3); expect(await catalyst.balanceOf(user1.address, 2)).to.be.equal(4); }); + it('should fail on burning non existing token', async function () { + const {catalystAsBurner, user1} = await runCatalystSetup(); + await expect( + catalystAsBurner.burnFrom(user1.address, 1, 1) + ).to.be.revertedWith('ERC1155: burn amount exceeds totalSupply'); + }); + it('should fail on batch burning non existing tokens', async function () { + const {catalystAsBurner, user1} = await runCatalystSetup(); + const catalystId = [1, 2]; + const catalystAmount = [2, 2]; + await expect( + catalystAsBurner.burnBatchFrom( + user1.address, + catalystId, + catalystAmount + ) + ).to.be.revertedWith('ERC1155: burn amount exceeds totalSupply'); + }); }); describe('Metadata', function () { it("user can view token's metadata", async function () { @@ -670,6 +696,31 @@ describe('Catalyst (/packages/asset/contracts/Catalyst.sol)', function () { expect(await catalyst.balanceOf(user2.address, 1)).to.be.equal(10); expect(await catalyst.balanceOf(user2.address, 2)).to.be.equal(10); }); + it('should fail on transfering non existing token', async function () { + const {catalyst, user1, user2} = await runCatalystSetup(); + + await expect( + catalyst + .connect(user1) + .safeTransferFrom(user1.address, user2.address, 1, 1, '0x') + ).to.be.revertedWith('ERC1155: insufficient balance for transfer'); + }); + it('should fail on batch transfering non existing tokens', async function () { + const {catalyst, user1, user2} = await runCatalystSetup(); + const catalystId = [1, 2]; + const catalystAmount = [2, 2]; + await expect( + catalyst + .connect(user1) + .safeBatchTransferFrom( + user1.address, + user2.address, + catalystId, + catalystAmount, + '0x' + ) + ).to.be.revertedWith('ERC1155: insufficient balance for transfer'); + }); }); describe('OperatorFilterer', function () { describe('common subscription setup', function () { @@ -709,7 +760,7 @@ describe('Catalyst (/packages/asset/contracts/Catalyst.sol)', function () { await catalyst .connect(catalystAdmin) .setOperatorRegistry(operatorFilterRegistry.address); - expect(await catalyst.operatorFilterRegistry()).to.be.equals( + expect(await catalyst.getOperatorFilterRegistry()).to.be.equals( operatorFilterRegistry.address ); diff --git a/packages/asset/test/Splitter.abi.ts b/packages/asset/test/Splitter.abi.ts index 330b79049a..3d0d752164 100644 --- a/packages/asset/test/Splitter.abi.ts +++ b/packages/asset/test/Splitter.abi.ts @@ -76,17 +76,17 @@ export const splitterAbi = [ type: 'event', }, { - inputs: [], - name: '_recipient', - outputs: [ + anonymous: false, + inputs: [ { - internalType: 'address payable', - name: '', + indexed: true, + internalType: 'address', + name: 'recipientAddress', type: 'address', }, ], - stateMutability: 'view', - type: 'function', + name: 'RecipientSet', + type: 'event', }, { inputs: [], @@ -117,12 +117,12 @@ export const splitterAbi = [ inputs: [ { internalType: 'address payable', - name: 'recipient', + name: '_recipient', type: 'address', }, { internalType: 'address', - name: 'sandBoxRegistry', + name: '_royaltyManager', type: 'address', }, ], @@ -131,6 +131,25 @@ export const splitterAbi = [ stateMutability: 'nonpayable', type: 'function', }, + { + inputs: [ + { + internalType: 'address', + name: 'forwarder', + type: 'address', + }, + ], + name: 'isTrustedForwarder', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [], name: 'owner', @@ -145,21 +164,16 @@ export const splitterAbi = [ type: 'function', }, { - inputs: [ + inputs: [], + name: 'recipient', + outputs: [ { internalType: 'address payable', - name: 'target', + name: '', type: 'address', }, - { - internalType: 'bytes', - name: 'callData', - type: 'bytes', - }, ], - name: 'proxyCall', - outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'view', type: 'function', }, { @@ -169,6 +183,19 @@ export const splitterAbi = [ stateMutability: 'nonpayable', type: 'function', }, + { + inputs: [], + name: 'royaltyManager', + outputs: [ + { + internalType: 'contract IRoyaltyManager', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [ { @@ -211,7 +238,7 @@ export const splitterAbi = [ inputs: [], name: 'splitETH', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', type: 'function', }, { diff --git a/packages/asset/test/fixtures/asset/assetCreateFixtures.ts b/packages/asset/test/fixtures/asset/assetCreateFixtures.ts index 8d9526e2d8..a26123f61c 100644 --- a/packages/asset/test/fixtures/asset/assetCreateFixtures.ts +++ b/packages/asset/test/fixtures/asset/assetCreateFixtures.ts @@ -93,7 +93,13 @@ export async function runCreateTestSetup() { ); await AssetContract.deployed(); - + const RoyaltyManagerAsAdmin = RoyaltyManagerContract.connect(managerAdmin); + const splitterDeployerRole = + await RoyaltyManagerContract.SPLITTER_DEPLOYER_ROLE(); + await RoyaltyManagerAsAdmin.grantRole( + splitterDeployerRole, + AssetContract.address + ); const CatalystFactory = await ethers.getContractFactory('Catalyst'); const CatalystContract = await upgrades.deployProxy( CatalystFactory, diff --git a/packages/asset/test/fixtures/asset/assetFixture.ts b/packages/asset/test/fixtures/asset/assetFixture.ts index 23292d63a8..7ecff10e3a 100644 --- a/packages/asset/test/fixtures/asset/assetFixture.ts +++ b/packages/asset/test/fixtures/asset/assetFixture.ts @@ -152,6 +152,13 @@ export async function runAssetSetup() { ); await AssetContract.deployed(); + const RoyaltyManagerAsAdmin = RoyaltyManagerContract.connect(managerAdmin); + const splitterDeployerRole = + await RoyaltyManagerContract.SPLITTER_DEPLOYER_ROLE(); + await RoyaltyManagerAsAdmin.grantRole( + splitterDeployerRole, + AssetContract.address + ); const generateRandomTokenId = () => { return ethers.utils.randomBytes(32); diff --git a/packages/asset/test/fixtures/asset/assetRevealFixtures.ts b/packages/asset/test/fixtures/asset/assetRevealFixtures.ts index 75eb074fd5..dd62ce92bf 100644 --- a/packages/asset/test/fixtures/asset/assetRevealFixtures.ts +++ b/packages/asset/test/fixtures/asset/assetRevealFixtures.ts @@ -95,6 +95,13 @@ export async function runRevealTestSetup() { ); await AssetContract.deployed(); + const RoyaltyManagerAsAdmin = RoyaltyManagerContract.connect(managerAdmin); + const splitterDeployerRole = + await RoyaltyManagerContract.SPLITTER_DEPLOYER_ROLE(); + await RoyaltyManagerAsAdmin.grantRole( + splitterDeployerRole, + AssetContract.address + ); // deploy wrapped TokenIdUtils contract const TokenIdUtilsFactory = await ethers.getContractFactory( diff --git a/packages/asset/test/fixtures/asset/assetRoyaltyFixture.ts b/packages/asset/test/fixtures/asset/assetRoyaltyFixture.ts index 0430492181..28eab90a91 100644 --- a/packages/asset/test/fixtures/asset/assetRoyaltyFixture.ts +++ b/packages/asset/test/fixtures/asset/assetRoyaltyFixture.ts @@ -157,6 +157,9 @@ export async function assetRoyaltyDistribution() { Asset.address, DEFAULT_BPS ); + const splitterDeployerRole = + await RoyaltyManagerContract.SPLITTER_DEPLOYER_ROLE(); + await RoyaltyManagerAsAdmin.grantRole(splitterDeployerRole, Asset.address); return { Asset, ERC20, diff --git a/packages/asset/test/utils/events.ts b/packages/asset/test/utils/events.ts new file mode 100644 index 0000000000..fe18151f4d --- /dev/null +++ b/packages/asset/test/utils/events.ts @@ -0,0 +1,9 @@ +import {Event} from 'ethers'; + +export const findEventByName = (events: Event[], eventName: string) => { + return events.find((event) => event.event === eventName); +}; + +export const findAllEventsByName = (events: Event[], eventName: string) => { + return events.filter((event) => event.event === eventName); +}; diff --git a/packages/asset/test/utils/interfaceIds.ts b/packages/asset/test/utils/interfaceIds.ts index 79bd50143a..2ecf7170aa 100644 --- a/packages/asset/test/utils/interfaceIds.ts +++ b/packages/asset/test/utils/interfaceIds.ts @@ -4,5 +4,5 @@ export const ERC1155MetadataURIInterfaceId = '0x0e89341c'; export const AccessControlInterfaceId = '0x7965db0b'; export const ERC2981InterfaceId = '0x2a55205a'; export const RoyaltyUGCInterfaceId = '0xa30b4db9'; -export const RoyaltyMultiDistributorInterfaceId = '0x667873ce'; +export const RoyaltyMultiDistributorInterfaceId = '0xf1e82fd0'; export const RoyaltyMultiRecipientsInterfaceId = '0xfd90e897'; diff --git a/packages/dependency-operator-filter/contracts/OperatorFilterRegistrant.md b/packages/dependency-operator-filter/contracts/OperatorFilterRegistrant.md index 7c3b8b98c4..020a8fe519 100644 --- a/packages/dependency-operator-filter/contracts/OperatorFilterRegistrant.md +++ b/packages/dependency-operator-filter/contracts/OperatorFilterRegistrant.md @@ -1,19 +1,20 @@ -# OperatorFilterRegistry -Opensea in an attempt to regularize market places and creator earnings deployed a registry(https://github.com/ProjectOpenSea/operator-filter-registry) and asked NFT token contract to add filter logic(https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/OperatorFilterer.sol). +# OperatorFilterRegistry -These filter has two modifier -1st : onlyAllowedOperator +OpenSea in an attempt to regularize market places and creator earnings deployed a registry(https://github.com/ProjectOpenSea/operator-filter-registry) and asked NFT token contract to add filter logic(https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/OperatorFilterer.sol). + +These filter has two modifier +1st : onlyAllowedOperator 2nd : onlyAllowedOperatorApproval -These modifiers are added to to the transfer functions(onlyAllowedOperator) and approval function(onlyAllowedOperatorApproval) such that the when not an owner tried to transfer a Token(ex: Marketplace) or owner approves an operator(ex : Marketplace) they would be filtered on the OperatorFilterRegistry. +These modifiers are added to to the transfer functions(onlyAllowedOperator) and approval function(onlyAllowedOperatorApproval) such that the when not an owner tried to transfer a Token(ex: Marketplace) or owner approves an operator(ex : Marketplace) they would be filtered on the OperatorFilterRegistry. If the operator or the token transfer is not approved by the registry the transaction would be reverted. -On OperatorFilterRegistry a contract owner or the contract can register and maintain there own blacklist or subscribe to other blacklists but that blacklist should contain the default marketplaces blacklisted by Opensea. +On OperatorFilterRegistry a contract owner or the contract can register and maintain there own blacklist or subscribe to other blacklists but that blacklist should contain the default marketplaces blacklisted by OpenSea. -# OperatorFiltererRegistrant +# OperatorFiltererRegistrant -The OperatorFiltererRegistrant contract is made to be registered on the OperatorFilterRegistry and copy the default blacklist of the openSea. This contract would then be subscribed by the contract such as AssetERC721 and AssetERC1155. +The OperatorFiltererRegistrant contract is made to be registered on the OperatorFilterRegistry and copy the default blacklist of the OpenSea. This contract would then be subscribed by the contract such as AssetERC721 and AssetERC1155. The OperatorFiltererRegistrant would be the subscription for our token contracts on a layer(layer-1 : Ethereum , layer-2: Polygon), such that when a address is added or removed from OperatorFiltererRegistrant's blacklist it would be come in affect for each contact which subscribe to the OperatorFiltererRegistrant's blacklist. @@ -21,7 +22,7 @@ The OperatorFiltererRegistrant would be the subscription for our token contracts The OperatorFiltererRegistrant is so that sandbox will have a common blacklist that would be utilized by every Token contract on a layer. This would create a single list that would be subscribed by each contract to provide uniformity to which market places sandbox wants to blacklist. This would also provide a focal point to remove and add market places such that it would be applicable to every contract that subscribe to it. -# Implementation +# Implementation We didn't use the npm package as its solidity pragma(solidity version) doesn't match the one we have for our Asset contracts and updating our solidity version for Assets would have been to time consuming. diff --git a/packages/dependency-operator-filter/contracts/OperatorFilterSubscription.sol b/packages/dependency-operator-filter/contracts/OperatorFilterSubscription.sol index 622f1df24b..f5f41c7def 100644 --- a/packages/dependency-operator-filter/contracts/OperatorFilterSubscription.sol +++ b/packages/dependency-operator-filter/contracts/OperatorFilterSubscription.sol @@ -6,18 +6,18 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; /// @title OperatorFilterSubription /// @author The Sandbox -/// @notice This contract is meant to register and copy the default subscription of the OpenSea for the operator filter and our Token contract are supposed to subscribe to this contract on openSea operator filter registry +/// @notice This contract is meant to register and copy the default subscription of the OpenSea for the operator filter and our Token contract are supposed to subscribe to this contract on OpenSea operator filter registry contract OperatorFilterSubscription is Ownable { - address public constant DEFAULT_SUBSCRIPTION = address(0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6); + address public constant DEFAULT_SUBSCRIPTION = 0x3cc6CddA760b79bAfa08dF41ECFA224f810dCeB6; // solhint-disable-next-line const-name-snakecase - IOperatorFilterRegistry public constant operatorFilterRegistry = + IOperatorFilterRegistry public constant OPERATOR_FILTER_REGISTRY = IOperatorFilterRegistry(0x000000000000AAeB6D7670E522A718067333cd4E); constructor() Ownable() { - // Subscribe and copy the entries of the Default subscription list of open sea. - if (address(operatorFilterRegistry).code.length > 0) { - operatorFilterRegistry.registerAndCopyEntries(address(this), DEFAULT_SUBSCRIPTION); + // Subscribe and copy the entries of the Default subscription list of OpenSea. + if (address(OPERATOR_FILTER_REGISTRY).code.length > 0) { + OPERATOR_FILTER_REGISTRY.registerAndCopyEntries(address(this), DEFAULT_SUBSCRIPTION); } } } diff --git a/packages/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol b/packages/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol index 26c35fc29e..526e18846a 100644 --- a/packages/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol +++ b/packages/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol @@ -6,10 +6,12 @@ import {IOperatorFilterRegistry} from "./interfaces/IOperatorFilterRegistry.sol" import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; ///@title OperatorFiltererUpgradeable -///@author The SandBox -///@notice This contract would subscibe or copy or just to the subscription provided or just register to default subscription list. The operator filter registry's addess could be set using a setter which could be implemented in inherting contract +///@author The Sandbox +///@notice This contract would subscribe or copy or just to the subscription provided or just register to default subscription list. The operator filter registry's address could be set using a setter which could be implemented in inheriting contract abstract contract OperatorFiltererUpgradeable is Initializable, ContextUpgradeable { - IOperatorFilterRegistry public operatorFilterRegistry; + event OperatorFilterRegistrySet(address indexed registry); + + IOperatorFilterRegistry private operatorFilterRegistry; // solhint-disable-next-line func-name-mixedcase function __OperatorFilterer_init(address subscriptionOrRegistrantToCopy, bool subscribe) internal onlyInitializing { @@ -62,4 +64,28 @@ abstract contract OperatorFiltererUpgradeable is Initializable, ContextUpgradeab } _; } + + /// @notice returns the operator filter registry. + /// @return operatorFilterRegistryAddress address of operator filter registry contract. + function getOperatorFilterRegistry() external view returns (IOperatorFilterRegistry operatorFilterRegistryAddress) { + return _getOperatorFilterRegistry(); + } + + /// @notice internal method to set the operator filter registry + /// @param registry address the registry. + function _setOperatorFilterRegistry(address registry) internal { + operatorFilterRegistry = IOperatorFilterRegistry(registry); + emit OperatorFilterRegistrySet(registry); + } + + /// @notice internal method to get the operator filter registry. + function _getOperatorFilterRegistry() + internal + view + returns (IOperatorFilterRegistry operatorFilterRegistryAddress) + { + return operatorFilterRegistry; + } + + uint256[49] private __gap; } diff --git a/packages/dependency-operator-filter/contracts/interfaces/IOperatorFilterRegistry.sol b/packages/dependency-operator-filter/contracts/interfaces/IOperatorFilterRegistry.sol index 571c8f7914..769597d08f 100644 --- a/packages/dependency-operator-filter/contracts/interfaces/IOperatorFilterRegistry.sol +++ b/packages/dependency-operator-filter/contracts/interfaces/IOperatorFilterRegistry.sol @@ -1,155 +1,109 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +/// @title IOperatorFilterRegistry +/// @notice Interface for managing operators and filtering. interface IOperatorFilterRegistry { - /** - * @notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns - * true if supplied registrant address is not registered. - */ - function isOperatorAllowed(address registrant, address operator) external view returns (bool); - - /** - * @notice Registers an address with the registry. May be called by address itself or by EIP-173 owner. - */ + ///@notice Returns true if operator is not filtered for a given token, either by address or codeHash. Also returns + /// true if supplied registrant address is not registered. + function isOperatorAllowed(address registrant, address operator) external view returns (bool isAllowed); + + ///@notice Registers an address with the registry. May be called by address itself or by EIP-173 owner. function register(address registrant) external; - /** - * @notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes. - */ + ///@notice Registers an address with the registry and "subscribes" to another address's filtered operators and codeHashes. function registerAndSubscribe(address registrant, address subscription) external; - /** - * @notice Registers an address with the registry and copies the filtered operators and codeHashes from another - * address without subscribing. - */ + ///@notice Registers an address with the registry and copies the filtered operators and codeHashes from another + /// address without subscribing. function registerAndCopyEntries(address registrant, address registrantToCopy) external; - /** - * @notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner. - * Note that this does not remove any filtered addresses or codeHashes. - * Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes. - */ + ///@notice Unregisters an address with the registry and removes its subscription. May be called by address itself or by EIP-173 owner. + /// Note that this does not remove any filtered addresses or codeHashes. + /// Also note that any subscriptions to this registrant will still be active and follow the existing filtered addresses and codehashes. function unregister(address addr) external; - /** - * @notice Update an operator address for a registered address - when filtered is true, the operator is filtered. - */ + ///@notice Update an operator address for a registered address - when filtered is true, the operator is filtered. function updateOperator( address registrant, address operator, bool filtered ) external; - /** - * @notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates. - */ + ///@notice Update multiple operators for a registered address - when filtered is true, the operators will be filtered. Reverts on duplicates. function updateOperators( address registrant, address[] calldata operators, bool filtered ) external; - /** - * @notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered. - */ + ///@notice Update a codeHash for a registered address - when filtered is true, the codeHash is filtered. function updateCodeHash( address registrant, bytes32 codehash, bool filtered ) external; - /** - * @notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates. - */ + ///@notice Update multiple codeHashes for a registered address - when filtered is true, the codeHashes will be filtered. Reverts on duplicates. function updateCodeHashes( address registrant, bytes32[] calldata codeHashes, bool filtered ) external; - /** - * @notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous - * subscription if present. - * Note that accounts with subscriptions may go on to subscribe to other accounts - in this case, - * subscriptions will not be forwarded. Instead the former subscription's existing entries will still be - * used. - */ + ///@notice Subscribe an address to another registrant's filtered operators and codeHashes. Will remove previous + /// subscription if present. + /// Note that accounts with subscriptions may go on to subscribe to other accounts - in this case, + /// subscriptions will not be forwarded. Instead the former subscription's existing entries will still be + /// used. function subscribe(address registrant, address registrantToSubscribe) external; - /** - * @notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes. - */ + ///@notice Unsubscribe an address from its current subscribed registrant, and optionally copy its filtered operators and codeHashes. function unsubscribe(address registrant, bool copyExistingEntries) external; - /** - * @notice Get the subscription address of a given registrant, if any. - */ + ///@notice Get the subscription address of a given registrant, if any. function subscriptionOf(address addr) external returns (address registrant); - /** - * @notice Get the set of addresses subscribed to a given registrant. - * Note that order is not guaranteed as updates are made. - */ - function subscribers(address registrant) external returns (address[] memory); - - /** - * @notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant. - * Note that order is not guaranteed as updates are made. - */ - function subscriberAt(address registrant, uint256 index) external returns (address); - - /** - * @notice Copy filtered operators and codeHashes from a different registrantToCopy to addr. - */ + ///@notice Get the set of addresses subscribed to a given registrant. + /// Note that order is not guaranteed as updates are made. + function subscribers(address registrant) external returns (address[] memory subscribersList); + + ///@notice Get the subscriber at a given index in the set of addresses subscribed to a given registrant. + /// Note that order is not guaranteed as updates are made. + function subscriberAt(address registrant, uint256 index) external returns (address subscriberAddress); + + ///@notice Copy filtered operators and codeHashes from a different registrantToCopy to addr. function copyEntriesOf(address registrant, address registrantToCopy) external; - /** - * @notice Returns true if operator is filtered by a given address or its subscription. - */ - function isOperatorFiltered(address registrant, address operator) external returns (bool); - - /** - * @notice Returns true if the hash of an address's code is filtered by a given address or its subscription. - */ - function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool); - - /** - * @notice Returns true if a codeHash is filtered by a given address or its subscription. - */ - function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool); - - /** - * @notice Returns a list of filtered operators for a given address or its subscription. - */ - function filteredOperators(address addr) external returns (address[] memory); - - /** - * @notice Returns the set of filtered codeHashes for a given address or its subscription. - * Note that order is not guaranteed as updates are made. - */ - function filteredCodeHashes(address addr) external returns (bytes32[] memory); - - /** - * @notice Returns the filtered operator at the given index of the set of filtered operators for a given address or - * its subscription. - * Note that order is not guaranteed as updates are made. - */ - function filteredOperatorAt(address registrant, uint256 index) external returns (address); - - /** - * @notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or - * its subscription. - * Note that order is not guaranteed as updates are made. - */ - function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32); - - /** - * @notice Returns true if an address has registered - */ - function isRegistered(address addr) external returns (bool); - - /** - * @dev Convenience method to compute the code hash of an arbitrary contract - */ - function codeHashOf(address addr) external returns (bytes32); + ///@notice Returns true if operator is filtered by a given address or its subscription. + function isOperatorFiltered(address registrant, address operator) external returns (bool isFiltered); + + ///@notice Returns true if the hash of an address's code is filtered by a given address or its subscription. + function isCodeHashOfFiltered(address registrant, address operatorWithCode) external returns (bool isFiltered); + + ///@notice Returns true if a codeHash is filtered by a given address or its subscription. + function isCodeHashFiltered(address registrant, bytes32 codeHash) external returns (bool isFiltered); + + ///@notice Returns a list of filtered operators for a given address or its subscription. + function filteredOperators(address addr) external returns (address[] memory operatorList); + + ///@notice Returns the set of filtered codeHashes for a given address or its subscription. + /// Note that order is not guaranteed as updates are made. + function filteredCodeHashes(address addr) external returns (bytes32[] memory codeHashList); + + ///@notice Returns the filtered operator at the given index of the set of filtered operators for a given address or + /// its subscription. + /// Note that order is not guaranteed as updates are made. + function filteredOperatorAt(address registrant, uint256 index) external returns (address operator); + + ///@notice Returns the filtered codeHash at the given index of the list of filtered codeHashes for a given address or + /// its subscription. + /// Note that order is not guaranteed as updates are made. + function filteredCodeHashAt(address registrant, uint256 index) external returns (bytes32 codeHash); + + ///@notice Returns true if an address has registered + function isRegistered(address addr) external returns (bool registered); + + ///@dev Convenience method to compute the code hash of an arbitrary contract + function codeHashOf(address addr) external returns (bytes32 codeHash); } diff --git a/packages/dependency-operator-filter/contracts/mock/MockOperatorFilterRegistry.sol b/packages/dependency-operator-filter/contracts/mock/MockOperatorFilterRegistry.sol index 2fd476ba1e..bfcb5019e3 100644 --- a/packages/dependency-operator-filter/contracts/mock/MockOperatorFilterRegistry.sol +++ b/packages/dependency-operator-filter/contracts/mock/MockOperatorFilterRegistry.sol @@ -11,7 +11,7 @@ import { /** * @title MockOperatorFilterRegistry - * @notice Made based on the OperatorFilterRegistry of openSea at https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/OperatorFilterRegistry.sol + * @notice Made based on the OperatorFilterRegistry of OpenSea at https://github.com/ProjectOpenSea/operator-filter-registry/blob/main/src/OperatorFilterRegistry.sol * @notice This contracts allows tokens or token owners to register specific addresses or codeHashes that may be * * restricted according to the isOperatorAllowed function. */ diff --git a/packages/dependency-operator-filter/contracts/mock/TestERC1155.sol b/packages/dependency-operator-filter/contracts/mock/TestERC1155.sol index 78c952a896..25189616bf 100644 --- a/packages/dependency-operator-filter/contracts/mock/TestERC1155.sol +++ b/packages/dependency-operator-filter/contracts/mock/TestERC1155.sol @@ -13,7 +13,7 @@ import {OperatorFiltererUpgradeable} from "../OperatorFiltererUpgradeable.sol"; import {IOperatorFilterRegistry} from "../interfaces/IOperatorFilterRegistry.sol"; contract TestERC1155 is ERC1155Upgradeable, OperatorFiltererUpgradeable, ERC2771HandlerUpgradeable { - function initialize(string memory uri_, address trustedForwarder) external initializer() { + function initialize(string memory uri_, address trustedForwarder) external initializer { __ERC1155_init(uri_); __ERC2771Handler_init(trustedForwarder); } @@ -22,8 +22,8 @@ contract TestERC1155 is ERC1155Upgradeable, OperatorFiltererUpgradeable, ERC2771 /// @param registry address of registry /// @param subscription address to subscribe function setRegistryAndSubscribe(address registry, address subscription) external { - operatorFilterRegistry = IOperatorFilterRegistry(registry); - operatorFilterRegistry.registerAndSubscribe(address(this), subscription); + _setOperatorFilterRegistry(registry); + IOperatorFilterRegistry(registry).registerAndSubscribe(address(this), subscription); } /// @notice Mint new tokens with out minter role diff --git a/packages/dependency-operator-filter/contracts/mock/TestERC721.sol b/packages/dependency-operator-filter/contracts/mock/TestERC721.sol index ed48f614af..f2a81e6fd0 100644 --- a/packages/dependency-operator-filter/contracts/mock/TestERC721.sol +++ b/packages/dependency-operator-filter/contracts/mock/TestERC721.sol @@ -24,8 +24,8 @@ contract TestERC721 is ERC721Upgradeable, OperatorFiltererUpgradeable, ERC2771Ha /// @param registry address of registry /// @param subscription address to subscribe function setRegistryAndSubscribe(address registry, address subscription) external { - operatorFilterRegistry = IOperatorFilterRegistry(registry); - operatorFilterRegistry.registerAndSubscribe(address(this), subscription); + _setOperatorFilterRegistry(registry); + IOperatorFilterRegistry(registry).registerAndSubscribe(address(this), subscription); } /// @notice Mint new tokens with out minter role diff --git a/packages/dependency-operator-filter/contracts/mock/UnregisteredToken.sol b/packages/dependency-operator-filter/contracts/mock/UnregisteredToken.sol index 7f0e4e7c42..e93f814e28 100644 --- a/packages/dependency-operator-filter/contracts/mock/UnregisteredToken.sol +++ b/packages/dependency-operator-filter/contracts/mock/UnregisteredToken.sol @@ -26,7 +26,7 @@ contract UnregisteredToken is ERC1155Upgradeable, OperatorFiltererUpgradeable, E /// @notice sets registry /// @param registry address of registry function setRegistry(address registry) external { - operatorFilterRegistry = IOperatorFilterRegistry(registry); + _setOperatorFilterRegistry(registry); } /// @notice register contract @@ -40,8 +40,8 @@ contract UnregisteredToken is ERC1155Upgradeable, OperatorFiltererUpgradeable, E /// @param registry address of registry /// @param subscription address to subscribe function setRegistryAndSubscribe(address registry, address subscription) external { - operatorFilterRegistry = IOperatorFilterRegistry(registry); - operatorFilterRegistry.registerAndSubscribe(address(this), subscription); + _setOperatorFilterRegistry(registry); + IOperatorFilterRegistry(registry).registerAndSubscribe(address(this), subscription); } /// @notice Mint new tokens with out minter role diff --git a/packages/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol b/packages/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol index 39eaa5ba4f..50078e5be9 100644 --- a/packages/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol +++ b/packages/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol @@ -10,31 +10,32 @@ import { import {IEIP2981} from "@manifoldxyz/royalty-registry-solidity/contracts/specs/IEIP2981.sol"; import {IRoyaltyManager, Recipient} from "./interfaces/IRoyaltyManager.sol"; -/// @title MultiRoyaltyDistributer +/// @title MultiRoyaltyDistributor /// @author The Sandbox -/// @dev The MultiRoyaltyDistributer contract implements the ERC-2981 and ERC-165 interfaces for a royalty payment system. This payment system can be used to pay royalties to multiple recipients through splitters. -/// @dev This contract calls to the Royalties manager contract to deploy RoyaltySplitter for a creator to slip its royalty between the creator and Sandbox and use it for every token minted by that creator. +/// @dev The MultiRoyaltyDistributor contract implements the ERC-2981 and ERC-165 interfaces for a royalty payment system. This payment system can be used to pay royalties to multiple recipients through splitters. +/// @dev This contract calls to the Royalties manager contract to deploy RoyaltySplitter for a creator to split its royalty between the creator and Sandbox and use it for every token minted by that creator. abstract contract MultiRoyaltyDistributor is IEIP2981, IMultiRoyaltyDistributor, ERC165Upgradeable { uint16 internal constant TOTAL_BASIS_POINTS = 10000; - address public royaltyManager; + address private royaltyManager; - mapping(uint256 => address payable) public _tokenRoyaltiesSplitter; + mapping(uint256 => address payable) private _tokenRoyaltiesSplitter; uint256[] private _tokensWithRoyalties; // solhint-disable-next-line func-name-mixedcase - function __MultiRoyaltyDistributor_init(address _royaltyManager) internal { - royaltyManager = _royaltyManager; + function __MultiRoyaltyDistributor_init(address _royaltyManager) internal onlyInitializing { + _setRoyaltyManager(_royaltyManager); + __ERC165_init_unchained(); } - /// @notice EIP 165 interface function - /// @dev used to check the interface implemented - /// @param interfaceId to be checked for implementation + /// @notice Query if a contract implements interface `id`. + /// @param interfaceId the interface identifier, as specified in ERC-165. + /// @return isSupported `true` if the contract implements `id`. function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165) - returns (bool) + returns (bool isSupported) { return interfaceId == type(IEIP2981).interfaceId || @@ -46,6 +47,7 @@ abstract contract MultiRoyaltyDistributor is IEIP2981, IMultiRoyaltyDistributor, /// @notice sets token royalty /// @dev deploys a splitter if a creator doesn't have one /// @param tokenId id of token + /// @param recipient royalty recipient /// @param creator of the token function _setTokenRoyalties( uint256 tokenId, @@ -53,33 +55,28 @@ abstract contract MultiRoyaltyDistributor is IEIP2981, IMultiRoyaltyDistributor, address creator ) internal { address payable creatorSplitterAddress = IRoyaltyManager(royaltyManager).deploySplitter(creator, recipient); - _tokenRoyaltiesSplitter[tokenId] = creatorSplitterAddress; - _tokensWithRoyalties.push(tokenId); - emit TokenRoyaltySet(tokenId, recipient); - } - /// @notice Returns royalty receivers and their split of royalty for each token - /// @return royaltyConfigs receivers and their split array as long as the number of tokens. - function getTokenRoyalties() external view override returns (TokenRoyaltyConfig[] memory royaltyConfigs) { - royaltyConfigs = new TokenRoyaltyConfig[](_tokensWithRoyalties.length); - for (uint256 i; i < _tokensWithRoyalties.length; ++i) { - TokenRoyaltyConfig memory royaltyConfig; - uint256 tokenId = _tokensWithRoyalties[i]; - address splitterAddress = _tokenRoyaltiesSplitter[tokenId]; - if (splitterAddress != address(0)) { - royaltyConfig.recipients = IRoyaltySplitter(splitterAddress).getRecipients(); + if (_tokenRoyaltiesSplitter[tokenId] != address(0)) { + if (_tokenRoyaltiesSplitter[tokenId] != creatorSplitterAddress) { + _setTokenRoyaltiesSplitter(tokenId, creatorSplitterAddress); } - royaltyConfig.tokenId = tokenId; - royaltyConfigs[i] = royaltyConfig; + } else { + _tokensWithRoyalties.push(tokenId); + _setTokenRoyaltiesSplitter(tokenId, creatorSplitterAddress); } } /// @notice EIP 2981 royalty info function to return the royalty receiver and royalty amount /// @param tokenId of the token for which the royalty is needed to be distributed /// @param value the amount on which the royalty is calculated - /// @return address the royalty receiver - /// @return value the EIP2981 royalty - function royaltyInfo(uint256 tokenId, uint256 value) public view override returns (address, uint256) { + /// @return receiver address the royalty receiver + /// @return royaltyAmount value the EIP2981 royalty + function royaltyInfo(uint256 tokenId, uint256 value) + public + view + override + returns (address receiver, uint256 royaltyAmount) + { (address payable _defaultRoyaltyReceiver, uint16 _defaultRoyaltyBPS) = IRoyaltyManager(royaltyManager).getRoyaltyInfo(); if (_tokenRoyaltiesSplitter[tokenId] != address(0)) { @@ -114,15 +111,45 @@ abstract contract MultiRoyaltyDistributor is IEIP2981, IMultiRoyaltyDistributor, /// @notice returns the royalty recipients for each tokenId. /// @dev returns the default address for tokens with no recipients. /// @param tokenId is the token id for which the recipient should be returned. - /// @return addresses of royalty recipient of the token. - function getRecipients(uint256 tokenId) public view returns (Recipient[] memory) { + /// @return recipients array of royalty recipients for the token + function getRecipients(uint256 tokenId) public view returns (Recipient[] memory recipients) { address payable splitterAddress = _tokenRoyaltiesSplitter[tokenId]; (address payable _defaultRoyaltyReceiver, ) = IRoyaltyManager(royaltyManager).getRoyaltyInfo(); if (splitterAddress != address(0)) { return IRoyaltySplitter(splitterAddress).getRecipients(); } - Recipient[] memory defaultRecipient = new Recipient[](1); - defaultRecipient[0] = Recipient({recipient: _defaultRoyaltyReceiver, bps: TOTAL_BASIS_POINTS}); - return defaultRecipient; + recipients = new Recipient[](1); + recipients[0] = Recipient({recipient: _defaultRoyaltyReceiver, bps: TOTAL_BASIS_POINTS}); + return recipients; + } + + /// @notice internal function to set the token royalty splitter + /// @param tokenId id of token + /// @param splitterAddress address of the splitter contract + function _setTokenRoyaltiesSplitter(uint256 tokenId, address payable splitterAddress) internal { + _tokenRoyaltiesSplitter[tokenId] = splitterAddress; + emit TokenRoyaltySplitterSet(tokenId, splitterAddress); + } + + /// @notice returns the address of token royalty splitter. + /// @param tokenId is the token id for which royalty splitter should be returned. + /// @return splitterAddress address of royalty splitter for the token + function getTokenRoyaltiesSplitter(uint256 tokenId) external view returns (address payable splitterAddress) { + return _tokenRoyaltiesSplitter[tokenId]; + } + + /// @notice returns the address of royalty manager. + /// @return managerAddress address of royalty manager. + function getRoyaltyManager() external view returns (address managerAddress) { + return royaltyManager; } + + /// @notice set royalty manager address + /// @param _royaltyManager address of royalty manager to set + function _setRoyaltyManager(address _royaltyManager) internal { + royaltyManager = _royaltyManager; + emit RoyaltyManagerSet(_royaltyManager); + } + + uint256[47] private __gap; } diff --git a/packages/dependency-royalty-management/contracts/RoyaltyDistributor.sol b/packages/dependency-royalty-management/contracts/RoyaltyDistributor.sol index 2a54eb45df..0fa83a271e 100644 --- a/packages/dependency-royalty-management/contracts/RoyaltyDistributor.sol +++ b/packages/dependency-royalty-management/contracts/RoyaltyDistributor.sol @@ -8,13 +8,18 @@ import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; -contract RoyaltyDistributor is IERC2981Upgradeable, ERC165Upgradeable { +/// @title RoyaltyDistributor +/// @author The Sandbox +/// @notice Contract for distributing royalties based on the ERC2981 standard. +abstract contract RoyaltyDistributor is IERC2981Upgradeable, ERC165Upgradeable { + event RoyaltyManagerSet(address indexed _royaltyManager); uint16 internal constant TOTAL_BASIS_POINTS = 10000; - IRoyaltyManager public royaltyManager; + IRoyaltyManager private royaltyManager; // solhint-disable-next-line func-name-mixedcase - function __RoyaltyDistributor_init(address _royaltyManager) internal { - royaltyManager = IRoyaltyManager(_royaltyManager); + function __RoyaltyDistributor_init(address _royaltyManager) internal onlyInitializing { + _setRoyaltyManager(_royaltyManager); + __ERC165_init_unchained(); } /// @notice Returns how much royalty is owed and to whom based on ERC2981 @@ -34,14 +39,29 @@ contract RoyaltyDistributor is IERC2981Upgradeable, ERC165Upgradeable { /// @notice Query if a contract implements interface `id`. /// @param interfaceId the interface identifier, as specified in ERC-165. - /// @return `true` if the contract implements `id`. + /// @return isSupported `true` if the contract implements `id`. function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165Upgradeable, IERC165Upgradeable) - returns (bool) + returns (bool isSupported) { - return interfaceId == type(IERC2981Upgradeable).interfaceId || super.supportsInterface(interfaceId); + return (interfaceId == type(IERC2981Upgradeable).interfaceId || super.supportsInterface(interfaceId)); + } + + /// @notice returns the royalty manager + /// @return royaltyManagerAddress address of royalty manager contract. + function getRoyaltyManager() external view returns (IRoyaltyManager royaltyManagerAddress) { + return royaltyManager; } + + /// @notice set royalty manager + /// @param _royaltyManager address of royalty manager to set + function _setRoyaltyManager(address _royaltyManager) internal { + royaltyManager = IRoyaltyManager(_royaltyManager); + emit RoyaltyManagerSet(_royaltyManager); + } + + uint256[49] private __gap; } diff --git a/packages/dependency-royalty-management/contracts/RoyaltyManager.sol b/packages/dependency-royalty-management/contracts/RoyaltyManager.sol index 59fec1e2a6..0bc532654f 100644 --- a/packages/dependency-royalty-management/contracts/RoyaltyManager.sol +++ b/packages/dependency-royalty-management/contracts/RoyaltyManager.sol @@ -12,16 +12,23 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; /// @notice Registry contract to set the common Recipient and Split for the RoyaltySplitter. Also, to set the royalty info /// for contracts that don't use the RoyaltySplitter. contract RoyaltyManager is AccessControlUpgradeable, IRoyaltyManager { - bytes32 public constant CONTRACT_ROYALTY_SETTER_ROLE = keccak256("CONTRACT_ROYALTY_SETTER"); + bytes32 public constant CONTRACT_ROYALTY_SETTER_ROLE = keccak256("CONTRACT_ROYALTY_SETTER_ROLE"); + bytes32 public constant SPLITTER_DEPLOYER_ROLE = keccak256("SPLITTER_DEPLOYER_ROLE"); uint16 internal constant TOTAL_BASIS_POINTS = 10000; uint16 public commonSplit; address payable public commonRecipient; mapping(address => uint16) public contractRoyalty; - mapping(address => address payable) public _creatorRoyaltiesSplitter; + mapping(address => address payable) public creatorRoyaltiesSplitter; address internal _royaltySplitterCloneable; address internal _trustedForwarder; + /// @dev this protects the implementation contract from behing initialized. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + /// @notice initialization function for the deployment of contract /// @dev called during the deployment via the proxy. /// @param _commonRecipient the != address(0)common recipient for all the splitters @@ -43,23 +50,23 @@ contract RoyaltyManager is AccessControlUpgradeable, IRoyaltyManager { _grantRole(DEFAULT_ADMIN_ROLE, managerAdmin); _grantRole(CONTRACT_ROYALTY_SETTER_ROLE, contractRoyaltySetter); _royaltySplitterCloneable = royaltySplitterCloneable; - _trustedForwarder = trustedForwarder; + _setTrustedForwarder(trustedForwarder); } /// @notice sets royalty recipient wallet /// @dev should be called by the creator. The bps is not set on the splitter as it is set here on manager contract. /// @param recipient new recipient wallet. function setRoyaltyRecipient(address payable recipient) external { - address payable creatorSplitterAddress = _creatorRoyaltiesSplitter[msg.sender]; - require(creatorSplitterAddress != address(0), "Manager: No splitter deployed for the creator"); - address _recipient = RoyaltySplitter(creatorSplitterAddress)._recipient(); + address payable _creatorSplitterAddress = creatorRoyaltiesSplitter[msg.sender]; + require(_creatorSplitterAddress != address(0), "Manager: No splitter deployed for the creator"); + address _recipient = RoyaltySplitter(_creatorSplitterAddress).recipient(); require(_recipient != recipient, "Manager: Recipient already set"); Recipient[] memory newRecipient = new Recipient[](1); newRecipient[0] = Recipient({recipient: recipient, bps: 0}); - RoyaltySplitter(creatorSplitterAddress).setRecipients(newRecipient); + RoyaltySplitter(_creatorSplitterAddress).setRecipients(newRecipient); } - /// @notice sets the common recipient and common split + /// @notice sets the common recipient /// @dev can only be called by the admin /// @param _commonRecipient is the common recipient for all the splitters function setRecipient(address payable _commonRecipient) external override onlyRole(DEFAULT_ADMIN_ROLE) { @@ -68,13 +75,13 @@ contract RoyaltyManager is AccessControlUpgradeable, IRoyaltyManager { /// @notice sets the trustedForwarder address to be used by the splitters /// @dev can only be called by the admin + /// new splitters will read this value /// @param _newForwarder is the new trusted forwarder address - /// @dev new splitters will be deployed with this setting; existing splitters will have to apply it function setTrustedForwarder(address _newForwarder) external onlyRole(DEFAULT_ADMIN_ROLE) { - _trustedForwarder = _newForwarder; + _setTrustedForwarder(_newForwarder); } - /// @notice sets the common recipient and common split + /// @notice sets the common split /// @dev can only be called by the admin. /// @param _commonSplit split for the common recipient and creators split would be 10000 - commonSplit function setSplit(uint16 _commonSplit) external override onlyRole(DEFAULT_ADMIN_ROLE) { @@ -82,7 +89,8 @@ contract RoyaltyManager is AccessControlUpgradeable, IRoyaltyManager { } /// @notice get the current trustedForwarder address - function getTrustedForwarder() public view returns (address) { + /// @return trustedForwarder address of current TrustedForwarder + function getTrustedForwarder() external view returns (address trustedForwarder) { return _trustedForwarder; } @@ -98,8 +106,17 @@ contract RoyaltyManager is AccessControlUpgradeable, IRoyaltyManager { emit SplitSet(_commonSplit); } + /// @notice sets trusted forwarder address + /// @param _newForwarder new trusted forwarder address to set + function _setTrustedForwarder(address _newForwarder) internal { + address oldTrustedForwarder = _trustedForwarder; + _trustedForwarder = _newForwarder; + emit TrustedForwarderSet(oldTrustedForwarder, _newForwarder); + } + /// @notice called to set the EIP 2981 royalty split /// @dev can only be called by contract royalty setter. + /// @param contractAddress address of contract for which royalty is set /// @param _royaltyBps the royalty split for the EIP 2981 function setContractRoyalty(address contractAddress, uint16 _royaltyBps) external @@ -113,49 +130,55 @@ contract RoyaltyManager is AccessControlUpgradeable, IRoyaltyManager { /// @notice to be called by the splitters to get the common recipient and split /// @return recipient which has the common recipient and split function getCommonRecipient() external view override returns (Recipient memory recipient) { - recipient = Recipient({recipient: commonRecipient, bps: commonSplit}); - return recipient; + return Recipient({recipient: commonRecipient, bps: commonSplit}); } /// @notice deploys splitter for creator /// @dev should only called once per creator - /// @param creator the address of the creator + /// @param creator the address of the creator /// @param recipient the wallet of the recipient where they would receive their royalty - /// @return creatorSplitterAddress deployed for a creator - function deploySplitter(address creator, address payable recipient) external returns (address payable) { - address payable creatorSplitterAddress = _creatorRoyaltiesSplitter[creator]; + /// @return creatorSplitterAddress splitter's address deployed for a creator + function deploySplitter(address creator, address payable recipient) + external + onlyRole(SPLITTER_DEPLOYER_ROLE) + returns (address payable creatorSplitterAddress) + { + creatorSplitterAddress = creatorRoyaltiesSplitter[creator]; if (creatorSplitterAddress == address(0)) { creatorSplitterAddress = payable(Clones.clone(_royaltySplitterCloneable)); RoyaltySplitter(creatorSplitterAddress).initialize(recipient, address(this)); - _creatorRoyaltiesSplitter[creator] = creatorSplitterAddress; + creatorRoyaltiesSplitter[creator] = creatorSplitterAddress; + emit SplitterDeployed(creator, recipient, creatorSplitterAddress); } return creatorSplitterAddress; } /// @notice returns the address of splitter of a creator. - /// @param creator the address of the creator - /// @return creatorSplitterAddress deployed for a creator - function getCreatorRoyaltySplitter(address creator) external view returns (address payable) { - return _creatorRoyaltiesSplitter[creator]; + /// @param creator the address of the creator + /// @return creatorSplitterAddress splitter's address deployed for a creator + function getCreatorRoyaltySplitter(address creator) external view returns (address payable creatorSplitterAddress) { + return creatorRoyaltiesSplitter[creator]; } - /// @notice to be called by the splitters to get the common recipient and split + /// @notice returns the amount of basis points allocated to the creator /// @return creatorSplit which is 10000 - commonSplit - function getCreatorSplit() external view returns (uint16) { + function getCreatorSplit() external view returns (uint16 creatorSplit) { return TOTAL_BASIS_POINTS - commonSplit; } - /// @notice returns the commonRecipient and EIP2981 royalty split - /// @return commonRecipient - /// @return royaltySplit - function getRoyaltyInfo() external view returns (address payable, uint16) { + /// @notice returns the commonRecipient and EIP2981 royalty bps + /// @return recipient address of common royalty recipient + /// @return royaltySplit contract EIP2981 royalty bps + function getRoyaltyInfo() external view returns (address payable recipient, uint16 royaltySplit) { return (commonRecipient, contractRoyalty[msg.sender]); } - /// @notice returns the commonRecipient and EIP2981 royalty split - /// @param _contractAddress the address of the contract for which the royalty is required. - /// @return royaltyBps royalty bps of the contarct + /// @notice returns the EIP2981 royalty bps + /// @param _contractAddress the address of the contract for which the royalty is required + /// @return royaltyBps royalty bps of the contract function getContractRoyalty(address _contractAddress) external view returns (uint16 royaltyBps) { return contractRoyalty[_contractAddress]; } + + uint256[44] private __gap; } diff --git a/packages/dependency-royalty-management/contracts/RoyaltySplitter.sol b/packages/dependency-royalty-management/contracts/RoyaltySplitter.sol index 82547718e2..ff9a0e2204 100644 --- a/packages/dependency-royalty-management/contracts/RoyaltySplitter.sol +++ b/packages/dependency-royalty-management/contracts/RoyaltySplitter.sol @@ -10,6 +10,7 @@ import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/Addr import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol"; import {SafeMath} from "@openzeppelin/contracts/utils/math/SafeMath.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {BytesLibrary} from "@manifoldxyz/royalty-registry-solidity/contracts/libraries/BytesLibrary.sol"; import { IRoyaltySplitter, @@ -34,58 +35,67 @@ contract RoyaltySplitter is using AddressUpgradeable for address payable; using AddressUpgradeable for address; using SafeMath for uint256; + using SafeERC20 for IERC20; uint256 internal constant TOTAL_BASIS_POINTS = 10000; - uint256 internal constant IERC20_APPROVE_SELECTOR = - 0x095ea7b300000000000000000000000000000000000000000000000000000000; - uint256 internal constant SELECTOR_MASK = 0xffffffff00000000000000000000000000000000000000000000000000000000; - address payable public _recipient; - IRoyaltyManager public _royaltyManager; + address payable public recipient; + IRoyaltyManager public royaltyManager; event ETHTransferred(address indexed account, uint256 amount); event ERC20Transferred(address indexed erc20Contract, address indexed account, uint256 amount); + event RecipientSet(address indexed recipientAddress); + /// @dev this protects the implementation contract from behing initialized. + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Query if a contract implements interface `id`. + /// @param interfaceId the interface identifier, as specified in ERC-165. + /// @return isSupported `true` if the contract implements `id`. function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165Upgradeable) - returns (bool) + returns (bool isSupported) { - return interfaceId == type(IRoyaltySplitter).interfaceId || super.supportsInterface(interfaceId); + return (interfaceId == type(IRoyaltySplitter).interfaceId || super.supportsInterface(interfaceId)); } /// @notice initialize the contract /// @dev can only be run once. - /// @param recipient the wallet of the creator when the contract is deployed - /// @param royaltyManager the address of the royalty manager contract - function initialize(address payable recipient, address royaltyManager) public initializer { - _royaltyManager = IRoyaltyManager(royaltyManager); // set manager before Ownable_init for _isTrustedForwarder - _recipient = recipient; + /// @param recipientAddress the wallet of the creator when the contract is deployed + /// @param _royaltyManager the address of the royalty manager contract + function initialize(address payable recipientAddress, address _royaltyManager) external initializer { + royaltyManager = IRoyaltyManager(_royaltyManager); // set manager before Ownable_init for _isTrustedForwarder + _setRecipient(recipientAddress); __Ownable_init(); + __ERC165_init(); } /// @notice sets recipient for the splitter /// @dev only the owner can call this. /// @param recipients the array of recipients which should only have one recipient. function setRecipients(Recipient[] calldata recipients) external override onlyOwner { - _setRecipients(recipients); + require(recipients.length == 1, "Invalid recipents length"); + _setRecipient(recipients[0].recipient); } - function _setRecipients(Recipient[] calldata recipients) private { - delete _recipient; - require(recipients.length == 1, "Invalid recipents length"); - _recipient = recipients[0].recipient; + function _setRecipient(address payable recipientAddress) private { + recipient = recipientAddress; + emit RecipientSet(recipientAddress); } /// @notice to get recipients of royalty through this splitter and their splits of royalty. - /// @return recipients of royalty through this splitter and their splits of royalty. - function getRecipients() external view override returns (Recipient[] memory) { - Recipient memory commonRecipient = _royaltyManager.getCommonRecipient(); - uint16 creatorSplit = _royaltyManager.getCreatorSplit(); - Recipient[] memory recipients = new Recipient[](2); - recipients[0].recipient = _recipient; + /// @return recipients array of royalty recipients through the splitter and their splits of royalty. + function getRecipients() external view override returns (Recipient[] memory recipients) { + Recipient memory commonRecipient = royaltyManager.getCommonRecipient(); + uint16 creatorSplit = royaltyManager.getCreatorSplit(); + recipients = new Recipient[](2); + recipients[0].recipient = recipient; recipients[0].bps = creatorSplit; recipients[1] = commonRecipient; return recipients; @@ -99,27 +109,27 @@ contract RoyaltySplitter is /// @notice Splits and forwards ETH to the royalty receivers /// @dev normally ETH should be split automatically by receive function. - function splitETH() public payable { + function splitETH() external payable { _splitETH(address(this).balance); } function _splitETH(uint256 value) internal { if (value > 0) { - Recipient memory commonRecipient = _royaltyManager.getCommonRecipient(); - uint16 creatorSplit = _royaltyManager.getCreatorSplit(); + Recipient memory commonRecipient = royaltyManager.getCommonRecipient(); + uint16 creatorSplit = royaltyManager.getCreatorSplit(); Recipient[] memory _recipients = new Recipient[](2); - _recipients[0].recipient = _recipient; + _recipients[0].recipient = recipient; _recipients[0].bps = creatorSplit; _recipients[1] = commonRecipient; uint256 totalSent; uint256 amountToSend; unchecked { for (uint256 i = _recipients.length - 1; i > 0; i--) { - Recipient memory recipient = _recipients[i]; - amountToSend = (value * recipient.bps) / TOTAL_BASIS_POINTS; + Recipient memory _recipient = _recipients[i]; + amountToSend = (value * _recipient.bps) / TOTAL_BASIS_POINTS; totalSent += amountToSend; - recipient.recipient.sendValue(amountToSend); - emit ETHTransferred(recipient.recipient, amountToSend); + _recipient.recipient.sendValue(amountToSend); + emit ETHTransferred(_recipient.recipient, amountToSend); } // Favor the 1st recipient if there are any rounding issues amountToSend = value - totalSent; @@ -132,80 +142,60 @@ contract RoyaltySplitter is /// @notice split ERC20 Tokens owned by this contract. /// @dev can only be called by one of the recipients /// @param erc20Contract the address of the tokens to be split. - function splitERC20Tokens(IERC20 erc20Contract) public { + function splitERC20Tokens(IERC20 erc20Contract) external { require(_splitERC20Tokens(erc20Contract), "Split: ERC20 split failed"); } - function _splitERC20Tokens(IERC20 erc20Contract) internal returns (bool) { + function _splitERC20Tokens(IERC20 erc20Contract) internal returns (bool success) { try erc20Contract.balanceOf(address(this)) returns (uint256 balance) { if (balance == 0) { return false; } - Recipient memory commonRecipient = _royaltyManager.getCommonRecipient(); - uint16 creatorSplit = _royaltyManager.getCreatorSplit(); + Recipient memory commonRecipient = royaltyManager.getCommonRecipient(); + uint16 creatorSplit = royaltyManager.getCreatorSplit(); require( - commonRecipient.recipient == _msgSender() || _recipient == _msgSender(), + commonRecipient.recipient == _msgSender() || recipient == _msgSender(), "Split: Can only be called by one of the recipients" ); Recipient[] memory _recipients = new Recipient[](2); - _recipients[0].recipient = _recipient; + _recipients[0].recipient = recipient; _recipients[0].bps = creatorSplit; _recipients[1] = commonRecipient; uint256 amountToSend; uint256 totalSent; unchecked { for (uint256 i = _recipients.length - 1; i > 0; i--) { - Recipient memory recipient = _recipients[i]; - bool success; - (success, amountToSend) = balance.tryMul(recipient.bps); + Recipient memory _recipient = _recipients[i]; + (success, amountToSend) = balance.tryMul(_recipient.bps); + require(success, "RoyaltySplitter: Multiplication Overflow"); amountToSend /= TOTAL_BASIS_POINTS; totalSent += amountToSend; - try erc20Contract.transfer(recipient.recipient, amountToSend) { - emit ERC20Transferred(address(erc20Contract), recipient.recipient, amountToSend); - } catch { - return false; - } + + erc20Contract.safeTransfer(_recipient.recipient, amountToSend); + emit ERC20Transferred(address(erc20Contract), _recipient.recipient, amountToSend); } // Favor the 1st recipient if there are any rounding issues amountToSend = balance - totalSent; } - try erc20Contract.transfer(_recipients[0].recipient, amountToSend) { - emit ERC20Transferred(address(erc20Contract), _recipients[0].recipient, amountToSend); - } catch { - return false; - } + erc20Contract.safeTransfer(_recipients[0].recipient, amountToSend); + emit ERC20Transferred(address(erc20Contract), _recipients[0].recipient, amountToSend); return true; } catch { return false; } } - /// @notice made for unexpected scenarios when assets are sent to this contact such that they could be recovered. - /// @dev first attempts to split ERC20 tokens. - /// @param target target of the call - /// @param callData for the call. - function proxyCall(address payable target, bytes calldata callData) external { - Recipient memory commonRecipient = _royaltyManager.getCommonRecipient(); - require( - commonRecipient.recipient == _msgSender() || _recipient == _msgSender(), - "Split: Can only be called by one of the recipients" - ); - require( - !callData.startsWith(IERC20Approve.approve.selector) && - !callData.startsWith(IERC20Approve.increaseAllowance.selector), - "Split: ERC20 tokens must be split" - ); - /* solhint-disable-next-line no-empty-blocks*/ - try this.splitERC20Tokens(IERC20(target)) {} catch {} - target.functionCall(callData); - } - /// @notice verify whether a forwarder address is the trustedForwarder address, using the manager setting /// @dev this function is used to avoid having a trustedForwarder variable inside the splitter - /// @return bool whether the forwarder is the trusted address - function _isTrustedForwarder(address forwarder) internal view override(ERC2771HandlerAbstract) returns (bool) { - return forwarder == _royaltyManager.getTrustedForwarder(); + /// @return isTrusted bool whether the forwarder is the trusted address + function _isTrustedForwarder(address forwarder) + internal + view + override(ERC2771HandlerAbstract) + returns (bool isTrusted) + { + return (forwarder == royaltyManager.getTrustedForwarder()); } function _msgSender() @@ -223,7 +213,7 @@ contract RoyaltySplitter is view virtual override(ContextUpgradeable, ERC2771HandlerAbstract) - returns (bytes calldata) + returns (bytes calldata messageData) { return ERC2771HandlerAbstract._msgData(); } diff --git a/packages/dependency-royalty-management/contracts/interfaces/IERC20Approve.sol b/packages/dependency-royalty-management/contracts/interfaces/IERC20Approve.sol index 6bcdf5df23..a5f667d8b8 100644 --- a/packages/dependency-royalty-management/contracts/interfaces/IERC20Approve.sol +++ b/packages/dependency-royalty-management/contracts/interfaces/IERC20Approve.sol @@ -2,8 +2,18 @@ pragma solidity ^0.8.0; +///@title IERC20Approve +///@notice Interface for ERC20 token approval operations interface IERC20Approve { + ///@notice Approves the specified spender to spend up to the given amount of tokens on behalf of the sender + ///@param spender The address that is allowed to spend tokens + ///@param amount The maximum amount of tokens that the spender is allowed to spend + ///@return `true` if the approval was successful, otherwise `false` function approve(address spender, uint256 amount) external returns (bool); + ///@notice Increases the allowance granted to the specified spender by the given amount + ///@param spender The address that is allowed to spend tokens + ///@param amount The additional amount of tokens that the spender is allowed to spend + ///@return `true` if the increase in allowance was successful, otherwise `false` function increaseAllowance(address spender, uint256 amount) external returns (bool); } diff --git a/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyDistributor.sol b/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyDistributor.sol index fb296a4ff6..760894bba0 100644 --- a/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyDistributor.sol +++ b/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyDistributor.sol @@ -5,17 +5,18 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IMultiRoyaltyRecipients} from "./IMultiRoyaltyRecipients.sol"; import {Recipient} from "@manifoldxyz/royalty-registry-solidity/contracts/overrides/IRoyaltySplitter.sol"; -/** - * Multi-receiver EIP2981 reference override implementation - */ +///Multi-receiver EIP2981 reference override implementation interface IMultiRoyaltyDistributor is IERC165, IMultiRoyaltyRecipients { event TokenRoyaltyRemoved(uint256 tokenId); - event TokenRoyaltySet(uint256 tokenId, address recipient); event DefaultRoyaltyBpsSet(uint16 royaltyBPS); - event DefaultRoyaltyReceiverSet(address recipient); + event DefaultRoyaltyReceiverSet(address indexed recipient); - event RoyaltyRecipientSet(address splitter, address recipient); + event RoyaltyRecipientSet(address indexed splitter, address indexed recipient); + + event TokenRoyaltySplitterSet(uint256 tokenId, address splitterAddress); + + event RoyaltyManagerSet(address indexed _royaltyManager); struct TokenRoyaltyConfig { uint256 tokenId; @@ -23,22 +24,17 @@ interface IMultiRoyaltyDistributor is IERC165, IMultiRoyaltyRecipients { Recipient[] recipients; } - /** - * @dev Set per token royalties. Passing a recipient of address(0) will delete any existing configuration - */ + ///@notice Set per token royalties. Passing a recipient of address(0) will delete any existing configuration + ///@param tokenId The ID of the token for which to set the royalties. + ///@param recipient The address that will receive the royalties. + ///@param creator The creator's address for the token. function setTokenRoyalties( uint256 tokenId, address payable recipient, address creator ) external; - /** - * @dev Get all token royalty configurations - */ - function getTokenRoyalties() external view returns (TokenRoyaltyConfig[] memory); - - /** - * @dev Helper function to get all splits contracts - */ + ///@notice Helper function to get all splits contracts + ///@return an array of royalty receiver function getAllSplits() external view returns (address payable[] memory); } diff --git a/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyRecipients.sol b/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyRecipients.sol index 91e60192e4..a6f788df99 100644 --- a/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyRecipients.sol +++ b/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyRecipients.sol @@ -4,12 +4,8 @@ pragma solidity ^0.8.0; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {Recipient} from "@manifoldxyz/royalty-registry-solidity/contracts/overrides/IRoyaltySplitter.sol"; -/** - * Multi-receiver EIP2981 reference override implementation - */ +/// Multi-receiver EIP2981 implementation interface IMultiRoyaltyRecipients is IERC165 { - /** - * @dev Helper function to get all recipients - */ + /// @dev Helper function to get all recipients function getRecipients(uint256 tokenId) external view returns (Recipient[] memory); } diff --git a/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyManager.sol b/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyManager.sol index 95aa807d78..6e45f0d14c 100644 --- a/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyManager.sol +++ b/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyManager.sol @@ -3,30 +3,63 @@ pragma solidity ^0.8.0; import {Recipient} from "@manifoldxyz/royalty-registry-solidity/contracts/overrides/IRoyaltySplitter.sol"; +/// @title IRoyaltyManager +/// @notice interface for RoyaltyManager Contract interface IRoyaltyManager { - event RecipientSet(address commonRecipient); + event RecipientSet(address indexed commonRecipient); event SplitSet(uint16 commonSplit); - event RoyaltySet(uint16 royaltyBps, address contractAddress); + event RoyaltySet(uint16 royaltyBps, address indexed contractAddress); + event TrustedForwarderSet(address indexed previousForwarder, address indexed newForwarder); + + event SplitterDeployed(address indexed creator, address indexed recipient, address splitterAddress); + + ///@notice sets the common recipient + ///@param _commonRecipient is the common recipient for all the splitters function setRecipient(address payable _commonRecipient) external; + ///@notice sets the common split + ///@param commonSplit split for the common recipient function setSplit(uint16 commonSplit) external; + ///@notice to be called by the splitters to get the common recipient and split + ///@return recipient which has the common recipient and split function getCommonRecipient() external view returns (Recipient memory recipient); - function getCreatorSplit() external view returns (uint16); + ///@notice returns the amount of basis points allocated to the creator + ///@return creatorSplit the share of creator in bps + function getCreatorSplit() external view returns (uint16 creatorSplit); - function getRoyaltyInfo() external view returns (address payable, uint16); + ///@notice returns the commonRecipient and EIP2981 royalty split + ///@return recipient address of common royalty recipient + ///@return royaltySplit contract EIP2981 royalty bps + function getRoyaltyInfo() external view returns (address payable recipient, uint16 royaltySplit); - function deploySplitter(address creator, address payable recipient) external returns (address payable); + ///@notice deploys splitter for creator + ///@param creator the address of the creator + ///@param recipient the wallet of the recipient where they would receive their royalty + ///@return creatorSplitterAddress splitter's address deployed for creator + function deploySplitter(address creator, address payable recipient) + external + returns (address payable creatorSplitterAddress); - function getCreatorRoyaltySplitter(address creator) external view returns (address payable); + ///@notice returns the address of splitter of a creator. + ///@param creator the address of the creator + ///@return creatorSplitterAddress splitter's address deployed for a creator + function getCreatorRoyaltySplitter(address creator) external view returns (address payable creatorSplitterAddress); + ///@notice returns the EIP2981 royalty split + ///@param _contractAddress the address of the contract for which the royalty is required + ///@return royaltyBps royalty bps of the contract function getContractRoyalty(address _contractAddress) external view returns (uint16 royaltyBps); + ///@notice sets the trustedForwarder address to be used by the splitters + ///@param _newForwarder is the new trusted forwarder address function setTrustedForwarder(address _newForwarder) external; - function getTrustedForwarder() external view returns (address); + ///@notice get the current trustedForwarder address + ///@return trustedForwarder address of current trusted Forwarder + function getTrustedForwarder() external view returns (address trustedForwarder); } diff --git a/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyUGC.sol b/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyUGC.sol index 2f7b89cfda..9344fd554d 100644 --- a/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyUGC.sol +++ b/packages/dependency-royalty-management/contracts/interfaces/IRoyaltyUGC.sol @@ -1,6 +1,11 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +/// @title IRoyaltyUGC +/// @notice interface define function for managing creator of UGC (User-Generated Content) interface IRoyaltyUGC { + ///@notice Gets the address of the creator associated with a specific token. + ///@param tokenId the Id of token to retrieve the creator address for + ///@return creator the address of creator function getCreatorAddress(uint256 tokenId) external pure returns (address creator); } diff --git a/packages/dependency-royalty-management/contracts/mock/OverflowERC20.sol b/packages/dependency-royalty-management/contracts/mock/OverflowERC20.sol new file mode 100644 index 0000000000..729c072da9 --- /dev/null +++ b/packages/dependency-royalty-management/contracts/mock/OverflowERC20.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +contract OverflowTestERC20 { + uint256 constant MAX_BALANCE = 115792089237316195423570985008687907853269984665640564039457584007913129639935; + mapping(address => uint256) private balances; + + function mintMax(address account) external { + balances[account] = MAX_BALANCE; + } + + function balanceOf(address _account) external view returns (uint256) { + return balances[_account]; + } +} diff --git a/packages/dependency-royalty-management/docs/RoyaltySplitter.md b/packages/dependency-royalty-management/docs/RoyaltySplitter.md index 4f2229795b..2a5b665c35 100644 --- a/packages/dependency-royalty-management/docs/RoyaltySplitter.md +++ b/packages/dependency-royalty-management/docs/RoyaltySplitter.md @@ -53,25 +53,11 @@ This contract calls the RoyaltyManager contract for the common recipient's addre --- -```Solidity - function proxyCall( - address payable target, - bytes calldata callData - ) external -``` - -- Made for unexpected scenarios when assets are sent to this contact such that they could be recovered. -- first attempts to split ERC20 tokens. -- `target` target of the call -- `callData` for the call. - ---- - ```Solidity function splitERC20Tokens(IERC20 erc20Contract) public ``` -- This function allows recipients to split all available ERC20 at the provided address between the recipients +- This function allows recipients to split all available ERC20 at the provided address between the recipients - recipients(both creator and common) can only call this function to split all available tokens recipients. - `erc20Contract`: The ERC20 token contract to split diff --git a/packages/dependency-royalty-management/test/RoyaltyDistribution.test.ts b/packages/dependency-royalty-management/test/RoyaltyDistribution.test.ts index 98913e74d0..831b01b14d 100644 --- a/packages/dependency-royalty-management/test/RoyaltyDistribution.test.ts +++ b/packages/dependency-royalty-management/test/RoyaltyDistribution.test.ts @@ -7,6 +7,51 @@ const zeroAddress = '0x0000000000000000000000000000000000000000'; describe('Royalty', function () { describe('Royalty distribution through splitter', function () { + it('should emit TrustedForwarderSet event with correct data when setting new trusted forwarder', async function () { + const { + deployer, + RoyaltyManagerContract, + RoyaltyManagerAsAdmin, + TrustedForwarder, + } = await royaltyDistribution(); + expect( + await await RoyaltyManagerContract.getTrustedForwarder() + ).to.be.equal(TrustedForwarder.address); + await expect(RoyaltyManagerAsAdmin.setTrustedForwarder(deployer.address)) + .to.emit(RoyaltyManagerContract, 'TrustedForwarderSet') + .withArgs(TrustedForwarder.address, deployer.address); + }); + + it('should emit SplitterDeployed event with correct data when deploying splitter', async function () { + const { + seller, + royaltyReceiver, + RoyaltyManagerContract, + RoyaltyManagerAsAdmin, + splitterDeployerRole, + } = await royaltyDistribution(); + + expect( + await RoyaltyManagerContract.creatorRoyaltiesSplitter(seller.address) + ).to.be.equals('0x0000000000000000000000000000000000000000'); + await RoyaltyManagerAsAdmin.grantRole( + splitterDeployerRole, + seller.address + ); + + const splitterDeployedTx = await RoyaltyManagerContract.connect( + seller + ).deploySplitter(seller.address, royaltyReceiver.address); + + await expect(splitterDeployedTx) + .to.emit(RoyaltyManagerContract, 'SplitterDeployed') + .withArgs( + seller.address, + royaltyReceiver.address, + await RoyaltyManagerContract.creatorRoyaltiesSplitter(seller.address) + ); + }); + it('should split ERC20 using EIP2981', async function () { const { ERC1155, @@ -41,7 +86,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -76,6 +121,7 @@ describe('Royalty', function () { (1000000 * (erc1155Royalty / 10000)) / 2 ); }); + it('should split ERC20 using EIP2981 using trusted forwarder', async function () { const { ERC1155, @@ -111,7 +157,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -380,7 +426,7 @@ describe('Royalty', function () { '0x' ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( seller.address ); @@ -389,7 +435,7 @@ describe('Royalty', function () { splitter ); - expect(await splitterContract._recipient()).to.be.equal( + expect(await splitterContract.recipient()).to.be.equal( royaltyReceiver.address ); @@ -399,7 +445,7 @@ describe('Royalty', function () { await tnx.wait(); - expect(await splitterContract._recipient()).to.be.equal( + expect(await splitterContract.recipient()).to.be.equal( royaltyReceiver2.address ); @@ -655,7 +701,7 @@ describe('Royalty', function () { '0x' ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( seller.address ); @@ -664,7 +710,7 @@ describe('Royalty', function () { splitter ); - expect(await splitterContract._recipient()).to.be.equal( + expect(await splitterContract.recipient()).to.be.equal( royaltyReceiver.address ); @@ -674,7 +720,7 @@ describe('Royalty', function () { await tnx.wait(); - expect(await splitterContract._recipient()).to.be.equal( + expect(await splitterContract.recipient()).to.be.equal( royaltyReceiver2.address ); @@ -863,7 +909,7 @@ describe('Royalty', function () { '0x' ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); const splitterContract = await ethers.getContractAt( @@ -929,7 +975,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -964,7 +1010,7 @@ describe('Royalty', function () { '0x' ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( seller.address ); @@ -973,7 +1019,7 @@ describe('Royalty', function () { splitter ); - expect(await splitterContract._recipient()).to.be.equal( + expect(await splitterContract.recipient()).to.be.equal( royaltyReceiver.address ); @@ -983,7 +1029,7 @@ describe('Royalty', function () { await tnx.wait(); - expect(await splitterContract._recipient()).to.be.equal(seller.address); + expect(await splitterContract.recipient()).to.be.equal(seller.address); }); it('only creator could change the recipient for his splitter', async function () { @@ -1093,6 +1139,47 @@ describe('Royalty', function () { `AccessControl: account ${seller.address.toLowerCase()} is missing role ${await RoyaltyManagerContract.CONTRACT_ROYALTY_SETTER_ROLE()}` ); }); + it('should be reverted when caller do not have splitter deployer role', async function () { + const { + RoyaltyManagerContract, + seller, + royaltyReceiver, + splitterDeployerRole, + } = await royaltyDistribution(); + await expect( + RoyaltyManagerContract.connect(seller).deploySplitter( + seller.address, + royaltyReceiver.address + ) + ).to.be.revertedWith( + `AccessControl: account ${seller.address.toLocaleLowerCase()} is missing role ${splitterDeployerRole}` + ); + }); + it('should not be reverted when caller have splitter deployer role', async function () { + const { + RoyaltyManagerContract, + seller, + royaltyReceiver, + splitterDeployerRole, + RoyaltyManagerAsAdmin, + } = await royaltyDistribution(); + await RoyaltyManagerAsAdmin.grantRole( + splitterDeployerRole, + seller.address + ); + expect( + await RoyaltyManagerContract.creatorRoyaltiesSplitter(seller.address) + ).to.be.equals('0x0000000000000000000000000000000000000000'); + + await RoyaltyManagerContract.connect(seller).deploySplitter( + seller.address, + royaltyReceiver.address + ); + + expect( + await RoyaltyManagerContract.creatorRoyaltiesSplitter(seller.address) + ).to.not.equals('0x0000000000000000000000000000000000000000'); + }); }); describe('Multi contract splitter setup', function () { @@ -1106,7 +1193,7 @@ describe('Royalty', function () { '0x' ); - const splitter1 = await ERC1155._tokenRoyaltiesSplitter(1); + const splitter1 = await ERC1155.getTokenRoyaltiesSplitter(1); await ERC1155.connect(seller).mint( seller.address, @@ -1116,7 +1203,7 @@ describe('Royalty', function () { '0x' ); - const splitter2 = await ERC1155._tokenRoyaltiesSplitter(2); + const splitter2 = await ERC1155.getTokenRoyaltiesSplitter(2); expect(splitter1).to.be.equal(splitter2); }); @@ -1132,7 +1219,7 @@ describe('Royalty', function () { '0x' ); - const splitter1 = await ERC1155._tokenRoyaltiesSplitter(1); + const splitter1 = await ERC1155.getTokenRoyaltiesSplitter(1); await ERC1155.connect(buyer).mint( buyer.address, @@ -1142,7 +1229,7 @@ describe('Royalty', function () { '0x' ); - const splitter2 = await ERC1155._tokenRoyaltiesSplitter(2); + const splitter2 = await ERC1155.getTokenRoyaltiesSplitter(2); expect(splitter1).to.not.be.equal(splitter2); }); @@ -1157,7 +1244,7 @@ describe('Royalty', function () { '0x' ); - const splitter = await ERC1155._tokenRoyaltiesSplitter(1); + const splitter = await ERC1155.getTokenRoyaltiesSplitter(1); const royaltyInfo = await ERC1155.royaltyInfo(1, 10000); @@ -1214,7 +1301,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1650,7 +1737,7 @@ describe('Royalty', function () { '0x' ); - const splitter1 = await ERC1155._tokenRoyaltiesSplitter(1); + const splitter1 = await ERC1155.getTokenRoyaltiesSplitter(1); await ERC721.connect(seller).mint( seller.address, @@ -1658,7 +1745,7 @@ describe('Royalty', function () { royaltyReceiver.address ); - const splitter2 = await ERC721._tokenRoyaltiesSplitter(2); + const splitter2 = await ERC721.getTokenRoyaltiesSplitter(2); expect(splitter1).to.be.equal(splitter2); }); @@ -1681,7 +1768,94 @@ describe('Royalty', function () { expect( await RoyaltyManagerContract.getCreatorRoyaltySplitter(deployer.address) - ).to.be.equal(await ERC1155._tokenRoyaltiesSplitter(1)); + ).to.be.equal(await ERC1155.getTokenRoyaltiesSplitter(1)); + }); + it('should emit Token Royalty Splitter set event when a token is minted for first time', async function () { + const {seller, ERC1155, deployer, royaltyReceiver} = + await royaltyDistribution(); + + const mintTx = await ERC1155.connect(deployer).mint( + seller.address, + 1, + 1, + royaltyReceiver.address, + '0x' + ); + const mintResult = await mintTx.wait(); + const splitterSetEvent = mintResult.events[5]; + expect(splitterSetEvent.event).to.equal('TokenRoyaltySplitterSet'); + }); + it('should not emit Token Royalty Splitter set event when a token is minted for second time', async function () { + const {seller, ERC1155, deployer, royaltyReceiver} = + await royaltyDistribution(); + const mintTx = await ERC1155.connect(deployer).mint( + seller.address, + 1, + 1, + royaltyReceiver.address, + '0x' + ); + const mintResult = await mintTx.wait(); + const splitterSetEvent = mintResult.events[5]; + expect(splitterSetEvent.event).to.equal('TokenRoyaltySplitterSet'); + const mintTx2 = await ERC1155.connect(deployer).mint( + seller.address, + 1, + 1, + royaltyReceiver.address, + '0x' + ); + const mintResult2 = await mintTx2.wait(); + for (let i = 0; i < mintResult2.events.length; i++) { + expect(mintResult.events[i].event).to.not.equal( + 'TokenRoyaltySplitterSet' + ); + } + }); + it('should emit recipient set event when a token minted for the first time', async function () { + const {seller, ERC1155, deployer, royaltyReceiver} = + await royaltyDistribution(); + const mintTx = await ERC1155.connect(deployer).mint( + seller.address, + 1, + 1, + royaltyReceiver.address, + '0x' + ); + const mintResult = await mintTx.wait(); + const log = mintResult.logs[1]; + expect(log.topics[0]).to.be.equal( + ethers.utils.id('RecipientSet(address)') + ); + }); + it('should not emit recipient set event when token is minted for second time', async function () { + const {seller, ERC1155, deployer, royaltyReceiver} = + await royaltyDistribution(); + const mintTx = await ERC1155.connect(deployer).mint( + seller.address, + 1, + 1, + royaltyReceiver.address, + '0x' + ); + const mintResult = await mintTx.wait(); + const log = mintResult.logs[1]; + expect(log.topics[0]).to.be.equal( + ethers.utils.id('RecipientSet(address)') + ); + const mintTx2 = await ERC1155.connect(deployer).mint( + seller.address, + 1, + 1, + royaltyReceiver.address, + '0x' + ); + const mintResult2 = await mintTx2.wait(); + for (let i = 0; i < mintResult2.events.length; i++) { + expect(mintResult.logs[i].topics[0]).to.not.equal( + ethers.utils.id('RecipientSet(address)') + ); + } }); }); @@ -1768,7 +1942,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); const splitterContract = await ethers.getContractAt( @@ -1815,7 +1989,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1868,7 +2042,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1893,16 +2067,14 @@ describe('Royalty', function () { ) ).to.be.revertedWith('Manager: No splitter deployed for the creator'); }); - }); - describe('Interfaces', function () { - it('should support interface for Royalty Splitter', async function () { + it('should revert on for overflow for try mul in splitter contract', async function () { const { - RoyaltyManagerContract, - deployer, ERC1155, + deployer, seller, royaltyReceiver, + RoyaltyManagerContract, } = await royaltyDistribution(); await ERC1155.connect(deployer).mint( seller.address, @@ -1911,7 +2083,12 @@ describe('Royalty', function () { royaltyReceiver.address, '0x' ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const TestERC20Factory = await ethers.getContractFactory( + 'OverflowTestERC20' + ); + const OverflowERC20 = await TestERC20Factory.deploy(); + + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1919,72 +2096,24 @@ describe('Royalty', function () { splitterAbi, splitter ); - expect(await splitterContract.supportsInterface(0x16cf0c05)).to.be.equal( - true - ); - }); - it('should support interface for royalty distributer', async function () { - const royaltyDistributerFactory = await ethers.getContractFactory( - 'RoyaltyDistributor' - ); - const royaltyDistributer = await royaltyDistributerFactory.deploy(); - expect( - await royaltyDistributer.supportsInterface(0x2a55205a) - ).to.be.equal(true); - }); - it('should support interface for multiRoyalty distributer', async function () { - const {ERC1155} = await royaltyDistribution(); - expect(await ERC1155.supportsInterface(0x2a55205a)).to.be.equal(true); + await OverflowERC20.mintMax(splitter); + await expect( + splitterContract + .connect(royaltyReceiver) + .splitERC20Tokens(OverflowERC20.address) + ).to.be.revertedWith('RoyaltySplitter: Multiplication Overflow'); }); }); - describe('Proxy method for extracting stuck NFTs on splitters', function () { - it('should transfer stuck Token using proxy call', async function () { + describe('Interfaces', function () { + it('should support interface for Royalty Splitter', async function () { const { RoyaltyManagerContract, - seller, - ERC1155, deployer, - royaltyReceiver, - NFT, - } = await royaltyDistribution(); - await ERC1155.connect(deployer).mint( - seller.address, - 1, - 1, - royaltyReceiver.address, - '0x' - ); - - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( - deployer.address - ); - - await NFT.mint(splitter, 1, 1); - expect(await NFT.balanceOf(splitter, 1)).to.be.equal(1); - const data = await NFT.populateTransaction[ - 'transfer(address,address,uint256,uint256)' - ](splitter, seller.address, 1, 1); - - const splitterContract = await ethers.getContractAt( - splitterAbi, - splitter - ); - await splitterContract - .connect(royaltyReceiver) - .proxyCall(NFT.address, data.data); - expect(await NFT.balanceOf(seller.address, 1)).to.be.equal(1); - }); - it('should revert when approving ERC20 token using proxy call', async function () { - const { - RoyaltyManagerContract, - seller, ERC1155, - deployer, + seller, royaltyReceiver, - ERC20, - NFT, } = await royaltyDistribution(); await ERC1155.connect(deployer).mint( seller.address, @@ -1993,66 +2122,29 @@ describe('Royalty', function () { royaltyReceiver.address, '0x' ); - - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); - await NFT.mint(splitter, 1, 1); - await ERC20.mint(splitter, 1000000); - const data = await ERC20.populateTransaction['approve(address,uint256)']( - seller.address, - 10000 - ); - const splitterContract = await ethers.getContractAt( splitterAbi, splitter ); - await expect( - splitterContract - .connect(royaltyReceiver) - .proxyCall(ERC20.address, data.data) - ).to.be.revertedWith('Split: ERC20 tokens must be split'); - }); - it('should revert when proxy call is called by non recipient', async function () { - const { - RoyaltyManagerContract, - seller, - ERC1155, - deployer, - royaltyReceiver, - ERC20, - NFT, - } = await royaltyDistribution(); - await ERC1155.connect(deployer).mint( - seller.address, - 1, - 1, - royaltyReceiver.address, - '0x' - ); - - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( - deployer.address + expect(await splitterContract.supportsInterface(0x16cf0c05)).to.be.equal( + true ); + }); + it('should support interface for royalty distributer', async function () { + const {SingleReceiver} = await royaltyDistribution(); - await NFT.mint(splitter, 1, 1); - await ERC20.mint(splitter, 1000000); - const data = await ERC20.populateTransaction['approve(address,uint256)']( - seller.address, - 10000 + expect(await SingleReceiver.supportsInterface(0x2a55205a)).to.be.equal( + true ); + }); - const splitterContract = await ethers.getContractAt( - splitterAbi, - splitter - ); - await expect( - splitterContract.proxyCall(ERC20.address, data.data) - ).to.be.revertedWith( - 'Split: Can only be called by one of the recipients' - ); + it('should support interface for multiRoyalty distributer', async function () { + const {ERC1155} = await royaltyDistribution(); + expect(await ERC1155.supportsInterface(0x2a55205a)).to.be.equal(true); }); }); @@ -2090,7 +2182,7 @@ describe('Royalty', function () { ); expect(await ERC1155.royaltyInfo(1, 0)).to.deep.equal([ - await ERC1155._tokenRoyaltiesSplitter(1), + await ERC1155.getTokenRoyaltiesSplitter(1), 0, ]); }); @@ -2124,7 +2216,7 @@ describe('Royalty', function () { ); expect(await ERC1155.getAllSplits()).to.deep.equal([ commonRoyaltyReceiver.address, - await ERC1155._tokenRoyaltiesSplitter(1), + await ERC1155.getTokenRoyaltiesSplitter(1), ]); }); it('should return all the royalty recipient of a token', async function () { @@ -2152,16 +2244,5 @@ describe('Royalty', function () { commonRoyaltyReceiver.address ); }); - it('should return the tokens for which the royalties is set', async function () { - const {ERC1155, seller, royaltyReceiver} = await royaltyDistribution(); - await ERC1155.connect(seller).mint( - seller.address, - 1, - 1, - royaltyReceiver.address, - '0x' - ); - expect((await ERC1155.getTokenRoyalties()).length).to.be.equals(1); - }); }); }); diff --git a/packages/dependency-royalty-management/test/Splitter.abi.ts b/packages/dependency-royalty-management/test/Splitter.abi.ts index a338671f71..3d0d752164 100644 --- a/packages/dependency-royalty-management/test/Splitter.abi.ts +++ b/packages/dependency-royalty-management/test/Splitter.abi.ts @@ -76,30 +76,17 @@ export const splitterAbi = [ type: 'event', }, { - inputs: [], - name: '_recipient', - outputs: [ - { - internalType: 'address payable', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: '_royaltyManager', - outputs: [ + anonymous: false, + inputs: [ { - internalType: 'contract IRoyaltyManager', - name: '', + indexed: true, + internalType: 'address', + name: 'recipientAddress', type: 'address', }, ], - stateMutability: 'view', - type: 'function', + name: 'RecipientSet', + type: 'event', }, { inputs: [], @@ -130,12 +117,12 @@ export const splitterAbi = [ inputs: [ { internalType: 'address payable', - name: 'recipient', + name: '_recipient', type: 'address', }, { internalType: 'address', - name: 'royaltyManager', + name: '_royaltyManager', type: 'address', }, ], @@ -144,6 +131,25 @@ export const splitterAbi = [ stateMutability: 'nonpayable', type: 'function', }, + { + inputs: [ + { + internalType: 'address', + name: 'forwarder', + type: 'address', + }, + ], + name: 'isTrustedForwarder', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [], name: 'owner', @@ -158,21 +164,16 @@ export const splitterAbi = [ type: 'function', }, { - inputs: [ + inputs: [], + name: 'recipient', + outputs: [ { internalType: 'address payable', - name: 'target', + name: '', type: 'address', }, - { - internalType: 'bytes', - name: 'callData', - type: 'bytes', - }, ], - name: 'proxyCall', - outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'view', type: 'function', }, { @@ -182,6 +183,19 @@ export const splitterAbi = [ stateMutability: 'nonpayable', type: 'function', }, + { + inputs: [], + name: 'royaltyManager', + outputs: [ + { + internalType: 'contract IRoyaltyManager', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [ { diff --git a/packages/dependency-royalty-management/test/fixture.ts b/packages/dependency-royalty-management/test/fixture.ts index fd41948413..79d4042ba1 100644 --- a/packages/dependency-royalty-management/test/fixture.ts +++ b/packages/dependency-royalty-management/test/fixture.ts @@ -112,6 +112,8 @@ export async function royaltyDistribution() { const managerAdminRole = await RoyaltyManagerContract.DEFAULT_ADMIN_ROLE(); const contractRoyaltySetterRole = await RoyaltyManagerContract.CONTRACT_ROYALTY_SETTER_ROLE(); + const splitterDeployerRole = + await RoyaltyManagerContract.SPLITTER_DEPLOYER_ROLE(); const RoyaltyManagerAsAdmin = RoyaltyManagerContract.connect(managerAdmin); const RoyaltyManagerAsRoyaltySetter = RoyaltyManagerContract.connect( contractRoyaltySetter @@ -126,6 +128,8 @@ export async function royaltyDistribution() { await RoyaltyManagerAsRoyaltySetter.setContractRoyalty(ERC1155.address, 300); await RoyaltyManagerAsRoyaltySetter.setContractRoyalty(ERC721.address, 300); + await RoyaltyManagerAsAdmin.grantRole(splitterDeployerRole, ERC1155.address); + await RoyaltyManagerAsAdmin.grantRole(splitterDeployerRole, ERC721.address); return { ERC1155, @@ -154,5 +158,6 @@ export async function royaltyDistribution() { SingleReceiver, NFT, TrustedForwarder, + splitterDeployerRole, }; } diff --git a/packages/deploy/deploy/400_asset/400_asset_operator_filter_setup.ts b/packages/deploy/deploy/400_asset/400_asset_operator_filter_setup.ts index a04b80f56a..8441e7963f 100644 --- a/packages/deploy/deploy/400_asset/400_asset_operator_filter_setup.ts +++ b/packages/deploy/deploy/400_asset/400_asset_operator_filter_setup.ts @@ -27,7 +27,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { DEFAULT_SUBSCRIPTION ); console.log( - "common subscription registered on operator filter registry and opensea's blacklist copied" + "common subscription registered on operator filter registry and OpenSea's blacklist copied" ); } } diff --git a/packages/deploy/deploy/400_asset/407_asset_setup.ts b/packages/deploy/deploy/400_asset/407_asset_setup.ts index 909778b554..925488d523 100644 --- a/packages/deploy/deploy/400_asset/407_asset_setup.ts +++ b/packages/deploy/deploy/400_asset/407_asset_setup.ts @@ -5,7 +5,8 @@ export const DEFAULT_BPS = 300; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployments, getNamedAccounts} = hre; const {execute, log, read, catchUnknownSigner} = deployments; - const {assetAdmin, contractRoyaltySetter} = await getNamedAccounts(); + const {assetAdmin, contractRoyaltySetter, royaltyManagerAdmin} = + await getNamedAccounts(); const Asset = await deployments.get('Asset'); @@ -33,6 +34,21 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ) ); log(`Asset set on RoyaltyManager with ${DEFAULT_BPS} BPS royalty`); + + const splitterDeployerRole = await read( + 'RoyaltyManager', + 'SPLITTER_DEPLOYER_ROLE' + ); + await catchUnknownSigner( + execute( + 'RoyaltyManager', + {from: royaltyManagerAdmin, log: true}, + 'grantRole', + splitterDeployerRole, + Asset.address + ) + ); + log(`Asset set on RoyaltyManager with Splitter Deployer Role`); }; export default func; diff --git a/packages/deploy/test/asset/Asset.test.ts b/packages/deploy/test/asset/Asset.test.ts index e295d96974..80a034f1c8 100644 --- a/packages/deploy/test/asset/Asset.test.ts +++ b/packages/deploy/test/asset/Asset.test.ts @@ -255,7 +255,7 @@ describe('Asset', function () { describe('MultiRoyaltyDistributor', function () { it('RoyaltyManager contract is set correctly', async function () { const {AssetContract, RoyaltyManagerContract} = await setupTest(); - expect(await AssetContract.royaltyManager()).to.be.equal( + expect(await AssetContract.getRoyaltyManager()).to.be.equal( RoyaltyManagerContract.address ); }); diff --git a/packages/deploy/test/catalyst/catalyst.test.ts b/packages/deploy/test/catalyst/catalyst.test.ts index d4868133fb..4f3c519dee 100644 --- a/packages/deploy/test/catalyst/catalyst.test.ts +++ b/packages/deploy/test/catalyst/catalyst.test.ts @@ -145,7 +145,7 @@ describe('Catalyst', function () { describe('Check Royalty', function () { it('RoyaltyManager contract is set correctly', async function () { const {CatalystContract, RoyaltyManagerContract} = await setupTest(); - expect(await CatalystContract.royaltyManager()).to.be.equal( + expect(await CatalystContract.getRoyaltyManager()).to.be.equal( RoyaltyManagerContract.address ); });