diff --git a/contracts/interfaces/modules/royalty/IRoyaltyModule.sol b/contracts/interfaces/modules/royalty/IRoyaltyModule.sol index 7e34caee0..50db63c4a 100644 --- a/contracts/interfaces/modules/royalty/IRoyaltyModule.sol +++ b/contracts/interfaces/modules/royalty/IRoyaltyModule.sol @@ -27,6 +27,10 @@ interface IRoyaltyModule { /// @param amount The amount that is paid event RoyaltyPaid(address receiverIpId, address payerIpId, address sender, address token, uint256 amount); + /// @notice Sets the license registry + /// @param licenseRegistry The address of the license registry + function setLicenseRegistry(address licenseRegistry) external; + /// @notice Whitelist a royalty policy /// @param royaltyPolicy The address of the royalty policy /// @param allowed Indicates if the royalty policy is whitelisted or not diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index be902b99d..a180dfd88 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -131,6 +131,7 @@ library Errors { error LicenseRegistry__CallerNotLicensorAndPolicyNotSet(); error LicenseRegistry__DerivativesCannotAddPolicy(); error LicenseRegistry__IncompatibleLicensorRoyaltyPolicy(); + error LicenseRegistry__DerivativeRevShareSumExceedsMaxRNFTSupply(); //////////////////////////////////////////////////////////////////////////// // LicenseRegistryAware // @@ -181,8 +182,8 @@ library Errors { error RoyaltyModule__NotWhitelistedRoyaltyToken(); error RoyaltyModule__NoRoyaltyPolicySet(); error RoyaltyModule__IncompatibleRoyaltyPolicy(); - error RoyaltyModule__NotRegistrationModule(); - error RoyaltyModule__ZeroLicensingModule(); + error RoyaltyModule__NotAllowedCaller(); + error RoyaltyModule__ZeroLicenseRegistry(); error RoyaltyPolicyLS__ZeroRoyaltyModule(); error RoyaltyPolicyLS__ZeroLiquidSplitFactory(); @@ -198,14 +199,11 @@ library Errors { error LSClaimer__InvalidPathFirstPosition(); error LSClaimer__InvalidPathLastPosition(); error LSClaimer__AlreadyClaimed(); - error LSClaimer__ZeroRNFT(); - error LSClaimer__RNFTAlreadySet(); error LSClaimer__ETHBalanceNotZero(); error LSClaimer__ERC20BalanceNotZero(); error LSClaimer__ZeroIpId(); error LSClaimer__ZeroLicenseRegistry(); error LSClaimer__ZeroRoyaltyPolicyLS(); - error LSClaimer__NotRoyaltyPolicyLS(); //////////////////////////////////////////////////////////////////////////// // ModuleRegistry // diff --git a/contracts/modules/royalty-module/RoyaltyModule.sol b/contracts/modules/royalty-module/RoyaltyModule.sol index 60fecf190..47b5265d1 100644 --- a/contracts/modules/royalty-module/RoyaltyModule.sol +++ b/contracts/modules/royalty-module/RoyaltyModule.sol @@ -13,8 +13,8 @@ import { Errors } from "contracts/lib/Errors.sol"; /// @notice The Story Protocol royalty module allows to set royalty policies an ipId /// and pay royalties as a derivative ip. contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard { - /// @notice registration module address - address public REGISTRATION_MODULE; + /// @notice License registry address + address public LICENSE_REGISTRY; /// @notice Indicates if a royalty policy is whitelisted mapping(address royaltyPolicy => bool allowed) public isWhitelistedRoyaltyPolicy; @@ -25,19 +25,19 @@ contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard { /// @notice Indicates the royalty policy for a given ipId mapping(address ipId => address royaltyPolicy) public royaltyPolicies; - /// @notice Restricts the calls to the license module - modifier onlyRegistrationModule() { - // TODO: if (msg.sender != REGISTRATION_MODULE) revert Errors.RoyaltyModule__NotRegistrationModule(); - _; - } + /// @notice Indicates if a royalty policy is immutable + mapping(address ipId => bool) public isRoyaltyPolicyImmutable; /// @notice Constructor /// @param _governance The address of the governance contract constructor(address _governance) Governable(_governance) {} - function initialize(address _registrationModule) external onlyProtocolAdmin { - if (_registrationModule == address(0)) revert Errors.RoyaltyModule__ZeroLicensingModule(); - REGISTRATION_MODULE = _registrationModule; + /// @notice Sets the license registry + /// @param _licenseRegistry The address of the license registry + function setLicenseRegistry(address _licenseRegistry) external onlyProtocolAdmin { + if (_licenseRegistry == address(0)) revert Errors.RoyaltyModule__ZeroLicenseRegistry(); + + LICENSE_REGISTRY = _licenseRegistry; } /// @notice Whitelist a royalty policy @@ -62,7 +62,6 @@ contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard { emit RoyaltyTokenWhitelistUpdated(_token, _allowed); } - // TODO: Ensure this function is called on ipId registration: root and derivatives registrations // TODO: Ensure that the ipId that is passed in from license cannot be manipulated - given ipId addresses are deterministic /// @notice Sets the royalty policy for an ipId /// @param _ipId The ipId @@ -70,16 +69,21 @@ contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard { /// @param _parentIpIds The parent ipIds /// @param _data The data to initialize the policy function setRoyaltyPolicy( - address _ipId, + address _ipId, address _royaltyPolicy, address[] calldata _parentIpIds, bytes calldata _data - ) external onlyRegistrationModule nonReentrant { - if (royaltyPolicies[_ipId] != address(0)) revert Errors.RoyaltyModule__AlreadySetRoyaltyPolicy(); + ) external nonReentrant { + if (msg.sender != LICENSE_REGISTRY) revert Errors.RoyaltyModule__NotAllowedCaller(); + if (isRoyaltyPolicyImmutable[_ipId]) revert Errors.RoyaltyModule__AlreadySetRoyaltyPolicy(); if (!isWhitelistedRoyaltyPolicy[_royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); + if (_parentIpIds.length > 0) isRoyaltyPolicyImmutable[_ipId] = true; + + // the loop below is limited to 100 iterations for (uint32 i = 0; i < _parentIpIds.length; i++) { if (royaltyPolicies[_parentIpIds[i]] != _royaltyPolicy) revert Errors.RoyaltyModule__IncompatibleRoyaltyPolicy(); + isRoyaltyPolicyImmutable[_parentIpIds[i]] = true; } royaltyPolicies[_ipId] = _royaltyPolicy; diff --git a/contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol b/contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol index d4bcffb31..40f275500 100644 --- a/contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol +++ b/contracts/modules/royalty-module/policies/RoyaltyPolicyLS.sol @@ -19,7 +19,7 @@ import { Errors } from "contracts/lib/Errors.sol"; /// the percentage of royalty NFTs owned by each account. contract RoyaltyPolicyLS is IRoyaltyPolicyLS, ERC1155Holder { using SafeERC20 for IERC20; - + struct LSRoyaltyData { address splitClone; // address of the liquid split clone contract for a given ipId address claimer; // address of the claimer contract for a given ipId @@ -68,7 +68,6 @@ contract RoyaltyPolicyLS is IRoyaltyPolicyLS, ERC1155Holder { LIQUID_SPLIT_MAIN = _liquidSplitMain; } - // TODO: Ensure that parentsIds should be correctly passed in through the licensing contract, otherwise we must call parents() on licenseRegistry directly /// @notice Initializes the royalty policy /// @param _ipId The ipId /// @param _parentIpIds The parent ipIds diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index d618fe51b..845c496de 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -185,6 +185,7 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { // When a child passes in a royalty policy address royaltyPolicyAddress = address(0); uint32 royaltyDerivativeRevShare = 0; + uint32 derivativeRevShareSum = 0; for (uint256 i = 0; i < licenseIds.length; i++) { uint256 licenseId = licenseIds[i]; @@ -193,19 +194,23 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { } Licensing.License memory licenseData = _licenses[licenseId]; licensors[i] = licenseData.licensorIpId; - (royaltyPolicyAddress, royaltyDerivativeRevShare) = _linkIpToParent( + + (royaltyPolicyAddress, royaltyDerivativeRevShare, derivativeRevShareSum) = _linkIpToParent( i, licenseId, licenseData, licensors[i], childIpId, - royaltyPolicyAddress + royaltyPolicyAddress, + derivativeRevShareSum ); + values[i] = 1; } emit IpIdLinkedToParents(msg.sender, childIpId, licensors); // Licenses unanimously require royalty. + // TODO: currently, `royaltyDerivativeRevShare` is the derivative rev share value of the last license. if (royaltyPolicyAddress != address(0)) { ROYALTY_MODULE.setRoyaltyPolicy( childIpId, @@ -225,8 +230,9 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { Licensing.License memory licenseData, address licensor, address childIpId, - address royaltyPolicyAddress - ) private returns (address nextRoyaltyPolicyAddress, uint32 royaltyDerivativeRevShare) { + address royaltyPolicyAddress, + uint32 derivativeRevShareSum + ) private returns (address nextRoyaltyPolicyAddress, uint32 nextRoyaltyDerivativeRevShare, uint32 nextDerivativeRevShareSum) { // TODO: check licensor not part of a branch tagged by disputer if (licensor == childIpId) { revert Errors.LicenseRegistry__ParentIdEqualThanChild(); @@ -248,13 +254,28 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { // If link says royalty is required for license (licenseIds[i]) and no royalty policy is set, set it. // But if the index is NOT 0, this is previous licenses didn't set the royalty policy because they don't - // require royalty payment. So, revert in this case. - if (response.isRoyaltyRequired && royaltyPolicyAddress == address(0)) { - if (iteration != 0) { + // require royalty payment. So, revert in this case. Similarly, if the new royaltyPolicyAddress is different + // from the previous one (in iteration > 0), revert. We currently restrict all licenses (parents) to have + // the same royalty policy, so the child can inherit it. + if (response.isRoyaltyRequired) { + if (iteration > 0 && royaltyPolicyAddress != response.royaltyPolicy) { + // If iteration > 0 and + // - royaltyPolicyAddress == address(0), revert. Previous licenses didn't set RP. + // - royaltyPolicyAddress != response.royaltyPolicy, revert. Previous licenses set different RP. + // ==> this can be considered as royaltyPolicyAddress != response.royaltyPolicy revert Errors.LicenseRegistry__IncompatibleLicensorRoyaltyPolicy(); } - royaltyPolicyAddress = response.royaltyPolicy; - royaltyDerivativeRevShare = response.royaltyDerivativeRevShare; + + // TODO: Read max RNFT supply instead of hardcoding the expected max supply + // TODO: Do we need safe check? + // TODO: Test this in unit test. + if (derivativeRevShareSum + response.royaltyDerivativeRevShare > 1000) { + revert Errors.LicenseRegistry__DerivativeRevShareSumExceedsMaxRNFTSupply(); + } + + nextRoyaltyPolicyAddress = response.royaltyPolicy; + nextRoyaltyDerivativeRevShare = response.royaltyDerivativeRevShare; + nextDerivativeRevShareSum = derivativeRevShareSum + response.royaltyDerivativeRevShare; } // Add the policy of licenseIds[i] to the child. If the policy's already set from previous parents, @@ -262,7 +283,6 @@ contract LicenseRegistry is ERC1155, ILicenseRegistry, AccessControlled { _addPolicyIdToIp({ ipId: childIpId, policyId: licenseData.policyId, isInherited: true, skipIfDuplicate: true }); // Set parent _ipIdParents[childIpId].add(licensor); - return (royaltyPolicyAddress, royaltyDerivativeRevShare); } /// @notice True if the framework address is registered in LicenseRegistry diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index 617f9466b..e16b7c5cd 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -235,7 +235,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { function _configInitialize() private { accessController.initialize(address(ipAssetRegistry), address(moduleRegistry)); - royaltyModule.initialize(address(registrationModule)); + royaltyModule.setLicenseRegistry(address(licenseRegistry)); } function _configureModuleRegistry() private { diff --git a/test/foundry/integration/BaseIntegration.sol b/test/foundry/integration/BaseIntegration.sol index 5b53d14b4..f446e50ac 100644 --- a/test/foundry/integration/BaseIntegration.sol +++ b/test/foundry/integration/BaseIntegration.sol @@ -170,7 +170,7 @@ contract BaseIntegration is Test { function _configDeployedContracts() internal { vm.startPrank(u.admin); accessController.initialize(address(ipAccountRegistry), address(moduleRegistry)); - royaltyModule.initialize(address(registrationModule)); + royaltyModule.setLicenseRegistry(address(licenseRegistry)); moduleRegistry.registerModule(REGISTRATION_MODULE_KEY, address(registrationModule)); moduleRegistry.registerModule(IP_RESOLVER_MODULE_KEY, address(ipResolver)); diff --git a/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol b/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol index 069ab2bf2..c09f51acd 100644 --- a/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol +++ b/test/foundry/integration/big-bang/NftLicenseRoyalty.t.sol @@ -97,6 +97,8 @@ contract BigBang_Integration_NftLicenseRoyalty is BaseIntegration, Integration_S // Alice sets royalty policy on her root IP { + // TODO: setRoyaltyPolicy should be called through mintLicense or addPolicyToIp, not directly by user + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy( ipAcct[1], address(royaltyPolicyLS), @@ -293,5 +295,5 @@ contract BigBang_Integration_NftLicenseRoyalty is BaseIntegration, Integration_S _tokens: tokens }); } - } + } } diff --git a/test/foundry/integration/big-bang/SingleNftCollection.t.sol b/test/foundry/integration/big-bang/SingleNftCollection.t.sol index 454f6e70e..a7831460f 100644 --- a/test/foundry/integration/big-bang/SingleNftCollection.t.sol +++ b/test/foundry/integration/big-bang/SingleNftCollection.t.sol @@ -124,6 +124,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration // Alice sets royalty policy for her root IPAccounts // (so other IPAccounts can use her policies that inits royalty policy on linking) + // TODO: setRoyaltyPolicy should be called through mintLicense or addPolicyToIp, not directly by user + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy( ipAcct[1], address(royaltyPolicyLS), @@ -137,6 +139,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration // Bob sets royalty policy for his root IPAccounts // (so other IPAccounts can use his policies that inits royalty policy on linking) + // TODO: setRoyaltyPolicy should be called through mintLicense or addPolicyToIp, not directly by user + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy( ipAcct[300], address(royaltyPolicyLS), @@ -161,7 +165,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration MINT & USE LICENSES ////////////////////////////////////////////////////////////////*/ -/* // Carl mints 1 license for policy "com_deriv_all_true" on Alice's NFT 1 IPAccount + // Carl mints 1 license for policy "com_deriv_all_true" on Alice's NFT 1 IPAccount // Carl creates NFT 6 IPAccount // Carl activates the license on his NFT 6 IPAccount, linking as child to Alice's NFT 1 IPAccount { @@ -309,6 +313,6 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration, Integration metadata, u.carl // caller ); - } */ + } } } diff --git a/test/foundry/modules/ModuleBase.t.sol b/test/foundry/modules/ModuleBase.t.sol index 99e98ee7c..d67eb4f5a 100644 --- a/test/foundry/modules/ModuleBase.t.sol +++ b/test/foundry/modules/ModuleBase.t.sol @@ -81,7 +81,7 @@ abstract contract ModuleBaseTest is BaseTest { ); baseModule = IModule(_deployModule()); accessController.initialize(address(ipAssetRegistry), address(moduleRegistry)); - royaltyModule.initialize(address(registrationModule)); + royaltyModule.setLicenseRegistry(address(licenseRegistry)); } /// @notice Tests that the default resolver constructor runs successfully. diff --git a/test/foundry/modules/royalty/LSClaimer.t.sol b/test/foundry/modules/royalty/LSClaimer.t.sol index 564f92b3c..536fc0811 100644 --- a/test/foundry/modules/royalty/LSClaimer.t.sol +++ b/test/foundry/modules/royalty/LSClaimer.t.sol @@ -40,7 +40,7 @@ contract TestLSClaimer is TestHelper { function setUp() public override { TestHelper.setUp(); _setUMLPolicyFrameworkManager(); - nft = new MockERC721("mock"); + nft = erc721.ape; _addUMLPolicy( true, true, @@ -102,15 +102,14 @@ contract TestLSClaimer is TestHelper { ); vm.label(ipAddr, string(abi.encodePacked("IPAccount", Strings.toString(nftIds[0])))); vm.stopPrank(); - // User sets royalty policy for her IPAccount (so other IPAccounts can use her policies that - // inits royalty policy on linking) - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy( ipAddr, address(royaltyPolicyLS), new address[](0), // no parent abi.encode(10) ); + vm.stopPrank(); vm.startPrank(deployer); uint256[] memory licenseId = new uint256[](1); diff --git a/test/foundry/modules/royalty/RoyaltyModule.t.sol b/test/foundry/modules/royalty/RoyaltyModule.t.sol index 8c60a9352..3895cfa9d 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -30,19 +30,19 @@ contract TestRoyaltyModule is TestHelper { vm.stopPrank(); } - function test_RoyaltyModule_constructor_revert_ZeroRegistrationModule() public { + function test_RoyaltyModule_setLicenseRegistry_revert_ZeroLicenseRegistry() public { RoyaltyModule testRoyaltyModule = new RoyaltyModule(address(governance)); - vm.expectRevert(Errors.RoyaltyModule__ZeroLicensingModule.selector); + vm.expectRevert(Errors.RoyaltyModule__ZeroLicenseRegistry.selector); vm.prank(u.admin); - testRoyaltyModule.initialize(address(0)); + testRoyaltyModule.setLicenseRegistry(address(0)); } - function test_RoyaltyModule_constructor() public { + function test_RoyaltyModule_setLicenseRegistry() public { + vm.startPrank(u.admin); RoyaltyModule testRoyaltyModule = new RoyaltyModule(address(governance)); - vm.prank(u.admin); - testRoyaltyModule.initialize(address(registrationModule)); - assertEq(testRoyaltyModule.REGISTRATION_MODULE(), address(registrationModule)); - assertEq(testRoyaltyModule.governance(), address(governance)); + testRoyaltyModule.setLicenseRegistry(address(licenseRegistry)); + + assertEq(testRoyaltyModule.LICENSE_REGISTRY(), address(licenseRegistry)); } function test_RoyaltyModule_whitelistRoyaltyPolicy_revert_ZeroRoyaltyToken() public { @@ -83,16 +83,35 @@ contract TestRoyaltyModule is TestHelper { assertEq(royaltyModule.isWhitelistedRoyaltyToken(address(1)), true); } + function test_RoyaltyModule_setRoyaltyPolicy_revert_NotAllowedCaller() public { + address[] memory parentIpIds = new address[](0); + uint32 minRoyaltyIpAccount = 100; // 10% + bytes memory data = abi.encode(minRoyaltyIpAccount); + + vm.expectRevert(Errors.RoyaltyModule__NotAllowedCaller.selector); + royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds, data); + } + function test_RoyaltyModule_setRoyaltyPolicy_revert_AlreadySetRoyaltyPolicy() public { address[] memory parentIpIds1 = new address[](0); uint32 minRoyaltyIpAccount1 = 100; // 10% - bytes memory data = abi.encode(minRoyaltyIpAccount1); + bytes memory data1 = abi.encode(minRoyaltyIpAccount1); - vm.startPrank(address(registrationModule)); - royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data); + vm.startPrank(address(licenseRegistry)); + royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data1); + + address[] memory parentIpIds2 = new address[](1); + parentIpIds2[0] = ipAccount1; + uint32 minRoyaltyIpAccount2 = 100; // 10% + bytes memory data2 = abi.encode(minRoyaltyIpAccount2); + + royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds2, data2); vm.expectRevert(Errors.RoyaltyModule__AlreadySetRoyaltyPolicy.selector); - royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data); + royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data1); + + vm.expectRevert(Errors.RoyaltyModule__AlreadySetRoyaltyPolicy.selector); + royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds2, data2); } function test_RoyaltyModule_setRoyaltyPolicy_revert_NotWhitelistedRoyaltyPolicy() public { @@ -100,7 +119,7 @@ contract TestRoyaltyModule is TestHelper { uint32 minRoyaltyIpAccount1 = 100; // 10% bytes memory data = abi.encode(minRoyaltyIpAccount1); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); vm.expectRevert(Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy.selector); royaltyModule.setRoyaltyPolicy(ipAccount1, address(1), parentIpIds1, data); } @@ -110,7 +129,7 @@ contract TestRoyaltyModule is TestHelper { uint32 minRoyaltyIpAccount1 = 100; // 10% bytes memory data1 = abi.encode(minRoyaltyIpAccount1); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data1); vm.stopPrank(); @@ -123,7 +142,7 @@ contract TestRoyaltyModule is TestHelper { royaltyModule.whitelistRoyaltyPolicy(address(1), true); vm.stopPrank(); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); vm.expectRevert(Errors.RoyaltyModule__IncompatibleRoyaltyPolicy.selector); royaltyModule.setRoyaltyPolicy(ipAccount2, address(1), parentIpIds2, data2); } @@ -136,7 +155,7 @@ contract TestRoyaltyModule is TestHelper { vm.expectEmit(true, true, true, true, address(royaltyModule)); emit RoyaltyPolicySet(ipAccount1, address(royaltyPolicyLS), data); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data); assertEq(royaltyModule.royaltyPolicies(ipAccount1), address(royaltyPolicyLS)); @@ -153,7 +172,7 @@ contract TestRoyaltyModule is TestHelper { uint32 minRoyaltyIpAccount1 = 100; // 10% bytes memory data = abi.encode(minRoyaltyIpAccount1); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data); vm.stopPrank(); @@ -166,7 +185,7 @@ contract TestRoyaltyModule is TestHelper { uint32 minRoyaltyIpAccount1 = 100; // 10% bytes memory data = abi.encode(minRoyaltyIpAccount1); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data); vm.stopPrank(); @@ -184,10 +203,9 @@ contract TestRoyaltyModule is TestHelper { uint32 minRoyaltyIpAccount1 = 100; // 10% bytes memory data1 = abi.encode(minRoyaltyIpAccount1); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data1); - address[] memory parentIpIds2 = new address[](0); uint32 minRoyaltyIpAccount2 = 100; // 10% bytes memory data2 = abi.encode(minRoyaltyIpAccount2); diff --git a/test/foundry/modules/royalty/RoyaltyPolicyLS.t.sol b/test/foundry/modules/royalty/RoyaltyPolicyLS.t.sol index 24f990b20..e64c3a317 100644 --- a/test/foundry/modules/royalty/RoyaltyPolicyLS.t.sol +++ b/test/foundry/modules/royalty/RoyaltyPolicyLS.t.sol @@ -52,12 +52,21 @@ contract TestLSClaimer is TestHelper { assertEq(testRoyaltyPolicyLS.ROYALTY_MODULE(), address(royaltyModule)); assertEq(testRoyaltyPolicyLS.LICENSE_REGISTRY(), address(1)); assertEq(testRoyaltyPolicyLS.LIQUID_SPLIT_FACTORY(), LIQUID_SPLIT_FACTORY); - assertEq(testRoyaltyPolicyLS.LIQUID_SPLIT_MAIN(), LIQUID_SPLIT_MAIN); - + assertEq(testRoyaltyPolicyLS.LIQUID_SPLIT_MAIN(), LIQUID_SPLIT_MAIN); } - function test_RoyaltyPolicyLS_revert_InvalidMinRoyalty() public { - vm.startPrank(address(registrationModule)); + function test_RoyaltyPolicyLS_initPolicy_NotRoyalModule() public { + vm.startPrank(address(licenseRegistry)); + address[] memory parentIpIds = new address[](0); + uint32 minRoyaltyIpAccount1 = 100; + bytes memory data = abi.encode(minRoyaltyIpAccount1); + + vm.expectRevert(Errors.RoyaltyPolicyLS__NotRoyaltyModule.selector); + royaltyPolicyLS.initPolicy(ipAccount1, parentIpIds, data); + } + + function test_RoyaltyPolicyLS_initPolicy_revert_InvalidMinRoyalty() public { + vm.startPrank(address(licenseRegistry)); // set root parent royalty policy address[] memory parentIpIds1 = new address[](0); uint32 minRoyaltyIpAccount1 = 100; @@ -74,8 +83,8 @@ contract TestLSClaimer is TestHelper { royaltyModule.setRoyaltyPolicy(ipAccount2, address(royaltyPolicyLS), parentIpIds2, data2); } - function test_RoyaltyPolicyLS_revert_ZeroMinRoyalty() public { - vm.startPrank(address(registrationModule)); + function test_RoyaltyPolicyLS_initPolicy_revert_ZeroMinRoyalty() public { + vm.startPrank(address(licenseRegistry)); // set root parent royalty policy address[] memory parentIpIds1 = new address[](0); uint32 minRoyaltyIpAccount1 = 100; @@ -92,8 +101,8 @@ contract TestLSClaimer is TestHelper { royaltyModule.setRoyaltyPolicy(ipAccount2, address(royaltyPolicyLS), parentIpIds2, data2); } - function test_RoyaltyPolicyLS_revert_InvalidRoyaltyStack() public { - vm.startPrank(address(registrationModule)); + function test_RoyaltyPolicyLS_initPolicy_revert_InvalidRoyaltyStack() public { + vm.startPrank(address(licenseRegistry)); address[] memory parentIpIds = new address[](0); uint32 minRoyaltyIpAccount3 = 1010; // 100.1% bytes memory data = abi.encode(minRoyaltyIpAccount3); @@ -107,7 +116,7 @@ contract TestLSClaimer is TestHelper { uint32 minRoyaltyIpAccount1 = 0; bytes memory data = abi.encode(minRoyaltyIpAccount1); - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds, data); (address splitClone, address claimer, uint32 royaltyStack, uint32 minRoyalty) = royaltyPolicyLS.royaltyData(ipAccount1); @@ -119,7 +128,7 @@ contract TestLSClaimer is TestHelper { } function test_RoyaltyPolicyLS_initPolicy_derivativeIPA() public { - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); // set root parent royalty policy address[] memory parentIpIds1 = new address[](0); uint32 minRoyaltyIpAccount1 = 100; @@ -142,8 +151,44 @@ contract TestLSClaimer is TestHelper { assertEq(minRoyalty, minRoyaltyIpAccount2); } + function test_RoyaltyPolicyLS_onRoyaltyPayment_NotRoyaltyModule() public { + vm.expectRevert(Errors.RoyaltyPolicyLS__NotRoyaltyModule.selector); + + royaltyPolicyLS.onRoyaltyPayment(address(1), ipAccount1, address(USDC), 1000 * 10 ** 6); + } + + function test_RoyaltyPolicyLS_onRoyaltyPayment() public { + vm.startPrank(address(licenseRegistry)); + // set root parent royalty policy + address[] memory parentIpIds1 = new address[](0); + uint32 minRoyaltyIpAccount1 = 100; + bytes memory data1 = abi.encode(minRoyaltyIpAccount1); + royaltyModule.setRoyaltyPolicy(ipAccount1, address(royaltyPolicyLS), parentIpIds1, data1); + (address splitClone1, address claimer2,,) = royaltyPolicyLS.royaltyData(ipAccount1); + vm.stopPrank(); + + uint256 royaltyAmount = 1000 * 10 ** 6; + USDC.mint(address(1), royaltyAmount); + vm.startPrank(address(1)); + USDC.approve(address(royaltyPolicyLS), royaltyAmount); + vm.stopPrank(); + + vm.startPrank(address(royaltyModule)); + + uint256 splitClone2USDCBalBefore = USDC.balanceOf(splitClone1); + uint256 splitMainUSDCBalBefore = USDC.balanceOf(address(1)); + + royaltyPolicyLS.onRoyaltyPayment(address(1), ipAccount1, address(USDC), royaltyAmount); + + uint256 splitClone2USDCBalAfter = USDC.balanceOf(splitClone1); + uint256 splitMainUSDCBalAfter = USDC.balanceOf(address(1)); + + assertEq(splitClone2USDCBalAfter - splitClone2USDCBalBefore, royaltyAmount); + assertEq(splitMainUSDCBalBefore - splitMainUSDCBalAfter, royaltyAmount); + } + function test_RoyaltyPolicyLS_distributeFunds() public { - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); // set root parent royalty policy address[] memory parentIpIds1 = new address[](0); uint32 minRoyaltyIpAccount1 = 100; @@ -179,7 +224,7 @@ contract TestLSClaimer is TestHelper { } function test_RoyaltyPolicyLS_claimRoyalties() public{ - vm.startPrank(address(registrationModule)); + vm.startPrank(address(licenseRegistry)); // set root parent royalty policy address[] memory parentIpIds1 = new address[](0); uint32 minRoyaltyIpAccount1 = 100; diff --git a/test/foundry/registries/metadata/IPAssetRenderer.t.sol b/test/foundry/registries/metadata/IPAssetRenderer.t.sol index 10404ed1d..23d8991c2 100644 --- a/test/foundry/registries/metadata/IPAssetRenderer.t.sol +++ b/test/foundry/registries/metadata/IPAssetRenderer.t.sol @@ -127,7 +127,7 @@ contract IPAssetRendererTest is BaseTest { ); accessController.initialize(address(ipAccountRegistry), address(moduleRegistry)); - royaltyModule.initialize(address(registrationModule)); + royaltyModule.setLicenseRegistry(address(licenseRegistry)); vm.prank(alice); uint256 tokenId = erc721.mintId(alice, 99); diff --git a/test/utils/DeployHelper.sol b/test/utils/DeployHelper.sol index 6ac9c7c76..a109008e7 100644 --- a/test/utils/DeployHelper.sol +++ b/test/utils/DeployHelper.sol @@ -182,7 +182,7 @@ contract DeployHelper is Test { function _configDeployedContracts() internal { vm.startPrank(u.admin); accessController.initialize(address(ipAccountRegistry), address(moduleRegistry)); - royaltyModule.initialize(address(registrationModule)); + royaltyModule.setLicenseRegistry(address(licenseRegistry)); moduleRegistry.registerModule(REGISTRATION_MODULE_KEY, address(registrationModule)); moduleRegistry.registerModule(IP_RESOLVER_MODULE_KEY, address(ipResolver));