From b8e4760b41aaa0c75f3df424bf7609a686ec13db Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sat, 17 Feb 2024 00:28:22 -0600 Subject: [PATCH 1/4] refactor: natspec, variable names, interface public funcs, comments, lints --- contracts/AccessController.sol | 166 ++++---- contracts/IPAccountImpl.sol | 54 +-- contracts/governance/Governable.sol | 2 +- contracts/governance/Governance.sol | 14 +- contracts/interfaces/IAccessController.sol | 81 ++-- contracts/interfaces/IIPAccount.sol | 48 ++- .../interfaces/governance/IGovernable.sol | 2 + .../interfaces/governance/IGovernance.sol | 2 +- .../modules/IRegistrationModule.sol | 37 +- .../interfaces/modules/ITaggingModule.sol | 57 +-- .../interfaces/modules/base/IHookModule.sol | 6 +- .../modules/dispute/IDisputeModule.sol | 88 ++-- .../dispute/policies/IArbitrationPolicy.sol | 25 +- .../modules/licensing/ILicensingModule.sol | 153 +++++-- .../licensing/IPolicyFrameworkManager.sol | 28 +- .../licensing/IUMLPolicyFrameworkManager.sol | 25 +- .../modules/royalty/IRoyaltyModule.sol | 28 +- .../royalty/policies/IAncestorsVaultLAP.sol | 9 +- .../modules/royalty/policies/ILSClaimer.sol | 21 - .../royalty/policies/ILiquidSplitClone.sol | 4 + .../royalty/policies/ILiquidSplitFactory.sol | 1 + .../royalty/policies/ILiquidSplitMain.sol | 2 + .../royalty/policies/IRoyaltyPolicy.sol | 30 +- .../royalty/policies/IRoyaltyPolicyLAP.sol | 96 +++-- .../registries/IIPAccountRegistry.sol | 40 +- .../registries/IIPAssetRegistry.sol | 116 +++-- .../registries/ILicenseRegistry.sol | 54 ++- .../interfaces/registries/IModuleRegistry.sol | 40 +- .../registries/metadata/IMetadataProvider.sol | 5 +- .../metadata/IMetadataProviderMigratable.sol | 15 +- .../metadata/IMetadataProviderV1.sol | 6 + .../resolvers/IKeyValueResolver.sol | 9 + contracts/lib/AccessPermission.sol | 16 +- contracts/lib/Errors.sol | 1 - contracts/modules/BaseModule.sol | 5 +- contracts/modules/RegistrationModule.sol | 46 +- .../modules/dispute-module/DisputeModule.sol | 209 +++++---- .../policies/ArbitrationPolicySP.sol | 54 +-- .../licensing/BasePolicyFrameworkManager.sol | 12 +- .../modules/licensing/LicensingModule.sol | 178 +++++--- .../licensing/LicensingModuleAware.sol | 8 +- .../licensing/UMLPolicyFrameworkManager.sol | 72 ++-- .../LicensorApprovalChecker.sol | 28 +- .../modules/royalty-module/RoyaltyModule.sol | 138 +++--- .../policies/AncestorsVaultLAP.sol | 149 +++---- .../policies/RoyaltyPolicyLAP.sol | 398 +++++++++--------- contracts/modules/tagging/TaggingModule.sol | 32 +- contracts/registries/IPAccountRegistry.sol | 68 +-- contracts/registries/IPAssetRegistry.sol | 94 +++-- contracts/registries/LicenseRegistry.sol | 74 ++-- contracts/registries/ModuleRegistry.sol | 44 +- .../metadata/IPMetadataProvider.sol | 13 +- .../metadata/MetadataProviderBase.sol | 43 +- .../metadata/MetadataProviderV1.sol | 6 + contracts/resolvers/IPResolver.sol | 15 +- contracts/resolvers/KeyValueResolver.sol | 22 +- contracts/resolvers/ResolverBase.sol | 9 +- script/foundry/deployment/Main.s.sol | 2 +- .../mocks/module/MockDisputeModule.sol | 13 +- .../mocks/module/MockLicensingModule.sol | 9 +- .../mocks/registry/MockLicenseRegistry.sol | 14 +- .../modules/dispute/DisputeModule.t.sol | 8 +- 62 files changed, 1731 insertions(+), 1313 deletions(-) delete mode 100644 contracts/interfaces/modules/royalty/policies/ILSClaimer.sol diff --git a/contracts/AccessController.sol b/contracts/AccessController.sol index 54a4fa22..d0b6ebe3 100644 --- a/contracts/AccessController.sol +++ b/contracts/AccessController.sol @@ -33,26 +33,36 @@ contract AccessController is IAccessController, Governable { address public IP_ACCOUNT_REGISTRY; address public MODULE_REGISTRY; - /// @dev encoded permission => permission - /// encoded permission = keccak256(abi.encodePacked(ipAccount, signer, to, func)) - mapping(bytes32 => uint8) public permissions; + /// @dev Tracks the permission granted to an encoded callpath, where the + /// encoded callpath = keccak256(abi.encodePacked(ipAccount, signer, to, func)) + mapping(bytes32 => uint8) internal encodedPermissions; constructor(address governance) Governable(governance) {} + // TODO: Change the function name to not clash with potential proxy contract `initialize`. + // TODO: Only allow calling once. + /// @dev Initialize the Access Controller with the IP Account Registry and Module Registry addresses. + /// These are separated from the constructor, because we need to deploy the AccessController first for + /// to deploy many registry and module contracts, including the IP Account Registry and Module Registry. + /// @dev Enforced to be only callable by the protocol admin in governance. + /// @param ipAccountRegistry The address of the IP Account Registry. + /// @param moduleRegistry The address of the Module Registry. function initialize(address ipAccountRegistry, address moduleRegistry) external onlyProtocolAdmin { IP_ACCOUNT_REGISTRY = ipAccountRegistry; MODULE_REGISTRY = moduleRegistry; } - /// @inheritdoc IAccessController - function setBatchPermissions(AccessPermission.Permission[] memory permissions_) external whenNotPaused { - for (uint256 i = 0; i < permissions_.length; ) { + /// @notice Sets a batch of permissions in a single transaction. + /// @dev This function allows setting multiple permissions at once. Pausable. + /// @param permissions An array of `Permission` structs, each representing the permission to be set. + function setBatchPermissions(AccessPermission.Permission[] memory permissions) external whenNotPaused { + for (uint256 i = 0; i < permissions.length; ) { setPermission( - permissions_[i].ipAccount, - permissions_[i].signer, - permissions_[i].to, - permissions_[i].func, - permissions_[i].permission + permissions[i].ipAccount, + permissions[i].signer, + permissions[i].to, + permissions[i].func, + permissions[i].permission ); unchecked { i += 1; @@ -61,92 +71,90 @@ contract AccessController is IAccessController, Governable { } /// @notice Sets the permission for all IPAccounts - function setGlobalPermission( - address signer_, - address to_, - bytes4 func_, - uint8 permission_ - ) external onlyProtocolAdmin { - if (signer_ == address(0)) { + /// @dev Enforced to be only callable by the protocol admin in governance. + /// @param signer The address that can call `to` on behalf of the IP account + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + /// @param permission The new permission level + function setGlobalPermission(address signer, address to, bytes4 func, uint8 permission) external onlyProtocolAdmin { + if (signer == address(0)) { revert Errors.AccessController__SignerIsZeroAddress(); } // permission must be one of ABSTAIN, ALLOW, DENY - if (permission_ > 2) { + if (permission > 2) { revert Errors.AccessController__PermissionIsNotValid(); } _setPermission(address(0), signer_, to_, func_, permission_); - emit PermissionSet(address(0), address(0), signer_, to_, func_, permission_); + emit PermissionSet(address(0), signer_, to_, func_, permission_); } /// @notice Sets the permission for a specific function call - /// @dev By default, all policies are set to ABSTAIN, which means that the permission is not set - /// Owner of ipAccount by default has permission sets the permission - /// permission 0 => ABSTAIN, 1 => ALLOW, 3 => DENY + /// @dev Each policy is represented as a mapping from an IP account address to a signer address to a recipient + /// address to a function selector to a permission level. The permission level can be 0 (ABSTAIN), 1 (ALLOW), or + /// 2 (DENY). + /// @dev By default, all policies are set to 0 (ABSTAIN), which means that the permission is not set. + /// The owner of ipAccount by default has all permission. /// address(0) => wildcard /// bytes4(0) => wildcard - /// specific permission overrides wildcard permission - /// @param ipAccount_ The account that owns the IP (not support wildcard permission) - /// @param signer_ The account that signs the transaction (not support wildcard permission) - /// @param to_ The recipient of the transaction (support wildcard permission) - /// @param func_ The function selector (support wildcard permission) - /// @param permission_ The permission level (0 => ABSTAIN, 1 => ALLOW, 3 => DENY) + /// Specific permission overrides wildcard permission. + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the `ipAccount` + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + /// @param permission The new permission level function setPermission( - address ipAccount_, - address signer_, - address to_, - bytes4 func_, - uint8 permission_ + address ipAccount, + address signer, + address to, + bytes4 func, + uint8 permission ) public whenNotPaused { // IPAccount and signer does not support wildcard permission - if (ipAccount_ == address(0)) { + if (ipAccount == address(0)) { revert Errors.AccessController__IPAccountIsZeroAddress(); } - if (signer_ == address(0)) { + if (signer == address(0)) { revert Errors.AccessController__SignerIsZeroAddress(); } - if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount_)) { - revert Errors.AccessController__IPAccountIsNotValid(ipAccount_); + if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount)) { + revert Errors.AccessController__IPAccountIsNotValid(ipAccount); } // permission must be one of ABSTAIN, ALLOW, DENY - if (permission_ > 2) { + if (permission > 2) { revert Errors.AccessController__PermissionIsNotValid(); } - if (!IModuleRegistry(MODULE_REGISTRY).isRegistered(msg.sender) && ipAccount_ != msg.sender) { + if (!IModuleRegistry(MODULE_REGISTRY).isRegistered(msg.sender) && ipAccount != msg.sender) { revert Errors.AccessController__CallerIsNotIPAccount(); } - _setPermission(ipAccount_, signer_, to_, func_, permission_); + _setPermission(ipAccount, signer, to, func, permission); - emit PermissionSet(IIPAccount(payable(ipAccount_)).owner(), ipAccount_, signer_, to_, func_, permission_); + emit PermissionSet(ipAccount_, signer_, to_, func_, permission_); } - /// @notice Checks if a specific function call is allowed. + /// @notice Checks the permission level for a specific function call. Reverts if permission is not granted. + /// Otherwise, the function is a noop. /// @dev This function checks the permission level for a specific function call. /// If a specific permission is set, it overrides the general (wildcard) permission. /// If the current level permission is ABSTAIN, the final permission is determined by the upper level. - /// @param ipAccount_ The account that owns the IP. - /// @param signer_ The account that signs the transaction. - /// @param to_ The recipient of the transaction. - /// @param func_ The function selector. + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the `ipAccount` + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` // solhint-disable code-complexity - function checkPermission( - address ipAccount_, - address signer_, - address to_, - bytes4 func_ - ) external view whenNotPaused { - // ipAccount_ can only call registered modules or set Permissions - if (to_ != address(this) && !IModuleRegistry(MODULE_REGISTRY).isRegistered(to_)) { - revert Errors.AccessController__RecipientIsNotRegisteredModule(to_); + function checkPermission(address ipAccount, address signer, address to, bytes4 func) external view whenNotPaused { + // ipAccount can only call registered modules or set Permissions + if (to != address(this) && !IModuleRegistry(MODULE_REGISTRY).isRegistered(to)) { + revert Errors.AccessController__RecipientIsNotRegisteredModule(to); } // Must be a valid IPAccount - if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount_)) { - revert Errors.AccessController__IPAccountIsNotValid(ipAccount_); + if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount)) { + revert Errors.AccessController__IPAccountIsNotValid(ipAccount); } // Owner can call all functions of all modules - if (IIPAccount(payable(ipAccount_)).owner() == signer_) { + if (IIPAccount(payable(ipAccount)).owner() == signer) { return; } - uint functionPermission = getPermission(ipAccount_, signer_, to_, func_); + uint functionPermission = getPermission(ipAccount, signer, to, func); // Specific function permission overrides wildcard/general permission if (functionPermission == AccessPermission.ALLOW) { return; @@ -154,52 +162,44 @@ contract AccessController is IAccessController, Governable { // If specific function permission is ABSTAIN, check module level permission if (functionPermission == AccessPermission.ABSTAIN) { - uint8 modulePermission = getPermission(ipAccount_, signer_, to_, bytes4(0)); + uint8 modulePermission = getPermission(ipAccount, signer, to, bytes4(0)); // Return true if allow to call all functions of the module if (modulePermission == AccessPermission.ALLOW) { return; } // If module level permission is ABSTAIN, check transaction signer level permission if (modulePermission == AccessPermission.ABSTAIN) { - if (getPermission(address(0), signer_, to_, func_) == AccessPermission.ALLOW) { + if (getPermission(address(0), signer, to, func) == AccessPermission.ALLOW) { return; } // Pass if the ipAccount allow the signer can call all functions of all modules // Otherwise, revert - if (getPermission(ipAccount_, signer_, address(0), bytes4(0)) == AccessPermission.ALLOW) { + if (getPermission(ipAccount, signer, address(0), bytes4(0)) == AccessPermission.ALLOW) { return; } - revert Errors.AccessController__PermissionDenied(ipAccount_, signer_, to_, func_); + revert Errors.AccessController__PermissionDenied(ipAccount, signer, to, func); } - revert Errors.AccessController__PermissionDenied(ipAccount_, signer_, to_, func_); + revert Errors.AccessController__PermissionDenied(ipAccount, signer, to, func); } - revert Errors.AccessController__PermissionDenied(ipAccount_, signer_, to_, func_); + revert Errors.AccessController__PermissionDenied(ipAccount, signer, to, func); } /// @notice Returns the permission level for a specific function call. - /// @param ipAccount The account that owns the IP. - /// @param signer The account that signs the transaction. - /// @param to The recipient of the transaction. - /// @param func The function selector. - /// @return The permission level for the specific function call. + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the `ipAccount` + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + /// @return permission The current permission level for the function call on `to` by the `signer` for `ipAccount` function getPermission(address ipAccount, address signer, address to, bytes4 func) public view returns (uint8) { - return permissions[_encodePermission(ipAccount, signer, to, func)]; + return encodedPermissions[_encodePermission(ipAccount, signer, to, func)]; } - /// @dev the permission parameters will be encoded into bytes32 as key in the permissions mapping to save storage + /// @dev The permission parameters will be encoded into bytes32 as key in the permissions mapping to save storage function _setPermission(address ipAccount, address signer, address to, bytes4 func, uint8 permission) internal { - permissions[_encodePermission(ipAccount, signer, to, func)] = permission; + encodedPermissions[_encodePermission(ipAccount, signer, to, func)] = permission; } - /// @dev Encodes permission parameters into a hash (bytes32) to serve as a unique permission record ID. - /// This function is utilized both when setting permissions and checking permissions to uniquely identify them. - /// In addition to the four permission fields passed by the parameters (ipAccount, signer, to, func), - /// an additional field, "ipAccountOwner", is retrieved on-the-fly when encoding the permission. - /// @param ipAccount The IP account involved in the permission. - /// @param signer The account that signs the transaction. - /// @param to The recipient of the transaction. - /// @param func The function selector involved in the permission. - /// @return A bytes32 hash representing the unique ID of the permission record. + /// @dev encode permission to hash (bytes32) function _encodePermission( address ipAccount, address signer, diff --git a/contracts/IPAccountImpl.sol b/contracts/IPAccountImpl.sol index 8b632b35..d9fcada6 100644 --- a/contracts/IPAccountImpl.sol +++ b/contracts/IPAccountImpl.sol @@ -17,8 +17,10 @@ import { Errors } from "./lib/Errors.sol"; /// @title IPAccountImpl /// @notice The Story Protocol's implementation of the IPAccount. contract IPAccountImpl is IERC165, IIPAccount { - address public immutable accessController; + /// @notice Returns the address of the protocol-wide access controller. + address public ACCESS_CONTROLLER; + /// @notice Returns the IPAccount's internal nonce for transaction ordering. uint256 public state; receive() external payable override(IERC6551Account) {} @@ -28,15 +30,13 @@ contract IPAccountImpl is IERC165, IIPAccount { /// in the implementation code's storage. /// This means that each cloned IPAccount will inherently use the same AccessController /// without the need for individual configuration. - /// @param accessController_ The address of the AccessController contract to be used for permission checks - constructor(address accessController_) { - if (accessController_ == address(0)) revert Errors.IPAccount__InvalidAccessController(); - accessController = accessController_; + /// @param accessController The address of the AccessController contract to be used for permission checks + constructor(address accessController) { + if (accessController == address(0)) revert Errors.IPAccount__InvalidAccessController(); + ACCESS_CONTROLLER = accessController; } - /// @notice Checks if the contract supports a specific interface - /// @param interfaceId_ The interface identifier, as specified in ERC-165 - /// @return True if the contract supports the interface, false otherwise + /// @notice IERC165 interface support. function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { return (interfaceId_ == type(IIPAccount).interfaceId || interfaceId_ == type(IERC6551Account).interfaceId || @@ -68,11 +68,11 @@ contract IPAccountImpl is IERC165, IIPAccount { } /// @notice Checks if the signer is valid for the given data - /// @param signer_ The signer to check - /// @param data_ The data to check against + /// @param signer The signer to check + /// @param data The data to check against /// @return The function selector if the signer is valid, 0 otherwise - function isValidSigner(address signer_, bytes calldata data_) external view returns (bytes4) { - if (_isValidSigner(signer_, address(0), data_)) { + function isValidSigner(address signer, bytes calldata data) external view returns (bytes4) { + if (_isValidSigner(signer, address(0), data)) { return IERC6551Account.isValidSigner.selector; } @@ -80,30 +80,29 @@ contract IPAccountImpl is IERC165, IIPAccount { } /// @notice Returns the owner of the IP Account. - /// @return The address of the owner. + /// @return owner The address of the owner. function owner() public view returns (address) { (uint256 chainId, address contractAddress, uint256 tokenId) = token(); if (chainId != block.chainid) return address(0); return IERC721(contractAddress).ownerOf(tokenId); } - /// @notice Checks if the signer is valid for the given data and recipient - /// @dev It leverages the access controller to check the permission - /// @param signer_ The signer to check - /// @param to_ The recipient of the transaction - /// @param data_ The calldata to check against - /// @return True if the signer is valid, false otherwise - function _isValidSigner(address signer_, address to_, bytes calldata data_) internal view returns (bool) { - if (data_.length > 0 && data_.length < 4) { + /// @dev Checks if the signer is valid for the given data and recipient via the AccessController permission system. + /// @param signer The signer to check + /// @param to The recipient of the transaction + /// @param data The calldata to check against + /// @return isValid True if the signer is valid, false otherwise + function _isValidSigner(address signer, address to, bytes calldata data) internal view returns (bool) { + if (data.length > 0 && data.length < 4) { revert Errors.IPAccount__InvalidCalldata(); } - require(data_.length == 0 || data_.length >= 4, "Invalid calldata"); + require(data.length == 0 || data.length >= 4, "Invalid calldata"); bytes4 selector = bytes4(0); - if (data_.length >= 4) { - selector = bytes4(data_[:4]); + if (data.length >= 4) { + selector = bytes4(data[:4]); } // the check will revert if permission is denied - IAccessController(accessController).checkPermission(address(this), signer_, to_, selector); + IAccessController(ACCESS_CONTROLLER).checkPermission(address(this), signer, to, selector); return true; } @@ -114,6 +113,7 @@ contract IPAccountImpl is IERC165, IIPAccount { /// @param signer The signer of the transaction. /// @param deadline The deadline of the transaction signature. /// @param signature The signature of the transaction, EIP-712 encoded. + /// @return result The return data from the transaction. function executeWithSig( address to, uint256 value, @@ -158,14 +158,17 @@ contract IPAccountImpl is IERC165, IIPAccount { emit Executed(to, value, data, state); } + /// @inheritdoc IERC721Receiver function onERC721Received(address, address, uint256, bytes memory) public pure returns (bytes4) { return this.onERC721Received.selector; } + /// @inheritdoc IERC1155Receiver function onERC1155Received(address, address, uint256, uint256, bytes memory) public pure returns (bytes4) { return this.onERC1155Received.selector; } + /// @inheritdoc IERC1155Receiver function onERC1155BatchReceived( address, address, @@ -176,6 +179,7 @@ contract IPAccountImpl is IERC165, IIPAccount { return this.onERC1155BatchReceived.selector; } + /// @dev Executes a transaction from the IP Account. function _execute( address signer, address to, diff --git a/contracts/governance/Governable.sol b/contracts/governance/Governable.sol index 25b4efc1..e29a4946 100644 --- a/contracts/governance/Governable.sol +++ b/contracts/governance/Governable.sol @@ -51,7 +51,7 @@ abstract contract Governable is IGovernable { } /// @notice Returns the current governance address. - /// @return The address of the current governance. + /// @return governance The address of the current governance. function getGovernance() external view returns (address) { return governance; } diff --git a/contracts/governance/Governance.sol b/contracts/governance/Governance.sol index 2e42b0eb..7ce13a70 100644 --- a/contracts/governance/Governance.sol +++ b/contracts/governance/Governance.sol @@ -12,6 +12,7 @@ import { GovernanceLib } from "../lib/GovernanceLib.sol"; /// @dev This contract is used for governance of the protocol. /// TODO: Replace with OZ's 2StepOwnable contract Governance is AccessControl, IGovernance { + /// @dev The current governance state. GovernanceLib.ProtocolState internal state; /// @notice Creates a new Governance contract. @@ -21,8 +22,9 @@ contract Governance is AccessControl, IGovernance { _grantRole(GovernanceLib.PROTOCOL_ADMIN, admin); } - /// @notice Sets the state of the protocol. - /// @param newState The new state of the protocol. + /// @notice Sets the state of the protocol + /// @dev This function can only be called by an account with the appropriate role + /// @param newState The new state to set for the protocol function setState(GovernanceLib.ProtocolState newState) external override { if (!hasRole(GovernanceLib.PROTOCOL_ADMIN, msg.sender)) revert Errors.Governance__OnlyProtocolAdmin(); if (newState == state) revert Errors.Governance__NewStateIsTheSameWithOldState(); @@ -30,15 +32,13 @@ contract Governance is AccessControl, IGovernance { state = newState; } - /// @notice Returns the current state of the protocol. - /// @return The current state of the protocol. + /// @notice Returns the current state of the protocol + /// @return state The current state of the protocol function getState() external view override returns (GovernanceLib.ProtocolState) { return state; } - /// @notice Checks if the contract supports a specific interface. - /// @param interfaceId The id of the interface. - /// @return True if the contract supports the interface, false otherwise. + /// @notice IERC165 interface support. function supportsInterface(bytes4 interfaceId) public view override returns (bool) { return (interfaceId == type(IGovernance).interfaceId || super.supportsInterface(interfaceId)); } diff --git a/contracts/interfaces/IAccessController.sol b/contracts/interfaces/IAccessController.sol index c560e395..3c612c5e 100644 --- a/contracts/interfaces/IAccessController.sol +++ b/contracts/interfaces/IAccessController.sol @@ -5,6 +5,12 @@ pragma solidity ^0.8.23; import { AccessPermission } from "../lib/AccessPermission.sol"; interface IAccessController { + /// @notice Emitted when a permission is set. + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the IP account + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + /// @param permission The permission level event PermissionSet( address ipAccountOwner, address indexed ipAccount, @@ -14,50 +20,51 @@ interface IAccessController { uint8 permission ); - /// @notice Sets the permission for a specific function call - /// @dev Each policy is represented as a mapping from an IP account address to a signer address to a recipient - /// address to a function selector to a permission level. - /// The permission level can be 0 (ABSTAIN), 1 (ALLOW), or 2 (DENY). - /// @param ipAccount_ The account that owns the IP - /// @param signer_ The account that signs the transaction - /// @param to_ The recipient(modules) of the transaction - /// @param func_ The function selector - /// @param permission_ The permission level - function setPermission(address ipAccount_, address signer_, address to_, bytes4 func_, uint8 permission_) external; - - /// @notice Sets a batch of permissions in a single transaction - /// @dev This function allows setting multiple permissions at once. + /// @notice Sets a batch of permissions in a single transaction. + /// @dev This function allows setting multiple permissions at once. Pausable. /// @param permissions An array of `Permission` structs, each representing the permission to be set. function setBatchPermissions(AccessPermission.Permission[] memory permissions) external; /// @notice Sets the permission for all IPAccounts - /// @dev Only the protocol admin can set the global permission - /// @param signer_ The account that signs the transaction - /// @param to_ The recipient(modules) of the transaction - /// @param func_ The function selector - /// @param permission_ The permission level - function setGlobalPermission(address signer_, address to_, bytes4 func_, uint8 permission_) external; + /// @dev Enforced to be only callable by the protocol admin in governance. + /// @param signer The address that can call `to` on behalf of the IP account + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + /// @param permission The new permission level + function setGlobalPermission(address signer, address to, bytes4 func, uint8 permission) external; - /// @notice Gets the permission for a specific function call - /// @param ipAccount_ The account that owns the IP - /// @param signer_ The account that signs the transaction - /// @param to_ The recipient (modules) of the transaction - /// @param func_ The function selector - /// @return The current permission level for the function call - function getPermission( - address ipAccount_, - address signer_, - address to_, - bytes4 func_ - ) external view returns (uint8); + /// @notice Sets the permission for a specific function call + /// @dev Each policy is represented as a mapping from an IP account address to a signer address to a recipient + /// address to a function selector to a permission level. The permission level can be 0 (ABSTAIN), 1 (ALLOW), or + /// 2 (DENY). + /// @dev By default, all policies are set to 0 (ABSTAIN), which means that the permission is not set. + /// The owner of ipAccount by default has all permission. + /// address(0) => wildcard + /// bytes4(0) => wildcard + /// Specific permission overrides wildcard permission. + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the `ipAccount` + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + /// @param permission The new permission level + function setPermission(address ipAccount, address signer, address to, bytes4 func, uint8 permission) external; - /// @notice Checks the permission for a specific function call + /// @notice Checks the permission level for a specific function call. Reverts if permission is not granted. + /// Otherwise, the function is a noop. /// @dev This function checks the permission level for a specific function call. /// If a specific permission is set, it overrides the general (wildcard) permission. /// If the current level permission is ABSTAIN, the final permission is determined by the upper level. - /// @param ipAccount_ The account that owns the IP - /// @param signer_ The account that signs the transaction - /// @param to_ The recipient of the transaction - /// @param func_ The function selector - function checkPermission(address ipAccount_, address signer_, address to_, bytes4 func_) external view; + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the `ipAccount` + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + function checkPermission(address ipAccount, address signer, address to, bytes4 func) external view; + + /// @notice Returns the permission level for a specific function call. + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the `ipAccount` + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` + /// @return permission The current permission level for the function call on `to` by the `signer` for `ipAccount` + function getPermission(address ipAccount, address signer, address to, bytes4 func) external view returns (uint8); } diff --git a/contracts/interfaces/IIPAccount.sol b/contracts/interfaces/IIPAccount.sol index f10304cc..fa008edb 100644 --- a/contracts/interfaces/IIPAccount.sol +++ b/contracts/interfaces/IIPAccount.sol @@ -16,9 +16,20 @@ import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol"; /// IPAccount is core identity for all actions. interface IIPAccount is IERC6551Account, IERC721Receiver, IERC1155Receiver { /// @notice Emitted when a transaction is executed. + /// @param to The recipient of the transaction. + /// @param value The amount of Ether sent. + /// @param data The data sent along with the transaction. + /// @param nonce The nonce of the transaction. event Executed(address indexed to, uint256 value, bytes data, uint256 nonce); /// @notice Emitted when a transaction is executed on behalf of the signer. + /// @param to The recipient of the transaction. + /// @param value The amount of Ether sent. + /// @param data The data sent along with the transaction. + /// @param nonce The nonce of the transaction. + /// @param deadline The deadline of the transaction signature. + /// @param signer The signer of the transaction. + /// @param signature The signature of the transaction, EIP-712 encoded. event ExecutedWithSig( address indexed to, uint256 value, @@ -29,12 +40,27 @@ interface IIPAccount is IERC6551Account, IERC721Receiver, IERC1155Receiver { bytes signature ); - /// @notice Executes a transaction from the IP Account. - /// @param to_ The recipient of the transaction. - /// @param value_ The amount of Ether to send. - /// @param data_ The data to send along with the transaction. - /// @return The return data from the transaction. - function execute(address to_, uint256 value_, bytes calldata data_) external payable returns (bytes memory); + /// @notice Returns the address of the protocol-wide access controller. + function ACCESS_CONTROLLER() external view returns (address); + + /// @notice Returns the IPAccount's internal nonce for transaction ordering. + function state() external view returns (uint256); + + /// @notice Returns the identifier of the non-fungible token which owns the account + /// @return chainId The EIP-155 ID of the chain the token exists on + /// @return tokenContract The contract address of the token + /// @return tokenId The ID of the token + function token() external view returns (uint256, address, uint256); + + /// @notice Checks if the signer is valid for the given data + /// @param signer The signer to check + /// @param data The data to check against + /// @return The function selector if the signer is valid, 0 otherwise + function isValidSigner(address signer, bytes calldata data) external view returns (bytes4); + + /// @notice Returns the owner of the IP Account. + /// @return owner The address of the owner. + function owner() external view returns (address); /// @notice Executes a transaction from the IP Account on behalf of the signer. /// @param to The recipient of the transaction. @@ -43,6 +69,7 @@ interface IIPAccount is IERC6551Account, IERC721Receiver, IERC1155Receiver { /// @param signer The signer of the transaction. /// @param deadline The deadline of the transaction signature. /// @param signature The signature of the transaction, EIP-712 encoded. + /// @return result The return data from the transaction. function executeWithSig( address to, uint256 value, @@ -52,7 +79,10 @@ interface IIPAccount is IERC6551Account, IERC721Receiver, IERC1155Receiver { bytes calldata signature ) external payable returns (bytes memory); - /// @notice Returns the owner of the IP Account. - /// @return The address of the owner. - function owner() external view returns (address); + /// @notice Executes a transaction from the IP Account. + /// @param to The recipient of the transaction. + /// @param value The amount of Ether to send. + /// @param data The data to send along with the transaction. + /// @return result The return data from the transaction. + function execute(address to, uint256 value, bytes calldata data) external payable returns (bytes memory); } diff --git a/contracts/interfaces/governance/IGovernable.sol b/contracts/interfaces/governance/IGovernable.sol index f9b2b239..c0e95382 100644 --- a/contracts/interfaces/governance/IGovernable.sol +++ b/contracts/interfaces/governance/IGovernable.sol @@ -8,9 +8,11 @@ interface IGovernable { /// @notice Emitted when the governance is updated /// @param newGovernance The address of the new governance event GovernanceUpdated(address indexed newGovernance); + /// @notice Sets the governance address /// @param newGovernance The address of the new governance function setGovernance(address newGovernance) external; + /// @notice Returns the current governance address /// @return The address of the current governance function getGovernance() external view returns (address); diff --git a/contracts/interfaces/governance/IGovernance.sol b/contracts/interfaces/governance/IGovernance.sol index c0a4efce..5f0c01c4 100644 --- a/contracts/interfaces/governance/IGovernance.sol +++ b/contracts/interfaces/governance/IGovernance.sol @@ -27,6 +27,6 @@ interface IGovernance is IAccessControl { function setState(GovernanceLib.ProtocolState newState) external; /// @notice Returns the current state of the protocol - /// @return The current state of the protocol + /// @return state The current state of the protocol function getState() external view returns (GovernanceLib.ProtocolState); } diff --git a/contracts/interfaces/modules/IRegistrationModule.sol b/contracts/interfaces/modules/IRegistrationModule.sol index e9bef5d0..487c8e5e 100644 --- a/contracts/interfaces/modules/IRegistrationModule.sol +++ b/contracts/interfaces/modules/IRegistrationModule.sol @@ -1,27 +1,58 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.23; +import { IPResolver } from "../../resolvers/IPResolver.sol"; + interface IRegistrationModule { + /// @notice Emitted when a root-level IP is registered. + /// @param caller The address of the caller. + /// @param ipId The address of the IP that was registered. + /// @param policyId The policy that identifies the licensing terms of the IP. event RootIPRegistered(address indexed caller, address indexed ipId, uint256 indexed policyId); + /// @notice Emitted when a derivative IP is registered. + /// @param caller The address of the caller. + /// @param ipId The address of the IP that was registered. + /// @param licenseIds The licenses that were used to register the derivative IP. event DerivativeIPRegistered(address indexed caller, address indexed ipId, uint256[] licenseIds); + /// @notice Returns the metadata resolver used by the registration module. + function ipResolver() external view returns (IPResolver); + + /// @notice Registers a root-level IP into the protocol. Root-level IPs can be thought of as organizational hubs + /// for encapsulating policies that actual IPs can use to register through. As such, a root-level IP is not an + /// actual IP, but a container for IP policy management for their child IP assets. + /// @param policyId The policy that identifies the licensing terms of the IP. + /// @param tokenContract The address of the NFT bound to the root-level IP. + /// @param tokenId The token id of the NFT bound to the root-level IP. + /// @param ipName The name assigned to the new IP. + /// @param contentHash The content hash of the IP being registered. + /// @param externalURL An external URI to link to the IP. function registerRootIp( uint256 policyId, address tokenContract, uint256 tokenId, string memory ipName, - bytes32 hash, + bytes32 contentHash, string calldata externalURL ) external returns (address); + /// @notice Registers derivative IPs into the protocol. Derivative IPs are IP assets that inherit policies from + /// parent IPs by burning acquired license NFTs. + /// @param licenseIds The licenses to incorporate for the new IP. + /// @param tokenContract The address of the NFT bound to the derivative IP. + /// @param tokenId The token id of the NFT bound to the derivative IP. + /// @param ipName The name assigned to the new IP. + /// @param contentHash The content hash of the IP being registered. + /// @param externalURL An external URI to link to the IP. + /// @param royaltyContext The royalty context for the derivative IP. function registerDerivativeIp( uint256[] calldata licenseIds, address tokenContract, uint256 tokenId, string memory ipName, - bytes32 hash, + bytes32 contentHash, string calldata externalURL, - bytes calldata data + bytes calldata royaltyContext ) external; } diff --git a/contracts/interfaces/modules/ITaggingModule.sol b/contracts/interfaces/modules/ITaggingModule.sol index d786903e..4414e8d8 100644 --- a/contracts/interfaces/modules/ITaggingModule.sol +++ b/contracts/interfaces/modules/ITaggingModule.sol @@ -5,48 +5,53 @@ import { IModule } from "../../interfaces/modules/base/IModule.sol"; /// @title Tagging module interface interface ITaggingModule is IModule { - /// @notice Emitted when a tag is set for an IP - /// @param tag The tag - /// @param ipId The IP id + /// @notice Emitted when a tag is set for an IP asset + /// @param tag The tag value + /// @param ipId The ID of the IP asset event TagSet(string tag, address ipId); - /// @notice Emitted when a tag is removed for an IP - /// @param tag The tag - /// @param ipId The IP id + /// @notice Emitted when a tag is removed for an IP asset + /// @param tag The tag value + /// @param ipId The ID of the IP asset event TagRemoved(string tag, address ipId); - /// @notice Sets a tag for an IP - /// @param tag The tag - /// @param ipId The IP id + /// @notice The maximum number of tag permissions that can be set at once + function MAX_TAG_PERMISSIONS_AT_ONCE() external view returns (uint256); + + /// @notice Sets a tag on an IP asset + /// @param tag The tag value + /// @param ipId The ID of the IP asset /// @return added True if the tag was added function setTag(string calldata tag, address ipId) external returns (bool added); - /// @notice Removes a tag for an IP - /// @param tag The tag - /// @param ipId The IP id + /// @notice Removes a tag from an IP asset + /// @param tag The tag value + /// @param ipId The ID of the IP asset /// @return removed True if the tag was removed function removeTag(string calldata tag, address ipId) external returns (bool removed); - /// @notice Checks if an IP is tagged with a specific tag - /// @param tag The tag - /// @param ipId The IP id - /// @return True if the IP is tagged with the tag + /// @notice Checks if an IP asset is tagged with a specific tag + /// @param tag The tag value + /// @param ipId The ID of the IP asset + /// @return True if the IP asset is tagged with the tag function isTagged(string calldata tag, address ipId) external view returns (bool); - /// @notice Gets the total number of tags for an IP - /// @param ipId The IP id - /// @return The total number of tags for the IP + /// @notice Gets the total number of tags for an IP asset + /// @param ipId The ID of the IP asset + /// @return totalTags The total number of tags for the IP asset function totalTagsForIp(address ipId) external view returns (uint256); - /// @notice Gets the tag at a specific index for an IP - /// @param ipId The IP id - /// @param index The index - /// @return The tag at the index + /// @notice Gets the tag at a specific index for an IP asset + /// @dev Tag ordering is not guaranteed, as it's stored in a set + /// @param ipId The ID of the IP asset + /// @param index The local index of the tag on the IP asset + /// @return tagBytes The tag value in bytes function tagAtIndexForIp(address ipId, uint256 index) external view returns (bytes32); /// @notice Gets the tag string at a specific index for an IP - /// @param ipId The IP id - /// @param index The index - /// @return The tag string at the index + /// @dev Tag ordering is not guaranteed, as it's stored in a set + /// @param ipId The ID of the IP asset + /// @param index The local index of the tag on the IP asset + /// @return tagString The tag value casted as string function tagStringAtIndexForIp(address ipId, uint256 index) external view returns (string memory); } diff --git a/contracts/interfaces/modules/base/IHookModule.sol b/contracts/interfaces/modules/base/IHookModule.sol index 6aa5f20f..e8145f34 100644 --- a/contracts/interfaces/modules/base/IHookModule.sol +++ b/contracts/interfaces/modules/base/IHookModule.sol @@ -6,8 +6,12 @@ import { IModule } from "./IModule.sol"; /// @notice Hook Module Interface interface IHookModule is IModule { - /// @notice Verify if the caller is qualified + /// @notice Verify if the caller can pass the hook + /// @param caller The address of the caller + /// @param data The arbitrary data to be verified + /// @return bool Whether or not the caller has passed the hook's verification function verify(address caller, bytes calldata data) external returns (bool); + /// @notice Validates the configuration for the hook. /// @param configData The configuration data for the hook. function validateConfig(bytes calldata configData) external view; diff --git a/contracts/interfaces/modules/dispute/IDisputeModule.sol b/contracts/interfaces/modules/dispute/IDisputeModule.sol index b8471588..d502623e 100644 --- a/contracts/interfaces/modules/dispute/IDisputeModule.sol +++ b/contracts/interfaces/modules/dispute/IDisputeModule.sol @@ -3,6 +3,22 @@ pragma solidity ^0.8.23; /// @title Dispute Module Interface interface IDisputeModule { + /// @notice Dispute struct + /// @param targetIpId The ipId that is the target of the dispute + /// @param disputeInitiator The address of the dispute initiator + /// @param arbitrationPolicy The address of the arbitration policy + /// @param linkToDisputeEvidence The link of the dispute evidence + /// @param targetTag The target tag of the dispute + /// @param currentTag The current tag of the dispute + struct Dispute { + address targetIpId; + address disputeInitiator; + address arbitrationPolicy; + bytes32 linkToDisputeEvidence; + bytes32 targetTag; + bytes32 currentTag; + } + /// @notice Event emitted when a dispute tag whitelist status is updated /// @param tag The dispute tag /// @param allowed Indicates if the dispute tag is whitelisted @@ -61,23 +77,51 @@ interface IDisputeModule { /// @param disputeId The dispute id event DisputeResolved(uint256 disputeId); - /// @notice Dispute id - function disputeId() external view returns (uint256); + /// @notice Tag to represent the dispute is in dispute state waiting for judgement + function IN_DISPUTE() external view returns (bytes32); + + /// @notice Dispute ID counter + function disputeCounter() external view returns (uint256); /// @notice The address of the base arbitration policy function baseArbitrationPolicy() external view returns (address); + /// @notice Returns the dispute information for a given dispute id + /// @param disputeId The dispute id + /// @return targetIpId The ipId that is the target of the dispute + /// @return disputeInitiator The address of the dispute initiator + /// @return arbitrationPolicy The address of the arbitration policy + /// @return linkToDisputeEvidence The link of the dispute summary + /// @return targetTag The target tag of the dispute + /// @return currentTag The current tag of the dispute + function disputes( + uint256 disputeId + ) + external + view + returns ( + address targetIpId, + address disputeInitiator, + address arbitrationPolicy, + bytes32 linkToDisputeEvidence, + bytes32 targetTag, + bytes32 currentTag + ); + /// @notice Indicates if a dispute tag is whitelisted /// @param tag The dispute tag + /// @return allowed Indicates if the dispute tag is whitelisted function isWhitelistedDisputeTag(bytes32 tag) external view returns (bool allowed); /// @notice Indicates if an arbitration policy is whitelisted /// @param arbitrationPolicy The address of the arbitration policy + /// @return allowed Indicates if the arbitration policy is whitelisted function isWhitelistedArbitrationPolicy(address arbitrationPolicy) external view returns (bool allowed); /// @notice Indicates if an arbitration relayer is whitelisted for a given arbitration policy /// @param arbitrationPolicy The address of the arbitration policy /// @param arbitrationRelayer The address of the arbitration relayer + /// @return allowed Indicates if the arbitration relayer is whitelisted function isWhitelistedArbitrationRelayer( address arbitrationPolicy, address arbitrationRelayer @@ -85,6 +129,7 @@ interface IDisputeModule { /// @notice Arbitration policy for a given ipId /// @param ipId The ipId + /// @return policy The address of the arbitration policy function arbitrationPolicies(address ipId) external view returns (address policy); /// @notice Whitelists a dispute tag @@ -104,20 +149,20 @@ interface IDisputeModule { function whitelistArbitrationRelayer(address arbitrationPolicy, address arbPolicyRelayer, bool allowed) external; /// @notice Sets the base arbitration policy - /// @param _arbitrationPolicy The address of the arbitration policy - function setBaseArbitrationPolicy(address _arbitrationPolicy) external; + /// @param arbitrationPolicy The address of the arbitration policy + function setBaseArbitrationPolicy(address arbitrationPolicy) external; /// @notice Sets the arbitration policy for an ipId - /// @param _ipId The ipId - /// @param _arbitrationPolicy The address of the arbitration policy - function setArbitrationPolicy(address _ipId, address _arbitrationPolicy) external; + /// @param ipId The ipId + /// @param arbitrationPolicy The address of the arbitration policy + function setArbitrationPolicy(address ipId, address arbitrationPolicy) external; - /// @notice Raises a dispute + /// @notice Raises a dispute on a given ipId /// @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 - /// @return disputeId The dispute id + /// @return disputeId The id of the newly raised dispute function raiseDispute( address targetIpId, string memory linkToDisputeEvidence, @@ -125,7 +170,7 @@ interface IDisputeModule { bytes calldata data ) external returns (uint256 disputeId); - /// @notice Sets the dispute judgement + /// @notice Sets the dispute judgement on a given dispute. Only whitelisted arbitration relayers can call to judge. /// @param disputeId The dispute id /// @param decision The decision of the dispute /// @param data The data to set the dispute judgement @@ -140,23 +185,8 @@ interface IDisputeModule { /// @param disputeId The dispute id function resolveDispute(uint256 disputeId) external; - /// @notice Gets the dispute struct characteristics - /// @param disputeId The dispute id - function disputes( - uint256 disputeId - ) - external - view - returns ( - address targetIpId, // The ipId that is the target of the dispute - address disputeInitiator, // The address of the dispute initiator - address arbitrationPolicy, // The address of the arbitration policy - bytes32 linkToDisputeEvidence, // The link of the dispute summary - bytes32 targetTag, // The target tag of the dispute - bytes32 currentTag // The current tag of the dispute - ); - - /// @notice returns true if the ipId is tagged with any tag (meaning at least one dispute went through) - /// @param _ipId The ipId - function isIpTagged(address _ipId) external view returns (bool); + /// @notice Returns true if the ipId is tagged with any tag (meaning at least one dispute went through) + /// @param ipId The ipId + /// @return isTagged True if the ipId is tagged + function isIpTagged(address ipId) external view returns (bool); } diff --git a/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol b/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol index 29b6360c..d43ae4ae 100644 --- a/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol +++ b/contracts/interfaces/modules/dispute/policies/IArbitrationPolicy.sol @@ -7,23 +7,36 @@ interface IArbitrationPolicy { /// @param amount The amount withdrawn event GovernanceWithdrew(uint256 amount); - /// @notice Executes custom logic on raise dispute + /// @notice Returns the dispute module address + function DISPUTE_MODULE() external view returns (address); + + /// @notice Returns the payment token address + function PAYMENT_TOKEN() external view returns (address); + + /// @notice Returns the arbitration price + function ARBITRATION_PRICE() external view returns (uint256); + + /// @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 data to raise the dispute + /// @param data The arbitrary data used to raise the dispute function onRaiseDispute(address caller, bytes calldata data) external; - /// @notice Executes custom logic on dispute judgement + /// @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 data to set the dispute judgement + /// @param data The arbitrary data used to set the dispute judgement function onDisputeJudgement(uint256 disputeId, bool decision, bytes calldata data) external; - /// @notice Executes custom logic on dispute cancel + /// @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 data to cancel the dispute + /// @param data The arbitrary data used to cancel the dispute function onDisputeCancel(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/interfaces/modules/licensing/ILicensingModule.sol b/contracts/interfaces/modules/licensing/ILicensingModule.sol index 3d52825b..2d31f284 100644 --- a/contracts/interfaces/modules/licensing/ILicensingModule.sol +++ b/contracts/interfaces/modules/licensing/ILicensingModule.sol @@ -2,18 +2,25 @@ pragma solidity ^0.8.23; import { Licensing } from "../../../lib/Licensing.sol"; -import { IModule } from "../../modules/base/IModule.sol"; +import { IModule } from "../base/IModule.sol"; +import { RoyaltyModule } from "../../../modules/royalty-module/RoyaltyModule.sol"; +import { ILicenseRegistry } from "../../registries/ILicenseRegistry.sol"; +import { IDisputeModule } from "../dispute/IDisputeModule.sol"; /// @title ILicensingModule -/// @notice Interface for the LicensingModule contract, which is the main entry point for the licensing system. -/// It is responsible for: -/// - Registering policy frameworks -/// - Registering policies -/// - Minting licenses -/// - Linking IP to its parent -/// - Verifying linking parameters -/// - Verifying policy parameters interface ILicensingModule is IModule { + /// @notice Status of a policy on IP asset + /// @param index The local index of the policy in the IP asset + /// @param isSet True if the policy is set in the IP asset + /// @param active True if the policy is active + /// @param isInherited True if the policy is inherited from a parent IP asset + struct PolicySetup { + uint256 index; + bool isSet; + bool active; + bool isInherited; + } + /// @notice Emitted when a policy framework is created by registering a policy framework manager /// @param framework The address of the IPolicyFrameworkManager /// @param framework The policy framework data @@ -37,7 +44,7 @@ interface ILicensingModule is IModule { /// @param caller The address that called the function /// @param ipId The id of the IP /// @param policyId The id of the policy - /// @param index The index of the policy in the IP's policy list + /// @param index The index of the policy in the IP's policy set /// @param isInherited Whether the policy was inherited from a parent IP (linking) or set by IP owner event PolicyAddedToIpId( address indexed caller, @@ -53,17 +60,21 @@ interface ILicensingModule is IModule { /// @param parentIpIds The ids of the parent IPs event IpIdLinkedToParents(address indexed caller, address indexed ipId, address[] parentIpIds); - /// @notice Returns the address of the LicenseRegistry - function licenseRegistry() external view returns (address); + /// @notice Returns the canonical protocol-wide RoyaltyModule + function ROYALTY_MODULE() external view returns (RoyaltyModule); + + /// @notice Returns the canonical protocol-wide LicenseRegistry + function LICENSE_REGISTRY() external view returns (ILicenseRegistry); + + /// @notice Returns the canonical protocol-wide DisputeModule + function DISPUTE_MODULE() external view returns (IDisputeModule); - /// @notice Registers a policy framework manager into the contract, so it can add policy data for - /// licenses. + /// @notice Registers a policy framework manager into the contract, so it can add policy data for licenses. /// @param manager the address of the manager. Will be ERC165 checked for IPolicyFrameworkManager function registerPolicyFrameworkManager(address manager) external; - /// @notice Registers a policy into the contract. MUST be called by a registered - /// framework or it will revert. The policy data and its integrity must be - /// verified by the policy framework manager. + /// @notice Registers a policy into the contract. MUST be called by a registered framework or it will revert. + /// The policy data and its integrity must be verified by the policy framework manager. /// @param isLicenseTransferable True if the license is transferable /// @param royaltyPolicy The address of the royalty policy /// @param royaltyData The royalty policy specific encoded data @@ -75,32 +86,34 @@ interface ILicensingModule is IModule { bytes memory frameworkData ) external returns (uint256 policyId); - /// @notice returns the policy id for the given data, or 0 if not found - function getPolicyId(Licensing.Policy calldata pol) external view returns (uint256 policyId); - - /// @notice Adds a policy to an IP policy list + /// @notice Adds a policy to the set of policies of an IP /// @param ipId The id of the IP /// @param polId The id of the policy - /// @return indexOnIpId The index of the policy in the IP's policy list + /// @return indexOnIpId The index of the policy in the IP's policy set function addPolicyToIp(address ipId, uint256 polId) external returns (uint256 indexOnIpId); - /// @notice Mints a license to create derivative IP + /// @notice Mints a license to create derivative IP. License NFTs represent a policy granted by IPs (licensors). + /// Reverts if caller is not authorized by any of the licensors. + /// @dev This NFT needs to be burned in order to link a derivative IP with its parents. If this is the first + /// combination of policy and licensors, a new licenseId will be created (by incrementing prev totalLicenses). + /// If not, the license is fungible and an id will be reused. The licensing terms that regulate creating new + /// licenses will be verified to allow minting. /// @param policyId The id of the policy with the licensing parameters /// @param licensorIpId The id of the licensor IP /// @param amount The amount of licenses to mint /// @param receiver The address that will receive the license /// @param royaltyContext The context for the royalty module to process - /// @return licenseId of the NFT(s) + /// @return licenseId The ID of the license NFT(s) function mintLicense( uint256 policyId, address licensorIpId, - uint256 amount, // mint amount + uint256 amount, address receiver, bytes calldata royaltyContext ) external returns (uint256 licenseId); - /// @notice Links an IP to the licensors (parent IP IDs) listed in the License NFTs, if their policies allow it, - /// burning the NFTs in the proccess. The caller must be the owner of the NFTs and the IP owner. + /// @notice Links an IP to the licensors listed in the license NFTs, if their policies allow it. Burns the license + /// NFTs in the proccess. The caller must be the owner of the IP asset and license NFTs. /// @param licenseIds The id of the licenses to burn /// @param childIpId The id of the child IP to be linked /// @param royaltyContext The context for the royalty module to process @@ -110,58 +123,108 @@ interface ILicensingModule is IModule { /// Getters /// - /// @notice True if the framework address is registered in LicenseRegistry - function isFrameworkRegistered(address framework) external view returns (bool); + /// @notice Returns if the framework address is registered in the LicensingModule. + /// @param policyFramework The address of the policy framework manager + /// @return isRegistered True if the framework is registered + function isFrameworkRegistered(address policyFramework) external view returns (bool); - /// @notice Gets total number of policies (framework parameter configurations) in the contract + /// @notice Returns amount of distinct licensing policies in the LicensingModule. + /// @return totalPolicies The amount of policies function totalPolicies() external view returns (uint256); - /// @notice Gets policy data by id + /// @notice Returns the policy data for policyId, reverts if not found. + /// @param policyId The id of the policy + /// @return pol The policy data function policy(uint256 policyId) external view returns (Licensing.Policy memory pol); - /// @notice True if policy is defined in the contract + /// @notice Returns the policy id for the given policy data, or 0 if not found. + /// @param pol The policy data in Policy struct + /// @return policyId The id of the policy + function getPolicyId(Licensing.Policy calldata pol) external view returns (uint256 policyId); + + /// @notice Returns the policy aggregator data for the given IP ID in the framework. + /// @param framework The address of the policy framework manager + /// @param ipId The id of the IP asset + /// @return data The encoded policy aggregator data to be decoded by the framework manager + function policyAggregatorData(address framework, address ipId) external view returns (bytes memory); + + /// @notice Returns if policyId exists in the LicensingModule + /// @param policyId The id of the policy + /// @return isDefined True if the policy is defined function isPolicyDefined(uint256 policyId) external view returns (bool); - /// @notice Gets the policy ids for an IP + /// @notice Returns the policy ids attached to an IP + /// @dev Potentially gas-intensive operation, use with care. + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset + /// @return policyIds The ids of policy ids for the IP function policyIdsForIp(bool isInherited, address ipId) external view returns (uint256[] memory policyIds); - /// @notice Gets total number of policies for an IP + /// @notice Returns the total number of policies attached to an IP + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset + /// @return totalPolicies The total number of policies for the IP function totalPoliciesForIp(bool isInherited, address ipId) external view returns (uint256); - /// @notice True if policy is part of an IP's policy list + /// @notice Returns if a given policyId is attached to an IP + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset + /// @param policyId The id of the policy + /// @return isSet True if the policy is set in the IP asset function isPolicyIdSetForIp(bool isInherited, address ipId, uint256 policyId) external view returns (bool); - /// @notice True if policy is inherited from an IP - function isPolicyInherited(address ipId, uint256 policyId) external view returns (bool); - - /// @notice Gets the policy ID for an IP by index on the IP's policy list + /// @notice Returns the policy ID for an IP by local index on the IP's policy set + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset to check + /// @param index The local index of a policy in the IP's policy set + /// @return policyId The id of the policy function policyIdForIpAtIndex( bool isInherited, address ipId, uint256 index ) external view returns (uint256 policyId); - /// @notice Gets the policy for an IP by index on the IP's policy list + /// @notice Returns the policy data for an IP by the policy's local index on the IP's policy set + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset to check + /// @param index The local index of a policy in the IP's policy set + /// @return policy The policy data function policyForIpAtIndex( bool isInherited, address ipId, uint256 index ) external view returns (Licensing.Policy memory); - /// @notice Gets the status of a policy in an IP's policy list + /// @notice Returns the status of a policy in an IP's policy set + /// @param ipId The id of the IP asset to check + /// @param policyId The id of the policy + /// @return index The local index of the policy in the IP's policy set + /// @return isInherited True if the policy is inherited from a parent IP + /// @return active True if the policy is active function policyStatus( address ipId, uint256 policyId ) external view returns (uint256 index, bool isInherited, bool active); - function policyAggregatorData(address framework, address ipId) external view returns (bytes memory); + /// @notice Returns if the given policy attached to the given IP is inherited from a parent IP. + /// @param ipId The id of the IP asset that has the policy attached + /// @param policyId The id of the policy to check if inherited + /// @return isInherited True if the policy is inherited from a parent IP + function isPolicyInherited(address ipId, uint256 policyId) external view returns (bool); - /// @notice True if an IP is a derivative of another IP + /// @notice Returns if an IP is a derivative of another IP + /// @param parentIpId The id of the parent IP asset to check + /// @param childIpId The id of the child IP asset to check + /// @return isParent True if the child IP is a derivative of the parent IP function isParent(address parentIpId, address childIpId) external view returns (bool); - /// @notice Returns the parent IP IDs for an IP ID + /// @notice Returns the list of parent IP assets for a given child IP asset + /// @param ipId The id of the child IP asset to check + /// @return parentIpIds The ids of the parent IP assets function parentIpIds(address ipId) external view returns (address[] memory); - /// @notice Total number of parents for an IP ID + /// @notice Returns the total number of parents for an IP asset + /// @param ipId The id of the IP asset to check + /// @return totalParents The total number of parent IP assets function totalParentsForIpId(address ipId) external view returns (uint256); } diff --git a/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol b/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol index 3cc191b7..99a90eea 100644 --- a/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol +++ b/contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol @@ -4,22 +4,23 @@ pragma solidity ^0.8.23; import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; /// @title IPolicyFrameworkManager -/// @notice Interface to define a policy framework contract, that will -/// register itself into the LicenseRegistry to format policy into the LicenseRegistry +/// @notice Interface to define a policy framework contract, that will register itself into the LicenseRegistry to +/// format policy into the LicenseRegistry interface IPolicyFrameworkManager is IERC165 { /// @notice Name to be show in LNFT metadata function name() external view returns (string memory); - /// @notice URL to the off chain legal agreement template text + + /// @notice Returns the URL to the off chain legal agreement template text function licenseTextUrl() external view returns (string memory); - /// @notice address of Story Protocol licensing module - function licensingModule() external view returns (address); - /// @notice called by the LicenseRegistry uri(uint256) method. - /// Must return ERC1155 OpenSea standard compliant metadata + /// @notice Returns the stringified JSON policy data for the LicenseRegistry.uri(uint256) method. + /// @dev Must return ERC1155 OpenSea standard compliant metadata. + /// @param policyData The encoded licensing policy data to be decoded by the PFM + /// @return jsonString The OpenSea-compliant metadata URI of the policy function policyToJson(bytes memory policyData) external view returns (string memory); - /// Called by licenseRegistry to verify compatibility when inheriting from a parent IP - /// The objective is to verify compatibility of multiple policies. + /// @notice Verify compatibility of one or more policies when inheriting them from one or more parent IPs. + /// @dev Enforced to be only callable by LicenseRegistry /// @dev The assumption in this method is that we can add parents later on, hence the need /// for an aggregator, if not we will do this when linking to parents directly with an /// array of policies. @@ -34,13 +35,15 @@ interface IPolicyFrameworkManager is IERC165 { bytes memory policy ) external view returns (bool changedAgg, bytes memory newAggregator); - /// Called by licenseRegistry to verify policy parameters for minting a license + /// @notice Verify policy parameters for minting a license. + /// @dev Enforced to be only callable by LicenseRegistry /// @param caller the address executing the mint /// @param policyWasInherited true if the policy was inherited (licensorIpId is not original IP owner) /// @param receiver the address receiving the license /// @param licensorIpId the IP id of the licensor /// @param mintAmount the amount of licenses to mint /// @param policyData the encoded framework policy data to verify + /// @return verified True if the link is verified function verifyMint( address caller, bool policyWasInherited, @@ -50,13 +53,14 @@ interface IPolicyFrameworkManager is IERC165 { bytes memory policyData ) external returns (bool); - /// Called by licenseRegistry to verify policy parameters for linking a child IP to a parent IP (licensor) - /// by burning a license. + /// @notice Verify policy parameters for linking a child IP to a parent IP (licensor) by burning a license NFT. + /// @dev Enforced to be only callable by LicenseRegistry /// @param licenseId the license id to burn /// @param caller the address executing the link /// @param ipId the IP id of the IP being linked /// @param parentIpId the IP id of the parent IP /// @param policyData the encoded framework policy data to verify + /// @return verified True if the link is verified function verifyLink( uint256 licenseId, address caller, diff --git a/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol b/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol index 28fbf8b7..2d598a08 100644 --- a/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol +++ b/contracts/interfaces/modules/licensing/IUMLPolicyFrameworkManager.sol @@ -3,13 +3,13 @@ pragma solidity ^0.8.23; import { IPolicyFrameworkManager } from "../../../interfaces/modules/licensing/IPolicyFrameworkManager.sol"; -/// @notice Licensing parameters for the UML standard +/// @notice Licensing parameters for the Universal Media License v1 (UML) standard /// @param transferable Whether or not the license is transferable /// @param attribution Whether or not attribution is required when reproducing the work /// @param commercialUse Whether or not the work can be used commercially /// @param commercialAttribution Whether or not attribution is required when reproducing the work commercially -/// @param commercializerChecker commericializers that are allowed to commercially exploit the work. If zero -/// address then no restrictions. +/// @param commercializerChecker commericializers that are allowed to commercially exploit the work. If zero address, +/// then no restrictions is enforced. /// @param commercialRevShare Percentage of revenue that must be shared with the licensor /// @param derivativesAllowed Whether or not the licensee can create derivatives of his work /// @param derivativesAttribution Whether or not attribution is required for derivatives of the work @@ -45,14 +45,13 @@ struct RegisterUMLPolicyParams { UMLPolicy policy; } -/// @notice Struct that accumulates values of inherited policies -/// so we can verify compatibility when inheriting new policies. -/// The assumption is that new policies may be added later, not only when -/// linking an IP to its parent. +/// @notice Struct that accumulates values of inherited policies so we can verify compatibility when inheriting +/// new policies. +/// @dev The assumption is that new policies may be added later, not only when linking an IP to its parent. /// @param commercial Whether or not there is a policy that allows commercial use /// @param derivatives Whether or not there is a policy that allows derivatives -/// @param derivativesReciprocal Whether or not there is a policy that requires derivatives -/// to be licensed under the same terms +/// @param derivativesReciprocal Whether or not there is a policy that requires derivatives to be licensed under the +/// same terms /// @param lastPolicyId The last policy ID that was added to the IP /// @param territoriesAcc The last hash of the territories array /// @param distributionChannelsAcc The last hash of the distributionChannels array @@ -71,15 +70,19 @@ struct UMLAggregator { /// @notice Defines the interface for a Policy Framework Manager compliant with the UML standard interface IUMLPolicyFrameworkManager is IPolicyFrameworkManager { /// @notice Registers a new policy to the registry - /// @dev must generate a Licensing.Policy struct and call registerPolicy + /// @dev Internally, this function must generate a Licensing.Policy struct and call registerPolicy. /// @param params parameters needed to register a UMLPolicy /// @return policyId The ID of the newly registered policy function registerPolicy(RegisterUMLPolicyParams calldata params) external returns (uint256 policyId); - /// @notice gets the aggregation data for inherited policies. + /// @notice Returns the aggregation data for inherited policies of an IP asset. + /// @param ipId The ID of the IP asset to get the aggregator for + /// @return rights The UMLAggregator struct function getAggregator(address ipId) external view returns (UMLAggregator memory rights); /// @notice gets the UMLPolicy for a given policy ID decoded from Licensing.Policy.frameworkData /// @dev Do not call this function from a smart contract, it is only for off-chain + /// @param policyId The ID of the policy to get + /// @return policy The UMLPolicy struct function getUMLPolicy(uint256 policyId) external view returns (UMLPolicy memory policy); } diff --git a/contracts/interfaces/modules/royalty/IRoyaltyModule.sol b/contracts/interfaces/modules/royalty/IRoyaltyModule.sol index ebe25128..48cf5b24 100644 --- a/contracts/interfaces/modules/royalty/IRoyaltyModule.sol +++ b/contracts/interfaces/modules/royalty/IRoyaltyModule.sol @@ -16,42 +16,51 @@ interface IRoyaltyModule is IModule { event RoyaltyTokenWhitelistUpdated(address token, bool allowed); /// @notice Event emitted when a royalty policy is set - /// @param ipId The ipId + /// @param ipId The ID of IP asset /// @param royaltyPolicy The address of the royalty policy /// @param data The data to initialize the policy event RoyaltyPolicySet(address ipId, address royaltyPolicy, bytes data); /// @notice Event emitted when royalties are paid - /// @param receiverIpId The ipId that receives the royalties - /// @param payerIpId The ipId that pays the royalties - /// @param sender The address that pays the royalties on behalf of the payer ipId + /// @param receiverIpId The ID of IP asset that receives the royalties + /// @param payerIpId The ID of IP asset that pays the royalties + /// @param sender The address that pays the royalties on behalf of the payer ID of IP asset /// @param token The token that is used to pay the royalties /// @param amount The amount that is paid event RoyaltyPaid(address receiverIpId, address payerIpId, address sender, address token, uint256 amount); + /// @notice Returns the licensing module address + function LICENSING_MODULE() external view returns (address); + /// @notice Indicates if a royalty policy is whitelisted /// @param royaltyPolicy The address of the royalty policy + /// @return isWhitelisted True if the royalty policy is whitelisted function isWhitelistedRoyaltyPolicy(address royaltyPolicy) external view returns (bool); /// @notice Indicates if a royalty token is whitelisted /// @param token The address of the royalty token + /// @return isWhitelisted True if the royalty token is whitelisted function isWhitelistedRoyaltyToken(address token) external view returns (bool); - /// @notice Indicates the royalty policy for a given ipId - /// @param ipId The ipId + /// @notice Indicates the royalty policy for a given IP asset + /// @param ipId The ID of IP asset + /// @return royaltyPolicy The address of the royalty policy function royaltyPolicies(address ipId) external view returns (address); /// @notice Whitelist a royalty policy + /// @dev Enforced to be only callable by the protocol admin /// @param royaltyPolicy The address of the royalty policy /// @param allowed Indicates if the royalty policy is whitelisted or not function whitelistRoyaltyPolicy(address royaltyPolicy, bool allowed) external; /// @notice Whitelist a royalty token + /// @dev Enforced to be only callable by the protocol admin /// @param token The token address /// @param allowed Indicates if the token is whitelisted or not function whitelistRoyaltyToken(address token, bool allowed) external; /// @notice Executes royalty related logic on license minting + /// @dev Enforced to be only callable by LicensingModule /// @param ipId The ipId whose license is being minted (licensor) /// @param royaltyPolicy The royalty policy address of the license being minted /// @param licenseData The license data custom to each the royalty policy @@ -64,6 +73,7 @@ interface IRoyaltyModule is IModule { ) external; /// @notice Executes royalty related logic on linking to parents + /// @dev Enforced to be only callable by LicensingModule /// @param ipId The children ipId that is being linked to parents /// @param royaltyPolicy The common royalty policy address of all the licenses being burned /// @param parentIpIds The parent ipIds that the children ipId is being linked to @@ -77,9 +87,9 @@ interface IRoyaltyModule is IModule { bytes calldata externalData ) external; - /// @notice Allows a sender to to pay royalties on behalf of an ipId - /// @param receiverIpId The ipId that receives the royalties - /// @param payerIpId The ipId that pays the royalties + /// @notice Allows the function caller to pay royalties to the receiver IP asset on behalf of the payer IP asset. + /// @param receiverIpId The ID of the IP asset that receives the royalties + /// @param payerIpId The ID of the IP asset that pays the royalties /// @param token The token to use to pay the royalties /// @param amount The amount to pay function payRoyaltyOnBehalf(address receiverIpId, address payerIpId, address token, uint256 amount) external; diff --git a/contracts/interfaces/modules/royalty/policies/IAncestorsVaultLAP.sol b/contracts/interfaces/modules/royalty/policies/IAncestorsVaultLAP.sol index 883014c2..a6621906 100644 --- a/contracts/interfaces/modules/royalty/policies/IAncestorsVaultLAP.sol +++ b/contracts/interfaces/modules/royalty/policies/IAncestorsVaultLAP.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.23; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { IRoyaltyPolicyLAP } from "./IRoyaltyPolicyLAP.sol"; + /// @title Liquid absolute percentage policy ancestor vault interface interface IAncestorsVaultLAP { /// @notice Event emitted when a claim is made @@ -12,8 +14,11 @@ interface IAncestorsVaultLAP { /// @param tokens The ERC20 tokens to withdraw event Claimed(address ipId, address claimerIpId, bool withdrawETH, ERC20[] tokens); - /// @notice Allows an ipId to claim their rnfts and accrued royalties - /// @param ipId The ipId of the IP + /// @notice Returns the canonical RoyaltyPolicyLAP + function ROYALTY_POLICY_LAP() external view returns (IRoyaltyPolicyLAP); + + /// @notice Allows an ancestor IP asset to claim their Royalty NFTs and accrued royalties + /// @param ipId The ipId of the IP asset /// @param claimerIpId The ipId of the claimer /// @param ancestors The ancestors of the IP /// @param ancestorsRoyalties The royalties of the ancestors diff --git a/contracts/interfaces/modules/royalty/policies/ILSClaimer.sol b/contracts/interfaces/modules/royalty/policies/ILSClaimer.sol deleted file mode 100644 index a95ca293..00000000 --- a/contracts/interfaces/modules/royalty/policies/ILSClaimer.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.23; - -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; - -/// @title Liquid split policy claimer interface -interface ILSClaimer { - /// @notice Event emitted when a claim is made - /// @param path The path from the ipId to the claimer - /// @param claimer The claimer ipId address - /// @param withdrawETH Indicates if the claimer wants to withdraw ETH - /// @param tokens The ERC20 tokens to withdraw - event Claimed(address[] path, address claimer, bool withdrawETH, ERC20[] tokens); - - /// @notice Allows an ipId to claim their rnfts and accrued royalties - /// @param path The path of the ipId - /// @param claimerIpId The ipId of the claimer - /// @param withdrawETH Indicates if the claimer wants to withdraw ETH - /// @param tokens The ERC20 tokens to withdraw - function claim(address[] calldata path, address claimerIpId, bool withdrawETH, ERC20[] calldata tokens) external; -} diff --git a/contracts/interfaces/modules/royalty/policies/ILiquidSplitClone.sol b/contracts/interfaces/modules/royalty/policies/ILiquidSplitClone.sol index 21cd532d..6e63b95f 100644 --- a/contracts/interfaces/modules/royalty/policies/ILiquidSplitClone.sol +++ b/contracts/interfaces/modules/royalty/policies/ILiquidSplitClone.sol @@ -17,5 +17,9 @@ interface ILiquidSplitClone { /// @param data Custom data function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external; + /// @notice Returns the balance of the account + /// @param account The account to check + /// @param id The token id + /// @return balance The balance of the account function balanceOf(address account, uint256 id) external view returns (uint256); } diff --git a/contracts/interfaces/modules/royalty/policies/ILiquidSplitFactory.sol b/contracts/interfaces/modules/royalty/policies/ILiquidSplitFactory.sol index d9647e64..fbff5a59 100644 --- a/contracts/interfaces/modules/royalty/policies/ILiquidSplitFactory.sol +++ b/contracts/interfaces/modules/royalty/policies/ILiquidSplitFactory.sol @@ -8,6 +8,7 @@ interface ILiquidSplitFactory { /// @param initAllocations The initial allocations /// @param _distributorFee The distributor fee /// @param owner The owner of the LiquidSplitClone contract + /// @return address The address of the new LiquidSplitClone contract function createLiquidSplitClone( address[] calldata accounts, uint32[] calldata initAllocations, diff --git a/contracts/interfaces/modules/royalty/policies/ILiquidSplitMain.sol b/contracts/interfaces/modules/royalty/policies/ILiquidSplitMain.sol index 7723e1f0..f28bb941 100644 --- a/contracts/interfaces/modules/royalty/policies/ILiquidSplitMain.sol +++ b/contracts/interfaces/modules/royalty/policies/ILiquidSplitMain.sol @@ -13,10 +13,12 @@ interface ILiquidSplitMain { /// @notice Gets the ETH balance of an account /// @param account The account to get the ETH balance of + /// @return balance The ETH balance of the account function getETHBalance(address account) external view returns (uint256); /// @notice Gets the ERC20 balance of an account /// @param account The account to get the ERC20 balance of /// @param token The token to get the balance of + /// @return balance The ERC20 balance of the account function getERC20Balance(address account, ERC20 token) external view returns (uint256); } diff --git a/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicy.sol b/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicy.sol index 1acc20e8..04426c45 100644 --- a/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicy.sol +++ b/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicy.sol @@ -4,26 +4,28 @@ pragma solidity ^0.8.23; /// @title RoyaltyPolicy interface interface IRoyaltyPolicy { /// @notice Executes royalty related logic on minting a license - /// @param _ipId The children ipId that is being linked to parents - /// @param _licenseData The license data custom to each the royalty policy - /// @param _externalData The external data custom to each the royalty policy - function onLicenseMinting(address _ipId, bytes calldata _licenseData, bytes calldata _externalData) external; + /// @dev Enforced to be only callable by RoyaltyModule + /// @param ipId The children ipId that is being linked to parents + /// @param licenseData The license data custom to each the royalty policy + /// @param externalData The external data custom to each the royalty policy + function onLicenseMinting(address ipId, bytes calldata licenseData, bytes calldata externalData) external; /// @notice Executes royalty related logic on linking to parents - /// @param _ipId The children ipId that is being linked to parents - /// @param _parentIpIds The selected parent ipIds - /// @param _licenseData The license data custom to each the royalty policy - /// @param _externalData The external data custom to each the royalty policy + /// @dev Enforced to be only callable by RoyaltyModule + /// @param ipId The children ipId that is being linked to parents + /// @param parentIpIds The selected parent ipIds + /// @param licenseData The license data custom to each the royalty policy + /// @param externalData The external data custom to each the royalty policy function onLinkToParents( - address _ipId, - address[] calldata _parentIpIds, - bytes[] memory _licenseData, - bytes calldata _externalData + address ipId, + address[] calldata parentIpIds, + bytes[] memory licenseData, + bytes calldata externalData ) external; - /// @notice Allows to pay a royalty + /// @notice Allows the caller to pay royalty to the given IP asset /// @param caller The caller - /// @param ipId The ipId + /// @param ipId The ID of the IP asset /// @param token The token to pay /// @param amount The amount to pay function onRoyaltyPayment(address caller, address ipId, address token, uint256 amount) external; diff --git a/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicyLAP.sol b/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicyLAP.sol index 6f335950..82f411fe 100644 --- a/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicyLAP.sol +++ b/contracts/interfaces/modules/royalty/policies/IRoyaltyPolicyLAP.sol @@ -17,7 +17,7 @@ interface IRoyaltyPolicyLAP is IRoyaltyPolicy { } /// @notice Event emitted when a policy is initialized - /// @param ipId The ipId + /// @param ipId The ID of the IP asset that the policy is being initialized for /// @param splitClone The split clone address /// @param ancestorsVault The ancestors vault address /// @param royaltyStack The royalty stack @@ -32,13 +32,13 @@ interface IRoyaltyPolicyLAP is IRoyaltyPolicy { uint32[] targetRoyaltyAmount ); - /// @notice Gets the royalty data - /// @param ipId The ipId + /// @notice Returns the royalty data for a given IP asset + /// @param ipId The ID of the IP asset /// @return isUnlinkable Indicates if the ipId is unlinkable - /// splitClone The split clone address - /// ancestorsVault The ancestors vault address - /// royaltyStack The royalty stack - /// ancestorsHash The unique ancestors hash + /// @return splitClone The split clone address + /// @return ancestorsVault The ancestors vault address + /// @return royaltyStack The royalty stack + /// @return ancestorsHash The unique ancestors hash function royaltyData( address ipId ) @@ -52,51 +52,69 @@ interface IRoyaltyPolicyLAP is IRoyaltyPolicy { bytes32 ancestorsHash ); - /// @notice Gets liquid split main address + /// @notice Returns the percentage scale - 1000 rnfts represents 100% + function TOTAL_RNFT_SUPPLY() external view returns (uint32); + + /// @notice Returns the maximum number of parents + function MAX_PARENTS() external view returns (uint256); + + /// @notice Returns the maximum number of total ancestors. + /// @dev The IP derivative tree is limited to 14 ancestors, which represents 3 levels of a binary tree 14 = 2+4+8 + function MAX_ANCESTORS() external view returns (uint256); + + /// @notice Returns the RoyaltyModule address + function ROYALTY_MODULE() external view returns (address); + + /// @notice Returns the LicensingModule address + function LICENSING_MODULE() external view returns (address); + + /// @notice Returns the 0xSplits LiquidSplitFactory address + function LIQUID_SPLIT_FACTORY() external view returns (address); + + /// @notice Returns the 0xSplits LiquidSplitMain address function LIQUID_SPLIT_MAIN() external view returns (address); - /// @notice Set the ancestors vault implementation address - /// @param _ancestorsVaultImpl The ancestors vault implementation address - function setAncestorsVaultImplementation(address _ancestorsVaultImpl) external; + /// @notice Returns the Ancestors Vault Implementation address + function ANCESTORS_VAULT_IMPL() external view returns (address); /// @notice Distributes funds internally so that accounts holding the royalty nfts at distribution moment can /// claim afterwards - /// @param _ipId The ipId - /// @param _token The token to distribute - /// @param _accounts The accounts to distribute to - /// @param _distributorAddress The distributor address + /// @param ipId The ipId + /// @param token The token to distribute + /// @param accounts The accounts to distribute to + /// @param distributorAddress The distributor address function distributeIpPoolFunds( - address _ipId, - address _token, - address[] calldata _accounts, - address _distributorAddress + address ipId, + address token, + address[] calldata accounts, + address distributorAddress ) external; /// @notice Claims the available royalties for a given address - /// @param _account The account to claim for - /// @param _withdrawETH The amount of ETH to withdraw - /// @param _tokens The tokens to withdraw - function claimFromIpPool(address _account, uint256 _withdrawETH, ERC20[] calldata _tokens) external; + /// @param account The account to claim for + /// @param withdrawETH The amount of ETH to withdraw + /// @param tokens The tokens to withdraw + function claimFromIpPool(address account, uint256 withdrawETH, ERC20[] calldata tokens) external; /// @notice Claims the available royalties for a given address that holds all the royalty nfts of an ipId - /// @param _ipId The ipId - /// @param _withdrawETH The amount of ETH to withdraw - /// @param _token The token to withdraw - function claimFromIpPoolAsTotalRnftOwner(address _ipId, uint256 _withdrawETH, address _token) external; + /// @param ipId The ipId + /// @param withdrawETH The amount of ETH to withdraw + /// @param token The token to withdraw + function claimFromIpPoolAsTotalRnftOwner(address ipId, uint256 withdrawETH, address token) external; /// @notice Claims all available royalty nfts and accrued royalties for an ancestor of a given ipId - /// @param _ipId The ipId - /// @param _claimerIpId The claimer ipId - /// @param _ancestors The ancestors of the IP - /// @param _ancestorsRoyalties The royalties of the ancestors - /// @param _withdrawETH Indicates if the claimer wants to withdraw ETH - /// @param _tokens The ERC20 tokens to withdraw + /// @param ipId The ipId + /// @param claimerIpId The claimer ipId + /// @param ancestors The ancestors of the IP + /// @param ancestorsRoyalties The royalties of the ancestors + /// @param withdrawETH Indicates if the claimer wants to withdraw ETH + /// @param tokens The ERC20 tokens to withdraw function claimFromAncestorsVault( - address _ipId, - address _claimerIpId, - address[] calldata _ancestors, - uint32[] calldata _ancestorsRoyalties, - bool _withdrawETH, - ERC20[] calldata _tokens + address ipId, + address claimerIpId, + address[] calldata ancestors, + uint32[] calldata ancestorsRoyalties, + bool withdrawETH, + ERC20[] calldata tokens ) external; } diff --git a/contracts/interfaces/registries/IIPAccountRegistry.sol b/contracts/interfaces/registries/IIPAccountRegistry.sol index d6bc8d0d..e17c6612 100644 --- a/contracts/interfaces/registries/IIPAccountRegistry.sol +++ b/contracts/interfaces/registries/IIPAccountRegistry.sol @@ -8,7 +8,7 @@ interface IIPAccountRegistry { /// @notice Event emitted when a new IP Account is created /// @param account The address of the new IP Account /// @param implementation The address of the IP Account implementation - /// @param chainId The chain ID where the token contract deployed + /// @param chainId The chain ID where the token contract was deployed /// @param tokenContract The address of the token contract associated with the IP Account /// @param tokenId The ID of the token associated with the IP Account event IPAccountRegistered( @@ -19,22 +19,34 @@ interface IIPAccountRegistry { uint256 tokenId ); + /// @notice Returns the IPAccount implementation address + function IP_ACCOUNT_IMPL() external view returns (address); + + /// @notice Returns the IPAccount salt + function IP_ACCOUNT_SALT() external view returns (bytes32); + + /// @notice Returns the public ERC6551 registry address + function ERC6551_PUBLIC_REGISTRY() external view returns (address); + + /// @notice Returns the access controller address + function ACCESS_CONTROLLER() external view returns (address); + /// @notice Deploys an IPAccount contract with the IPAccount implementation and returns the address of the new IP /// @dev The IPAccount deployment deltegates to public ERC6551 Registry - /// @param chainId_ The chain ID where the token contract deployed - /// @param tokenContract_ The address of the token contract to be associated with the IP Account - /// @param tokenId_ The ID of the token to be associated with the IP Account - /// @return The address of the newly created IP Account - function registerIpAccount(uint256 chainId_, address tokenContract_, uint256 tokenId_) external returns (address); - - /// @notice Returns the IPAccount address for the given NFT token - /// @param chainId_ The chain ID where the token contract deployed - /// @param tokenContract_ The address of the token contract associated with the IP Account - /// @param tokenId_ The ID of the token associated with the IP Account - /// @return The address of the IP Account associated with the given NFT token - function ipAccount(uint256 chainId_, address tokenContract_, uint256 tokenId_) external view returns (address); + /// @param chainId The chain ID where the IP Account will be created + /// @param tokenContract The address of the token contract to be associated with the IP Account + /// @param tokenId The ID of the token to be associated with the IP Account + /// @return ipAccountAddress The address of the newly created IP Account + function registerIpAccount(uint256 chainId, address tokenContract, uint256 tokenId) external returns (address); - /// @notice Returns the IPAccount implementation address + /// @notice Returns the IPAccount address for the given NFT token. + /// @param chainId The chain ID where the IP Account is located + /// @param tokenContract The address of the token contract associated with the IP Account + /// @param tokenId The ID of the token associated with the IP Account + /// @return ipAccountAddress The address of the IP Account associated with the given NFT token + function ipAccount(uint256 chainId, address tokenContract, uint256 tokenId) external view returns (address); + + /// @notice Returns the IPAccount implementation address. /// @return The address of the IPAccount implementation function getIPAccountImpl() external view returns (address); } diff --git a/contracts/interfaces/registries/IIPAssetRegistry.sol b/contracts/interfaces/registries/IIPAssetRegistry.sol index a8b9ee42..81683580 100644 --- a/contracts/interfaces/registries/IIPAssetRegistry.sol +++ b/contracts/interfaces/registries/IIPAssetRegistry.sol @@ -3,10 +3,23 @@ pragma solidity ^0.8.23; import { IIPAccountRegistry } from "./IIPAccountRegistry.sol"; +import { IModuleRegistry } from "./IModuleRegistry.sol"; +import { IMetadataProviderMigratable } from "./metadata/IMetadataProviderMigratable.sol"; +import { IRegistrationModule } from "../modules/IRegistrationModule.sol"; /// @title Interface for IP Account Registry /// @notice This interface manages the registration and tracking of IP Accounts interface IIPAssetRegistry is IIPAccountRegistry { + // TODO: Deprecate `resolver` in favor of consolidation through the provider. + /// @notice Attributes for the IP asset type. + /// @param metadataProvider Metadata provider for Story Protocol canonicalized metadata. + /// @param resolver Metadata resolver for custom metadata added by the IP owner. + struct Record { + IMetadataProviderMigratable metadataProvider; + address resolver; + } + + // TODO: Add support for optional licenseIds. /// @notice Emits when an IP is officially registered into the protocol. /// @param ipId The canonical identifier for the IP. /// @param chainId The chain identifier of where the IP resides. @@ -15,7 +28,6 @@ interface IIPAssetRegistry is IIPAccountRegistry { /// @param resolver The address of the resolver linked to the IP. /// @param provider The address of the metadata provider linked to the IP. /// @param metadata Canonical metadata that was linked to the IP. - /// TODO: Add support for optional licenseIds. event IPRegistered( address ipId, uint256 indexed chainId, @@ -43,71 +55,105 @@ interface IIPAssetRegistry is IIPAccountRegistry { /// @param metadata The canonical metadata in bytes associated with the IP. event MetadataSet(address indexed ipId, address indexed metadataProvider, bytes metadata); - /// @notice Upgrades the metadata for an IP asset, migrating to a new provider. - /// @param id The canonical ID of the IP. - /// @param metadataProvider Address of the new metadata provider hosting the data. - /// @param data Canonical metadata to associate with the IP. - function setMetadata(address id, address metadataProvider, bytes calldata data) external; + /// @notice The canonical module registry used by the protocol. + function MODULE_REGISTRY() external view returns (IModuleRegistry); + + /// @notice The registration module that interacts with IPAssetRegistry. + function REGISTRATION_MODULE() external view returns (IRegistrationModule); + + /// @notice Tracks the total number of IP assets in existence. + function totalSupply() external view returns (uint256); + + /// @notice Checks whether an operator is approved to register on behalf of an IP owner. + /// @param owner The address of the IP owner whose approval is being checked for. + /// @param operator The address of the operator the owner has approved for registration delgation. + /// @return Whether the operator is approved on behalf of the owner for registering. + function isApprovedForAll(address owner, address operator) external view returns (bool); - /// @notice Sets the metadata provider to use for new registrations. - /// @param metadataProvider The address of the new metadata provider to use. - function setMetadataProvider(address metadataProvider) external; + /// @notice Enables third party operators to register on behalf of an NFT owner. + /// @param operator The address of the operator the sender authorizes. + /// @param approved Whether or not to approve that operator for registration. + function setApprovalForAll(address operator, bool approved) external; /// @notice Registers an NFT as IP, creating a corresponding IP record. - /// @param chainId The chain identifier of where the IP resides. - /// @param tokenContract The address of the IP. - /// @param tokenId The token identifier of the IP. + /// @param chainId The chain identifier of where the NFT resides. + /// @param tokenContract The address of the NFT. + /// @param tokenId The token identifier of the NFT. /// @param resolverAddr The address of the resolver to associate with the IP. - /// @param createAccount Whether to create an IP account in the process. - /// @param metadata Metadata in bytes to associate with the IP. + /// @param createAccount Whether to create an IP account when registering. + /// @param metadata_ Metadata in bytes to associate with the IP. + /// @return ipId_ The address of the newly registered IP. function register( uint256 chainId, address tokenContract, uint256 tokenId, address resolverAddr, bool createAccount, - bytes calldata metadata + bytes calldata metadata_ ) external returns (address); - /// @notice Sets the resolver for an IP based on its canonical ID. - /// @param id The canonical ID of the IP. - /// @param resolverAddr The address of the resolver being set. - function setResolver(address id, address resolverAddr) external; + /// @notice Registers an NFT as an IP using licenses derived from parent IP asset(s). + /// @param licenseIds The parent IP asset licenses used to derive the new IP asset. + /// @param royaltyContext The context for the royalty module to process. + /// @param chainId The chain identifier of where the NFT resides. + /// @param tokenContract The address of the NFT. + /// @param tokenId The token identifier of the NFT. + /// @param resolverAddr The address of the resolver to associate with the IP. + /// @param createAccount Whether to create an IP account when registering. + /// @param metadata_ Metadata in bytes to associate with the IP. + /// @return ipId_ The address of the newly registered IP. + function register( + uint256[] calldata licenseIds, + bytes calldata royaltyContext, + uint256 chainId, + address tokenContract, + uint256 tokenId, + address resolverAddr, + bool createAccount, + bytes calldata metadata_ + ) external returns (address); - /// @notice Gets the canonical IP identifier associated with an IP (NFT). - /// @dev This is the same as the address of the IP account bound to the IP. + /// @notice Gets the canonical IP identifier associated with an IP NFT. + /// @dev This is equivalent to the address of its bound IP account. /// @param chainId The chain identifier of where the IP resides. /// @param tokenContract The address of the IP. /// @param tokenId The token identifier of the IP. - /// @return The address of the associated IP account. + /// @return ipId The IP's canonical address identifier. function ipId(uint256 chainId, address tokenContract, uint256 tokenId) external view returns (address); - /// @notice Checks whether an operator is approved to register on behalf of an IP owner. - /// @param owner The address of the IP owner whose approval is being checked for. - /// @param operator The address of the operator the owner has approved for registration delgation. - /// @return Whether the operator is approved on behalf of the owner for registering. - function isApprovedForAll(address owner, address operator) external view returns (bool); - /// @notice Checks whether an IP was registered based on its ID. /// @param id The canonical identifier for the IP. - /// @return Whether the IP was registered into the protocol. + /// @return isRegistered Whether the IP was registered into the protocol. function isRegistered(address id) external view returns (bool); /// @notice Gets the resolver bound to an IP based on its ID. /// @param id The canonical identifier for the IP. - /// @return The IP resolver address if registered, else the zero address. + /// @return resolver The IP resolver address if registered, else the zero address. function resolver(address id) external view returns (address); - /// @notice Gets the metadata provider currently used for metadata storage. + /// @notice Gets the metadata provider used for new metadata registrations. + /// @return metadataProvider The address of the metadata provider used for new IP registrations. function metadataProvider() external view returns (address); /// @notice Gets the metadata provider linked to an IP based on its ID. /// @param id The canonical identifier for the IP. - /// @return The metadata provider that was bound to this IP at creation time. + /// @return metadataProvider The metadata provider that was bound to this IP at creation time. function metadataProvider(address id) external view returns (address); - /// @notice Gets the metadata linked to an IP based on its ID. - /// @param id The canonical identifier for the IP. - /// @return The metadata that was bound to this IP at creation time. + /// @notice Gets the underlying canonical metadata linked to an IP asset. + /// @param id The canonical ID of the IP asset. + /// @return metadata The metadata that was bound to this IP at creation time. function metadata(address id) external view returns (bytes memory); + + /// @notice Sets the underlying metadata for an IP asset. + /// @dev As metadata is immutable but additive, this will only be used when an IP migrates from a new provider that + /// introduces new attributes. + /// @param id The canonical ID of the IP. + /// @param data Canonical metadata to associate with the IP. + function setMetadata(address id, address metadataProvider, bytes calldata data) external; + + /// @notice Sets the resolver for an IP based on its canonical ID. + /// @param id The canonical ID of the IP. + /// @param resolverAddr The address of the resolver being set. + function setResolver(address id, address resolverAddr) external; } diff --git a/contracts/interfaces/registries/ILicenseRegistry.sol b/contracts/interfaces/registries/ILicenseRegistry.sol index 13b4e90d..2d3b344d 100644 --- a/contracts/interfaces/registries/ILicenseRegistry.sol +++ b/contracts/interfaces/registries/ILicenseRegistry.sol @@ -4,14 +4,15 @@ pragma solidity ^0.8.23; import { IERC1155 } from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import { Licensing } from "../../lib/Licensing.sol"; +import { IDisputeModule } from "../modules/dispute/IDisputeModule.sol"; +import { ILicensingModule } from "../modules/licensing/ILicensingModule.sol"; /// @title ILicenseRegistry - interface ILicenseRegistry is IERC1155 { /// @notice Emitted when a license is minted /// @param creator The address that created the license /// @param receiver The address that received the license - /// @param licenseId The id of the license + /// @param licenseId The ID of the license /// @param amount The amount of licenses minted /// @param licenseData The license data event LicenseMinted( @@ -22,18 +23,25 @@ interface ILicenseRegistry is IERC1155 { Licensing.License licenseData ); - /// @notice Returns the address of the licensing module - function licensingModule() external view returns (address); + /// @notice Returns the canonical protocol-wide LicensingModule + function LICENSING_MODULE() external view returns (ILicensingModule); + + /// @notice Returns the canonical protocol-wide DisputeModule + function DISPUTE_MODULE() external view returns (IDisputeModule); - /// @notice Mints a license to create derivative IP - /// @param policyId The id of the policy with the licensing parameters - /// @param licensorIpId The id of the licensor IP + /// @notice Mints license NFTs representing a policy granted by a set of ipIds (licensors). This NFT needs to be + /// burned in order to link a derivative IP with its parents. If this is the first combination of policy and + /// licensors, a new licenseId will be created. If not, the license is fungible and an id will be reused. + /// @dev Only callable by the licensing module. + /// @param policyId The ID of the policy to be minted + /// @param licensorIpId_ The ID of the IP granting the license (ie. licensor) /// @param transferable True if the license is transferable - /// @param amount The amount of licenses to mint - /// @param receiver The address that will receive the license + /// @param amount Number of licenses to mint. License NFT is fungible for same policy and same licensors + /// @param receiver Receiver address of the minted license NFT(s). + /// @return licenseId The ID of the minted license NFT(s). function mintLicense( uint256 policyId, - address licensorIpId, + address licensorIpId_, bool transferable, uint256 amount, address receiver @@ -48,22 +56,34 @@ interface ILicenseRegistry is IERC1155 { /// Getters /// + /// @notice Returns the number of licenses registered in the protocol. + /// @dev Token ID counter total count. + /// @return mintedLicenses The number of minted licenses function mintedLicenses() external view returns (uint256); - /// @notice True if holder is the licensee for the license (owner of the license NFT), or derivative IP owner if - /// the license was added to the IP by linking (burning a license) + /// @notice Returns true if holder has positive balance for the given license ID. + /// @return isLicensee True if holder is the licensee for the license (owner of the license NFT), or derivative IP + /// owner if the license was added to the IP by linking (burning a license). function isLicensee(uint256 licenseId, address holder) external view returns (bool); - /// @notice IP ID of the licensor for the license (parent IP) + /// @notice Returns the license data for the given license ID + /// @param licenseId The ID of the license + /// @return licenseData The license data + function license(uint256 licenseId) external view returns (Licensing.License memory); + + /// @notice Returns the ID of the IP asset that is the licensor of the given license ID + /// @param licenseId The ID of the license + /// @return licensorIpId The ID of the licensor function licensorIpId(uint256 licenseId) external view returns (address); + /// @notice Returns the policy ID for the given license ID + /// @param licenseId The ID of the license + /// @return policyId The ID of the policy function policyIdForLicense(uint256 licenseId) external view returns (uint256); - /// @notice License data (licensor, policy...) for the license id - function license(uint256 licenseId) external view returns (Licensing.License memory); - /// @notice Returns true if the license has been revoked (licensor tagged after a dispute in /// the dispute module). If the tag is removed, the license is not revoked anymore. - /// @param licenseId The id of the license + /// @param licenseId The id of the license to check + /// @return isRevoked True if the license is revoked function isLicenseRevoked(uint256 licenseId) external view returns (bool); } diff --git a/contracts/interfaces/registries/IModuleRegistry.sol b/contracts/interfaces/registries/IModuleRegistry.sol index e0cd3f84..258586a6 100644 --- a/contracts/interfaces/registries/IModuleRegistry.sol +++ b/contracts/interfaces/registries/IModuleRegistry.sol @@ -15,17 +15,34 @@ interface IModuleRegistry { /// @param module The address of the module. event ModuleRemoved(string name, address indexed module); + /// @notice Returns the address of a registered module by its name. + /// @param name The name of the module. + /// @return moduleAddress The address of the module. + function getModule(string memory name) external view returns (address); + + /// @notice Returns the module type of a registered module by its address. + /// @param moduleAddress The address of the module. + /// @return moduleType The type of the module as a string. + function getModuleType(address moduleAddress) external view returns (string memory); + + /// @notice Returns the interface ID of a registered module type. + /// @param moduleType The name of the module type. + /// @return moduleTypeInterfaceId The interface ID of the module type as bytes4. + function getModuleTypeInterfaceId(string memory moduleType) external view returns (bytes4); + /// @notice Registers a new module type in the registry associate with an interface. + /// @dev Enforced to be only callable by the protocol admin in governance. /// @param name The name of the module type to be registered. /// @param interfaceId The interface ID associated with the module type. function registerModuleType(string memory name, bytes4 interfaceId) external; /// @notice Removes a module type from the registry. + /// @dev Enforced to be only callable by the protocol admin in governance. /// @param name The name of the module type to be removed. function removeModuleType(string memory name) external; /// @notice Registers a new module in the registry. - /// @dev This function can only be called by the owner of the registry. + /// @dev Enforced to be only callable by the protocol admin in governance. /// @param name The name of the module. /// @param moduleAddress The address of the module. function registerModule(string memory name, address moduleAddress) external; @@ -37,27 +54,12 @@ interface IModuleRegistry { function registerModule(string memory name, address moduleAddress, string memory moduleType) external; /// @notice Removes a module from the registry. - /// @dev This function can only be called by the owner of the registry. + /// @dev Enforced to be only callable by the protocol admin in governance. /// @param name The name of the module. function removeModule(string memory name) external; - /// @notice Returns the address of a module by its name. - /// @param name The name of the module. - /// @return The address of the module. - function getModule(string memory name) external view returns (address); - - /// @notice Checks if a module is registered in the registry. + /// @notice Checks if a module is registered in the protocol. /// @param moduleAddress The address of the module. - /// @return A boolean indicating whether the module is registered. + /// @return isRegistered True if the module is registered, false otherwise. function isRegistered(address moduleAddress) external view returns (bool); - - /// @notice Returns the module type of a given module address. - /// @param moduleAddress The address of the module. - /// @return The type of the module as a string. - function getModuleType(address moduleAddress) external view returns (string memory); - - /// @notice Returns the interface ID associated with a given module type. - /// @param moduleType The type of the module as a string. - /// @return The interface ID of the module type as bytes4. - function getModuleTypeInterfaceId(string memory moduleType) external view returns (bytes4); } diff --git a/contracts/interfaces/registries/metadata/IMetadataProvider.sol b/contracts/interfaces/registries/metadata/IMetadataProvider.sol index ad21ef35..991cfe29 100644 --- a/contracts/interfaces/registries/metadata/IMetadataProvider.sol +++ b/contracts/interfaces/registries/metadata/IMetadataProvider.sol @@ -5,14 +5,17 @@ pragma solidity ^0.8.23; /// @title Metadata Provider Interface interface IMetadataProvider { /// @notice Emits when canonical metadata was set for a specific IP asset. + /// @param ipId The address of the IP asset. + /// @param metadata The encoded metadata associated with the IP asset. event MetadataSet(address ipId, bytes metadata); /// @notice Gets the metadata associated with an IP asset. /// @param ipId The address identifier of the IP asset. + /// @return metadata The encoded metadata associated with the IP asset. function getMetadata(address ipId) external view returns (bytes memory); /// @notice Sets the metadata associated with an IP asset. /// @param ipId The address identifier of the IP asset. - /// @param metadata Metadata in bytes to associate with the IP asset. + /// @param metadata The metadata in bytes to associate with the IP asset. function setMetadata(address ipId, bytes memory metadata) external; } diff --git a/contracts/interfaces/registries/metadata/IMetadataProviderMigratable.sol b/contracts/interfaces/registries/metadata/IMetadataProviderMigratable.sol index c283d92e..eb489977 100644 --- a/contracts/interfaces/registries/metadata/IMetadataProviderMigratable.sol +++ b/contracts/interfaces/registries/metadata/IMetadataProviderMigratable.sol @@ -3,20 +3,23 @@ pragma solidity ^0.8.23; import { IMetadataProvider } from "./IMetadataProvider.sol"; +import { IIPAssetRegistry } from "../IIPAssetRegistry.sol"; /// @title Metadata Provider Interface interface IMetadataProviderMigratable is IMetadataProvider { + /// @notice Returns the protocol-wide IP asset registry. + function IP_ASSET_REGISTRY() external view returns (IIPAssetRegistry); + /// @notice Returns the new metadata provider IP assets may migrate to. - /// @return Address of the new metadata provider if set, else the zero address. function upgradeProvider() external returns (IMetadataProvider); - /// @notice Sets a new metadata provider that IP assets may migrate to. - /// @param provider The address of the new metadata provider. + /// @notice Sets a upgrade provider for users to migrate their metadata to. + /// @param provider The address of the new metadata provider to migrate to. function setUpgradeProvider(address provider) external; - /// @notice Updates the provider used by the IP asset, migrating existing - /// metadata to the new provider, and adding new metadata. + /// @notice Updates the provider used by the IP asset, migrating existing metadata to the new provider, and adding + /// new metadata. /// @param ipId The address identifier of the IP asset. - /// @param extraMetadata Additional metadata used by the new metadata provider. + /// @param extraMetadata Additional metadata in bytes used by the new metadata provider. function upgrade(address payable ipId, bytes memory extraMetadata) external; } diff --git a/contracts/interfaces/registries/metadata/IMetadataProviderV1.sol b/contracts/interfaces/registries/metadata/IMetadataProviderV1.sol index 0b826c0b..67f1aaa9 100644 --- a/contracts/interfaces/registries/metadata/IMetadataProviderV1.sol +++ b/contracts/interfaces/registries/metadata/IMetadataProviderV1.sol @@ -9,25 +9,31 @@ import { IMetadataProvider } from "./IMetadataProvider.sol"; interface IMetadataProviderV1 is IMetadataProvider { /// @notice Fetches the metadata linked to an IP asset. /// @param ipId The address identifier of the IP asset. + /// @return metadata The metadata linked to the IP asset. function metadata(address ipId) external view returns (IP.MetadataV1 memory); /// @notice Gets the name associated with the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return name The name associated with the IP asset. function name(address ipId) external view returns (string memory); /// @notice Gets the hash associated with the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return hash The hash associated with the IP asset. function hash(address ipId) external view returns (bytes32); /// @notice Gets the date in which the IP asset was registered. /// @param ipId The address identifier of the IP asset. + /// @return registrationDate The date in which the IP asset was registered. function registrationDate(address ipId) external view returns (uint64); /// @notice Gets the initial registrant address of the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return registrant The initial registrant address of the IP asset. function registrant(address ipId) external view returns (address); /// @notice Gets the external URI associated with the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return uri The external URI associated with the IP asset. function uri(address ipId) external view returns (string memory); } diff --git a/contracts/interfaces/resolvers/IKeyValueResolver.sol b/contracts/interfaces/resolvers/IKeyValueResolver.sol index 637dd327..311c6a2c 100644 --- a/contracts/interfaces/resolvers/IKeyValueResolver.sol +++ b/contracts/interfaces/resolvers/IKeyValueResolver.sol @@ -7,6 +7,15 @@ interface IKeyValueResolver { /// @notice Emits when a new key-value pair is set for the resolver. event KeyValueSet(address indexed ipId, string indexed key, string value); + /// @notice Sets the string value for a specified key of an IP ID. + /// @dev Enforced to be only callable by users with valid permission to call on behalf of the ipId. + /// @param ipId The canonical identifier of the IP asset. + /// @param key The string parameter key to update. + /// @param val The value to set for the specified key. + function setValue(address ipId, string calldata key, string calldata val) external; + /// @notice Retrieves the string value associated with a key for an IP asset. + /// @param key The string parameter key to query. + /// @return value The value associated with the specified key. function value(address ipId, string calldata key) external view returns (string memory); } diff --git a/contracts/lib/AccessPermission.sol b/contracts/lib/AccessPermission.sol index ca18d344..caf72832 100644 --- a/contracts/lib/AccessPermission.sol +++ b/contracts/lib/AccessPermission.sol @@ -6,21 +6,21 @@ pragma solidity ^0.8.23; /// @notice Library for IPAccount access control permissions. /// These permissions are used by the AccessController. library AccessPermission { - // ABSTAIN means having not enough information to make decision at current level, - // deferred decision to up level permission. + /// @notice ABSTAIN means having not enough information to make decision at current level, deferred decision to up + /// level permission. uint8 public constant ABSTAIN = 0; - // ALLOW means the permission is granted to transaction signer to call the function. + /// @notice ALLOW means the permission is granted to transaction signer to call the function. uint8 public constant ALLOW = 1; - // DENY means the permission is denied to transaction signer to call the function. + /// @notice DENY means the permission is denied to transaction signer to call the function. uint8 public constant DENY = 2; /// @notice This struct is used to represent permissions in batch operations within the AccessController. - /// @param ipAccount The IPAccount address for which the permission is being set. - /// @param signer The address of the signer of the transaction. - /// @param to The address of the recipient of the transaction. - /// @param func The function selector for the transaction. + /// @param ipAccount The address of the IP account that grants the permission for `signer` + /// @param signer The address that can call `to` on behalf of the `ipAccount` + /// @param to The address that can be called by the `signer` (currently only modules can be `to`) + /// @param func The function selector of `to` that can be called by the `signer` on behalf of the `ipAccount` /// @param permission The permission level for the transaction (0 = ABSTAIN, 1 = ALLOW, 2 = DENY). struct Permission { address ipAccount; diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 1687fa0d..1fccbcec 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -151,7 +151,6 @@ library Errors { error LicensingModule__IncompatibleLicensorCommercialPolicy(); error LicensingModule__IncompatibleLicensorRoyaltyDerivativeRevShare(); error LicensingModule__DerivativeRevShareSumExceedsMaxRNFTSupply(); - error LicensingModule__MismatchBetweenCommercialRevenueShareAndMinRoyalty(); error LicensingModule__MismatchBetweenRoyaltyPolicy(); /// @notice emitted when trying to interact with an IP that has been disputed in the DisputeModule error LicensingModule__DisputedIpId(); diff --git a/contracts/modules/BaseModule.sol b/contracts/modules/BaseModule.sol index 7ba2a679..1780ff5a 100644 --- a/contracts/modules/BaseModule.sol +++ b/contracts/modules/BaseModule.sol @@ -8,10 +8,7 @@ import { IModule } from "../interfaces/modules/base/IModule.sol"; /// @title BaseModule /// @notice Base implementation for all modules in Story Protocol. abstract contract BaseModule is ERC165, IModule { - /// @notice Checks if the contract implements an interface. - /// @dev Overrides ERC165's `supportsInterface` to include support for IModule interface. - /// @param interfaceId The interface identifier, as specified in ERC-165. - /// @return True if the contract implements `interfaceId`, false otherwise. + /// @notice IERC165 interface support. function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId); } diff --git a/contracts/modules/RegistrationModule.sol b/contracts/modules/RegistrationModule.sol index fe9336a6..e230a22f 100644 --- a/contracts/modules/RegistrationModule.sol +++ b/contracts/modules/RegistrationModule.sol @@ -19,29 +19,31 @@ import { IIPAssetRegistry } from "../interfaces/registries/IIPAssetRegistry.sol" /// into the protocol, create a resolver, and bind to it any licenses /// and terms specified by the IP registrant (IP account owner). contract RegistrationModule is BaseModule, IRegistrationModule { - /// @notice The metadata resolver used by the registration module. - IPResolver public resolver; + string public constant override name = REGISTRATION_MODULE_KEY; + + /// @notice Returns the metadata resolver used by the registration module. + IPResolver public ipResolver; + + /// @dev The canonical protocol-wide IP Asset Registry IIPAssetRegistry private _IP_ASSET_REGISTRY; + + /// @dev The canonical protocol-wide Licensing Module ILicensingModule private _LICENSING_MODULE; - /// @notice Initializes the registration module contract. - /// @param assetRegistry The address of the IP asset registry. constructor(address assetRegistry, address licensingModule, address resolverAddr) { - resolver = IPResolver(resolverAddr); + ipResolver = IPResolver(resolverAddr); _LICENSING_MODULE = ILicensingModule(licensingModule); _IP_ASSET_REGISTRY = IIPAssetRegistry(assetRegistry); } - /// @notice Registers a root-level IP into the protocol. Root-level IPs can - /// be thought of as organizational hubs for encapsulating policies - /// that actual IPs can use to register through. As such, a - /// root-level IP is not an actual IP, but a container for IP policy - /// management for their child IP assets. - /// TODO: Rethink the semantics behind "root-level IPs" vs. "normal IPs". - /// TODO: Update function parameters to utilize a struct instead. - /// TODO: Revisit requiring binding an existing NFT to a "root-level IP". - /// If root-level IPs are an organizational primitive, why require NFTs? - /// TODO: Change to a different resolver optimized for root IP metadata. + // TODO: Rethink the semantics behind "root-level IPs" vs. "normal IPs". + // TODO: Update function parameters to utilize a struct instead. + // TODO: Revisit requiring binding an existing NFT to a "root-level IP". + // If root-level IPs are an organizational primitive, why require NFTs? + // TODO: Change to a different resolver optimized for root IP metadata. + /// @notice Registers a root-level IP into the protocol. Root-level IPs can be thought of as organizational hubs + /// for encapsulating policies that actual IPs can use to register through. As such, a root-level IP is not an + /// actual IP, but a container for IP policy management for their child IP assets. /// @param policyId The policy that identifies the licensing terms of the IP. /// @param tokenContract The address of the NFT bound to the root-level IP. /// @param tokenId The token id of the NFT bound to the root-level IP. @@ -80,7 +82,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule { block.chainid, tokenContract, tokenId, - address(resolver), + address(ipResolver), true, metadata ); @@ -97,7 +99,10 @@ contract RegistrationModule is BaseModule, IRegistrationModule { return ipId; } - /// @notice Registers IP derivatives into the protocol. + // TODO: Replace all metadata with a generic bytes parameter type, and do encoding on the periphery contract + // level instead. + /// @notice Registers derivative IPs into the protocol. Derivative IPs are IP assets that inherit policies from + /// parent IPs by burning acquired license NFTs. /// @param licenseIds The licenses to incorporate for the new IP. /// @param tokenContract The address of the NFT bound to the derivative IP. /// @param tokenId The token id of the NFT bound to the derivative IP. @@ -137,7 +142,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule { block.chainid, tokenContract, tokenId, - address(resolver), + address(ipResolver), true, metadata ); @@ -147,9 +152,4 @@ contract RegistrationModule is BaseModule, IRegistrationModule { emit DerivativeIPRegistered(msg.sender, ipId, licenseIds); } - - /// @notice Gets the protocol-wide module identifier for this module. - function name() public pure override returns (string memory) { - return REGISTRATION_MODULE_KEY; - } } diff --git a/contracts/modules/dispute-module/DisputeModule.sol b/contracts/modules/dispute-module/DisputeModule.sol index 948a4fec..50538dc6 100644 --- a/contracts/modules/dispute-module/DisputeModule.sol +++ b/contracts/modules/dispute-module/DisputeModule.sol @@ -14,33 +14,27 @@ import { IArbitrationPolicy } from "../../interfaces/modules/dispute/policies/IA import { Errors } from "../../lib/Errors.sol"; import { ShortStringOps } from "../../utils/ShortStringOps.sol"; -/// @title Story Protocol Dispute Module -/// @notice The Story Protocol dispute module acts as an enforcement layer for -/// that allows to raise disputes and resolve them through arbitration. +/// @title Dispute Module +/// @notice The dispute module acts as an enforcement layer for IP assets that allows raising and resolving disputes +/// through arbitration by judges. contract DisputeModule is IDisputeModule, BaseModule, Governable, ReentrancyGuard, AccessControlled { using EnumerableSet for EnumerableSet.Bytes32Set; - /// @notice tag to represent the dispute is in dispute state waiting for judgement + + string public constant override name = DISPUTE_MODULE_KEY; + + /// @notice Tag to represent the dispute is in dispute state waiting for judgement bytes32 public constant IN_DISPUTE = bytes32("IN_DISPUTE"); + /// @notice Protocol-wide IP asset registry IIPAssetRegistry public IP_ASSET_REGISTRY; - /// @notice Dispute struct - struct Dispute { - address targetIpId; // The ipId that is the target of the dispute - address disputeInitiator; // The address of the dispute initiator - address arbitrationPolicy; // The address of the arbitration policy - bytes32 linkToDisputeEvidence; // The link of the dispute evidence - bytes32 targetTag; // The target tag of the dispute - bytes32 currentTag; // The current tag of the dispute - } - - /// @notice Dispute id - uint256 public disputeId; + /// @notice Dispute ID counter + uint256 public disputeCounter; /// @notice The address of the base arbitration policy address public baseArbitrationPolicy; - /// @notice Contains the dispute information for a given dispute id + /// @notice Returns the dispute information for a given dispute id mapping(uint256 disputeId => Dispute dispute) public disputes; /// @notice Indicates if a dispute tag is whitelisted @@ -56,15 +50,11 @@ contract DisputeModule is IDisputeModule, BaseModule, Governable, ReentrancyGuar /// @notice Arbitration policy for a given ipId mapping(address ipId => address arbitrationPolicy) public arbitrationPolicies; - /// @notice counter of successful disputes per ipId + /// @dev Counter of successful disputes per ipId /// @dev BETA feature, for mainnet tag semantics must influence effect in other modules. For now /// any successful dispute will affect the IP protocol wide mapping(address ipId => uint256) private successfulDisputesPerIp; - /// @notice Initializes the registration module contract - /// @param _controller The access controller used for IP authorization - /// @param _assetRegistry The address of the IP asset registry - /// @param _governance The address of the governance contract constructor( address _controller, address _assetRegistry, @@ -74,178 +64,173 @@ contract DisputeModule is IDisputeModule, BaseModule, Governable, ReentrancyGuar } /// @notice Whitelists a dispute tag - /// @param _tag The dispute tag - /// @param _allowed Indicates if the dispute tag is whitelisted or not - function whitelistDisputeTag(bytes32 _tag, bool _allowed) external onlyProtocolAdmin { - if (_tag == bytes32(0)) revert Errors.DisputeModule__ZeroDisputeTag(); + /// @param tag The dispute tag + /// @param allowed Indicates if the dispute tag is whitelisted or not + function whitelistDisputeTag(bytes32 tag, bool allowed) external onlyProtocolAdmin { + if (tag == bytes32(0)) revert Errors.DisputeModule__ZeroDisputeTag(); - isWhitelistedDisputeTag[_tag] = _allowed; + isWhitelistedDisputeTag[tag] = allowed; - emit TagWhitelistUpdated(_tag, _allowed); + emit TagWhitelistUpdated(tag, allowed); } /// @notice Whitelists an arbitration policy - /// @param _arbitrationPolicy The address of the arbitration policy - /// @param _allowed Indicates if the arbitration policy is whitelisted or not - function whitelistArbitrationPolicy(address _arbitrationPolicy, bool _allowed) external onlyProtocolAdmin { - if (_arbitrationPolicy == address(0)) revert Errors.DisputeModule__ZeroArbitrationPolicy(); + /// @param arbitrationPolicy The address of the arbitration policy + /// @param allowed Indicates if the arbitration policy is whitelisted or not + function whitelistArbitrationPolicy(address arbitrationPolicy, bool allowed) external onlyProtocolAdmin { + if (arbitrationPolicy == address(0)) revert Errors.DisputeModule__ZeroArbitrationPolicy(); - isWhitelistedArbitrationPolicy[_arbitrationPolicy] = _allowed; + isWhitelistedArbitrationPolicy[arbitrationPolicy] = allowed; - emit ArbitrationPolicyWhitelistUpdated(_arbitrationPolicy, _allowed); + emit ArbitrationPolicyWhitelistUpdated(arbitrationPolicy, allowed); } /// @notice Whitelists an arbitration relayer for a given arbitration policy - /// @param _arbitrationPolicy The address of the arbitration policy - /// @param _arbPolicyRelayer The address of the arbitration relayer - /// @param _allowed Indicates if the arbitration relayer is whitelisted or not + /// @param arbitrationPolicy The address of the arbitration policy + /// @param arbPolicyRelayer The address of the arbitration relayer + /// @param allowed Indicates if the arbitration relayer is whitelisted or not function whitelistArbitrationRelayer( - address _arbitrationPolicy, - address _arbPolicyRelayer, - bool _allowed + address arbitrationPolicy, + address arbPolicyRelayer, + bool allowed ) external onlyProtocolAdmin { - if (_arbitrationPolicy == address(0)) revert Errors.DisputeModule__ZeroArbitrationPolicy(); - if (_arbPolicyRelayer == address(0)) revert Errors.DisputeModule__ZeroArbitrationRelayer(); + if (arbitrationPolicy == address(0)) revert Errors.DisputeModule__ZeroArbitrationPolicy(); + if (arbPolicyRelayer == address(0)) revert Errors.DisputeModule__ZeroArbitrationRelayer(); - isWhitelistedArbitrationRelayer[_arbitrationPolicy][_arbPolicyRelayer] = _allowed; + isWhitelistedArbitrationRelayer[arbitrationPolicy][arbPolicyRelayer] = allowed; - emit ArbitrationRelayerWhitelistUpdated(_arbitrationPolicy, _arbPolicyRelayer, _allowed); + emit ArbitrationRelayerWhitelistUpdated(arbitrationPolicy, arbPolicyRelayer, allowed); } /// @notice Sets the base arbitration policy - /// @param _arbitrationPolicy The address of the arbitration policy - function setBaseArbitrationPolicy(address _arbitrationPolicy) external onlyProtocolAdmin { - if (!isWhitelistedArbitrationPolicy[_arbitrationPolicy]) + /// @param arbitrationPolicy The address of the arbitration policy + function setBaseArbitrationPolicy(address arbitrationPolicy) external onlyProtocolAdmin { + if (!isWhitelistedArbitrationPolicy[arbitrationPolicy]) revert Errors.DisputeModule__NotWhitelistedArbitrationPolicy(); - baseArbitrationPolicy = _arbitrationPolicy; + baseArbitrationPolicy = arbitrationPolicy; - emit DefaultArbitrationPolicyUpdated(_arbitrationPolicy); + emit DefaultArbitrationPolicyUpdated(arbitrationPolicy); } /// @notice Sets the arbitration policy for an ipId - /// @param _ipId The ipId - /// @param _arbitrationPolicy The address of the arbitration policy - function setArbitrationPolicy(address _ipId, address _arbitrationPolicy) external verifyPermission(_ipId) { - if (!isWhitelistedArbitrationPolicy[_arbitrationPolicy]) + /// @param ipId The ipId + /// @param arbitrationPolicy The address of the arbitration policy + function setArbitrationPolicy(address ipId, address arbitrationPolicy) external verifyPermission(ipId) { + if (!isWhitelistedArbitrationPolicy[arbitrationPolicy]) revert Errors.DisputeModule__NotWhitelistedArbitrationPolicy(); - arbitrationPolicies[_ipId] = _arbitrationPolicy; + arbitrationPolicies[ipId] = arbitrationPolicy; - emit ArbitrationPolicySet(_ipId, _arbitrationPolicy); + emit ArbitrationPolicySet(ipId, arbitrationPolicy); } - /// @notice Raises a dispute - /// @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 - /// @return disputeId The dispute id + /// @notice Raises a dispute on a given ipId + /// @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 + /// @return disputeId The id of the newly raised dispute function raiseDispute( - address _targetIpId, - string memory _linkToDisputeEvidence, - bytes32 _targetTag, - bytes calldata _data + address targetIpId, + string memory linkToDisputeEvidence, + bytes32 targetTag, + bytes calldata data ) external nonReentrant returns (uint256) { - if (!IP_ASSET_REGISTRY.isRegistered(_targetIpId)) revert Errors.DisputeModule__NotRegisteredIpId(); - if (!isWhitelistedDisputeTag[_targetTag]) revert Errors.DisputeModule__NotWhitelistedDisputeTag(); + if (!IP_ASSET_REGISTRY.isRegistered(targetIpId)) revert Errors.DisputeModule__NotRegisteredIpId(); + if (!isWhitelistedDisputeTag[targetTag]) revert Errors.DisputeModule__NotWhitelistedDisputeTag(); - bytes32 linkToDisputeEvidence = ShortStringOps.stringToBytes32(_linkToDisputeEvidence); - if (linkToDisputeEvidence == bytes32(0)) revert Errors.DisputeModule__ZeroLinkToDisputeEvidence(); + bytes32 linkToDisputeEvidenceBytes = ShortStringOps.stringToBytes32(linkToDisputeEvidence); + if (linkToDisputeEvidenceBytes == bytes32(0)) revert Errors.DisputeModule__ZeroLinkToDisputeEvidence(); - address arbitrationPolicy = arbitrationPolicies[_targetIpId]; + address arbitrationPolicy = arbitrationPolicies[targetIpId]; if (!isWhitelistedArbitrationPolicy[arbitrationPolicy]) arbitrationPolicy = baseArbitrationPolicy; - uint256 disputeId_ = ++disputeId; + uint256 disputeId_ = ++disputeCounter; disputes[disputeId_] = Dispute({ - targetIpId: _targetIpId, + targetIpId: targetIpId, disputeInitiator: msg.sender, arbitrationPolicy: arbitrationPolicy, - linkToDisputeEvidence: linkToDisputeEvidence, - targetTag: _targetTag, + linkToDisputeEvidence: linkToDisputeEvidenceBytes, + targetTag: targetTag, currentTag: IN_DISPUTE }); - IArbitrationPolicy(arbitrationPolicy).onRaiseDispute(msg.sender, _data); + IArbitrationPolicy(arbitrationPolicy).onRaiseDispute(msg.sender, data); emit DisputeRaised( disputeId_, - _targetIpId, + targetIpId, msg.sender, arbitrationPolicy, - linkToDisputeEvidence, - _targetTag, - _data + linkToDisputeEvidenceBytes, + targetTag, + data ); return disputeId_; } - /// @notice Sets the dispute judgement - /// @param _disputeId The dispute id - /// @param _decision The decision of the dispute - /// @param _data The data to set the dispute judgement - function setDisputeJudgement(uint256 _disputeId, bool _decision, bytes calldata _data) external nonReentrant { - Dispute memory dispute = disputes[_disputeId]; + /// @notice Sets the dispute judgement on a given dispute. Only whitelisted arbitration relayers can call to judge. + /// @param disputeId The dispute id + /// @param decision The decision of the dispute + /// @param data The data to set the dispute judgement + function setDisputeJudgement(uint256 disputeId, bool decision, bytes calldata data) external nonReentrant { + Dispute memory dispute = disputes[disputeId]; if (dispute.currentTag != IN_DISPUTE) revert Errors.DisputeModule__NotInDisputeState(); if (!isWhitelistedArbitrationRelayer[dispute.arbitrationPolicy][msg.sender]) { revert Errors.DisputeModule__NotWhitelistedArbitrationRelayer(); } - if (_decision) { - disputes[_disputeId].currentTag = dispute.targetTag; + if (decision) { + disputes[disputeId].currentTag = dispute.targetTag; successfulDisputesPerIp[dispute.targetIpId]++; } else { - disputes[_disputeId].currentTag = bytes32(0); + disputes[disputeId].currentTag = bytes32(0); } - IArbitrationPolicy(dispute.arbitrationPolicy).onDisputeJudgement(_disputeId, _decision, _data); + IArbitrationPolicy(dispute.arbitrationPolicy).onDisputeJudgement(disputeId, decision, data); - emit DisputeJudgementSet(_disputeId, _decision, _data); + emit DisputeJudgementSet(disputeId, decision, data); } /// @notice Cancels an ongoing dispute - /// @param _disputeId The dispute id - /// @param _data The data to cancel the dispute - function cancelDispute(uint256 _disputeId, bytes calldata _data) external nonReentrant { - Dispute memory dispute = disputes[_disputeId]; + /// @param disputeId The dispute id + /// @param data The data to cancel the dispute + function cancelDispute(uint256 disputeId, bytes calldata data) external nonReentrant { + Dispute memory dispute = disputes[disputeId]; if (dispute.currentTag != IN_DISPUTE) revert Errors.DisputeModule__NotInDisputeState(); if (msg.sender != dispute.disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator(); - IArbitrationPolicy(dispute.arbitrationPolicy).onDisputeCancel(msg.sender, _disputeId, _data); + IArbitrationPolicy(dispute.arbitrationPolicy).onDisputeCancel(msg.sender, disputeId, data); - disputes[_disputeId].currentTag = bytes32(0); + disputes[disputeId].currentTag = bytes32(0); - emit DisputeCancelled(_disputeId, _data); + emit DisputeCancelled(disputeId, data); } /// @notice Resolves a dispute after it has been judged - /// @param _disputeId The dispute id - function resolveDispute(uint256 _disputeId) external { - Dispute memory dispute = disputes[_disputeId]; + /// @param disputeId The dispute id + function resolveDispute(uint256 disputeId) external { + Dispute memory dispute = disputes[disputeId]; if (msg.sender != dispute.disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator(); if (dispute.currentTag == IN_DISPUTE || dispute.currentTag == bytes32(0)) revert Errors.DisputeModule__NotAbleToResolve(); successfulDisputesPerIp[dispute.targetIpId]--; - disputes[_disputeId].currentTag = bytes32(0); - - emit DisputeResolved(_disputeId); - } + disputes[disputeId].currentTag = bytes32(0); - /// @notice returns true if the ipId is tagged with any tag (meaning at least one dispute went through) - /// @param _ipId The ipId - function isIpTagged(address _ipId) external view returns (bool) { - return successfulDisputesPerIp[_ipId] > 0; + emit DisputeResolved(disputeId); } - /// @notice Gets the protocol-wide module identifier for this module - /// @return The dispute module key - function name() public pure override returns (string memory) { - return DISPUTE_MODULE_KEY; + /// @notice Returns true if the ipId is tagged with any tag (meaning at least one dispute went through) + /// @param ipId The ipId + /// @return isTagged True if the ipId is tagged + function isIpTagged(address ipId) external view returns (bool) { + return successfulDisputesPerIp[ipId] > 0; } } diff --git a/contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol b/contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol index afbfe0e9..4b65bce6 100644 --- a/contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol +++ b/contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol @@ -10,33 +10,24 @@ import { IArbitrationPolicy } from "../../../interfaces/modules/dispute/policies import { Errors } from "../../../lib/Errors.sol"; /// @title Story Protocol Arbitration Policy -/// @notice The Story Protocol arbitration policy is a simple policy that -/// requires the dispute initiator to pay a fixed amount of tokens -/// to raise a dispute and refunds that amount if the dispute initiator -/// wins the dispute. +/// @notice The Story Protocol arbitration policy is a simple policy that requires the dispute initiator to pay a fixed +/// amount of tokens to raise a dispute and refunds that amount if the dispute initiator wins the dispute. contract ArbitrationPolicySP is IArbitrationPolicy, Governable { using SafeERC20 for IERC20; - /// @notice Dispute module address + /// @notice Returns the dispute module address address public immutable DISPUTE_MODULE; - - /// @notice Payment token address + /// @notice Returns the payment token address address public immutable PAYMENT_TOKEN; - - /// @notice Arbitration price + /// @notice Returns the arbitration price uint256 public immutable ARBITRATION_PRICE; - /// @notice Restricts the calls to the dispute module + /// @notice Restricts the calls to the DisputeModule modifier onlyDisputeModule() { if (msg.sender != DISPUTE_MODULE) revert Errors.ArbitrationPolicySP__NotDisputeModule(); _; } - /// @notice Constructor - /// @param _disputeModule Address of the dispute module contract - /// @param _paymentToken Address of the payment token - /// @param _arbitrationPrice Arbitration price - /// @param _governable Address of the governable contract constructor( address _disputeModule, address _paymentToken, @@ -51,27 +42,36 @@ contract ArbitrationPolicySP is IArbitrationPolicy, Governable { ARBITRATION_PRICE = _arbitrationPrice; } - /// @notice Executes custom logic on raise dispute - /// @param _caller Address of the caller - function onRaiseDispute(address _caller, bytes calldata) external onlyDisputeModule { + /// @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 { // requires that the caller has given approve() to this contract - IERC20(PAYMENT_TOKEN).safeTransferFrom(_caller, address(this), ARBITRATION_PRICE); + IERC20(PAYMENT_TOKEN).safeTransferFrom(caller, address(this), ARBITRATION_PRICE); } - /// @notice Executes custom logic on dispute judgement - /// @param _disputeId The dispute id - /// @param _decision The decision of the dispute - function onDisputeJudgement(uint256 _disputeId, bool _decision, bytes calldata) external onlyDisputeModule { - if (_decision) { - (, address disputeInitiator, , , , ) = IDisputeModule(DISPUTE_MODULE).disputes(_disputeId); + /// @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 + function onDisputeJudgement(uint256 disputeId, bool decision, bytes calldata data) external onlyDisputeModule { + if (decision) { + (, address disputeInitiator, , , , ) = IDisputeModule(DISPUTE_MODULE).disputes(disputeId); IERC20(PAYMENT_TOKEN).safeTransfer(disputeInitiator, ARBITRATION_PRICE); } } - /// @notice Executes custom logic on dispute cancel - function onDisputeCancel(address, uint256, bytes calldata) external onlyDisputeModule {} + /// @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 Allows governance address to withdraw + /// @dev Enforced to be only callable by the governance protocol admin. function governanceWithdraw() external onlyProtocolAdmin { uint256 balance = IERC20(PAYMENT_TOKEN).balanceOf(address(this)); IERC20(PAYMENT_TOKEN).safeTransfer(governance, balance); diff --git a/contracts/modules/licensing/BasePolicyFrameworkManager.sol b/contracts/modules/licensing/BasePolicyFrameworkManager.sol index 8d9afe64..e290bae1 100644 --- a/contracts/modules/licensing/BasePolicyFrameworkManager.sol +++ b/contracts/modules/licensing/BasePolicyFrameworkManager.sol @@ -12,23 +12,19 @@ import { LicensingModuleAware } from "../../modules/licensing/LicensingModuleAwa /// @title BasePolicyFrameworkManager /// @notice Base contract for policy framework managers. abstract contract BasePolicyFrameworkManager is IPolicyFrameworkManager, ERC165, LicensingModuleAware { + /// @notice Returns the name to be show in license NFT (LNFT) metadata string public override name; + + /// @notice Returns the URL to the off chain legal agreement template text string public override licenseTextUrl; - /// @notice Initializes the base contract. - /// @param licensing The address of the license LicensingModule. constructor(address licensing, string memory name_, string memory licenseTextUrl_) LicensingModuleAware(licensing) { name = name_; licenseTextUrl = licenseTextUrl_; } - /// @notice ERC165 interface identifier for the policy framework manager. + /// @notice IERC165 interface support. function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { return interfaceId == type(IPolicyFrameworkManager).interfaceId || super.supportsInterface(interfaceId); } - - /// @notice returns the address of the license registry - function licensingModule() external view virtual override returns (address) { - return address(LICENSING_MODULE); - } } diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index 2c2c6542..7b2f722e 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -9,10 +9,11 @@ import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.s import { IIPAccount } from "../../interfaces/IIPAccount.sol"; import { IPolicyFrameworkManager } from "../../interfaces/modules/licensing/IPolicyFrameworkManager.sol"; -import { ILicenseRegistry } from "../../interfaces/registries/ILicenseRegistry.sol"; +import { IModule } from "../../interfaces/modules/base/IModule.sol"; import { ILicensingModule } from "../../interfaces/modules/licensing/ILicensingModule.sol"; import { IIPAccountRegistry } from "../../interfaces/registries/IIPAccountRegistry.sol"; import { IDisputeModule } from "../../interfaces/modules/dispute/IDisputeModule.sol"; +import { ILicenseRegistry } from "../../interfaces/registries/ILicenseRegistry.sol"; import { Errors } from "../../lib/Errors.sol"; import { DataUniqueness } from "../../lib/DataUniqueness.sol"; import { Licensing } from "../../lib/Licensing.sol"; @@ -23,6 +24,14 @@ import { LICENSING_MODULE_KEY } from "../../lib/modules/Module.sol"; import { BaseModule } from "../BaseModule.sol"; // TODO: consider disabling operators/approvals on creation +/// @title Licensing Module +/// @notice Licensing module is the main entry point for the licensing system. It is responsible for: +/// - Registering policy frameworks +/// - Registering policies +/// - Minting licenses +/// - Linking IP to its parent +/// - Verifying linking parameters +/// - Verifying policy parameters contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, ReentrancyGuard { using ERC165Checker for address; using IPAccountChecker for IIPAccountRegistry; @@ -31,30 +40,44 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen using Licensing for *; using Strings for *; - struct PolicySetup { - uint256 index; - bool isSet; - bool active; - bool isInherited; - } + /// @inheritdoc IModule + string public constant override name = LICENSING_MODULE_KEY; + /// @notice Returns the canonical protocol-wide RoyaltyModule RoyaltyModule public immutable ROYALTY_MODULE; + + /// @notice Returns the canonical protocol-wide LicenseRegistry ILicenseRegistry public immutable LICENSE_REGISTRY; + + /// @notice Returns the dispute module IDisputeModule public immutable DISPUTE_MODULE; - string public constant override name = LICENSING_MODULE_KEY; + /// @dev Returns if a framework is registered or not mapping(address framework => bool registered) private _registeredFrameworkManagers; + + /// @dev Returns the policy id for the given policy data (hashed) mapping(bytes32 policyHash => uint256 policyId) private _hashedPolicies; + + /// @dev Returns the policy data for the given policy id mapping(uint256 policyId => Licensing.Policy policyData) private _policies; + + /// @dev Total amount of distinct licensing policies in LicenseRegistry uint256 private _totalPolicies; - /// @notice internal mapping to track if a policy was set by linking or minting, and the - /// index of the policy in the ipId policy set - /// Policies can't be removed, but they can be deactivated by setting active to false + + /// @dev Internal mapping to track if a policy was set by linking or minting, and the index of the policy in the + /// ipId policy set. Policies can't be removed, but they can be deactivated by setting active to false. mapping(address ipId => mapping(uint256 policyId => PolicySetup setup)) private _policySetups; + + /// @dev Returns the set of policy ids attached to the given ipId mapping(bytes32 hashIpIdAnInherited => EnumerableSet.UintSet policyIds) private _policiesPerIpId; + + /// @dev Returns the set of parent policy ids for the given ipId mapping(address ipId => EnumerableSet.AddressSet parentIpIds) private _ipIdParents; + + /// @dev Returns the policy aggregator data for the given ipId in a framework mapping(address framework => mapping(address ipId => bytes policyAggregatorData)) private _ipRights; + /// @notice Modifier to allow only LicenseRegistry as the caller modifier onlyLicenseRegistry() { if (msg.sender == address(LICENSE_REGISTRY)) revert Errors.LicensingModule__CallerNotLicenseRegistry(); _; @@ -72,12 +95,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen DISPUTE_MODULE = IDisputeModule(disputeModule); } - function licenseRegistry() external view returns (address) { - return address(LICENSE_REGISTRY); - } - - /// @notice registers a policy framework manager into the contract, so it can add policy data for - /// licenses. + /// @notice Registers a policy framework manager into the contract, so it can add policy data for licenses. /// @param manager the address of the manager. Will be ERC165 checked for IPolicyFrameworkManager function registerPolicyFrameworkManager(address manager) external { if (!ERC165Checker.supportsInterface(manager, type(IPolicyFrameworkManager).interfaceId)) { @@ -93,9 +111,8 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen emit PolicyFrameworkRegistered(manager, fwManager.name(), licenseUrl); } - /// @notice Registers a policy into the contract. MUST be called by a registered - /// framework or it will revert. The policy data and its integrity must be - /// verified by the policy framework manager. + /// @notice Registers a policy into the contract. MUST be called by a registered framework or it will revert. + /// The policy data and its integrity must be verified by the policy framework manager. /// @param isLicenseTransferable True if the license is transferable /// @param royaltyPolicy The address of the royalty policy /// @param royaltyData The royalty policy specific encoded data @@ -129,13 +146,10 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen return polId; } - /// Adds a policy to an ipId, which can be used to mint licenses. - /// Licnses are permissions for ipIds to be derivatives (children). - /// if policyId is not defined in LicenseRegistry, reverts. - /// Will revert if ipId already has the same policy - /// @param ipId to receive the policy - /// @param polId id of the policy data - /// @return indexOnIpId position of policy within the ipIds policy set + /// @notice Adds a policy to the set of policies of an IP + /// @param ipId The id of the IP + /// @param polId The id of the policy + /// @return indexOnIpId The index of the policy in the IP's policy list function addPolicyToIp( address ipId, uint256 polId @@ -148,19 +162,18 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen return _addPolicyIdToIp({ ipId: ipId, policyId: polId, isInherited: false, skipIfDuplicate: false }); } - /// Mints license NFTs representing a policy granted by a set of ipIds (licensors). This NFT needs to be burned - /// in order to link a derivative IP with its parents. - /// If this is the first combination of policy and licensors, a new licenseId - /// will be created (by incrementing prev totalLicenses). - /// If not, the license is fungible and an id will be reused. - /// The licensing terms that regulate creating new licenses will be verified to allow minting. - /// Reverts if caller is not authorized by licensors. - /// @param policyId id of the policy to be minted - /// @param licensorIpId IP Id granting the license - /// @param amount of licenses to be minted. License NFT is fungible for same policy and same licensors - /// @param receiver of the License NFT(s). + /// @notice Mints a license to create derivative IP. License NFTs represent a policy granted by IPs (licensors). + /// Reverts if caller is not authorized by any of the licensors. + /// @dev This NFT needs to be burned in order to link a derivative IP with its parents. If this is the first + /// combination of policy and licensors, a new licenseId will be created (by incrementing prev totalLicenses). + /// If not, the license is fungible and an id will be reused. The licensing terms that regulate creating new + /// licenses will be verified to allow minting. + /// @param policyId The id of the policy with the licensing parameters + /// @param licensorIpId The id of the licensor IP + /// @param amount The amount of licenses to mint + /// @param receiver The address that will receive the license /// @param royaltyContext The context for the royalty module to process - /// @return licenseId of the NFT(s). + /// @return licenseId The ID of the license NFT(s) // solhint-disable-next-line code-complexity function mintLicense( uint256 policyId, @@ -203,8 +216,8 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen licenseId = LICENSE_REGISTRY.mintLicense(policyId, licensorIpId, pol.isLicenseTransferable, amount, receiver); } - /// @notice Links an IP to the licensors (parent IP IDs) listed in the License NFTs, if their policies allow it, - /// burning the NFTs in the proccess. The caller must be the owner of the NFTs and the IP owner. + /// @notice Links an IP to the licensors listed in the license NFTs, if their policies allow it. Burns the license + /// NFTs in the proccess. The caller must be the owner of the IP asset and license NFTs. /// @param licenseIds The id of the licenses to burn /// @param childIpId The id of the child IP to be linked /// @param royaltyContext The context for the royalty module to process @@ -272,51 +285,80 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen return (licenseData.licensorIpId, pol.royaltyPolicy, pol.royaltyData); } - /// @notice True if the framework address is registered in LicenseRegistry + /// @notice Returns if the framework address is registered in the LicensingModule. + /// @param policyFramework The address of the policy framework manager + /// @return isRegistered True if the framework is registered function isFrameworkRegistered(address policyFramework) external view returns (bool) { return _registeredFrameworkManagers[policyFramework]; } - /// Returns amount of distinct licensing policies in LicenseRegistry + /// @notice Returns amount of distinct licensing policies in the LicensingModule. + /// @return totalPolicies The amount of policies function totalPolicies() external view returns (uint256) { return _totalPolicies; } - /// Gets policy data for policyId, reverts if not found + /// @notice Returns the policy data for policyId, reverts if not found. + /// @param policyId The id of the policy + /// @return pol The policy data function policy(uint256 policyId) public view returns (Licensing.Policy memory pol) { pol = _policies[policyId]; _verifyPolicy(pol); return pol; } - /// @notice gets the policy id for the given data, or 0 if not found + /// @notice Returns the policy id for the given policy data, or 0 if not found. + /// @param pol The policy data in Policy struct + /// @return policyId The id of the policy function getPolicyId(Licensing.Policy calldata pol) external view returns (uint256 policyId) { return _hashedPolicies[keccak256(abi.encode(pol))]; } + /// @notice Returns the policy aggregator data for the given IP ID in the framework. + /// @param framework The address of the policy framework manager + /// @param ipId The id of the IP asset + /// @return data The encoded policy aggregator data to be decoded by the framework manager function policyAggregatorData(address framework, address ipId) external view returns (bytes memory) { return _ipRights[framework][ipId]; } - /// Returns true if policyId is defined in LicenseRegistry, false otherwise. + /// @notice Returns if policyId exists in the LicensingModule + /// @param policyId The id of the policy + /// @return isDefined True if the policy is defined function isPolicyDefined(uint256 policyId) public view returns (bool) { return _policies[policyId].policyFramework != address(0); } - /// Gets the policy set for an IpId - /// @dev potentially expensive operation, use with care + /// @notice Returns the policy ids attached to an IP + /// @dev Potentially gas-intensive operation, use with care. + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset + /// @return policyIds The ids of policy ids for the IP function policyIdsForIp(bool isInherited, address ipId) external view returns (uint256[] memory policyIds) { return _policySetPerIpId(isInherited, ipId).values(); } + /// @notice Returns the total number of policies attached to an IP + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset + /// @return totalPolicies The total number of policies for the IP function totalPoliciesForIp(bool isInherited, address ipId) public view returns (uint256) { return _policySetPerIpId(isInherited, ipId).length(); } + /// @notice True if the given policy attached to the given IP is inherited from a parent IP. + /// @param ipId The id of the IP asset that has the policy attached + /// @param policyId The id of the policy to check if inherited + /// @return isInherited True if the policy is inherited from a parent IP function isPolicyIdSetForIp(bool isInherited, address ipId, uint256 policyId) external view returns (bool) { return _policySetPerIpId(isInherited, ipId).contains(policyId); } + /// @notice Returns the policy ID for an IP by local index on the IP's policy set + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset to check + /// @param index The local index of a policy in the IP's policy set + /// @return policyId The id of the policy function policyIdForIpAtIndex( bool isInherited, address ipId, @@ -325,6 +367,11 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen return _policySetPerIpId(isInherited, ipId).at(index); } + /// @notice Returns the policy data for an IP by the policy's local index on the IP's policy set + /// @param isInherited True if the policy is inherited from a parent IP + /// @param ipId The id of the IP asset to check + /// @param index The local index of a policy in the IP's policy set + /// @return policy The policy data function policyForIpAtIndex( bool isInherited, address ipId, @@ -333,6 +380,12 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen return _policies[_policySetPerIpId(isInherited, ipId).at(index)]; } + /// @notice Returns the status of a policy in an IP's policy set + /// @param ipId The id of the IP asset to check + /// @param policyId The id of the policy + /// @return index The local index of the policy in the IP's policy set + /// @return isInherited True if the policy is inherited from a parent IP + /// @return active True if the policy is active function policyStatus( address ipId, uint256 policyId @@ -341,36 +394,44 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen return (setup.index, setup.isInherited, setup.active); } + /// @notice Returns if the given policy attached to the given IP is inherited from a parent IP. + /// @param ipId The id of the IP asset that has the policy attached + /// @param policyId The id of the policy to check if inherited + /// @return isInherited True if the policy is inherited from a parent IP function isPolicyInherited(address ipId, uint256 policyId) external view returns (bool) { return _policySetups[ipId][policyId].isInherited; } - /// Returns true if the child is derivative from the parent, by at least 1 policy. + /// @notice Returns if an IP is a derivative of another IP + /// @param parentIpId The id of the parent IP asset to check + /// @param childIpId The id of the child IP asset to check + /// @return isParent True if the child IP is a derivative of the parent IP function isParent(address parentIpId, address childIpId) external view returns (bool) { return _ipIdParents[childIpId].contains(parentIpId); } + /// @notice Returns the list of parent IP assets for a given child IP asset + /// @param ipId The id of the child IP asset to check + /// @return parentIpIds The ids of the parent IP assets function parentIpIds(address ipId) external view returns (address[] memory) { return _ipIdParents[ipId].values(); } + /// @notice Returns the total number of parents for an IP asset + /// @param ipId The id of the IP asset to check + /// @return totalParents The total number of parent IP assets function totalParentsForIpId(address ipId) external view returns (uint256) { return _ipIdParents[ipId].length(); } + /// @dev Verifies that the framework is registered in the LicensingModule function _verifyRegisteredFramework(address policyFramework) private view { if (!_registeredFrameworkManagers[policyFramework]) { revert Errors.LicensingModule__FrameworkNotFound(); } } - /// Adds a policy id to the ipId policy set - /// Will revert if policy set already has policyId - /// @param ipId the IP identifier - /// @param policyId id of the policy data - /// @param isInherited true if set in linkIpToParent, false otherwise - /// @param skipIfDuplicate if true, will skip if policyId is already set - /// @return index of the policy added to the set + /// @dev Adds a policy id to the ipId policy set. Reverts if policy set already has policyId function _addPolicyIdToIp( address ipId, uint256 policyId, @@ -400,6 +461,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen return index; } + /// @dev Link IP to a parent IP using the license NFT. function _linkIpToParent( uint256 iteration, uint256 licenseId, @@ -433,6 +495,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen _ipIdParents[childIpId].add(licensor); } + /// @dev Verifies if the policyId can be added to the IP function _verifyCanAddPolicy(uint256 policyId, address ipId, bool isInherited) private { bool ipIdIsDerivative = _policySetPerIpId(true, ipId).length() > 0; if ( @@ -460,16 +523,19 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen } } + /// @dev Verifies if the policy is set function _verifyPolicy(Licensing.Policy memory pol) private pure { if (pol.policyFramework == address(0)) { revert Errors.LicensingModule__PolicyNotFound(); } } + /// @dev Returns the policy set for the given ipId function _policySetPerIpId(bool isInherited, address ipId) private view returns (EnumerableSet.UintSet storage) { return _policiesPerIpId[keccak256(abi.encode(isInherited, ipId))]; } + /// @dev Verifies if the IP is disputed function _verifyIpNotDisputed(address ipId) private view { // TODO: in beta, any tag means revocation, for mainnet we need more context if (DISPUTE_MODULE.isIpTagged(ipId)) { diff --git a/contracts/modules/licensing/LicensingModuleAware.sol b/contracts/modules/licensing/LicensingModuleAware.sol index 783fc37d..500e1b58 100644 --- a/contracts/modules/licensing/LicensingModuleAware.sol +++ b/contracts/modules/licensing/LicensingModuleAware.sol @@ -7,18 +7,16 @@ import { ILicensingModule } from "../../interfaces/modules/licensing/ILicensingM import { Errors } from "../../lib/Errors.sol"; /// @title LicensingModuleAware -/// @notice Base contract to be inherited by modules that need to access the license registry. +/// @notice Base contract to be inherited by modules that need to access the licensing module. abstract contract LicensingModuleAware { - /// @notice Gets the protocol-wide license registry. + /// @notice Returns the protocol-wide licensing module. ILicensingModule public immutable LICENSING_MODULE; - /// @notice Initializes the base module contract. - /// @param licensingModule The address of the license registry. constructor(address licensingModule) { LICENSING_MODULE = ILicensingModule(licensingModule); } - /// @notice Modifier for authorizing the calling entity. + /// @notice Modifier for authorizing the calling entity to only the LicensingModule. modifier onlyLicensingModule() { if (msg.sender != address(LICENSING_MODULE)) { revert Errors.LicensingModuleAware__CallerNotLicensingModule(); diff --git a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol index 6381f4a3..71d7a062 100644 --- a/contracts/modules/licensing/UMLPolicyFrameworkManager.sol +++ b/contracts/modules/licensing/UMLPolicyFrameworkManager.sol @@ -19,9 +19,8 @@ import { BasePolicyFrameworkManager } from "../../modules/licensing/BasePolicyFr import { LicensorApprovalChecker } from "../../modules/licensing/parameter-helpers/LicensorApprovalChecker.sol"; /// @title UMLPolicyFrameworkManager -/// @notice This is the UML Policy Framework Manager, which implements the UML Policy Framework -/// logic for encoding and decoding UML policies into the LicenseRegistry and verifying -/// the licensing parameters for linking, minting, and transferring. +/// @notice UML Policy Framework Manager implements the UML Policy Framework logic for encoding and decoding UML +/// policies into the LicenseRegistry and verifying the licensing parameters for linking, minting, and transferring. contract UMLPolicyFrameworkManager is IUMLPolicyFrameworkManager, BasePolicyFrameworkManager, @@ -31,6 +30,7 @@ contract UMLPolicyFrameworkManager is using ERC165Checker for address; using Strings for *; + /// @dev Hash of an empty string array bytes32 private constant _EMPTY_STRING_ARRAY_HASH = 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; @@ -42,14 +42,18 @@ contract UMLPolicyFrameworkManager is string memory licenseUrl_ ) BasePolicyFrameworkManager(licensing, name_, licenseUrl_) - LicensorApprovalChecker(accessController, ipAccountRegistry, ILicensingModule(licensing).licenseRegistry()) + LicensorApprovalChecker( + accessController, + ipAccountRegistry, + address(ILicensingModule(licensing).LICENSE_REGISTRY()) + ) {} - /// @notice Re a new policy to the registry - /// @dev Must encode the policy into bytes to be stored in the LicensingModule - /// @param params the parameters for the policy - /// @return policyId the ID of the policy - function registerPolicy(RegisterUMLPolicyParams calldata params) external returns (uint256 policyId) { + /// @notice Registers a new policy to the registry + /// @dev Internally, this function must generate a Licensing.Policy struct and call registerPolicy. + /// @param params parameters needed to register a UMLPolicy + /// @return policyId The ID of the newly registered policy + function registerPolicy(RegisterUMLPolicyParams calldata params) external nonReentrant returns (uint256 policyId) { _verifyComercialUse(params.policy, params.royaltyPolicy); _verifyDerivatives(params.policy); /// TODO: DO NOT deploy on production networks without hashing string[] values instead of storing them @@ -64,17 +68,19 @@ contract UMLPolicyFrameworkManager is ); } - /// Called by licenseRegistry to verify policy parameters for linking a child IP to a parent IP (licensor) - /// by burning a license. + /// @notice Verify policy parameters for linking a child IP to a parent IP (licensor) by burning a license NFT. + /// @dev Enforced to be only callable by LicenseRegistry /// @param licenseId the license id to burn /// @param caller the address executing the link /// @param ipId the IP id of the IP being linked + /// @param parentIpId the IP id of the parent IP /// @param policyData the encoded framework policy data to verify + /// @return verified True if the link is verified function verifyLink( uint256 licenseId, address caller, address ipId, - address, // parentIpId + address parentIpId, bytes calldata policyData ) external override nonReentrant onlyLicensingModule returns (bool) { UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy)); @@ -99,16 +105,21 @@ contract UMLPolicyFrameworkManager is return true; } - /// Called by licenseRegistry to verify policy parameters for minting a license + /// @notice Verify policy parameters for minting a license. + /// @dev Enforced to be only callable by LicenseRegistry /// @param caller the address executing the mint /// @param policyWasInherited true if the policy was inherited (licensorIpId is not original IP owner) + /// @param licensorIpId the IP id of the licensor + /// @param receiver the address receiving the license + /// @param mintAmount the amount of licenses to mint /// @param policyData the encoded framework policy data to verify + /// @return verified True if the link is verified function verifyMint( address caller, bool policyWasInherited, - address, - address, - uint256, + address licensorIpId, + address receiver, + uint256 mintAmount, bytes memory policyData ) external nonReentrant onlyLicensingModule returns (bool) { UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy)); @@ -133,7 +144,9 @@ contract UMLPolicyFrameworkManager is return true; } - /// @notice gets the aggregation data for inherited policies, decoded for the framework + /// @notice Returns the aggregation data for inherited policies of an IP asset. + /// @param ipId The ID of the IP asset to get the aggregator for + /// @return rights The UMLAggregator struct function getAggregator(address ipId) external view returns (UMLAggregator memory rights) { bytes memory policyAggregatorData = LICENSING_MODULE.policyAggregatorData(address(this), ipId); if (policyAggregatorData.length == 0) { @@ -142,13 +155,17 @@ contract UMLPolicyFrameworkManager is rights = abi.decode(policyAggregatorData, (UMLAggregator)); } + /// @notice gets the UMLPolicy for a given policy ID decoded from Licensing.Policy.frameworkData + /// @dev Do not call this function from a smart contract, it is only for off-chain + /// @param policyId The ID of the policy to get + /// @return policy The UMLPolicy struct function getUMLPolicy(uint256 policyId) external view returns (UMLPolicy memory policy) { Licensing.Policy memory pol = LICENSING_MODULE.policy(policyId); return abi.decode(pol.frameworkData, (UMLPolicy)); } - /// Called by licenseRegistry to verify compatibility when inheriting from a parent IP - /// The objective is to verify compatibility of multiple policies. + /// @notice Verify compatibility of one or more policies when inheriting them from one or more parent IPs. + /// @dev Enforced to be only callable by LicenseRegistry /// @dev The assumption in this method is that we can add parents later on, hence the need /// for an aggregator, if not we will do this when linking to parents directly with an /// array of policies. @@ -230,6 +247,10 @@ contract UMLPolicyFrameworkManager is return (changedAgg, abi.encode(agg)); } + /// @notice Returns the stringified JSON policy data for the LicenseRegistry.uri(uint256) method. + /// @dev Must return ERC1155 OpenSea standard compliant metadata. + /// @param policyData The encoded licensing policy data to be decoded by the PFM + /// @return jsonString The OpenSea-compliant metadata URI of the policy function policyToJson(bytes memory policyData) public pure returns (string memory) { UMLPolicy memory policy = abi.decode(policyData, (UMLPolicy)); @@ -277,7 +298,8 @@ contract UMLPolicyFrameworkManager is return json; } - /// @notice Encodes the commercial traits of UML policy into a JSON string for OpenSea + /// @dev Encodes the commercial traits of UML policy into a JSON string for OpenSea + /// @param policy The policy to encode function _policyCommercialTraitsToJson(UMLPolicy memory policy) internal pure returns (string memory) { /* solhint-disable */ // NOTE: TOTAL_RNFT_SUPPLY = 1000 in trait with max_value. For numbers, don't add any display_type, so that @@ -303,7 +325,8 @@ contract UMLPolicyFrameworkManager is /* solhint-enable */ } - /// @notice Encodes the derivative traits of UML policy into a JSON string for OpenSea + /// @dev Encodes the derivative traits of UML policy into a JSON string for OpenSea + /// @param policy The policy to encode function _policyDerivativeTraitsToJson(UMLPolicy memory policy) internal pure returns (string memory) { /* solhint-disable */ // NOTE: TOTAL_RNFT_SUPPLY = 1000 in trait with max_value. For numbers, don't add any display_type, so that @@ -328,8 +351,9 @@ contract UMLPolicyFrameworkManager is /* solhint-enable */ } - /// Checks the configuration of commercial use and throws if the policy is not compliant + /// @dev Checks the configuration of commercial use and throws if the policy is not compliant /// @param policy The policy to verify + /// @param royaltyPolicy The address of the royalty policy // solhint-disable-next-line code-complexity function _verifyComercialUse(UMLPolicy calldata policy, address royaltyPolicy) internal view { if (!policy.commercialUse) { @@ -361,7 +385,7 @@ contract UMLPolicyFrameworkManager is } } - /// Checks the configuration of derivative parameters and throws if the policy is not compliant + /// @notice Checks the configuration of derivative parameters and throws if the policy is not compliant /// @param policy The policy to verify function _verifyDerivatives(UMLPolicy calldata policy) internal pure { if (!policy.derivativesAllowed) { @@ -377,7 +401,7 @@ contract UMLPolicyFrameworkManager is } } - /// Verifies compatibility for params where the valid options are either permissive value, or equal params + /// @dev Verifies compatibility for params where the valid options are either permissive value, or equal params /// @param oldHash hash of the old param /// @param newHash hash of the new param /// @param permissive hash of the most permissive param diff --git a/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol b/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol index 11da528d..1bdf5dd3 100644 --- a/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol +++ b/contracts/modules/licensing/parameter-helpers/LicensorApprovalChecker.sol @@ -8,14 +8,14 @@ import { ILicenseRegistry } from "../../../interfaces/registries/ILicenseRegistr /// @notice Manages the approval of derivative IP accounts by the licensor. Used to verify /// licensing terms like "Derivatives With Approval" in UML. abstract contract LicensorApprovalChecker is AccessControlled { - /// Emits when a derivative IP account is approved by the licensor. - /// @param licenseId id of the license waiting for approval - /// @param ipId id of the derivative IP to be approved - /// @param caller executor of the approval - /// @param approved result of the approval + /// @notice Emits when a derivative IP account is approved by the licensor. + /// @param licenseId 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 licenseId, address indexed ipId, address indexed caller, bool approved); - /// @notice License registry + /// @notice Returns the license registry address ILicenseRegistry public immutable LICENSE_REGISTRY; /// @notice Approvals for derivative IP. @@ -31,21 +31,29 @@ abstract contract LicensorApprovalChecker is AccessControlled { } /// @notice Approves or disapproves a derivative IP account. - /// @param licenseId id of the license waiting for approval - /// @param childIpId id of the derivative IP to be approved - /// @param approved result of the approval + /// @param licenseId 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 licenseId, address childIpId, bool approved) external { address licensorIpId = LICENSE_REGISTRY.licensorIpId(licenseId); _setApproval(licensorIpId, licenseId, childIpId, approved); } /// @notice Checks if a derivative IP account is approved by the licensor. + /// @param licenseId The ID of the license NFT issued from a policy of the licensor + /// @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 licenseId, address childIpId) public view returns (bool) { address licensorIpId = LICENSE_REGISTRY.licensorIpId(licenseId); return _approvals[licenseId][licensorIpId][childIpId]; } - /// @dev Sets the approval for a derivative IP account. + /// @notice Sets the approval for a derivative IP account. + /// @dev This function is only callable by the licensor IP account. + /// @param licensorIpId The ID of the licensor IP account + /// @param licenseId 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 licensorIpId, uint256 licenseId, diff --git a/contracts/modules/royalty-module/RoyaltyModule.sol b/contracts/modules/royalty-module/RoyaltyModule.sol index fa1d8456..398d8b4e 100644 --- a/contracts/modules/royalty-module/RoyaltyModule.sol +++ b/contracts/modules/royalty-module/RoyaltyModule.sol @@ -14,144 +14,148 @@ import { ROYALTY_MODULE_KEY } from "../../lib/modules/Module.sol"; import { BaseModule } from "../BaseModule.sol"; /// @title Story Protocol Royalty Module -/// @notice The Story Protocol royalty module allows to set royalty policies an ipId -/// and pay royalties as a derivative ip. +/// @notice The Story Protocol royalty module allows to set royalty policies an IP asset and pay royalties as a +/// derivative IP. contract RoyaltyModule is IRoyaltyModule, Governable, ReentrancyGuard, BaseModule { using ERC165Checker for address; string public constant override name = ROYALTY_MODULE_KEY; - /// @notice Licensing module address + /// @notice Returns the licensing module address address public LICENSING_MODULE; /// @notice Indicates if a royalty policy is whitelisted - mapping(address royaltyPolicy => bool allowed) public isWhitelistedRoyaltyPolicy; + mapping(address royaltyPolicy => bool isWhitelisted) public isWhitelistedRoyaltyPolicy; /// @notice Indicates if a royalty token is whitelisted mapping(address token => bool) public isWhitelistedRoyaltyToken; - /// @notice Indicates the royalty policy for a given ipId + /// @notice Indicates the royalty policy for a given IP asset mapping(address ipId => address royaltyPolicy) public royaltyPolicies; - /// @notice Constructor - /// @param _governance The address of the governance contract - constructor(address _governance) Governable(_governance) {} + constructor(address governance) Governable(governance) {} + /// @notice Modifier to enforce that the caller is the licensing module modifier onlyLicensingModule() { if (msg.sender != LICENSING_MODULE) revert Errors.RoyaltyModule__NotAllowedCaller(); _; } /// @notice Sets the license registry - /// @param _licensingModule The address of the license registry - function setLicensingModule(address _licensingModule) external onlyProtocolAdmin { - if (_licensingModule == address(0)) revert Errors.RoyaltyModule__ZeroLicensingModule(); - - LICENSING_MODULE = _licensingModule; + /// @dev Enforced to be only callable by the protocol admin + /// @param licensingModule The address of the license registry + function setLicensingModule(address licensingModule) external onlyProtocolAdmin { + if (licensingModule == address(0)) revert Errors.RoyaltyModule__ZeroLicensingModule(); + LICENSING_MODULE = licensingModule; } /// @notice Whitelist a royalty policy - /// @param _royaltyPolicy The address of the royalty policy - /// @param _allowed Indicates if the royalty policy is whitelisted or not - function whitelistRoyaltyPolicy(address _royaltyPolicy, bool _allowed) external onlyProtocolAdmin { - if (_royaltyPolicy == address(0)) revert Errors.RoyaltyModule__ZeroRoyaltyPolicy(); + /// @dev Enforced to be only callable by the protocol admin + /// @param royaltyPolicy The address of the royalty policy + /// @param allowed Indicates if the royalty policy is whitelisted or not + function whitelistRoyaltyPolicy(address royaltyPolicy, bool allowed) external onlyProtocolAdmin { + if (royaltyPolicy == address(0)) revert Errors.RoyaltyModule__ZeroRoyaltyPolicy(); - isWhitelistedRoyaltyPolicy[_royaltyPolicy] = _allowed; + isWhitelistedRoyaltyPolicy[royaltyPolicy] = allowed; - emit RoyaltyPolicyWhitelistUpdated(_royaltyPolicy, _allowed); + emit RoyaltyPolicyWhitelistUpdated(royaltyPolicy, allowed); } /// @notice Whitelist a royalty token - /// @param _token The token address - /// @param _allowed Indicates if the token is whitelisted or not - function whitelistRoyaltyToken(address _token, bool _allowed) external onlyProtocolAdmin { - if (_token == address(0)) revert Errors.RoyaltyModule__ZeroRoyaltyToken(); + /// @dev Enforced to be only callable by the protocol admin + /// @param token The token address + /// @param allowed Indicates if the token is whitelisted or not + function whitelistRoyaltyToken(address token, bool allowed) external onlyProtocolAdmin { + if (token == address(0)) revert Errors.RoyaltyModule__ZeroRoyaltyToken(); - isWhitelistedRoyaltyToken[_token] = _allowed; + isWhitelistedRoyaltyToken[token] = allowed; - emit RoyaltyTokenWhitelistUpdated(_token, _allowed); + emit RoyaltyTokenWhitelistUpdated(token, allowed); } /// @notice Executes royalty related logic on license minting - /// @param _ipId The ipId whose license is being minted (licensor) - /// @param _royaltyPolicy The royalty policy address of the license being minted - /// @param _licenseData The license data custom to each the royalty policy - /// @param _externalData The external data custom to each the royalty policy + /// @dev Enforced to be only callable by LicensingModule + /// @param ipId The ipId whose license is being minted (licensor) + /// @param royaltyPolicy The royalty policy address of the license being minted + /// @param licenseData The license data custom to each the royalty policy + /// @param externalData The external data custom to each the royalty policy function onLicenseMinting( - address _ipId, - address _royaltyPolicy, - bytes calldata _licenseData, - bytes calldata _externalData + address ipId, + address royaltyPolicy, + bytes calldata licenseData, + bytes calldata externalData ) external nonReentrant onlyLicensingModule { - if (!isWhitelistedRoyaltyPolicy[_royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); + if (!isWhitelistedRoyaltyPolicy[royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); - address royaltyPolicyIpId = royaltyPolicies[_ipId]; + address royaltyPolicyIpId = royaltyPolicies[ipId]; // if the node is a root node, then royaltyPolicyIpId will be address(0) and any type of royalty type can be // selected to mint a license if the node is a derivative node, then the any minted licenses by the derivative // node should have the same royalty policy as the parent node a derivative node set its royalty policy // immutably in onLinkToParents() function below - if (royaltyPolicyIpId != _royaltyPolicy && royaltyPolicyIpId != address(0)) + if (royaltyPolicyIpId != royaltyPolicy && royaltyPolicyIpId != address(0)) revert Errors.RoyaltyModule__CanOnlyMintSelectedPolicy(); - IRoyaltyPolicy(_royaltyPolicy).onLicenseMinting(_ipId, _licenseData, _externalData); + IRoyaltyPolicy(royaltyPolicy).onLicenseMinting(ipId, licenseData, externalData); } /// @notice Executes royalty related logic on linking to parents - /// @param _ipId The children ipId that is being linked to parents - /// @param _royaltyPolicy The common royalty policy address of all the licenses being burned - /// @param _parentIpIds The parent ipIds that the children ipId is being linked to - /// @param _licenseData The license data custom to each the royalty policy - /// @param _externalData The external data custom to each the royalty policy + /// @dev Enforced to be only callable by LicensingModule + /// @param ipId The children ipId that is being linked to parents + /// @param royaltyPolicy The common royalty policy address of all the licenses being burned + /// @param parentIpIds The parent ipIds that the children ipId is being linked to + /// @param licenseData The license data custom to each the royalty policy + /// @param externalData The external data custom to each the royalty policy function onLinkToParents( - address _ipId, - address _royaltyPolicy, - address[] calldata _parentIpIds, - bytes[] memory _licenseData, - bytes calldata _externalData + address ipId, + address royaltyPolicy, + address[] calldata parentIpIds, + bytes[] memory licenseData, + bytes calldata externalData ) external nonReentrant onlyLicensingModule { - if (!isWhitelistedRoyaltyPolicy[_royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); - if (_parentIpIds.length == 0) revert Errors.RoyaltyModule__NoParentsOnLinking(); + if (!isWhitelistedRoyaltyPolicy[royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); + if (parentIpIds.length == 0) revert Errors.RoyaltyModule__NoParentsOnLinking(); - for (uint32 i = 0; i < _parentIpIds.length; i++) { - address parentRoyaltyPolicy = royaltyPolicies[_parentIpIds[i]]; + for (uint32 i = 0; i < parentIpIds.length; i++) { + address parentRoyaltyPolicy = royaltyPolicies[parentIpIds[i]]; // if the parent node has a royalty policy set, then the derivative node should have the same royalty // policy if the parent node does not have a royalty policy set, then the derivative node can set any type // of royalty policy as long as the children ip obtained and is burning all licenses with that royalty type // from each parent (was checked in licensing module before calling this function) - if (parentRoyaltyPolicy != _royaltyPolicy && parentRoyaltyPolicy != address(0)) + if (parentRoyaltyPolicy != royaltyPolicy && parentRoyaltyPolicy != address(0)) revert Errors.RoyaltyModule__IncompatibleRoyaltyPolicy(); } - royaltyPolicies[_ipId] = _royaltyPolicy; + royaltyPolicies[ipId] = royaltyPolicy; - IRoyaltyPolicy(_royaltyPolicy).onLinkToParents(_ipId, _parentIpIds, _licenseData, _externalData); + IRoyaltyPolicy(royaltyPolicy).onLinkToParents(ipId, parentIpIds, licenseData, externalData); } - /// @notice Allows a sender to to pay royalties on behalf of an ipId - /// @param _receiverIpId The ipId that receives the royalties - /// @param _payerIpId The ipId that pays the royalties - /// @param _token The token to use to pay the royalties - /// @param _amount The amount to pay + /// @notice Allows the function caller to pay royalties to the receiver IP asset on behalf of the payer IP asset. + /// @param receiverIpId The ID of the IP asset that receives the royalties + /// @param payerIpId The ID of the IP asset that pays the royalties + /// @param token The token to use to pay the royalties + /// @param amount The amount to pay function payRoyaltyOnBehalf( - address _receiverIpId, - address _payerIpId, - address _token, - uint256 _amount + address receiverIpId, + address payerIpId, + address token, + uint256 amount ) external nonReentrant { - if (!isWhitelistedRoyaltyToken[_token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken(); + if (!isWhitelistedRoyaltyToken[token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken(); - address payerRoyaltyPolicy = royaltyPolicies[_payerIpId]; + address payerRoyaltyPolicy = royaltyPolicies[payerIpId]; // if the payer does not have a royalty policy set, then the payer is not a derivative ip and does not pay // royalties the receiver ip can have a zero royalty policy since that could mean it is an ip a root if (payerRoyaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet(); if (!isWhitelistedRoyaltyPolicy[payerRoyaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy(); - IRoyaltyPolicy(payerRoyaltyPolicy).onRoyaltyPayment(msg.sender, _receiverIpId, _token, _amount); + IRoyaltyPolicy(payerRoyaltyPolicy).onRoyaltyPayment(msg.sender, receiverIpId, token, amount); - emit RoyaltyPaid(_receiverIpId, _payerIpId, msg.sender, _token, _amount); + emit RoyaltyPaid(receiverIpId, payerIpId, msg.sender, token, amount); } + /// @notice IERC165 interface support. function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { return interfaceId == type(IRoyaltyModule).interfaceId || super.supportsInterface(interfaceId); } diff --git a/contracts/modules/royalty-module/policies/AncestorsVaultLAP.sol b/contracts/modules/royalty-module/policies/AncestorsVaultLAP.sol index c31026e6..38ced787 100644 --- a/contracts/modules/royalty-module/policies/AncestorsVaultLAP.sol +++ b/contracts/modules/royalty-module/policies/AncestorsVaultLAP.sol @@ -21,139 +21,128 @@ contract AncestorsVaultLAP is IAncestorsVaultLAP, ERC1155Holder, ReentrancyGuard using SafeERC20 for IERC20; /// @notice The liquid split royalty policy address - IRoyaltyPolicyLAP public immutable IROYALTY_POLICY_LAP; + IRoyaltyPolicyLAP public immutable ROYALTY_POLICY_LAP; /// @notice Indicates if a given ancestor address has already claimed mapping(address ipId => mapping(address claimerIpId => bool)) public isClaimed; - /// @notice Constructor - /// @param _royaltyPolicyLAP The liquid split royalty policy address - constructor(address _royaltyPolicyLAP) { - if (_royaltyPolicyLAP == address(0)) revert Errors.AncestorsVaultLAP__ZeroRoyaltyPolicyLAP(); + constructor(address royaltyPolicyLAP) { + if (royaltyPolicyLAP == address(0)) revert Errors.AncestorsVaultLAP__ZeroRoyaltyPolicyLAP(); - IROYALTY_POLICY_LAP = IRoyaltyPolicyLAP(_royaltyPolicyLAP); + ROYALTY_POLICY_LAP = IRoyaltyPolicyLAP(royaltyPolicyLAP); } - //TODO: double check everything given this is a permissionless call - /// @notice Allows an ancestor ipId to claim their rnfts and accrued royalties - /// @param _ipId The ipId of the IP - /// @param _claimerIpId The ipId of the claimer - /// @param _ancestors The ancestors of the IP - /// @param _ancestorsRoyalties The royalties of the ancestors - /// @param _withdrawETH Indicates if the claimer wants to withdraw ETH - /// @param _tokens The ERC20 tokens to withdraw + // TODO: double check everything given this is a permissionless call + /// @notice Allows an ancestor IP asset to claim their Royalty NFTs and accrued royalties + /// @param ipId The ipId of the IP asset + /// @param claimerIpId The ipId of the claimer + /// @param ancestors The ancestors of the IP + /// @param ancestorsRoyalties The royalties of the ancestors + /// @param withdrawETH Indicates if the claimer wants to withdraw ETH + /// @param tokens The ERC20 tokens to withdraw function claim( - address _ipId, - address _claimerIpId, - address[] calldata _ancestors, - uint32[] calldata _ancestorsRoyalties, - bool _withdrawETH, - ERC20[] calldata _tokens + address ipId, + address claimerIpId, + address[] calldata ancestors, + uint32[] calldata ancestorsRoyalties, + bool withdrawETH, + ERC20[] calldata tokens ) external nonReentrant { - (, address splitClone, address ancestorsVault, , bytes32 ancestorsHash) = IROYALTY_POLICY_LAP.royaltyData( - _ipId - ); + (, address splitClone, address ancestorsVault, , bytes32 ancestorsHash) = ROYALTY_POLICY_LAP.royaltyData(ipId); - if (isClaimed[_ipId][_claimerIpId]) revert Errors.AncestorsVaultLAP__AlreadyClaimed(); + if (isClaimed[ipId][claimerIpId]) revert Errors.AncestorsVaultLAP__AlreadyClaimed(); if (address(this) != ancestorsVault) revert Errors.AncestorsVaultLAP__InvalidClaimer(); - if (keccak256(abi.encodePacked(_ancestors, _ancestorsRoyalties)) != ancestorsHash) + if (keccak256(abi.encodePacked(ancestors, ancestorsRoyalties)) != ancestorsHash) revert Errors.AncestorsVaultLAP__InvalidAncestorsHash(); // transfer the rnfts to the claimer accrued royalties to the claimer split clone - _transferRnftsAndAccruedTokens( - _claimerIpId, - splitClone, - _ancestors, - _ancestorsRoyalties, - _withdrawETH, - _tokens - ); - - isClaimed[_ipId][_claimerIpId] = true; - - emit Claimed(_ipId, _claimerIpId, _withdrawETH, _tokens); + _transferRnftsAndAccruedTokens(claimerIpId, splitClone, ancestors, ancestorsRoyalties, withdrawETH, tokens); + + isClaimed[ipId][claimerIpId] = true; + + emit Claimed(ipId, claimerIpId, withdrawETH, tokens); } - /// @notice Transfers the rnfts and accrued tokens to the claimer - /// @param _claimerIpId The claimer ipId - /// @param _splitClone The split clone address - /// @param _ancestors The ancestors of the IP - /// @param _ancestorsRoyalties The royalties of each of the ancestors - /// @param _withdrawETH Indicates if the claimer wants to withdraw ETH - /// @param _tokens The ERC20 tokens to withdraw + /// @dev Transfers the Royalty NFTs and accrued tokens to the claimer + /// @param claimerIpId The claimer ipId + /// @param splitClone The split clone address + /// @param ancestors The ancestors of the IP + /// @param ancestorsRoyalties The royalties of each of the ancestors + /// @param withdrawETH Indicates if the claimer wants to withdraw ETH + /// @param tokens The ERC20 tokens to withdraw function _transferRnftsAndAccruedTokens( - address _claimerIpId, - address _splitClone, - address[] calldata _ancestors, - uint32[] calldata _ancestorsRoyalties, - bool _withdrawETH, - ERC20[] calldata _tokens + address claimerIpId, + address splitClone, + address[] calldata ancestors, + uint32[] calldata ancestorsRoyalties, + bool withdrawETH, + ERC20[] calldata tokens ) internal { - (uint32 index, bool isIn) = ArrayUtils.indexOf(_ancestors, _claimerIpId); + (uint32 index, bool isIn) = ArrayUtils.indexOf(ancestors, claimerIpId); if (!isIn) revert Errors.AncestorsVaultLAP__ClaimerNotAnAncestor(); // transfer the rnfts from the ancestors vault to the claimer split clone // the rnfts that are meant for the ancestors were transferred to the ancestors vault at its deployment // and each ancestor can claim their share of the rnfts only once - ILiquidSplitClone rnft = ILiquidSplitClone(_splitClone); + ILiquidSplitClone rnft = ILiquidSplitClone(splitClone); uint256 totalUnclaimedRnfts = rnft.balanceOf(address(this), 0); - (, address claimerSplitClone, , , ) = IROYALTY_POLICY_LAP.royaltyData(_claimerIpId); - uint32 rnftAmountToTransfer = _ancestorsRoyalties[index]; + (, address claimerSplitClone, , , ) = ROYALTY_POLICY_LAP.royaltyData(claimerIpId); + uint32 rnftAmountToTransfer = ancestorsRoyalties[index]; rnft.safeTransferFrom(address(this), claimerSplitClone, 0, rnftAmountToTransfer, ""); // transfer the accrued tokens to the claimer split clone - _claimAccruedTokens(rnftAmountToTransfer, totalUnclaimedRnfts, claimerSplitClone, _withdrawETH, _tokens); + _claimAccruedTokens(rnftAmountToTransfer, totalUnclaimedRnfts, claimerSplitClone, withdrawETH, tokens); } - /// @notice Claims the accrued tokens (if any) - /// @param _rnftClaimAmount The amount of rnfts to claim - /// @param _totalUnclaimedRnfts The total unclaimed rnfts - /// @param _claimerSplitClone The claimer's split clone - /// @param _withdrawETH Indicates if the claimer wants to withdraw ETH - /// @param _tokens The ERC20 tokens to withdraw + /// @dev Claims the accrued tokens (if any) + /// @param rnftClaimAmount The amount of rnfts to claim + /// @param totalUnclaimedRnfts The total unclaimed rnfts + /// @param claimerSplitClone The claimer's split clone + /// @param withdrawETH Indicates if the claimer wants to withdraw ETH + /// @param tokens The ERC20 tokens to withdraw function _claimAccruedTokens( - uint256 _rnftClaimAmount, - uint256 _totalUnclaimedRnfts, - address _claimerSplitClone, - bool _withdrawETH, - ERC20[] calldata _tokens + uint256 rnftClaimAmount, + uint256 totalUnclaimedRnfts, + address claimerSplitClone, + bool withdrawETH, + ERC20[] calldata tokens ) internal { - ILiquidSplitMain splitMain = ILiquidSplitMain(IROYALTY_POLICY_LAP.LIQUID_SPLIT_MAIN()); + ILiquidSplitMain splitMain = ILiquidSplitMain(ROYALTY_POLICY_LAP.LIQUID_SPLIT_MAIN()); - if (_withdrawETH) { + if (withdrawETH) { if (splitMain.getETHBalance(address(this)) != 0) revert Errors.AncestorsVaultLAP__ETHBalanceNotZero(); uint256 ethBalance = address(this).balance; // when totalUnclaimedRnfts is 0, claim() call will revert as expected behaviour so no need to check for it - uint256 ethClaimAmount = (ethBalance * _rnftClaimAmount) / _totalUnclaimedRnfts; + uint256 ethClaimAmount = (ethBalance * rnftClaimAmount) / totalUnclaimedRnfts; - _safeTransferETH(_claimerSplitClone, ethClaimAmount); + _safeTransferETH(claimerSplitClone, ethClaimAmount); } - for (uint256 i = 0; i < _tokens.length; ++i) { + for (uint256 i = 0; i < tokens.length; ++i) { // When withdrawing ERC20, 0xSplits sets the value to 1 to have warm storage access. // But this still means 0 amount left. So, in the check below, we use `> 1`. - if (splitMain.getERC20Balance(address(this), _tokens[i]) > 1) + if (splitMain.getERC20Balance(address(this), tokens[i]) > 1) revert Errors.AncestorsVaultLAP__ERC20BalanceNotZero(); - IERC20 IToken = IERC20(_tokens[i]); + IERC20 IToken = IERC20(tokens[i]); uint256 tokenBalance = IToken.balanceOf(address(this)); // when totalUnclaimedRnfts is 0, claim() call will revert as expected behaviour so no need to check for it - uint256 tokenClaimAmount = (tokenBalance * _rnftClaimAmount) / _totalUnclaimedRnfts; + uint256 tokenClaimAmount = (tokenBalance * rnftClaimAmount) / totalUnclaimedRnfts; - IToken.safeTransfer(_claimerSplitClone, tokenClaimAmount); + IToken.safeTransfer(claimerSplitClone, tokenClaimAmount); } } - /// @notice Allows to transfers ETH - /// @param _to The address to transfer to - /// @param _amount The amount to transfer - function _safeTransferETH(address _to, uint256 _amount) internal { + /// @dev Allows to transfers ETH + /// @param to The address to transfer to + /// @param amount The amount to transfer + function _safeTransferETH(address to, uint256 amount) internal { bool callStatus; assembly { // Transfer the ETH and store if it succeeded or not. - callStatus := call(gas(), _to, _amount, 0, 0, 0, 0) + callStatus := call(gas(), to, amount, 0, 0, 0, 0) } if (!callStatus) revert Errors.AncestorsVaultLAP__TransferFailed(); diff --git a/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol b/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol index 0a59c4ee..ef12f77b 100644 --- a/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol +++ b/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol @@ -23,93 +23,96 @@ import { Errors } from "../../../lib/Errors.sol"; contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, ReentrancyGuard { using SafeERC20 for IERC20; + /// @notice The state data of the LAP royalty policy + /// @param isUnlinkableToParents Indicates if the ipId is unlinkable to new parents + /// @param splitClone The address of the liquid split clone contract for a given ipId + /// @param ancestorsVault The address of the ancestors vault contract for a given ipId + /// @param royaltyStack The royalty stack for a given ipId is the sum of the royalties to be paid to all its parents + /// @param ancestorsHash The hash of the unique ancestors array struct LAPRoyaltyData { - bool isUnlinkableToParents; // indicates if the ipId is unlinkable to new parents - address splitClone; // address of the liquid split clone contract for a given ipId - address ancestorsVault; // address of the ancestors vault contract for a given ipId - uint32 royaltyStack; // royalty stack for a given ipId is the sum of the royalties to be paid to all its parents - bytes32 ancestorsHash; // hash of the unique ancestors array + bool isUnlinkableToParents; + address splitClone; + address ancestorsVault; + uint32 royaltyStack; + bytes32 ancestorsHash; } - /// @notice Percentage scale - 1000 rnfts represents 100% + /// @notice Returns the percentage scale - 1000 rnfts represents 100% uint32 public constant TOTAL_RNFT_SUPPLY = 1000; - /// @notice Maximum number of parents + /// @notice Returns the maximum number of parents uint256 public constant MAX_PARENTS = 2; - /// @notice Maximum number of total ancestors - // The IP derivative tree is limited to 14 ancestors - // which represents 3 levels of a binary tree 14 = 2 + 4 + 8 + /// @notice Returns the maximum number of total ancestors. + /// @dev The IP derivative tree is limited to 14 ancestors, which represents 3 levels of a binary tree 14 = 2+4+8 uint256 public constant MAX_ANCESTORS = 14; - /// @notice RoyaltyModule address + /// @notice Returns the RoyaltyModule address address public immutable ROYALTY_MODULE; - /// @notice LicensingModule address + /// @notice Returns the LicensingModule address address public immutable LICENSING_MODULE; - /// @notice LiquidSplitFactory address + /// @notice Returns the 0xSplits LiquidSplitFactory address address public immutable LIQUID_SPLIT_FACTORY; - /// @notice LiquidSplitMain address + /// @notice Returns the 0xSplits LiquidSplitMain address address public immutable LIQUID_SPLIT_MAIN; - /// @notice Ancestors vault implementation address + /// @notice Returns the Ancestors Vault Implementation address address public ANCESTORS_VAULT_IMPL; - /// @notice Links the ipId to its royalty data + /// @notice Returns the royalty data for a given IP asset mapping(address ipId => LAPRoyaltyData) public royaltyData; - /// @notice Restricts the calls to the royalty module + /// @dev Restricts the calls to the royalty module modifier onlyRoyaltyModule() { if (msg.sender != ROYALTY_MODULE) revert Errors.RoyaltyPolicyLAP__NotRoyaltyModule(); _; } - /// @notice Constructor - /// @param _royaltyModule Address of the RoyaltyModule contract - /// @param _licensingModule Address of the LicensingModule contract - /// @param _liquidSplitFactory Address of the LiquidSplitFactory contract - /// @param _liquidSplitMain Address of the LiquidSplitMain contract - /// @param _governance Address of the governance contract constructor( - address _royaltyModule, - address _licensingModule, - address _liquidSplitFactory, - address _liquidSplitMain, - address _governance - ) Governable(_governance) { - if (_royaltyModule == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroRoyaltyModule(); - if (_licensingModule == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroLicensingModule(); - if (_liquidSplitFactory == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroLiquidSplitFactory(); - if (_liquidSplitMain == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroLiquidSplitMain(); - - ROYALTY_MODULE = _royaltyModule; - LICENSING_MODULE = _licensingModule; - LIQUID_SPLIT_FACTORY = _liquidSplitFactory; - LIQUID_SPLIT_MAIN = _liquidSplitMain; + address royaltyModule, + address licensingModule, + address liquidSplitFactory, + address liquidSplitMain, + address governance + ) Governable(governance) { + if (royaltyModule == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroRoyaltyModule(); + if (licensingModule == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroLicensingModule(); + if (liquidSplitFactory == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroLiquidSplitFactory(); + if (liquidSplitMain == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroLiquidSplitMain(); + + ROYALTY_MODULE = royaltyModule; + LICENSING_MODULE = licensingModule; + LIQUID_SPLIT_FACTORY = liquidSplitFactory; + LIQUID_SPLIT_MAIN = liquidSplitMain; } - /// @notice Set the ancestors vault implementation address - /// @param _ancestorsVaultImpl The ancestors vault implementation address - function setAncestorsVaultImplementation(address _ancestorsVaultImpl) external onlyProtocolAdmin { - if (_ancestorsVaultImpl == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroAncestorsVaultImpl(); + receive() external payable {} + + /// @dev Set the ancestors vault implementation address + /// @dev Enforced to be only callable by the protocol admin in governance + /// @param ancestorsVaultImpl The ancestors vault implementation address + function setAncestorsVaultImplementation(address ancestorsVaultImpl) external onlyProtocolAdmin { + if (ancestorsVaultImpl == address(0)) revert Errors.RoyaltyPolicyLAP__ZeroAncestorsVaultImpl(); if (ANCESTORS_VAULT_IMPL != address(0)) revert Errors.RoyaltyPolicyLAP__ImplementationAlreadySet(); - ANCESTORS_VAULT_IMPL = _ancestorsVaultImpl; + ANCESTORS_VAULT_IMPL = ancestorsVaultImpl; } /// @notice Executes royalty related logic on minting a license - /// @param _ipId The children ipId that is being linked to parents - /// @param _licenseData The license data custom to each the royalty policy - /// @param _externalData The external data custom to each the royalty policy + /// @dev Enforced to be only callable by RoyaltyModule + /// @param ipId The children ipId that is being linked to parents + /// @param licenseData The license data custom to each the royalty policy + /// @param externalData The external data custom to each the royalty policy function onLicenseMinting( - address _ipId, - bytes calldata _licenseData, - bytes calldata _externalData + address ipId, + bytes calldata licenseData, + bytes calldata externalData ) external onlyRoyaltyModule { - uint32 newLicenseRoyalty = abi.decode(_licenseData, (uint32)); - LAPRoyaltyData memory data = royaltyData[_ipId]; + uint32 newLicenseRoyalty = abi.decode(licenseData, (uint32)); + LAPRoyaltyData memory data = royaltyData[ipId]; if (data.royaltyStack + newLicenseRoyalty > TOTAL_RNFT_SUPPLY) revert Errors.RoyaltyPolicyLAP__AboveRoyaltyStackLimit(); @@ -120,62 +123,64 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent // called _initPolicy() when linking to their parents with onLinkToParents() call. address[] memory rootParents = new address[](0); bytes[] memory rootParentRoyalties = new bytes[](0); - if (data.splitClone == address(0)) _initPolicy(_ipId, rootParents, rootParentRoyalties, _externalData); + if (data.splitClone == address(0)) _initPolicy(ipId, rootParents, rootParentRoyalties, externalData); } /// @notice Executes royalty related logic on linking to parents - /// @param _ipId The children ipId that is being linked to parents - /// @param _parentIpIds The selected parent ipIds - /// @param _licenseData The license data custom to each the royalty policy - /// @param _externalData The external data custom to each the royalty policy + /// @dev Enforced to be only callable by RoyaltyModule + /// @param ipId The children ipId that is being linked to parents + /// @param parentIpIds The selected parent ipIds + /// @param licenseData The license data custom to each the royalty policy + /// @param externalData The external data custom to each the royalty policy function onLinkToParents( - address _ipId, - address[] calldata _parentIpIds, - bytes[] memory _licenseData, - bytes calldata _externalData + address ipId, + address[] calldata parentIpIds, + bytes[] memory licenseData, + bytes calldata externalData ) external onlyRoyaltyModule { - if (royaltyData[_ipId].isUnlinkableToParents) revert Errors.RoyaltyPolicyLAP__UnlinkableToParents(); + if (royaltyData[ipId].isUnlinkableToParents) revert Errors.RoyaltyPolicyLAP__UnlinkableToParents(); - _initPolicy(_ipId, _parentIpIds, _licenseData, _externalData); + _initPolicy(ipId, parentIpIds, licenseData, externalData); } - /// @notice Initializes the royalty policy - /// @param _ipId The ipId - /// @param _parentIpIds The selected parent ipIds - /// @param _licenseData The license data custom to each the royalty policy - /// @param _externalData The external data custom to each the royalty policy + /// @dev Initializes the royalty policy for a given IP asset. + /// @dev Enforced to be only callable by RoyaltyModule + /// @param ipId The ipId + /// @param parentIpIds The selected parent ipIds + /// @param licenseData The license data custom to each the royalty policy + /// @param externalData The external data custom to each the royalty policy function _initPolicy( - address _ipId, - address[] memory _parentIpIds, - bytes[] memory _licenseData, - bytes calldata _externalData + address ipId, + address[] memory parentIpIds, + bytes[] memory licenseData, + bytes calldata externalData ) internal onlyRoyaltyModule { // decode license and external data - InitParams memory params = abi.decode(_externalData, (InitParams)); - uint32[] memory parentRoyalties = new uint32[](_parentIpIds.length); - for (uint256 i = 0; i < _parentIpIds.length; i++) { - parentRoyalties[i] = abi.decode(_licenseData[i], (uint32)); + InitParams memory params = abi.decode(externalData, (InitParams)); + uint32[] memory parentRoyalties = new uint32[](parentIpIds.length); + for (uint256 i = 0; i < parentIpIds.length; i++) { + parentRoyalties[i] = abi.decode(licenseData[i], (uint32)); } if (params.targetAncestors.length > MAX_ANCESTORS) revert Errors.RoyaltyPolicyLAP__AboveAncestorsLimit(); - if (_parentIpIds.length > MAX_PARENTS) revert Errors.RoyaltyPolicyLAP__AboveParentLimit(); + if (parentIpIds.length > MAX_PARENTS) revert Errors.RoyaltyPolicyLAP__AboveParentLimit(); // calculate new royalty stack - uint32 royaltyStack = _checkAncestorsDataIsValid(_parentIpIds, parentRoyalties, params); + uint32 royaltyStack = _checkAncestorsDataIsValid(parentIpIds, parentRoyalties, params); // set the parents as unlinkable / loop limited to 2 parents - for (uint256 i = 0; i < _parentIpIds.length; i++) { - royaltyData[_parentIpIds[i]].isUnlinkableToParents = true; + for (uint256 i = 0; i < parentIpIds.length; i++) { + royaltyData[parentIpIds[i]].isUnlinkableToParents = true; } // deploy ancestors vault if not root ip // 0xSplit requires two addresses to allow a split so for root ip address(this) is used as the second address - address ancestorsVault = _parentIpIds.length > 0 ? Clones.clone(ANCESTORS_VAULT_IMPL) : address(this); + address ancestorsVault = parentIpIds.length > 0 ? Clones.clone(ANCESTORS_VAULT_IMPL) : address(this); // deploy split clone - address splitClone = _deploySplitClone(_ipId, ancestorsVault, royaltyStack); + address splitClone = _deploySplitClone(ipId, ancestorsVault, royaltyStack); - royaltyData[_ipId] = LAPRoyaltyData({ + royaltyData[ipId] = LAPRoyaltyData({ // whether calling via minting license or linking to parents the ipId becomes unlinkable isUnlinkableToParents: true, splitClone: splitClone, @@ -185,7 +190,7 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent }); emit PolicyInitialized( - _ipId, + ipId, splitClone, ancestorsVault, royaltyStack, @@ -194,54 +199,45 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent ); } - /// @notice Allows to pay a royalty - /// @param _caller The caller - /// @param _ipId The ipId - /// @param _token The token to pay - /// @param _amount The amount to pay - function onRoyaltyPayment( - address _caller, - address _ipId, - address _token, - uint256 _amount - ) external onlyRoyaltyModule { - address destination = royaltyData[_ipId].splitClone; - IERC20(_token).safeTransferFrom(_caller, destination, _amount); + /// @notice Allows the caller to pay royalty to the given IP asset + /// @param caller The caller + /// @param ipId The ID of the IP asset + /// @param token The token to pay + /// @param amount The amount to pay + function onRoyaltyPayment(address caller, address ipId, address token, uint256 amount) external onlyRoyaltyModule { + address destination = royaltyData[ipId].splitClone; + IERC20(token).safeTransferFrom(caller, destination, amount); } - /// @notice Distributes funds internally so that accounts holding the royalty nfts at distribution moment can claim - /// afterwards - /// @param _ipId The ipId - /// @param _token The token to distribute - /// @param _accounts The accounts to distribute to - /// @param _distributorAddress The distributor address + /// @notice Distributes funds internally so that accounts holding the royalty nfts at distribution moment can + /// claim afterwards + /// @param ipId The ipId + /// @param token The token to distribute + /// @param accounts The accounts to distribute to + /// @param distributorAddress The distributor address function distributeIpPoolFunds( - address _ipId, - address _token, - address[] calldata _accounts, - address _distributorAddress + address ipId, + address token, + address[] calldata accounts, + address distributorAddress ) external { - ILiquidSplitClone(royaltyData[_ipId].splitClone).distributeFunds(_token, _accounts, _distributorAddress); + ILiquidSplitClone(royaltyData[ipId].splitClone).distributeFunds(token, accounts, distributorAddress); } /// @notice Claims the available royalties for a given address - /// @param _account The account to claim for - /// @param _withdrawETH The amount of ETH to withdraw - /// @param _tokens The tokens to withdraw - function claimFromIpPool(address _account, uint256 _withdrawETH, ERC20[] calldata _tokens) external { - ILiquidSplitMain(LIQUID_SPLIT_MAIN).withdraw(_account, _withdrawETH, _tokens); + /// @param account The account to claim for + /// @param withdrawETH The amount of ETH to withdraw + /// @param tokens The tokens to withdraw + function claimFromIpPool(address account, uint256 withdrawETH, ERC20[] calldata tokens) external { + ILiquidSplitMain(LIQUID_SPLIT_MAIN).withdraw(account, withdrawETH, tokens); } /// @notice Claims the available royalties for a given address that holds all the royalty nfts of an ipId - /// @param _ipId The ipId - /// @param _withdrawETH The amount of ETH to withdraw - /// @param _token The token to withdraw - function claimFromIpPoolAsTotalRnftOwner( - address _ipId, - uint256 _withdrawETH, - address _token - ) external nonReentrant { - ILiquidSplitClone splitClone = ILiquidSplitClone(royaltyData[_ipId].splitClone); + /// @param ipId The ipId + /// @param withdrawETH The amount of ETH to withdraw + /// @param token The token to withdraw + function claimFromIpPoolAsTotalRnftOwner(address ipId, uint256 withdrawETH, address token) external nonReentrant { + ILiquidSplitClone splitClone = ILiquidSplitClone(royaltyData[ipId].splitClone); ILiquidSplitMain splitMain = ILiquidSplitMain(LIQUID_SPLIT_MAIN); if (splitClone.balanceOf(msg.sender, 0) < TOTAL_RNFT_SUPPLY) revert Errors.RoyaltyPolicyLAP__NotFullOwnership(); @@ -252,67 +248,65 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent accounts[0] = msg.sender; accounts[1] = address(this); - ERC20[] memory token = _withdrawETH != 0 ? new ERC20[](0) : new ERC20[](1); + ERC20[] memory tokens = withdrawETH != 0 ? new ERC20[](0) : new ERC20[](1); - if (_withdrawETH != 0) { + if (withdrawETH != 0) { splitClone.distributeFunds(address(0), accounts, address(0)); } else { - splitClone.distributeFunds(_token, accounts, address(0)); - token[0] = ERC20(_token); + splitClone.distributeFunds(token, accounts, address(0)); + tokens[0] = ERC20(token); } - splitMain.withdraw(msg.sender, _withdrawETH, token); - splitMain.withdraw(address(this), _withdrawETH, token); + splitMain.withdraw(msg.sender, withdrawETH, tokens); + splitMain.withdraw(address(this), withdrawETH, tokens); splitClone.safeTransferFrom(address(this), msg.sender, 0, 1, "0x0"); - if (_withdrawETH != 0) { + if (withdrawETH != 0) { _safeTransferETH(msg.sender, address(this).balance); } else { - IERC20(_token).safeTransfer(msg.sender, IERC20(_token).balanceOf(address(this))); + IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this))); } } /// @notice Claims all available royalty nfts and accrued royalties for an ancestor of a given ipId - /// @param _ipId The ipId - /// @param _claimerIpId The claimer ipId - /// @param _ancestors The ancestors of the IP - /// @param _ancestorsRoyalties The royalties of the ancestors - /// @param _withdrawETH Indicates if the claimer wants to withdraw ETH - /// @param _tokens The ERC20 tokens to withdraw + /// @param ipId The ipId + /// @param claimerIpId The claimer ipId + /// @param ancestors The ancestors of the IP + /// @param ancestorsRoyalties The royalties of the ancestors + /// @param withdrawETH Indicates if the claimer wants to withdraw ETH + /// @param tokens The ERC20 tokens to withdraw function claimFromAncestorsVault( - address _ipId, - address _claimerIpId, - address[] calldata _ancestors, - uint32[] calldata _ancestorsRoyalties, - bool _withdrawETH, - ERC20[] calldata _tokens + address ipId, + address claimerIpId, + address[] calldata ancestors, + uint32[] calldata ancestorsRoyalties, + bool withdrawETH, + ERC20[] calldata tokens ) external { - IAncestorsVaultLAP(royaltyData[_ipId].ancestorsVault).claim( - _ipId, - _claimerIpId, - _ancestors, - _ancestorsRoyalties, - _withdrawETH, - _tokens + IAncestorsVaultLAP(royaltyData[ipId].ancestorsVault).claim( + ipId, + claimerIpId, + ancestors, + ancestorsRoyalties, + withdrawETH, + tokens ); } - receive() external payable {} - - /// @notice Checks if the ancestors data is valid - /// @param _parentIpIds The parent ipIds - /// @param _parentRoyalties The parent royalties - /// @param _params The init params + /// @dev Checks if the ancestors data is valid + /// @param parentIpIds The parent ipIds + /// @param parentRoyalties The parent royalties + /// @param params The init params /// @return newRoyaltyStack The new royalty stack function _checkAncestorsDataIsValid( - address[] memory _parentIpIds, - uint32[] memory _parentRoyalties, - InitParams memory _params + address[] memory parentIpIds, + uint32[] memory parentRoyalties, + InitParams memory params ) internal view returns (uint32) { - if (_params.targetRoyaltyAmount.length != _params.targetAncestors.length) + if (params.targetRoyaltyAmount.length != params.targetAncestors.length) revert Errors.RoyaltyPolicyLAP__InvalidRoyaltyAmountLength(); - if (_parentRoyalties.length != _parentIpIds.length) + if (parentRoyalties.length != parentIpIds.length) revert Errors.RoyaltyPolicyLAP__InvalidParentRoyaltiesLength(); ( @@ -320,34 +314,34 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent uint32[] memory newAncestorsRoyalty, uint32 newAncestorsCount, uint32 newRoyaltyStack - ) = _getExpectedOutputs(_parentIpIds, _parentRoyalties, _params); + ) = _getExpectedOutputs(parentIpIds, parentRoyalties, params); - if (_params.targetAncestors.length != newAncestorsCount) + if (params.targetAncestors.length != newAncestorsCount) revert Errors.RoyaltyPolicyLAP__InvalidAncestorsLength(); if (newRoyaltyStack > TOTAL_RNFT_SUPPLY) revert Errors.RoyaltyPolicyLAP__AboveRoyaltyStackLimit(); for (uint256 k = 0; k < newAncestorsCount; k++) { - if (_params.targetAncestors[k] != newAncestors[k]) revert Errors.RoyaltyPolicyLAP__InvalidAncestors(); - if (_params.targetRoyaltyAmount[k] != newAncestorsRoyalty[k]) + if (params.targetAncestors[k] != newAncestors[k]) revert Errors.RoyaltyPolicyLAP__InvalidAncestors(); + if (params.targetRoyaltyAmount[k] != newAncestorsRoyalty[k]) revert Errors.RoyaltyPolicyLAP__InvalidAncestorsRoyalty(); } return newRoyaltyStack; } - /// @notice Gets the expected outputs for the ancestors and ancestors royalties - /// @param _parentIpIds The parent ipIds - /// @param _parentRoyalties The parent royalties - /// @param _params The init params + /// @dev Gets the expected outputs for the ancestors and ancestors royalties + /// @param parentIpIds The parent ipIds + /// @param parentRoyalties The parent royalties + /// @param params The init params /// @return newAncestors The new ancestors /// @return newAncestorsRoyalty The new ancestors royalty /// @return ancestorsCount The number of ancestors /// @return royaltyStack The royalty stack // solhint-disable-next-line code-complexity function _getExpectedOutputs( - address[] memory _parentIpIds, - uint32[] memory _parentRoyalties, - InitParams memory _params + address[] memory parentIpIds, + uint32[] memory parentRoyalties, + InitParams memory params ) internal view @@ -358,35 +352,35 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent uint32 royaltyStack ) { - newAncestorsRoyalty = new uint32[](_params.targetRoyaltyAmount.length); - newAncestors = new address[](_params.targetAncestors.length); + newAncestorsRoyalty = new uint32[](params.targetRoyaltyAmount.length); + newAncestors = new address[](params.targetAncestors.length); - for (uint256 i = 0; i < _parentIpIds.length; i++) { + for (uint256 i = 0; i < parentIpIds.length; i++) { if (i == 0) { - newAncestors[ancestorsCount] = _parentIpIds[i]; - newAncestorsRoyalty[ancestorsCount] += _parentRoyalties[i]; - royaltyStack += _parentRoyalties[i]; + newAncestors[ancestorsCount] = parentIpIds[i]; + newAncestorsRoyalty[ancestorsCount] += parentRoyalties[i]; + royaltyStack += parentRoyalties[i]; ancestorsCount++; } else if (i == 1) { - (uint256 index, bool isIn) = ArrayUtils.indexOf(newAncestors, _parentIpIds[i]); + (uint256 index, bool isIn) = ArrayUtils.indexOf(newAncestors, parentIpIds[i]); if (!isIn) { - newAncestors[ancestorsCount] = _parentIpIds[i]; - newAncestorsRoyalty[ancestorsCount] += _parentRoyalties[i]; - royaltyStack += _parentRoyalties[i]; + newAncestors[ancestorsCount] = parentIpIds[i]; + newAncestorsRoyalty[ancestorsCount] += parentRoyalties[i]; + royaltyStack += parentRoyalties[i]; ancestorsCount++; } else { - newAncestorsRoyalty[index] += _parentRoyalties[i]; - royaltyStack += _parentRoyalties[i]; + newAncestorsRoyalty[index] += parentRoyalties[i]; + royaltyStack += parentRoyalties[i]; } } - address[] memory parentAncestors = i == 0 ? _params.parentAncestors1 : _params.parentAncestors2; + address[] memory parentAncestors = i == 0 ? params.parentAncestors1 : params.parentAncestors2; uint32[] memory parentAncestorsRoyalties = i == 0 - ? _params.parentAncestorsRoyalties1 - : _params.parentAncestorsRoyalties2; + ? params.parentAncestorsRoyalties1 + : params.parentAncestorsRoyalties2; if ( keccak256(abi.encodePacked(parentAncestors, parentAncestorsRoyalties)) != - royaltyData[_parentIpIds[i]].ancestorsHash + royaltyData[parentIpIds[i]].ancestorsHash ) revert Errors.RoyaltyPolicyLAP__InvalidAncestorsHash(); for (uint256 j = 0; j < parentAncestors.length; j++) { @@ -411,23 +405,19 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent } } - /// @notice Deploys a liquid split clone contract - /// @param _ipId The ipId - /// @param _ancestorsVault The ancestors vault address - /// @param _royaltyStack The number of rnfts that the ipId has to give to its parents and/or grandparents + /// @dev Deploys a liquid split clone contract + /// @param ipId The ipId + /// @param ancestorsVault The ancestors vault address + /// @param royaltyStack The number of rnfts that the ipId has to give to its parents and/or grandparents /// @return The address of the deployed liquid split clone contract - function _deploySplitClone( - address _ipId, - address _ancestorsVault, - uint32 _royaltyStack - ) internal returns (address) { + function _deploySplitClone(address ipId, address ancestorsVault, uint32 royaltyStack) internal returns (address) { address[] memory accounts = new address[](2); - accounts[0] = _ipId; - accounts[1] = _ancestorsVault; + accounts[0] = ipId; + accounts[1] = ancestorsVault; uint32[] memory initAllocations = new uint32[](2); - initAllocations[0] = TOTAL_RNFT_SUPPLY - _royaltyStack; - initAllocations[1] = _royaltyStack; + initAllocations[0] = TOTAL_RNFT_SUPPLY - royaltyStack; + initAllocations[1] = royaltyStack; address splitClone = ILiquidSplitFactory(LIQUID_SPLIT_FACTORY).createLiquidSplitClone( accounts, @@ -439,15 +429,15 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent return splitClone; } - /// @notice Allows to transfers ETH - /// @param _to The address to transfer to - /// @param _amount The amount to transfer - function _safeTransferETH(address _to, uint256 _amount) internal { + /// @dev Allows to transfers ETH + /// @param to The address to transfer to + /// @param amount The amount to transfer + function _safeTransferETH(address to, uint256 amount) internal { bool callStatus; assembly { // Transfer the ETH and store if it succeeded or not. - callStatus := call(gas(), _to, _amount, 0, 0, 0, 0) + callStatus := call(gas(), to, amount, 0, 0, 0, 0) } if (!callStatus) revert Errors.RoyaltyPolicyLAP__TransferFailed(); diff --git a/contracts/modules/tagging/TaggingModule.sol b/contracts/modules/tagging/TaggingModule.sol index f89cb783..272102f1 100644 --- a/contracts/modules/tagging/TaggingModule.sol +++ b/contracts/modules/tagging/TaggingModule.sol @@ -19,12 +19,18 @@ contract TaggingModule is BaseModule, ITaggingModule { using EnumerableSet for EnumerableSet.Bytes32Set; using EnumerableSet for EnumerableSet.AddressSet; - uint256 public constant MAX_TAG_PERMISSIONS_AT_ONCE = 300; - string public constant override name = TAGGING_MODULE_KEY; + /// @notice The maximum number of tag permissions that can be set at once + uint256 public constant MAX_TAG_PERMISSIONS_AT_ONCE = 300; + + /// @dev The tags for IP assets mapping(address => EnumerableSet.Bytes32Set) private _tagsForIpIds; + /// @notice Sets a tag on an IP asset + /// @param tag The tag value + /// @param ipId The ID of the IP asset + /// @return added True if the tag was added function setTag(string calldata tag, address ipId) external returns (bool added) { // TODO: access control // TODO: emit @@ -32,30 +38,52 @@ contract TaggingModule is BaseModule, ITaggingModule { return _tagsForIpIds[ipId].add(ShortStringOps.stringToBytes32(tag)); } + /// @notice Removes a tag from an IP asset + /// @param tag The tag value + /// @param ipId The ID of the IP asset + /// @return removed True if the tag was removed function removeTag(string calldata tag, address ipId) external returns (bool removed) { // TODO: access control emit TagRemoved(tag, ipId); return _tagsForIpIds[ipId].remove(ShortStringOps.stringToBytes32(tag)); } + /// @notice Checks if an IP asset is tagged with a specific tag + /// @param tag The tag value + /// @param ipId The ID of the IP asset + /// @return True if the IP asset is tagged with the tag function isTagged(string calldata tag, address ipId) external view returns (bool) { return _tagsForIpIds[ipId].contains(ShortStringOps.stringToBytes32(tag)); } + /// @notice Gets the total number of tags for an IP asset + /// @param ipId The ID of the IP asset + /// @return totalTags The total number of tags for the IP asset function totalTagsForIp(address ipId) external view returns (uint256) { return _tagsForIpIds[ipId].length(); } + /// @notice Gets the tag at a specific index for an IP asset + /// @dev Tag ordering is not guaranteed, as it's stored in a set + /// @param ipId The ID of the IP asset + /// @param index The local index of the tag on the IP asset + /// @return tagBytes The tag value in bytes function tagAtIndexForIp(address ipId, uint256 index) external view returns (bytes32) { // WARNING: tag ordering not guaranteed (since they can be removed) return _tagsForIpIds[ipId].at(index); } + /// @notice Gets the tag string at a specific index for an IP + /// @dev Tag ordering is not guaranteed, as it's stored in a set + /// @param ipId The ID of the IP asset + /// @param index The local index of the tag on the IP asset + /// @return tagString The tag value casted as string function tagStringAtIndexForIp(address ipId, uint256 index) external view returns (string memory) { // WARNING: tag ordering not guaranteed (since they can be removed) return ShortString.wrap(_tagsForIpIds[ipId].at(index)).toString(); } + /// @notice IERC165 interface support. function supportsInterface(bytes4 interfaceId) public view virtual override(BaseModule, IERC165) returns (bool) { return interfaceId == type(ITaggingModule).interfaceId || super.supportsInterface(interfaceId); } diff --git a/contracts/registries/IPAccountRegistry.sol b/contracts/registries/IPAccountRegistry.sol index 38164c5b..43e7a92f 100644 --- a/contracts/registries/IPAccountRegistry.sol +++ b/contracts/registries/IPAccountRegistry.sol @@ -11,67 +11,71 @@ import { Errors } from "../lib/Errors.sol"; /// @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 { + /// @notice Returns the IPAccount implementation address address public immutable IP_ACCOUNT_IMPL; + + /// @notice Returns the IPAccount salt bytes32 public immutable IP_ACCOUNT_SALT; + + /// @notice Returns the public ERC6551 registry address address public immutable ERC6551_PUBLIC_REGISTRY; - /// @notice Constructor for the IPAccountRegistry contract. - /// @param erc6551Registry_ The address of the ERC6551 registry. - /// @param ipAccountImpl_ The address of the IP account implementation. - constructor(address erc6551Registry_, address ipAccountImpl_) { - if (ipAccountImpl_ == address(0)) revert Errors.IPAccountRegistry_InvalidIpAccountImpl(); - IP_ACCOUNT_IMPL = ipAccountImpl_; + constructor(address erc6551Registry, address ipAccountImpl) { + if (ipAccountImpl == address(0)) revert Errors.IPAccountRegistry_InvalidIpAccountImpl(); + IP_ACCOUNT_IMPL = ipAccountImpl; IP_ACCOUNT_SALT = bytes32(0); - ERC6551_PUBLIC_REGISTRY = erc6551Registry_; + ERC6551_PUBLIC_REGISTRY = erc6551Registry; } - /// @notice Deploys an IPAccount contract with the IPAccount implementation and returns the address of the new IP. - /// @param chainId_ The chain ID where the IP Account will be created. - /// @param tokenContract_ The address of the token contract to be associated with the IP Account. - /// @param tokenId_ The ID of the token to be associated with the IP Account. - /// @return ipAccountAddress The address of the newly created IP Account. + /// @notice Deploys an IPAccount contract with the IPAccount implementation and returns the address of the new IP + /// @dev The IPAccount deployment deltegates to public ERC6551 Registry + /// @param chainId The chain ID where the IP Account will be created + /// @param tokenContract The address of the token contract to be associated with the IP Account + /// @param tokenId The ID of the token to be associated with the IP Account + /// @return ipAccountAddress The address of the newly created IP Account function registerIpAccount( - uint256 chainId_, - address tokenContract_, - uint256 tokenId_ + uint256 chainId, + address tokenContract, + uint256 tokenId ) public returns (address ipAccountAddress) { ipAccountAddress = IERC6551Registry(ERC6551_PUBLIC_REGISTRY).createAccount( IP_ACCOUNT_IMPL, IP_ACCOUNT_SALT, - chainId_, - tokenContract_, - tokenId_ + chainId, + tokenContract, + tokenId ); - emit IPAccountRegistered(ipAccountAddress, IP_ACCOUNT_IMPL, chainId_, tokenContract_, tokenId_); + emit IPAccountRegistered(ipAccountAddress, IP_ACCOUNT_IMPL, chainId, tokenContract, tokenId); } /// @notice Returns the IPAccount address for the given NFT token. - /// @param chainId_ The chain ID where the IP Account is located. - /// @param tokenContract_ The address of the token contract associated with the IP Account. - /// @param tokenId_ The ID of the token associated with the IP Account. - /// @return The address of the IP Account associated with the given NFT token. - function ipAccount(uint256 chainId_, address tokenContract_, uint256 tokenId_) public view returns (address) { - return _get6551AccountAddress(chainId_, tokenContract_, tokenId_); + /// @param chainId The chain ID where the IP Account is located + /// @param tokenContract The address of the token contract associated with the IP Account + /// @param tokenId The ID of the token associated with the IP Account + /// @return ipAccountAddress The address of the IP Account associated with the given NFT token + function ipAccount(uint256 chainId, address tokenContract, uint256 tokenId) public view returns (address) { + return _get6551AccountAddress(chainId, tokenContract, tokenId); } /// @notice Returns the IPAccount implementation address. - /// @return The address of the IPAccount implementation. + /// @return The address of the IPAccount implementation function getIPAccountImpl() external view override returns (address) { return IP_ACCOUNT_IMPL; } + /// @dev Helper function to get the IPAccount address from the ERC6551 registry. function _get6551AccountAddress( - uint256 chainId_, - address tokenContract_, - uint256 tokenId_ + uint256 chainId, + address tokenContract, + uint256 tokenId ) internal view returns (address) { return IERC6551Registry(ERC6551_PUBLIC_REGISTRY).account( IP_ACCOUNT_IMPL, IP_ACCOUNT_SALT, - chainId_, - tokenContract_, - tokenId_ + chainId, + tokenContract, + tokenId ); } } diff --git a/contracts/registries/IPAssetRegistry.sol b/contracts/registries/IPAssetRegistry.sol index 81715f1d..3c786290 100644 --- a/contracts/registries/IPAssetRegistry.sol +++ b/contracts/registries/IPAssetRegistry.sol @@ -29,15 +29,6 @@ import { Governable } from "../governance/Governable.sol"; /// IMPORTANT: The IP account address, besides being used for protocol /// auth, is also the canonical IP identifier for the IP NFT. contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry, Governable { - /// @notice Attributes for the IP asset type. - struct Record { - // Metadata provider for Story Protocol canonicalized metadata. - IMetadataProviderMigratable metadataProvider; - // Metadata resolver for custom metadata added by the IP owner. - // TODO: Deprecate this in favor of consolidation through the provider. - address resolver; - } - /// @notice The canonical module registry used by the protocol. IModuleRegistry public immutable MODULE_REGISTRY; @@ -53,14 +44,9 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry, Governable { /// @dev Maps an IP, identified by its IP ID, to an IP record. mapping(address => Record) internal _records; - /// @notice Tracks the current metadata provider used for IP registrations. + /// @dev Tracks the current metadata provider used for IP registrations. IMetadataProviderMigratable internal _metadataProvider; - /// @notice Initializes the IP Asset Registry. - /// @param erc6551Registry The address of the ERC6551 registry. - /// @param ipAccountImpl The address of the IP account implementation. - /// @param moduleRegistry The address of the module registry. - /// @param governance The address of the governance contract. /// TODO: Utilize module registry for fetching different modules. constructor( address erc6551Registry, @@ -72,38 +58,56 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry, Governable { _metadataProvider = IMetadataProviderMigratable(new MetadataProviderV1(address(this))); } + // TODO: Switch to access controller for centralizing this auth mechanism. /// @notice Enables third party operators to register on behalf of an NFT owner. /// @param operator The address of the operator the sender authorizes. /// @param approved Whether or not to approve that operator for registration. - /// TODO: Switch to access controller for centralizing this auth mechanism. function setApprovalForAll(address operator, bool approved) external { isApprovedForAll[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } - /// @notice Sets the registration module that interacts with IPAssetRegistry. + /// @dev Sets the registration module that interacts with IPAssetRegistry. /// @param registrationModule The address of the registration module. function setRegistrationModule(address registrationModule) external onlyProtocolAdmin { REGISTRATION_MODULE = IRegistrationModule(registrationModule); } - /// @notice Registers an NFT as an IP asset. + /// @dev Sets the provider for storage of new IP metadata, while enabling existing IP assets to migrate their + /// metadata to the new provider. + /// @param newMetadataProvider Address of the new metadata provider contract. + function setMetadataProvider(address newMetadataProvider) external onlyProtocolAdmin { + _metadataProvider.setUpgradeProvider(newMetadataProvider); + _metadataProvider = IMetadataProviderMigratable(newMetadataProvider); + } + + /// @notice Registers an NFT as IP, creating a corresponding IP record. /// @param chainId The chain identifier of where the NFT resides. /// @param tokenContract The address of the NFT. /// @param tokenId The token identifier of the NFT. /// @param resolverAddr The address of the resolver to associate with the IP. /// @param createAccount Whether to create an IP account when registering. - /// @param data Canonical metadata to associate with the IP. + /// @param metadata_ Metadata in bytes to associate with the IP. + /// @return ipId_ The address of the newly registered IP. function register( uint256 chainId, address tokenContract, uint256 tokenId, address resolverAddr, bool createAccount, - bytes calldata data - ) external returns (address id) { - id = _register(new uint256[](0), "", chainId, tokenContract, tokenId, resolverAddr, createAccount, data); - emit IPRegistered(id, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), data); + bytes calldata metadata_ + ) external returns (address ipId_) { + ipId_ = _register( + new uint256[](0), + "", + chainId, + tokenContract, + tokenId, + resolverAddr, + createAccount, + metadata_ + ); + emit IPRegistered(ipId_, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), metadata_); } /// @notice Registers an NFT as an IP using licenses derived from parent IP asset(s). @@ -112,8 +116,10 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry, Governable { /// @param chainId The chain identifier of where the NFT resides. /// @param tokenContract The address of the NFT. /// @param tokenId The token identifier of the NFT. + /// @param resolverAddr The address of the resolver to associate with the IP. /// @param createAccount Whether to create an IP account when registering. - /// @param data Canonical metadata to associate with the IP. + /// @param metadata_ Metadata in bytes to associate with the IP. + /// @return ipId_ The address of the newly registered IP. function register( uint256[] calldata licenseIds, bytes calldata royaltyContext, @@ -122,10 +128,19 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry, Governable { uint256 tokenId, address resolverAddr, bool createAccount, - bytes calldata data - ) external returns (address id) { - id = _register(licenseIds, royaltyContext, chainId, tokenContract, tokenId, resolverAddr, createAccount, data); - emit IPRegistered(id, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), data); + bytes calldata metadata_ + ) external returns (address ipId_) { + ipId_ = _register( + licenseIds, + royaltyContext, + chainId, + tokenContract, + tokenId, + resolverAddr, + createAccount, + metadata_ + ); + emit IPRegistered(ipId_, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), metadata_); } /// @notice Gets the canonical IP identifier associated with an IP NFT. @@ -133,40 +148,41 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry, Governable { /// @param chainId The chain identifier of where the IP resides. /// @param tokenContract The address of the IP. /// @param tokenId The token identifier of the IP. - /// @return The IP's canonical address identifier. + /// @return ipId The IP's canonical address identifier. function ipId(uint256 chainId, address tokenContract, uint256 tokenId) public view returns (address) { return super.ipAccount(chainId, tokenContract, tokenId); } /// @notice Checks whether an IP was registered based on its ID. /// @param id The canonical identifier for the IP. - /// @return Whether the IP was registered into the protocol. + /// @return isRegistered Whether the IP was registered into the protocol. function isRegistered(address id) external view returns (bool) { return _records[id].resolver != address(0); } /// @notice Gets the resolver bound to an IP based on its ID. /// @param id The canonical identifier for the IP. - /// @return The IP resolver address if registered, else the zero address. + /// @return resolver The IP resolver address if registered, else the zero address. function resolver(address id) external view returns (address) { return _records[id].resolver; } /// @notice Gets the metadata provider used for new metadata registrations. - /// @return The address of the metadata provider used for new IP registrations. + /// @return metadataProvider The address of the metadata provider used for new IP registrations. function metadataProvider() external view returns (address) { return address(_metadataProvider); } /// @notice Gets the metadata provider linked to an IP based on its ID. /// @param id The canonical identifier for the IP. - /// @return The metadata provider that was bound to this IP at creation time. + /// @return metadataProvider The metadata provider that was bound to this IP at creation time. function metadataProvider(address id) external view returns (address) { return address(_records[id].metadataProvider); } /// @notice Gets the underlying canonical metadata linked to an IP asset. /// @param id The canonical ID of the IP asset. + /// @return metadata The metadata that was bound to this IP at creation time. function metadata(address id) external view returns (bytes memory) { if (address(_records[id].metadataProvider) == address(0)) { revert Errors.IPAssetRegistry__NotYetRegistered(); @@ -174,17 +190,9 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry, Governable { return _records[id].metadataProvider.getMetadata(id); } - /// @notice Sets the provider for storage of new IP metadata, while enabling - /// existing IP assets to migrate their metadata to the new provider. - /// @param newMetadataProvider Address of the new metadata provider contract. - function setMetadataProvider(address newMetadataProvider) external onlyProtocolAdmin { - _metadataProvider.setUpgradeProvider(newMetadataProvider); - _metadataProvider = IMetadataProviderMigratable(newMetadataProvider); - } - /// @notice Sets the underlying metadata for an IP asset. - /// @dev As metadata is immutable but additive, this will only be used when - /// an IP migrates from a new provider that introduces new attributes. + /// @dev As metadata is immutable but additive, this will only be used when an IP migrates from a new provider that + /// introduces new attributes. /// @param id The canonical ID of the IP. /// @param data Canonical metadata to associate with the IP. function setMetadata(address id, address provider, bytes calldata data) external { diff --git a/contracts/registries/LicenseRegistry.sol b/contracts/registries/LicenseRegistry.sol index e2bb8c63..5d481f0a 100644 --- a/contracts/registries/LicenseRegistry.sol +++ b/contracts/registries/LicenseRegistry.sol @@ -20,19 +20,26 @@ contract LicenseRegistry is ILicenseRegistry, ERC1155, Governable { using Strings for *; // TODO: deploy with CREATE2 to make this immutable - ILicensingModule private _licensingModule; + /// @notice Returns the canonical protocol-wide LicensingModule + ILicensingModule public LICENSING_MODULE; + + /// @notice Returns the canonical protocol-wide DisputeModule IDisputeModule public DISPUTE_MODULE; - mapping(bytes32 licenseHash => uint256 ids) private _hashedLicenses; - mapping(uint256 licenseIds => Licensing.License licenseData) private _licenses; - /// This tracks the number of licenses registered in the protocol, it will not decrease when a license is burnt. + /// @dev Maps the hash of the license data to the licenseId + mapping(bytes32 licenseHash => uint256 licenseId) private _hashedLicenses; + + /// @dev Maps the licenseId to the license data + mapping(uint256 licenseId => Licensing.License licenseData) private _licenses; + + /// @dev Tracks the number of licenses registered in the protocol, it will not decrease when a license is burnt. uint256 private _mintedLicenses; /// @dev We have to implement this modifier instead of inheriting `LicensingModuleAware` because LicensingModule /// constructor requires the licenseRegistry address, which would create a circular dependency. Thus, we use the /// function `setLicensingModule` to set the licensing module address after deploying the module. modifier onlyLicensingModule() { - if (msg.sender != address(_licensingModule)) { + if (msg.sender != address(LICENSING_MODULE)) { revert Errors.LicenseRegistry__CallerNotLicensingModule(); } _; @@ -40,6 +47,9 @@ contract LicenseRegistry is ILicenseRegistry, ERC1155, Governable { constructor(address governance) ERC1155("") Governable(governance) {} + /// @dev Sets the DisputeModule address. + /// @dev Enforced to be only callable by the protocol admin + /// @param newDisputeModule The address of the DisputeModule function setDisputeModule(address newDisputeModule) external onlyProtocolAdmin { if (newDisputeModule == address(0)) { revert Errors.LicenseRegistry__ZeroDisputeModule(); @@ -47,39 +57,36 @@ contract LicenseRegistry is ILicenseRegistry, ERC1155, Governable { DISPUTE_MODULE = IDisputeModule(newDisputeModule); } + /// @dev Sets the LicensingModule address. + /// @dev Enforced to be only callable by the protocol admin + /// @param newLicensingModule The address of the LicensingModule function setLicensingModule(address newLicensingModule) external onlyProtocolAdmin { if (newLicensingModule == address(0)) { revert Errors.LicenseRegistry__ZeroLicensingModule(); } - _licensingModule = ILicensingModule(newLicensingModule); - } - - function licensingModule() external view override returns (address) { - return address(_licensingModule); + LICENSING_MODULE = ILicensingModule(newLicensingModule); } - /// Mints license NFTs representing a policy granted by a set of ipIds (licensors). This NFT needs to be burned - /// in order to link a derivative IP with its parents. - /// If this is the first combination of policy and licensors, a new licenseId - /// will be created. - /// If not, the license is fungible and an id will be reused. - /// Only callable by the LicensingModule. - /// @param policyId id of the policy to be minted - /// @param licensorIpId IP Id granting the license + /// @notice Mints license NFTs representing a policy granted by a set of ipIds (licensors). This NFT needs to be + /// burned in order to link a derivative IP with its parents. If this is the first combination of policy and + /// licensors, a new licenseId will be created. If not, the license is fungible and an id will be reused. + /// @dev Only callable by the licensing module. + /// @param policyId The ID of the policy to be minted + /// @param licensorIpId_ The ID of the IP granting the license (ie. licensor) /// @param transferable True if the license is transferable - /// @param amount of licenses to be minted. License NFT is fungible for same policy and same licensors - /// @param receiver of the License NFT(s). - /// @return licenseId of the NFT(s). + /// @param amount Number of licenses to mint. License NFT is fungible for same policy and same licensors + /// @param receiver Receiver address of the minted license NFT(s). + /// @return licenseId The ID of the minted license NFT(s). function mintLicense( uint256 policyId, - address licensorIpId, + address licensorIpId_, bool transferable, uint256 amount, // mint amount address receiver ) external onlyLicensingModule returns (uint256 licenseId) { Licensing.License memory licenseData = Licensing.License({ policyId: policyId, - licensorIpId: licensorIpId, + licensorIpId: licensorIpId_, transferable: transferable }); bool isNew; @@ -106,30 +113,45 @@ contract LicenseRegistry is ILicenseRegistry, ERC1155, Governable { _burnBatch(holder, licenseIds, values); } + /// @notice Returns the number of licenses registered in the protocol. + /// @dev Token ID counter total count. + /// @return mintedLicenses The number of minted licenses function mintedLicenses() external view returns (uint256) { return _mintedLicenses; } - /// Returns true if holder has positive balance for licenseId + /// @notice Returns true if holder has positive balance for the given license ID. + /// @return isLicensee True if holder is the licensee for the license (owner of the license NFT), or derivative IP + /// owner if the license was added to the IP by linking (burning a license). function isLicensee(uint256 licenseId, address holder) external view returns (bool) { return balanceOf(holder, licenseId) > 0; } + /// @notice Returns the license data for the given license ID + /// @param licenseId The ID of the license + /// @return licenseData The license data function license(uint256 licenseId) external view returns (Licensing.License memory) { return _licenses[licenseId]; } + /// @notice Returns the ID of the IP asset that is the licensor of the given license ID + /// @param licenseId The ID of the license + /// @return licensorIpId The ID of the licensor function licensorIpId(uint256 licenseId) external view returns (address) { return _licenses[licenseId].licensorIpId; } + /// @notice Returns the policy ID for the given license ID + /// @param licenseId The ID of the license + /// @return policyId The ID of the policy function policyIdForLicense(uint256 licenseId) external view returns (uint256) { return _licenses[licenseId].policyId; } /// @notice Returns true if the license has been revoked (licensor tagged after a dispute in /// the dispute module). If the tag is removed, the license is not revoked anymore. - /// @param licenseId The id of the license + /// @param licenseId The id of the license to check + /// @return isRevoked True if the license is revoked function isLicenseRevoked(uint256 licenseId) public view returns (bool) { // For beta, any tag means revocation, for mainnet we need more context. // TODO: signal metadata update when tag changes. @@ -141,7 +163,7 @@ contract LicenseRegistry is ILicenseRegistry, ERC1155, Governable { /// (last attribute must not have a comma at the end) function uri(uint256 id) public view virtual override returns (string memory) { Licensing.License memory licenseData = _licenses[id]; - Licensing.Policy memory pol = _licensingModule.policy(licenseData.policyId); + Licensing.Policy memory pol = LICENSING_MODULE.policy(licenseData.policyId); string memory licensorIpIdHex = licenseData.licensorIpId.toHexString(); diff --git a/contracts/registries/ModuleRegistry.sol b/contracts/registries/ModuleRegistry.sol index 115da2e5..fd34fdb8 100644 --- a/contracts/registries/ModuleRegistry.sol +++ b/contracts/registries/ModuleRegistry.sol @@ -12,18 +12,19 @@ import { Governable } from "../governance/Governable.sol"; import { MODULE_TYPE_DEFAULT } from "../lib/modules/Module.sol"; /// @title ModuleRegistry +/// @notice This contract is used to register and track modules in the protocol. contract ModuleRegistry is IModuleRegistry, Governable { using Strings for *; using ERC165Checker for address; - /// @dev Maps module names to their address. - mapping(string => address) public modules; + /// @dev Returns the address of a registered module by its name. + mapping(string moduleName => address moduleAddress) internal modules; - /// @dev Maps module addresses to their module type name. - mapping(address => string) public moduleTypes; + /// @dev Returns the module type of a registered module by its address. + mapping(address moduleAddress => string moduleType) internal moduleTypes; - /// @dev All registered module types to their interface id. - mapping(string => bytes4) public allModuleTypes; + /// @dev Returns the interface ID of a registered module type. + mapping(string moduleType => bytes4 moduleTypeInterface) internal allModuleTypes; constructor(address governance) Governable(governance) { // Register the default module types @@ -31,6 +32,7 @@ contract ModuleRegistry is IModuleRegistry, Governable { } /// @notice Registers a new module type in the registry associate with an interface. + /// @dev Enforced to be only callable by the protocol admin in governance. /// @param name The name of the module type to be registered. /// @param interfaceId The interface ID associated with the module type. function registerModuleType(string memory name, bytes4 interfaceId) external override onlyProtocolAdmin { @@ -47,6 +49,7 @@ contract ModuleRegistry is IModuleRegistry, Governable { } /// @notice Removes a module type from the registry. + /// @dev Enforced to be only callable by the protocol admin in governance. /// @param name The name of the module type to be removed. function removeModuleType(string memory name) external override onlyProtocolAdmin { if (bytes(name).length == 0) { @@ -58,15 +61,16 @@ contract ModuleRegistry is IModuleRegistry, Governable { delete allModuleTypes[name]; } - /// @notice Registers a new module in the registry with an default module type. - /// @param name The name of the module to be registered. + /// @notice Registers a new module in the registry. + /// @dev Enforced to be only callable by the protocol admin in governance. + /// @param name The name of the module. /// @param moduleAddress The address of the module. function registerModule(string memory name, address moduleAddress) external onlyProtocolAdmin { _registerModule(name, moduleAddress, MODULE_TYPE_DEFAULT); } - /// @notice Registers a new module in the protocol with given module type. - /// @param name The name of the module. + /// @notice Registers a new module in the registry with an associated module type. + /// @param name The name of the module to be registered. /// @param moduleAddress The address of the module. /// @param moduleType The type of the module being registered. function registerModule( @@ -77,8 +81,9 @@ contract ModuleRegistry is IModuleRegistry, Governable { _registerModule(name, moduleAddress, moduleType); } - /// @notice Removes a module from the protocol. - /// @param name The name of the module to be removed. + /// @notice Removes a module from the registry. + /// @dev Enforced to be only callable by the protocol admin in governance. + /// @param name The name of the module. function removeModule(string memory name) external onlyProtocolAdmin { if (bytes(name).length == 0) { revert Errors.ModuleRegistry__NameEmptyString(); @@ -95,6 +100,13 @@ contract ModuleRegistry is IModuleRegistry, Governable { emit ModuleRemoved(name, module); } + /// @notice Checks if a module is registered in the protocol. + /// @param moduleAddress The address of the module. + /// @return isRegistered True if the module is registered, false otherwise. + function isRegistered(address moduleAddress) external view returns (bool) { + return bytes(moduleTypes[moduleAddress]).length > 0; + } + /// @notice Returns the address of a module. /// @param name The name of the module. /// @return The address of the module. @@ -102,13 +114,6 @@ contract ModuleRegistry is IModuleRegistry, Governable { return modules[name]; } - /// @notice Checks if a module is registered in the protocol. - /// @param moduleAddress The address of the module. - /// @return True if the module is registered, false otherwise. - function isRegistered(address moduleAddress) external view returns (bool) { - return bytes(moduleTypes[moduleAddress]).length > 0; - } - /// @notice Returns the module type of a given module address. /// @param moduleAddress The address of the module. /// @return The type of the module as a string. @@ -123,6 +128,7 @@ contract ModuleRegistry is IModuleRegistry, Governable { return allModuleTypes[moduleType]; } + /// @dev Registers a new module in the registry. // solhint-disable code-complexity function _registerModule(string memory name, address moduleAddress, string memory moduleType) internal { if (moduleAddress == address(0)) { diff --git a/contracts/registries/metadata/IPMetadataProvider.sol b/contracts/registries/metadata/IPMetadataProvider.sol index 7079f36a..c8aacf22 100644 --- a/contracts/registries/metadata/IPMetadataProvider.sol +++ b/contracts/registries/metadata/IPMetadataProvider.sol @@ -14,22 +14,21 @@ contract IPMetadataProvider is IMetadataProvider { /// @notice Maps IPs to their metadata based on their IP IDs. mapping(address ip => bytes metadata) internal _ipMetadata; - /// @notice Initializes the metadata provider contract. - /// @param moduleRegistry Gets the protocol-wide module registry. constructor(address moduleRegistry) { MODULE_REGISTRY = IModuleRegistry(moduleRegistry); } - /// @notice Gets the IP metadata associated with an IP asset based on its IP ID. - /// @param ipId The IP id of the target IP asset. + /// @notice Gets the metadata associated with an IP asset. + /// @param ipId The address identifier of the IP asset. + /// @return metadata The encoded metadata associated with the IP asset. function getMetadata(address ipId) external view virtual override returns (bytes memory) { return _ipMetadata[ipId]; } - /// @notice Sets the IP metadata associated with an IP asset based on its IP ID. - /// @param ipId The IP id of the IP asset to set metadata for. - /// @param metadata The metadata in bytes to set for the IP asset. // TODO: Add access control for IP owner can set metadata. + /// @notice Sets the metadata associated with an IP asset. + /// @param ipId The address identifier of the IP asset. + /// @param metadata The metadata in bytes to associate with the IP asset. function setMetadata(address ipId, bytes memory metadata) external { _ipMetadata[ipId] = metadata; } diff --git a/contracts/registries/metadata/MetadataProviderBase.sol b/contracts/registries/metadata/MetadataProviderBase.sol index e4a1a9bb..d2826384 100644 --- a/contracts/registries/metadata/MetadataProviderBase.sol +++ b/contracts/registries/metadata/MetadataProviderBase.sol @@ -4,17 +4,17 @@ pragma solidity ^0.8.23; import { IIPAccount } from "../../interfaces/IIPAccount.sol"; import { IMetadataProvider } from "../../interfaces/registries/metadata/IMetadataProvider.sol"; +import { IIPAssetRegistry } from "../../interfaces/registries/IIPAssetRegistry.sol"; import { IMetadataProviderMigratable } from "../../interfaces/registries/metadata/IMetadataProviderMigratable.sol"; import { Errors } from "../../lib/Errors.sol"; -import { IPAssetRegistry } from "../../registries/IPAssetRegistry.sol"; /// @title IP Metadata Provider Base Contract /// @notice Metadata provider base contract for storing canonical IP metadata. abstract contract MetadataProviderBase is IMetadataProviderMigratable { - /// @notice Gets the protocol-wide IP asset registry. - IPAssetRegistry public immutable IP_ASSET_REGISTRY; + /// @notice Returns the protocol-wide IP asset registry. + IIPAssetRegistry public immutable IP_ASSET_REGISTRY; - /// @notice Returns the new metadata provider users may migrate to. + /// @notice Returns the new metadata provider IP assets may migrate to. IMetadataProvider public upgradeProvider; /// @notice Maps IP assets (via their IP ID) to their canonical metadata. @@ -28,14 +28,13 @@ abstract contract MetadataProviderBase is IMetadataProviderMigratable { _; } - /// @notice Initializes the metadata provider contract. - /// @param ipAssetRegistry The protocol-wide IP asset registry. constructor(address ipAssetRegistry) { - IP_ASSET_REGISTRY = IPAssetRegistry(ipAssetRegistry); + IP_ASSET_REGISTRY = IIPAssetRegistry(ipAssetRegistry); } - /// @notice Gets the IP metadata associated with an IP asset based on its IP ID. - /// @param ipId The IP id of the target IP asset. + /// @notice Gets the metadata associated with an IP asset. + /// @param ipId The address identifier of the IP asset. + /// @return metadata The encoded metadata associated with the IP asset. function getMetadata(address ipId) external view virtual override returns (bytes memory) { return _ipMetadata[ipId]; } @@ -50,9 +49,10 @@ abstract contract MetadataProviderBase is IMetadataProviderMigratable { upgradeProvider = IMetadataProviderMigratable(provider); } - /// @notice Upgrades the metadata provider of an IP asset. - /// @param ipId The IP id of the target IP asset. - /// @param metadata The existing metadata paired with new metadata to add. + /// @notice Updates the provider used by the IP asset, migrating existing metadata to the new provider, and adding + /// new metadata. + /// @param ipId The address identifier of the IP asset. + /// @param metadata Additional metadata in bytes used by the new metadata provider. function upgrade(address payable ipId, bytes memory metadata) external override { if (address(upgradeProvider) == address(0)) { revert Errors.MetadataProvider__UpgradeUnavailable(); @@ -67,18 +67,19 @@ abstract contract MetadataProviderBase is IMetadataProviderMigratable { IP_ASSET_REGISTRY.setMetadata(ipId, address(upgradeProvider), metadata); } - /// @notice Sets the IP metadata associated with an IP asset based on its IP ID. - /// @param ipId The IP id of the IP asset to set metadata for. - /// @param data The metadata in bytes to set for the IP asset. - function setMetadata(address ipId, bytes memory data) external virtual onlyIPAssetRegistry { - _verifyMetadata(data); - _ipMetadata[ipId] = data; - emit MetadataSet(ipId, data); + /// @notice Sets the metadata associated with an IP asset. + /// @dev Enforced to be only callable by the IP asset registry. + /// @param ipId The address identifier of the IP asset. + /// @param metadata The metadata in bytes to associate with the IP asset. + function setMetadata(address ipId, bytes memory metadata) external virtual onlyIPAssetRegistry { + _verifyMetadata(metadata); + _ipMetadata[ipId] = metadata; + emit MetadataSet(ipId, metadata); } /// @dev Checks that the data conforms to the canonical metadata standards. - /// @param data The canonical metadata in bytes to verify. - function _verifyMetadata(bytes memory data) internal virtual; + /// @param metadata The canonical metadata in bytes to verify. + function _verifyMetadata(bytes memory metadata) internal virtual; /// @dev Checks whether two sets of metadata are compatible with one another. /// @param m1 The first set of bytes metadata being compared. diff --git a/contracts/registries/metadata/MetadataProviderV1.sol b/contracts/registries/metadata/MetadataProviderV1.sol index ba9a190e..38e3e4da 100644 --- a/contracts/registries/metadata/MetadataProviderV1.sol +++ b/contracts/registries/metadata/MetadataProviderV1.sol @@ -15,36 +15,42 @@ contract MetadataProviderV1 is MetadataProviderBase { /// @notice Fetches the metadata linked to an IP asset. /// @param ipId The address identifier of the IP asset. + /// @return metadata The metadata linked to the IP asset. function metadata(address ipId) external view returns (IP.MetadataV1 memory) { return _metadataV1(ipId); } /// @notice Gets the name associated with the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return name The name associated with the IP asset. function name(address ipId) external view returns (string memory) { return _metadataV1(ipId).name; } /// @notice Gets the hash associated with the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return hash The hash associated with the IP asset. function hash(address ipId) external view returns (bytes32) { return _metadataV1(ipId).hash; } /// @notice Gets the date in which the IP asset was registered. /// @param ipId The address identifier of the IP asset. + /// @return registrationDate The date in which the IP asset was registered. function registrationDate(address ipId) external view returns (uint64) { return _metadataV1(ipId).registrationDate; } /// @notice Gets the initial registrant address of the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return registrant The initial registrant address of the IP asset. function registrant(address ipId) external view returns (address) { return _metadataV1(ipId).registrant; } /// @notice Gets the external URI associated with the IP asset. /// @param ipId The address identifier of the IP asset. + /// @return uri The external URI associated with the IP asset. function uri(address ipId) external view returns (string memory) { return _metadataV1(ipId).uri; } diff --git a/contracts/resolvers/IPResolver.sol b/contracts/resolvers/IPResolver.sol index 5586ccc0..63f82cc0 100644 --- a/contracts/resolvers/IPResolver.sol +++ b/contracts/resolvers/IPResolver.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.23; import { ResolverBase } from "./ResolverBase.sol"; -import { IModule } from "../interfaces/modules/base/IModule.sol"; import { KeyValueResolver } from "../resolvers/KeyValueResolver.sol"; import { IP_RESOLVER_MODULE_KEY } from "../lib/modules/Module.sol"; @@ -13,20 +12,12 @@ import { IP_RESOLVER_MODULE_KEY } from "../lib/modules/Module.sol"; /// and supported interface (address, interfaceId) to tie to an IP asset. /// TODO: Add support for multicall, so multiple records may be set at once. contract IPResolver is KeyValueResolver { - /// @notice Initializes the IP metadata resolver. - /// @param accessController The access controller used for IP authorization. - /// @param ipAssetRegistry The address of the IP record registry. + string public constant override name = IP_RESOLVER_MODULE_KEY; + constructor(address accessController, address ipAssetRegistry) ResolverBase(accessController, ipAssetRegistry) {} - /// @notice Checks whether the resolver interface is supported. - /// @param id The resolver interface identifier. - /// @return Whether the resolver interface is supported. + /// @notice IERC165 interface support. function supportsInterface(bytes4 id) public view virtual override returns (bool) { return super.supportsInterface(id); } - - /// @notice Gets the protocol-wide module identifier for this module. - function name() public pure override(IModule) returns (string memory) { - return IP_RESOLVER_MODULE_KEY; - } } diff --git a/contracts/resolvers/KeyValueResolver.sol b/contracts/resolvers/KeyValueResolver.sol index 8a8a5e09..a60433db 100644 --- a/contracts/resolvers/KeyValueResolver.sol +++ b/contracts/resolvers/KeyValueResolver.sol @@ -14,23 +14,23 @@ abstract contract KeyValueResolver is IKeyValueResolver, ResolverBase { mapping(address => mapping(string => string)) internal _values; /// @notice Sets the string value for a specified key of an IP ID. + /// @dev Enforced to be only callable by users with valid permission to call on behalf of the ipId. /// @param ipId The canonical identifier of the IP asset. - /// @param k The string parameter key to update. - /// @param v The value to set for the specified key. - function setValue(address ipId, string calldata k, string calldata v) external virtual verifyPermission(ipId) { - _values[ipId][k] = v; - emit KeyValueSet(ipId, k, v); + /// @param key The string parameter key to update. + /// @param val The value to set for the specified key. + function setValue(address ipId, string calldata key, string calldata val) external virtual verifyPermission(ipId) { + _values[ipId][key] = val; + emit KeyValueSet(ipId, key, val); } /// @notice Retrieves the string value associated with a key for an IP asset. - /// @param k The string parameter key to query. - function value(address ipId, string calldata k) external view virtual returns (string memory) { - return _values[ipId][k]; + /// @param key The string parameter key to query. + /// @return value The value associated with the specified key. + function value(address ipId, string calldata key) external view virtual returns (string memory) { + return _values[ipId][key]; } - /// @notice Checks whether the resolver interface is supported. - /// @param id The resolver interface identifier. - /// @return Whether the resolver interface is supported. + /// @notice IERC165 interface support. function supportsInterface(bytes4 id) public view virtual override returns (bool) { return id == type(IKeyValueResolver).interfaceId || super.supportsInterface(id); } diff --git a/contracts/resolvers/ResolverBase.sol b/contracts/resolvers/ResolverBase.sol index e39b330b..a6c3fe75 100644 --- a/contracts/resolvers/ResolverBase.sol +++ b/contracts/resolvers/ResolverBase.sol @@ -8,14 +8,9 @@ import { AccessControlled } from "../access/AccessControlled.sol"; /// @notice IP Resolver Base Contract abstract contract ResolverBase is IResolver, BaseModule, AccessControlled { - /// @notice Initializes the base module contract. - /// @param controller The access controller used for IP authorization. - /// @param assetRegistry The address of the IP record registry. - constructor(address controller, address assetRegistry) AccessControlled(controller, assetRegistry) {} + constructor(address accessController, address assetRegistry) AccessControlled(accessController, assetRegistry) {} - /// @notice Checks whether the resolver interface is supported. - /// @param id The resolver interface identifier. - /// @return Whether the resolver interface is supported. + /// @notice IERC165 interface support. function supportsInterface(bytes4 id) public view virtual override(BaseModule, IResolver) returns (bool) { return id == type(IResolver).interfaceId || super.supportsInterface(id); } diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index 5794f99f..dfd9595a 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -605,7 +605,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { // Alice calls on behalf of Dan's vault to send money from the Split Main to Dan's vault, // since the revenue payment was made to Dan's Split Wallet, which got distributed to the vault. - royaltyPolicyLAP.claimFromIpPool({ _account: ipAcct3_splitClone, _withdrawETH: 0, _tokens: tokens }); + royaltyPolicyLAP.claimFromIpPool({ account: ipAcct3_splitClone, withdrawETH: 0, tokens: tokens }); } /*/////////////////////////////////////////////////////////////// diff --git a/test/foundry/mocks/module/MockDisputeModule.sol b/test/foundry/mocks/module/MockDisputeModule.sol index d6f9227f..55fc1043 100644 --- a/test/foundry/mocks/module/MockDisputeModule.sol +++ b/test/foundry/mocks/module/MockDisputeModule.sol @@ -9,17 +9,8 @@ import { ShortStringOps } from "../../../../contracts/utils/ShortStringOps.sol"; contract MockDisputeModule is BaseModule, IDisputeModule { bytes32 public constant IN_DISPUTE = bytes32("IN_DISPUTE"); - struct Dispute { - address targetIpId; - address disputeInitiator; - address arbitrationPolicy; - bytes32 linkToDisputeEvidence; - bytes32 targetTag; - bytes32 currentTag; - } - string public constant override name = "DISPUTE_MODULE"; - uint256 public disputeId; + uint256 public disputeCounter; address public baseArbitrationPolicy; mapping(uint256 disputeId => Dispute dispute) public disputes; @@ -63,7 +54,7 @@ contract MockDisputeModule is BaseModule, IDisputeModule { address arbitrationPolicy = arbitrationPolicies[_targetIpId]; if (!isWhitelistedArbitrationPolicy[arbitrationPolicy]) arbitrationPolicy = baseArbitrationPolicy; - uint256 disputeId_ = ++disputeId; + uint256 disputeId_ = ++disputeCounter; disputes[disputeId_] = Dispute({ targetIpId: _targetIpId, diff --git a/test/foundry/mocks/module/MockLicensingModule.sol b/test/foundry/mocks/module/MockLicensingModule.sol index e5d033d5..4113f7cf 100644 --- a/test/foundry/mocks/module/MockLicensingModule.sol +++ b/test/foundry/mocks/module/MockLicensingModule.sol @@ -6,6 +6,7 @@ import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableS import { ILicensingModule } from "../../../../contracts/interfaces/modules/licensing/ILicensingModule.sol"; import { ILicenseRegistry } from "../../../../contracts/interfaces/registries/ILicenseRegistry.sol"; +import { IDisputeModule } from "../../../../contracts/interfaces/modules/dispute/IDisputeModule.sol"; import { DataUniqueness } from "../../../../contracts/lib/DataUniqueness.sol"; import { Licensing } from "../../../../contracts/lib/Licensing.sol"; import { RoyaltyModule } from "../../../../contracts/modules/royalty-module/RoyaltyModule.sol"; @@ -15,15 +16,9 @@ contract MockLicensingModule is BaseModule, ILicensingModule { using EnumerableSet for EnumerableSet.UintSet; using EnumerableSet for EnumerableSet.AddressSet; - struct PolicySetup { - uint256 index; - bool isSet; - bool active; - bool isInherited; - } - RoyaltyModule public immutable ROYALTY_MODULE; ILicenseRegistry public immutable LICENSE_REGISTRY; + IDisputeModule public immutable DISPUTE_MODULE; string public constant name = "LICENSING_MODULE"; mapping(address framework => bool registered) private _registeredFrameworkManagers; diff --git a/test/foundry/mocks/registry/MockLicenseRegistry.sol b/test/foundry/mocks/registry/MockLicenseRegistry.sol index 20f9b5be..15834d76 100644 --- a/test/foundry/mocks/registry/MockLicenseRegistry.sol +++ b/test/foundry/mocks/registry/MockLicenseRegistry.sol @@ -7,9 +7,11 @@ import { ILicensingModule } from "../../../../contracts/interfaces/modules/licen import { DataUniqueness } from "../../../../contracts/lib/DataUniqueness.sol"; import { Licensing } from "../../../../contracts/lib/Licensing.sol"; import { ILicenseRegistry } from "../../../../contracts/interfaces/registries/ILicenseRegistry.sol"; +import { IDisputeModule } from "../../../../contracts/interfaces/modules/dispute/IDisputeModule.sol"; contract MockLicenseRegistry is ERC1155, ILicenseRegistry { - ILicensingModule private _licensingModule; + ILicensingModule public LICENSING_MODULE; + IDisputeModule public DISPUTE_MODULE; mapping(bytes32 licenseHash => uint256 ids) private _hashedLicenses; mapping(uint256 licenseIds => Licensing.License licenseData) private _licenses; uint256 private _mintedLicenses; @@ -17,23 +19,19 @@ contract MockLicenseRegistry is ERC1155, ILicenseRegistry { constructor() ERC1155("") {} function setLicensingModule(address newLicensingModule) external { - _licensingModule = ILicensingModule(newLicensingModule); - } - - function licensingModule() external view returns (address) { - return address(_licensingModule); + LICENSING_MODULE = ILicensingModule(newLicensingModule); } function mintLicense( uint256 policyId, - address licensorIpId, + address licensorIpId_, bool transferable, uint256 amount, address receiver ) external returns (uint256 licenseId) { Licensing.License memory licenseData = Licensing.License({ policyId: policyId, - licensorIpId: licensorIpId, + licensorIpId: licensorIpId_, transferable: transferable }); bool isNew; diff --git a/test/foundry/modules/dispute/DisputeModule.t.sol b/test/foundry/modules/dispute/DisputeModule.t.sol index 2a6f50fa..4c81c2e1 100644 --- a/test/foundry/modules/dispute/DisputeModule.t.sol +++ b/test/foundry/modules/dispute/DisputeModule.t.sol @@ -257,7 +257,7 @@ contract DisputeModuleTest is BaseTest { vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP2), ARBITRATION_PRICE); - uint256 disputeIdBefore = disputeModule.disputeId(); + uint256 disputeIdBefore = disputeModule.disputeCounter(); uint256 ipAccount1USDCBalanceBefore = IERC20(USDC).balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceBefore = IERC20(USDC).balanceOf(address(arbitrationPolicySP2)); @@ -274,7 +274,7 @@ contract DisputeModuleTest is BaseTest { disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); - uint256 disputeIdAfter = disputeModule.disputeId(); + uint256 disputeIdAfter = disputeModule.disputeCounter(); uint256 ipAccount1USDCBalanceAfter = IERC20(USDC).balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceAfter = IERC20(USDC).balanceOf(address(arbitrationPolicySP2)); @@ -302,7 +302,7 @@ contract DisputeModuleTest is BaseTest { vm.startPrank(ipAccount1); IERC20(USDC).approve(address(arbitrationPolicySP), ARBITRATION_PRICE); - uint256 disputeIdBefore = disputeModule.disputeId(); + uint256 disputeIdBefore = disputeModule.disputeCounter(); uint256 ipAccount1USDCBalanceBefore = USDC.balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceBefore = USDC.balanceOf(address(arbitrationPolicySP)); @@ -319,7 +319,7 @@ contract DisputeModuleTest is BaseTest { disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", ""); - uint256 disputeIdAfter = disputeModule.disputeId(); + uint256 disputeIdAfter = disputeModule.disputeCounter(); uint256 ipAccount1USDCBalanceAfter = USDC.balanceOf(ipAccount1); uint256 arbitrationPolicySPUSDCBalanceAfter = USDC.balanceOf(address(arbitrationPolicySP)); From bf73fb475e79b2250fca2a1eff320d33c4480855 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sat, 17 Feb 2024 01:34:18 -0600 Subject: [PATCH 2/4] refactor: rebase --- contracts/AccessController.sol | 10 +++--- contracts/IPAccountImpl.sol | 35 +++++++++---------- contracts/interfaces/IIPAccount.sol | 3 -- .../registries/IIPAccountRegistry.sol | 3 -- 4 files changed, 22 insertions(+), 29 deletions(-) diff --git a/contracts/AccessController.sol b/contracts/AccessController.sol index d0b6ebe3..26f0f124 100644 --- a/contracts/AccessController.sol +++ b/contracts/AccessController.sol @@ -33,8 +33,8 @@ contract AccessController is IAccessController, Governable { address public IP_ACCOUNT_REGISTRY; address public MODULE_REGISTRY; - /// @dev Tracks the permission granted to an encoded callpath, where the - /// encoded callpath = keccak256(abi.encodePacked(ipAccount, signer, to, func)) + /// @dev Tracks the permission granted to an encoded permission path, where the + /// encoded permission path = keccak256(abi.encodePacked(ipAccount, signer, to, func)) mapping(bytes32 => uint8) internal encodedPermissions; constructor(address governance) Governable(governance) {} @@ -84,8 +84,8 @@ contract AccessController is IAccessController, Governable { if (permission > 2) { revert Errors.AccessController__PermissionIsNotValid(); } - _setPermission(address(0), signer_, to_, func_, permission_); - emit PermissionSet(address(0), signer_, to_, func_, permission_); + _setPermission(address(0), signer, to, func, permission); + emit PermissionSet(address(0), address(0), signer, to, func, permission); } /// @notice Sets the permission for a specific function call @@ -128,7 +128,7 @@ contract AccessController is IAccessController, Governable { } _setPermission(ipAccount, signer, to, func, permission); - emit PermissionSet(ipAccount_, signer_, to_, func_, permission_); + emit PermissionSet(IIPAccount(payable(ipAccount)).owner(), ipAccount, signer, to, func, permission); } /// @notice Checks the permission level for a specific function call. Reverts if permission is not granted. diff --git a/contracts/IPAccountImpl.sol b/contracts/IPAccountImpl.sol index d9fcada6..ae5b14a7 100644 --- a/contracts/IPAccountImpl.sol +++ b/contracts/IPAccountImpl.sol @@ -17,8 +17,7 @@ import { Errors } from "./lib/Errors.sol"; /// @title IPAccountImpl /// @notice The Story Protocol's implementation of the IPAccount. contract IPAccountImpl is IERC165, IIPAccount { - /// @notice Returns the address of the protocol-wide access controller. - address public ACCESS_CONTROLLER; + address public immutable accessController; /// @notice Returns the IPAccount's internal nonce for transaction ordering. uint256 public state; @@ -30,19 +29,21 @@ contract IPAccountImpl is IERC165, IIPAccount { /// in the implementation code's storage. /// This means that each cloned IPAccount will inherently use the same AccessController /// without the need for individual configuration. - /// @param accessController The address of the AccessController contract to be used for permission checks - constructor(address accessController) { - if (accessController == address(0)) revert Errors.IPAccount__InvalidAccessController(); - ACCESS_CONTROLLER = accessController; + /// @param accessController_ The address of the AccessController contract to be used for permission checks + constructor(address accessController_) { + if (accessController_ == address(0)) revert Errors.IPAccount__InvalidAccessController(); + accessController = accessController_; } - /// @notice IERC165 interface support. - function supportsInterface(bytes4 interfaceId_) external pure returns (bool) { - return (interfaceId_ == type(IIPAccount).interfaceId || - interfaceId_ == type(IERC6551Account).interfaceId || - interfaceId_ == type(IERC1155Receiver).interfaceId || - interfaceId_ == type(IERC721Receiver).interfaceId || - interfaceId_ == type(IERC165).interfaceId); + /// @notice Checks if the contract supports a specific interface + /// @param interfaceId The interface identifier, as specified in ERC-165 + /// @return bool is true if the contract supports the interface, false otherwise + function supportsInterface(bytes4 interfaceId) external pure returns (bool) { + return (interfaceId == type(IIPAccount).interfaceId || + interfaceId == type(IERC6551Account).interfaceId || + interfaceId == type(IERC1155Receiver).interfaceId || + interfaceId == type(IERC721Receiver).interfaceId || + interfaceId == type(IERC165).interfaceId); } /// @notice Returns the identifier of the non-fungible token which owns the account @@ -80,7 +81,7 @@ contract IPAccountImpl is IERC165, IIPAccount { } /// @notice Returns the owner of the IP Account. - /// @return owner The address of the owner. + /// @return The address of the owner. function owner() public view returns (address) { (uint256 chainId, address contractAddress, uint256 tokenId) = token(); if (chainId != block.chainid) return address(0); @@ -91,18 +92,17 @@ contract IPAccountImpl is IERC165, IIPAccount { /// @param signer The signer to check /// @param to The recipient of the transaction /// @param data The calldata to check against - /// @return isValid True if the signer is valid, false otherwise + /// @return bool is true if the signer is valid, false otherwise function _isValidSigner(address signer, address to, bytes calldata data) internal view returns (bool) { if (data.length > 0 && data.length < 4) { revert Errors.IPAccount__InvalidCalldata(); } - require(data.length == 0 || data.length >= 4, "Invalid calldata"); bytes4 selector = bytes4(0); if (data.length >= 4) { selector = bytes4(data[:4]); } // the check will revert if permission is denied - IAccessController(ACCESS_CONTROLLER).checkPermission(address(this), signer, to, selector); + IAccessController(accessController).checkPermission(address(this), signer, to, selector); return true; } @@ -113,7 +113,6 @@ contract IPAccountImpl is IERC165, IIPAccount { /// @param signer The signer of the transaction. /// @param deadline The deadline of the transaction signature. /// @param signature The signature of the transaction, EIP-712 encoded. - /// @return result The return data from the transaction. function executeWithSig( address to, uint256 value, diff --git a/contracts/interfaces/IIPAccount.sol b/contracts/interfaces/IIPAccount.sol index fa008edb..27a609e3 100644 --- a/contracts/interfaces/IIPAccount.sol +++ b/contracts/interfaces/IIPAccount.sol @@ -40,9 +40,6 @@ interface IIPAccount is IERC6551Account, IERC721Receiver, IERC1155Receiver { bytes signature ); - /// @notice Returns the address of the protocol-wide access controller. - function ACCESS_CONTROLLER() external view returns (address); - /// @notice Returns the IPAccount's internal nonce for transaction ordering. function state() external view returns (uint256); diff --git a/contracts/interfaces/registries/IIPAccountRegistry.sol b/contracts/interfaces/registries/IIPAccountRegistry.sol index e17c6612..a3499230 100644 --- a/contracts/interfaces/registries/IIPAccountRegistry.sol +++ b/contracts/interfaces/registries/IIPAccountRegistry.sol @@ -28,9 +28,6 @@ interface IIPAccountRegistry { /// @notice Returns the public ERC6551 registry address function ERC6551_PUBLIC_REGISTRY() external view returns (address); - /// @notice Returns the access controller address - function ACCESS_CONTROLLER() external view returns (address); - /// @notice Deploys an IPAccount contract with the IPAccount implementation and returns the address of the new IP /// @dev The IPAccount deployment deltegates to public ERC6551 Registry /// @param chainId The chain ID where the IP Account will be created From 117247c5d37e3a5eef21de627a39c7bd8cc765b2 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sat, 17 Feb 2024 01:55:50 -0600 Subject: [PATCH 3/4] fix: variable name typos --- .../modules/royalty-module/policies/RoyaltyPolicyLAP.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol b/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol index d7289ec8..9167f93b 100644 --- a/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol +++ b/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol @@ -124,9 +124,9 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent // called _initPolicy() when linking to their parents with onLinkToParents() call. address[] memory rootParents = new address[](0); bytes[] memory rootParentRoyalties = new bytes[](0); - _initPolicy(_ipId, rootParents, rootParentRoyalties, _externalData); + _initPolicy(ipId, rootParents, rootParentRoyalties, externalData); } else { - InitParams memory params = abi.decode(_externalData, (InitParams)); + InitParams memory params = abi.decode(externalData, (InitParams)); // If the policy is already initialized and an ipId has the maximum number of ancestors // it can not have any derivative and therefore is not allowed to mint any license if (params.targetAncestors.length >= MAX_ANCESTORS) revert Errors.RoyaltyPolicyLAP__LastPositionNotAbleToMintLicense(); @@ -134,7 +134,7 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent // and that the targetAncestors passed in by the user matches the record stored in state on policy initialization if ( keccak256(abi.encodePacked(params.targetAncestors, params.targetRoyaltyAmount)) != - royaltyData[_ipId].ancestorsHash + royaltyData[ipId].ancestorsHash ) revert Errors.RoyaltyPolicyLAP__InvalidAncestorsHash(); } } From 667bc9630a5d3c7db6404f6d487893803d6f045e Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sat, 17 Feb 2024 01:59:45 -0600 Subject: [PATCH 4/4] style: lint --- .../royalty-module/policies/RoyaltyPolicyLAP.sol | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol b/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol index 9167f93b..5261eca1 100644 --- a/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol +++ b/contracts/modules/royalty-module/policies/RoyaltyPolicyLAP.sol @@ -118,9 +118,10 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent revert Errors.RoyaltyPolicyLAP__AboveRoyaltyStackLimit(); if (data.splitClone == address(0)) { - // If the policy is already initialized, it means that the ipId setup is already done. If not, it means that - // the license for this royalty policy is being minted for the first time parentIpIds are zero given that only - // roots can call _initPolicy() for the first time in the function onLicenseMinting() while derivatives already + // If the policy is already initialized, it means that the ipId setup is already done. If not, it means + // that the license for this royalty policy is being minted for the first time parentIpIds are zero given + // that only roots can call _initPolicy() for the first time in the function onLicenseMinting() while + // derivatives already // called _initPolicy() when linking to their parents with onLinkToParents() call. address[] memory rootParents = new address[](0); bytes[] memory rootParentRoyalties = new bytes[](0); @@ -129,9 +130,12 @@ contract RoyaltyPolicyLAP is IRoyaltyPolicyLAP, Governable, ERC1155Holder, Reent InitParams memory params = abi.decode(externalData, (InitParams)); // If the policy is already initialized and an ipId has the maximum number of ancestors // it can not have any derivative and therefore is not allowed to mint any license - if (params.targetAncestors.length >= MAX_ANCESTORS) revert Errors.RoyaltyPolicyLAP__LastPositionNotAbleToMintLicense(); + if (params.targetAncestors.length >= MAX_ANCESTORS) + revert Errors.RoyaltyPolicyLAP__LastPositionNotAbleToMintLicense(); + // the check below ensures that the ancestors hash is the same as the one stored in the royalty data - // and that the targetAncestors passed in by the user matches the record stored in state on policy initialization + // and that the targetAncestors passed in by the user matches the record stored in state on policy + // initialization if ( keccak256(abi.encodePacked(params.targetAncestors, params.targetRoyaltyAmount)) != royaltyData[ipId].ancestorsHash