From bef1f358b4b1a0bc08e821df719b563f3aec1553 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 11:27:31 +0300 Subject: [PATCH 01/31] adjust coverage cmd --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 20114d0bb..3361a47fc 100644 --- a/Makefile +++ b/Makefile @@ -44,8 +44,8 @@ format: coverage: mkdir -p coverage forge coverage --report lcov - lcov --remove lcov.info -o coverage/lcov.info 'test/*' 'script/*' --rc branch_coverage=1 - genhtml coverage/lcov.info -o coverage --rc branch_coverage=1 --ignore-errors category + lcov --remove lcov.info -o coverage/lcov.info 'test/*' 'script/*' --rc lcov_branch_coverage=1 + genhtml coverage/lcov.info -o coverage --rc lcov_branch_coverage=1 abi: rm -rf abi From 0cdb6aa1ddd882b1e4b21ec658f7faa7c801a939 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 12:09:01 +0300 Subject: [PATCH 02/31] add decimals and fix variable name warnings --- .../royalty/policies/IpRoyaltyVault.sol | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/contracts/modules/royalty/policies/IpRoyaltyVault.sol b/contracts/modules/royalty/policies/IpRoyaltyVault.sol index d8f57977f..bc75a00ff 100644 --- a/contracts/modules/royalty/policies/IpRoyaltyVault.sol +++ b/contracts/modules/royalty/policies/IpRoyaltyVault.sol @@ -102,6 +102,11 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy __ERC20_init(name, symbol); } + /// @notice Returns the number royalty token decimals + function decimals() public view override returns (uint8) { + return 6; + } + /// @notice Adds a new revenue token to the vault /// @param token The address of the revenue token /// @dev Only callable by the royalty policy LAP @@ -125,24 +130,24 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy uint32 unclaimedTokens = $.unclaimedRoyaltyTokens; $.unclaimedAtSnapshot[snapshotId] = unclaimedTokens; - address[] memory tokens = $.tokens.values(); + address[] memory tokenList = $.tokens.values(); - for (uint256 i = 0; i < tokens.length; i++) { - uint256 tokenBalance = IERC20Upgradeable(tokens[i]).balanceOf(address(this)); + for (uint256 i = 0; i < tokenList.length; i++) { + uint256 tokenBalance = IERC20Upgradeable(tokenList[i]).balanceOf(address(this)); if (tokenBalance == 0) { - $.tokens.remove(tokens[i]); + $.tokens.remove(tokenList[i]); continue; } - uint256 newRevenue = tokenBalance - $.claimVaultAmount[tokens[i]] - $.ancestorsVaultAmount[tokens[i]]; + uint256 newRevenue = tokenBalance - $.claimVaultAmount[tokenList[i]] - $.ancestorsVaultAmount[tokenList[i]]; if (newRevenue == 0) continue; uint256 ancestorsTokens = (newRevenue * unclaimedTokens) / totalSupply(); - $.ancestorsVaultAmount[tokens[i]] += ancestorsTokens; + $.ancestorsVaultAmount[tokenList[i]] += ancestorsTokens; uint256 claimableTokens = newRevenue - ancestorsTokens; - $.claimableAtSnapshot[snapshotId][tokens[i]] = claimableTokens; - $.claimVaultAmount[tokens[i]] += claimableTokens; + $.claimableAtSnapshot[snapshotId][tokenList[i]] = claimableTokens; + $.claimVaultAmount[tokenList[i]] += claimableTokens; } emit SnapshotCompleted(snapshotId, block.timestamp, unclaimedTokens); @@ -161,19 +166,19 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy /// @notice Allows token holders to claim revenue token based on the token balance at certain snapshot /// @param snapshotId The snapshot id - /// @param tokens The list of revenue tokens to claim - function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokens) external nonReentrant { + /// @param tokenList The list of revenue tokens to claim + function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokenList) external nonReentrant { IpRoyaltyVaultStorage storage $ = _getIpRoyaltyVaultStorage(); - for (uint256 i = 0; i < tokens.length; i++) { - uint256 claimableToken = _claimableRevenue(msg.sender, snapshotId, tokens[i]); + for (uint256 i = 0; i < tokenList.length; i++) { + uint256 claimableToken = _claimableRevenue(msg.sender, snapshotId, tokenList[i]); if (claimableToken == 0) continue; - $.isClaimedAtSnapshot[snapshotId][msg.sender][tokens[i]] = true; - $.claimVaultAmount[tokens[i]] -= claimableToken; - IERC20Upgradeable(tokens[i]).safeTransfer(msg.sender, claimableToken); + $.isClaimedAtSnapshot[snapshotId][msg.sender][tokenList[i]] = true; + $.claimVaultAmount[tokenList[i]] -= claimableToken; + IERC20Upgradeable(tokenList[i]).safeTransfer(msg.sender, claimableToken); - emit RevenueTokenClaimed(msg.sender, tokens[i], claimableToken); + emit RevenueTokenClaimed(msg.sender, tokenList[i], claimableToken); } } @@ -246,19 +251,19 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy function _collectAccruedTokens(uint256 royaltyTokensToClaim, address ancestorIpId) internal { IpRoyaltyVaultStorage storage $ = _getIpRoyaltyVaultStorage(); - address[] memory tokens = $.tokens.values(); + address[] memory tokenList = $.tokens.values(); - for (uint256 i = 0; i < tokens.length; ++i) { + for (uint256 i = 0; i < tokenList.length; ++i) { // the only case in which unclaimedRoyaltyTokens can be 0 is when the vault is empty and everyone claimed // in which case the call will revert upstream with IpRoyaltyVault__AlreadyClaimed error - uint256 collectAmount = ($.ancestorsVaultAmount[tokens[i]] * royaltyTokensToClaim) / + uint256 collectAmount = ($.ancestorsVaultAmount[tokenList[i]] * royaltyTokensToClaim) / $.unclaimedRoyaltyTokens; if (collectAmount == 0) continue; - $.ancestorsVaultAmount[tokens[i]] -= collectAmount; - IERC20Upgradeable(tokens[i]).safeTransfer(ancestorIpId, collectAmount); + $.ancestorsVaultAmount[tokenList[i]] -= collectAmount; + IERC20Upgradeable(tokenList[i]).safeTransfer(ancestorIpId, collectAmount); - emit RevenueTokenClaimed(ancestorIpId, tokens[i], collectAmount); + emit RevenueTokenClaimed(ancestorIpId, tokenList[i], collectAmount); } } From d893bdaf49d1a8352639444a6538534727a52517 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 12:16:34 +0300 Subject: [PATCH 03/31] name fix --- .../interfaces/modules/royalty/policies/IIpRoyaltyVault.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol b/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol index ecfed7922..079a7fe35 100644 --- a/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol +++ b/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol @@ -52,8 +52,8 @@ interface IIpRoyaltyVault { /// @notice Allows token holders to claim revenue token based on the token balance at certain snapshot /// @param snapshotId The snapshot id - /// @param tokens The list of revenue tokens to claim - function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokens) external; + /// @param tokenList The list of revenue tokens to claim + function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokenList) external; /// @notice Allows token holders to claim by a list of snapshot ids based on the token balance at certain snapshot /// @param snapshotIds The list of snapshot ids From b3314e2870cecc2ebb256cd66f642601e12e48b9 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 12:17:06 +0300 Subject: [PATCH 04/31] format fix --- .../foundry/modules/royalty/RoyaltyPolicyLAP.t.sol | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol index b3154b69c..be4fb574c 100644 --- a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol +++ b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol @@ -148,12 +148,7 @@ contract TestRoyaltyPolicyLAP is BaseTest { } function test_RoyaltyPolicyLAP_setSnapshotInterval_revert_NotOwner() public { - vm.expectRevert( - abi.encodeWithSelector( - IAccessManaged.AccessManagedUnauthorized.selector, - address(this) - ) - ); + vm.expectRevert(abi.encodeWithSelector(IAccessManaged.AccessManagedUnauthorized.selector, address(this))); royaltyPolicyLAP.setSnapshotInterval(100); } @@ -164,12 +159,7 @@ contract TestRoyaltyPolicyLAP is BaseTest { } function test_RoyaltyPolicyLAP_setIpRoyaltyVaultBeacon_revert_NotOwner() public { - vm.expectRevert( - abi.encodeWithSelector( - IAccessManaged.AccessManagedUnauthorized.selector, - address(this) - ) - ); + vm.expectRevert(abi.encodeWithSelector(IAccessManaged.AccessManagedUnauthorized.selector, address(this))); royaltyPolicyLAP.setIpRoyaltyVaultBeacon(address(1)); } From 8cb686a10660093361dfe7a35978921b852741a5 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 12:36:52 +0300 Subject: [PATCH 05/31] eliminate DataUniqueness --- contracts/lib/DataUniqueness.sol | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/contracts/lib/DataUniqueness.sol b/contracts/lib/DataUniqueness.sol index 7566ca4e5..e69de29bb 100644 --- a/contracts/lib/DataUniqueness.sol +++ b/contracts/lib/DataUniqueness.sol @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.23; - -/// @title DataUniqueness -/// @notice Library to store data without repetition, assigning an id to it if new or reusing existing one -/// if already stored -library DataUniqueness { - /// Stores data without repetition, assigning an id to it if new or reusing existing one if already stored - /// @param data raw bytes, abi.encode() a value to be hashed - /// @param _hashToIds storage ref to the mapping of hash -> data id - /// @param existingIds amount of distinct data stored. - /// @return id new sequential id if new data, reused id if not new - /// @return isNew True if a new id was generated, signaling the value was stored in _hashToIds. - /// False if id is reused and data was not stored - function addIdOrGetExisting( - bytes memory data, - mapping(bytes32 => uint256) storage _hashToIds, - uint256 existingIds - ) internal returns (uint256 id, bool isNew) { - // We could just use the hash as id to save some gas, but the UX/DX of having huge random - // numbers for ID is bad enough to justify the cost, plus we have a counter if we need to. - bytes32 hash = keccak256(data); - id = _hashToIds[hash]; - if (id != 0) { - return (id, false); - } - id = existingIds + 1; - _hashToIds[hash] = id; - return (id, true); - } -} From 6dd054ee5db73be73d30662047c092cc5311dab2 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 12:38:46 +0300 Subject: [PATCH 06/31] eliminate DataUniqueness 2 --- contracts/lib/DataUniqueness.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 contracts/lib/DataUniqueness.sol diff --git a/contracts/lib/DataUniqueness.sol b/contracts/lib/DataUniqueness.sol deleted file mode 100644 index e69de29bb..000000000 From 9edc3f0baf596e321fc9f2013934a468bc9b6a56 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 12:59:51 +0300 Subject: [PATCH 07/31] naming fixes and add zero var restrictions --- contracts/lib/ArrayUtils.sol | 10 ++++----- .../dispute/policies/ArbitrationPolicySP.sol | 22 ++++++++++--------- contracts/modules/royalty/RoyaltyModule.sol | 4 +++- .../royalty/policies/IpRoyaltyVault.sol | 1 + .../royalty/policies/RoyaltyPolicyLAP.sol | 8 ++++--- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/contracts/lib/ArrayUtils.sol b/contracts/lib/ArrayUtils.sol index 29c5dec08..dbec7667d 100644 --- a/contracts/lib/ArrayUtils.sol +++ b/contracts/lib/ArrayUtils.sol @@ -5,12 +5,12 @@ pragma solidity 0.8.23; /// @notice Library for address array operations library ArrayUtils { /// @notice Finds the index of the first occurrence of the given element. - /// @param _array The input array to search - /// @param _element The value to find + /// @param array The input array to search + /// @param element The value to find /// @return Returns (index and isIn) for the first occurrence starting from index 0 - function indexOf(address[] memory _array, address _element) internal pure returns (uint32, bool) { - for (uint32 i = 0; i < _array.length; i++) { - if (_array[i] == _element) return (i, true); + function indexOf(address[] memory array, address element) internal pure returns (uint32, bool) { + for (uint32 i = 0; i < array.length; i++) { + if (array[i] == element) return (i, true); } return (0, false); } diff --git a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol index e4db914c3..91beb7b8d 100644 --- a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol +++ b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol @@ -34,22 +34,24 @@ contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UU } /// Constructor - /// @param _disputeModule The dispute module address - /// @param _paymentToken The ERC20 payment token address - /// @param _arbitrationPrice The arbitration price + /// @param disputeModule The dispute module address + /// @param paymentToken The ERC20 payment token address + /// @param arbitrationPrice The arbitration price /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address _disputeModule, address _paymentToken, uint256 _arbitrationPrice) { - if (_disputeModule == address(0)) revert Errors.ArbitrationPolicySP__ZeroDisputeModule(); - if (_paymentToken == address(0)) revert Errors.ArbitrationPolicySP__ZeroPaymentToken(); + constructor(address disputeModule, address paymentToken, uint256 arbitrationPrice) { + if (disputeModule == address(0)) revert Errors.ArbitrationPolicySP__ZeroDisputeModule(); + if (paymentToken == address(0)) revert Errors.ArbitrationPolicySP__ZeroPaymentToken(); - DISPUTE_MODULE = _disputeModule; - PAYMENT_TOKEN = _paymentToken; - ARBITRATION_PRICE = _arbitrationPrice; + DISPUTE_MODULE = disputeModule; + PAYMENT_TOKEN = paymentToken; + ARBITRATION_PRICE = arbitrationPrice; } /// @notice initializer for this implementation contract /// @param accessManager The address of the protocol admin roles contract function initialize(address accessManager) public initializer { + if (accessManager == address(0)) revert Errors.ArbitrationPolicySP__ZeroAccessManager(); + __AccessManaged_init(accessManager); __UUPSUpgradeable_init(); } @@ -70,7 +72,7 @@ contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UU /// @param data The arbitrary data used to set the dispute judgement function onDisputeJudgement(uint256 disputeId, bool decision, bytes calldata data) external onlyDisputeModule { if (decision) { - (, address disputeInitiator, , , , ) = IDisputeModule(DISPUTE_MODULE).disputes(disputeId); + (, address disputeInitiator, , , , , ) = IDisputeModule(DISPUTE_MODULE).disputes(disputeId); IERC20(PAYMENT_TOKEN).safeTransfer(disputeInitiator, ARBITRATION_PRICE); } } diff --git a/contracts/modules/royalty/RoyaltyModule.sol b/contracts/modules/royalty/RoyaltyModule.sol index cd5f9416e..073173e51 100644 --- a/contracts/modules/royalty/RoyaltyModule.sol +++ b/contracts/modules/royalty/RoyaltyModule.sol @@ -55,9 +55,11 @@ contract RoyaltyModule is _disableInitializers(); } - /// @notice initializer for this implementation contract + /// @notice Initializer for this implementation contract /// @param accessManager The address of the protocol admin roles contract function initialize(address accessManager) external initializer { + if (accessManager == address(0)) revert Errors.RoyaltyModule__ZeroAccessManager(); + __AccessManaged_init(accessManager); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); diff --git a/contracts/modules/royalty/policies/IpRoyaltyVault.sol b/contracts/modules/royalty/policies/IpRoyaltyVault.sol index bc75a00ff..3dd723760 100644 --- a/contracts/modules/royalty/policies/IpRoyaltyVault.sol +++ b/contracts/modules/royalty/policies/IpRoyaltyVault.sol @@ -87,6 +87,7 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy address ipIdAddress ) external initializer { if (ipIdAddress == address(0)) revert Errors.IpRoyaltyVault__ZeroIpId(); + if (supply == 0) revert Errors.IpRoyaltyVault__ZeroSupply(); IpRoyaltyVaultStorage storage $ = _getIpRoyaltyVaultStorage(); diff --git a/contracts/modules/royalty/policies/RoyaltyPolicyLAP.sol b/contracts/modules/royalty/policies/RoyaltyPolicyLAP.sol index 55cf17c3d..4c9ad7fc8 100644 --- a/contracts/modules/royalty/policies/RoyaltyPolicyLAP.sol +++ b/contracts/modules/royalty/policies/RoyaltyPolicyLAP.sol @@ -72,9 +72,11 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, AccessManagedUpgradeable, Reentr } /// @notice Initializer for this implementation contract - /// @param governance The governance address - function initialize(address governance) external initializer { - __AccessManaged_init(governance); + /// @param accessManager The address of the protocol admin roles contract + function initialize(address accessManager) external initializer { + if (accessManager == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroAccessManager(); + + __AccessManaged_init(accessManager); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); } From 7ebc219e7d96d15c935c8e37fc495b7b6278192e Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 14:29:28 +0300 Subject: [PATCH 08/31] add new tagDerivativeIfParentInfringed function --- .../modules/dispute/IDisputeModule.sol | 28 ++- .../registries/ILicenseRegistry.sol | 6 + contracts/lib/Errors.sol | 12 ++ contracts/modules/dispute/DisputeModule.sol | 97 +++++++-- contracts/registries/LicenseRegistry.sol | 6 +- script/foundry/utils/DeployHelper.sol | 24 +-- test/foundry/integration/e2e/e2e.t.sol | 2 +- .../modules/dispute/DisputeModule.t.sol | 185 +++++++++++++++--- 8 files changed, 306 insertions(+), 54 deletions(-) diff --git a/contracts/interfaces/modules/dispute/IDisputeModule.sol b/contracts/interfaces/modules/dispute/IDisputeModule.sol index 60f0d31b4..8a0e81e75 100644 --- a/contracts/interfaces/modules/dispute/IDisputeModule.sol +++ b/contracts/interfaces/modules/dispute/IDisputeModule.sol @@ -10,6 +10,7 @@ interface IDisputeModule { /// @param linkToDisputeEvidence The link of the dispute evidence /// @param targetTag The target tag of the dispute /// @param currentTag The current tag of the dispute + /// @param parentDisputeId The parent dispute id struct Dispute { address targetIpId; address disputeInitiator; @@ -17,6 +18,7 @@ interface IDisputeModule { bytes32 linkToDisputeEvidence; bytes32 targetTag; bytes32 currentTag; + uint256 parentDisputeId; } /// @notice Event emitted when a dispute tag whitelist status is updated @@ -73,6 +75,18 @@ interface IDisputeModule { /// @param data Custom data adjusted to each policy event DisputeCancelled(uint256 disputeId, bytes data); + /// @notice Event emitted when a derivative is tagged on a parent infringement + /// @param parentIpId The parent ipId which infringed + /// @param derivativeIpId The derivative ipId which was tagged + /// @param parentDisputeId The parent dispute id in which infringement was found + /// @param tag The tag of the dispute applied to the derivative + event DerivativeTaggedOnParentInfringement( + address parentIpId, + address derivativeIpId, + uint256 parentDisputeId, + bytes32 tag + ); + /// @notice Event emitted when a dispute is resolved /// @param disputeId The dispute id event DisputeResolved(uint256 disputeId); @@ -94,6 +108,7 @@ interface IDisputeModule { /// @return linkToDisputeEvidence The link of the dispute summary /// @return targetTag The target tag of the dispute /// @return currentTag The current tag of the dispute + /// @return parentDisputeId The parent dispute id function disputes( uint256 disputeId ) @@ -105,7 +120,8 @@ interface IDisputeModule { address arbitrationPolicy, bytes32 linkToDisputeEvidence, bytes32 targetTag, - bytes32 currentTag + bytes32 currentTag, + uint256 parentDisputeId ); /// @notice Indicates if a dispute tag is whitelisted @@ -181,6 +197,16 @@ interface IDisputeModule { /// @param data The data to cancel the dispute function cancelDispute(uint256 disputeId, bytes calldata data) external; + /// @notice Tags a derivative if a parent has been tagged with an infringement tag + /// @param parentIpId The infringing parent ipId + /// @param derivativeIpId The derivative ipId + /// @param parentDisputeId The dispute id that tagged the parent ipId as infringing + function tagDerivativeIfParentInfringed( + address parentIpId, + address derivativeIpId, + uint256 parentDisputeId + ) external; + /// @notice Resolves a dispute after it has been judged /// @param disputeId The dispute id function resolveDispute(uint256 disputeId) external; diff --git a/contracts/interfaces/registries/ILicenseRegistry.sol b/contracts/interfaces/registries/ILicenseRegistry.sol index 238fba368..248d38dc2 100644 --- a/contracts/interfaces/registries/ILicenseRegistry.sol +++ b/contracts/interfaces/registries/ILicenseRegistry.sol @@ -134,6 +134,12 @@ interface ILicenseRegistry { /// @return parentIpId The address of the parent IP. function getParentIp(address childIpId, uint256 index) external view returns (address parentIpId); + /// @notice Checks if an IP is a parent IP. + /// @param parentIpId The address of the parent IP. + /// @param childIpId The address of the child IP. + /// @return Whether the IP is a parent IP. + function isParentIp(address parentIpId, address childIpId) external view returns (bool); + /// @notice Gets the count of parent IPs. /// @param childIpId The address of the childIP. /// @return The count o parent IPs. diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index e4cb47906..b29a28fff 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -238,10 +238,19 @@ library Errors { error DisputeModule__NotInDisputeState(); error DisputeModule__NotAbleToResolve(); error DisputeModule__NotRegisteredIpId(); + error DisputeModule__ParentIpIdMismatch(); + error DisputeModule__ParentNotTagged(); + error DisputeModule__NotDerivative(); + error DisputeModule__ParentDisputeNotResolved(); + error DisputeModule__ZeroLicenseRegistry(); + error DisputeModule__ZeroAssetRegistry(); + error DisputeModule__ZeroController(); + error DisputeModule__ZeroAccessManager(); error ArbitrationPolicySP__ZeroDisputeModule(); error ArbitrationPolicySP__ZeroPaymentToken(); error ArbitrationPolicySP__NotDisputeModule(); + error ArbitrationPolicySP__ZeroAccessManager(); //////////////////////////////////////////////////////////////////////////// // Royalty Module // @@ -259,6 +268,7 @@ library Errors { error RoyaltyModule__NoParentsOnLinking(); error RoyaltyModule__ZeroDisputeModule(); error RoyaltyModule__IpIsTagged(); + error RoyaltyModule__ZeroAccessManager(); error RoyaltyPolicyLAP__ZeroRoyaltyModule(); error RoyaltyPolicyLAP__ZeroLiquidSplitFactory(); @@ -275,8 +285,10 @@ library Errors { error RoyaltyPolicyLAP__UnlinkableToParents(); error RoyaltyPolicyLAP__LastPositionNotAbleToMintLicense(); error RoyaltyPolicyLAP__ZeroIpRoyaltyVaultBeacon(); + error RoyaltyPolicyLAP__ZeroAccessManager(); error IpRoyaltyVault__ZeroIpId(); + error IpRoyaltyVault__ZeroSupply(); error IpRoyaltyVault__ZeroRoyaltyPolicyLAP(); error IpRoyaltyVault__NotRoyaltyPolicyLAP(); error IpRoyaltyVault__SnapshotIntervalTooShort(); diff --git a/contracts/modules/dispute/DisputeModule.sol b/contracts/modules/dispute/DisputeModule.sol index feb22ae1d..761b9056a 100644 --- a/contracts/modules/dispute/DisputeModule.sol +++ b/contracts/modules/dispute/DisputeModule.sol @@ -6,11 +6,13 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; // solhint-disable-next-line max-line-length import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; +import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; import { DISPUTE_MODULE_KEY } from "../../lib/modules/Module.sol"; import { BaseModule } from "../../modules/BaseModule.sol"; import { AccessControlled } from "../../access/AccessControlled.sol"; import { IIPAssetRegistry } from "../../interfaces/registries/IIPAssetRegistry.sol"; +import { ILicenseRegistry } from "../../interfaces/registries/ILicenseRegistry.sol"; import { IDisputeModule } from "../../interfaces/modules/dispute/IDisputeModule.sol"; import { IArbitrationPolicy } from "../../interfaces/modules/dispute/policies/IArbitrationPolicy.sol"; import { Errors } from "../../lib/Errors.sol"; @@ -25,7 +27,8 @@ contract DisputeModule is AccessManagedUpgradeable, ReentrancyGuardUpgradeable, AccessControlled, - UUPSUpgradeable + UUPSUpgradeable, + MulticallUpgradeable { using EnumerableSet for EnumerableSet.Bytes32Set; @@ -64,21 +67,38 @@ contract DisputeModule is /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IIPAssetRegistry public immutable IP_ASSET_REGISTRY; + /// @notice Protocol-wide license registry + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + ILicenseRegistry public immutable LICENSE_REGISTRY; + /// Constructor /// @param controller The address of the access controller /// @param assetRegistry The address of the asset registry + /// @param licenseRegistry The address of the license registry /// @custom:oz-upgrades-unsafe-allow constructor - constructor(address controller, address assetRegistry) AccessControlled(controller, assetRegistry) { + constructor( + address controller, + address assetRegistry, + address licenseRegistry + ) AccessControlled(controller, assetRegistry) { + if (licenseRegistry == address(0)) revert Errors.DisputeModule__ZeroLicenseRegistry(); + if (assetRegistry == address(0)) revert Errors.DisputeModule__ZeroAssetRegistry(); + if (controller == address(0)) revert Errors.DisputeModule__ZeroController(); + IP_ASSET_REGISTRY = IIPAssetRegistry(assetRegistry); + LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry); _disableInitializers(); } - /// @notice initializer for this implementation contract + /// @notice Initializer for this implementation contract /// @param accessManager The address of the protocol admin roles contract function initialize(address accessManager) external initializer { + if (accessManager == address(0)) revert Errors.DisputeModule__ZeroAccessManager(); + __AccessManaged_init(accessManager); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); + __Multicall_init(); } /// @notice Whitelists a dispute tag @@ -178,7 +198,8 @@ contract DisputeModule is arbitrationPolicy: arbitrationPolicy, linkToDisputeEvidence: linkToDisputeEvidenceBytes, targetTag: targetTag, - currentTag: IN_DISPUTE + currentTag: IN_DISPUTE, + parentDisputeId: 0 }); IArbitrationPolicy(arbitrationPolicy).onRaiseDispute(msg.sender, data); @@ -239,13 +260,60 @@ contract DisputeModule is emit DisputeCancelled(disputeId, data); } + /// @notice Tags a derivative if a parent has been tagged with an infringement tag + /// @param parentIpId The infringing parent ipId + /// @param derivativeIpId The derivative ipId + /// @param parentDisputeId The dispute id that tagged the parent ipId as infringing + function tagDerivativeIfParentInfringed( + address parentIpId, + address derivativeIpId, + uint256 parentDisputeId + ) external { + DisputeModuleStorage storage $ = _getDisputeModuleStorage(); + + Dispute memory parentDispute = $.disputes[parentDisputeId]; + if (parentDispute.targetIpId != parentIpId) revert Errors.DisputeModule__ParentIpIdMismatch(); + if (parentDispute.currentTag == IN_DISPUTE || parentDispute.currentTag == bytes32(0)) + revert Errors.DisputeModule__ParentNotTagged(); + + if (!LICENSE_REGISTRY.isParentIp(parentIpId, derivativeIpId)) revert Errors.DisputeModule__NotDerivative(); + + address arbitrationPolicy = $.arbitrationPolicies[derivativeIpId]; + if (!$.isWhitelistedArbitrationPolicy[arbitrationPolicy]) arbitrationPolicy = $.baseArbitrationPolicy; + + uint256 disputeId = ++$.disputeCounter; + + $.disputes[disputeId] = Dispute({ + targetIpId: derivativeIpId, + disputeInitiator: msg.sender, + arbitrationPolicy: arbitrationPolicy, + linkToDisputeEvidence: "", + targetTag: parentDispute.currentTag, + currentTag: parentDispute.currentTag, + parentDisputeId: parentDisputeId + }); + + $.successfulDisputesPerIp[derivativeIpId]++; + + emit DerivativeTaggedOnParentInfringement( + parentIpId, + derivativeIpId, + parentDisputeId, + parentDispute.currentTag + ); + } + /// @notice Resolves a dispute after it has been judged /// @param disputeId The dispute id function resolveDispute(uint256 disputeId) external { DisputeModuleStorage storage $ = _getDisputeModuleStorage(); Dispute memory dispute = $.disputes[disputeId]; - if (msg.sender != dispute.disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator(); + if (dispute.parentDisputeId == 0 && msg.sender != dispute.disputeInitiator) + revert Errors.DisputeModule__NotDisputeInitiator(); + if (dispute.parentDisputeId > 0 && $.disputes[dispute.parentDisputeId].currentTag != bytes32(0)) + revert Errors.DisputeModule__ParentDisputeNotResolved(); + if (dispute.currentTag == IN_DISPUTE || dispute.currentTag == bytes32(0)) revert Errors.DisputeModule__NotAbleToResolve(); @@ -283,6 +351,7 @@ contract DisputeModule is /// @return linkToDisputeEvidence The link of the dispute summary /// @return targetTag The target tag of the dispute /// @return currentTag The current tag of the dispute + /// @return parentDisputeId The parent dispute id function disputes( uint256 disputeId ) @@ -294,17 +363,19 @@ contract DisputeModule is address arbitrationPolicy, bytes32 linkToDisputeEvidence, bytes32 targetTag, - bytes32 currentTag + bytes32 currentTag, + uint256 parentDisputeId ) { - DisputeModuleStorage storage $ = _getDisputeModuleStorage(); + Dispute memory dispute = _getDisputeModuleStorage().disputes[disputeId]; return ( - $.disputes[disputeId].targetIpId, - $.disputes[disputeId].disputeInitiator, - $.disputes[disputeId].arbitrationPolicy, - $.disputes[disputeId].linkToDisputeEvidence, - $.disputes[disputeId].targetTag, - $.disputes[disputeId].currentTag + dispute.targetIpId, + dispute.disputeInitiator, + dispute.arbitrationPolicy, + dispute.linkToDisputeEvidence, + dispute.targetTag, + dispute.currentTag, + dispute.parentDisputeId ); } diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index 611fc0ffc..e0dcc84ec 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -333,7 +333,7 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr return _getLicenseRegistryStorage().attachedLicenseTerms[ipId].length(); } - /// @notice got the derivative IP of an IP by its index. + /// @notice Gets the derivative IP of an IP by its index. /// @param parentIpId The address of the IP. /// @param index The index of the derivative IP within the array of all derivative IPs of the IP. /// @return childIpId The address of the derivative IP. @@ -364,6 +364,10 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr parentIpId = $.parentIps[childIpId].at(index); } + function isParentIp(address parentIpId, address childIpId) external view returns (bool) { + return _getLicenseRegistryStorage().parentIps[childIpId].contains(parentIpId); + } + /// @notice Gets the count of parent IPs. /// @param childIpId The address of the childIP. /// @return The count o parent IPs. diff --git a/script/foundry/utils/DeployHelper.sol b/script/foundry/utils/DeployHelper.sol index 476e276b9..0f7f629b7 100644 --- a/script/foundry/utils/DeployHelper.sol +++ b/script/foundry/utils/DeployHelper.sol @@ -215,18 +215,6 @@ contract DeployHelper is Script, BroadcastManager, JsonDeploymentHandler, Storag impl = address(0); _postdeploy(contractKey, address(royaltyModule)); - contractKey = "DisputeModule"; - _predeploy(contractKey); - impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry))); - disputeModule = DisputeModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(DisputeModule.initialize, address(protocolAccessManager)) - ) - ); - impl = address(0); - _postdeploy(contractKey, address(disputeModule)); - contractKey = "LicenseRegistry"; _predeploy(contractKey); impl = address(new LicenseRegistry()); @@ -239,6 +227,18 @@ contract DeployHelper is Script, BroadcastManager, JsonDeploymentHandler, Storag impl = address(0); // Make sure we don't deploy wrong impl _postdeploy(contractKey, address(licenseRegistry)); + contractKey = "DisputeModule"; + _predeploy(contractKey); + impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry))); + disputeModule = DisputeModule( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(DisputeModule.initialize, address(protocolAccessManager)) + ) + ); + impl = address(0); + _postdeploy(contractKey, address(disputeModule)); + contractKey = "LicenseToken"; _predeploy(contractKey); impl = address(new LicenseToken()); diff --git a/test/foundry/integration/e2e/e2e.t.sol b/test/foundry/integration/e2e/e2e.t.sol index a83f99b2e..ee410309a 100644 --- a/test/foundry/integration/e2e/e2e.t.sol +++ b/test/foundry/integration/e2e/e2e.t.sol @@ -119,7 +119,7 @@ contract e2e is Test { ); vm.label(address(royaltyModule), "RoyaltyModule"); - impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry))); + impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry))); disputeModule = DisputeModule( TestProxyHelper.deployUUPSProxy( impl, diff --git a/test/foundry/modules/dispute/DisputeModule.t.sol b/test/foundry/modules/dispute/DisputeModule.t.sol index b1df27c79..68a387ff9 100644 --- a/test/foundry/modules/dispute/DisputeModule.t.sol +++ b/test/foundry/modules/dispute/DisputeModule.t.sol @@ -9,6 +9,7 @@ import { Errors } from "contracts/lib/Errors.sol"; import { IModule } from "contracts/interfaces/modules/base/IModule.sol"; import { ArbitrationPolicySP } from "contracts/modules/dispute/policies/ArbitrationPolicySP.sol"; import { ShortStringOps } from "contracts/utils/ShortStringOps.sol"; +import { IDisputeModule } from "contracts/interfaces/modules/dispute/IDisputeModule.sol"; // test import { BaseTest } from "test/foundry/utils/BaseTest.t.sol"; import { TestProxyHelper } from "test/foundry/utils/TestProxyHelper.sol"; @@ -36,6 +37,7 @@ contract DisputeModuleTest is BaseTest { address internal ipAccount2 = address(0x111000bbb); address internal ipAddr; + address internal ipAddr2; address internal arbitrationRelayer; ArbitrationPolicySP internal arbitrationPolicySP2; @@ -70,6 +72,7 @@ contract DisputeModuleTest is BaseTest { }); mockNFT.mintId(u.alice, 0); + mockNFT.mintId(u.bob, 1); address expectedAddr = ERC6551AccountLib.computeAddress( address(erc6551Registry), @@ -79,17 +82,44 @@ contract DisputeModuleTest is BaseTest { address(mockNFT), 0 ); - vm.label(expectedAddr, "IPAccount0"); vm.startPrank(u.alice); ipAddr = ipAssetRegistry.register(address(mockNFT), 0); - licensingModule.attachLicenseTerms(ipAddr, address(pilTemplate), getSelectedPILicenseTermsId("cheap_flexible")); + // Bob mints 1 license of policy "pil-commercial-remix" from IPAccount1 and registers the derivative IP for + // NFT tokenId 2. + vm.startPrank(u.bob); + + uint256 mintAmount = 3; + erc20.approve(address(royaltyPolicyLAP), type(uint256).max); + + uint256[] memory licenseIds = new uint256[](1); + + licenseIds[0] = licensingModule.mintLicenseTokens({ + licensorIpId: ipAddr, + licenseTemplate: address(pilTemplate), + licenseTermsId: getSelectedPILicenseTermsId("cheap_flexible"), + amount: mintAmount, + receiver: u.bob, + royaltyContext: "" + }); // first license minted + + ipAddr2 = ipAssetRegistry.register(address(mockNFT), 1); + + licensingModule.registerDerivativeWithLicenseTokens(ipAddr2, licenseIds, ""); + + vm.stopPrank(); + // set arbitration policy vm.startPrank(ipAddr); disputeModule.setArbitrationPolicy(ipAddr, address(arbitrationPolicySP)); vm.stopPrank(); + + // set arbitration policy + vm.startPrank(ipAddr2); + disputeModule.setArbitrationPolicy(ipAddr2, address(arbitrationPolicySP)); + vm.stopPrank(); } function test_DisputeModule_whitelistDisputeTag_revert_ZeroDisputeTag() public { @@ -185,7 +215,7 @@ contract DisputeModuleTest is BaseTest { disputeModule.setArbitrationPolicy(ipAddr, address(arbitrationPolicySP2)); } - function test_setArbitrationPolicy() public { + function test_DisputeModule_setArbitrationPolicy() public { vm.startPrank(u.admin); disputeModule.whitelistArbitrationPolicy(address(arbitrationPolicySP2), true); vm.stopPrank(); @@ -199,22 +229,22 @@ contract DisputeModuleTest is BaseTest { assertEq(disputeModule.arbitrationPolicies(ipAddr), address(arbitrationPolicySP2)); } - function test_DisputeModule_PolicySP_raiseDispute_revert_NotRegisteredIpId() public { + function test_DisputeModule_raiseDispute_revert_NotRegisteredIpId() public { vm.expectRevert(Errors.DisputeModule__NotRegisteredIpId.selector); disputeModule.raiseDispute(address(1), string("urlExample"), "PLAGIARISM", ""); } - function test_DisputeModule_PolicySP_raiseDispute_revert_NotWhitelistedDisputeTag() public { + function test_DisputeModule_raiseDispute_revert_NotWhitelistedDisputeTag() public { vm.expectRevert(Errors.DisputeModule__NotWhitelistedDisputeTag.selector); disputeModule.raiseDispute(ipAddr, string("urlExample"), "NOT_WHITELISTED", ""); } - function test_DisputeModule_PolicySP_raiseDispute_revert_ZeroLinkToDisputeEvidence() public { + function test_DisputeModule_raiseDispute_revert_ZeroLinkToDisputeEvidence() public { vm.expectRevert(Errors.DisputeModule__ZeroLinkToDisputeEvidence.selector); disputeModule.raiseDispute(ipAddr, string(""), "PLAGIARISM", ""); } - function test_DisputeModule_PolicySP_raiseDispute_BlacklistedPolicy() public { + function test_DisputeModule_raiseDispute_BlacklistedPolicy() public { vm.startPrank(u.admin); disputeModule.whitelistArbitrationPolicy(address(arbitrationPolicySP), false); vm.stopPrank(); @@ -249,9 +279,11 @@ contract DisputeModuleTest is BaseTest { address arbitrationPolicy, bytes32 linkToDisputeEvidence, bytes32 targetTag, - bytes32 currentTag + bytes32 currentTag, + uint256 parentDisputeId ) = disputeModule.disputes(disputeIdAfter); + assertEq(disputeIdAfter, 1); assertEq(disputeIdAfter - disputeIdBefore, 1); assertEq(ipAccount1USDCBalanceBefore - ipAccount1USDCBalanceAfter, ARBITRATION_PRICE); assertEq(arbitrationPolicySPUSDCBalanceAfter - arbitrationPolicySPUSDCBalanceBefore, ARBITRATION_PRICE); @@ -261,9 +293,10 @@ contract DisputeModuleTest is BaseTest { assertEq(linkToDisputeEvidence, ShortStringOps.stringToBytes32("urlExample")); assertEq(targetTag, bytes32("PLAGIARISM")); assertEq(currentTag, bytes32("IN_DISPUTE")); + assertEq(parentDisputeId, 0); } - function test_DisputeModule_PolicySP_raiseDispute() public { + function test_DisputeModule_raiseDispute() public { vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); @@ -294,7 +327,8 @@ contract DisputeModuleTest is BaseTest { address arbitrationPolicy, bytes32 linkToDisputeEvidence, bytes32 targetTag, - bytes32 currentTag + bytes32 currentTag, + uint256 parentDisputeId ) = disputeModule.disputes(disputeIdAfter); assertEq(disputeIdAfter - disputeIdBefore, 1); @@ -306,14 +340,15 @@ contract DisputeModuleTest is BaseTest { assertEq(linkToDisputeEvidence, ShortStringOps.stringToBytes32("urlExample")); assertEq(targetTag, bytes32("PLAGIARISM")); assertEq(currentTag, bytes32("IN_DISPUTE")); + assertEq(parentDisputeId, 0); } - function test_DisputeModule_PolicySP_setDisputeJudgement_revert_NotInDisputeState() public { + function test_DisputeModule_setDisputeJudgement_revert_NotInDisputeState() public { vm.expectRevert(Errors.DisputeModule__NotInDisputeState.selector); disputeModule.setDisputeJudgement(1, true, ""); } - function test_DisputeModule_PolicySP_setDisputeJudgement_revert_NotWhitelistedArbitrationRelayer() public { + function test_DisputeModule_setDisputeJudgement_revert_NotWhitelistedArbitrationRelayer() public { // raise dispute vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); @@ -324,7 +359,7 @@ contract DisputeModuleTest is BaseTest { disputeModule.setDisputeJudgement(1, true, ""); } - function test_DisputeModule_PolicySP_setDisputeJudgement_True() public { + function test_DisputeModule_setDisputeJudgement_True() public { // raise dispute vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); @@ -332,7 +367,7 @@ contract DisputeModuleTest is BaseTest { vm.stopPrank(); // set dispute judgement - (, , , , , bytes32 currentTagBefore) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagBefore, ) = disputeModule.disputes(1); uint256 ipAccount1USDCBalanceBefore = USDC.balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceBefore = USDC.balanceOf(address(arbitrationPolicySP)); @@ -342,7 +377,7 @@ contract DisputeModuleTest is BaseTest { vm.startPrank(arbitrationRelayer); disputeModule.setDisputeJudgement(1, true, ""); - (, , , , , bytes32 currentTagAfter) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagAfter, ) = disputeModule.disputes(1); uint256 ipAccount1USDCBalanceAfter = USDC.balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceAfter = USDC.balanceOf(address(arbitrationPolicySP)); @@ -353,7 +388,7 @@ contract DisputeModuleTest is BaseTest { assertTrue(disputeModule.isIpTagged(ipAddr)); } - function test_DisputeModule_PolicySP_setDisputeJudgement_False() public { + function test_DisputeModule_setDisputeJudgement_False() public { // raise dispute vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); @@ -361,7 +396,7 @@ contract DisputeModuleTest is BaseTest { vm.stopPrank(); // set dispute judgement - (, , , , , bytes32 currentTagBefore) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagBefore, ) = disputeModule.disputes(1); uint256 ipAccount1USDCBalanceBefore = USDC.balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceBefore = USDC.balanceOf(address(arbitrationPolicySP)); @@ -371,7 +406,7 @@ contract DisputeModuleTest is BaseTest { vm.startPrank(arbitrationRelayer); disputeModule.setDisputeJudgement(1, false, ""); - (, , , , , bytes32 currentTagAfter) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagAfter, ) = disputeModule.disputes(1); uint256 ipAccount1USDCBalanceAfter = USDC.balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceAfter = USDC.balanceOf(address(arbitrationPolicySP)); @@ -382,7 +417,7 @@ contract DisputeModuleTest is BaseTest { assertFalse(disputeModule.isIpTagged(ipAddr)); } - function test_DisputeModule_PolicySP_cancelDispute_revert_NotDisputeInitiator() public { + function test_DisputeModule_cancelDispute_revert_NotDisputeInitiator() public { // raise dispute vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); @@ -393,19 +428,19 @@ contract DisputeModuleTest is BaseTest { disputeModule.cancelDispute(1, ""); } - function test_DisputeModule_PolicySP_cancelDispute_revert_NotInDisputeState() public { + function test_DisputeModule_cancelDispute_revert_NotInDisputeState() public { vm.expectRevert(Errors.DisputeModule__NotInDisputeState.selector); disputeModule.cancelDispute(1, ""); } - function test_DisputeModule_PolicySP_cancelDispute() public { + function test_DisputeModule_cancelDispute() public { // raise dispute vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); vm.stopPrank(); - (, , , , , bytes32 currentTagBeforeCancel) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagBeforeCancel, ) = disputeModule.disputes(1); vm.startPrank(ipAccount1); vm.expectEmit(true, true, true, true, address(disputeModule)); @@ -414,13 +449,92 @@ contract DisputeModuleTest is BaseTest { disputeModule.cancelDispute(1, ""); vm.stopPrank(); - (, , , , , bytes32 currentTagAfterCancel) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagAfterCancel, ) = disputeModule.disputes(1); assertEq(currentTagBeforeCancel, bytes32("IN_DISPUTE")); assertEq(currentTagAfterCancel, bytes32(0)); assertFalse(disputeModule.isIpTagged(ipAddr)); } + function test_DisputeModule_tagDerivativeIfParentInfringed_revert_ParentIpIdMismatch() public { + vm.expectRevert(Errors.DisputeModule__ParentIpIdMismatch.selector); + disputeModule.tagDerivativeIfParentInfringed(address(1), address(2), 1); + } + + function test_DisputeModule_tagDerivativeIfParentInfringed_revert_ParentNotTagged() public { + // raise dispute + vm.startPrank(ipAccount1); + IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); + disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); + vm.stopPrank(); + + vm.expectRevert(Errors.DisputeModule__ParentNotTagged.selector); + disputeModule.tagDerivativeIfParentInfringed(ipAddr, ipAddr2, 1); + } + + function test_DisputeModule_tagDerivativeIfParentInfringed_revert_NotDerivative() public { + // raise dispute + vm.startPrank(ipAccount1); + IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); + disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); + vm.stopPrank(); + + // set dispute judgement + vm.startPrank(arbitrationRelayer); + disputeModule.setDisputeJudgement(1, true, ""); + vm.stopPrank(); + + vm.expectRevert(Errors.DisputeModule__NotDerivative.selector); + disputeModule.tagDerivativeIfParentInfringed(ipAddr, address(0), 1); + } + + function test_DisputeModule_tagDerivativeIfParentInfringed() public { + // raise dispute + vm.startPrank(ipAccount1); + IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); + disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); + vm.stopPrank(); + + // set dispute judgement + vm.startPrank(arbitrationRelayer); + disputeModule.setDisputeJudgement(1, true, ""); + vm.stopPrank(); + + assertEq(licenseRegistry.isParentIp(ipAddr, ipAddr2), true); + + // tag child ip + vm.startPrank(address(1)); + vm.expectEmit(true, true, true, true, address(disputeModule)); + emit IDisputeModule.DerivativeTaggedOnParentInfringement(ipAddr, ipAddr2, 1, "PLAGIARISM"); + + uint256 disputeIdBefore = disputeModule.disputeCounter(); + + disputeModule.tagDerivativeIfParentInfringed(ipAddr, ipAddr2, 1); + + uint256 disputeIdAfter = disputeModule.disputeCounter(); + + ( + address targetIpId, + address disputeInitiator, + address arbitrationPolicy, + bytes32 linkToDisputeEvidence, + bytes32 targetTag, + bytes32 currentTag, + uint256 parentDisputeId + ) = disputeModule.disputes(disputeIdAfter); + + assertEq(disputeIdAfter - disputeIdBefore, 1); + assertEq(disputeIdAfter, 2); + assertTrue(disputeModule.isIpTagged(ipAddr2)); + assertEq(targetIpId, ipAddr2); + assertEq(disputeInitiator, address(1)); + assertEq(arbitrationPolicy, address(arbitrationPolicySP)); + assertEq(linkToDisputeEvidence, bytes32(0)); + assertEq(targetTag, bytes32("PLAGIARISM")); + assertEq(currentTag, bytes32("PLAGIARISM")); + assertEq(parentDisputeId, 1); + } + function test_DisputeModule_resolveDispute_revert_NotDisputeInitiator() public { vm.expectRevert(Errors.DisputeModule__NotDisputeInitiator.selector); disputeModule.resolveDispute(1); @@ -438,6 +552,25 @@ contract DisputeModuleTest is BaseTest { disputeModule.resolveDispute(1); } + function test_DisputeModule_resolveDispute_revert_ParentDisputeNotResolved() public { + // raise dispute + vm.startPrank(ipAccount1); + IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); + disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); + vm.stopPrank(); + + // set dispute judgement + vm.startPrank(arbitrationRelayer); + disputeModule.setDisputeJudgement(1, true, ""); + vm.stopPrank(); + + // tag derivative + disputeModule.tagDerivativeIfParentInfringed(ipAddr, ipAddr2, 1); + + vm.expectRevert(Errors.DisputeModule__ParentDisputeNotResolved.selector); + disputeModule.resolveDispute(2); + } + function test_DisputeModule_resolveDispute() public { // raise dispute vm.startPrank(ipAccount1); @@ -450,7 +583,7 @@ contract DisputeModuleTest is BaseTest { disputeModule.setDisputeJudgement(1, true, ""); vm.stopPrank(); - (, , , , , bytes32 currentTagBeforeResolve) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagBeforeResolve, ) = disputeModule.disputes(1); // resolve dispute vm.startPrank(ipAccount1); @@ -459,13 +592,13 @@ contract DisputeModuleTest is BaseTest { disputeModule.resolveDispute(1); - (, , , , , bytes32 currentTagAfterResolve) = disputeModule.disputes(1); + (, , , , , bytes32 currentTagAfterResolve, ) = disputeModule.disputes(1); assertEq(currentTagBeforeResolve, bytes32("PLAGIARISM")); assertEq(currentTagAfterResolve, bytes32(0)); assertFalse(disputeModule.isIpTagged(ipAddr)); - // Cant resolve again + // Can't resolve again vm.expectRevert(Errors.DisputeModule__NotAbleToResolve.selector); disputeModule.resolveDispute(1); vm.stopPrank(); From 21fc94c9b8ba49373e38f0da57b01877dced5c06 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 14:31:52 +0300 Subject: [PATCH 09/31] format fix --- test/foundry/integration/e2e/e2e.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/foundry/integration/e2e/e2e.t.sol b/test/foundry/integration/e2e/e2e.t.sol index ee410309a..648d4bbc5 100644 --- a/test/foundry/integration/e2e/e2e.t.sol +++ b/test/foundry/integration/e2e/e2e.t.sol @@ -119,7 +119,9 @@ contract e2e is Test { ); vm.label(address(royaltyModule), "RoyaltyModule"); - impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry))); + impl = address( + new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry)) + ); disputeModule = DisputeModule( TestProxyHelper.deployUUPSProxy( impl, From 091b199524de1ddf1cd7719fc2a90be593f1fb4f Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 14:46:05 +0300 Subject: [PATCH 10/31] add onResolveDispute hook for future-proofing --- .../modules/dispute/IDisputeModule.sol | 7 ++++--- .../dispute/policies/IArbitrationPolicy.sol | 7 +++++++ contracts/modules/dispute/DisputeModule.sol | 5 ++++- .../dispute/policies/ArbitrationPolicySP.sol | 21 ++++++++++++------- .../integration/flows/disputes/Disputes.t.sol | 2 +- .../modules/dispute/DisputeModule.t.sol | 10 ++++----- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/contracts/interfaces/modules/dispute/IDisputeModule.sol b/contracts/interfaces/modules/dispute/IDisputeModule.sol index 8a0e81e75..3d4bab0c7 100644 --- a/contracts/interfaces/modules/dispute/IDisputeModule.sol +++ b/contracts/interfaces/modules/dispute/IDisputeModule.sol @@ -177,7 +177,7 @@ interface IDisputeModule { /// @param targetIpId The ipId that is the target of the dispute /// @param linkToDisputeEvidence The link of the dispute evidence /// @param targetTag The target tag of the dispute - /// @param data The data to initialize the policy + /// @param data The data to raise a dispute /// @return disputeId The id of the newly raised dispute function raiseDispute( address targetIpId, @@ -208,8 +208,9 @@ interface IDisputeModule { ) external; /// @notice Resolves a dispute after it has been judged - /// @param disputeId The dispute id - function resolveDispute(uint256 disputeId) external; + /// @param disputeId The dispute + /// @param data The data to resolve the dispute + function resolveDispute(uint256 disputeId, bytes calldata data) external; /// @notice Returns true if the ipId is tagged with any tag (meaning at least one dispute went through) /// @param ipId The ipId diff --git a/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol b/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol index 4850ffe6c..fc067d933 100644 --- a/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol +++ b/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol @@ -36,6 +36,13 @@ interface IArbitrationPolicy { /// @param data The arbitrary data used to cancel the dispute function onDisputeCancel(address caller, uint256 disputeId, bytes calldata data) external; + /// @notice Executes custom logic on resolving dispute + /// @dev Enforced to be only callable by the DisputeModule + /// @param caller Address of the caller + /// @param disputeId The dispute id + /// @param data The arbitrary data used to resolve the dispute + function onResolveDispute(address caller, uint256 disputeId, bytes calldata data) external; + /// @notice Allows governance address to withdraw /// @dev Enforced to be only callable by the governance protocol admin. function governanceWithdraw() external; diff --git a/contracts/modules/dispute/DisputeModule.sol b/contracts/modules/dispute/DisputeModule.sol index 761b9056a..7dcf0aaa9 100644 --- a/contracts/modules/dispute/DisputeModule.sol +++ b/contracts/modules/dispute/DisputeModule.sol @@ -305,7 +305,8 @@ contract DisputeModule is /// @notice Resolves a dispute after it has been judged /// @param disputeId The dispute id - function resolveDispute(uint256 disputeId) external { + /// @param data The data to resolve the dispute + function resolveDispute(uint256 disputeId, bytes calldata data) external { DisputeModuleStorage storage $ = _getDisputeModuleStorage(); Dispute memory dispute = $.disputes[disputeId]; @@ -320,6 +321,8 @@ contract DisputeModule is $.successfulDisputesPerIp[dispute.targetIpId]--; $.disputes[disputeId].currentTag = bytes32(0); + IArbitrationPolicy(dispute.arbitrationPolicy).onResolveDispute(msg.sender, disputeId, data); + emit DisputeResolved(disputeId); } diff --git a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol index 91beb7b8d..8811858a3 100644 --- a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol +++ b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol @@ -56,8 +56,8 @@ contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UU __UUPSUpgradeable_init(); } - /// @notice Executes custom logic on raising dispute. - /// @dev Enforced to be only callable by the DisputeModule. + /// @notice Executes custom logic on raising dispute + /// @dev Enforced to be only callable by the DisputeModule /// @param caller Address of the caller /// @param data The arbitrary data used to raise the dispute function onRaiseDispute(address caller, bytes calldata data) external onlyDisputeModule { @@ -65,8 +65,8 @@ contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UU IERC20(PAYMENT_TOKEN).safeTransferFrom(caller, address(this), ARBITRATION_PRICE); } - /// @notice Executes custom logic on disputing judgement. - /// @dev Enforced to be only callable by the DisputeModule. + /// @notice Executes custom logic on disputing judgement + /// @dev Enforced to be only callable by the DisputeModule /// @param disputeId The dispute id /// @param decision The decision of the dispute /// @param data The arbitrary data used to set the dispute judgement @@ -77,15 +77,22 @@ contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UU } } - /// @notice Executes custom logic on disputing cancel. - /// @dev Enforced to be only callable by the DisputeModule. + /// @notice Executes custom logic on disputing cancel + /// @dev Enforced to be only callable by the DisputeModule /// @param caller Address of the caller /// @param disputeId The dispute id /// @param data The arbitrary data used to cancel the dispute function onDisputeCancel(address caller, uint256 disputeId, bytes calldata data) external onlyDisputeModule {} + /// @notice Executes custom logic on resolving dispute + /// @dev Enforced to be only callable by the DisputeModule + /// @param caller Address of the caller + /// @param disputeId The dispute id + /// @param data The arbitrary data used to resolve the dispute + function onResolveDispute(address caller, uint256 disputeId, bytes calldata data) external onlyDisputeModule {} + /// @notice Allows governance address to withdraw - /// @dev Enforced to be only callable by the governance protocol admin. + /// @dev Enforced to be only callable by the governance protocol admin function governanceWithdraw() external restricted { uint256 balance = IERC20(PAYMENT_TOKEN).balanceOf(address(this)); IERC20(PAYMENT_TOKEN).safeTransfer(msg.sender, balance); diff --git a/test/foundry/integration/flows/disputes/Disputes.t.sol b/test/foundry/integration/flows/disputes/Disputes.t.sol index 271844752..e75638fb7 100644 --- a/test/foundry/integration/flows/disputes/Disputes.t.sol +++ b/test/foundry/integration/flows/disputes/Disputes.t.sol @@ -116,7 +116,7 @@ contract Flows_Integration_Disputes is BaseIntegration { uint256 disputeId = _disputeIp(u.bob, ipAcct[1]); vm.prank(u.bob); - disputeModule.resolveDispute(disputeId); + disputeModule.resolveDispute(disputeId, ""); assertEq(licenseToken.balanceOf(u.carl), 0); diff --git a/test/foundry/modules/dispute/DisputeModule.t.sol b/test/foundry/modules/dispute/DisputeModule.t.sol index 68a387ff9..ac964c60f 100644 --- a/test/foundry/modules/dispute/DisputeModule.t.sol +++ b/test/foundry/modules/dispute/DisputeModule.t.sol @@ -537,7 +537,7 @@ contract DisputeModuleTest is BaseTest { function test_DisputeModule_resolveDispute_revert_NotDisputeInitiator() public { vm.expectRevert(Errors.DisputeModule__NotDisputeInitiator.selector); - disputeModule.resolveDispute(1); + disputeModule.resolveDispute(1, ""); } function test_DisputeModule_resolveDispute_revert_NotAbleToResolve() public { @@ -549,7 +549,7 @@ contract DisputeModuleTest is BaseTest { vm.startPrank(ipAccount1); vm.expectRevert(Errors.DisputeModule__NotAbleToResolve.selector); - disputeModule.resolveDispute(1); + disputeModule.resolveDispute(1, ""); } function test_DisputeModule_resolveDispute_revert_ParentDisputeNotResolved() public { @@ -568,7 +568,7 @@ contract DisputeModuleTest is BaseTest { disputeModule.tagDerivativeIfParentInfringed(ipAddr, ipAddr2, 1); vm.expectRevert(Errors.DisputeModule__ParentDisputeNotResolved.selector); - disputeModule.resolveDispute(2); + disputeModule.resolveDispute(2, ""); } function test_DisputeModule_resolveDispute() public { @@ -590,7 +590,7 @@ contract DisputeModuleTest is BaseTest { vm.expectEmit(true, true, true, true, address(disputeModule)); emit DisputeResolved(1); - disputeModule.resolveDispute(1); + disputeModule.resolveDispute(1, ""); (, , , , , bytes32 currentTagAfterResolve, ) = disputeModule.disputes(1); @@ -600,7 +600,7 @@ contract DisputeModuleTest is BaseTest { // Can't resolve again vm.expectRevert(Errors.DisputeModule__NotAbleToResolve.selector); - disputeModule.resolveDispute(1); + disputeModule.resolveDispute(1, ""); vm.stopPrank(); } From afad72a09a2f36f08a48a904f5ade8f686c125e0 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 14:53:57 +0300 Subject: [PATCH 11/31] remove InitParams from royalty tests --- .../modules/royalty/RoyaltyModule.t.sol | 274 +++--------------- .../modules/royalty/RoyaltyPolicyLAP.t.sol | 50 ++-- 2 files changed, 69 insertions(+), 255 deletions(-) diff --git a/test/foundry/modules/royalty/RoyaltyModule.t.sol b/test/foundry/modules/royalty/RoyaltyModule.t.sol index 187bf9f54..5bea403ae 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -24,21 +24,6 @@ contract TestRoyaltyModule is BaseTest { address internal ipAddr; address internal arbitrationRelayer; - struct InitParams { - address[] targetAncestors; - uint32[] targetRoyaltyAmount; - address[] parentAncestors1; - address[] parentAncestors2; - uint32[] parentAncestorsRoyalties1; - uint32[] parentAncestorsRoyalties2; - } - - InitParams internal initParamsMax; - bytes internal MAX_ANCESTORS; - address[] internal MAX_ANCESTORS_ = new address[](14); - uint32[] internal MAX_ANCESTORS_ROYALTY_ = new uint32[](14); - address[] internal parentsIpIds100; - RoyaltyPolicyLAP internal royaltyPolicyLAP2; function setUp() public override { @@ -48,10 +33,7 @@ contract TestRoyaltyModule is BaseTest { address impl = address(new RoyaltyPolicyLAP(address(royaltyModule), address(licensingModule))); royaltyPolicyLAP2 = RoyaltyPolicyLAP( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(RoyaltyPolicyLAP.initialize, address(protocolAccessManager)) - ) + TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyPolicyLAP.initialize, address(protocolAccessManager))) ); arbitrationRelayer = u.relayer; @@ -105,64 +87,29 @@ contract TestRoyaltyModule is BaseTest { function _setupTree() internal { // init royalty policy for roots - address[] memory nullTargetAncestors = new address[](0); - uint32[] memory nullTargetRoyaltyAmount = new uint32[](0); - uint32[] memory parentRoyalties = new uint32[](0); - address[] memory nullParentAncestors1 = new address[](0); - address[] memory nullParentAncestors2 = new address[](0); - uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); - uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); - InitParams memory nullInitParams = InitParams({ - targetAncestors: nullTargetAncestors, - targetRoyaltyAmount: nullTargetRoyaltyAmount, - parentAncestors1: nullParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - bytes memory nullBytes = abi.encode(nullInitParams); - - royaltyModule.onLicenseMinting(address(7), address(royaltyPolicyLAP), abi.encode(uint32(7)), nullBytes); - royaltyModule.onLicenseMinting(address(8), address(royaltyPolicyLAP), abi.encode(uint32(8)), nullBytes); + royaltyModule.onLicenseMinting(address(7), address(royaltyPolicyLAP), abi.encode(uint32(7)), ""); + royaltyModule.onLicenseMinting(address(8), address(royaltyPolicyLAP), abi.encode(uint32(8)), ""); // init 2nd level with children address[] memory parents = new address[](2); - address[] memory targetAncestors1 = new address[](2); - uint32[] memory targetRoyaltyAmount1 = new uint32[](2); - uint32[] memory parentRoyalties1 = new uint32[](2); + uint32[] memory parentRoyalties = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); // 3 is child of 7 and 8 parents[0] = address(7); parents[1] = address(8); - parentRoyalties1[0] = 7; - parentRoyalties1[1] = 8; - targetAncestors1[0] = address(7); - targetAncestors1[1] = address(8); - targetRoyaltyAmount1[0] = 7; - targetRoyaltyAmount1[1] = 8; - InitParams memory initParams = InitParams({ - targetAncestors: targetAncestors1, - targetRoyaltyAmount: targetRoyaltyAmount1, - parentAncestors1: nullParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + parentRoyalties[0] = 7; + parentRoyalties[1] = 8; + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } - bytes memory encodedBytes = abi.encode(initParams); - royaltyModule.onLinkToParents(address(3), address(royaltyPolicyLAP), parents, encodedLicenseData, encodedBytes); + royaltyModule.onLinkToParents(address(3), address(royaltyPolicyLAP), parents, encodedLicenseData, ""); } function test_RoyaltyModule_setDisputeModule_revert_ZeroDisputeModule() public { address impl = address(new RoyaltyModule()); RoyaltyModule testRoyaltyModule = RoyaltyModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager)) - ) + TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))) ); vm.expectRevert(Errors.RoyaltyModule__ZeroDisputeModule.selector); vm.prank(u.admin); @@ -173,10 +120,7 @@ contract TestRoyaltyModule is BaseTest { vm.startPrank(u.admin); address impl = address(new RoyaltyModule()); RoyaltyModule testRoyaltyModule = RoyaltyModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager)) - ) + TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))) ); testRoyaltyModule.setDisputeModule(address(disputeModule)); assertEq(testRoyaltyModule.disputeModule(), address(disputeModule)); @@ -192,10 +136,7 @@ contract TestRoyaltyModule is BaseTest { vm.startPrank(u.admin); address impl = address(new RoyaltyModule()); RoyaltyModule testRoyaltyModule = RoyaltyModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager)) - ) + TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))) ); testRoyaltyModule.setLicensingModule(address(licensingModule)); assertEq(testRoyaltyModule.licensingModule(), address(licensingModule)); @@ -265,40 +206,8 @@ contract TestRoyaltyModule is BaseTest { address licensor = address(3); bytes memory licenseData = abi.encode(uint32(15)); - address[] memory parents = new address[](2); - address[] memory targetAncestors1 = new address[](2); - uint32[] memory targetRoyaltyAmount1 = new uint32[](2); - uint32[] memory parentRoyalties1 = new uint32[](2); - bytes[] memory encodedLicenseData = new bytes[](2); - - address[] memory nullParentAncestors1 = new address[](0); - address[] memory nullParentAncestors2 = new address[](0); - uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); - uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); - - parents[0] = address(7); - parents[1] = address(8); - parentRoyalties1[0] = 7; - parentRoyalties1[1] = 8; - targetAncestors1[0] = address(7); - targetAncestors1[1] = address(8); - targetRoyaltyAmount1[0] = 7; - targetRoyaltyAmount1[1] = 8; - InitParams memory initParams = InitParams({ - targetAncestors: targetAncestors1, - targetRoyaltyAmount: targetRoyaltyAmount1, - parentAncestors1: nullParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); - } - bytes memory encodedBytes = abi.encode(initParams); - vm.startPrank(address(licensingModule)); - royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, encodedBytes); + royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, ""); } function test_RoyaltyModule_onLicenseMinting_Root() public { @@ -306,25 +215,8 @@ contract TestRoyaltyModule is BaseTest { bytes memory licenseData = abi.encode(uint32(15)); // mint a license of another policy - address[] memory nullTargetAncestors = new address[](0); - uint32[] memory nullTargetRoyaltyAmount = new uint32[](0); - uint32[] memory parentRoyalties = new uint32[](0); - address[] memory nullParentAncestors1 = new address[](0); - address[] memory nullParentAncestors2 = new address[](0); - uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); - uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); - InitParams memory nullInitParams = InitParams({ - targetAncestors: nullTargetAncestors, - targetRoyaltyAmount: nullTargetRoyaltyAmount, - parentAncestors1: nullParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - bytes memory nullBytes = abi.encode(nullInitParams); - vm.startPrank(address(licensingModule)); - royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, nullBytes); + royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, ""); vm.stopPrank(); vm.startPrank(u.admin); @@ -332,119 +224,59 @@ contract TestRoyaltyModule is BaseTest { vm.stopPrank(); vm.startPrank(address(licensingModule)); - royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, nullBytes); + royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, ""); } function test_RoyaltyModule_onLinkToParents_revert_NotWhitelistedRoyaltyPolicy() public { address newChild = address(9); address[] memory parents = new address[](2); - address[] memory targetAncestors1 = new address[](2); - uint32[] memory targetRoyaltyAmount1 = new uint32[](2); - uint32[] memory parentRoyalties1 = new uint32[](2); + uint32[] memory parentRoyalties = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); - address[] memory nullParentAncestors1 = new address[](0); - address[] memory nullParentAncestors2 = new address[](0); - uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); - uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); parents[0] = address(7); parents[1] = address(8); - parentRoyalties1[0] = 7; - parentRoyalties1[1] = 8; - targetAncestors1[0] = address(7); - targetAncestors1[1] = address(8); - targetRoyaltyAmount1[0] = 7; - targetRoyaltyAmount1[1] = 8; - InitParams memory initParams = InitParams({ - targetAncestors: targetAncestors1, - targetRoyaltyAmount: targetRoyaltyAmount1, - parentAncestors1: nullParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + parentRoyalties[0] = 7; + parentRoyalties[1] = 8; + + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } - bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(address(licensingModule)); vm.expectRevert(Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy.selector); - royaltyModule.onLinkToParents(newChild, address(1), parents, encodedLicenseData, encodedBytes); + royaltyModule.onLinkToParents(newChild, address(1), parents, encodedLicenseData, ""); } function test_RoyaltyModule_onLinkToParents_revert_NoParentsOnLinking() public { address newChild = address(9); address[] memory parents = new address[](0); - address[] memory targetAncestors1 = new address[](2); - uint32[] memory targetRoyaltyAmount1 = new uint32[](2); - uint32[] memory parentRoyalties1 = new uint32[](2); + uint32[] memory parentRoyalties = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); - address[] memory nullParentAncestors1 = new address[](0); - address[] memory nullParentAncestors2 = new address[](0); - uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); - uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); - - parentRoyalties1[0] = 7; - parentRoyalties1[1] = 8; - targetAncestors1[0] = address(7); - targetAncestors1[1] = address(8); - targetRoyaltyAmount1[0] = 7; - targetRoyaltyAmount1[1] = 8; - InitParams memory initParams = InitParams({ - targetAncestors: targetAncestors1, - targetRoyaltyAmount: targetRoyaltyAmount1, - parentAncestors1: nullParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + + parentRoyalties[0] = 7; + parentRoyalties[1] = 8; + + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } - bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(address(licensingModule)); vm.expectRevert(Errors.RoyaltyModule__NoParentsOnLinking.selector); - royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, encodedBytes); + royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, ""); } function test_RoyaltyModule_onLinkToParents_revert_IncompatibleRoyaltyPolicy() public { address newChild = address(9); address[] memory parents = new address[](2); - address[] memory targetAncestors1 = new address[](3); - uint32[] memory targetRoyaltyAmount1 = new uint32[](3); - uint32[] memory parentRoyalties1 = new uint32[](1); + uint32[] memory parentRoyalties = new uint32[](1); bytes[] memory encodedLicenseData = new bytes[](2); - address[] memory ParentAncestors1 = new address[](2); - address[] memory nullParentAncestors2 = new address[](0); - uint32[] memory ParentAncestorsRoyalties1 = new uint32[](2); - uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); parents[0] = address(3); - parentRoyalties1[0] = 3; - targetAncestors1[0] = address(3); - targetAncestors1[1] = address(7); - targetAncestors1[2] = address(8); - targetRoyaltyAmount1[0] = 3; - targetRoyaltyAmount1[1] = 7; - targetRoyaltyAmount1[2] = 8; - ParentAncestors1[0] = address(7); - ParentAncestors1[1] = address(8); - ParentAncestorsRoyalties1[0] = 7; - ParentAncestorsRoyalties1[1] = 8; - InitParams memory initParams = InitParams({ - targetAncestors: targetAncestors1, - targetRoyaltyAmount: targetRoyaltyAmount1, - parentAncestors1: ParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: ParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + parentRoyalties[0] = 3; + + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } - bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(u.admin); royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP2), true); @@ -452,7 +284,7 @@ contract TestRoyaltyModule is BaseTest { vm.startPrank(address(licensingModule)); vm.expectRevert(Errors.RoyaltyModule__IncompatibleRoyaltyPolicy.selector); - royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP2), parents, encodedLicenseData, encodedBytes); + royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP2), parents, encodedLicenseData, ""); } function test_RoyaltyModule_onLinkToParents() public { @@ -460,38 +292,20 @@ contract TestRoyaltyModule is BaseTest { // new child is linked to 7 and 8 address[] memory parents = new address[](2); - address[] memory targetAncestors1 = new address[](2); - uint32[] memory targetRoyaltyAmount1 = new uint32[](2); - uint32[] memory parentRoyalties1 = new uint32[](2); + uint32[] memory parentRoyalties = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); - address[] memory nullParentAncestors1 = new address[](0); - address[] memory nullParentAncestors2 = new address[](0); - uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); - uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); parents[0] = address(7); parents[1] = address(8); - parentRoyalties1[0] = 7; - parentRoyalties1[1] = 8; - targetAncestors1[0] = address(7); - targetAncestors1[1] = address(8); - targetRoyaltyAmount1[0] = 7; - targetRoyaltyAmount1[1] = 8; - InitParams memory initParams = InitParams({ - targetAncestors: targetAncestors1, - targetRoyaltyAmount: targetRoyaltyAmount1, - parentAncestors1: nullParentAncestors1, - parentAncestors2: nullParentAncestors2, - parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, - parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 - }); - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + parentRoyalties[0] = 7; + parentRoyalties[1] = 8; + + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } - bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(address(licensingModule)); - royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, encodedBytes); + royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, ""); assertEq(royaltyModule.royaltyPolicies(newChild), address(royaltyPolicyLAP)); } @@ -613,4 +427,4 @@ contract TestRoyaltyModule is BaseTest { assertEq(payerAddressUSDCBalBefore - payerAddressUSDCBalAfter, royaltyAmount); assertEq(ipRoyaltyVaultUSDCBalAfter - ipRoyaltyVaultUSDCBalBefore, royaltyAmount); } -} +} \ No newline at end of file diff --git a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol index be4fb574c..d3a4d7594 100644 --- a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol +++ b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol @@ -40,50 +40,50 @@ contract TestRoyaltyPolicyLAP is BaseTest { // init 2nd level with children address[] memory parents = new address[](2); - uint32[] memory parentRoyalties1 = new uint32[](2); + uint32[] memory parentRoyalties = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); // 3 is child of 7 and 8 parents[0] = address(7); parents[1] = address(8); - parentRoyalties1[0] = 7; - parentRoyalties1[1] = 8; + parentRoyalties[0] = 7; + parentRoyalties[1] = 8; - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } royaltyPolicyLAP.onLinkToParents(address(3), parents, encodedLicenseData, ""); // 4 is child of 9 and 10 parents[0] = address(9); parents[1] = address(10); - parentRoyalties1[0] = 9; - parentRoyalties1[1] = 10; + parentRoyalties[0] = 9; + parentRoyalties[1] = 10; - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } royaltyPolicyLAP.onLinkToParents(address(4), parents, encodedLicenseData, ""); // 5 is child of 11 and 12 parents[0] = address(11); parents[1] = address(12); - parentRoyalties1[0] = 11; - parentRoyalties1[1] = 12; + parentRoyalties[0] = 11; + parentRoyalties[1] = 12; - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } royaltyPolicyLAP.onLinkToParents(address(5), parents, encodedLicenseData, ""); // 6 is child of 13 and 14 parents[0] = address(13); parents[1] = address(14); - parentRoyalties1[0] = 13; - parentRoyalties1[1] = 14; + parentRoyalties[0] = 13; + parentRoyalties[1] = 14; - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } royaltyPolicyLAP.onLinkToParents(address(6), parents, encodedLicenseData, ""); @@ -91,22 +91,22 @@ contract TestRoyaltyPolicyLAP is BaseTest { // 1 is child of 3 and 4 parents[0] = address(3); parents[1] = address(4); - parentRoyalties1[0] = 3; - parentRoyalties1[1] = 4; + parentRoyalties[0] = 3; + parentRoyalties[1] = 4; - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } royaltyPolicyLAP.onLinkToParents(address(1), parents, encodedLicenseData, ""); // 2 is child of 5 and 6 parents[0] = address(5); parents[1] = address(6); - parentRoyalties1[0] = 5; - parentRoyalties1[1] = 6; + parentRoyalties[0] = 5; + parentRoyalties[1] = 6; - for (uint32 i = 0; i < parentRoyalties1.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + for (uint32 i = 0; i < parentRoyalties.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties[i]); } royaltyPolicyLAP.onLinkToParents(address(2), parents, encodedLicenseData, ""); From 35289856cc810fb07f8258a922df774d2ff6c138 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 14:58:14 +0300 Subject: [PATCH 12/31] format fix --- .../modules/royalty/RoyaltyModule.t.sol | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/test/foundry/modules/royalty/RoyaltyModule.t.sol b/test/foundry/modules/royalty/RoyaltyModule.t.sol index 5bea403ae..bfa4f8ae6 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -33,7 +33,10 @@ contract TestRoyaltyModule is BaseTest { address impl = address(new RoyaltyPolicyLAP(address(royaltyModule), address(licensingModule))); royaltyPolicyLAP2 = RoyaltyPolicyLAP( - TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyPolicyLAP.initialize, address(protocolAccessManager))) + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(RoyaltyPolicyLAP.initialize, address(protocolAccessManager)) + ) ); arbitrationRelayer = u.relayer; @@ -109,7 +112,10 @@ contract TestRoyaltyModule is BaseTest { function test_RoyaltyModule_setDisputeModule_revert_ZeroDisputeModule() public { address impl = address(new RoyaltyModule()); RoyaltyModule testRoyaltyModule = RoyaltyModule( - TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))) + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager)) + ) ); vm.expectRevert(Errors.RoyaltyModule__ZeroDisputeModule.selector); vm.prank(u.admin); @@ -120,7 +126,10 @@ contract TestRoyaltyModule is BaseTest { vm.startPrank(u.admin); address impl = address(new RoyaltyModule()); RoyaltyModule testRoyaltyModule = RoyaltyModule( - TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))) + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager)) + ) ); testRoyaltyModule.setDisputeModule(address(disputeModule)); assertEq(testRoyaltyModule.disputeModule(), address(disputeModule)); @@ -136,7 +145,10 @@ contract TestRoyaltyModule is BaseTest { vm.startPrank(u.admin); address impl = address(new RoyaltyModule()); RoyaltyModule testRoyaltyModule = RoyaltyModule( - TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))) + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager)) + ) ); testRoyaltyModule.setLicensingModule(address(licensingModule)); assertEq(testRoyaltyModule.licensingModule(), address(licensingModule)); @@ -427,4 +439,4 @@ contract TestRoyaltyModule is BaseTest { assertEq(payerAddressUSDCBalBefore - payerAddressUSDCBalAfter, royaltyAmount); assertEq(ipRoyaltyVaultUSDCBalAfter - ipRoyaltyVaultUSDCBalBefore, royaltyAmount); } -} \ No newline at end of file +} From 96f6f17dd9e0d71fc1a2ba865c08d2fed0768841 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 15:22:21 +0300 Subject: [PATCH 13/31] increase test coverage RoyaltyModule --- contracts/modules/royalty/RoyaltyModule.sol | 1 - .../modules/royalty/RoyaltyModule.t.sol | 39 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/contracts/modules/royalty/RoyaltyModule.sol b/contracts/modules/royalty/RoyaltyModule.sol index 073173e51..f921d96d8 100644 --- a/contracts/modules/royalty/RoyaltyModule.sol +++ b/contracts/modules/royalty/RoyaltyModule.sol @@ -221,7 +221,6 @@ contract RoyaltyModule is RoyaltyModuleStorage storage $ = _getRoyaltyModuleStorage(); if (!$.isWhitelistedRoyaltyToken[token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken(); if (IDisputeModule($.disputeModule).isIpTagged(receiverIpId)) revert Errors.RoyaltyModule__IpIsTagged(); - if (licenseRoyaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet(); if (!$.isWhitelistedRoyaltyPolicy[licenseRoyaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); diff --git a/test/foundry/modules/royalty/RoyaltyModule.t.sol b/test/foundry/modules/royalty/RoyaltyModule.t.sol index bfa4f8ae6..1afa27b56 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -109,6 +109,17 @@ contract TestRoyaltyModule is BaseTest { royaltyModule.onLinkToParents(address(3), address(royaltyPolicyLAP), parents, encodedLicenseData, ""); } + function test_RoyaltyModule_initialize_revert_ZeroAccessManager() public { + address impl = address(new RoyaltyModule()); + vm.expectRevert(Errors.RoyaltyModule__ZeroAccessManager.selector); + RoyaltyModule( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(RoyaltyModule.initialize, address(0)) + ) + ); + } + function test_RoyaltyModule_setDisputeModule_revert_ZeroDisputeModule() public { address impl = address(new RoyaltyModule()); RoyaltyModule testRoyaltyModule = RoyaltyModule( @@ -410,6 +421,34 @@ contract TestRoyaltyModule is BaseTest { royaltyModule.payLicenseMintingFee(ipAddr, ipAccount1, address(royaltyPolicyLAP), address(USDC), 100); } + function test_RoyaltyModule_payLicenseMintingFee_revert_NotWhitelistedRoyaltyToken() public { + uint256 royaltyAmount = 100 * 10 ** 6; + address receiverIpId = address(7); + address payerAddress = address(3); + address licenseRoyaltyPolicy = address(royaltyPolicyLAP); + address token = address(1); + + vm.startPrank(address(licensingModule)); + + vm.expectRevert(Errors.RoyaltyModule__NotWhitelistedRoyaltyToken.selector); + royaltyModule.payLicenseMintingFee(receiverIpId, payerAddress, licenseRoyaltyPolicy, token, royaltyAmount); + } + + function test_RoyaltyModule_payLicenseMintingFee_revert_NotWhitelistedRoyaltyPolicy() public { + uint256 royaltyAmount = 100 * 10 ** 6; + address receiverIpId = address(7); + address payerAddress = address(3); + address licenseRoyaltyPolicy = address(1); + address token = address(USDC); + + vm.startPrank(u.admin); + royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP), false); + + vm.startPrank(address(licensingModule)); + vm.expectRevert(Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy.selector); + royaltyModule.payLicenseMintingFee(receiverIpId, payerAddress, licenseRoyaltyPolicy, token, royaltyAmount); + } + function test_RoyaltyModule_payLicenseMintingFee() public { uint256 royaltyAmount = 100 * 10 ** 6; address receiverIpId = address(7); From 460c381ab0a6f799f23a9c98be45f904fa143cbc Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 15:22:58 +0300 Subject: [PATCH 14/31] increase test coverage ArbitrationPolicySP --- .../modules/dispute/ArbitrationPolicySP.t.sol | 15 +++++++++++++++ test/foundry/modules/royalty/RoyaltyModule.t.sol | 7 +------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol index 596cd980d..0180633d0 100644 --- a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol +++ b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol @@ -96,6 +96,21 @@ contract TestArbitrationPolicySP is BaseTest { assertEq(arbitrationPolicySP.ARBITRATION_PRICE(), arbitrationPrice); } + function test_ArbitrationPolicySP_revert_ZeroAccessManager() public { + address disputeModule = address(1); + address paymentToken = address(2); + uint256 arbitrationPrice = 1000; + + ArbitrationPolicySP arbitrationPolicySP = new ArbitrationPolicySP( + disputeModule, + paymentToken, + arbitrationPrice + ); + + vm.expectRevert(Errors.ArbitrationPolicySP__ZeroAccessManager.selector); + arbitrationPolicySP.initialize(address(0)); + } + function test_ArbitrationPolicySP_onRaiseDispute_NotDisputeModule() public { vm.expectRevert(Errors.ArbitrationPolicySP__NotDisputeModule.selector); arbitrationPolicySP.onRaiseDispute(address(1), new bytes(0)); diff --git a/test/foundry/modules/royalty/RoyaltyModule.t.sol b/test/foundry/modules/royalty/RoyaltyModule.t.sol index 1afa27b56..ba5e7e5b5 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -112,12 +112,7 @@ contract TestRoyaltyModule is BaseTest { function test_RoyaltyModule_initialize_revert_ZeroAccessManager() public { address impl = address(new RoyaltyModule()); vm.expectRevert(Errors.RoyaltyModule__ZeroAccessManager.selector); - RoyaltyModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(RoyaltyModule.initialize, address(0)) - ) - ); + RoyaltyModule(TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(0)))); } function test_RoyaltyModule_setDisputeModule_revert_ZeroDisputeModule() public { From 5c7922a01f2c0e348979d3f2c5a21c8ab72b9268 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 15:41:54 +0300 Subject: [PATCH 15/31] add disableInitializers() to ArbitrationPolicySP --- .../dispute/policies/ArbitrationPolicySP.sol | 2 ++ .../modules/dispute/ArbitrationPolicySP.t.sol | 14 ++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol index 8811858a3..1a27ec7d3 100644 --- a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol +++ b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol @@ -45,6 +45,8 @@ contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UU DISPUTE_MODULE = disputeModule; PAYMENT_TOKEN = paymentToken; ARBITRATION_PRICE = arbitrationPrice; + + _disableInitializers(); } /// @notice initializer for this implementation contract diff --git a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol index 0180633d0..443a5ee3d 100644 --- a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol +++ b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol @@ -9,6 +9,7 @@ import { Errors } from "contracts/lib/Errors.sol"; import { ArbitrationPolicySP } from "contracts/modules/dispute/policies/ArbitrationPolicySP.sol"; // test import { BaseTest } from "test/foundry/utils/BaseTest.t.sol"; +import { TestProxyHelper } from "test/foundry/utils/TestProxyHelper.sol"; contract TestArbitrationPolicySP is BaseTest { event GovernanceWithdrew(uint256 amount); @@ -101,14 +102,15 @@ contract TestArbitrationPolicySP is BaseTest { address paymentToken = address(2); uint256 arbitrationPrice = 1000; - ArbitrationPolicySP arbitrationPolicySP = new ArbitrationPolicySP( - disputeModule, - paymentToken, - arbitrationPrice - ); + address impl = address(new ArbitrationPolicySP(address(disputeModule), paymentToken, arbitrationPrice)); vm.expectRevert(Errors.ArbitrationPolicySP__ZeroAccessManager.selector); - arbitrationPolicySP.initialize(address(0)); + arbitrationPolicySP = ArbitrationPolicySP( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(ArbitrationPolicySP.initialize, address(0)) + ) + ); } function test_ArbitrationPolicySP_onRaiseDispute_NotDisputeModule() public { From 6b897566272f20c3ce2f47725b7a16d63d536463 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 16:07:43 +0300 Subject: [PATCH 16/31] increase test coverage in IpRoyaltyVault --- contracts/lib/Errors.sol | 2 -- contracts/modules/royalty/policies/IpRoyaltyVault.sol | 3 --- test/foundry/modules/dispute/ArbitrationPolicySP.t.sol | 5 +---- test/foundry/modules/royalty/IpRoyaltyVault.t.sol | 7 ++++++- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index b29a28fff..f86d7cbcf 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -287,8 +287,6 @@ library Errors { error RoyaltyPolicyLAP__ZeroIpRoyaltyVaultBeacon(); error RoyaltyPolicyLAP__ZeroAccessManager(); - error IpRoyaltyVault__ZeroIpId(); - error IpRoyaltyVault__ZeroSupply(); error IpRoyaltyVault__ZeroRoyaltyPolicyLAP(); error IpRoyaltyVault__NotRoyaltyPolicyLAP(); error IpRoyaltyVault__SnapshotIntervalTooShort(); diff --git a/contracts/modules/royalty/policies/IpRoyaltyVault.sol b/contracts/modules/royalty/policies/IpRoyaltyVault.sol index 3dd723760..f26655296 100644 --- a/contracts/modules/royalty/policies/IpRoyaltyVault.sol +++ b/contracts/modules/royalty/policies/IpRoyaltyVault.sol @@ -86,9 +86,6 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy uint32 unclaimedTokens, address ipIdAddress ) external initializer { - if (ipIdAddress == address(0)) revert Errors.IpRoyaltyVault__ZeroIpId(); - if (supply == 0) revert Errors.IpRoyaltyVault__ZeroSupply(); - IpRoyaltyVaultStorage storage $ = _getIpRoyaltyVaultStorage(); $.ipId = ipIdAddress; diff --git a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol index 443a5ee3d..62957f02f 100644 --- a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol +++ b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol @@ -106,10 +106,7 @@ contract TestArbitrationPolicySP is BaseTest { vm.expectRevert(Errors.ArbitrationPolicySP__ZeroAccessManager.selector); arbitrationPolicySP = ArbitrationPolicySP( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(ArbitrationPolicySP.initialize, address(0)) - ) + TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(ArbitrationPolicySP.initialize, address(0))) ); } diff --git a/test/foundry/modules/royalty/IpRoyaltyVault.t.sol b/test/foundry/modules/royalty/IpRoyaltyVault.t.sol index 4e3b91cdd..edf0e1f2a 100644 --- a/test/foundry/modules/royalty/IpRoyaltyVault.t.sol +++ b/test/foundry/modules/royalty/IpRoyaltyVault.t.sol @@ -28,6 +28,8 @@ contract TestIpRoyaltyVault is BaseTest { (, address IpRoyaltyVault2, , , ) = royaltyPolicyLAP.getRoyaltyData(address(2)); ipRoyaltyVault = IpRoyaltyVault(IpRoyaltyVault2); + + assertEq(ipRoyaltyVault.ipId(), address(2)); } function _setupMaxUniqueTree() internal { @@ -129,7 +131,6 @@ contract TestIpRoyaltyVault is BaseTest { vm.startPrank(address(licensingModule)); royaltyModule.onLinkToParents(address(100), address(royaltyPolicyLAP), parents, encodedLicenseData, ""); - //royaltyPolicyLAP.onLinkToParents(address(100), parents, encodedLicenseData, ""); } function test_IpRoyaltyVault_AddIpRoyaltyVaultTokens_NotRoyaltyPolicyLAP() public { @@ -137,6 +138,10 @@ contract TestIpRoyaltyVault is BaseTest { ipRoyaltyVault.addIpRoyaltyVaultTokens(address(0)); } + function test_IpRoyaltyVault_decimals() public { + assertEq(ipRoyaltyVault.decimals(), 6); + } + function test_IpRoyaltyVault_AddIpRoyaltyVaultTokens() public { vm.startPrank(address(royaltyPolicyLAP)); ipRoyaltyVault.addIpRoyaltyVaultTokens(address(1)); From 7487a09c3c39d434ba840675454a8bb7fbc49d47 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 16:09:45 +0300 Subject: [PATCH 17/31] format fix --- contracts/modules/dispute/policies/ArbitrationPolicySP.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol index 1a27ec7d3..666f4c8f7 100644 --- a/contracts/modules/dispute/policies/ArbitrationPolicySP.sol +++ b/contracts/modules/dispute/policies/ArbitrationPolicySP.sol @@ -45,7 +45,7 @@ contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UU DISPUTE_MODULE = disputeModule; PAYMENT_TOKEN = paymentToken; ARBITRATION_PRICE = arbitrationPrice; - + _disableInitializers(); } From 450edf1fc848b477301391c70f987bd941d6ce5e Mon Sep 17 00:00:00 2001 From: Spablob Date: Fri, 12 Apr 2024 08:30:04 +0300 Subject: [PATCH 18/31] delete e2e --- test/foundry/integration/e2e/e2e.t.sol | 405 ------------------------- 1 file changed, 405 deletions(-) delete mode 100644 test/foundry/integration/e2e/e2e.t.sol diff --git a/test/foundry/integration/e2e/e2e.t.sol b/test/foundry/integration/e2e/e2e.t.sol deleted file mode 100644 index 648d4bbc5..000000000 --- a/test/foundry/integration/e2e/e2e.t.sol +++ /dev/null @@ -1,405 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity 0.8.23; - -// external -import { Test } from "forge-std/Test.sol"; -import { ERC6551Registry } from "erc6551/ERC6551Registry.sol"; -import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; -import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; - -// contracts -import { AccessController } from "../../../../contracts/access/AccessController.sol"; -import { IPAccountImpl } from "../../../../contracts/IPAccountImpl.sol"; -import { IPAssetRegistry } from "../../../../contracts/registries/IPAssetRegistry.sol"; -import { ModuleRegistry } from "../../../../contracts/registries/ModuleRegistry.sol"; -import { LicenseRegistry } from "../../../../contracts/registries/LicenseRegistry.sol"; -import { RoyaltyModule } from "../../../../contracts/modules/royalty/RoyaltyModule.sol"; -import { RoyaltyPolicyLAP } from "../../../../contracts/modules/royalty/policies/RoyaltyPolicyLAP.sol"; -import { DisputeModule } from "../../../../contracts/modules/dispute/DisputeModule.sol"; -import { LicensingModule } from "../../../../contracts/modules/licensing/LicensingModule.sol"; -import { ArbitrationPolicySP } from "../../../../contracts/modules/dispute/policies/ArbitrationPolicySP.sol"; -import { IpRoyaltyVault } from "../../../../contracts/modules/royalty/policies/IpRoyaltyVault.sol"; -import { LicenseToken } from "../../../../contracts/LicenseToken.sol"; -import { DISPUTE_MODULE_KEY, LICENSING_MODULE_KEY, ROYALTY_MODULE_KEY } from "contracts/lib/modules/Module.sol"; -import { PILicenseTemplate } from "../../../../contracts/modules/licensing/PILicenseTemplate.sol"; -import { PILFlavors } from "../../../../contracts/lib/PILFlavors.sol"; -import { MockERC20 } from "../../mocks/token/MockERC20.sol"; -import { MockERC721 } from "../../mocks/token/MockERC721.sol"; -import { TestProxyHelper } from "../../utils/TestProxyHelper.sol"; - -contract e2e is Test { - MockERC20 erc20; - MockERC721 mockNft; - uint256 internal constant ARBITRATION_PRICE = 1000; - - address admin; - address alice; - address bob; - address charlie; - address dave; - address eve; - - AccessManager protocolAccessManager; - AccessController accessController; - ModuleRegistry moduleRegistry; - ERC6551Registry erc6551Registry; - IPAccountImpl ipAccountImpl; - IPAssetRegistry ipAssetRegistry; - LicenseRegistry licenseRegistry; - LicenseToken licenseToken; - RoyaltyModule royaltyModule; - DisputeModule disputeModule; - LicensingModule licensingModule; - PILicenseTemplate piLicenseTemplate; - RoyaltyPolicyLAP royaltyPolicyLAP; - - address ipId1; - address ipId2; - address ipId3; - address ipId6; - address ipId7; - - error ERC721NonexistentToken(uint256 tokenId); - - function setUp() public { - admin = vm.addr(1); - alice = vm.addr(2); - bob = vm.addr(3); - charlie = vm.addr(4); - dave = vm.addr(5); - eve = vm.addr(6); - - erc20 = new MockERC20(); - mockNft = new MockERC721("ape"); - protocolAccessManager = new AccessManager(admin); - - // Deploy contracts - address impl = address(new AccessController()); - accessController = AccessController( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(AccessController.initialize, address(protocolAccessManager)) - ) - ); - - impl = address(new ModuleRegistry()); - moduleRegistry = ModuleRegistry( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(AccessController.initialize, address(protocolAccessManager)) - ) - ); - - erc6551Registry = new ERC6551Registry(); - ipAccountImpl = new IPAccountImpl(address(accessController)); - ipAssetRegistry = new IPAssetRegistry(address(erc6551Registry), address(ipAccountImpl)); - - impl = address(new LicenseRegistry()); - licenseRegistry = LicenseRegistry( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(LicenseRegistry.initialize, (address(protocolAccessManager))) - ) - ); - - impl = address(new LicenseToken()); - licenseToken = LicenseToken( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(LicenseToken.initialize, (address(protocolAccessManager), "image_url")) - ) - ); - - impl = address(new RoyaltyModule()); - royaltyModule = RoyaltyModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(RoyaltyModule.initialize, (address(protocolAccessManager))) - ) - ); - vm.label(address(royaltyModule), "RoyaltyModule"); - - impl = address( - new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry)) - ); - disputeModule = DisputeModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(DisputeModule.initialize, (address(protocolAccessManager))) - ) - ); - - impl = address( - new LicensingModule( - address(accessController), - address(ipAssetRegistry), - address(royaltyModule), - address(licenseRegistry), - address(disputeModule), - address(licenseToken) - ) - ); - - licensingModule = LicensingModule( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(LicensingModule.initialize, (address(protocolAccessManager))) - ) - ); - - erc20 = new MockERC20(); - mockNft = new MockERC721("ape"); - - impl = address(new ArbitrationPolicySP(address(disputeModule), address(erc20), ARBITRATION_PRICE)); - ArbitrationPolicySP arbitrationPolicySP = ArbitrationPolicySP( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(ArbitrationPolicySP.initialize, (address(protocolAccessManager))) - ) - ); - - impl = address(new RoyaltyPolicyLAP(address(royaltyModule), address(licensingModule))); - royaltyPolicyLAP = RoyaltyPolicyLAP( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(RoyaltyPolicyLAP.initialize, (address(protocolAccessManager))) - ) - ); - - impl = address( - new PILicenseTemplate( - address(accessController), - address(ipAssetRegistry), - address(licenseRegistry), - address(royaltyModule), - address(licenseToken) - ) - ); - piLicenseTemplate = PILicenseTemplate( - TestProxyHelper.deployUUPSProxy( - impl, - abi.encodeCall(PILicenseTemplate.initialize, ("PIL", "PIL-metadata-url")) - ) - ); - - // Configure protocol - vm.startPrank(admin); - - address ipRoyaltyVaultImplementation = address( - new IpRoyaltyVault(address(royaltyPolicyLAP), address(disputeModule)) - ); - address ipRoyaltyVaultBeacon = address( - new UpgradeableBeacon(ipRoyaltyVaultImplementation, address(protocolAccessManager)) - ); - royaltyPolicyLAP.setIpRoyaltyVaultBeacon(ipRoyaltyVaultBeacon); - - royaltyPolicyLAP.setSnapshotInterval(7 days); - - accessController.setAddresses(address(ipAssetRegistry), address(moduleRegistry)); - - moduleRegistry.registerModule(DISPUTE_MODULE_KEY, address(disputeModule)); - moduleRegistry.registerModule(LICENSING_MODULE_KEY, address(licensingModule)); - moduleRegistry.registerModule(ROYALTY_MODULE_KEY, address(royaltyModule)); - - royaltyModule.setLicensingModule(address(licensingModule)); - royaltyModule.setDisputeModule(address(disputeModule)); - royaltyModule.whitelistRoyaltyToken(address(erc20), true); - royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP), true); - - disputeModule.whitelistDisputeTag("PLAGIARISM", true); - disputeModule.whitelistArbitrationPolicy(address(arbitrationPolicySP), true); - disputeModule.whitelistArbitrationRelayer(address(arbitrationPolicySP), admin, true); - disputeModule.setBaseArbitrationPolicy(address(arbitrationPolicySP)); - - licenseRegistry.setDisputeModule(address(disputeModule)); - licenseRegistry.setLicensingModule(address(licensingModule)); - licenseRegistry.registerLicenseTemplate(address(piLicenseTemplate)); - - licenseToken.setDisputeModule(address(disputeModule)); - licenseToken.setLicensingModule(address(licensingModule)); - - vm.stopPrank(); - } - - function test_e2e() public { - uint256 tokenId1 = mockNft.mint(alice); - uint256 tokenId2 = mockNft.mint(bob); - uint256 tokenId3 = mockNft.mint(charlie); - uint256 tokenId6 = mockNft.mint(dave); - uint256 tokenId7 = mockNft.mint(eve); - - ipId1 = ipAssetRegistry.register(address(mockNft), tokenId1); - ipId2 = ipAssetRegistry.register(address(mockNft), tokenId2); - ipId3 = ipAssetRegistry.register(address(mockNft), tokenId3); - ipId6 = ipAssetRegistry.register(address(mockNft), tokenId6); - ipId7 = ipAssetRegistry.register(address(mockNft), tokenId7); - - // register license terms - uint256 lcId1 = piLicenseTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); - assertEq(lcId1, 1); - - uint256 lcId2 = piLicenseTemplate.registerLicenseTerms( - PILFlavors.commercialRemix(100, 10, address(royaltyPolicyLAP), address(erc20)) - ); - assertEq(lcId2, 2); - assertEq( - piLicenseTemplate.getLicenseTermsId( - PILFlavors.commercialRemix(100, 10, address(royaltyPolicyLAP), address(erc20)) - ), - 2 - ); - assertTrue(piLicenseTemplate.exists(2)); - - assertTrue(piLicenseTemplate.exists(lcId2)); - - // attach licenses - vm.startPrank(alice); - licensingModule.attachLicenseTerms(ipId1, address(piLicenseTemplate), 1); - - assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(piLicenseTemplate), 1), true); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 1); - - (address attachedTemplate, uint256 attachedId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); - assertEq(attachedTemplate, address(piLicenseTemplate)); - assertEq(attachedId, 1); - - licensingModule.attachLicenseTerms(ipId1, address(piLicenseTemplate), 2); - - assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(piLicenseTemplate), 2), true); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 2); - - (attachedTemplate, attachedId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 1); - assertEq(attachedTemplate, address(piLicenseTemplate)); - assertEq(attachedId, 2); - vm.stopPrank(); - - // register derivative directly - vm.startPrank(bob); - address[] memory parentIpIds = new address[](1); - uint256[] memory licenseTermsIds = new uint256[](1); - parentIpIds[0] = ipId1; - licenseTermsIds[0] = 1; - - licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(piLicenseTemplate), ""); - - assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId2, address(piLicenseTemplate), 1), true); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId2), 1); - assertEq(licenseRegistry.isDerivativeIp(ipId2), true); - assertEq(licenseRegistry.hasDerivativeIps(ipId2), false); - assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); - assertEq(licenseRegistry.isDerivativeIp(ipId1), false); - assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 1); - assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 0); - assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); - assertEq(licenseRegistry.getParentIp(ipId2, 0), ipId1); - assertEq(licenseRegistry.getParentIpCount(ipId2), 1); - vm.stopPrank(); - - // mint license token - vm.startPrank(charlie); - uint256 lcTokenId = licensingModule.mintLicenseTokens( - ipId1, - address(piLicenseTemplate), - 1, - 1, - address(charlie), - "" - ); - assertEq(licenseToken.ownerOf(lcTokenId), charlie); - assertEq(licenseToken.getLicenseTermsId(lcTokenId), 1); - assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(piLicenseTemplate)); - assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); - assertEq(licenseToken.getExpirationTime(lcTokenId), 0); - assertEq(licenseToken.totalMintedTokens(), 1); - - // register derivative with license tokens - uint256[] memory licenseTokens = new uint256[](1); - licenseTokens[0] = lcTokenId; - - licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); - - assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId3, address(piLicenseTemplate), 1), true); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId3), 1); - assertEq(licenseRegistry.isDerivativeIp(ipId3), true); - assertEq(licenseRegistry.hasDerivativeIps(ipId3), false); - assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); - assertEq(licenseRegistry.isDerivativeIp(ipId1), false); - assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 2); - assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 0); - assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); - assertEq(licenseRegistry.getDerivativeIp(ipId1, 1), ipId3); - assertEq(licenseRegistry.getParentIp(ipId3, 0), ipId1); - assertEq(licenseRegistry.getParentIpCount(ipId3), 1); - - vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId)); - assertEq(licenseToken.ownerOf(lcTokenId), address(0)); - assertEq(licenseToken.totalMintedTokens(), 1); - vm.stopPrank(); - - // mint license token with payments - vm.startPrank(dave); - erc20.mint(dave, 1000); - erc20.approve(address(royaltyPolicyLAP), 100); - - lcTokenId = licensingModule.mintLicenseTokens(ipId1, address(piLicenseTemplate), 2, 1, address(dave), ""); - - assertEq(licenseToken.ownerOf(lcTokenId), dave); - assertEq(licenseToken.getLicenseTermsId(lcTokenId), 2); - assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(piLicenseTemplate)); - assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); - assertEq(licenseToken.getExpirationTime(lcTokenId), 0); - assertEq(licenseToken.totalMintedTokens(), 2); - assertEq(erc20.balanceOf(dave), 900); - - // register derivative with license tokens - licenseTokens = new uint256[](1); - licenseTokens[0] = lcTokenId; - - licensingModule.registerDerivativeWithLicenseTokens(ipId6, licenseTokens, ""); - - assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId6, address(piLicenseTemplate), 2), true); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId6), 1); - assertEq(licenseRegistry.isDerivativeIp(ipId6), true); - assertEq(licenseRegistry.hasDerivativeIps(ipId6), false); - assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); - assertEq(licenseRegistry.isDerivativeIp(ipId1), false); - assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 3); - assertEq(licenseRegistry.getDerivativeIpCount(ipId6), 0); - assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); - assertEq(licenseRegistry.getDerivativeIp(ipId1, 1), ipId3); - assertEq(licenseRegistry.getDerivativeIp(ipId1, 2), ipId6); - assertEq(licenseRegistry.getParentIp(ipId6, 0), ipId1); - assertEq(licenseRegistry.getParentIpCount(ipId6), 1); - - vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId)); - assertEq(licenseToken.ownerOf(lcTokenId), address(0)); - assertEq(licenseToken.totalMintedTokens(), 2); - vm.stopPrank(); - - // register derivative directly with payments - vm.startPrank(eve); - erc20.mint(eve, 1000); - erc20.approve(address(royaltyPolicyLAP), 100); - parentIpIds = new address[](1); - licenseTermsIds = new uint256[](1); - parentIpIds[0] = ipId1; - licenseTermsIds[0] = 2; - - licensingModule.registerDerivative(ipId7, parentIpIds, licenseTermsIds, address(piLicenseTemplate), ""); - - assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId7, address(piLicenseTemplate), 2), true); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId7), 1); - assertEq(licenseRegistry.isDerivativeIp(ipId7), true); - assertEq(licenseRegistry.hasDerivativeIps(ipId7), false); - assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); - assertEq(licenseRegistry.isDerivativeIp(ipId1), false); - assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 4); - assertEq(licenseRegistry.getDerivativeIpCount(ipId7), 0); - assertEq(licenseRegistry.getDerivativeIp(ipId1, 3), ipId7); - assertEq(licenseRegistry.getParentIp(ipId7, 0), ipId1); - assertEq(licenseRegistry.getParentIpCount(ipId7), 1); - assertEq(licenseToken.totalMintedTokens(), 2); - assertEq(erc20.balanceOf(eve), 900); - vm.stopPrank(); - } -} From 3c6de2045aa4bc675a3ff7a1b92816bb4f7bcc2c Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Thu, 11 Apr 2024 15:50:46 -0500 Subject: [PATCH 19/31] fix(registry): Abstract for IPAccountRegistry (#56) --- contracts/registries/IPAccountRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/registries/IPAccountRegistry.sol b/contracts/registries/IPAccountRegistry.sol index 853024a8b..d96809867 100644 --- a/contracts/registries/IPAccountRegistry.sol +++ b/contracts/registries/IPAccountRegistry.sol @@ -9,7 +9,7 @@ import { Errors } from "../lib/Errors.sol"; /// @title IPAccountRegistry /// @notice This contract is responsible for managing the registration and tracking of IP Accounts. /// It leverages a public ERC6551 registry to deploy IPAccount contracts. -contract IPAccountRegistry is IIPAccountRegistry { +abstract contract IPAccountRegistry is IIPAccountRegistry { /// @notice Returns the IPAccount implementation address address public immutable IP_ACCOUNT_IMPL; From df15b0990eef7eb22e766634db3b64fa1876b1e8 Mon Sep 17 00:00:00 2001 From: kingster-will <83567446+kingster-will@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:50:18 -0700 Subject: [PATCH 20/31] Unit Tests and Enhancements for Licensing Components (#64) * Add licensing tests * Add More tests --- contracts/LicenseToken.sol | 8 +- .../modules/licensing/IPILicenseTemplate.sol | 5 + contracts/lib/Errors.sol | 21 +- .../BaseLicenseTemplateUpgradeable.sol | 30 +- .../modules/licensing/LicensingModule.sol | 4 +- .../modules/licensing/PILicenseTemplate.sol | 26 +- .../LicensorApprovalChecker.sol | 45 +- contracts/registries/LicenseRegistry.sol | 23 +- deploy-out/deployment-11155111.json | 2 +- script/foundry/utils/DeployHelper.sol | 3 +- .../licensing/LicensingIntegration.t.sol | 402 +++++ .../mocks/module/MockLicenseTemplate.sol | 145 ++ .../modules/licensing/LicensingModule.t.sol | 1309 +++++++++++++---- .../modules/licensing/PILicenseTemplate.t.sol | 526 +++++++ test/foundry/registries/LicenseRegistry.t.sol | 268 ++++ 15 files changed, 2492 insertions(+), 325 deletions(-) create mode 100644 test/foundry/integration/flows/licensing/LicensingIntegration.t.sol create mode 100644 test/foundry/mocks/module/MockLicenseTemplate.sol create mode 100644 test/foundry/modules/licensing/PILicenseTemplate.t.sol create mode 100644 test/foundry/registries/LicenseRegistry.t.sol diff --git a/contracts/LicenseToken.sol b/contracts/LicenseToken.sol index 034176e70..ae5554e73 100644 --- a/contracts/LicenseToken.sol +++ b/contracts/LicenseToken.sol @@ -22,7 +22,7 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag /// @notice Emitted for metadata updates, per EIP-4906 event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId); - /// @dev Storage of the LicenseToken + /// @dev Storage structure for the LicenseToken /// @custom:storage-location erc7201:story-protocol.LicenseToken struct LicenseTokenStorage { string imageUrl; @@ -32,10 +32,9 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag mapping(uint256 tokenId => LicenseTokenMetadata) licenseTokenMetadatas; } - // TODO: update the storage location // keccak256(abi.encode(uint256(keccak256("story-protocol.LicenseToken")) - 1)) & ~bytes32(uint256(0xff)); bytes32 private constant LicenseTokenStorageLocation = - 0x5ed898e10dedf257f39672a55146f3fecade9da16f4ff022557924a10d60a900; + 0x62a0d75e37bea0c3e666dc72a74112fc6af15ce635719127e380d8ca1e555d00; modifier onlyLicensingModule() { if (msg.sender != address(_getLicenseTokenStorage().licensingModule)) { @@ -319,7 +318,8 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag // Upgrades related // //////////////////////////////////////////////////////////////////////////// - function _getLicenseTokenStorage() internal pure returns (LicenseTokenStorage storage $) { + /// @dev Returns the storage struct of LicenseToken. + function _getLicenseTokenStorage() private pure returns (LicenseTokenStorage storage $) { assembly { $.slot := LicenseTokenStorageLocation } diff --git a/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol b/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol index 76fb3b943..299a8edd7 100644 --- a/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol +++ b/contracts/interfaces/modules/licensing/IPILicenseTemplate.sol @@ -57,4 +57,9 @@ interface IPILicenseTemplate is ILicenseTemplate { /// @param terms The PILTerms to get the ID for. /// @return selectedLicenseTermsId The ID of the given license terms. function getLicenseTermsId(PILTerms calldata terms) external view returns (uint256 selectedLicenseTermsId); + + /// @notice Gets license terms of the given ID. + /// @param selectedLicenseTermsId The ID of the license terms. + /// @return terms The PILTerms associate with the given ID. + function getLicenseTerms(uint256 selectedLicenseTermsId) external view returns (PILTerms memory terms); } diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index f86d7cbcf..1632a1ae6 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -123,6 +123,7 @@ library Errors { error LicenseRegistry__ParentIpExpired(address ipId); error LicenseRegistry__LicenseTermsNotExists(address licenseTemplate, uint256 licenseTermsId); error LicenseRegistry__ParentIpHasNoLicenseTerms(address ipId, uint256 licenseTermsId); + error LicenseRegistry__LicensorIpHasNoLicenseTerms(address ipId, address licenseTemplate, uint256 licenseTermsId); error LicenseRegistry__NoParentIp(); error LicenseRegistry__DerivativeIpAlreadyHasLicense(address childIpId); error LicenseRegistry__DerivativeAlreadyRegistered(address childIpId); @@ -130,6 +131,8 @@ library Errors { error LicenseRegistry__DerivativeIsParent(address ipId); error LicenseRegistry__ParentIpUnmachedLicenseTemplate(address ipId, address licenseTemplate); error LicenseRegistry__IndexOutOfBounds(address ipId, uint256 index, uint256 length); + error LicenseRegistry__LicenseTermsAlreadyAttached(address ipId, address licenseTemplate, uint256 licenseTermsId); + error LicenseRegistry__UnmatchedLicenseTemplate(address ipId, address licenseTemplate, address newLicenseTemplate); //////////////////////////////////////////////////////////////////////////// // LicenseToken // @@ -204,19 +207,11 @@ library Errors { error LicensingModule__LicenseNotCompatibleForDerivative(address childIpId); error LicensingModule__NoLicenseToken(); error LicensingModule__LicenseTokenNotCompatibleForDerivative(address childIpId, uint256[] licenseTokenIds); - - //////////////////////////////////////////////////////////////////////////// - // BasePolicyFrameworkManager // - //////////////////////////////////////////////////////////////////////////// - - error BasePolicyFrameworkManager__CallerNotLicensingModule(); - - //////////////////////////////////////////////////////////////////////////// - // PolicyFrameworkManager // - //////////////////////////////////////////////////////////////////////////// - - error PolicyFrameworkManager__GettingPolicyWrongFramework(); - error PolicyFrameworkManager__CommercializerCheckerDoesNotSupportHook(address commercializer); + error LicensingModule__LicenseDenyMintLicenseToken( + address licenseTemplate, + uint256 licenseTermsId, + address licensorIpId + ); //////////////////////////////////////////////////////////////////////////// // LicensorApprovalChecker // diff --git a/contracts/modules/licensing/BaseLicenseTemplateUpgradeable.sol b/contracts/modules/licensing/BaseLicenseTemplateUpgradeable.sol index 1e646197c..219d79afb 100644 --- a/contracts/modules/licensing/BaseLicenseTemplateUpgradeable.sol +++ b/contracts/modules/licensing/BaseLicenseTemplateUpgradeable.sol @@ -9,16 +9,17 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I import { ILicenseTemplate } from "../../interfaces/modules/licensing/ILicenseTemplate.sol"; abstract contract BaseLicenseTemplateUpgradeable is ILicenseTemplate, ERC165, Initializable { - /// @custom:storage-location erc7201:story-protocol.BaseLicenseTemplate - struct BaseLicenseTemplateStorage { + /// @dev Storage structure for the BaseLicenseTemplateUpgradeable + /// @custom:storage-location erc7201:story-protocol.BaseLicenseTemplateUpgradeable + struct BaseLicenseTemplateUpgradeableStorage { string name; string metadataURI; } - // keccak256(abi.encode(uint256(keccak256("story-protocol.BaseLicenseTemplate")) - 1)) - // & ~bytes32(uint256(0xff)); - bytes32 private constant BaseLicenseTemplateStorageLocation = - 0xa55803740ac9329334ad7b6cde0ec056cc3ba32125b59c579552512bed001f00; + // keccak256(abi.encode(uint256(keccak256("story-protocol.BaseLicenseTemplateUpgradeable")) - 1)) & + // ~bytes32(uint256(0xff)); + bytes32 private constant BaseLicenseTemplateUpgradeableStorageLocation = + 0x96c2f019b095cfe7c4d1f26aa9d2741961fe73294777688374a3299707c2fb00; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -29,18 +30,18 @@ abstract contract BaseLicenseTemplateUpgradeable is ILicenseTemplate, ERC165, In /// @param _name The name of the license template /// @param _metadataURI The URL to the off chain metadata function __BaseLicenseTemplate_init(string memory _name, string memory _metadataURI) internal onlyInitializing { - _getBaseLicenseTemplateStorage().name = _name; - _getBaseLicenseTemplateStorage().metadataURI = _metadataURI; + _getBaseLicenseTemplateUpgradeableStorage().name = _name; + _getBaseLicenseTemplateUpgradeableStorage().metadataURI = _metadataURI; } /// @notice Returns the name of the license template function name() public view override returns (string memory) { - return _getBaseLicenseTemplateStorage().name; + return _getBaseLicenseTemplateUpgradeableStorage().name; } /// @notice Returns the URL to the off chain metadata function getMetadataURI() public view override returns (string memory) { - return _getBaseLicenseTemplateStorage().metadataURI; + return _getBaseLicenseTemplateUpgradeableStorage().metadataURI; } /// @notice IERC165 interface support. @@ -48,9 +49,14 @@ abstract contract BaseLicenseTemplateUpgradeable is ILicenseTemplate, ERC165, In return interfaceId == type(ILicenseTemplate).interfaceId || super.supportsInterface(interfaceId); } - function _getBaseLicenseTemplateStorage() internal pure returns (BaseLicenseTemplateStorage storage $) { + /// @dev Returns the storage struct of BaseLicenseTemplateUpgradeable. + function _getBaseLicenseTemplateUpgradeableStorage() + private + pure + returns (BaseLicenseTemplateUpgradeableStorage storage $) + { assembly { - $.slot := BaseLicenseTemplateStorageLocation + $.slot := BaseLicenseTemplateUpgradeableStorageLocation } } } diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index 5e25ac574..503f0288c 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -167,7 +167,9 @@ contract LicensingModule is _payMintingFee(licensorIpId, licenseTemplate, licenseTermsId, amount, royaltyContext, mlc); - ILicenseTemplate(licenseTemplate).verifyMintLicenseToken(licenseTermsId, receiver, licensorIpId, amount); + if (!ILicenseTemplate(licenseTemplate).verifyMintLicenseToken(licenseTermsId, receiver, licensorIpId, amount)) { + revert Errors.LicensingModule__LicenseDenyMintLicenseToken(licenseTemplate, licenseTermsId, licensorIpId); + } startLicenseTokenId = LICENSE_NFT.mintLicenseTokens( licensorIpId, diff --git a/contracts/modules/licensing/PILicenseTemplate.sol b/contracts/modules/licensing/PILicenseTemplate.sol index 206dfc5f1..581280154 100644 --- a/contracts/modules/licensing/PILicenseTemplate.sol +++ b/contracts/modules/licensing/PILicenseTemplate.sol @@ -27,6 +27,7 @@ contract PILicenseTemplate is using ERC165Checker for address; using Strings for *; + /// @dev Storage structure for the PILicenseTemplate /// @custom:storage-location erc7201:story-protocol.PILicenseTemplate struct PILicenseTemplateStorage { mapping(uint256 licenseTermsId => PILTerms) licenseTerms; @@ -39,20 +40,17 @@ contract PILicenseTemplate is /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IRoyaltyModule public immutable ROYALTY_MODULE; - // TODO: update storage location - // keccak256(abi.encode(uint256(keccak256("story-protocol.BaseLicenseTemplate")) - 1)) - // & ~bytes32(uint256(0xff)); + // keccak256(abi.encode(uint256(keccak256("story-protocol.PILicenseTemplate")) - 1)) & ~bytes32(uint256(0xff)); bytes32 private constant PILicenseTemplateStorageLocation = - 0xa55803740ac9329334ad7b6cde0ec056cc3ba32125b59c579552512bed001f00; + 0xc6c6991297bc120d0383f0017fab72b8ca34fd4849ed6478dbaac67a33c3a700; /// @custom:oz-upgrades-unsafe-allow constructor constructor( address accessController, address ipAccountRegistry, address licenseRegistry, - address royaltyModule, - address licenseToken - ) LicensorApprovalChecker(accessController, ipAccountRegistry, licenseToken) { + address royaltyModule + ) LicensorApprovalChecker(accessController, ipAccountRegistry) { LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry); ROYALTY_MODULE = IRoyaltyModule(royaltyModule); _disableInitializers(); @@ -234,7 +232,7 @@ contract PILicenseTemplate is uint expireTime = _getExpireTime(licenseTermsIds[0], start); for (uint i = 1; i < licenseTermsIds.length; i++) { uint newExpireTime = _getExpireTime(licenseTermsIds[i], start); - if (newExpireTime < expireTime) { + if (newExpireTime < expireTime || expireTime == 0) { expireTime = newExpireTime; } } @@ -258,6 +256,14 @@ contract PILicenseTemplate is return $.hashedLicenseTerms[licenseTermsHash]; } + /// @notice Gets license terms of the given ID. + /// @param selectedLicenseTermsId The ID of the license terms. + /// @return terms The PILTerms associate with the given ID. + function getLicenseTerms(uint256 selectedLicenseTermsId) external view returns (PILTerms memory terms) { + PILicenseTemplateStorage storage $ = _getPILicenseTemplateStorage(); + return $.licenseTerms[selectedLicenseTermsId]; + } + /// @notice Returns the total number of registered license terms. /// @return The total number of registered license terms. function totalRegisteredLicenseTerms() external view returns (uint256) { @@ -289,7 +295,7 @@ contract PILicenseTemplate is terms.expiration == 0 ? "never" : terms.expiration.toString(), '"},', '{"trait_type": "Currency", "value": "', - terms.currency == address(0) ? "Native Token" : terms.currency.toHexString(), + terms.currency.toHexString(), '"},', // Skip transferable, it's already added in the common attributes by the LicenseRegistry. _policyCommercialTraitsToJson(terms), @@ -419,7 +425,7 @@ contract PILicenseTemplate is // If the policy defines the licensor must approve derivatives, check if the // derivative is approved by the licensor - if (terms.derivativesApproval && !isDerivativeApproved(licenseTermsId, childIpId)) { + if (terms.derivativesApproval && !isDerivativeApproved(parentIpId, licenseTermsId, childIpId)) { return false; } // Check if the commercializerChecker allows the link diff --git a/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol b/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol index d01621b83..a221b98e8 100644 --- a/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol +++ b/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.23; import { AccessControlled } from "../../../access/AccessControlled.sol"; -import { ILicenseToken } from "../../../interfaces/ILicenseToken.sol"; - import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; /// @title LicensorApprovalChecker @@ -11,12 +9,12 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I /// licensing terms like "Derivatives With Approval" in PIL. abstract contract LicensorApprovalChecker is AccessControlled, Initializable { /// @notice Emits when a derivative IP account is approved by the parentIp. - /// @param licenseTokenId The ID of the license waiting for approval + /// @param licenseTermsId The ID of the license waiting for approval /// @param ipId The ID of the derivative IP to be approved /// @param caller The executor of the approval /// @param approved Result of the approval event DerivativeApproved( - uint256 indexed licenseTokenId, + uint256 indexed licenseTermsId, address indexed ipId, address indexed caller, bool approved @@ -30,10 +28,6 @@ abstract contract LicensorApprovalChecker is AccessControlled, Initializable { mapping(uint256 => mapping(address => mapping(address => bool))) approvals; } - /// @notice Returns the licenseToken address - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - ILicenseToken public immutable LICENSE_NFT; - // keccak256(abi.encode(uint256(keccak256("story-protocol.LicensorApprovalChecker")) - 1)) // & ~bytes32(uint256(0xff)); bytes32 private constant LicensorApprovalCheckerStorageLocation = @@ -42,50 +36,49 @@ abstract contract LicensorApprovalChecker is AccessControlled, Initializable { /// @notice Constructor function /// @param accessController The address of the AccessController contract /// @param ipAccountRegistry The address of the IPAccountRegistry contract - /// @param licenseToken The address of the LicenseRegistry contract /// @custom:oz-upgrades-unsafe-allow constructor constructor( address accessController, - address ipAccountRegistry, - address licenseToken - ) AccessControlled(accessController, ipAccountRegistry) { - LICENSE_NFT = ILicenseToken(licenseToken); - } + address ipAccountRegistry + ) AccessControlled(accessController, ipAccountRegistry) {} /// @notice Approves or disapproves a derivative IP account. - /// @param licenseTokenId The ID of the license waiting for approval + /// @param parentIpId The ID of the parent IP grant the approval + /// @param licenseTermsId The ID of the license waiting for approval /// @param childIpId The ID of the derivative IP to be approved /// @param approved Result of the approval - function setApproval(uint256 licenseTokenId, address childIpId, bool approved) external { - address parentIpId = LICENSE_NFT.getLicensorIpId(licenseTokenId); - _setApproval(parentIpId, licenseTokenId, childIpId, approved); + function setApproval(address parentIpId, uint256 licenseTermsId, address childIpId, bool approved) external { + _setApproval(parentIpId, licenseTermsId, childIpId, approved); } /// @notice Checks if a derivative IP account is approved by the parent. - /// @param licenseTokenId The ID of the license NFT issued from a policy of the parent + /// @param licenseTermsId The ID of the license NFT issued from a policy of the parent /// @param childIpId The ID of the derivative IP to be approved /// @return approved True if the derivative IP account using the license is approved - function isDerivativeApproved(uint256 licenseTokenId, address childIpId) public view returns (bool) { - address parentIpId = LICENSE_NFT.getLicensorIpId(licenseTokenId); + function isDerivativeApproved( + address parentIpId, + uint256 licenseTermsId, + address childIpId + ) public view returns (bool) { LicensorApprovalCheckerStorage storage $ = _getLicensorApprovalCheckerStorage(); - return $.approvals[licenseTokenId][parentIpId][childIpId]; + return $.approvals[licenseTermsId][parentIpId][childIpId]; } /// @notice Sets the approval for a derivative IP account. /// @dev This function is only callable by the parent IP account. /// @param parentIpId The ID of the parent IP account - /// @param licenseTokenId The ID of the license waiting for approval + /// @param licenseTermsId The ID of the license waiting for approval /// @param childIpId The ID of the derivative IP to be approved /// @param approved Result of the approval function _setApproval( address parentIpId, - uint256 licenseTokenId, + uint256 licenseTermsId, address childIpId, bool approved ) internal verifyPermission(parentIpId) { LicensorApprovalCheckerStorage storage $ = _getLicensorApprovalCheckerStorage(); - $.approvals[licenseTokenId][parentIpId][childIpId] = approved; - emit DerivativeApproved(licenseTokenId, parentIpId, msg.sender, approved); + $.approvals[licenseTermsId][parentIpId][childIpId] = approved; + emit DerivativeApproved(licenseTermsId, parentIpId, msg.sender, approved); } /// @dev Returns the storage struct of LicensorApprovalChecker. diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index e0dcc84ec..f18ba8efd 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -43,6 +43,7 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr /// @param mintingLicenseConfigs Mapping of minting license configs to a licenseTerms of an IP /// @param mintingLicenseConfigsForIp Mapping of minting license configs to an IP, /// the config will apply to all licenses under the IP + /// @dev Storage structure for the LicenseRegistry /// @custom:storage-location erc7201:story-protocol.LicenseRegistry struct LicenseRegistryStorage { ILicensingModule licensingModule; @@ -58,7 +59,6 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr mapping(address ipId => Licensing.MintingLicenseConfig mintingLicenseConfig) mintingLicenseConfigsForIp; } - // TODO: update the storage location // keccak256(abi.encode(uint256(keccak256("story-protocol.LicenseRegistry")) - 1)) & ~bytes32(uint256(0xff)); bytes32 private constant LicenseRegistryStorageLocation = 0x5ed898e10dedf257f39672a55146f3fecade9da16f4ff022557924a10d60a900; @@ -193,14 +193,23 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr revert Errors.LicensingModule__LicenseTermsNotFound(licenseTemplate, licenseTermsId); } + if (_isExpiredNow(ipId)) { + revert Errors.LicenseRegistry__IpExpired(ipId); + } + + if (_hasIpAttachedLicenseTerms(ipId, licenseTemplate, licenseTermsId)) { + revert Errors.LicenseRegistry__LicenseTermsAlreadyAttached(ipId, licenseTemplate, licenseTermsId); + } + if (_isDerivativeIp(ipId)) { revert Errors.LicensingModule__DerivativesCannotAddLicenseTerms(); } LicenseRegistryStorage storage $ = _getLicenseRegistryStorage(); - if (_isExpiredNow(ipId)) { - revert Errors.LicenseRegistry__IpExpired(ipId); + if ($.licenseTemplates[ipId] != address(0) && $.licenseTemplates[ipId] != licenseTemplate) { + revert Errors.LicenseRegistry__UnmatchedLicenseTemplate(ipId, $.licenseTemplates[ipId], licenseTemplate); } + $.licenseTemplates[ipId] = licenseTemplate; $.attachedLicenseTerms[ipId].add(licenseTermsId); } @@ -220,12 +229,12 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr revert Errors.LicenseRegistry__NoParentIp(); } LicenseRegistryStorage storage $ = _getLicenseRegistryStorage(); - if ($.attachedLicenseTerms[childIpId].length() > 0) { - revert Errors.LicenseRegistry__DerivativeIpAlreadyHasLicense(childIpId); - } if ($.parentIps[childIpId].length() > 0) { revert Errors.LicenseRegistry__DerivativeAlreadyRegistered(childIpId); } + if ($.attachedLicenseTerms[childIpId].length() > 0) { + revert Errors.LicenseRegistry__DerivativeIpAlreadyHasLicense(childIpId); + } for (uint256 i = 0; i < parentIpIds.length; i++) { _verifyDerivativeFromParent(parentIpIds[i], childIpId, licenseTemplate, licenseTermsIds[i]); @@ -262,7 +271,7 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr revert Errors.LicenseRegistry__LicenseTermsNotExists(licenseTemplate, licenseTermsId); } } else if (!_hasIpAttachedLicenseTerms(licensorIpId, licenseTemplate, licenseTermsId)) { - revert Errors.LicenseRegistry__ParentIpHasNoLicenseTerms(licensorIpId, licenseTermsId); + revert Errors.LicenseRegistry__LicensorIpHasNoLicenseTerms(licensorIpId, licenseTemplate, licenseTermsId); } return _getMintingLicenseConfig(licensorIpId, licenseTemplate, licenseTermsId); } diff --git a/deploy-out/deployment-11155111.json b/deploy-out/deployment-11155111.json index 172cd1fb8..8418b1e48 100644 --- a/deploy-out/deployment-11155111.json +++ b/deploy-out/deployment-11155111.json @@ -13,7 +13,7 @@ "MockERC721": "0x5eB725102781e57E2FF93c74822A7e22Ae6b8810", "MockTokenGatedHook": "0x0f5900B48245F29D10a3cEE68B3E66e01F416E89", "ModuleRegistry": "0xECaaD3dCB6bD1f6D92b308C115c0698952A65769", - "PILPolicyFrameworkManager": "0x4DbFB22d5Fe7f05C1C0a2977B7dB9E688E396016", + "PILicenseTemplate": "0x4DbFB22d5Fe7f05C1C0a2977B7dB9E688E396016", "RoyaltyModule": "0xaac2E226b2Fa900cfEbC9d4bcdb9267De5E13573", "RoyaltyPolicyLAP": "0x8524E19E9e64B8ba299c7821BD7968d4E8b670A8", "TokenWithdrawalModule": "0xCbCdCf569383c0C743c1E79Fdc1F1d7442D9cE63" diff --git a/script/foundry/utils/DeployHelper.sol b/script/foundry/utils/DeployHelper.sol index 0f7f629b7..83a6eee82 100644 --- a/script/foundry/utils/DeployHelper.sol +++ b/script/foundry/utils/DeployHelper.sol @@ -310,8 +310,7 @@ contract DeployHelper is Script, BroadcastManager, JsonDeploymentHandler, Storag address(accessController), address(ipAccountRegistry), address(licenseRegistry), - address(royaltyModule), - address(licenseToken) + address(royaltyModule) ) ); pilTemplate = PILicenseTemplate( diff --git a/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol b/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol new file mode 100644 index 000000000..2746c02a3 --- /dev/null +++ b/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.23; + +// external +import { Test } from "forge-std/Test.sol"; +import { ERC6551Registry } from "erc6551/ERC6551Registry.sol"; +import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; + +// contracts +import { AccessController } from "../../../../../contracts/access/AccessController.sol"; +import { IPAccountImpl } from "../../../../../contracts/IPAccountImpl.sol"; +import { IPAssetRegistry } from "../../../../../contracts/registries/IPAssetRegistry.sol"; +import { ModuleRegistry } from "../../../../../contracts/registries/ModuleRegistry.sol"; +import { LicenseRegistry } from "../../../../../contracts/registries/LicenseRegistry.sol"; +import { RoyaltyModule } from "../../../../../contracts/modules/royalty/RoyaltyModule.sol"; +import { RoyaltyPolicyLAP } from "../../../../../contracts/modules/royalty/policies/RoyaltyPolicyLAP.sol"; +import { DisputeModule } from "../../../../../contracts/modules/dispute/DisputeModule.sol"; +import { LicensingModule } from "../../../../../contracts/modules/licensing/LicensingModule.sol"; +import { ArbitrationPolicySP } from "../../../../../contracts/modules/dispute/policies/ArbitrationPolicySP.sol"; +import { IpRoyaltyVault } from "../../../../../contracts/modules/royalty/policies/IpRoyaltyVault.sol"; +import { LicenseToken } from "../../../../../contracts/LicenseToken.sol"; +import { DISPUTE_MODULE_KEY, LICENSING_MODULE_KEY, ROYALTY_MODULE_KEY } from "contracts/lib/modules/Module.sol"; +import { PILicenseTemplate } from "../../../../../contracts/modules/licensing/PILicenseTemplate.sol"; +import { PILFlavors } from "../../../../../contracts/lib/PILFlavors.sol"; +import { MockERC20 } from "../../../mocks/token/MockERC20.sol"; +import { MockERC721 } from "../../../mocks/token/MockERC721.sol"; +import { TestProxyHelper } from "../../../utils/TestProxyHelper.sol"; + +contract e2e is Test { + MockERC20 erc20; + MockERC721 mockNft; + uint256 internal constant ARBITRATION_PRICE = 1000; + + address admin; + address alice; + address bob; + address charlie; + address dave; + address eve; + + AccessManager protocolAccessManager; + AccessController accessController; + ModuleRegistry moduleRegistry; + ERC6551Registry erc6551Registry; + IPAccountImpl ipAccountImpl; + IPAssetRegistry ipAssetRegistry; + LicenseRegistry licenseRegistry; + LicenseToken licenseToken; + RoyaltyModule royaltyModule; + DisputeModule disputeModule; + LicensingModule licensingModule; + PILicenseTemplate piLicenseTemplate; + RoyaltyPolicyLAP royaltyPolicyLAP; + + address ipId1; + address ipId2; + address ipId3; + address ipId6; + address ipId7; + + error ERC721NonexistentToken(uint256 tokenId); + + function setUp() public { + admin = vm.addr(1); + alice = vm.addr(2); + bob = vm.addr(3); + charlie = vm.addr(4); + dave = vm.addr(5); + eve = vm.addr(6); + + erc20 = new MockERC20(); + mockNft = new MockERC721("ape"); + protocolAccessManager = new AccessManager(admin); + + // Deploy contracts + address impl = address(new AccessController()); + accessController = AccessController( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(AccessController.initialize, address(protocolAccessManager)) + ) + ); + + impl = address(new ModuleRegistry()); + moduleRegistry = ModuleRegistry( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(AccessController.initialize, address(protocolAccessManager)) + ) + ); + + erc6551Registry = new ERC6551Registry(); + ipAccountImpl = new IPAccountImpl(address(accessController)); + ipAssetRegistry = new IPAssetRegistry(address(erc6551Registry), address(ipAccountImpl)); + + impl = address(new LicenseRegistry()); + licenseRegistry = LicenseRegistry( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(LicenseRegistry.initialize, (address(protocolAccessManager))) + ) + ); + + impl = address(new LicenseToken()); + licenseToken = LicenseToken( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(LicenseToken.initialize, (address(protocolAccessManager), "image_url")) + ) + ); + + impl = address(new RoyaltyModule()); + royaltyModule = RoyaltyModule( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(RoyaltyModule.initialize, (address(protocolAccessManager))) + ) + ); + vm.label(address(royaltyModule), "RoyaltyModule"); + + impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry))); + disputeModule = DisputeModule( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(DisputeModule.initialize, (address(protocolAccessManager))) + ) + ); + + impl = address( + new LicensingModule( + address(accessController), + address(ipAssetRegistry), + address(royaltyModule), + address(licenseRegistry), + address(disputeModule), + address(licenseToken) + ) + ); + + licensingModule = LicensingModule( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(LicensingModule.initialize, (address(protocolAccessManager))) + ) + ); + + erc20 = new MockERC20(); + mockNft = new MockERC721("ape"); + + impl = address(new ArbitrationPolicySP(address(disputeModule), address(erc20), ARBITRATION_PRICE)); + ArbitrationPolicySP arbitrationPolicySP = ArbitrationPolicySP( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(ArbitrationPolicySP.initialize, (address(protocolAccessManager))) + ) + ); + + impl = address(new RoyaltyPolicyLAP(address(royaltyModule), address(licensingModule))); + royaltyPolicyLAP = RoyaltyPolicyLAP( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(RoyaltyPolicyLAP.initialize, (address(protocolAccessManager))) + ) + ); + + impl = address( + new PILicenseTemplate( + address(accessController), + address(ipAssetRegistry), + address(licenseRegistry), + address(royaltyModule) + ) + ); + piLicenseTemplate = PILicenseTemplate( + TestProxyHelper.deployUUPSProxy( + impl, + abi.encodeCall(PILicenseTemplate.initialize, ("PIL", "PIL-metadata-url")) + ) + ); + + // Configure protocol + vm.startPrank(admin); + + address ipRoyaltyVaultImplementation = address( + new IpRoyaltyVault(address(royaltyPolicyLAP), address(disputeModule)) + ); + address ipRoyaltyVaultBeacon = address( + new UpgradeableBeacon(ipRoyaltyVaultImplementation, address(protocolAccessManager)) + ); + royaltyPolicyLAP.setIpRoyaltyVaultBeacon(ipRoyaltyVaultBeacon); + + royaltyPolicyLAP.setSnapshotInterval(7 days); + + accessController.setAddresses(address(ipAssetRegistry), address(moduleRegistry)); + + moduleRegistry.registerModule(DISPUTE_MODULE_KEY, address(disputeModule)); + moduleRegistry.registerModule(LICENSING_MODULE_KEY, address(licensingModule)); + moduleRegistry.registerModule(ROYALTY_MODULE_KEY, address(royaltyModule)); + + royaltyModule.setLicensingModule(address(licensingModule)); + royaltyModule.setDisputeModule(address(disputeModule)); + royaltyModule.whitelistRoyaltyToken(address(erc20), true); + royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP), true); + + disputeModule.whitelistDisputeTag("PLAGIARISM", true); + disputeModule.whitelistArbitrationPolicy(address(arbitrationPolicySP), true); + disputeModule.whitelistArbitrationRelayer(address(arbitrationPolicySP), admin, true); + disputeModule.setBaseArbitrationPolicy(address(arbitrationPolicySP)); + + licenseRegistry.setDisputeModule(address(disputeModule)); + licenseRegistry.setLicensingModule(address(licensingModule)); + licenseRegistry.registerLicenseTemplate(address(piLicenseTemplate)); + + licenseToken.setDisputeModule(address(disputeModule)); + licenseToken.setLicensingModule(address(licensingModule)); + + vm.stopPrank(); + } + + function test_e2e() public { + uint256 tokenId1 = mockNft.mint(alice); + uint256 tokenId2 = mockNft.mint(bob); + uint256 tokenId3 = mockNft.mint(charlie); + uint256 tokenId6 = mockNft.mint(dave); + uint256 tokenId7 = mockNft.mint(eve); + + ipId1 = ipAssetRegistry.register(address(mockNft), tokenId1); + ipId2 = ipAssetRegistry.register(address(mockNft), tokenId2); + ipId3 = ipAssetRegistry.register(address(mockNft), tokenId3); + ipId6 = ipAssetRegistry.register(address(mockNft), tokenId6); + ipId7 = ipAssetRegistry.register(address(mockNft), tokenId7); + + // register license terms + uint256 lcId1 = piLicenseTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + assertEq(lcId1, 1); + + uint256 lcId2 = piLicenseTemplate.registerLicenseTerms( + PILFlavors.commercialRemix(100, 10, address(royaltyPolicyLAP), address(erc20)) + ); + assertEq(lcId2, 2); + assertEq( + piLicenseTemplate.getLicenseTermsId( + PILFlavors.commercialRemix(100, 10, address(royaltyPolicyLAP), address(erc20)) + ), + 2 + ); + assertTrue(piLicenseTemplate.exists(2)); + + assertTrue(piLicenseTemplate.exists(lcId2)); + + // attach licenses + vm.startPrank(alice); + licensingModule.attachLicenseTerms(ipId1, address(piLicenseTemplate), 1); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(piLicenseTemplate), 1), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 1); + + (address attachedTemplate, uint256 attachedId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); + assertEq(attachedTemplate, address(piLicenseTemplate)); + assertEq(attachedId, 1); + + licensingModule.attachLicenseTerms(ipId1, address(piLicenseTemplate), 2); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(piLicenseTemplate), 2), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 2); + + (attachedTemplate, attachedId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 1); + assertEq(attachedTemplate, address(piLicenseTemplate)); + assertEq(attachedId, 2); + vm.stopPrank(); + + // register derivative directly + vm.startPrank(bob); + address[] memory parentIpIds = new address[](1); + uint256[] memory licenseTermsIds = new uint256[](1); + parentIpIds[0] = ipId1; + licenseTermsIds[0] = 1; + + licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(piLicenseTemplate), ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId2, address(piLicenseTemplate), 1), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId2), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId2), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId2), false); + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 0); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); + assertEq(licenseRegistry.getParentIp(ipId2, 0), ipId1); + assertEq(licenseRegistry.getParentIpCount(ipId2), 1); + vm.stopPrank(); + + // mint license token + vm.startPrank(charlie); + uint256 lcTokenId = licensingModule.mintLicenseTokens( + ipId1, + address(piLicenseTemplate), + 1, + 1, + address(charlie), + "" + ); + assertEq(licenseToken.ownerOf(lcTokenId), charlie); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), 1); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(piLicenseTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), 0); + assertEq(licenseToken.totalMintedTokens(), 1); + + // register derivative with license tokens + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + + licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId3, address(piLicenseTemplate), 1), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId3), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId3), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId3), false); + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 2); + assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 0); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 1), ipId3); + assertEq(licenseRegistry.getParentIp(ipId3, 0), ipId1); + assertEq(licenseRegistry.getParentIpCount(ipId3), 1); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId)); + assertEq(licenseToken.ownerOf(lcTokenId), address(0)); + assertEq(licenseToken.totalMintedTokens(), 1); + vm.stopPrank(); + + // mint license token with payments + vm.startPrank(dave); + erc20.mint(dave, 1000); + erc20.approve(address(royaltyPolicyLAP), 100); + + lcTokenId = licensingModule.mintLicenseTokens(ipId1, address(piLicenseTemplate), 2, 1, address(dave), ""); + + assertEq(licenseToken.ownerOf(lcTokenId), dave); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), 2); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(piLicenseTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), 0); + assertEq(licenseToken.totalMintedTokens(), 2); + assertEq(erc20.balanceOf(dave), 900); + + // register derivative with license tokens + licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + + licensingModule.registerDerivativeWithLicenseTokens(ipId6, licenseTokens, ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId6, address(piLicenseTemplate), 2), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId6), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId6), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId6), false); + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 3); + assertEq(licenseRegistry.getDerivativeIpCount(ipId6), 0); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 1), ipId3); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 2), ipId6); + assertEq(licenseRegistry.getParentIp(ipId6, 0), ipId1); + assertEq(licenseRegistry.getParentIpCount(ipId6), 1); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId)); + assertEq(licenseToken.ownerOf(lcTokenId), address(0)); + assertEq(licenseToken.totalMintedTokens(), 2); + vm.stopPrank(); + + // register derivative directly with payments + vm.startPrank(eve); + erc20.mint(eve, 1000); + erc20.approve(address(royaltyPolicyLAP), 100); + parentIpIds = new address[](1); + licenseTermsIds = new uint256[](1); + parentIpIds[0] = ipId1; + licenseTermsIds[0] = 2; + + licensingModule.registerDerivative(ipId7, parentIpIds, licenseTermsIds, address(piLicenseTemplate), ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId7, address(piLicenseTemplate), 2), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId7), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId7), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId7), false); + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 4); + assertEq(licenseRegistry.getDerivativeIpCount(ipId7), 0); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 3), ipId7); + assertEq(licenseRegistry.getParentIp(ipId7, 0), ipId1); + assertEq(licenseRegistry.getParentIpCount(ipId7), 1); + assertEq(licenseToken.totalMintedTokens(), 2); + assertEq(erc20.balanceOf(eve), 900); + vm.stopPrank(); + } +} diff --git a/test/foundry/mocks/module/MockLicenseTemplate.sol b/test/foundry/mocks/module/MockLicenseTemplate.sol new file mode 100644 index 000000000..974bd2ecd --- /dev/null +++ b/test/foundry/mocks/module/MockLicenseTemplate.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity 0.8.23; + +import { BaseLicenseTemplateUpgradeable } from "contracts/modules/licensing/BaseLicenseTemplateUpgradeable.sol"; + +contract MockLicenseTemplate is BaseLicenseTemplateUpgradeable { + uint256 public licenseTermsCounter; + mapping(uint256 => bool) public licenseTerms; + + function registerLicenseTerms() external returns (uint256 id) { + id = licenseTermsCounter++; + licenseTerms[id] = true; + } + + /// @notice Checks if a license terms exists. + /// @param licenseTermsId The ID of the license terms. + /// @return True if the license terms exists, false otherwise. + function exists(uint256 licenseTermsId) external view override returns (bool) { + return licenseTerms[licenseTermsId]; + } + + /// @notice Verifies the minting of a license token. + /// @dev the function will be called by the LicensingModule when minting a license token to + /// verify the minting is whether allowed by the license terms. + /// @param licenseTermsId The ID of the license terms. + /// @param licensee The address of the licensee who will receive the license token. + /// @param licensorIpId The IP ID of the licensor who attached the license terms minting the license token. + /// @return True if the minting is verified, false otherwise. + function verifyMintLicenseToken( + uint256 licenseTermsId, + address licensee, + address licensorIpId, + uint256 amount + ) external override returns (bool) { + return true; + } + + /// @notice Verifies the registration of a derivative. + /// @dev This function is invoked by the LicensingModule during the registration of a derivative work + //// to ensure compliance with the parent IP's licensing terms. + /// It verifies whether the derivative's registration is permitted under those terms. + /// @param childIpId The IP ID of the derivative. + /// @param parentIpId The IP ID of the parent. + /// @param licenseTermsId The ID of the license terms. + /// @param licensee The address of the licensee. + /// @return True if the registration is verified, false otherwise. + function verifyRegisterDerivative( + address childIpId, + address parentIpId, + uint256 licenseTermsId, + address licensee + ) external override returns (bool) { + return true; + } + + /// @notice Verifies if the licenses are compatible. + /// @dev This function is called by the LicensingModule to verify license compatibility + /// when registering a derivative IP to multiple parent IPs. + /// It ensures that the licenses of all parent IPs are compatible with each other during the registration process. + /// @param licenseTermsIds The IDs of the license terms. + /// @return True if the licenses are compatible, false otherwise. + function verifyCompatibleLicenses(uint256[] calldata licenseTermsIds) external view override returns (bool) { + return true; + } + + /// @notice Verifies the registration of a derivative for all parent IPs. + /// @dev This function is called by the LicensingModule to verify licenses for registering a derivative IP + /// to multiple parent IPs. + /// the function will verify the derivative for each parent IP's license and + /// also verify all licenses are compatible. + /// @param childIpId The IP ID of the derivative. + /// @param parentIpIds The IP IDs of the parents. + /// @param licenseTermsIds The IDs of the license terms. + /// @param childIpOwner The address of the derivative IP owner. + /// @return True if the registration is verified, false otherwise. + function verifyRegisterDerivativeForAllParents( + address childIpId, + address[] calldata parentIpIds, + uint256[] calldata licenseTermsIds, + address childIpOwner + ) external override returns (bool) { + return true; + } + + /// @notice Returns the royalty policy of a license terms. + /// @param licenseTermsId The ID of the license terms. + /// @return royaltyPolicy The address of the royalty policy specified for the license terms. + /// @return royaltyData The data of the royalty policy. + /// @return mintingFee The fee for minting a license. + /// @return currency The address of the ERC20 token, used for minting license fee and royalties. + /// the currency token will used for pay for license token minting fee and royalties. + function getRoyaltyPolicy( + uint256 licenseTermsId + ) external view returns (address royaltyPolicy, bytes memory royaltyData, uint256 mintingFee, address currency) { + return (address(0), "", 0, address(0)); + } + + /// @notice Checks if a license terms is transferable. + /// @param licenseTermsId The ID of the license terms. + /// @return True if the license terms is transferable, false otherwise. + function isLicenseTransferable(uint256 licenseTermsId) external view override returns (bool) { + return true; + } + + /// @notice Returns the earliest expiration time among the given license terms. + /// @param start The start time. + /// @param licenseTermsIds The IDs of the license terms. + /// @return The earliest expiration time. + function getEarlierExpireTime( + uint256[] calldata licenseTermsIds, + uint256 start + ) external view override returns (uint256) { + return 0; + } + + /// @notice Returns the expiration time of a license terms. + /// @param start The start time. + /// @param licenseTermsId The ID of the license terms. + /// @return The expiration time. + function getExpireTime(uint256 licenseTermsId, uint256 start) external view returns (uint256) { + return 0; + } + + /// @notice Returns the total number of registered license terms. + /// @return The total number of registered license terms. + function totalRegisteredLicenseTerms() external view returns (uint256) { + return licenseTermsCounter; + } + + /// @notice checks the contract whether supports the given interface. + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(BaseLicenseTemplateUpgradeable) returns (bool) { + return super.supportsInterface(interfaceId); + } + + /// @notice Converts the license terms to a JSON string which will be part of the metadata of license token. + /// @dev Must return OpenSea standard compliant metadata. + /// @param licenseTermsId The ID of the license terms. + /// @return The JSON string of the license terms, follow the OpenSea metadata standard. + function toJson(uint256 licenseTermsId) public view returns (string memory) { + return ""; + } +} diff --git a/test/foundry/modules/licensing/LicensingModule.t.sol b/test/foundry/modules/licensing/LicensingModule.t.sol index 9aeef8eab..e60f8ee87 100644 --- a/test/foundry/modules/licensing/LicensingModule.t.sol +++ b/test/foundry/modules/licensing/LicensingModule.t.sol @@ -5,9 +5,12 @@ pragma solidity 0.8.23; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; // contracts -import { IIPAccount } from "../../../../contracts/interfaces/IIPAccount.sol"; import { Errors } from "../../../../contracts/lib/Errors.sol"; import { PILFlavors } from "../../../../contracts/lib/PILFlavors.sol"; +import { ILicensingModule } from "../../../../contracts/interfaces/modules/licensing/ILicensingModule.sol"; +import { MockTokenGatedHook } from "../../mocks/MockTokenGatedHook.sol"; +import { MockLicenseTemplate } from "../../mocks/module/MockLicenseTemplate.sol"; +import { PILTerms } from "../../../../contracts/interfaces/modules/licensing/IPILicenseTemplate.sol"; // test import { MockERC721 } from "../../mocks/token/MockERC721.sol"; @@ -16,378 +19,1186 @@ import { BaseTest } from "../../utils/BaseTest.t.sol"; contract LicensingModuleTest is BaseTest { using Strings for *; - MockERC721 internal nft = new MockERC721("MockERC721"); + error ERC721NonexistentToken(uint256 tokenId); + + MockERC721 internal mockNft = new MockERC721("MockERC721"); MockERC721 internal gatedNftFoo = new MockERC721{ salt: bytes32(uint256(1)) }("GatedNftFoo"); MockERC721 internal gatedNftBar = new MockERC721{ salt: bytes32(uint256(2)) }("GatedNftBar"); - string public licenseUrl = "https://example.com/license"; address public ipId1; address public ipId2; address public ipId3; - address public ipOwner = address(0x100); // use static address, otherwise uri check fails because licensor changes - address public licenseHolder = address(0x101); + address public ipId5; + address public ipOwner1 = address(0x111); + address public ipOwner2 = address(0x222); + address public ipOwner3 = address(0x333); + address public ipOwner5 = address(0x444); + uint256 public tokenId1 = 1; + uint256 public tokenId2 = 2; + uint256 public tokenId3 = 3; + uint256 public tokenId5 = 5; - uint256 internal commRemixTermsId; - uint256 internal commUseTermsId; + address public licenseHolder = address(0x101); function setUp() public override { super.setUp(); - - vm.prank(u.admin); - royaltyModule.whitelistRoyaltyToken(address(0x123), true); - - commRemixTermsId = registerSelectedPILicenseTerms( - "commercial_remix", - PILFlavors.commercialRemix({ - mintingFee: 0, - commercialRevShare: 0, - royaltyPolicy: address(royaltyPolicyLAP), - currencyToken: address(0x123) - }) - ); - - commUseTermsId = registerSelectedPILicenseTerms( - "commercial_use", - PILFlavors.commercialUse({ - mintingFee: 0, - currencyToken: address(0x123), - royaltyPolicy: address(royaltyPolicyLAP) - }) - ); - // Create IPAccounts - nft.mintId(ipOwner, 1); - nft.mintId(ipOwner, 2); - nft.mintId(ipOwner, 3); + mockNft.mintId(ipOwner1, tokenId1); + mockNft.mintId(ipOwner2, tokenId2); + mockNft.mintId(ipOwner3, tokenId3); + mockNft.mintId(ipOwner5, tokenId5); - ipId1 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 1); - ipId2 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 2); - ipId3 = ipAccountRegistry.registerIpAccount(block.chainid, address(nft), 3); + ipId1 = ipAssetRegistry.register(address(mockNft), tokenId1); + ipId2 = ipAssetRegistry.register(address(mockNft), tokenId2); + ipId3 = ipAssetRegistry.register(address(mockNft), tokenId3); + ipId5 = ipAssetRegistry.register(address(mockNft), tokenId5); vm.label(ipId1, "IPAccount1"); vm.label(ipId2, "IPAccount2"); vm.label(ipId3, "IPAccount3"); - - useMock_RoyaltyPolicyLAP(); + vm.label(ipId5, "IPAccount5"); } - function test_LicensingModule_attachLicenseTerms() public { - vm.prank(ipOwner); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); - assertEq(commRemixTermsId, 1, "policyId not 1"); - assertTrue(licenseRegistry.exists(address(pilTemplate), commRemixTermsId)); - assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId)); + function test_LicensingModule_attachLicenseTerms_attachOneLicenseToOneIP() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.expectEmit(); + emit ILicensingModule.LicenseTermsAttached(ipOwner1, ipId1, address(pilTemplate), termsId); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, termsId); + assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), termsId)); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 0); + assertEq(licenseRegistry.getParentIpCount(ipId1), 0); + assertFalse(licenseRegistry.getMintingLicenseConfig(ipId1, address(pilTemplate), termsId).isSet); + assertEq(licenseRegistry.getExpireTime(ipId1), 0); assertFalse(licenseRegistry.isDerivativeIp(ipId1)); + assertTrue(licenseRegistry.exists(address(pilTemplate), termsId)); } - function test_LicensingModule_attachLicenseTerms_sameReusePolicyId() public { - address licenseTemplate; - uint256 licenseTermsId; + function test_LicensingModule_attachLicenseTerms_sameLicenseAttachMultipleIP() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); - vm.startPrank(ipOwner); + vm.expectEmit(); + emit ILicensingModule.LicenseTermsAttached(ipOwner1, ipId1, address(pilTemplate), termsId); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); - (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); - assertTrue(licenseRegistry.exists(address(pilTemplate), commRemixTermsId)); - assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId)); + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); assertEq(licenseTemplate, address(pilTemplate)); - assertEq(licenseTermsId, commRemixTermsId); + assertEq(licenseTermsId, termsId); + assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), termsId)); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 0); + assertEq(licenseRegistry.getParentIpCount(ipId1), 0); + assertFalse(licenseRegistry.getMintingLicenseConfig(ipId1, address(pilTemplate), termsId).isSet); + assertEq(licenseRegistry.getExpireTime(ipId1), 0); + assertFalse(licenseRegistry.isDerivativeIp(ipId1)); + assertTrue(licenseRegistry.exists(address(pilTemplate), termsId)); - licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), commRemixTermsId); + vm.expectEmit(); + emit ILicensingModule.LicenseTermsAttached(ipOwner2, ipId2, address(pilTemplate), termsId); + vm.prank(ipOwner2); + licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), termsId); (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId2, 0); - assertTrue(licenseRegistry.exists(address(pilTemplate), commRemixTermsId)); - assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId2, address(pilTemplate), commRemixTermsId)); assertEq(licenseTemplate, address(pilTemplate)); - assertEq(licenseTermsId, commRemixTermsId); + assertEq(licenseTermsId, termsId); + assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId2, address(pilTemplate), termsId)); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId2), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 0); + assertEq(licenseRegistry.getParentIpCount(ipId2), 0); + assertFalse(licenseRegistry.getMintingLicenseConfig(ipId2, address(pilTemplate), termsId).isSet); + assertEq(licenseRegistry.getExpireTime(ipId2), 0); + assertFalse(licenseRegistry.isDerivativeIp(ipId2)); + assertTrue(licenseRegistry.exists(address(pilTemplate), termsId)); } - function test_LicensingModule_attachLicenseTerms_TwoPoliciesToOneIpId() public { - address licenseTemplate; - uint256 licenseTermsId; + function test_LicensingModule_attachLicenseTerms_DifferentLicensesAttachToSameIP() public { + uint256 termsId1 = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.expectEmit(); + emit ILicensingModule.LicenseTermsAttached(ipOwner1, ipId1, address(pilTemplate), termsId1); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId1); + (address licenseTemplate1, uint256 licenseTermsId1) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); + assertEq(licenseTemplate1, address(pilTemplate)); + assertEq(licenseTermsId1, termsId1); + assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), termsId1)); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 0); + assertEq(licenseRegistry.getParentIpCount(ipId1), 0); + assertFalse(licenseRegistry.getMintingLicenseConfig(ipId1, address(pilTemplate), termsId1).isSet); + assertEq(licenseRegistry.getExpireTime(ipId1), 0); + assertFalse(licenseRegistry.isDerivativeIp(ipId1)); + assertTrue(licenseRegistry.exists(address(pilTemplate), termsId1)); + + uint256 termsId2 = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.expectEmit(); + emit ILicensingModule.LicenseTermsAttached(ipOwner1, ipId1, address(pilTemplate), termsId2); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId2); + (address licenseTemplate2, uint256 licenseTermsId2) = licenseRegistry.getAttachedLicenseTerms(ipId1, 1); + assertEq(licenseTemplate2, address(pilTemplate)); + assertEq(licenseTermsId2, termsId2); + assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), termsId2)); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 2); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 0); + assertEq(licenseRegistry.getParentIpCount(ipId1), 0); + assertFalse(licenseRegistry.getMintingLicenseConfig(ipId1, address(pilTemplate), termsId2).isSet); + assertEq(licenseRegistry.getExpireTime(ipId1), 0); + assertFalse(licenseRegistry.isDerivativeIp(ipId1)); + assertTrue(licenseRegistry.exists(address(pilTemplate), termsId2)); + } - vm.startPrank(ipOwner); + function test_LicensingModule_attachLicenseTerms_revert_licenseNotExist() public { + uint256 nonExistTermsId = 9999; + assertFalse(licenseRegistry.exists(address(pilTemplate), nonExistTermsId)); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); - (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); - assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId)); - assertEq(licenseTemplate, address(pilTemplate)); - assertEq(licenseTermsId, commRemixTermsId); - assertFalse(licenseRegistry.isDerivativeIp(ipId1)); + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicensingModule__LicenseTermsNotFound.selector, + address(pilTemplate), + nonExistTermsId + ) + ); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), nonExistTermsId); + } + + function test_LicensingModule_attachLicenseTerms_revert_IpExpired() public { + vm.prank(u.admin); + royaltyModule.whitelistRoyaltyToken(address(0x123), true); + PILTerms memory terms = PILTerms({ + transferable: true, + royaltyPolicy: address(royaltyPolicyLAP), + mintingFee: 0, + expiration: 10 days, + commercialUse: true, + commercialAttribution: true, + commercializerChecker: address(0), + commercializerCheckerData: "", + commercialRevShare: 0, + commercialRevCelling: 0, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesApproval: false, + derivativesReciprocal: true, + derivativeRevCelling: 0, + currency: address(0x123) + }); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commUseTermsId); - (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 1); - assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), commUseTermsId)); + uint256 termsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, + royaltyContext: "" + }); + assertEq(licenseToken.ownerOf(lcTokenId), ipOwner2); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), block.timestamp + 10 days); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(ipOwner2), 1); + + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + vm.prank(ipOwner2); + licensingModule.registerDerivativeWithLicenseTokens(ipId2, licenseTokens, ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId2, address(pilTemplate), termsId), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId2), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId2), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId2), false); + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 0); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); + assertEq(licenseRegistry.getParentIp(ipId2, 0), ipId1); + assertEq(licenseRegistry.getParentIpCount(ipId2), 1); + assertEq(licenseToken.totalSupply(), 0); + assertEq(licenseToken.totalMintedTokens(), 1); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId)); + licenseToken.ownerOf(lcTokenId); + + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId2, 0); assertEq(licenseTemplate, address(pilTemplate)); - assertEq(licenseTermsId, commUseTermsId); - assertFalse(licenseRegistry.isDerivativeIp(ipId1)); + assertEq(licenseTermsId, termsId); + + licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: address(ipOwner2), + royaltyContext: "" + }); + + vm.warp(11 days); + uint256 anotherTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__IpExpired.selector, ipId2)); + vm.prank(ipOwner2); + licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), anotherTermsId); } - function test_LicensingModule_attachLicenseTerms_revert_policyNotFound() public { - uint256 undefinedPILTermsId = 111222333222111; - assertFalse(licenseRegistry.exists(address(pilTemplate), undefinedPILTermsId)); + function test_LicensingModule_attachLicenseTerms_revert_attachSameLicenseToIpTwice() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicenseRegistry__LicenseTermsAlreadyAttached.selector, + ipId1, + address(pilTemplate), + termsId + ) + ); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + } - vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__IndexOutOfBounds.selector, ipId1, 0, 0)); - licenseRegistry.getAttachedLicenseTerms(ipId1, 0); + function test_LicensingModule_attachLicenseTerms_revert_attachLicenseDifferentTemplate() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + MockLicenseTemplate mockLicenseTemplate = new MockLicenseTemplate(); + vm.prank(admin); + licenseRegistry.registerLicenseTemplate(address(mockLicenseTemplate)); + uint256 mockTermsId = mockLicenseTemplate.registerLicenseTerms(); vm.expectRevert( abi.encodeWithSelector( - Errors.LicensingModule__LicenseTermsNotFound.selector, + Errors.LicenseRegistry__UnmatchedLicenseTemplate.selector, + ipId1, address(pilTemplate), - undefinedPILTermsId + address(mockLicenseTemplate) ) ); - vm.prank(ipOwner); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), undefinedPILTermsId); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(mockLicenseTemplate), mockTermsId); } - function test_LicensingModule_attachLicenseTerms_revert_policyAlreadySetForIpId() public { - address licenseTemplate; - uint256 licenseTermsId; + function test_LicensingModule_attachLicenseTerms_revert_Unregistered_LicenseTemplate() public { + MockLicenseTemplate mockLicenseTemplate = new MockLicenseTemplate(); + uint256 mockTermsId = mockLicenseTemplate.registerLicenseTerms(); + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicensingModule__LicenseTermsNotFound.selector, + address(mockLicenseTemplate), + mockTermsId + ) + ); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(mockLicenseTemplate), mockTermsId); + } - vm.startPrank(ipOwner); + function test_LicensingModule_attachLicenseTerms_revert_NonExists_LicenseTerms() public { + uint256 nonExistsTermsId = 9999; + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicensingModule__LicenseTermsNotFound.selector, + address(pilTemplate), + nonExistsTermsId + ) + ); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), nonExistsTermsId); + } - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); - (licenseTemplate, licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); - assertTrue(licenseRegistry.exists(address(pilTemplate), commRemixTermsId)); - assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId)); - assertEq(licenseTemplate, address(pilTemplate)); - assertEq(licenseTermsId, commRemixTermsId); + function test_LicensingModule_mintLicenseTokens_singleToken() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); - // TODO: This should revert! - // vm.expectRevert(Errors.LicensingModule__PolicyAlreadySetForIpId.selector); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); + address receiver = address(0x111); + vm.expectEmit(); + emit ILicensingModule.LicenseTokensMinted(address(this), ipId1, address(pilTemplate), termsId, 1, receiver, 0); + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: receiver, + royaltyContext: "" + }); + assertEq(licenseToken.ownerOf(lcTokenId), receiver); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), 0); + assertEq(licenseToken.tokenOfOwnerByIndex(receiver, 0), lcTokenId); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(receiver), 1); } - function test_LicensingModule_mintLicenseTokens() public { - vm.prank(ipOwner); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); - assertTrue(licenseRegistry.hasIpAttachedLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId)); + function test_LicensingModule_mintLicenseTokens_mintMultipleTokens() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); - uint256 startLicenseId = attachAndMint_PILCommRemix_LicenseTokens({ - ipId: ipId1, + address receiver = address(0x111); + vm.expectEmit(); + emit ILicensingModule.LicenseTokensMinted(address(this), ipId1, address(pilTemplate), termsId, 2, receiver, 0); + uint256 firstTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, amount: 2, - receiver: licenseHolder + receiver: receiver, + royaltyContext: "" }); - assertEq(licenseToken.balanceOf(licenseHolder), 2); - assertEq(licenseToken.tokenOfOwnerByIndex(licenseHolder, 0), startLicenseId); - assertEq(licenseToken.tokenOfOwnerByIndex(licenseHolder, 1), startLicenseId + 1); + assertEq(licenseToken.ownerOf(firstTokenId), receiver); + assertEq(licenseToken.getLicenseTermsId(firstTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(firstTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(firstTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(firstTokenId), 0); + assertEq(licenseToken.tokenOfOwnerByIndex(receiver, 0), firstTokenId); + + uint256 secondTokenId = firstTokenId + 1; + + assertEq(licenseToken.ownerOf(secondTokenId), receiver); + assertEq(licenseToken.getLicenseTermsId(secondTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(secondTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(secondTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(secondTokenId), 0); + assertEq(licenseToken.tokenOfOwnerByIndex(receiver, 1), secondTokenId); + assertEq(licenseToken.totalMintedTokens(), 2); + assertEq(licenseToken.totalSupply(), 2); + assertEq(licenseToken.balanceOf(receiver), 2); } - function test_LIcensingModule_mintLicenseTokens_revert_inputValidations() public {} + function test_LicensingModule_mintLicenseTokens_mintMultipleTimes() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); - function test_LicensingModule_mintLicenseTokens_revert_callerNotLicensorAndIpIdHasNoPolicy() public {} + address receiver = address(0x111); + for (uint i = 0; i < 10; i++) { + vm.expectEmit(); + emit ILicensingModule.LicenseTokensMinted( + address(this), + ipId1, + address(pilTemplate), + termsId, + 1, + receiver, + i + ); + uint256 tokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: receiver, + royaltyContext: "" + }); + assertEq(licenseToken.ownerOf(tokenId), receiver); + assertEq(licenseToken.getLicenseTermsId(tokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(tokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(tokenId), ipId1); + assertEq(licenseToken.getExpirationTime(tokenId), 0); + assertEq(licenseToken.tokenOfOwnerByIndex(receiver, i), tokenId); + assertEq(licenseToken.totalMintedTokens(), i + 1); + assertEq(licenseToken.totalSupply(), i + 1); + assertEq(licenseToken.balanceOf(receiver), i + 1); + } + } + + function test_LicensingModule_mintLicenseTokens_ExpirationTime() public { + vm.prank(u.admin); + royaltyModule.whitelistRoyaltyToken(address(0x123), true); + PILTerms memory terms = PILTerms({ + transferable: true, + royaltyPolicy: address(royaltyPolicyLAP), + mintingFee: 0, + expiration: 10 days, + commercialUse: true, + commercialAttribution: true, + commercializerChecker: address(0), + commercializerCheckerData: "", + commercialRevShare: 0, + commercialRevCelling: 0, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesApproval: false, + derivativesReciprocal: true, + derivativeRevCelling: 0, + currency: address(0x123) + }); - function test_LicensingModule_mintLicenseTokens_ipIdHasNoPolicyButCallerIsLicensor() public { - vm.prank(IIPAccount(payable(ipId1)).owner()); - uint256 startLicenseId = licensingModule.mintLicenseTokens({ + uint256 termsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ licensorIpId: ipId1, licenseTemplate: address(pilTemplate), - licenseTermsId: commRemixTermsId, - amount: 2, - receiver: ipId1, + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, royaltyContext: "" }); - assertEq(licenseToken.balanceOf(ipId1), 2); - assertEq(licenseToken.tokenOfOwnerByIndex(ipId1, 0), startLicenseId); - assertEq(licenseToken.tokenOfOwnerByIndex(ipId1, 1), startLicenseId + 1); - - // Licensor (IP Account owner) calls via IP Account execute - // The returned license ID (from decoding `result`) should be the same as above, as we're not creating a new - // license, but rather minting an existing one (existing ID, minted above). - vm.prank(IIPAccount(payable(ipId1)).owner()); - bytes memory result = IIPAccount(payable(ipId1)).execute( - address(licensingModule), - 0, - abi.encodeWithSignature( - "mintLicenseTokens(address,address,uint256,uint256,address,bytes)", + assertEq(licenseToken.ownerOf(lcTokenId), ipOwner2); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), block.timestamp + 10 days); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(ipOwner2), 1); + } + + function test_LicensingModule_mintLicenseTokens_revert_invalidInputs() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + address receiver = address(0x111); + vm.expectRevert(Errors.LicensingModule__MintAmountZero.selector); + licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 0, + receiver: receiver, + royaltyContext: "" + }); + + vm.expectRevert(Errors.LicensingModule__ReceiverZeroAddress.selector); + licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: address(0), + royaltyContext: "" + }); + } + + function test_LicensingModule_mintLicenseTokens_revert_NonIpOwnerMintNotAttachedLicense() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + address receiver = address(0x111); + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicenseRegistry__LicensorIpHasNoLicenseTerms.selector, ipId1, address(pilTemplate), - commRemixTermsId, - 2, - ipId1, - "" + termsId ) ); - assertEq(startLicenseId + 2, abi.decode(result, (uint256))); - assertEq(licenseToken.balanceOf(ipId1), 4); - assertEq(licenseToken.tokenOfOwnerByIndex(ipId1, 2), startLicenseId + 2); - assertEq(licenseToken.tokenOfOwnerByIndex(ipId1, 3), startLicenseId + 3); - - // IP Account calls directly - vm.prank(ipId1); - startLicenseId = licensingModule.mintLicenseTokens({ + licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: receiver, + royaltyContext: "" + }); + } + + function test_LicensingModule_mintLicenseTokens_revert_IpOwnerMintNonExistsLicense() public { + address receiver = address(0x111); + vm.expectRevert( + abi.encodeWithSelector(Errors.LicenseRegistry__LicenseTermsNotExists.selector, address(pilTemplate), 9999) + ); + vm.prank(ipOwner1); + licensingModule.mintLicenseTokens({ licensorIpId: ipId1, licenseTemplate: address(pilTemplate), - licenseTermsId: commUseTermsId, // different selected license terms + licenseTermsId: 9999, amount: 1, - receiver: ipId1, + receiver: receiver, royaltyContext: "" }); - assertEq(licenseToken.balanceOf(ipId1), 5); - assertEq(licenseToken.tokenOfOwnerByIndex(ipId1, 4), startLicenseId); + } + + function test_LicensingModule_mintLicenseTokens_IpOwnerMintNotAttachedLicense() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + address receiver = address(0x111); + vm.prank(ipOwner1); + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: receiver, + royaltyContext: "" + }); + assertEq(licenseToken.ownerOf(lcTokenId), receiver); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), 0); + assertEq(licenseToken.tokenOfOwnerByIndex(receiver, 0), lcTokenId); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(receiver), 1); } function test_LicensingModule_registerDerivativeWithLicenseTokens_singleParent() public { - uint256 startLicenseId = attachAndMint_PILCommRemix_LicenseTokens({ - ipId: ipId1, - amount: 2, - receiver: licenseHolder + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, + royaltyContext: "" }); - uint256 endLicenseId = startLicenseId + 1; + assertEq(licenseToken.ownerOf(lcTokenId), ipOwner2); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), 0); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(ipOwner2), 1); + + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + vm.prank(ipOwner2); + licensingModule.registerDerivativeWithLicenseTokens(ipId2, licenseTokens, ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId2, address(pilTemplate), termsId), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId2), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId2), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId2), false); + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 0); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId2); + assertEq(licenseRegistry.getParentIp(ipId2, 0), ipId1); + assertEq(licenseRegistry.getParentIpCount(ipId2), 1); + assertEq(licenseToken.totalSupply(), 0); + assertEq(licenseToken.totalMintedTokens(), 1); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId)); + licenseToken.ownerOf(lcTokenId); - vm.prank(licenseHolder); - licenseToken.transferFrom(licenseHolder, ipOwner, endLicenseId); - assertEq(licenseToken.balanceOf(licenseHolder), 1, "not transferred"); - assertEq(licenseToken.ownerOf(startLicenseId), licenseHolder); - assertEq(licenseToken.ownerOf(endLicenseId), ipOwner); + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId2, 0); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, termsId); + } + + function test_LicensingModule_registerDerivativeWithLicenseTokens_twoParents() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + vm.prank(ipOwner2); + licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), termsId); - uint256[] memory licenseIds = new uint256[](1); - licenseIds[0] = endLicenseId; + uint256 lcTokenId1 = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "" + }); - vm.prank(ipOwner); - licensingModule.registerDerivativeWithLicenseTokens(ipId2, licenseIds, ""); + uint256 lcTokenId2 = licensingModule.mintLicenseTokens({ + licensorIpId: ipId2, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "" + }); - assertEq(licenseToken.balanceOf(ipOwner), 0, "not burnt"); - assertTrue(licenseRegistry.isDerivativeIp(ipId2)); - assertTrue(licenseRegistry.hasDerivativeIps(ipId1)); - assertEq(licenseRegistry.getParentIpCount(ipId2), 1); + assertEq(licenseToken.ownerOf(lcTokenId1), ipOwner3); + assertEq(licenseToken.getLicenseTermsId(lcTokenId1), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId1), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId1), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId1), 0); + + assertEq(licenseToken.ownerOf(lcTokenId2), ipOwner3); + assertEq(licenseToken.getLicenseTermsId(lcTokenId2), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId2), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId2), ipId2); + assertEq(licenseToken.getExpirationTime(lcTokenId2), 0); + + assertEq(licenseToken.totalMintedTokens(), 2); + assertEq(licenseToken.totalSupply(), 2); + assertEq(licenseToken.balanceOf(ipOwner3), 2); + + uint256[] memory licenseTokens = new uint256[](2); + licenseTokens[0] = lcTokenId1; + licenseTokens[1] = lcTokenId2; + vm.prank(ipOwner3); + licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId3, address(pilTemplate), termsId), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId3), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId3), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId3), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId3), 0); + + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId3); assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 1); - assertEq(licenseRegistry.getParentIp(ipId2, 0), ipId1); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId1), 1); - assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId2), 1); - (address lt1, uint256 ltId1) = licenseRegistry.getAttachedLicenseTerms(ipId1, 0); - (address lt2, uint256 ltId2) = licenseRegistry.getAttachedLicenseTerms(ipId2, 0); - assertEq(lt1, lt2); - assertEq(ltId1, ltId2); + assertEq(licenseRegistry.hasDerivativeIps(ipId2), true); + assertEq(licenseRegistry.isDerivativeIp(ipId2), false); + assertEq(licenseRegistry.getDerivativeIp(ipId2, 0), ipId3); + assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 1); + + assertEq(licenseRegistry.getParentIpCount(ipId3), 2); + assertEq(licenseRegistry.getParentIp(ipId3, 0), ipId1); + assertEq(licenseRegistry.getParentIp(ipId3, 1), ipId2); + + assertEq(licenseToken.totalSupply(), 0); + assertEq(licenseToken.totalMintedTokens(), 2); + + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId1)); + licenseToken.ownerOf(lcTokenId1); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId2)); + licenseToken.ownerOf(lcTokenId2); + + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId3, 0); + assertEq(licenseTemplate, address(pilTemplate)); } function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_parentIsChild() public { - uint256 startLicenseId = attachAndMint_PILCommRemix_LicenseTokens({ - ipId: ipId1, - amount: 2, - receiver: ipOwner + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(ipOwner1); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner1, + royaltyContext: "" }); - assertEq(startLicenseId, 0); - uint256[] memory licenseIds = new uint256[](1); - licenseIds[0] = startLicenseId; + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; - // TODO: this error is not descriptive of this test case. - vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__DerivativeIpAlreadyHasLicense.selector, ipId1)); - vm.prank(ipOwner); - licensingModule.registerDerivativeWithLicenseTokens(ipId1, licenseIds, ""); + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__DerivativeIsParent.selector, ipId1)); + vm.prank(ipOwner1); + licensingModule.registerDerivativeWithLicenseTokens(ipId1, licenseTokens, ""); } - function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_linkTwice() public { - uint256[] memory licenseIds = new uint256[](1); - uint256 startLicenseId; + function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_ExpiredLicenseToken() public { + vm.prank(u.admin); + royaltyModule.whitelistRoyaltyToken(address(0x123), true); + PILTerms memory terms = PILTerms({ + transferable: true, + royaltyPolicy: address(royaltyPolicyLAP), + mintingFee: 0, + expiration: 10 days, + commercialUse: true, + commercialAttribution: true, + commercializerChecker: address(0), + commercializerCheckerData: "", + commercialRevShare: 0, + commercialRevCelling: 0, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesApproval: false, + derivativesReciprocal: true, + derivativeRevCelling: 0, + currency: address(0x123) + }); - vm.startPrank(ipOwner); - licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commRemixTermsId); + uint256 termsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); - startLicenseId = licensingModule.mintLicenseTokens({ + uint256 lcTokenId = licensingModule.mintLicenseTokens({ licensorIpId: ipId1, licenseTemplate: address(pilTemplate), - licenseTermsId: commRemixTermsId, - amount: 2, - receiver: ipOwner, + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, royaltyContext: "" }); - assertEq(startLicenseId, 0); - licenseIds[0] = startLicenseId; + uint256 lcTokenExpiredTime = licenseToken.getExpirationTime(lcTokenId); + assertEq(licenseToken.ownerOf(lcTokenId), ipOwner2); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(lcTokenExpiredTime, block.timestamp + 10 days); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(ipOwner2), 1); - licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseIds, ""); + vm.warp(11 days); - licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), commRemixTermsId); - startLicenseId = licensingModule.mintLicenseTokens({ + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicenseToken__LicenseTokenExpired.selector, + lcTokenId, + lcTokenExpiredTime, + block.timestamp + ) + ); + vm.prank(ipOwner2); + licensingModule.registerDerivativeWithLicenseTokens(ipId2, licenseTokens, ""); + } + + function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_ParentExpired() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + PILTerms memory expiredTerms = PILFlavors.nonCommercialSocialRemixing(); + expiredTerms.expiration = 10 days; + uint256 expiredTermsId = pilTemplate.registerLicenseTerms(expiredTerms); + + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + vm.prank(ipOwner2); + licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), expiredTermsId); + + uint256 lcTokenId1 = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "" + }); + + uint256 lcTokenId2 = licensingModule.mintLicenseTokens({ licensorIpId: ipId2, licenseTemplate: address(pilTemplate), - licenseTermsId: commRemixTermsId, - amount: 2, - receiver: ipOwner, + licenseTermsId: expiredTermsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "" + }); + + assertEq(licenseToken.ownerOf(lcTokenId1), ipOwner3); + assertEq(licenseToken.getLicenseTermsId(lcTokenId1), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId1), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId1), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId1), 0); + + assertEq(licenseToken.ownerOf(lcTokenId2), ipOwner3); + assertEq(licenseToken.getLicenseTermsId(lcTokenId2), expiredTermsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId2), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId2), ipId2); + assertEq(licenseToken.getExpirationTime(lcTokenId2), block.timestamp + 10 days); + + assertEq(licenseToken.totalMintedTokens(), 2); + assertEq(licenseToken.totalSupply(), 2); + assertEq(licenseToken.balanceOf(ipOwner3), 2); + + uint256[] memory licenseTokens = new uint256[](2); + licenseTokens[0] = lcTokenId1; + licenseTokens[1] = lcTokenId2; + vm.prank(ipOwner3); + licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); + + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId3, address(pilTemplate), termsId), true); + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId3, address(pilTemplate), expiredTermsId), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId3), 2); + assertEq(licenseRegistry.isDerivativeIp(ipId3), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId3), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId3), 0); + + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId3); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 1); + + assertEq(licenseRegistry.hasDerivativeIps(ipId2), true); + assertEq(licenseRegistry.isDerivativeIp(ipId2), false); + assertEq(licenseRegistry.getDerivativeIp(ipId2, 0), ipId3); + assertEq(licenseRegistry.getDerivativeIpCount(ipId2), 1); + + assertEq(licenseRegistry.getParentIpCount(ipId3), 2); + assertEq(licenseRegistry.getParentIp(ipId3, 0), ipId1); + assertEq(licenseRegistry.getParentIp(ipId3, 1), ipId2); + + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId3, 0); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, termsId); + (address anotherLicenseTemplate, uint256 anotherLicenseTermsId) = licenseRegistry.getAttachedLicenseTerms( + ipId3, + 1 + ); + assertEq(anotherLicenseTemplate, address(pilTemplate)); + assertEq(anotherLicenseTermsId, expiredTermsId); + uint256[] memory licenseTerms = new uint256[](2); + licenseTerms[0] = termsId; + licenseTerms[1] = expiredTermsId; + + assertEq(licenseRegistry.getExpireTime(ipId3), block.timestamp + 10 days, "IPA has unexpected expiration time"); + vm.warp(5 days); + + uint256 lcTokenId3 = licensingModule.mintLicenseTokens({ + licensorIpId: ipId3, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner5, royaltyContext: "" }); - assertEq(startLicenseId, 2); - licenseIds[0] = startLicenseId; + vm.warp(11 days); + + licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId3; - // TODO: this error is not descriptive of this test case. - vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__DerivativeIpAlreadyHasLicense.selector, ipId3)); - licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseIds, ""); - vm.stopPrank(); + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__ParentIpExpired.selector, ipId3)); + vm.prank(ipOwner5); + licensingModule.registerDerivativeWithLicenseTokens(ipId5, licenseTokens, ""); } - function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_notLicensee() public { - uint256 startLicenseId = attachAndMint_PILCommRemix_LicenseTokens({ - ipId: ipId1, - amount: 2, - receiver: ipOwner + function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_childAlreadyAttachedLicense() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner1, + royaltyContext: "" }); - assertEq(startLicenseId, 0); - vm.stopPrank(); + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__DerivativeIpAlreadyHasLicense.selector, ipId1)); + vm.prank(ipOwner1); + licensingModule.registerDerivativeWithLicenseTokens(ipId1, licenseTokens, ""); + } + + function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_AlreadyRegisteredAsDerivative() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + vm.prank(ipOwner2); + licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), termsId); + + uint256 lcTokenId1 = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "" + }); + + uint256 lcTokenId2 = licensingModule.mintLicenseTokens({ + licensorIpId: ipId2, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner3, + royaltyContext: "" + }); + + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId1; + vm.prank(ipOwner3); + licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); + + licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId2; + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__DerivativeAlreadyRegistered.selector, ipId3)); + vm.prank(ipOwner3); + licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); + } + + function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_notLicensee() public { + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, + royaltyContext: "" + }); - uint256[] memory licenseIds = new uint256[](1); - licenseIds[0] = startLicenseId; + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; - // TODO: this error is not descriptive of this test case. vm.expectRevert( - abi.encodeWithSelector( - Errors.AccessController__PermissionDenied.selector, - ipId1, - licenseHolder, - address(licensingModule), - licensingModule.registerDerivativeWithLicenseTokens.selector - ) + abi.encodeWithSelector(Errors.LicenseToken__NotLicenseTokenOwner.selector, lcTokenId, ipOwner3, ipOwner2) ); - vm.prank(licenseHolder); - licensingModule.registerDerivativeWithLicenseTokens(ipId1, licenseIds, ""); + vm.prank(ipOwner3); + licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); } function test_LicensingModule_singleTransfer_verifyOk() public { - uint256 startLicenseId = attachAndMint_PILCommRemix_LicenseTokens({ - ipId: ipId1, - amount: 2, - receiver: licenseHolder + uint256 termsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, + royaltyContext: "" }); - uint256 endLicenseId = startLicenseId + 1; - assertEq(startLicenseId, 0); - address licenseHolder2 = address(0x102); - assertEq(licenseToken.balanceOf(licenseHolder), 2); - assertEq(licenseToken.balanceOf(licenseHolder2), 0); + vm.prank(ipOwner2); + licenseToken.transferFrom(ipOwner2, ipOwner3, lcTokenId); - vm.prank(licenseHolder); - licenseToken.transferFrom(licenseHolder, licenseHolder2, startLicenseId); + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + vm.prank(ipOwner3); + licensingModule.registerDerivativeWithLicenseTokens(ipId3, licenseTokens, ""); - assertEq(licenseToken.balanceOf(licenseHolder), 1); - assertEq(licenseToken.balanceOf(licenseHolder2), 1); - assertEq(licenseToken.ownerOf(startLicenseId), licenseHolder2); - assertEq(licenseToken.ownerOf(endLicenseId), licenseHolder); + assertEq(licenseRegistry.hasIpAttachedLicenseTerms(ipId3, address(pilTemplate), termsId), true); + assertEq(licenseRegistry.getAttachedLicenseTermsCount(ipId3), 1); + assertEq(licenseRegistry.isDerivativeIp(ipId3), true); + assertEq(licenseRegistry.hasDerivativeIps(ipId3), false); + assertEq(licenseRegistry.hasDerivativeIps(ipId1), true); + assertEq(licenseRegistry.isDerivativeIp(ipId1), false); + assertEq(licenseRegistry.getDerivativeIpCount(ipId1), 1); + assertEq(licenseRegistry.getDerivativeIpCount(ipId3), 0); + assertEq(licenseRegistry.getDerivativeIp(ipId1, 0), ipId3); + assertEq(licenseRegistry.getParentIp(ipId3, 0), ipId1); + assertEq(licenseRegistry.getParentIpCount(ipId3), 1); + assertEq(licenseToken.totalSupply(), 0); + assertEq(licenseToken.totalMintedTokens(), 1); + vm.expectRevert(abi.encodeWithSelector(ERC721NonexistentToken.selector, lcTokenId)); + licenseToken.ownerOf(lcTokenId); + + (address licenseTemplate, uint256 licenseTermsId) = licenseRegistry.getAttachedLicenseTerms(ipId3, 0); + assertEq(licenseTemplate, address(pilTemplate)); + assertEq(licenseTermsId, termsId); } - function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { - return this.onERC721Received.selector; + function test_LicensingModule_mintLicenseTokens_HookVerifyPass() public { + vm.prank(u.admin); + royaltyModule.whitelistRoyaltyToken(address(0x123), true); + + MockTokenGatedHook tokenGatedHook = new MockTokenGatedHook(); + PILTerms memory terms = PILTerms({ + transferable: true, + royaltyPolicy: address(royaltyPolicyLAP), + mintingFee: 0, + expiration: 0, + commercialUse: true, + commercialAttribution: true, + commercializerChecker: address(tokenGatedHook), + commercializerCheckerData: abi.encode(address(gatedNftBar)), + commercialRevShare: 0, + commercialRevCelling: 0, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesApproval: false, + derivativesReciprocal: true, + derivativeRevCelling: 0, + currency: address(0x123) + }); + + uint256 termsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + gatedNftBar.mint(ipOwner2); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, + royaltyContext: "" + }); + + assertEq(licenseToken.ownerOf(lcTokenId), ipOwner2); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), 0); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(ipOwner2), 1); } - function attachAndMint_PILCommRemix_LicenseTokens( - address ipId, - uint256 amount, - address receiver - ) internal returns (uint256 startLicenseId) { - vm.prank(ipOwner); - licensingModule.attachLicenseTerms(ipId, address(pilTemplate), commRemixTermsId); + function test_LicensingModule_mintLicenseTokens_revert_HookVerifyFail() public { + vm.prank(u.admin); + royaltyModule.whitelistRoyaltyToken(address(0x123), true); + + MockTokenGatedHook tokenGatedHook = new MockTokenGatedHook(); + PILTerms memory terms = PILTerms({ + transferable: true, + royaltyPolicy: address(royaltyPolicyLAP), + mintingFee: 0, + expiration: 0, + commercialUse: true, + commercialAttribution: true, + commercializerChecker: address(tokenGatedHook), + commercializerCheckerData: abi.encode(address(gatedNftBar)), + commercialRevShare: 0, + commercialRevCelling: 0, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesApproval: false, + derivativesReciprocal: true, + derivativeRevCelling: 0, + currency: address(0x123) + }); + + uint256 termsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); - vm.prank(receiver); - startLicenseId = licensingModule.mintLicenseTokens({ - licensorIpId: ipId, + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicensingModule__LicenseDenyMintLicenseToken.selector, + address(pilTemplate), + termsId, + ipId1 + ) + ); + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, licenseTemplate: address(pilTemplate), - licenseTermsId: commRemixTermsId, - amount: amount, - receiver: receiver, + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, royaltyContext: "" }); } + + function test_LicensingModule_revert_HookVerifyFail() public { + vm.prank(u.admin); + royaltyModule.whitelistRoyaltyToken(address(0x123), true); + + MockTokenGatedHook tokenGatedHook = new MockTokenGatedHook(); + PILTerms memory terms = PILTerms({ + transferable: true, + royaltyPolicy: address(royaltyPolicyLAP), + mintingFee: 0, + expiration: 0, + commercialUse: true, + commercialAttribution: true, + commercializerChecker: address(tokenGatedHook), + commercializerCheckerData: abi.encode(address(gatedNftBar)), + commercialRevShare: 0, + commercialRevCelling: 0, + derivativesAllowed: true, + derivativesAttribution: true, + derivativesApproval: false, + derivativesReciprocal: true, + derivativeRevCelling: 0, + currency: address(0x123) + }); + + uint256 termsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), termsId); + + uint256 gatedNftId = gatedNftBar.mint(ipOwner2); + + uint256 lcTokenId = licensingModule.mintLicenseTokens({ + licensorIpId: ipId1, + licenseTemplate: address(pilTemplate), + licenseTermsId: termsId, + amount: 1, + receiver: ipOwner2, + royaltyContext: "" + }); + assertEq(licenseToken.ownerOf(lcTokenId), ipOwner2); + assertEq(licenseToken.getLicenseTermsId(lcTokenId), termsId); + assertEq(licenseToken.getLicenseTemplate(lcTokenId), address(pilTemplate)); + assertEq(licenseToken.getLicensorIpId(lcTokenId), ipId1); + assertEq(licenseToken.getExpirationTime(lcTokenId), 0); + assertEq(licenseToken.totalMintedTokens(), 1); + assertEq(licenseToken.totalSupply(), 1); + assertEq(licenseToken.balanceOf(ipOwner2), 1); + + vm.prank(ipOwner2); + gatedNftBar.burn(gatedNftId); + + uint256[] memory licenseTokens = new uint256[](1); + licenseTokens[0] = lcTokenId; + vm.expectRevert( + abi.encodeWithSelector( + Errors.LicensingModule__LicenseTokenNotCompatibleForDerivative.selector, + ipId2, + licenseTokens + ) + ); + vm.prank(ipOwner2); + licensingModule.registerDerivativeWithLicenseTokens(ipId2, licenseTokens, ""); + } + + // test registerDerivativeWithLicenseTokens revert licenseTokenIds is empty + function test_LicensingModule_registerDerivativeWithLicenseTokens_revert_emptyLicenseTokens() public { + vm.expectRevert(Errors.LicensingModule__NoLicenseToken.selector); + vm.prank(ipOwner1); + licensingModule.registerDerivativeWithLicenseTokens(ipId1, new uint256[](0), ""); + } + + // test registerDerivative revert parentIpIds is empty + function test_LicensingModule_registerDerivative_revert_emptyParentIpIds() public { + vm.expectRevert(Errors.LicensingModule__NoParentIp.selector); + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, new address[](0), new uint256[](0), address(0), ""); + } + + function test_LicensingModule_registerDerivative_revert_parentIdsLengthMismatchWithLicenseIds() public { + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipId1; + vm.expectRevert(abi.encodeWithSelector(Errors.LicensingModule__LicenseTermsLengthMismatch.selector, 1, 0)); + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, parentIpIds, new uint256[](0), address(0), ""); + } + + function test_LicensingModule_registerDerivative_revert_IncompatibleLicenses() public { + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), socialRemixTermsId); + vm.prank(ipOwner2); + licensingModule.attachLicenseTerms(ipId2, address(pilTemplate), commUseTermsId); + + address[] memory parentIpIds = new address[](2); + parentIpIds[0] = ipId1; + parentIpIds[1] = ipId2; + + uint256[] memory licenseTermsIds = new uint256[](2); + licenseTermsIds[0] = socialRemixTermsId; + licenseTermsIds[1] = commUseTermsId; + + vm.expectRevert( + abi.encodeWithSelector(Errors.LicensingModule__LicenseNotCompatibleForDerivative.selector, ipId3) + ); + vm.prank(ipOwner3); + licensingModule.registerDerivative(ipId3, parentIpIds, licenseTermsIds, address(pilTemplate), ""); + } + + function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { + return this.onERC721Received.selector; + } } diff --git a/test/foundry/modules/licensing/PILicenseTemplate.t.sol b/test/foundry/modules/licensing/PILicenseTemplate.t.sol new file mode 100644 index 000000000..c4d28347b --- /dev/null +++ b/test/foundry/modules/licensing/PILicenseTemplate.t.sol @@ -0,0 +1,526 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.23; + +// external +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +// contracts +import { PILicenseTemplateErrors } from "../../../../contracts/lib/PILicenseTemplateErrors.sol"; +import { PILFlavors } from "../../../../contracts/lib/PILFlavors.sol"; +import { PILTerms } from "../../../../contracts/interfaces/modules/licensing/IPILicenseTemplate.sol"; + +// test +import { MockERC721 } from "../../mocks/token/MockERC721.sol"; +import { BaseTest } from "../../utils/BaseTest.t.sol"; + +contract PILicenseTemplateTest is BaseTest { + using Strings for *; + + MockERC721 internal mockNft = new MockERC721("MockERC721"); + MockERC721 internal gatedNftFoo = new MockERC721{ salt: bytes32(uint256(1)) }("GatedNftFoo"); + MockERC721 internal gatedNftBar = new MockERC721{ salt: bytes32(uint256(2)) }("GatedNftBar"); + + address public ipId1; + address public ipId2; + address public ipId3; + address public ipId5; + address public ipOwner1 = address(0x111); + address public ipOwner2 = address(0x222); + address public ipOwner3 = address(0x333); + address public ipOwner5 = address(0x444); + uint256 public tokenId1 = 1; + uint256 public tokenId2 = 2; + uint256 public tokenId3 = 3; + uint256 public tokenId5 = 5; + + address public licenseHolder = address(0x101); + + function setUp() public override { + super.setUp(); + // Create IPAccounts + mockNft.mintId(ipOwner1, tokenId1); + mockNft.mintId(ipOwner2, tokenId2); + mockNft.mintId(ipOwner3, tokenId3); + mockNft.mintId(ipOwner5, tokenId5); + + ipId1 = ipAssetRegistry.register(address(mockNft), tokenId1); + ipId2 = ipAssetRegistry.register(address(mockNft), tokenId2); + ipId3 = ipAssetRegistry.register(address(mockNft), tokenId3); + ipId5 = ipAssetRegistry.register(address(mockNft), tokenId5); + + vm.label(ipId1, "IPAccount1"); + vm.label(ipId2, "IPAccount2"); + vm.label(ipId3, "IPAccount3"); + vm.label(ipId5, "IPAccount5"); + } + // this contract is for testing for each PILicenseTemplate's functions + // register license terms with PILTerms struct + function test_PILicenseTemplate_registerLicenseTerms() public { + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + assertEq(defaultTermsId, 1); + (address royaltyPolicy, bytes memory royaltyData, uint256 mintingFee, address currency) = pilTemplate + .getRoyaltyPolicy(defaultTermsId); + assertEq(royaltyPolicy, address(0), "royaltyPolicy should be address(0)"); + assertEq(royaltyData, abi.encode(0), "royaltyData should be empty"); + assertEq(mintingFee, 0, "mintingFee should be 0"); + assertEq(currency, address(0), "currency should be address(0)"); + assertTrue(pilTemplate.isLicenseTransferable(defaultTermsId), "license should be transferable"); + assertEq(pilTemplate.getLicenseTermsId(PILFlavors.defaultValuesLicenseTerms()), 1); + assertEq(pilTemplate.getExpireTime(defaultTermsId, block.timestamp), 0, "expire time should be 0"); + assertTrue(pilTemplate.exists(defaultTermsId), "license terms should exist"); + + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + assertEq(socialRemixTermsId, 2); + (royaltyPolicy, royaltyData, mintingFee, currency) = pilTemplate.getRoyaltyPolicy(socialRemixTermsId); + assertEq(royaltyPolicy, address(0)); + assertEq(royaltyData, abi.encode(0)); + assertEq(mintingFee, 0); + assertEq(currency, address(0)); + assertTrue(pilTemplate.isLicenseTransferable(socialRemixTermsId)); + assertEq(pilTemplate.getLicenseTermsId(PILFlavors.nonCommercialSocialRemixing()), 2); + assertEq(pilTemplate.getExpireTime(socialRemixTermsId, block.timestamp), 0, "expire time should be 0"); + assertTrue(pilTemplate.exists(socialRemixTermsId), "license terms should exist"); + + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + assertEq(commUseTermsId, 3); + (royaltyPolicy, royaltyData, mintingFee, currency) = pilTemplate.getRoyaltyPolicy(commUseTermsId); + assertEq(royaltyPolicy, address(royaltyPolicyLAP)); + assertEq(royaltyData, abi.encode(0)); + assertEq(mintingFee, 100); + assertEq(currency, address(erc20)); + assertTrue(pilTemplate.isLicenseTransferable(commUseTermsId)); + assertEq( + pilTemplate.getLicenseTermsId(PILFlavors.commercialUse(100, address(erc20), address(royaltyPolicyLAP))), + 3 + ); + assertEq(pilTemplate.getExpireTime(commUseTermsId, block.timestamp), 0, "expire time should be 0"); + assertEq(pilTemplate.totalRegisteredLicenseTerms(), 3); + + uint256 commRemixTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialRemix({ + mintingFee: 100, + commercialRevShare: 10, + royaltyPolicy: address(royaltyPolicyLAP), + currencyToken: address(erc20) + }) + ); + assertEq(commRemixTermsId, 4); + (royaltyPolicy, royaltyData, mintingFee, currency) = pilTemplate.getRoyaltyPolicy(commRemixTermsId); + assertEq(royaltyPolicy, address(royaltyPolicyLAP)); + assertEq(royaltyData, abi.encode(10)); + assertEq(mintingFee, 100); + assertEq(currency, address(erc20)); + assertTrue(pilTemplate.isLicenseTransferable(commRemixTermsId)); + assertEq( + pilTemplate.getLicenseTermsId( + PILFlavors.commercialRemix(100, 10, address(royaltyPolicyLAP), address(erc20)) + ), + 4 + ); + assertEq(pilTemplate.getExpireTime(commRemixTermsId, block.timestamp), 0, "expire time should be 0"); + assertTrue(pilTemplate.exists(commRemixTermsId), "license terms should exist"); + + assertEq(pilTemplate.totalRegisteredLicenseTerms(), 4); + + uint256[] memory licenseTermsIds = new uint256[](4); + licenseTermsIds[0] = defaultTermsId; + licenseTermsIds[1] = socialRemixTermsId; + licenseTermsIds[2] = commUseTermsId; + licenseTermsIds[3] = commRemixTermsId; + assertEq(pilTemplate.getEarlierExpireTime(licenseTermsIds, block.timestamp), 0); + + assertEq(pilTemplate.toJson(defaultTermsId), _DefaultToJson()); + } + // register license terms twice + function test_PILicenseTemplate_registerLicenseTerms_twice() public { + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + uint256 defaultTermsId1 = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + assertEq(defaultTermsId, defaultTermsId1); + } + + function test_PILicenseTemplate_registerLicenseTerms_revert_InvalidInputs() public { + // mintingFee is 0 + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__RoyaltyPolicyNotWhitelisted.selector); + pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ mintingFee: 0, currencyToken: address(erc20), royaltyPolicy: address(0x9999) }) + ); + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__CurrencyTokenNotWhitelisted.selector); + pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(0x333), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__CurrencyTokenNotWhitelisted.selector); + pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(0x333), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__RoyaltyPolicyRequiresCurrencyToken.selector); + pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(0), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + + PILTerms memory terms = PILFlavors.nonCommercialSocialRemixing(); + terms.commercialAttribution = true; + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__CommercialDisabled_CantAddAttribution.selector); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.nonCommercialSocialRemixing(); + terms.commercializerChecker = address(0x9999); + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__CommercialDisabled_CantAddCommercializers.selector); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.nonCommercialSocialRemixing(); + terms.commercialRevShare = 10; + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__CommercialDisabled_CantAddRevShare.selector); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.nonCommercialSocialRemixing(); + terms.royaltyPolicy = address(royaltyPolicyLAP); + terms.currency = address(erc20); + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__CommercialDisabled_CantAddRoyaltyPolicy.selector); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }); + terms.royaltyPolicy = address(0); + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__CommercialEnabled_RoyaltyPolicyRequired.selector); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }); + terms.commercializerChecker = address(0x9999); + vm.expectRevert( + abi.encodeWithSelector( + PILicenseTemplateErrors.PILicenseTemplate__CommercializerCheckerDoesNotSupportHook.selector, + address(0x9999) + ) + ); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.defaultValuesLicenseTerms(); + terms.derivativesAttribution = true; + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__DerivativesDisabled_CantAddAttribution.selector); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.defaultValuesLicenseTerms(); + terms.derivativesApproval = true; + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__DerivativesDisabled_CantAddApproval.selector); + pilTemplate.registerLicenseTerms(terms); + + terms = PILFlavors.defaultValuesLicenseTerms(); + terms.derivativesReciprocal = true; + vm.expectRevert(PILicenseTemplateErrors.PILicenseTemplate__DerivativesDisabled_CantAddReciprocal.selector); + pilTemplate.registerLicenseTerms(terms); + } + + // get license terms ID by PILTerms struct + function test_PILicenseTemplate_getLicenseTermsId() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + assertEq( + pilTemplate.getLicenseTermsId(PILFlavors.commercialUse(100, address(erc20), address(royaltyPolicyLAP))), + commUseTermsId + ); + + assertEq( + pilTemplate.getLicenseTermsId(PILFlavors.commercialUse(999, address(123), address(royaltyPolicyLAP))), + 0 + ); + } + + // get license terms struct by ID + function test_PILicenseTemplate_getLicenseTerms() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + PILTerms memory terms = pilTemplate.getLicenseTerms(commUseTermsId); + assertEq(terms.mintingFee, 100); + assertEq(terms.currency, address(erc20)); + assertEq(terms.royaltyPolicy, address(royaltyPolicyLAP)); + } + + // test license terms exists + function test_PILicenseTemplate_exists() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + assertTrue(pilTemplate.exists(commUseTermsId)); + assertFalse(pilTemplate.exists(999)); + } + + // test verifyMintLicenseToken + function test_PILicenseTemplate_verifyMintLicenseToken() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + bool result = pilTemplate.verifyMintLicenseToken(commUseTermsId, ipId2, ipId1, 1); + assertTrue(result); + } + + function test_PILicenseTemplate_verifyMintLicenseToken_FromDerivativeIp_ButNotAttachedLicense() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commUseTermsId); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipId1; + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = commUseTermsId; + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(pilTemplate), ""); + + uint256 anotherTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + + bool result = pilTemplate.verifyMintLicenseToken(anotherTermsId, ipOwner3, ipId2, 1); + assertFalse(result); + } + + function test_PILicenseTemplate_verifyMintLicenseToken_FromDerivativeIp_NotReciprocal() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 0, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), commUseTermsId); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipId1; + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = commUseTermsId; + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(pilTemplate), ""); + + bool result = pilTemplate.verifyMintLicenseToken(commUseTermsId, ipOwner3, ipId2, 1); + assertFalse(result); + } + + // test verifyRegisterDerivative + function test_PILicenseTemplate_verifyRegisterDerivative() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + + bool result = pilTemplate.verifyRegisterDerivative(ipId2, ipId1, commUseTermsId, ipOwner2); + assertTrue(result); + } + + function test_PILicenseTemplate_verifyRegisterDerivative_WithApproval() public { + PILTerms memory terms = PILFlavors.nonCommercialSocialRemixing(); + terms.derivativesApproval = true; + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipId1); + pilTemplate.setApproval(ipId1, socialRemixTermsId, ipId2, true); + assertTrue(pilTemplate.isDerivativeApproved(ipId1, socialRemixTermsId, ipId2)); + + bool result = pilTemplate.verifyRegisterDerivative(ipId2, ipId1, socialRemixTermsId, ipOwner2); + assertTrue(result); + } + + function test_PILicenseTemplate_verifyRegisterDerivative_WithoutApproval() public { + PILTerms memory terms = PILFlavors.nonCommercialSocialRemixing(); + terms.derivativesApproval = true; + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(terms); + vm.prank(ipId1); + pilTemplate.setApproval(ipId1, socialRemixTermsId, ipId2, false); + assertFalse(pilTemplate.isDerivativeApproved(ipId1, socialRemixTermsId, ipId2)); + + bool result = pilTemplate.verifyRegisterDerivative(ipId2, ipId1, socialRemixTermsId, ipOwner2); + assertFalse(result); + } + + function test_PILicenseTemplate_verifyRegisterDerivative_derivativeNotAllowed() public { + PILTerms memory terms = PILFlavors.defaultValuesLicenseTerms(); + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(terms); + bool result = pilTemplate.verifyRegisterDerivative(ipId2, ipId1, socialRemixTermsId, ipOwner2); + assertFalse(result); + } + + // test verifyCompatibleLicenses + function test_PILicenseTemplate_verifyCompatibleLicenses() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + uint256 commRemixTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialRemix({ + mintingFee: 100, + commercialRevShare: 10, + royaltyPolicy: address(royaltyPolicyLAP), + currencyToken: address(erc20) + }) + ); + uint256[] memory licenseTermsIds = new uint256[](2); + licenseTermsIds[0] = commUseTermsId; + licenseTermsIds[1] = commRemixTermsId; + assertFalse(pilTemplate.verifyCompatibleLicenses(licenseTermsIds)); + + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + licenseTermsIds[0] = defaultTermsId; + licenseTermsIds[1] = socialRemixTermsId; + assertFalse(pilTemplate.verifyCompatibleLicenses(licenseTermsIds)); + + licenseTermsIds[0] = socialRemixTermsId; + licenseTermsIds[1] = commRemixTermsId; + assertFalse(pilTemplate.verifyCompatibleLicenses(licenseTermsIds)); + + licenseTermsIds[0] = socialRemixTermsId; + licenseTermsIds[1] = socialRemixTermsId; + assertTrue(pilTemplate.verifyCompatibleLicenses(licenseTermsIds)); + } + + // test verifyRegisterDerivativeForAllParents + function test_PILicenseTemplate_verifyRegisterDerivativeForAllParents() public { + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + uint256 commRemixTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialRemix({ + mintingFee: 100, + commercialRevShare: 10, + royaltyPolicy: address(royaltyPolicyLAP), + currencyToken: address(erc20) + }) + ); + uint256[] memory licenseTermsIds = new uint256[](2); + licenseTermsIds[0] = commUseTermsId; + licenseTermsIds[1] = commRemixTermsId; + address[] memory parentIpIds = new address[](2); + parentIpIds[0] = ipId1; + parentIpIds[1] = ipId3; + assertFalse(pilTemplate.verifyRegisterDerivativeForAllParents(ipId2, parentIpIds, licenseTermsIds, ipOwner2)); + + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + licenseTermsIds[0] = defaultTermsId; + licenseTermsIds[1] = socialRemixTermsId; + assertFalse(pilTemplate.verifyRegisterDerivativeForAllParents(ipId2, parentIpIds, licenseTermsIds, ipOwner2)); + + licenseTermsIds[0] = socialRemixTermsId; + licenseTermsIds[1] = commRemixTermsId; + assertFalse(pilTemplate.verifyRegisterDerivativeForAllParents(ipId2, parentIpIds, licenseTermsIds, ipOwner2)); + + licenseTermsIds[0] = socialRemixTermsId; + licenseTermsIds[1] = socialRemixTermsId; + assertTrue(pilTemplate.verifyRegisterDerivativeForAllParents(ipId2, parentIpIds, licenseTermsIds, ipOwner2)); + } + + // test isLicenseTransferable + function test_PILicenseTemplate_isLicenseTransferable() public { + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + assertTrue(pilTemplate.isLicenseTransferable(defaultTermsId)); + + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + assertTrue(pilTemplate.isLicenseTransferable(socialRemixTermsId)); + + uint256 commUseTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialUse({ + mintingFee: 100, + currencyToken: address(erc20), + royaltyPolicy: address(royaltyPolicyLAP) + }) + ); + assertTrue(pilTemplate.isLicenseTransferable(commUseTermsId)); + + uint256 commRemixTermsId = pilTemplate.registerLicenseTerms( + PILFlavors.commercialRemix({ + mintingFee: 100, + commercialRevShare: 10, + royaltyPolicy: address(royaltyPolicyLAP), + currencyToken: address(erc20) + }) + ); + assertTrue(pilTemplate.isLicenseTransferable(commRemixTermsId)); + + PILTerms memory terms = pilTemplate.getLicenseTerms(commRemixTermsId); + terms.transferable = false; + uint256 nonTransferableTermsId = pilTemplate.registerLicenseTerms(terms); + assertFalse(pilTemplate.isLicenseTransferable(nonTransferableTermsId)); + } + + function test_PILicenseTemplate_getEarlierExpiredTime_WithEmptyLicenseTerms() public { + uint256[] memory licenseTermsIds = new uint256[](0); + assertEq(pilTemplate.getEarlierExpireTime(licenseTermsIds, block.timestamp), 0); + } + + function test_PILicenseTemplate_name() public { + assertEq(pilTemplate.name(), "pil"); + } + + function test_PILicenseTemplate_getMetadataURI() public { + assertEq( + pilTemplate.getMetadataURI(), + "https://github.com/storyprotocol/protocol-core/blob/main/PIL-Beta-2024-02.pdf" + ); + } + + function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { + return this.onERC721Received.selector; + } + + function _DefaultToJson() internal pure returns (string memory) { + /* solhint-disable */ + return + '{"trait_type": "Expiration", "value": "never"},{"trait_type": "Currency", "value": "0x0000000000000000000000000000000000000000"},{"trait_type": "Commercial Use", "value": "false"},{"trait_type": "Commercial Attribution", "value": "false"},{"trait_type": "Commercial Revenue Share", "max_value": 1000, "value": 0},{"trait_type": "Commercial Revenue Celling", "value": 0},{"trait_type": "Commercializer Check", "value": "0x0000000000000000000000000000000000000000"},{"trait_type": "Derivatives Allowed", "value": "false"},{"trait_type": "Derivatives Attribution", "value": "false"},{"trait_type": "Derivatives Revenue Celling", "value": 0},{"trait_type": "Derivatives Approval", "value": "false"},{"trait_type": "Derivatives Reciprocal", "value": "false"},'; + /* solhint-enable */ + } +} diff --git a/test/foundry/registries/LicenseRegistry.t.sol b/test/foundry/registries/LicenseRegistry.t.sol new file mode 100644 index 000000000..11f6f55d9 --- /dev/null +++ b/test/foundry/registries/LicenseRegistry.t.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.23; + +// external +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; + +// contracts +import { IIPAccount } from "../../../contracts/interfaces/IIPAccount.sol"; +import { Errors } from "../../../contracts/lib/Errors.sol"; +import { PILFlavors } from "../../../contracts/lib/PILFlavors.sol"; +import { MockLicenseTemplate } from "../mocks/module/MockLicenseTemplate.sol"; +import { IPAccountStorageOps } from "../../../contracts/lib/IPAccountStorageOps.sol"; +import { Licensing } from "../../../contracts/lib/Licensing.sol"; + +// test +import { MockERC721 } from "../mocks/token/MockERC721.sol"; +import { BaseTest } from "../utils/BaseTest.t.sol"; + +contract LicenseRegistryTest is BaseTest { + using Strings for *; + using IPAccountStorageOps for IIPAccount; + + error ERC721NonexistentToken(uint256 tokenId); + + MockERC721 internal mockNft = new MockERC721("MockERC721"); + MockERC721 internal gatedNftFoo = new MockERC721{ salt: bytes32(uint256(1)) }("GatedNftFoo"); + MockERC721 internal gatedNftBar = new MockERC721{ salt: bytes32(uint256(2)) }("GatedNftBar"); + + address public ipId1; + address public ipId2; + address public ipId3; + address public ipId5; + address public ipOwner1 = address(0x111); + address public ipOwner2 = address(0x222); + address public ipOwner3 = address(0x333); + address public ipOwner5 = address(0x444); + uint256 public tokenId1 = 1; + uint256 public tokenId2 = 2; + uint256 public tokenId3 = 3; + uint256 public tokenId5 = 5; + + address public licenseHolder = address(0x101); + + function setUp() public override { + super.setUp(); + // Create IPAccounts + mockNft.mintId(ipOwner1, tokenId1); + mockNft.mintId(ipOwner2, tokenId2); + mockNft.mintId(ipOwner3, tokenId3); + mockNft.mintId(ipOwner5, tokenId5); + + ipId1 = ipAssetRegistry.register(address(mockNft), tokenId1); + ipId2 = ipAssetRegistry.register(address(mockNft), tokenId2); + ipId3 = ipAssetRegistry.register(address(mockNft), tokenId3); + ipId5 = ipAssetRegistry.register(address(mockNft), tokenId5); + + vm.label(ipId1, "IPAccount1"); + vm.label(ipId2, "IPAccount2"); + vm.label(ipId3, "IPAccount3"); + vm.label(ipId5, "IPAccount5"); + } + + function test_LicenseRegistry_setDisputeModule() public { + vm.prank(admin); + licenseRegistry.setDisputeModule(address(123)); + assertEq(address(licenseRegistry.disputeModule()), address(123)); + } + + function test_LicenseRegistry_setLicensingModule() public { + vm.prank(admin); + licenseRegistry.setLicensingModule(address(123)); + assertEq(address(licenseRegistry.licensingModule()), address(123)); + } + + function test_LicenseRegistry_setDisputeModule_revert_ZeroAddress() public { + vm.expectRevert(Errors.LicenseRegistry__ZeroDisputeModule.selector); + vm.prank(admin); + licenseRegistry.setDisputeModule(address(0)); + } + + function test_LicenseRegistry_setLicensingModule_revert_ZeroAddress() public { + vm.expectRevert(Errors.LicenseRegistry__ZeroLicensingModule.selector); + vm.prank(admin); + licenseRegistry.setLicensingModule(address(0)); + } + + function test_LicenseRegistry_setDefaultLicenseTerms() public { + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(admin); + licenseRegistry.setDefaultLicenseTerms(address(pilTemplate), socialRemixTermsId); + (address defaultLicenseTemplate, uint256 defaultLicenseTermsId) = licenseRegistry.getDefaultLicenseTerms(); + assertEq(defaultLicenseTemplate, address(pilTemplate)); + assertEq(defaultLicenseTermsId, socialRemixTermsId); + } + + // test registerLicenseTemplate + function test_LicenseRegistry_registerLicenseTemplate() public { + MockLicenseTemplate pilTemplate2 = new MockLicenseTemplate(); + vm.prank(admin); + licenseRegistry.registerLicenseTemplate(address(pilTemplate2)); + assertTrue(licenseRegistry.isRegisteredLicenseTemplate(address(pilTemplate2))); + } + + function test_LicenseRegistry_registerLicenseTemplate_revert_NotImplementedInterface() public { + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__NotLicenseTemplate.selector, address(0x123))); + vm.prank(admin); + licenseRegistry.registerLicenseTemplate(address(0x123)); + } + + function test_LicenseRegistry_setExpireTime() public { + vm.prank(address(licensingModule)); + licenseRegistry.setExpireTime(ipId1, block.timestamp + 100); + assertEq(licenseRegistry.getExpireTime(ipId1), block.timestamp + 100); + assertEq( + IIPAccount(payable(ipId1)).getUint256(address(licenseRegistry), licenseRegistry.EXPIRATION_TIME()), + block.timestamp + 100 + ); + } + + function test_LicenseRegistry_setMintingLicenseConfigForLicense() public { + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + Licensing.MintingLicenseConfig memory mintingLicenseConfig = Licensing.MintingLicenseConfig({ + isSet: true, + mintingFee: 100, + mintingFeeModule: address(0), + receiverCheckModule: address(0), + receiverCheckData: "" + }); + + vm.prank(address(licensingModule)); + licenseRegistry.setMintingLicenseConfigForLicense( + ipId1, + address(pilTemplate), + defaultTermsId, + mintingLicenseConfig + ); + Licensing.MintingLicenseConfig memory returnedMintingLicenseConfig = licenseRegistry.getMintingLicenseConfig( + ipId1, + address(pilTemplate), + defaultTermsId + ); + assertEq(returnedMintingLicenseConfig.mintingFee, 100); + assertEq(returnedMintingLicenseConfig.mintingFeeModule, address(0)); + assertEq(returnedMintingLicenseConfig.receiverCheckModule, address(0)); + assertEq(returnedMintingLicenseConfig.receiverCheckData, ""); + } + + function test_LicenseRegistry_setMintingLicenseConfigForLicense_revert_UnregisteredTemplate() public { + MockLicenseTemplate pilTemplate2 = new MockLicenseTemplate(); + uint256 termsId = pilTemplate2.registerLicenseTerms(); + Licensing.MintingLicenseConfig memory mintingLicenseConfig = Licensing.MintingLicenseConfig({ + isSet: true, + mintingFee: 100, + mintingFeeModule: address(0), + receiverCheckModule: address(0), + receiverCheckData: "" + }); + + vm.expectRevert( + abi.encodeWithSelector(Errors.LicenseRegistry__UnregisteredLicenseTemplate.selector, address(pilTemplate2)) + ); + vm.prank(address(licensingModule)); + licenseRegistry.setMintingLicenseConfigForLicense(ipId1, address(pilTemplate2), termsId, mintingLicenseConfig); + } + + function test_LicenseRegistry_setMintingLicenseConfigForIp() public { + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + Licensing.MintingLicenseConfig memory mintingLicenseConfig = Licensing.MintingLicenseConfig({ + isSet: true, + mintingFee: 100, + mintingFeeModule: address(0), + receiverCheckModule: address(0), + receiverCheckData: "" + }); + + vm.prank(address(licensingModule)); + licenseRegistry.setMintingLicenseConfigForIp(ipId1, mintingLicenseConfig); + + Licensing.MintingLicenseConfig memory returnedMintingLicenseConfig = licenseRegistry.getMintingLicenseConfig( + ipId1, + address(pilTemplate), + defaultTermsId + ); + assertEq(returnedMintingLicenseConfig.mintingFee, 100); + assertEq(returnedMintingLicenseConfig.mintingFeeModule, address(0)); + assertEq(returnedMintingLicenseConfig.receiverCheckModule, address(0)); + assertEq(returnedMintingLicenseConfig.receiverCheckData, ""); + } + + // test attachLicenseTermsToIp + function test_LicenseRegistry_attachLicenseTermsToIp_revert_CannotAttachToDerivativeIP() public { + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(admin); + licenseRegistry.setDefaultLicenseTerms(address(pilTemplate), socialRemixTermsId); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipId1; + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = socialRemixTermsId; + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(pilTemplate), ""); + + uint256 defaultTermsId = pilTemplate.registerLicenseTerms(PILFlavors.defaultValuesLicenseTerms()); + vm.expectRevert(Errors.LicensingModule__DerivativesCannotAddLicenseTerms.selector); + vm.prank(address(licensingModule)); + licenseRegistry.attachLicenseTermsToIp(ipId2, address(pilTemplate), defaultTermsId); + } + + function test_LicenseRegistry_registerDerivativeIp_revert_parentsArrayEmpty() public { + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(admin); + licenseRegistry.setDefaultLicenseTerms(address(pilTemplate), socialRemixTermsId); + + address[] memory parentIpIds = new address[](0); + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = socialRemixTermsId; + vm.expectRevert(Errors.LicenseRegistry__NoParentIp.selector); + vm.prank(address(licensingModule)); + licenseRegistry.registerDerivativeIp(ipId2, parentIpIds, address(pilTemplate), licenseTermsIds); + } + + // test getAttachedLicenseTerms + function test_LicenseRegistry_getAttachedLicenseTerms_revert_OutOfIndex() public { + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + + vm.prank(ipOwner1); + licensingModule.attachLicenseTerms(ipId1, address(pilTemplate), socialRemixTermsId); + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__IndexOutOfBounds.selector, ipId1, 1, 1)); + licenseRegistry.getAttachedLicenseTerms(ipId1, 1); + } + + // test getDerivativeIp revert IndexOutOfBounds( + function test_LicenseRegistry_getDerivativeIp_revert_IndexOutOfBounds() public { + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(admin); + licenseRegistry.setDefaultLicenseTerms(address(pilTemplate), socialRemixTermsId); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipId1; + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = socialRemixTermsId; + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(pilTemplate), ""); + + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__IndexOutOfBounds.selector, ipId1, 1, 1)); + licenseRegistry.getDerivativeIp(ipId1, 1); + } + + function test_LicenseRegistry_getParentIp_revert_IndexOutOfBounds() public { + uint256 socialRemixTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing()); + vm.prank(admin); + licenseRegistry.setDefaultLicenseTerms(address(pilTemplate), socialRemixTermsId); + + address[] memory parentIpIds = new address[](1); + parentIpIds[0] = ipId1; + uint256[] memory licenseTermsIds = new uint256[](1); + licenseTermsIds[0] = socialRemixTermsId; + vm.prank(ipOwner2); + licensingModule.registerDerivative(ipId2, parentIpIds, licenseTermsIds, address(pilTemplate), ""); + + vm.expectRevert(abi.encodeWithSelector(Errors.LicenseRegistry__IndexOutOfBounds.selector, ipId2, 1, 1)); + licenseRegistry.getParentIp(ipId2, 1); + } + + function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { + return this.onERC721Received.selector; + } +} From e88c07c6a1671ced8c48080f84400506558dfaac Mon Sep 17 00:00:00 2001 From: Andy Wu Date: Thu, 11 Apr 2024 18:02:04 -0700 Subject: [PATCH 21/31] Feat/add reusable forge code coverage (#72) * Refactor Governance into AccessManager (#43) * refactor Governance into AccessManager * remove view method in script * [feat] add reusable forge code coverage workflow --------- Co-authored-by: Ramarti --- .github/workflows/foundry_ci.yml | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/.github/workflows/foundry_ci.yml b/.github/workflows/foundry_ci.yml index e62faa6be..1fed62765 100644 --- a/.github/workflows/foundry_ci.yml +++ b/.github/workflows/foundry_ci.yml @@ -7,24 +7,16 @@ on: jobs: - # Add job timestamp - print_timestamp: - runs-on: ubuntu-latest - steps: - - name: Generate timestamp - run: | - echo "TIMESTAMP=$(TZ='America/Los_Angeles' date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_ENV - - - name: Print timestamp - run: | - echo "Execution time (Pacific Time Zone) $TIMESTAMP" + # Add a timestamp to the build + Timestamp: + uses: storyprotocol/gha-workflows/.github/workflows/reusable-timestamp.yml@main foundry-test: strategy: fail-fast: true name: Foundry Unit Test runs-on: ubuntu-latest - needs: print_timestamp + needs: [Timestamp] steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: @@ -60,3 +52,12 @@ jobs: - name: Run solhint run: npx solhint contracts/**/*.sol + + Coverage: + needs: [foundry-test] + uses: storyprotocol/gha-workflows/.github/workflows/reusable-forge-code-coverage.yml@main + # FOR LOCAL TESTING ONLY + # uses: ./../gha-workflows/.github/workflows/reusable-forge-code-coverage.yml + with: + exclude_paths: 'test/*, script/*' + branch_coverage: true From 28d94d5b117d242d2c7f70f5c3b59c2237684136 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 12:36:52 +0300 Subject: [PATCH 22/31] eliminate DataUniqueness --- contracts/lib/DataUniqueness.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 contracts/lib/DataUniqueness.sol diff --git a/contracts/lib/DataUniqueness.sol b/contracts/lib/DataUniqueness.sol new file mode 100644 index 000000000..e69de29bb From bb51fdba7eb801c6fc16794aa1d9b01c5ce708e6 Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 14:29:28 +0300 Subject: [PATCH 23/31] add new tagDerivativeIfParentInfringed function --- contracts/lib/Errors.sol | 9 +++++++++ .../licensing/LicensingIntegration.t.sol | 2 +- .../modules/dispute/DisputeModule.t.sol | 19 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 1632a1ae6..1b088190b 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -241,11 +241,20 @@ library Errors { error DisputeModule__ZeroAssetRegistry(); error DisputeModule__ZeroController(); error DisputeModule__ZeroAccessManager(); + error DisputeModule__ParentIpIdMismatch(); + error DisputeModule__ParentNotTagged(); + error DisputeModule__NotDerivative(); + error DisputeModule__ParentDisputeNotResolved(); + error DisputeModule__ZeroLicenseRegistry(); + error DisputeModule__ZeroAssetRegistry(); + error DisputeModule__ZeroController(); + error DisputeModule__ZeroAccessManager(); error ArbitrationPolicySP__ZeroDisputeModule(); error ArbitrationPolicySP__ZeroPaymentToken(); error ArbitrationPolicySP__NotDisputeModule(); error ArbitrationPolicySP__ZeroAccessManager(); + error ArbitrationPolicySP__ZeroAccessManager(); //////////////////////////////////////////////////////////////////////////// // Royalty Module // diff --git a/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol b/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol index 2746c02a3..024d3d3dc 100644 --- a/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol +++ b/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol @@ -119,7 +119,7 @@ contract e2e is Test { ); vm.label(address(royaltyModule), "RoyaltyModule"); - impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry))); + impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry))); disputeModule = DisputeModule( TestProxyHelper.deployUUPSProxy( impl, diff --git a/test/foundry/modules/dispute/DisputeModule.t.sol b/test/foundry/modules/dispute/DisputeModule.t.sol index ac964c60f..2473f99d8 100644 --- a/test/foundry/modules/dispute/DisputeModule.t.sol +++ b/test/foundry/modules/dispute/DisputeModule.t.sol @@ -571,6 +571,25 @@ contract DisputeModuleTest is BaseTest { disputeModule.resolveDispute(2, ""); } + function test_DisputeModule_resolveDispute_revert_ParentDisputeNotResolved() public { + // raise dispute + vm.startPrank(ipAccount1); + IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); + disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); + vm.stopPrank(); + + // set dispute judgement + vm.startPrank(arbitrationRelayer); + disputeModule.setDisputeJudgement(1, true, ""); + vm.stopPrank(); + + // tag derivative + disputeModule.tagDerivativeIfParentInfringed(ipAddr, ipAddr2, 1); + + vm.expectRevert(Errors.DisputeModule__ParentDisputeNotResolved.selector); + disputeModule.resolveDispute(2); + } + function test_DisputeModule_resolveDispute() public { // raise dispute vm.startPrank(ipAccount1); From 94b3c641b03af3bc687e151ddeaafa429ba5062e Mon Sep 17 00:00:00 2001 From: Spablob Date: Thu, 11 Apr 2024 14:31:52 +0300 Subject: [PATCH 24/31] format fix --- .../integration/flows/licensing/LicensingIntegration.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol b/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol index 024d3d3dc..d088c9c7c 100644 --- a/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol +++ b/test/foundry/integration/flows/licensing/LicensingIntegration.t.sol @@ -119,7 +119,9 @@ contract e2e is Test { ); vm.label(address(royaltyModule), "RoyaltyModule"); - impl = address(new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry))); + impl = address( + new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry)) + ); disputeModule = DisputeModule( TestProxyHelper.deployUUPSProxy( impl, From 0f9f98a93ad473b7992d336caf7bee4c377e14c5 Mon Sep 17 00:00:00 2001 From: Spablob Date: Fri, 12 Apr 2024 08:54:49 +0300 Subject: [PATCH 25/31] rebase: eliminate data uniqueness --- contracts/lib/DataUniqueness.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 contracts/lib/DataUniqueness.sol diff --git a/contracts/lib/DataUniqueness.sol b/contracts/lib/DataUniqueness.sol deleted file mode 100644 index e69de29bb..000000000 From 929e14f8e439b729e4d1386f01b09542b362a278 Mon Sep 17 00:00:00 2001 From: Spablob Date: Fri, 12 Apr 2024 09:03:54 +0300 Subject: [PATCH 26/31] fix rebase duplication --- contracts/lib/Errors.sol | 9 --------- .../modules/dispute/DisputeModule.t.sol | 19 ------------------- 2 files changed, 28 deletions(-) diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 1b088190b..1632a1ae6 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -241,20 +241,11 @@ library Errors { error DisputeModule__ZeroAssetRegistry(); error DisputeModule__ZeroController(); error DisputeModule__ZeroAccessManager(); - error DisputeModule__ParentIpIdMismatch(); - error DisputeModule__ParentNotTagged(); - error DisputeModule__NotDerivative(); - error DisputeModule__ParentDisputeNotResolved(); - error DisputeModule__ZeroLicenseRegistry(); - error DisputeModule__ZeroAssetRegistry(); - error DisputeModule__ZeroController(); - error DisputeModule__ZeroAccessManager(); error ArbitrationPolicySP__ZeroDisputeModule(); error ArbitrationPolicySP__ZeroPaymentToken(); error ArbitrationPolicySP__NotDisputeModule(); error ArbitrationPolicySP__ZeroAccessManager(); - error ArbitrationPolicySP__ZeroAccessManager(); //////////////////////////////////////////////////////////////////////////// // Royalty Module // diff --git a/test/foundry/modules/dispute/DisputeModule.t.sol b/test/foundry/modules/dispute/DisputeModule.t.sol index 2473f99d8..ac964c60f 100644 --- a/test/foundry/modules/dispute/DisputeModule.t.sol +++ b/test/foundry/modules/dispute/DisputeModule.t.sol @@ -571,25 +571,6 @@ contract DisputeModuleTest is BaseTest { disputeModule.resolveDispute(2, ""); } - function test_DisputeModule_resolveDispute_revert_ParentDisputeNotResolved() public { - // raise dispute - vm.startPrank(ipAccount1); - IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); - disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); - vm.stopPrank(); - - // set dispute judgement - vm.startPrank(arbitrationRelayer); - disputeModule.setDisputeJudgement(1, true, ""); - vm.stopPrank(); - - // tag derivative - disputeModule.tagDerivativeIfParentInfringed(ipAddr, ipAddr2, 1); - - vm.expectRevert(Errors.DisputeModule__ParentDisputeNotResolved.selector); - disputeModule.resolveDispute(2); - } - function test_DisputeModule_resolveDispute() public { // raise dispute vm.startPrank(ipAccount1); From 1520309a4cbcb848200c13ce3f63922314e502a5 Mon Sep 17 00:00:00 2001 From: Spablob Date: Fri, 12 Apr 2024 09:55:32 +0300 Subject: [PATCH 27/31] revert royalty related fixes --- .../royalty/policies/IIpRoyaltyVault.sol | 6 +- contracts/modules/royalty/RoyaltyModule.sol | 1 + .../royalty/policies/IpRoyaltyVault.sol | 51 ++-- .../modules/royalty/IpRoyaltyVault.t.sol | 9 +- .../modules/royalty/RoyaltyModule.t.sol | 288 +++++++++++++----- .../modules/royalty/RoyaltyPolicyLAP.t.sol | 52 ++-- 6 files changed, 269 insertions(+), 138 deletions(-) diff --git a/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol b/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol index 079a7fe35..d4e685403 100644 --- a/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol +++ b/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol @@ -52,8 +52,8 @@ interface IIpRoyaltyVault { /// @notice Allows token holders to claim revenue token based on the token balance at certain snapshot /// @param snapshotId The snapshot id - /// @param tokenList The list of revenue tokens to claim - function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokenList) external; + /// @param tokens The list of revenue tokens to claim + function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokens) external; /// @notice Allows token holders to claim by a list of snapshot ids based on the token balance at certain snapshot /// @param snapshotIds The list of snapshot ids @@ -103,4 +103,4 @@ interface IIpRoyaltyVault { /// @notice The list of revenue tokens in the vault function tokens() external view returns (address[] memory); -} +} \ No newline at end of file diff --git a/contracts/modules/royalty/RoyaltyModule.sol b/contracts/modules/royalty/RoyaltyModule.sol index f921d96d8..073173e51 100644 --- a/contracts/modules/royalty/RoyaltyModule.sol +++ b/contracts/modules/royalty/RoyaltyModule.sol @@ -221,6 +221,7 @@ contract RoyaltyModule is RoyaltyModuleStorage storage $ = _getRoyaltyModuleStorage(); if (!$.isWhitelistedRoyaltyToken[token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken(); if (IDisputeModule($.disputeModule).isIpTagged(receiverIpId)) revert Errors.RoyaltyModule__IpIsTagged(); + if (licenseRoyaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet(); if (!$.isWhitelistedRoyaltyPolicy[licenseRoyaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); diff --git a/contracts/modules/royalty/policies/IpRoyaltyVault.sol b/contracts/modules/royalty/policies/IpRoyaltyVault.sol index f26655296..d2bd1ae43 100644 --- a/contracts/modules/royalty/policies/IpRoyaltyVault.sol +++ b/contracts/modules/royalty/policies/IpRoyaltyVault.sol @@ -100,11 +100,6 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy __ERC20_init(name, symbol); } - /// @notice Returns the number royalty token decimals - function decimals() public view override returns (uint8) { - return 6; - } - /// @notice Adds a new revenue token to the vault /// @param token The address of the revenue token /// @dev Only callable by the royalty policy LAP @@ -128,24 +123,24 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy uint32 unclaimedTokens = $.unclaimedRoyaltyTokens; $.unclaimedAtSnapshot[snapshotId] = unclaimedTokens; - address[] memory tokenList = $.tokens.values(); + address[] memory tokens = $.tokens.values(); - for (uint256 i = 0; i < tokenList.length; i++) { - uint256 tokenBalance = IERC20Upgradeable(tokenList[i]).balanceOf(address(this)); + for (uint256 i = 0; i < tokens.length; i++) { + uint256 tokenBalance = IERC20Upgradeable(tokens[i]).balanceOf(address(this)); if (tokenBalance == 0) { - $.tokens.remove(tokenList[i]); + $.tokens.remove(tokens[i]); continue; } - uint256 newRevenue = tokenBalance - $.claimVaultAmount[tokenList[i]] - $.ancestorsVaultAmount[tokenList[i]]; + uint256 newRevenue = tokenBalance - $.claimVaultAmount[tokens[i]] - $.ancestorsVaultAmount[tokens[i]]; if (newRevenue == 0) continue; uint256 ancestorsTokens = (newRevenue * unclaimedTokens) / totalSupply(); - $.ancestorsVaultAmount[tokenList[i]] += ancestorsTokens; + $.ancestorsVaultAmount[tokens[i]] += ancestorsTokens; uint256 claimableTokens = newRevenue - ancestorsTokens; - $.claimableAtSnapshot[snapshotId][tokenList[i]] = claimableTokens; - $.claimVaultAmount[tokenList[i]] += claimableTokens; + $.claimableAtSnapshot[snapshotId][tokens[i]] = claimableTokens; + $.claimVaultAmount[tokens[i]] += claimableTokens; } emit SnapshotCompleted(snapshotId, block.timestamp, unclaimedTokens); @@ -164,19 +159,19 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy /// @notice Allows token holders to claim revenue token based on the token balance at certain snapshot /// @param snapshotId The snapshot id - /// @param tokenList The list of revenue tokens to claim - function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokenList) external nonReentrant { + /// @param tokens The list of revenue tokens to claim + function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokens) external nonReentrant { IpRoyaltyVaultStorage storage $ = _getIpRoyaltyVaultStorage(); - for (uint256 i = 0; i < tokenList.length; i++) { - uint256 claimableToken = _claimableRevenue(msg.sender, snapshotId, tokenList[i]); + for (uint256 i = 0; i < tokens.length; i++) { + uint256 claimableToken = _claimableRevenue(msg.sender, snapshotId, tokens[i]); if (claimableToken == 0) continue; - $.isClaimedAtSnapshot[snapshotId][msg.sender][tokenList[i]] = true; - $.claimVaultAmount[tokenList[i]] -= claimableToken; - IERC20Upgradeable(tokenList[i]).safeTransfer(msg.sender, claimableToken); + $.isClaimedAtSnapshot[snapshotId][msg.sender][tokens[i]] = true; + $.claimVaultAmount[tokens[i]] -= claimableToken; + IERC20Upgradeable(tokens[i]).safeTransfer(msg.sender, claimableToken); - emit RevenueTokenClaimed(msg.sender, tokenList[i], claimableToken); + emit RevenueTokenClaimed(msg.sender, tokens[i], claimableToken); } } @@ -249,19 +244,19 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy function _collectAccruedTokens(uint256 royaltyTokensToClaim, address ancestorIpId) internal { IpRoyaltyVaultStorage storage $ = _getIpRoyaltyVaultStorage(); - address[] memory tokenList = $.tokens.values(); + address[] memory tokens = $.tokens.values(); - for (uint256 i = 0; i < tokenList.length; ++i) { + for (uint256 i = 0; i < tokens.length; ++i) { // the only case in which unclaimedRoyaltyTokens can be 0 is when the vault is empty and everyone claimed // in which case the call will revert upstream with IpRoyaltyVault__AlreadyClaimed error - uint256 collectAmount = ($.ancestorsVaultAmount[tokenList[i]] * royaltyTokensToClaim) / + uint256 collectAmount = ($.ancestorsVaultAmount[tokens[i]] * royaltyTokensToClaim) / $.unclaimedRoyaltyTokens; if (collectAmount == 0) continue; - $.ancestorsVaultAmount[tokenList[i]] -= collectAmount; - IERC20Upgradeable(tokenList[i]).safeTransfer(ancestorIpId, collectAmount); + $.ancestorsVaultAmount[tokens[i]] -= collectAmount; + IERC20Upgradeable(tokens[i]).safeTransfer(ancestorIpId, collectAmount); - emit RevenueTokenClaimed(ancestorIpId, tokenList[i], collectAmount); + emit RevenueTokenClaimed(ancestorIpId, tokens[i], collectAmount); } } @@ -331,4 +326,4 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy $.slot := IpRoyaltyVaultStorageLocation } } -} +} \ No newline at end of file diff --git a/test/foundry/modules/royalty/IpRoyaltyVault.t.sol b/test/foundry/modules/royalty/IpRoyaltyVault.t.sol index edf0e1f2a..5f7e6a5e8 100644 --- a/test/foundry/modules/royalty/IpRoyaltyVault.t.sol +++ b/test/foundry/modules/royalty/IpRoyaltyVault.t.sol @@ -28,8 +28,6 @@ contract TestIpRoyaltyVault is BaseTest { (, address IpRoyaltyVault2, , , ) = royaltyPolicyLAP.getRoyaltyData(address(2)); ipRoyaltyVault = IpRoyaltyVault(IpRoyaltyVault2); - - assertEq(ipRoyaltyVault.ipId(), address(2)); } function _setupMaxUniqueTree() internal { @@ -131,6 +129,7 @@ contract TestIpRoyaltyVault is BaseTest { vm.startPrank(address(licensingModule)); royaltyModule.onLinkToParents(address(100), address(royaltyPolicyLAP), parents, encodedLicenseData, ""); + //royaltyPolicyLAP.onLinkToParents(address(100), parents, encodedLicenseData, ""); } function test_IpRoyaltyVault_AddIpRoyaltyVaultTokens_NotRoyaltyPolicyLAP() public { @@ -138,10 +137,6 @@ contract TestIpRoyaltyVault is BaseTest { ipRoyaltyVault.addIpRoyaltyVaultTokens(address(0)); } - function test_IpRoyaltyVault_decimals() public { - assertEq(ipRoyaltyVault.decimals(), 6); - } - function test_IpRoyaltyVault_AddIpRoyaltyVaultTokens() public { vm.startPrank(address(royaltyPolicyLAP)); ipRoyaltyVault.addIpRoyaltyVaultTokens(address(1)); @@ -414,4 +409,4 @@ contract TestIpRoyaltyVault is BaseTest { accruedCollectableRevenue ); } -} +} \ 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 ba5e7e5b5..342f58a99 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -24,6 +24,21 @@ contract TestRoyaltyModule is BaseTest { address internal ipAddr; address internal arbitrationRelayer; + struct InitParams { + address[] targetAncestors; + uint32[] targetRoyaltyAmount; + address[] parentAncestors1; + address[] parentAncestors2; + uint32[] parentAncestorsRoyalties1; + uint32[] parentAncestorsRoyalties2; + } + + InitParams internal initParamsMax; + bytes internal MAX_ANCESTORS; + address[] internal MAX_ANCESTORS_ = new address[](14); + uint32[] internal MAX_ANCESTORS_ROYALTY_ = new uint32[](14); + address[] internal parentsIpIds100; + RoyaltyPolicyLAP internal royaltyPolicyLAP2; function setUp() public override { @@ -90,29 +105,55 @@ contract TestRoyaltyModule is BaseTest { function _setupTree() internal { // init royalty policy for roots - royaltyModule.onLicenseMinting(address(7), address(royaltyPolicyLAP), abi.encode(uint32(7)), ""); - royaltyModule.onLicenseMinting(address(8), address(royaltyPolicyLAP), abi.encode(uint32(8)), ""); + address[] memory nullTargetAncestors = new address[](0); + uint32[] memory nullTargetRoyaltyAmount = new uint32[](0); + uint32[] memory parentRoyalties = new uint32[](0); + address[] memory nullParentAncestors1 = new address[](0); + address[] memory nullParentAncestors2 = new address[](0); + uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); + uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); + InitParams memory nullInitParams = InitParams({ + targetAncestors: nullTargetAncestors, + targetRoyaltyAmount: nullTargetRoyaltyAmount, + parentAncestors1: nullParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + bytes memory nullBytes = abi.encode(nullInitParams); + + royaltyModule.onLicenseMinting(address(7), address(royaltyPolicyLAP), abi.encode(uint32(7)), nullBytes); + royaltyModule.onLicenseMinting(address(8), address(royaltyPolicyLAP), abi.encode(uint32(8)), nullBytes); // init 2nd level with children address[] memory parents = new address[](2); - uint32[] memory parentRoyalties = new uint32[](2); + address[] memory targetAncestors1 = new address[](2); + uint32[] memory targetRoyaltyAmount1 = new uint32[](2); + uint32[] memory parentRoyalties1 = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); // 3 is child of 7 and 8 parents[0] = address(7); parents[1] = address(8); - parentRoyalties[0] = 7; - parentRoyalties[1] = 8; - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + parentRoyalties1[0] = 7; + parentRoyalties1[1] = 8; + targetAncestors1[0] = address(7); + targetAncestors1[1] = address(8); + targetRoyaltyAmount1[0] = 7; + targetRoyaltyAmount1[1] = 8; + InitParams memory initParams = InitParams({ + targetAncestors: targetAncestors1, + targetRoyaltyAmount: targetRoyaltyAmount1, + parentAncestors1: nullParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } - royaltyModule.onLinkToParents(address(3), address(royaltyPolicyLAP), parents, encodedLicenseData, ""); - } - - function test_RoyaltyModule_initialize_revert_ZeroAccessManager() public { - address impl = address(new RoyaltyModule()); - vm.expectRevert(Errors.RoyaltyModule__ZeroAccessManager.selector); - RoyaltyModule(TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, address(0)))); + bytes memory encodedBytes = abi.encode(initParams); + royaltyModule.onLinkToParents(address(3), address(royaltyPolicyLAP), parents, encodedLicenseData, encodedBytes); } function test_RoyaltyModule_setDisputeModule_revert_ZeroDisputeModule() public { @@ -224,8 +265,40 @@ contract TestRoyaltyModule is BaseTest { address licensor = address(3); bytes memory licenseData = abi.encode(uint32(15)); + address[] memory parents = new address[](2); + address[] memory targetAncestors1 = new address[](2); + uint32[] memory targetRoyaltyAmount1 = new uint32[](2); + uint32[] memory parentRoyalties1 = new uint32[](2); + bytes[] memory encodedLicenseData = new bytes[](2); + + address[] memory nullParentAncestors1 = new address[](0); + address[] memory nullParentAncestors2 = new address[](0); + uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); + uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); + + parents[0] = address(7); + parents[1] = address(8); + parentRoyalties1[0] = 7; + parentRoyalties1[1] = 8; + targetAncestors1[0] = address(7); + targetAncestors1[1] = address(8); + targetRoyaltyAmount1[0] = 7; + targetRoyaltyAmount1[1] = 8; + InitParams memory initParams = InitParams({ + targetAncestors: targetAncestors1, + targetRoyaltyAmount: targetRoyaltyAmount1, + parentAncestors1: nullParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); + } + bytes memory encodedBytes = abi.encode(initParams); + vm.startPrank(address(licensingModule)); - royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, ""); + royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, encodedBytes); } function test_RoyaltyModule_onLicenseMinting_Root() public { @@ -233,8 +306,25 @@ contract TestRoyaltyModule is BaseTest { bytes memory licenseData = abi.encode(uint32(15)); // mint a license of another policy + address[] memory nullTargetAncestors = new address[](0); + uint32[] memory nullTargetRoyaltyAmount = new uint32[](0); + uint32[] memory parentRoyalties = new uint32[](0); + address[] memory nullParentAncestors1 = new address[](0); + address[] memory nullParentAncestors2 = new address[](0); + uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); + uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); + InitParams memory nullInitParams = InitParams({ + targetAncestors: nullTargetAncestors, + targetRoyaltyAmount: nullTargetRoyaltyAmount, + parentAncestors1: nullParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + bytes memory nullBytes = abi.encode(nullInitParams); + vm.startPrank(address(licensingModule)); - royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, ""); + royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, nullBytes); vm.stopPrank(); vm.startPrank(u.admin); @@ -242,59 +332,119 @@ contract TestRoyaltyModule is BaseTest { vm.stopPrank(); vm.startPrank(address(licensingModule)); - royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, ""); + royaltyModule.onLicenseMinting(licensor, address(royaltyPolicyLAP), licenseData, nullBytes); } function test_RoyaltyModule_onLinkToParents_revert_NotWhitelistedRoyaltyPolicy() public { address newChild = address(9); address[] memory parents = new address[](2); - uint32[] memory parentRoyalties = new uint32[](2); + address[] memory targetAncestors1 = new address[](2); + uint32[] memory targetRoyaltyAmount1 = new uint32[](2); + uint32[] memory parentRoyalties1 = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); + address[] memory nullParentAncestors1 = new address[](0); + address[] memory nullParentAncestors2 = new address[](0); + uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); + uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); parents[0] = address(7); parents[1] = address(8); - parentRoyalties[0] = 7; - parentRoyalties[1] = 8; - - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + parentRoyalties1[0] = 7; + parentRoyalties1[1] = 8; + targetAncestors1[0] = address(7); + targetAncestors1[1] = address(8); + targetRoyaltyAmount1[0] = 7; + targetRoyaltyAmount1[1] = 8; + InitParams memory initParams = InitParams({ + targetAncestors: targetAncestors1, + targetRoyaltyAmount: targetRoyaltyAmount1, + parentAncestors1: nullParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } + bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(address(licensingModule)); vm.expectRevert(Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy.selector); - royaltyModule.onLinkToParents(newChild, address(1), parents, encodedLicenseData, ""); + royaltyModule.onLinkToParents(newChild, address(1), parents, encodedLicenseData, encodedBytes); } function test_RoyaltyModule_onLinkToParents_revert_NoParentsOnLinking() public { address newChild = address(9); address[] memory parents = new address[](0); - uint32[] memory parentRoyalties = new uint32[](2); + address[] memory targetAncestors1 = new address[](2); + uint32[] memory targetRoyaltyAmount1 = new uint32[](2); + uint32[] memory parentRoyalties1 = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); - - parentRoyalties[0] = 7; - parentRoyalties[1] = 8; - - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + address[] memory nullParentAncestors1 = new address[](0); + address[] memory nullParentAncestors2 = new address[](0); + uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); + uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); + + parentRoyalties1[0] = 7; + parentRoyalties1[1] = 8; + targetAncestors1[0] = address(7); + targetAncestors1[1] = address(8); + targetRoyaltyAmount1[0] = 7; + targetRoyaltyAmount1[1] = 8; + InitParams memory initParams = InitParams({ + targetAncestors: targetAncestors1, + targetRoyaltyAmount: targetRoyaltyAmount1, + parentAncestors1: nullParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } + bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(address(licensingModule)); vm.expectRevert(Errors.RoyaltyModule__NoParentsOnLinking.selector); - royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, ""); + royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, encodedBytes); } function test_RoyaltyModule_onLinkToParents_revert_IncompatibleRoyaltyPolicy() public { address newChild = address(9); address[] memory parents = new address[](2); - uint32[] memory parentRoyalties = new uint32[](1); + address[] memory targetAncestors1 = new address[](3); + uint32[] memory targetRoyaltyAmount1 = new uint32[](3); + uint32[] memory parentRoyalties1 = new uint32[](1); bytes[] memory encodedLicenseData = new bytes[](2); + address[] memory ParentAncestors1 = new address[](2); + address[] memory nullParentAncestors2 = new address[](0); + uint32[] memory ParentAncestorsRoyalties1 = new uint32[](2); + uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); parents[0] = address(3); - parentRoyalties[0] = 3; - - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + parentRoyalties1[0] = 3; + targetAncestors1[0] = address(3); + targetAncestors1[1] = address(7); + targetAncestors1[2] = address(8); + targetRoyaltyAmount1[0] = 3; + targetRoyaltyAmount1[1] = 7; + targetRoyaltyAmount1[2] = 8; + ParentAncestors1[0] = address(7); + ParentAncestors1[1] = address(8); + ParentAncestorsRoyalties1[0] = 7; + ParentAncestorsRoyalties1[1] = 8; + InitParams memory initParams = InitParams({ + targetAncestors: targetAncestors1, + targetRoyaltyAmount: targetRoyaltyAmount1, + parentAncestors1: ParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: ParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } + bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(u.admin); royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP2), true); @@ -302,7 +452,7 @@ contract TestRoyaltyModule is BaseTest { vm.startPrank(address(licensingModule)); vm.expectRevert(Errors.RoyaltyModule__IncompatibleRoyaltyPolicy.selector); - royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP2), parents, encodedLicenseData, ""); + royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP2), parents, encodedLicenseData, encodedBytes); } function test_RoyaltyModule_onLinkToParents() public { @@ -310,20 +460,38 @@ contract TestRoyaltyModule is BaseTest { // new child is linked to 7 and 8 address[] memory parents = new address[](2); - uint32[] memory parentRoyalties = new uint32[](2); + address[] memory targetAncestors1 = new address[](2); + uint32[] memory targetRoyaltyAmount1 = new uint32[](2); + uint32[] memory parentRoyalties1 = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); + address[] memory nullParentAncestors1 = new address[](0); + address[] memory nullParentAncestors2 = new address[](0); + uint32[] memory nullParentAncestorsRoyalties1 = new uint32[](0); + uint32[] memory nullParentAncestorsRoyalties2 = new uint32[](0); parents[0] = address(7); parents[1] = address(8); - parentRoyalties[0] = 7; - parentRoyalties[1] = 8; - - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + parentRoyalties1[0] = 7; + parentRoyalties1[1] = 8; + targetAncestors1[0] = address(7); + targetAncestors1[1] = address(8); + targetRoyaltyAmount1[0] = 7; + targetRoyaltyAmount1[1] = 8; + InitParams memory initParams = InitParams({ + targetAncestors: targetAncestors1, + targetRoyaltyAmount: targetRoyaltyAmount1, + parentAncestors1: nullParentAncestors1, + parentAncestors2: nullParentAncestors2, + parentAncestorsRoyalties1: nullParentAncestorsRoyalties1, + parentAncestorsRoyalties2: nullParentAncestorsRoyalties2 + }); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } + bytes memory encodedBytes = abi.encode(initParams); vm.startPrank(address(licensingModule)); - royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, ""); + royaltyModule.onLinkToParents(newChild, address(royaltyPolicyLAP), parents, encodedLicenseData, encodedBytes); assertEq(royaltyModule.royaltyPolicies(newChild), address(royaltyPolicyLAP)); } @@ -416,34 +584,6 @@ contract TestRoyaltyModule is BaseTest { royaltyModule.payLicenseMintingFee(ipAddr, ipAccount1, address(royaltyPolicyLAP), address(USDC), 100); } - function test_RoyaltyModule_payLicenseMintingFee_revert_NotWhitelistedRoyaltyToken() public { - uint256 royaltyAmount = 100 * 10 ** 6; - address receiverIpId = address(7); - address payerAddress = address(3); - address licenseRoyaltyPolicy = address(royaltyPolicyLAP); - address token = address(1); - - vm.startPrank(address(licensingModule)); - - vm.expectRevert(Errors.RoyaltyModule__NotWhitelistedRoyaltyToken.selector); - royaltyModule.payLicenseMintingFee(receiverIpId, payerAddress, licenseRoyaltyPolicy, token, royaltyAmount); - } - - function test_RoyaltyModule_payLicenseMintingFee_revert_NotWhitelistedRoyaltyPolicy() public { - uint256 royaltyAmount = 100 * 10 ** 6; - address receiverIpId = address(7); - address payerAddress = address(3); - address licenseRoyaltyPolicy = address(1); - address token = address(USDC); - - vm.startPrank(u.admin); - royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP), false); - - vm.startPrank(address(licensingModule)); - vm.expectRevert(Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy.selector); - royaltyModule.payLicenseMintingFee(receiverIpId, payerAddress, licenseRoyaltyPolicy, token, royaltyAmount); - } - function test_RoyaltyModule_payLicenseMintingFee() public { uint256 royaltyAmount = 100 * 10 ** 6; address receiverIpId = address(7); @@ -473,4 +613,4 @@ contract TestRoyaltyModule is BaseTest { assertEq(payerAddressUSDCBalBefore - payerAddressUSDCBalAfter, royaltyAmount); assertEq(ipRoyaltyVaultUSDCBalAfter - ipRoyaltyVaultUSDCBalBefore, royaltyAmount); } -} +} \ No newline at end of file diff --git a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol index d3a4d7594..7583230ff 100644 --- a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol +++ b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol @@ -40,50 +40,50 @@ contract TestRoyaltyPolicyLAP is BaseTest { // init 2nd level with children address[] memory parents = new address[](2); - uint32[] memory parentRoyalties = new uint32[](2); + uint32[] memory parentRoyalties1 = new uint32[](2); bytes[] memory encodedLicenseData = new bytes[](2); // 3 is child of 7 and 8 parents[0] = address(7); parents[1] = address(8); - parentRoyalties[0] = 7; - parentRoyalties[1] = 8; + parentRoyalties1[0] = 7; + parentRoyalties1[1] = 8; - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } royaltyPolicyLAP.onLinkToParents(address(3), parents, encodedLicenseData, ""); // 4 is child of 9 and 10 parents[0] = address(9); parents[1] = address(10); - parentRoyalties[0] = 9; - parentRoyalties[1] = 10; + parentRoyalties1[0] = 9; + parentRoyalties1[1] = 10; - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } royaltyPolicyLAP.onLinkToParents(address(4), parents, encodedLicenseData, ""); // 5 is child of 11 and 12 parents[0] = address(11); parents[1] = address(12); - parentRoyalties[0] = 11; - parentRoyalties[1] = 12; + parentRoyalties1[0] = 11; + parentRoyalties1[1] = 12; - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } royaltyPolicyLAP.onLinkToParents(address(5), parents, encodedLicenseData, ""); // 6 is child of 13 and 14 parents[0] = address(13); parents[1] = address(14); - parentRoyalties[0] = 13; - parentRoyalties[1] = 14; + parentRoyalties1[0] = 13; + parentRoyalties1[1] = 14; - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } royaltyPolicyLAP.onLinkToParents(address(6), parents, encodedLicenseData, ""); @@ -91,22 +91,22 @@ contract TestRoyaltyPolicyLAP is BaseTest { // 1 is child of 3 and 4 parents[0] = address(3); parents[1] = address(4); - parentRoyalties[0] = 3; - parentRoyalties[1] = 4; + parentRoyalties1[0] = 3; + parentRoyalties1[1] = 4; - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } royaltyPolicyLAP.onLinkToParents(address(1), parents, encodedLicenseData, ""); // 2 is child of 5 and 6 parents[0] = address(5); parents[1] = address(6); - parentRoyalties[0] = 5; - parentRoyalties[1] = 6; + parentRoyalties1[0] = 5; + parentRoyalties1[1] = 6; - for (uint32 i = 0; i < parentRoyalties.length; i++) { - encodedLicenseData[i] = abi.encode(parentRoyalties[i]); + for (uint32 i = 0; i < parentRoyalties1.length; i++) { + encodedLicenseData[i] = abi.encode(parentRoyalties1[i]); } royaltyPolicyLAP.onLinkToParents(address(2), parents, encodedLicenseData, ""); @@ -297,4 +297,4 @@ contract TestRoyaltyPolicyLAP is BaseTest { assertEq(ipRoyaltyVault2USDCBalAfter - ipRoyaltyVault2USDCBalBefore, royaltyAmount); assertEq(splitMainUSDCBalBefore - splitMainUSDCBalAfter, royaltyAmount); } -} +} \ No newline at end of file From f8e56d3fb900716ad9406a903f4220c09ae7047c Mon Sep 17 00:00:00 2001 From: Spablob Date: Fri, 12 Apr 2024 09:58:21 +0300 Subject: [PATCH 28/31] format fix --- .../interfaces/modules/royalty/policies/IIpRoyaltyVault.sol | 2 +- contracts/modules/royalty/policies/IpRoyaltyVault.sol | 2 +- test/foundry/modules/royalty/IpRoyaltyVault.t.sol | 2 +- test/foundry/modules/royalty/RoyaltyModule.t.sol | 2 +- test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol b/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol index d4e685403..ecfed7922 100644 --- a/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol +++ b/contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol @@ -103,4 +103,4 @@ interface IIpRoyaltyVault { /// @notice The list of revenue tokens in the vault function tokens() external view returns (address[] memory); -} \ No newline at end of file +} diff --git a/contracts/modules/royalty/policies/IpRoyaltyVault.sol b/contracts/modules/royalty/policies/IpRoyaltyVault.sol index d2bd1ae43..711755506 100644 --- a/contracts/modules/royalty/policies/IpRoyaltyVault.sol +++ b/contracts/modules/royalty/policies/IpRoyaltyVault.sol @@ -326,4 +326,4 @@ contract IpRoyaltyVault is IIpRoyaltyVault, ERC20SnapshotUpgradeable, Reentrancy $.slot := IpRoyaltyVaultStorageLocation } } -} \ No newline at end of file +} diff --git a/test/foundry/modules/royalty/IpRoyaltyVault.t.sol b/test/foundry/modules/royalty/IpRoyaltyVault.t.sol index 5f7e6a5e8..4e3b91cdd 100644 --- a/test/foundry/modules/royalty/IpRoyaltyVault.t.sol +++ b/test/foundry/modules/royalty/IpRoyaltyVault.t.sol @@ -409,4 +409,4 @@ contract TestIpRoyaltyVault is BaseTest { accruedCollectableRevenue ); } -} \ 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 342f58a99..187bf9f54 100644 --- a/test/foundry/modules/royalty/RoyaltyModule.t.sol +++ b/test/foundry/modules/royalty/RoyaltyModule.t.sol @@ -613,4 +613,4 @@ contract TestRoyaltyModule is BaseTest { assertEq(payerAddressUSDCBalBefore - payerAddressUSDCBalAfter, royaltyAmount); assertEq(ipRoyaltyVaultUSDCBalAfter - ipRoyaltyVaultUSDCBalBefore, royaltyAmount); } -} \ No newline at end of file +} diff --git a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol index 7583230ff..be4fb574c 100644 --- a/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol +++ b/test/foundry/modules/royalty/RoyaltyPolicyLAP.t.sol @@ -297,4 +297,4 @@ contract TestRoyaltyPolicyLAP is BaseTest { assertEq(ipRoyaltyVault2USDCBalAfter - ipRoyaltyVault2USDCBalBefore, royaltyAmount); assertEq(splitMainUSDCBalBefore - splitMainUSDCBalAfter, royaltyAmount); } -} \ No newline at end of file +} From 6581549f82c44095d31241a4ac7c12f00dc13ddb Mon Sep 17 00:00:00 2001 From: Spablob Date: Fri, 12 Apr 2024 23:34:26 +0300 Subject: [PATCH 29/31] add business logic comments --- contracts/modules/dispute/DisputeModule.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/modules/dispute/DisputeModule.sol b/contracts/modules/dispute/DisputeModule.sol index 7dcf0aaa9..46de2da8c 100644 --- a/contracts/modules/dispute/DisputeModule.sol +++ b/contracts/modules/dispute/DisputeModule.sol @@ -310,8 +310,12 @@ contract DisputeModule is DisputeModuleStorage storage $ = _getDisputeModuleStorage(); Dispute memory dispute = $.disputes[disputeId]; + // there are two types of disputes - those that are subject to judgment and those that are not + // the way to distinguish is by whether dispute.parentDisputeId is 0 or higher than 0 + // for the former - only the dispute initiator can resolve if (dispute.parentDisputeId == 0 && msg.sender != dispute.disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator(); + // for the latter - resolving is permissionless as long as the parent dispute has been resolved if (dispute.parentDisputeId > 0 && $.disputes[dispute.parentDisputeId].currentTag != bytes32(0)) revert Errors.DisputeModule__ParentDisputeNotResolved(); From 3f73ef2cf048912644bbe8252e2d285900d3b2fd Mon Sep 17 00:00:00 2001 From: Spablob Date: Sat, 13 Apr 2024 00:17:18 +0300 Subject: [PATCH 30/31] add restriction clarifications --- contracts/modules/dispute/DisputeModule.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/modules/dispute/DisputeModule.sol b/contracts/modules/dispute/DisputeModule.sol index 46de2da8c..d10e22896 100644 --- a/contracts/modules/dispute/DisputeModule.sol +++ b/contracts/modules/dispute/DisputeModule.sol @@ -273,6 +273,9 @@ contract DisputeModule is Dispute memory parentDispute = $.disputes[parentDisputeId]; if (parentDispute.targetIpId != parentIpId) revert Errors.DisputeModule__ParentIpIdMismatch(); + + // a dispute current tag prior to being resolved can be in 3 states - IN_DISPUTE, 0, or a tag (ie. "PLAGIARISM) + // by restricting IN_DISPUTE and 0 - we ensure that the parent has been tagged as infringing before resolving dispute if (parentDispute.currentTag == IN_DISPUTE || parentDispute.currentTag == bytes32(0)) revert Errors.DisputeModule__ParentNotTagged(); From b4a5bc62e3e75964015dbada36fd2e9beccdfbc5 Mon Sep 17 00:00:00 2001 From: Spablob Date: Sat, 13 Apr 2024 00:29:49 +0300 Subject: [PATCH 31/31] fix comment size --- contracts/modules/dispute/DisputeModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/modules/dispute/DisputeModule.sol b/contracts/modules/dispute/DisputeModule.sol index d10e22896..f4bc1ddba 100644 --- a/contracts/modules/dispute/DisputeModule.sol +++ b/contracts/modules/dispute/DisputeModule.sol @@ -275,7 +275,7 @@ contract DisputeModule is if (parentDispute.targetIpId != parentIpId) revert Errors.DisputeModule__ParentIpIdMismatch(); // a dispute current tag prior to being resolved can be in 3 states - IN_DISPUTE, 0, or a tag (ie. "PLAGIARISM) - // by restricting IN_DISPUTE and 0 - we ensure that the parent has been tagged as infringing before resolving dispute + // by restricting IN_DISPUTE and 0 - it is ensire the parent has been tagged before resolving dispute if (parentDispute.currentTag == IN_DISPUTE || parentDispute.currentTag == bytes32(0)) revert Errors.DisputeModule__ParentNotTagged();