diff --git a/contracts/interfaces/modules/licensing/ILicensingModule.sol b/contracts/interfaces/modules/licensing/ILicensingModule.sol index 2d31f284..45ab5882 100644 --- a/contracts/interfaces/modules/licensing/ILicensingModule.sol +++ b/contracts/interfaces/modules/licensing/ILicensingModule.sol @@ -32,12 +32,16 @@ interface ILicensingModule is IModule { /// @param frameworkData The policy framework specific encoded data /// @param royaltyPolicy The address of the royalty policy /// @param royaltyData The royalty policy specific encoded data + /// @param mintingFee The fee to be paid when minting a license + /// @param mintingFeeToken The token to be used to pay the minting fee event PolicyRegistered( uint256 indexed policyId, address indexed policyFrameworkManager, bytes frameworkData, address royaltyPolicy, - bytes royaltyData + bytes royaltyData, + uint256 mintingFee, + address mintingFeeToken ); /// @notice Emitted when a policy is added to an IP @@ -73,18 +77,12 @@ interface ILicensingModule is IModule { /// @param manager the address of the manager. Will be ERC165 checked for IPolicyFrameworkManager function registerPolicyFrameworkManager(address manager) external; - /// @notice Registers a policy into the contract. MUST be called by a registered framework or it will revert. - /// The policy data and its integrity must be verified by the policy framework manager. - /// @param isLicenseTransferable True if the license is transferable - /// @param royaltyPolicy The address of the royalty policy - /// @param royaltyData The royalty policy specific encoded data - /// @param frameworkData The policy framework specific encoded data - function registerPolicy( - bool isLicenseTransferable, - address royaltyPolicy, - bytes memory royaltyData, - bytes memory frameworkData - ) external returns (uint256 policyId); + /// @notice Registers a policy into the contract. MUST be called by a registered + /// framework or it will revert. The policy data and its integrity must be + /// verified by the policy framework manager. + /// @param pol The Licensing policy data. MUST have same policy framework as the caller address + /// @return policyId The id of the newly registered policy + function registerPolicy(Licensing.Policy memory pol) external returns (uint256 policyId); /// @notice Adds a policy to the set of policies of an IP /// @param ipId The id of the IP diff --git a/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol b/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol index 74a6ba99..a4abdc73 100644 --- a/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol +++ b/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol @@ -38,10 +38,14 @@ struct UMLPolicy { /// @param transferable Whether or not the license is transferable /// @param royaltyPolicy Address of a royalty policy contract (e.g. RoyaltyPolicyLS) that will handle royalty payments +/// @param mintingFee Fee to be paid when minting a license +/// @param mintingFeeToken Token to be used to pay the minting fee /// @param umlPolicy UMLPolicy compliant licensing term values struct RegisterUMLPolicyParams { bool transferable; address royaltyPolicy; + uint256 mintingFee; + address mintingFeeToken; UMLPolicy policy; } diff --git a/contracts/interfaces/modules/royalty/IRoyaltyModule.sol b/contracts/interfaces/modules/royalty/IRoyaltyModule.sol index 48cf5b24..c85f5f91 100644 --- a/contracts/interfaces/modules/royalty/IRoyaltyModule.sol +++ b/contracts/interfaces/modules/royalty/IRoyaltyModule.sol @@ -29,6 +29,13 @@ interface IRoyaltyModule is IModule { /// @param amount The amount that is paid event RoyaltyPaid(address receiverIpId, address payerIpId, address sender, address token, uint256 amount); + /// @notice Event emitted when the license minting fee is paid + /// @param receiverIpId The ipId that receives the royalties + /// @param payerAddress The address that pays the royalties + /// @param token The token that is used to pay the royalties + /// @param amount The amount paid + event LicenseMintingFeePaid(address receiverIpId, address payerAddress, address token, uint256 amount); + /// @notice Returns the licensing module address function LICENSING_MODULE() external view returns (address); @@ -93,4 +100,18 @@ interface IRoyaltyModule is IModule { /// @param token The token to use to pay the royalties /// @param amount The amount to pay function payRoyaltyOnBehalf(address receiverIpId, address payerIpId, address token, uint256 amount) external; + + /// @notice Allows to pay the minting fee for a license + /// @param receiverIpId The ipId that receives the royalties + /// @param payerAddress The address that pays the royalties + /// @param licenseRoyaltyPolicy The royalty policy of the license being minted + /// @param token The token to use to pay the royalties + /// @param amount The amount to pay + function payLicenseMintingFee( + address receiverIpId, + address payerAddress, + address licenseRoyaltyPolicy, + address token, + uint256 amount + ) external; } diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index abf48713..7e1bf12c 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -152,6 +152,9 @@ library Errors { error LicensingModule__IncompatibleLicensorRoyaltyDerivativeRevShare(); error LicensingModule__DerivativeRevShareSumExceedsMaxRNFTSupply(); error LicensingModule__MismatchBetweenRoyaltyPolicy(); + error LicensingModule__RegisterPolicyFrameworkMismatch(); + error LicensingModule__RoyaltyPolicyNotWhitelisted(); + error LicensingModule__MintingFeeTokenNotWhitelisted(); /// @notice emitted when trying to interact with an IP that has been disputed in the DisputeModule error LicensingModule__DisputedIpId(); /// @notice emitted when linking a license from a licensor that has been disputed in the DisputeModule diff --git a/contracts/lib/Licensing.sol b/contracts/lib/Licensing.sol index fc4a0fb4..7af31c9c 100644 --- a/contracts/lib/Licensing.sol +++ b/contracts/lib/Licensing.sol @@ -11,13 +11,18 @@ library Licensing { /// @param frameworkData Data to be used by the policy framework to verify minting and linking /// @param royaltyPolicy address of the royalty policy to be used by the policy framework, if any /// @param royaltyData Data to be used by the royalty policy (for example, encoding of the royalty percentage) + /// @param mintingFee Fee to be paid when minting a license + /// @param mintingFeeToken Token to be used to pay the minting fee struct Policy { bool isLicenseTransferable; address policyFramework; bytes frameworkData; address royaltyPolicy; bytes royaltyData; + uint256 mintingFee; + address mintingFeeToken; } + /// @notice Data that define a License Agreement NFT /// @param policyId Id of the policy this license is based on, which will be set in the derivative IP when the /// license is burnt for linking diff --git a/contracts/lib/UMLFrameworkErrors.sol b/contracts/lib/UMLFrameworkErrors.sol index c0d0a5ea..2e327e0c 100644 --- a/contracts/lib/UMLFrameworkErrors.sol +++ b/contracts/lib/UMLFrameworkErrors.sol @@ -22,4 +22,6 @@ library UMLFrameworkErrors { error UMLPolicyFrameworkManager__ReciprocalValueMismatch(); error UMLPolicyFrameworkManager__CommercialValueMismatch(); error UMLPolicyFrameworkManager__StringArrayMismatch(); + error UMLPolicyFrameworkManager__CommecialDisabled_CantAddMintingFee(); + error UMLPolicyFrameworkManager__CommecialDisabled_CantAddMintingFeeToken(); } diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index cbc1b7ee..5fae07d1 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -113,35 +113,36 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen /// @notice Registers a policy into the contract. MUST be called by a registered framework or it will revert. /// The policy data and its integrity must be verified by the policy framework manager. - /// @param isLicenseTransferable True if the license is transferable - /// @param royaltyPolicy The address of the royalty policy - /// @param royaltyData The royalty policy specific encoded data - /// @param frameworkData The policy framework specific encoded data - function registerPolicy( - bool isLicenseTransferable, - address royaltyPolicy, - bytes memory royaltyData, - bytes memory frameworkData - ) external returns (uint256 policyId) { + /// @param pol The Licensing policy data. MUST have same policy framework as the caller address + /// @return policyId The id of the newly registered policy + function registerPolicy(Licensing.Policy memory pol) external returns (uint256 policyId) { _verifyRegisteredFramework(address(msg.sender)); - Licensing.Policy memory pol = Licensing.Policy({ - isLicenseTransferable: isLicenseTransferable, - policyFramework: msg.sender, - frameworkData: frameworkData, - royaltyPolicy: royaltyPolicy, - royaltyData: royaltyData - }); - + if (pol.policyFramework != address(msg.sender)) { + revert Errors.LicensingModule__RegisterPolicyFrameworkMismatch(); + } + if (pol.royaltyPolicy != address(0) && !ROYALTY_MODULE.isWhitelistedRoyaltyPolicy(pol.royaltyPolicy)) { + revert Errors.LicensingModule__RoyaltyPolicyNotWhitelisted(); + } + if (pol.mintingFee > 0 && !ROYALTY_MODULE.isWhitelistedRoyaltyToken(pol.mintingFeeToken)) { + revert Errors.LicensingModule__MintingFeeTokenNotWhitelisted(); + } (uint256 polId, bool newPol) = DataUniqueness.addIdOrGetExisting( abi.encode(pol), _hashedPolicies, _totalPolicies ); - if (newPol) { _totalPolicies = polId; _policies[polId] = pol; - emit PolicyRegistered(polId, msg.sender, frameworkData, royaltyPolicy, royaltyData); + emit PolicyRegistered( + polId, + pol.policyFramework, + pol.frameworkData, + pol.royaltyPolicy, + pol.royaltyData, + pol.mintingFee, + pol.mintingFeeToken + ); } return polId; } @@ -204,7 +205,10 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen // If the policy has a royalty policy, we need to call the royalty module to process the minting // Otherwise, it's non commercial and we can skip the call. if (pol.royaltyPolicy != address(0)) { - ROYALTY_MODULE.onLicenseMinting(licensorIpId, pol.royaltyPolicy, pol.royaltyData, royaltyContext); + // If there's a minting fee, sender must pay it + if (pol.mintingFee > 0) { + ROYALTY_MODULE.payLicenseMintingFee(licensorIpId, msg.sender, pol.royaltyPolicy, pol.mintingFeeToken, pol.mintingFee); + } } // If a policy is set, then is only up to the policy params. diff --git a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol index 84b33144..70091800 100644 --- a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol +++ b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol @@ -10,6 +10,7 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.s // contracts import { IHookModule } from "../../interfaces/modules/base/IHookModule.sol"; import { ILicensingModule } from "../../interfaces/modules/licensing/ILicensingModule.sol"; +import { IRoyaltyModule } from "../../interfaces/modules/royalty/IRoyaltyModule.sol"; import { Licensing } from "../../lib/Licensing.sol"; import { Errors } from "../../lib/Errors.sol"; import { UMLFrameworkErrors } from "../../lib/UMLFrameworkErrors.sol"; @@ -54,17 +55,23 @@ contract UMLPolicyFrameworkManager is /// @param params parameters needed to register a UMLPolicy /// @return policyId The ID of the newly registered policy function registerPolicy(RegisterUMLPolicyParams calldata params) external nonReentrant returns (uint256 policyId) { - _verifyComercialUse(params.policy, params.royaltyPolicy); + /// Minting fee amount & address checked in LicensingModule, no need to check here. + /// We don't limit charging for minting to commercial use, you could sell a NC license in theory. + _verifyComercialUse(params.policy, params.royaltyPolicy, params.mintingFee, params.mintingFeeToken); _verifyDerivatives(params.policy); /// TODO: DO NOT deploy on production networks without hashing string[] values instead of storing them + + Licensing.Policy memory pol = Licensing.Policy({ + isLicenseTransferable: params.transferable, + policyFramework: address(this), + frameworkData: abi.encode(params.policy), + royaltyPolicy: params.royaltyPolicy, + royaltyData: abi.encode(params.policy.commercialRevShare), + mintingFee: params.mintingFee, + mintingFeeToken: params.mintingFeeToken + }); // No need to emit here, as the LicensingModule will emit the event - return - LICENSING_MODULE.registerPolicy( - params.transferable, - params.royaltyPolicy, - abi.encode(params.policy.commercialRevShare), // TODO: this should be encoded by the royalty policy - abi.encode(params.policy) - ); + return LICENSING_MODULE.registerPolicy(pol); } /// @notice Verify policy parameters for linking a child IP to a parent IP (licensor) by burning a license NFT. @@ -347,7 +354,7 @@ contract UMLPolicyFrameworkManager is /// @param policy The policy to verify /// @param royaltyPolicy The address of the royalty policy // solhint-disable-next-line code-complexity - function _verifyComercialUse(UMLPolicy calldata policy, address royaltyPolicy) internal view { + function _verifyComercialUse(UMLPolicy calldata policy, address royaltyPolicy, uint256 mintingFee, address mintingFeeToken) internal view { if (!policy.commercialUse) { if (policy.commercialAttribution) { revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddAttribution(); @@ -361,8 +368,13 @@ contract UMLPolicyFrameworkManager is if (royaltyPolicy != address(0)) { revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommercialDisabled_CantAddRoyaltyPolicy(); } + if (mintingFee > 0) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddMintingFee(); + } + if (mintingFeeToken != address(0)) { + revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialDisabled_CantAddMintingFeeToken(); + } } else { - // TODO: check for supportInterface instead if (royaltyPolicy == address(0)) { revert UMLFrameworkErrors.UMLPolicyFrameworkManager__CommecialEnabled_RoyaltyPolicyRequired(); } diff --git a/contracts/modules/royalty-module/RoyaltyModule.sol b/contracts/modules/royalty-module/RoyaltyModule.sol index 398d8b4e..1e1501bc 100644 --- a/contracts/modules/royalty-module/RoyaltyModule.sol +++ b/contracts/modules/royalty-module/RoyaltyModule.sol @@ -155,8 +155,33 @@ contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard, BaseModul emit RoyaltyPaid(receiverIpId, payerIpId, msg.sender, token, amount); } + /// @notice Allows to pay the minting fee for a license + /// @param receiverIpId The ipId that receives the royalties + /// @param payerAddress The address that pays the royalties + /// @param licenseRoyaltyPolicy The royalty policy of the license being minted + /// @param token The token to use to pay the royalties + /// @param amount The amount to pay + function payLicenseMintingFee( + address receiverIpId, + address payerAddress, + address licenseRoyaltyPolicy, + address token, + uint256 amount + ) external onlyLicensingModule { + if (!isWhitelistedRoyaltyToken[token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken(); + + if (licenseRoyaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet(); + if (!isWhitelistedRoyaltyPolicy[licenseRoyaltyPolicy]) + revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); + + IRoyaltyPolicy(licenseRoyaltyPolicy).onRoyaltyPayment(payerAddress, receiverIpId, token, amount); + + emit LicenseMintingFeePaid(receiverIpId, payerAddress, token, amount); + } + /// @notice IERC165 interface support. function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { return interfaceId == type(IRoyaltyModule).interfaceId || super.supportsInterface(interfaceId); } + } diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index dfd9595a..c618343e 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -401,6 +401,8 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { RegisterUMLPolicyParams({ transferable: true, royaltyPolicy: address(royaltyPolicyLAP), + mintingFee: 0, + mintingFeeToken: address(0), policy: UMLPolicy({ attribution: true, commercialUse: true, @@ -423,6 +425,8 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { RegisterUMLPolicyParams({ transferable: false, royaltyPolicy: address(0), // no royalty, non-commercial + mintingFee: 0, + mintingFeeToken: address(0), policy: UMLPolicy({ attribution: true, commercialUse: false, diff --git a/test/foundry/integration/BaseIntegration.t.sol b/test/foundry/integration/BaseIntegration.t.sol index 6bdcab96..da98a461 100644 --- a/test/foundry/integration/BaseIntegration.t.sol +++ b/test/foundry/integration/BaseIntegration.t.sol @@ -42,9 +42,6 @@ contract BaseIntegration is BaseTest { dealMockAssets(); approveRegistration(); - - // Also deploy mock royalty policy LS - // mockRoyaltyPolicyLS = new MockRoyaltyPolicyLS(address(royaltyModule)); } function approveRegistration() internal { diff --git a/test/foundry/integration/big-bang/SingleNftCollection.t.sol b/test/foundry/integration/big-bang/SingleNftCollection.t.sol index c1fe7d14..a7c54a65 100644 --- a/test/foundry/integration/big-bang/SingleNftCollection.t.sol +++ b/test/foundry/integration/big-bang/SingleNftCollection.t.sol @@ -91,7 +91,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { REGISTER IP ACCOUNTS ///////////////////////////////////////////////////////////////*/ - // ipAcct[tokenId] => ipAccount address + /* // ipAcct[tokenId] => ipAccount address // owner is the vm.pranker vm.startPrank(u.alice); @@ -109,12 +109,12 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { vm.startPrank(u.carl); mockNFT.mintId(u.carl, 5); ipAcct[5] = registerIpAccount(mockNFT, 5, u.carl); - + */ /*////////////////////////////////////////////////////////////// ADD POLICIES TO IP ACCOUNTS ///////////////////////////////////////////////////////////////*/ - vm.startPrank(u.alice); + /* vm.startPrank(u.alice); licensingModule.addPolicyToIp(ipAcct[1], policyIds["uml_com_deriv_cheap_flexible"]); licensingModule.addPolicyToIp(ipAcct[100], policyIds["uml_noncom_deriv_reciprocal_derivative"]); @@ -133,13 +133,13 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { ipAcct[3], policyIds["uml_noncom_deriv_reciprocal_derivative"] ) - ); + ); */ /*/////////////////////////////////////////////////////////////// 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 { @@ -340,7 +340,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration { metadata, u.carl, // caller abi.encode(params) - ); - } + ); + }*/ } -} +} \ No newline at end of file diff --git a/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol b/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol index b3298909..10b73bb6 100644 --- a/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol +++ b/test/foundry/mocks/licensing/MockPolicyFrameworkManager.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.23; // contracts import { BasePolicyFrameworkManager } from "contracts/modules/licensing/BasePolicyFrameworkManager.sol"; +import { Licensing } from "contracts/lib/Licensing.sol"; struct MockPolicyFrameworkConfig { address licensingModule; @@ -32,7 +33,16 @@ contract MockPolicyFrameworkManager is BasePolicyFrameworkManager { function registerPolicy(MockPolicy calldata mockPolicy) external returns (uint256 policyId) { emit MockPolicyAdded(policyId, mockPolicy); - return LICENSING_MODULE.registerPolicy(true, royaltyPolicy, "", abi.encode(mockPolicy)); + Licensing.Policy memory pol = Licensing.Policy({ + isLicenseTransferable: true, + policyFramework: address(this), + frameworkData: "", + royaltyPolicy: royaltyPolicy, + royaltyData: abi.encode(mockPolicy), + mintingFee: 0, + mintingFeeToken: address(0) + }); + return LICENSING_MODULE.registerPolicy(pol); } function verifyMint(address, bool, address, address, uint256, bytes memory data) external pure returns (bool) { diff --git a/test/foundry/mocks/module/MockLicensingModule.sol b/test/foundry/mocks/module/MockLicensingModule.sol index 4113f7cf..3f53576f 100644 --- a/test/foundry/mocks/module/MockLicensingModule.sol +++ b/test/foundry/mocks/module/MockLicensingModule.sol @@ -43,20 +43,7 @@ contract MockLicensingModule is BaseModule, ILicensingModule { _registeredFrameworkManagers[manager] = true; } - function registerPolicy( - bool isLicenseTransferable, - address royaltyPolicy, - bytes memory royaltyData, - bytes memory frameworkData - ) external returns (uint256 policyId) { - Licensing.Policy memory pol = Licensing.Policy({ - isLicenseTransferable: isLicenseTransferable, - policyFramework: msg.sender, - frameworkData: frameworkData, - royaltyPolicy: royaltyPolicy, - royaltyData: royaltyData - }); - + function registerPolicy(Licensing.Policy memory pol) external returns (uint256 policyId) { (uint256 polId, bool newPol) = DataUniqueness.addIdOrGetExisting( abi.encode(pol), _hashedPolicies, diff --git a/test/foundry/mocks/module/MockRoyaltyModule.sol b/test/foundry/mocks/module/MockRoyaltyModule.sol index 6d097592..034c724d 100644 --- a/test/foundry/mocks/module/MockRoyaltyModule.sol +++ b/test/foundry/mocks/module/MockRoyaltyModule.sol @@ -56,6 +56,18 @@ contract MockRoyaltyModule is BaseModule, IRoyaltyModule { address payerRoyaltyPolicy = royaltyPolicies[_payerIpId]; // IRoyaltyPolicy(payerRoyaltyPolicy).onRoyaltyPayment(msg.sender, _receiverIpId, _token, _amount); } + + function payLicenseMintingFee(address receiverIpId, address payerAddress, address token, uint256 amount) external { + + } + + function payLicenseMintingFee( + address receiverIpId, + address payerAddress, + address licenseRoyaltyPolicy, + address token, + uint256 amount + ) external{} function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { return interfaceId == type(IRoyaltyModule).interfaceId || super.supportsInterface(interfaceId); diff --git a/test/foundry/modules/licensing/LicensingModule.t.sol b/test/foundry/modules/licensing/LicensingModule.t.sol index b5cb3adf..4d713b38 100644 --- a/test/foundry/modules/licensing/LicensingModule.t.sol +++ b/test/foundry/modules/licensing/LicensingModule.t.sol @@ -95,17 +95,16 @@ contract LicensingModuleTest is BaseTest { return abi.encode(MockPolicy({ returnVerifyLink: true, returnVerifyMint: true })); } - function _createPolicyFrameworkData() internal view returns (bytes memory) { - return - abi.encode( - Licensing.Policy({ - isLicenseTransferable: true, - policyFramework: address(mockPFM), - frameworkData: "", - royaltyPolicy: address(mockRoyaltyPolicyLAP), - royaltyData: "" - }) - ); + function _createPolicyFrameworkData() internal view returns (Licensing.Policy memory) { + return Licensing.Policy({ + isLicenseTransferable: true, + policyFramework: address(mockPFM), + frameworkData: _createMockPolicy(), + royaltyPolicy: address(mockRoyaltyPolicyLAP), + royaltyData: "", + mintingFee: 0, + mintingFeeToken: address(0) + }); } function test_LicensingModule_registerLicenseFramework() public { @@ -116,24 +115,21 @@ contract LicensingModuleTest is BaseTest { function test_LicensingModule_registerPolicy() public withPolicyFrameworkManager { licensingModule.registerPolicyFrameworkManager(address(mockPFM)); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); assertEq(policyId, 1, "policyId not 1"); } function test_LicensingModule_registerPolicy_reusesIdForAlreadyAddedPolicy() public withPolicyFrameworkManager { vm.startPrank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); - assertEq( - policyId, - licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()) - ); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); + assertEq(policyId, licensingModule.registerPolicy(_createPolicyFrameworkData())); vm.stopPrank(); } function test_LicensingModule_getPolicyId() public withPolicyFrameworkManager { bytes memory policy = _createMockPolicy(); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); Licensing.Policy memory storedPolicy = licensingModule.policy(policyId); assertEq(licensingModule.getPolicyId(storedPolicy), policyId, "policyId not found"); } @@ -141,19 +137,17 @@ contract LicensingModuleTest is BaseTest { function test_LicensingModule_registerPolicy_revert_frameworkNotFound() public { vm.expectRevert(Errors.LicensingModule__FrameworkNotFound.selector); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); } function test_LicensingModule_addPolicyToIpId() public withPolicyFrameworkManager { - bytes memory policyData = _createPolicyFrameworkData(); - + Licensing.Policy memory policy = _createPolicyFrameworkData(); + vm.prank(u.admin); + royaltyModule.whitelistRoyaltyToken(address(0x123), true); + policy.mintingFee = 123; + policy.mintingFeeToken = address(0x123); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy( - true, - address(mockRoyaltyPolicyLAP), - "", - _createPolicyFrameworkData() - ); + uint256 policyId = licensingModule.registerPolicy(policy); vm.prank(ipOwner); uint256 indexOnIpId = licensingModule.addPolicyToIp(ipId1, policyId); @@ -165,14 +159,16 @@ contract LicensingModuleTest is BaseTest { assertEq(storedPolicy.policyFramework, address(mockPFM), "policyFramework not stored properly"); assertEq(storedPolicy.royaltyPolicy, address(mockRoyaltyPolicyLAP), "royaltyPolicy not stored properly"); assertEq(storedPolicy.isLicenseTransferable, true, "isLicenseTransferable not stored properly"); - assertEq(storedPolicy.frameworkData, policyData, "frameworkData not stored properly"); + assertEq(storedPolicy.frameworkData, policy.frameworkData, "frameworkData not stored properly"); assertEq(storedPolicy.royaltyData, "", "royaltyData not stored properly"); - // assertEq(keccak256(abi.encode(storedPolicy)), keccak256(abi.encode(policy)), "policy not stored properly"); + assertEq(storedPolicy.mintingFee, 123, "mintingFee not stored properly"); + assertEq(storedPolicy.mintingFeeToken, address(0x123), "mintingFeeToken not stored properly"); + assertEq(keccak256(abi.encode(storedPolicy)), keccak256(abi.encode(policy)), "policy not stored properly"); } function test_LicensingModule_addSamePolicyReusesPolicyId() public withPolicyFrameworkManager { vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); vm.prank(ipOwner); uint256 indexOnIpId = licensingModule.addPolicyToIp(ipId1, policyId); @@ -191,7 +187,7 @@ contract LicensingModuleTest is BaseTest { // First time adding a policy vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); vm.prank(ipOwner); uint256 indexOnIpId = licensingModule.addPolicyToIp(ipId1, policyId); assertEq(policyId, 1, "policyId not 1"); @@ -203,9 +199,17 @@ contract LicensingModuleTest is BaseTest { assertFalse(isInherited); // Adding different policy to same ipId - bytes memory otherPolicy = abi.encode(MockPolicy({ returnVerifyLink: true, returnVerifyMint: false })); + Licensing.Policy memory otherPolicy = Licensing.Policy({ + isLicenseTransferable: true, + policyFramework: address(mockPFM), + frameworkData: abi.encode("something"), + royaltyPolicy: address(mockRoyaltyPolicyLAP), + royaltyData: "", + mintingFee: 0, + mintingFeeToken: address(0) + }); vm.prank(address(mockPFM)); - uint256 policyId2 = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", otherPolicy); + uint256 policyId2 = licensingModule.registerPolicy(otherPolicy); vm.prank(ipOwner); uint256 indexOnIpId2 = licensingModule.addPolicyToIp(ipId1, policyId2); assertEq(policyId2, 2, "policyId not 2"); @@ -219,7 +223,7 @@ contract LicensingModuleTest is BaseTest { function test_LicensingModule_mintLicense() public withPolicyFrameworkManager returns (uint256 licenseId) { vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); vm.prank(ipOwner); uint256 indexOnIpId = licensingModule.addPolicyToIp(ipId1, policyId); assertEq(policyId, 1); @@ -243,7 +247,7 @@ contract LicensingModuleTest is BaseTest { licensingModule.registerPolicyFrameworkManager(address(mockPFM)); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); vm.prank(ipOwner); licensingModule.addPolicyToIp(ipId1, policyId); @@ -258,7 +262,7 @@ contract LicensingModuleTest is BaseTest { IIPAccount ipAccount1 = IIPAccount(payable(ipId1)); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); // Anyone (this contract, in this case) calls vm.expectRevert(Errors.LicensingModule__CallerNotLicensorAndPolicyNotSet.selector); @@ -293,8 +297,10 @@ contract LicensingModuleTest is BaseTest { IIPAccount ipAccount1 = IIPAccount(payable(ipId1)); vm.startPrank(address(mockPFM)); - uint256 policyId1 = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", policy); - uint256 policyId2 = licensingModule.registerPolicy(false, address(mockRoyaltyPolicyLAP), "", policy); + uint256 policyId1 = licensingModule.registerPolicy(_createPolicyFrameworkData()); + Licensing.Policy memory pol2 = _createPolicyFrameworkData(); + pol2.isLicenseTransferable = false; + uint256 policyId2 = licensingModule.registerPolicy(pol2); vm.stopPrank(); // Licensor (IP Account owner) calls directly @@ -363,7 +369,7 @@ contract LicensingModuleTest is BaseTest { function test_LicensingModule_singleTransfer_verifyOk() public { licensingModule.registerPolicyFrameworkManager(address(mockPFM)); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy(true, address(mockRoyaltyPolicyLAP), "", _createMockPolicy()); + uint256 policyId = licensingModule.registerPolicy(_createPolicyFrameworkData()); vm.prank(ipOwner); licensingModule.addPolicyToIp(ipId1, policyId); @@ -382,12 +388,10 @@ contract LicensingModuleTest is BaseTest { function test_LicensingModule_singleTransfer_revert_verifyFalse() public { licensingModule.registerPolicyFrameworkManager(address(mockPFM)); vm.prank(address(mockPFM)); - uint256 policyId = licensingModule.registerPolicy( - false, - address(mockRoyaltyPolicyLAP), - "", - _createMockPolicy() - ); + + Licensing.Policy memory pol = _createPolicyFrameworkData(); + pol.isLicenseTransferable = false; + uint256 policyId = licensingModule.registerPolicy(pol); vm.prank(ipOwner); licensingModule.addPolicyToIp(ipId1, policyId); @@ -431,8 +435,9 @@ contract LicensingModuleTest is BaseTest { uint256 policyId = umlManager.registerPolicy( RegisterUMLPolicyParams({ transferable: true, - // TODO: use mock or real based on condition royaltyPolicy: address(mockRoyaltyPolicyLAP), + mintingFee: 0, + mintingFeeToken: address(0), policy: policyData }) ); @@ -447,4 +452,4 @@ contract LicensingModuleTest is BaseTest { function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { return this.onERC721Received.selector; } -} +} \ No newline at end of file diff --git a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol index 4a5f4b71..8f04a9ff 100644 --- a/test/foundry/modules/licensing/UMLPolicyFramework.t.sol +++ b/test/foundry/modules/licensing/UMLPolicyFramework.t.sol @@ -159,8 +159,9 @@ contract UMLPolicyFrameworkTest is BaseTest { RegisterUMLPolicyParams memory input = RegisterUMLPolicyParams({ transferable: true, - // TODO: use mock or real based on condition royaltyPolicy: address(0xbeef), + mintingFee: 0, + mintingFeeToken: address(0), policy: policyData }); @@ -333,4 +334,4 @@ contract UMLPolicyFrameworkTest is BaseTest { function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { return this.onERC721Received.selector; } -} +} \ No newline at end of file diff --git a/test/foundry/modules/royalty/RoyaltyModule.t.sol b/test/foundry/modules/royalty/RoyaltyModule.t.sol index bc917752..388f29d0 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -14,6 +14,7 @@ contract TestRoyaltyModule is BaseTest { event RoyaltyTokenWhitelistUpdated(address token, bool allowed); event RoyaltyPolicySet(address ipId, address royaltyPolicy, bytes data); event RoyaltyPaid(address receiverIpId, address payerIpId, address sender, address token, uint256 amount); + event LicenseMintingFeePaid(address receiverIpId, address payerAddress, address token, uint256 amount); address internal ipAccount1 = address(0x111000aaa); address internal ipAccount2 = address(0x111000bbb); @@ -488,4 +489,34 @@ contract TestRoyaltyModule is BaseTest { assertEq(payerIpIdUSDCBalBefore - payerIpIdUSDCBalAfter, royaltyAmount); assertEq(splitCloneUSDCBalAfter - splitCloneUSDCBalBefore, royaltyAmount); } + + function test_RoyaltyModule_payLicenseMintingFee() public { + uint256 royaltyAmount = 100 * 10 ** 6; + address receiverIpId = address(7); + address payerAddress = address(3); + address licenseRoyaltyPolicy = address(royaltyPolicyLAP); + address token = address(USDC); + + (, address splitClone, , , ) = royaltyPolicyLAP.royaltyData(receiverIpId); + + vm.startPrank(payerAddress); + USDC.mint(payerAddress, royaltyAmount); + USDC.approve(address(royaltyPolicyLAP), royaltyAmount); + vm.stopPrank; + + uint256 payerAddressUSDCBalBefore = USDC.balanceOf(payerAddress); + uint256 splitCloneUSDCBalBefore = USDC.balanceOf(splitClone); + + vm.expectEmit(true, true, true, true, address(royaltyModule)); + emit LicenseMintingFeePaid(receiverIpId, payerAddress, address(USDC), royaltyAmount); + + vm.startPrank(address(licensingModule)); + royaltyModule.payLicenseMintingFee(receiverIpId, payerAddress, licenseRoyaltyPolicy, token, royaltyAmount); + + uint256 payerAddressUSDCBalAfter = USDC.balanceOf(payerAddress); + uint256 splitCloneUSDCBalAfter = USDC.balanceOf(splitClone); + + assertEq(payerAddressUSDCBalBefore - payerAddressUSDCBalAfter, royaltyAmount); + assertEq(splitCloneUSDCBalAfter - splitCloneUSDCBalBefore, royaltyAmount); + } } diff --git a/test/foundry/utils/LicensingHelper.t.sol b/test/foundry/utils/LicensingHelper.t.sol index 541c2922..4307c7ec 100644 --- a/test/foundry/utils/LicensingHelper.t.sol +++ b/test/foundry/utils/LicensingHelper.t.sol @@ -207,6 +207,8 @@ contract LicensingHelper { policies[pName] = RegisterUMLPolicyParams({ transferable: transferable, royaltyPolicy: royaltyPolicy, + mintingFee: 0, + mintingFeeToken: address(0), policy: policy }); policyIds[pName] = UMLPolicyFrameworkManager(pfm["uml"]).registerPolicy(policies[pName]); @@ -224,6 +226,8 @@ contract LicensingHelper { transferable: true, // TODO: use mock or real based on condition royaltyPolicy: commercial ? address(ROYALTY_POLICY_LAP) : address(0), + mintingFee: 0, + mintingFeeToken: address(0), policy: UMLPolicy({ attribution: true, commercialUse: commercial, @@ -299,4 +303,4 @@ contract LicensingHelper { LICENSING_MODULE.registerPolicyFrameworkManager(address(_pfm)); pfm["uml"] = address(_pfm); } -} +} \ No newline at end of file