diff --git a/contracts/IPAccountImpl.sol b/contracts/IPAccountImpl.sol index e96f90bf..88d4c8f6 100644 --- a/contracts/IPAccountImpl.sol +++ b/contracts/IPAccountImpl.sol @@ -7,7 +7,7 @@ import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Rec import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import { SignatureChecker } from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import { MessageHashUtils } from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; -import { IERC6551Account } from "@erc6551/interfaces/IERC6551Account.sol"; +import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol"; import { IAccessController } from "./interfaces/IAccessController.sol"; import { IIPAccount } from "./interfaces/IIPAccount.sol"; diff --git a/contracts/interfaces/IIPAccount.sol b/contracts/interfaces/IIPAccount.sol index f4e9491a..f10304cc 100644 --- a/contracts/interfaces/IIPAccount.sol +++ b/contracts/interfaces/IIPAccount.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.23; import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import { IERC1155Receiver } from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; -import { IERC6551Account } from "@erc6551/interfaces/IERC6551Account.sol"; +import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol"; /// @title IIPAccount /// @dev IPAccount is a token-bound account that adopts the EIP-6551 standard. diff --git a/contracts/interfaces/registries/IIPAssetRegistry.sol b/contracts/interfaces/registries/IIPAssetRegistry.sol index 5d377202..a8b9ee42 100644 --- a/contracts/interfaces/registries/IIPAssetRegistry.sol +++ b/contracts/interfaces/registries/IIPAssetRegistry.sol @@ -15,6 +15,7 @@ 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, @@ -30,6 +31,12 @@ interface IIPAssetRegistry is IIPAccountRegistry { /// @param resolver The address of the new resolver bound to the IP. event IPResolverSet(address ipId, address resolver); + /// @notice Emits when an operator is approved for IP registration for an NFT owner. + /// @param owner The address of the IP owner. + /// @param operator The address of the operator the owneris authorizing. + /// @param approved Whether or not to approve that operator for registration. + event ApprovalForAll(address indexed owner, address indexed operator, bool approved); + /// @notice Emits when metadata is set for an IP asset. /// @param ipId The canonical identifier of the specified IP. /// @param metadataProvider Address of the metadata provider associated with the IP. @@ -47,7 +54,6 @@ interface IIPAssetRegistry is IIPAccountRegistry { function setMetadataProvider(address metadataProvider) external; /// @notice Registers an NFT as IP, creating a corresponding IP record. - /// @dev This is only callable by an authorized registration module. /// @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. @@ -76,6 +82,12 @@ interface IIPAssetRegistry is IIPAccountRegistry { /// @return The address of the associated IP account. 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. diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 0f93daf1..0e879e2a 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -48,6 +48,9 @@ library Errors { /// @notice The IP asset has not yet been registered. error IPAssetRegistry__NotYetRegistered(); + /// @notice The IP asset registrant is not authorized. + error IPAssetRegistry__RegistrantUnauthorized(); + /// @notice The specified IP resolver is not valid. error IPAssetRegistry__ResolverInvalid(); diff --git a/contracts/lib/modules/Module.sol b/contracts/lib/modules/Module.sol index ddd3b61f..0b898e69 100644 --- a/contracts/lib/modules/Module.sol +++ b/contracts/lib/modules/Module.sol @@ -8,11 +8,12 @@ string constant IP_RESOLVER_MODULE_KEY = "IP_RESOLVER_MODULE"; // String values for core protocol modules. string constant REGISTRATION_MODULE_KEY = "REGISTRATION_MODULE"; +// String values for core protocol modules. +string constant LICENSING_MODULE_KEY = "LICENSING_MODULE"; + // String values for core protocol modules. string constant DISPUTE_MODULE_KEY = "DISPUTE_MODULE"; string constant TAGGING_MODULE_KEY = "TAGGING_MODULE"; string constant ROYALTY_MODULE_KEY = "ROYALTY_MODULE"; - -string constant LICENSING_MODULE_KEY = "LICENSING_MODULE"; diff --git a/contracts/lib/registries/IPAccountChecker.sol b/contracts/lib/registries/IPAccountChecker.sol index d7639c25..09fb450b 100644 --- a/contracts/lib/registries/IPAccountChecker.sol +++ b/contracts/lib/registries/IPAccountChecker.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.23; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import { IERC6551Account } from "@erc6551/interfaces/IERC6551Account.sol"; +import { IERC6551Account } from "erc6551/interfaces/IERC6551Account.sol"; import { IIPAccountRegistry } from "../../interfaces/registries/IIPAccountRegistry.sol"; import { IIPAccount } from "../..//interfaces/IIPAccount.sol"; diff --git a/contracts/registries/IPAccountRegistry.sol b/contracts/registries/IPAccountRegistry.sol index 3f538ada..44007b41 100644 --- a/contracts/registries/IPAccountRegistry.sol +++ b/contracts/registries/IPAccountRegistry.sol @@ -2,7 +2,7 @@ // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.23; -import { IERC6551Registry } from "@erc6551/interfaces/IERC6551Registry.sol"; +import { IERC6551Registry } from "erc6551/interfaces/IERC6551Registry.sol"; import { IIPAccountRegistry } from "../interfaces/registries/IIPAccountRegistry.sol"; import { Errors } from "../lib/Errors.sol"; diff --git a/contracts/registries/IPAssetRegistry.sol b/contracts/registries/IPAssetRegistry.sol index 08022292..4c8ef7c5 100644 --- a/contracts/registries/IPAssetRegistry.sol +++ b/contracts/registries/IPAssetRegistry.sol @@ -2,6 +2,7 @@ // See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf pragma solidity ^0.8.23; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; import { IIPAccount } from "../interfaces/IIPAccount.sol"; @@ -11,6 +12,10 @@ import { IMetadataProviderMigratable } from "../interfaces/registries/metadata/I import { MetadataProviderV1 } from "../registries/metadata/MetadataProviderV1.sol"; import { Errors } from "../lib/Errors.sol"; import { IResolver } from "../interfaces/resolvers/IResolver.sol"; +import { LICENSING_MODULE_KEY } from "contracts/lib/modules/Module.sol"; +import { IModuleRegistry } from "../interfaces/registries/IModuleRegistry.sol"; +import { ILicensingModule } from "../interfaces/modules/licensing/ILicensingModule.sol"; +import { IIPAssetRegistry } from "../interfaces/registries/IIPAssetRegistry.sol"; /// @title IP Asset Registry /// @notice This contract acts as the source of truth for all IP registered in @@ -26,15 +31,23 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { 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; + /// @notice Tracks the total number of IP assets in existence. uint256 public totalSupply = 0; /// @notice Protocol governance administrator of the IP record registry. address public owner; + /// @notice Checks whether an operator is approved to register on behalf of an IP owner. + mapping(address owner => mapping(address operator => bool)) public isApprovedForAll; + /// @dev Maps an IP, identified by its IP ID, to an IP record. mapping(address => Record) internal _records; @@ -53,21 +66,37 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { /// @param erc6551Registry The address of the ERC6551 registry. /// @param accessController The address of the access controller. /// @param ipAccountImpl The address of the IP account implementation. + /// @param moduleRegistry The address of the module registry. + /// TODO: Utilize module registry for fetching different modules. constructor( address accessController, address erc6551Registry, - address ipAccountImpl + address ipAccountImpl, + address moduleRegistry ) IPAccountRegistry(erc6551Registry, accessController, ipAccountImpl) { // TODO: Migrate this to a parameterized governance owner address. + // TODO: Replace with OZ's 2StepOwnable owner = msg.sender; + MODULE_REGISTRY = IModuleRegistry(moduleRegistry); _metadataProvider = IMetadataProviderMigratable(new MetadataProviderV1(address(this))); } - /// @notice Registers an NFT as an IP, creating a corresponding IP asset. + /// @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 Registers an NFT as an IP asset. /// @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. function register( uint256 chainId, address tokenContract, @@ -76,17 +105,29 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { bool createAccount, bytes calldata data ) external returns (address id) { - id = ipId(chainId, tokenContract, tokenId); - if (_records[id].resolver != address(0)) { - revert Errors.IPAssetRegistry__AlreadyRegistered(); - } + id = _register(new uint256[](0), 0, chainId, tokenContract, tokenId, resolverAddr, createAccount, data); + emit IPRegistered(id, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), data); + } - if (id.code.length == 0 && createAccount && id != registerIpAccount(chainId, tokenContract, tokenId)) { - revert Errors.IPAssetRegistry__InvalidAccount(); - } - _setResolver(id, resolverAddr); - _setMetadata(id, _metadataProvider, data); - totalSupply++; + /// @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 minRoyalty The minimum royalty to enforce if applicable, else 0. + /// @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 createAccount Whether to create an IP account when registering. + /// @param data Canonical metadata to associate with the IP. + function register( + uint256[] calldata licenseIds, + uint32 minRoyalty, + uint256 chainId, + address tokenContract, + uint256 tokenId, + address resolverAddr, + bool createAccount, + bytes calldata data + ) external returns (address id) { + id = _register(licenseIds, minRoyalty, chainId, tokenContract, tokenId, resolverAddr, createAccount, data); emit IPRegistered(id, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), data); } @@ -150,8 +191,8 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { /// @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 { - // Metadata is set on registration and immutable thereafter, with new fields - // only added during a migration to new protocol-approved metadata provider. + // Canonical metadata is set on registration and immutable thereafter, with new + // fields only added during a migration to new protocol-approved metadata provider. if (address(_records[id].metadataProvider) != msg.sender) { revert Errors.IPAssetRegistry__Unauthorized(); } @@ -166,12 +207,55 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry { revert Errors.IPAssetRegistry__NotYetRegistered(); } // TODO: Update authorization logic to use the access controller. - if (msg.sender != IIPAccount(payable(id)).owner()) { + address owner = IIPAccount(payable(id)).owner(); + if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) { revert Errors.IPAssetRegistry__Unauthorized(); } _setResolver(id, resolverAddr); } + /// @dev Registers an NFT as an IP. + /// @param licenseIds IP asset licenses used to derive the new IP asset, if any. + /// @param minRoyalty The minimum royalty to enforce if applicable, else 0. + /// @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. + function _register( + uint256[] memory licenseIds, + uint32 minRoyalty, + uint256 chainId, + address tokenContract, + uint256 tokenId, + address resolverAddr, + bool createAccount, + bytes calldata data + ) internal returns (address id) { + id = ipId(chainId, tokenContract, tokenId); + if (_records[id].resolver != address(0)) { + revert Errors.IPAssetRegistry__AlreadyRegistered(); + } + + address owner = IERC721(tokenContract).ownerOf(tokenId); + if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) { + revert Errors.IPAssetRegistry__RegistrantUnauthorized(); + } + + if (id.code.length == 0 && createAccount && id != registerIpAccount(chainId, tokenContract, tokenId)) { + revert Errors.IPAssetRegistry__InvalidAccount(); + } + _setResolver(id, resolverAddr); + _setMetadata(id, _metadataProvider, data); + totalSupply++; + + if (licenseIds.length != 0) { + ILicensingModule licensingModule = ILicensingModule(MODULE_REGISTRY.getModule(LICENSING_MODULE_KEY)); + licensingModule.linkIpToParents(licenseIds, id, minRoyalty); + } + } + /// @dev Sets the resolver for the specified IP. /// @param id The canonical ID of the IP. /// @param resolverAddr The address of the resolver being set. diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index 7c6fb683..933a2fe6 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -164,7 +164,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { contractKey = "IPAssetRegistry"; _predeploy(contractKey); - ipAssetRegistry = new IPAssetRegistry(address(accessController), ERC6551_REGISTRY, address(ipAccountImpl)); + ipAssetRegistry = new IPAssetRegistry(address(accessController), ERC6551_REGISTRY, address(ipAccountImpl), address(moduleRegistry)); _postdeploy(contractKey, address(ipAssetRegistry)); contractKey = "MetadataProviderV1"; @@ -433,6 +433,20 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler { "https://example.com/test-ip" ); + accessController.setGlobalPermission( + address(ipAssetRegistry), + address(licensingModule), + bytes4(0), + 1 + ); + + accessController.setGlobalPermission( + address(registrationModule), + address(licenseRegistry), + bytes4(0), // wildcard + 1 // AccessPermission.ALLOW + ); + // wildcard allow IIPAccount(payable(ipAcct[1])).execute( address(accessController), diff --git a/test/foundry/IPAssetRegistry.t.sol b/test/foundry/IPAssetRegistry.t.sol index b908f59e..ababb84a 100644 --- a/test/foundry/IPAssetRegistry.t.sol +++ b/test/foundry/IPAssetRegistry.t.sol @@ -2,15 +2,20 @@ pragma solidity ^0.8.23; import { BaseTest } from "./utils/BaseTest.sol"; +import { IModuleRegistry } from "contracts/interfaces/registries/IModuleRegistry.sol"; +import { Governance } from "contracts/governance/Governance.sol"; import { IIPAssetRegistry } from "contracts/interfaces/registries/IIPAssetRegistry.sol"; import { IPAccountChecker } from "contracts/lib/registries/IPAccountChecker.sol"; +import { LICENSING_MODULE_KEY } from "contracts/lib/modules/Module.sol"; import { IP } from "contracts/lib/IP.sol"; +import { MockLicensingModule } from "test/foundry/mocks/licensing/MockLicensingModule.sol"; import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; import { ERC6551Registry } from "@erc6551/ERC6551Registry.sol"; import { IPAssetRegistry } from "contracts/registries/IPAssetRegistry.sol"; import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; import { MockAccessController } from "test/foundry/mocks/MockAccessController.sol"; import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; +import { ModuleRegistry } from "contracts/registries/ModuleRegistry.sol"; import { Errors } from "contracts/lib/Errors.sol"; /// @title IP Asset Registry Testing Contract @@ -26,12 +31,18 @@ contract IPAssetRegistryTest is BaseTest { address public resolver = vm.addr(0x6969); address public resolver2 = vm.addr(0x6978); + /// @notice Test governance contract. + Governance public governance; + /// @notice The IP asset registry SUT. IPAssetRegistry public registry; /// @notice The IP account registry used for account creation. IPAccountRegistry public ipAccountRegistry; + /// @notice Protocol-wide module registry. + IModuleRegistry public moduleRegistry; + /// @notice Mock NFT address for IP registration testing. address public tokenAddress; @@ -51,10 +62,14 @@ contract IPAssetRegistryTest is BaseTest { function setUp() public virtual override { BaseTest.setUp(); address accessController = address(new MockAccessController()); + governance = new Governance(address(this)); + moduleRegistry = new ModuleRegistry(address(governance)); + MockLicensingModule licensingModule = new MockLicensingModule(); + moduleRegistry.registerModule(LICENSING_MODULE_KEY, address(licensingModule)); erc6551Registry = address(new ERC6551Registry()); ipAccountImpl = address(new IPAccountImpl()); ipAccountRegistry = new IPAccountRegistry(erc6551Registry, accessController, ipAccountImpl); - registry = new IPAssetRegistry(accessController, erc6551Registry, ipAccountImpl); + registry = new IPAssetRegistry(accessController, erc6551Registry, ipAccountImpl, address(moduleRegistry)); MockERC721 erc721 = new MockERC721("MockERC721"); tokenAddress = address(erc721); tokenId = erc721.mintId(alice, 99); @@ -71,7 +86,16 @@ contract IPAssetRegistryTest is BaseTest { ); } - /// @notice Tests registration of IP assets. + /// @notice Tests operator approvals. + function test_IPAssetRegistry_SetApprovalForAll() public { + vm.prank(alice); + registry.setApprovalForAll(bob, true); + bytes memory metadata = _generateMetadata(); + vm.prank(bob); + registry.register(block.chainid, tokenAddress, tokenId, resolver, true, metadata); + } + + /// @notice Tests registration of IP assets without licenses. function test_IPAssetRegistry_Register() public { uint256 totalSupply = registry.totalSupply(); @@ -94,6 +118,43 @@ contract IPAssetRegistryTest is BaseTest { address(registry.metadataProvider()), metadata ); + vm.prank(alice); + registry.register(block.chainid, tokenAddress, tokenId, resolver, true, metadata); + + /// Ensures IP asset post-registration conditions are met. + assertEq(registry.resolver(ipId), resolver); + assertEq(totalSupply + 1, registry.totalSupply()); + assertTrue(registry.isRegistered(ipId)); + assertTrue(IPAccountChecker.isRegistered(ipAccountRegistry, block.chainid, tokenAddress, tokenId)); + } + + /// @notice Tests registration of IP assets with licenses. + function test_IPAssetRegistry_RegisterWithLicenses() public { + uint256 totalSupply = registry.totalSupply(); + + // Ensure unregistered IP preconditions are satisfied. + assertEq(registry.resolver(ipId), address(0)); + assertTrue(!registry.isRegistered(ipId)); + assertTrue(!IPAccountChecker.isRegistered(ipAccountRegistry, block.chainid, tokenAddress, tokenId)); + bytes memory metadata = _generateMetadata(); + uint256[] memory licenses = new uint256[](2); + licenses[0] = 1; + licenses[1] = 2; + + // Ensure all expected events are emitted. + vm.expectEmit(true, true, true, true); + emit IIPAssetRegistry.IPResolverSet(ipId, resolver); + vm.expectEmit(true, true, true, true); + emit IIPAssetRegistry.IPRegistered( + ipId, + block.chainid, + tokenAddress, + tokenId, + resolver, + address(registry.metadataProvider()), + metadata + ); + vm.prank(alice); registry.register(block.chainid, tokenAddress, tokenId, resolver, true, metadata); /// Ensures IP asset post-registration conditions are met. @@ -125,6 +186,7 @@ contract IPAssetRegistryTest is BaseTest { address(registry.metadataProvider()), metadata ); + vm.prank(alice); registry.register(block.chainid, tokenAddress, tokenId, resolver, false, metadata); /// Ensures IP asset post-registration conditions are met. @@ -139,12 +201,14 @@ contract IPAssetRegistryTest is BaseTest { registry.registerIpAccount(block.chainid, tokenAddress, tokenId); assertTrue(IPAccountChecker.isRegistered(ipAccountRegistry, block.chainid, tokenAddress, tokenId)); bytes memory metadata = _generateMetadata(); + vm.prank(alice); registry.register(block.chainid, tokenAddress, tokenId, resolver, true, metadata); } /// @notice Tests registration of IP reverts when an IP has already been registered. function test_IPAssetRegistry_Register_Reverts_ExistingRegistration() public { bytes memory metadata = _generateMetadata(); + vm.prank(alice); registry.register(block.chainid, tokenAddress, tokenId, resolver, false, metadata); vm.expectRevert(Errors.IPAssetRegistry__AlreadyRegistered.selector); registry.register(block.chainid, tokenAddress, tokenId, resolver, false, metadata); @@ -152,6 +216,7 @@ contract IPAssetRegistryTest is BaseTest { /// @notice Tests IP resolver setting works. function test_IPAssetRegistry_SetResolver() public { + vm.prank(alice); registry.register(block.chainid, tokenAddress, tokenId, resolver, true, _generateMetadata()); vm.expectEmit(true, true, true, true); diff --git a/test/foundry/integration/BaseIntegration.sol b/test/foundry/integration/BaseIntegration.sol index da66e9f8..9ec82595 100644 --- a/test/foundry/integration/BaseIntegration.sol +++ b/test/foundry/integration/BaseIntegration.sol @@ -97,6 +97,7 @@ contract BaseIntegration is Test { _deployContracts(); _configDeployedContracts(); _mintMockAssets(); + _approveRegistration(); _configAccessControl(); } @@ -121,7 +122,8 @@ contract BaseIntegration is Test { ipAssetRegistry = new IPAssetRegistry( address(accessController), address(erc6551Registry), - address(ipAccountImpl) + address(ipAccountImpl), + address(moduleRegistry) ); licenseRegistry = new LicenseRegistry(); licensingModule = new LicensingModule( @@ -192,6 +194,17 @@ contract BaseIntegration is Test { USDC = new MockUSDC(); } + function _approveRegistration() internal { + vm.prank(u.alice); + ipAssetRegistry.setApprovalForAll(address(registrationModule), true); + vm.prank(u.bob); + ipAssetRegistry.setApprovalForAll(address(registrationModule), true); + vm.prank(u.carl); + ipAssetRegistry.setApprovalForAll(address(registrationModule), true); + vm.prank(u.dan); + ipAssetRegistry.setApprovalForAll(address(registrationModule), true); + } + function _mintMockAssets() internal { erc20.mint(u.alice, 1000 * 10 ** erc20.decimals()); erc20.mint(u.bob, 1000 * 10 ** erc20.decimals()); diff --git a/test/foundry/mocks/licensing/MockLicensingModule.sol b/test/foundry/mocks/licensing/MockLicensingModule.sol new file mode 100644 index 00000000..8f43a6f0 --- /dev/null +++ b/test/foundry/mocks/licensing/MockLicensingModule.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.23; + +import { Licensing } from "contracts/lib/Licensing.sol"; +import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol"; + +/// @title Mock Licensing Module +contract MockLicensingModule is ILicensingModule { + function licenseRegistry() external view returns (address) {} + + function registerPolicyFrameworkManager(address manager) external {} + + function registerPolicy(bool isLicenseTransferable, bytes memory data) external returns (uint256 policyId) {} + + function addPolicyToIp(address ipId, uint256 polId) external returns (uint256 indexOnIpId) {} + + function mintLicense( + uint256 policyId, + address licensorIpId, + uint256 amount, + address receiver + ) external returns (uint256 licenseId) {} + + function linkIpToParents(uint256[] calldata licenseIds, address childIpId, uint32 minRoyalty) external {} + + function isFrameworkRegistered(address framework) external view returns (bool) {} + + function totalPolicies() external view returns (uint256) {} + + function policy(uint256 policyId) external view returns (Licensing.Policy memory pol) {} + + function isPolicyDefined(uint256 policyId) external view returns (bool) {} + + function policyIdsForIp(bool isInherited, address ipId) external view returns (uint256[] memory policyIds) {} + + function totalPoliciesForIp(bool isInherited, address ipId) external view returns (uint256) {} + + function isPolicyIdSetForIp(bool isInherited, address ipId, uint256 policyId) external view returns (bool) {} + + function policyIdForIpAtIndex( + bool isInherited, + address ipId, + uint256 index + ) external view returns (uint256 policyId) {} + + function policyForIpAtIndex( + bool isInherited, + address ipId, + uint256 index + ) external view returns (Licensing.Policy memory) {} + + 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) {} + + function isParent(address parentIpId, address childIpId) external view returns (bool) {} + + function parentIpIds(address ipId) external view returns (address[] memory) {} + + function totalParentsForIpId(address ipId) external view returns (uint256) {} + + function name() external view returns (string memory) { + return "LICENSING_MODULE"; + } +} diff --git a/test/foundry/modules/ModuleBase.t.sol b/test/foundry/modules/ModuleBase.t.sol index 013c372a..85b00ecc 100644 --- a/test/foundry/modules/ModuleBase.t.sol +++ b/test/foundry/modules/ModuleBase.t.sol @@ -52,7 +52,8 @@ abstract contract ModuleBaseTest is BaseTest { ipAssetRegistry = new IPAssetRegistry( address(accessController), address(new ERC6551Registry()), - address(new IPAccountImpl()) + address(new IPAccountImpl()), + address(moduleRegistry) ); royaltyModule = new RoyaltyModule(address(governance)); licenseRegistry = new LicenseRegistry(); diff --git a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol index 60a73964..8f79e505 100644 --- a/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol +++ b/test/foundry/modules/dispute/ArbitrationPolicySP.t.sol @@ -75,6 +75,7 @@ contract TestArbitrationPolicySP is TestHelper { vm.label(expectedAddr, string(abi.encodePacked("IPAccount", Strings.toString(0)))); vm.startPrank(deployer); + ipAssetRegistry.setApprovalForAll(address(registrationModule), true); ipAddr = registrationModule.registerRootIp( policyIds["uml_cheap_flexible"], address(nft), diff --git a/test/foundry/modules/dispute/DisputeModule.t.sol b/test/foundry/modules/dispute/DisputeModule.t.sol index 231aef8d..7897176f 100644 --- a/test/foundry/modules/dispute/DisputeModule.t.sol +++ b/test/foundry/modules/dispute/DisputeModule.t.sol @@ -95,6 +95,7 @@ contract TestDisputeModule is TestHelper { vm.label(expectedAddr, string(abi.encodePacked("IPAccount", Strings.toString(0)))); vm.startPrank(deployer); + ipAssetRegistry.setApprovalForAll(address(registrationModule), true); ipAddr = registrationModule.registerRootIp( policyIds["uml_cheap_flexible"], address(nft), diff --git a/test/foundry/modules/licensing/LicensingModule.t.sol b/test/foundry/modules/licensing/LicensingModule.t.sol index f79a5c37..8607d7e1 100644 --- a/test/foundry/modules/licensing/LicensingModule.t.sol +++ b/test/foundry/modules/licensing/LicensingModule.t.sol @@ -9,6 +9,7 @@ import { ERC6551Registry } from "@erc6551/ERC6551Registry.sol"; // contracts import { IPAccountImpl } from "contracts/IPAccountImpl.sol"; import { Governance } from "contracts/governance/Governance.sol"; +import { ModuleRegistry } from "contracts/registries/ModuleRegistry.sol"; import { AccessPermission } from "contracts/lib/AccessPermission.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { Licensing } from "contracts/lib/Licensing.sol"; @@ -40,6 +41,8 @@ contract LicensingModuleTest is Test { MockERC721 internal nft = new MockERC721("MockERC721"); + ModuleRegistry internal moduleRegistry; + ERC6551Registry internal erc6551Registry; IPAccountImpl internal ipAccountImpl; @@ -66,10 +69,12 @@ contract LicensingModuleTest is Test { address(accessController), address(ipAccountImpl) ); + moduleRegistry = new ModuleRegistry(address(governance)); ipAssetRegistry = new IPAssetRegistry( address(accessController), address(erc6551Registry), - address(ipAccountImpl) + address(ipAccountImpl), + address(moduleRegistry) ); royaltyModule = new RoyaltyModule(address(governance)); licenseRegistry = new LicenseRegistry(); diff --git a/test/foundry/modules/royalty/LSClaimer.t.sol b/test/foundry/modules/royalty/LSClaimer.t.sol index 0b5e913a..0b2fc915 100644 --- a/test/foundry/modules/royalty/LSClaimer.t.sol +++ b/test/foundry/modules/royalty/LSClaimer.t.sol @@ -80,6 +80,7 @@ contract TestLSClaimer is TestHelper { ); vm.label(expectedAddr, string(abi.encodePacked("IPAccount", Strings.toString(nftIds[0])))); + ipAssetRegistry.setApprovalForAll(address(registrationModule), true); address ipAddr = registrationModule.registerRootIp( policyIds["uml_cheap_flexible"], address(nft), diff --git a/test/foundry/registries/metadata/IPAssetRenderer.t.sol b/test/foundry/registries/metadata/IPAssetRenderer.t.sol index c16679e1..d4ee2d0c 100644 --- a/test/foundry/registries/metadata/IPAssetRenderer.t.sol +++ b/test/foundry/registries/metadata/IPAssetRenderer.t.sol @@ -94,7 +94,8 @@ contract IPAssetRendererTest is BaseTest { ipAssetRegistry = new IPAssetRegistry( address(accessController), address(erc6551Registry), - address(ipAccountImpl) + address(ipAccountImpl), + address(moduleRegistry) ); RoyaltyModule royaltyModule = new RoyaltyModule(address(governance)); licenseRegistry = new LicenseRegistry(); @@ -132,7 +133,7 @@ contract IPAssetRendererTest is BaseTest { uri: IP_EXTERNAL_URL }) ); - vm.prank(address(registrationModule)); + vm.prank(alice); ipId = ipAssetRegistry.register(block.chainid, address(erc721), tokenId, address(resolver), true, metadata); } diff --git a/test/foundry/registries/metadata/MetadataProvider.t.sol b/test/foundry/registries/metadata/MetadataProvider.t.sol index a0f11ce8..32616e19 100644 --- a/test/foundry/registries/metadata/MetadataProvider.t.sol +++ b/test/foundry/registries/metadata/MetadataProvider.t.sol @@ -5,6 +5,7 @@ import { BaseTest } from "test/foundry/utils/BaseTest.sol"; import { IP } from "contracts/lib/IP.sol"; import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol"; import { IPResolver } from "contracts/resolvers/IPResolver.sol"; +import { MockLicensingModule } from "test/foundry/mocks/licensing/MockLicensingModule.sol"; import { ModuleRegistry } from "contracts/registries/ModuleRegistry.sol"; import { IPAssetRegistry } from "contracts/registries/IPAssetRegistry.sol"; import { ERC6551Registry } from "@erc6551/ERC6551Registry.sol"; @@ -109,6 +110,7 @@ contract MetadataProviderTest is BaseTest { Governance governance = new Governance(address(this)); AccessController accessController = new AccessController(address(governance)); ModuleRegistry moduleRegistry = new ModuleRegistry(address(governance)); + MockLicensingModule licensingModule = new MockLicensingModule(); ERC6551Registry erc6551Registry = new ERC6551Registry(); IPAccountImpl ipAccountImpl = new IPAccountImpl(); @@ -118,12 +120,17 @@ contract MetadataProviderTest is BaseTest { address(ipAccountImpl) ); vm.prank(bob); - registry = new IPAssetRegistry(address(accessController), address(erc6551Registry), address(ipAccountImpl)); - + registry = new IPAssetRegistry( + address(accessController), + address(erc6551Registry), + address(ipAccountImpl), + address(licensingModule) + ); accessController.initialize(address(ipAccountRegistry), address(moduleRegistry)); MockERC721 erc721 = new MockERC721("MockERC721"); uint256 tokenId = erc721.mintId(alice, 99); IPResolver resolver = new IPResolver(address(accessController), address(registry)); + vm.prank(alice); ipId = registry.register(block.chainid, address(erc721), tokenId, address(resolver), true, v1Metadata); metadataProvider = MetadataProviderV1(registry.metadataProvider()); diff --git a/test/foundry/resolvers/IPResolver.t.sol b/test/foundry/resolvers/IPResolver.t.sol index a5cfb4f8..024c39ca 100644 --- a/test/foundry/resolvers/IPResolver.t.sol +++ b/test/foundry/resolvers/IPResolver.t.sol @@ -41,6 +41,7 @@ contract IPResolverTest is ResolverBaseTest { uri: "https://storyprotocol.xyz" }) ); + vm.prank(alice); ipId = ipAssetRegistry.register(block.chainid, address(erc721), tokenId, address(ipResolver), true, metadata); } diff --git a/test/foundry/utils/DeployHelper.sol b/test/foundry/utils/DeployHelper.sol index 67fb78da..1d4b4eb1 100644 --- a/test/foundry/utils/DeployHelper.sol +++ b/test/foundry/utils/DeployHelper.sol @@ -115,7 +115,8 @@ contract DeployHelper is Test { ipAssetRegistry = new IPAssetRegistry( address(accessController), address(erc6551Registry), - address(ipAccountImpl) + address(ipAccountImpl), + address(moduleRegistry) ); licenseRegistry = new LicenseRegistry(); licensingModule = new LicensingModule(