diff --git a/packages/asset/contracts/Asset.sol b/packages/asset/contracts/Asset.sol index 964f35add2..38eebde71f 100644 --- a/packages/asset/contracts/Asset.sol +++ b/packages/asset/contracts/Asset.sol @@ -336,7 +336,7 @@ 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". diff --git a/packages/asset/contracts/Catalyst.sol b/packages/asset/contracts/Catalyst.sol index 4ff7d57407..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"); OperatorFiltererUpgradeable._setOperatorFilterRegistry(registry); + emit OperatorRegistrySet(registry); } + + uint256[49] private __gap; } 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/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/AssetReveal.test.ts b/packages/asset/test/AssetReveal.test.ts index 21f84fd9b2..2d8de7aed4 100644 --- a/packages/asset/test/AssetReveal.test.ts +++ b/packages/asset/test/AssetReveal.test.ts @@ -66,8 +66,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes1, [revealHashA] ); - expect(result.events[4].event).to.equal('AssetRevealMint'); - const newTokenId = result.events[4].args.newTokenIds[0]; + expect(result.events[3].event).to.equal('AssetRevealMint'); + const newTokenId = result.events[3].args.newTokenIds[0]; const revealNonce = await TokenIdUtilsContract.getRevealNonce(newTokenId); expect(revealNonce.toString()).to.equal('1'); @@ -86,8 +86,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashB] ); - expect(result2.events[4].event).to.equal('AssetRevealMint'); - const newTokenId2 = result2.events[4].args.newTokenIds[0]; + expect(result2.events[3].event).to.equal('AssetRevealMint'); + const newTokenId2 = result2.events[3].args.newTokenIds[0]; const revealNonce2 = await TokenIdUtilsContract.getRevealNonce( newTokenId2 ); @@ -123,8 +123,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashA] ); - expect(result.events[4].event).to.equal('AssetRevealMint'); - const newTokenId = result.events[4].args.newTokenIds[0]; + expect(result.events[3].event).to.equal('AssetRevealMint'); + const newTokenId = result.events[3].args.newTokenIds[0]; const revealNonce = await TokenIdUtilsContract.getRevealNonce(newTokenId); expect(revealNonce.toString()).to.equal('1'); @@ -143,8 +143,8 @@ 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]; + expect(result2.events[1].event).to.equal('AssetRevealMint'); + const newTokenId2 = result2.events[1].args.newTokenIds[0]; const revealNonce2 = await TokenIdUtilsContract.getRevealNonce( newTokenId2 ); @@ -650,8 +650,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashA] ); - expect(result.events[4].event).to.equal('AssetRevealMint'); - const newTokenId = result.events[4].args.newTokenIds[0]; + expect(result.events[3].event).to.equal('AssetRevealMint'); + const newTokenId = result.events[3].args.newTokenIds[0]; const balance = await AssetContract.balanceOf( user.address, newTokenId @@ -684,9 +684,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes, [revealHashA] ); - expect(result.events[4].event).to.equal('AssetRevealMint'); - expect(result.events[4].args['newTokenIds'].length).to.equal(1); - const newTokenId = result.events[4].args.newTokenIds[0]; + 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 balance = await AssetContract.balanceOf( user.address, newTokenId @@ -720,7 +720,7 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashA] ); - const newTokenId = result.events[4].args.newTokenIds[0]; + const newTokenId = result.events[3].args.newTokenIds[0]; const balance = await AssetContract.balanceOf( user.address, newTokenId @@ -791,9 +791,8 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () revealHashF, ] ); - - expect(result.events[19].event).to.equal('AssetRevealMint'); - expect(result.events[19].args['newTokenIds'].length).to.equal(6); + expect(result.events[13].event).to.equal('AssetRevealMint'); + expect(result.events[13].args['newTokenIds'].length).to.equal(6); }); it('should set the reveal hash as used after successful mint', async function () { const { @@ -958,7 +957,7 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () newMetadataHashes, [revealHashA, revealHashB] ); - expect(result.events[7].event).to.equal('AssetRevealMint'); + expect(result.events[5].event).to.equal('AssetRevealMint'); }); it('should emit AssetRevealMint event with correct arguments', async function () { const { @@ -988,9 +987,9 @@ describe('AssetReveal (/packages/asset/contracts/AssetReveal.sol)', function () [revealHashA, revealHashB] ); - expect(result.events[7].event).to.equal('AssetRevealMint'); + expect(result.events[5].event).to.equal('AssetRevealMint'); - const args = result.events[7].args; + const args = result.events[5].args; const { recipient, unrevealedTokenId, diff --git a/packages/asset/test/AssetRoyalty.test.ts b/packages/asset/test/AssetRoyalty.test.ts index 39989a908b..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 () { diff --git a/packages/asset/test/Catalyst.test.ts b/packages/asset/test/Catalyst.test.ts index b596927ccd..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 () { diff --git a/packages/asset/test/Splitter.abi.ts b/packages/asset/test/Splitter.abi.ts index 7ac539e4d2..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', @@ -144,6 +163,19 @@ export const splitterAbi = [ stateMutability: 'view', type: 'function', }, + { + inputs: [], + name: 'recipient', + outputs: [ + { + internalType: 'address payable', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [], name: 'renounceOwnership', @@ -151,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: [ { @@ -193,7 +238,7 @@ export const splitterAbi = [ inputs: [], name: 'splitETH', outputs: [], - stateMutability: 'nonpayable', + stateMutability: 'payable', type: 'function', }, { 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 127c2dbe50..526e18846a 100644 --- a/packages/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol +++ b/packages/dependency-operator-filter/contracts/OperatorFiltererUpgradeable.sol @@ -6,8 +6,8 @@ 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 { event OperatorFilterRegistrySet(address indexed registry); @@ -86,4 +86,6 @@ abstract contract OperatorFiltererUpgradeable is Initializable, ContextUpgradeab { 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-royalty-management/contracts/MultiRoyaltyDistributor.sol b/packages/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol index 4b75574efa..50078e5be9 100644 --- a/packages/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol +++ b/packages/dependency-royalty-management/contracts/MultiRoyaltyDistributor.sol @@ -10,10 +10,10 @@ 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 private royaltyManager; @@ -22,19 +22,20 @@ abstract contract MultiRoyaltyDistributor is IEIP2981, IMultiRoyaltyDistributor, 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); - _setTokenRoyaltiesSplitter(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,16 +111,16 @@ 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 @@ -146,4 +143,13 @@ abstract contract MultiRoyaltyDistributor is IEIP2981, IMultiRoyaltyDistributor, 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 025eebb169..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 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,15 +39,15 @@ 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 @@ -50,4 +55,13 @@ contract RoyaltyDistributor is IERC2981Upgradeable, ERC165Upgradeable { 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 97a111f8ff..0bc532654f 100644 --- a/packages/dependency-royalty-management/contracts/RoyaltyManager.sol +++ b/packages/dependency-royalty-management/contracts/RoyaltyManager.sol @@ -19,10 +19,16 @@ contract RoyaltyManager is AccessControlUpgradeable, IRoyaltyManager { 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 @@ -44,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) { @@ -69,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) { @@ -83,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; } @@ -99,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 @@ -114,53 +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 + /// @return creatorSplitterAddress splitter's address deployed for a creator function deploySplitter(address creator, address payable recipient) external onlyRole(SPLITTER_DEPLOYER_ROLE) - returns (address payable) + returns (address payable creatorSplitterAddress) { - address payable creatorSplitterAddress = _creatorRoyaltiesSplitter[creator]; + 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 01d25461c0..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,50 +142,44 @@ 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; @@ -184,9 +188,14 @@ contract RoyaltySplitter is /// @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() @@ -204,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 5c847739da..760894bba0 100644 --- a/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyDistributor.sol +++ b/packages/dependency-royalty-management/contracts/interfaces/IMultiRoyaltyDistributor.sol @@ -5,42 +5,36 @@ 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; uint16 royaltyBPS; 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/test/RoyaltyDistribution.test.ts b/packages/dependency-royalty-management/test/RoyaltyDistribution.test.ts index 43c4f8d013..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 () { @@ -1122,7 +1168,7 @@ describe('Royalty', function () { seller.address ); expect( - await RoyaltyManagerContract._creatorRoyaltiesSplitter(seller.address) + await RoyaltyManagerContract.creatorRoyaltiesSplitter(seller.address) ).to.be.equals('0x0000000000000000000000000000000000000000'); await RoyaltyManagerContract.connect(seller).deploySplitter( @@ -1131,7 +1177,7 @@ describe('Royalty', function () { ); expect( - await RoyaltyManagerContract._creatorRoyaltiesSplitter(seller.address) + await RoyaltyManagerContract.creatorRoyaltiesSplitter(seller.address) ).to.not.equals('0x0000000000000000000000000000000000000000'); }); }); @@ -1255,7 +1301,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1724,6 +1770,93 @@ describe('Royalty', function () { await RoyaltyManagerContract.getCreatorRoyaltySplitter(deployer.address) ).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)') + ); + } + }); }); describe('Input validation', function () { @@ -1809,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( @@ -1856,7 +1989,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1909,7 +2042,7 @@ describe('Royalty', function () { seller.address, true ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1955,7 +2088,7 @@ describe('Royalty', function () { ); const OverflowERC20 = await TestERC20Factory.deploy(); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -1989,7 +2122,7 @@ describe('Royalty', function () { royaltyReceiver.address, '0x' ); - const splitter = await RoyaltyManagerContract._creatorRoyaltiesSplitter( + const splitter = await RoyaltyManagerContract.creatorRoyaltiesSplitter( deployer.address ); @@ -2002,13 +2135,11 @@ describe('Royalty', function () { ); }); it('should support interface for royalty distributer', async function () { - const royaltyDistributerFactory = await ethers.getContractFactory( - 'RoyaltyDistributor' + const {SingleReceiver} = await royaltyDistribution(); + + expect(await SingleReceiver.supportsInterface(0x2a55205a)).to.be.equal( + true ); - const royaltyDistributer = await royaltyDistributerFactory.deploy(); - expect( - await royaltyDistributer.supportsInterface(0x2a55205a) - ).to.be.equal(true); }); it('should support interface for multiRoyalty distributer', async function () { @@ -2113,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 a3a8bcdc65..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', @@ -157,6 +163,19 @@ export const splitterAbi = [ stateMutability: 'view', type: 'function', }, + { + inputs: [], + name: 'recipient', + outputs: [ + { + internalType: 'address payable', + name: '', + type: 'address', + }, + ], + stateMutability: 'view', + type: 'function', + }, { inputs: [], name: 'renounceOwnership', @@ -164,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/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" ); } }