diff --git a/contracts/IPAssetRegistry.sol b/contracts/IPAssetRegistry.sol index 6b8d71e2..17fd684a 100644 --- a/contracts/IPAssetRegistry.sol +++ b/contracts/IPAssetRegistry.sol @@ -2,121 +2,138 @@ pragma solidity ^0.8.19; import { IIPAssetRegistry } from "contracts/interfaces/IIPAssetRegistry.sol"; +import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; +import { IModuleRegistry } from "contracts/interfaces/modules/IModuleRegistry.sol"; +import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; import { Errors } from "contracts/lib/Errors.sol"; -import { IPAsset } from "contracts/lib/IPAsset.sol"; /// @title Global IP Asset Registry /// @notice The source of truth for IP on Story Protocol. -// TO-DO(@leeren): Migrate from consecutive ids to a global namehashing scheme. contract IPAssetRegistry is IIPAssetRegistry { /// @notice Core attributes that make up an IP Asset. struct IPA { string name; // Human-readable identifier for the IP asset. uint64 ipAssetType; // Numerical code corresponding to IP type (e.g. patent, copyright, etc.) + address registrant; // Address of the initial registrant of the IP asset. uint8 status; // Current status of the IP asset (e.g. active, expired, etc.) - address owner; // Address of the current owner of the IP asset. - address initialRegistrant; // Address of the initial registrant of the IP asset. address ipOrg; // Address of the governing entity of the IP asset. bytes32 hash; // A unique content hash of the IP asset for preserving integrity. - string url; // URL linked to additional metadata for the IP asset. uint64 registrationDate; // Timestamp for which the IP asset was first registered. - bytes data; // Any additional data to be tied to the IP asset. } + /// @notice Used for fetching modules associated with an IP asset. + IModuleRegistry public immutable MODULE_REGISTRY; + /// @notice Mapping from IP asset ids to registry records. - mapping(uint256 => IPA) public ipAssets; + mapping(uint256 => IPA) internal _ipAssets; /// @notice Tracks the total number of IP Assets in existence. - uint256 numIPAssets = 0; - - /// @notice Restricts calls to only being from the owner or IPOrg of an IP asset. - /// TODO(leeren): Add more cohesive authorization once the core alpha refactor is completed. - modifier onlyAuthorized(uint256 id) { - address ipOrg = ipAssets[id].ipOrg; - address owner = ipAssets[id].owner; - if (msg.sender != owner || msg.sender != ipOrg) { + /// TODO(leeren) Switch from numerical ids to a universal namehash. + uint256 totalSupply = 0; + + /// @notice Restricts calls to the registration module of the IP Asset. + /// TODO(ramarti): Enable IPOrg-specific registration modules to be authorized. + modifier onlyRegistrationModule() { + if (MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) != msg.sender) { revert Errors.Unauthorized(); } _; } - /// @notice Restricts calls to only being from the disputer of an IP asset. + /// @notice Restricts calls to only being from the disputer for an IP asset. /// TODO(ramarti): Add authorization for calls that manage dispute lifecycle changes. modifier onlyDisputer(uint256 id) { _; } - /// @notice Registers a new IP Asset. - /// @param params_ The IP asset registration parameters. - // TODO(ramarti): Add registration authorization via registration module. - // TODO(ramarti): Include module parameters and interfacing to registration. - function register(IPAsset.RegisterIpAssetParams calldata params_) public returns (uint256) { - uint256 ipAssetId = numIPAssets++; - uint64 registrationDate = uint64(block.timestamp); + /// @notice Initializes the Global IP Asset Registry. + /// @param moduleRegistry_ Address of the module registry. + constructor(address moduleRegistry_) { + MODULE_REGISTRY = IModuleRegistry(moduleRegistry_); + } + + /// @notice Registers a new IP asset. + /// @param registrant_ The initial registrant for the IP asset. + /// @param name_ A name given to describe the IP asset. + /// @param ipAssetType_ A numerical code corresponding to IP asset type. + /// @param hash_ A content hash used for verifyign provenance of the asset. + function register( + address registrant_, + string memory name_, + uint64 ipAssetType_, + bytes32 hash_ + ) public onlyRegistrationModule returns (uint256 ipAssetId) { + + if (MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) != msg.sender) { + revert Errors.Unauthorized(); + } - ipAssets[ipAssetId] = IPA({ - name: params_.name, - ipAssetType: params_.ipAssetType, + // Crate a new IP asset with the provided IP attributes. + ipAssetId = totalSupply++; + uint64 registrationDate = uint64(block.timestamp); + _ipAssets[ipAssetId] = IPA({ + name: name_, + ipAssetType: ipAssetType_, status: 0, // TODO(ramarti): Define status types. - owner: params_.owner, - initialRegistrant: params_.owner, - ipOrg: params_.ipOrg, - hash: params_.hash, - url: params_.url, - registrationDate: registrationDate, - data: params_.data + registrant: registrant_, + ipOrg: msg.sender, + hash: hash_, + registrationDate: registrationDate }); - - emit IPAssetRegistered( + emit Registered( ipAssetId, - params_.ipAssetType, - params_.owner, - params_.ipOrg, - params_.hash + name_, + ipAssetType_, + msg.sender, + registrant_, + hash_ ); - - emit IPAssetTransferred(ipAssetId, address(0), params_.owner); - - return ipAssetId; } - /// @notice Changes the status of an IP asset.. + /// @notice Changes the IP Org of an IP asset. /// @param ipAssetId_ The identifier of the IP asset being transferred. - /// @param status_ The new status of the IP asset. - /// TODO(ramarti) Finalize authorization logic around the disputer. - function setIPAssetStatus(uint256 ipAssetId_, uint8 status_) public onlyDisputer(ipAssetId_) { - uint8 oldStatus = ipAssets[ipAssetId_].status; - ipAssets[ipAssetId_].status = status_; - emit IPAssetStatusChanged(ipAssetId_, oldStatus, status_); + /// @param ipOrg_ The new IP Org to govern the IP asset. + function transferIPOrg(uint256 ipAssetId_, address ipOrg_) public onlyRegistrationModule { + address oldIPOrg = _ipAssets[ipAssetId_].ipOrg; + _ipAssets[ipAssetId_].ipOrg = ipOrg_; + emit IPOrgTransferred(ipAssetId_, oldIPOrg, ipOrg_); } - /// @notice Transfers ownership of an IP asset to a new owner. + /// @notice Changes the status of an IP asset. /// @param ipAssetId_ The identifier of the IP asset being transferred. - /// @param owner_ The new owner of the IP asset. - /// TODO(leeren) Add authorization around IPOrg transferring rights. - function setIPAssetOwner(uint256 ipAssetId_, address owner_) public onlyAuthorized(ipAssetId_) { - address prevOwner = ipAssets[ipAssetId_].owner; - ipAssets[ipAssetId_].owner = owner_; - emit IPAssetTransferred(ipAssetId_, prevOwner, owner_); - } - - /// @notice Gets the owner of a specific IP Asset. - /// @param ipAssetId_ The id of the IP Asset being queried. - function ipAssetOwner(uint256 ipAssetId_) public view returns (address) { - return ipAssets[ipAssetId_].owner; + /// @param status_ The new status of the IP asset. + /// TODO(ramarti) Finalize authorization logic around status changes. + function setStatus(uint256 ipAssetId_, uint8 status_) public onlyDisputer(ipAssetId_) { + uint8 oldStatus = _ipAssets[ipAssetId_].status; + _ipAssets[ipAssetId_].status = status_; + emit StatusChanged(ipAssetId_, oldStatus, status_); } /// @notice Gets the status for a specific IP Asset. /// @param ipAssetId_ The id of the IP Asset being queried. - function ipAssetStatus(uint256 ipAssetId_) public view returns (uint8) { - return ipAssets[ipAssetId_].status; + function status(uint256 ipAssetId_) public view returns (uint8) { + return _ipAssets[ipAssetId_].status; } /// @notice Gets the IP Asset Org that administers a specific IP Asset. - /// @param ipAssetId_ The id of the IP Asset being queried. + /// @param ipAssetId_ The id of the IP asset being queried. function ipAssetOrg(uint256 ipAssetId_) public view returns (address) { - return ipAssets[ipAssetId_].ipOrg; + return _ipAssets[ipAssetId_].ipOrg; + } + + /// @notice Returns the current owner of an IP asset. + /// @param ipAssetId_ The id of the IP asset being queried. + function ipAssetOwner(uint256 ipAssetId_) public view returns (address) { + address registrationModule = MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + return IRegistrationModule(registrationModule).ownerOf(ipAssetId_); + } + + /// @notice Returns all attributes related to an IP asset. + /// @param ipAssetId_ The id of the IP asset being queried for. + function ipAsset(uint256 ipAssetId_) public view returns (IPA memory) { + return _ipAssets[ipAssetId_]; } } diff --git a/contracts/StoryProtocol.sol b/contracts/StoryProtocol.sol index 28d4ff87..9f6fdbfe 100644 --- a/contracts/StoryProtocol.sol +++ b/contracts/StoryProtocol.sol @@ -1,35 +1,127 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.19; -import { IIPOrgFactory } from "contracts/interfaces/ip-org/IIPOrgFactory.sol"; +import { IIPOrgController } from "contracts/interfaces/ip-org/IIPOrgController.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; +import { Registration } from "contracts/lib/modules/Registration.sol"; import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; contract StoryProtocol { - // TODO: should this be immutable, or should the protocol be able to change factory - IIPOrgFactory public immutable FACTORY; + + IIPOrgController public immutable IP_ORG_CONTROLLER; ModuleRegistry public immutable MODULE_REGISTRY; - constructor(IIPOrgFactory ipOrgFactory_, ModuleRegistry moduleRegistry_) { + constructor(IIPOrgController ipOrgController_, ModuleRegistry moduleRegistry_) { if ( - address(ipOrgFactory_) == address(0) || + address(ipOrgController_) == address(0) || address(moduleRegistry_) == address(0) ) { revert Errors.ZeroAddress(); } - FACTORY = ipOrgFactory_; + IP_ORG_CONTROLLER = ipOrgController_; MODULE_REGISTRY = moduleRegistry_; } + //////////////////////////////////////////////////////////////////////////// + // IPOrg // + //////////////////////////////////////////////////////////////////////////// + + /// @notice Sets the metadata for an IP Org. + /// @param ipOrg_ The address of the IP Org being configured. + /// @param baseURI_ The base token metadata URI for the IP Org. + /// @param contractURI_ The contract URI associated with the IP Org. + function setMetadata( + address ipOrg_, + string calldata baseURI_, + string calldata contractURI_ + ) public { + bytes memory encodedParams = abi.encode( + Registration.SET_IP_ORG_METADATA, + abi.encode(baseURI_, contractURI_) + ); + MODULE_REGISTRY.configure( + IIPOrg(ipOrg_), + msg.sender, + ModuleRegistryKeys.REGISTRATION_MODULE, + encodedParams + ); + } + + /// @notice Registers a new IP Org + /// @param owner_ The address of the IP Org to be registered. + /// @param name_ A name to associate with the IP Org. + /// @param symbol_ A symbol to associate with the IP Org. + /// TODO: Add module configurations to the IP Org registration process. + /// TODO: Add permissions for IP Org registration. function registerIpOrg( - IPOrgParams.RegisterIPOrgParams calldata params_ - ) external returns (address) { - return FACTORY.registerIpOrg(params_); + address owner_, + string calldata name_, + string calldata symbol_ + ) external returns (address ipOrg_) { + return IP_ORG_CONTROLLER.registerIpOrg( + owner_, + name_, + symbol_ + ); + } + + /// @notice Transfers an IP asset to another owner. + /// @param ipOrg_ The governing IP Org under which the IP asset is registered. + /// @param params_ The registration params, including owner, name, hash. + /// @param preHooksData_ Hooks to embed with the registration pre-call. + /// @param postHooksData_ Hooks to embed with the registration post-call. + /// @return The global IP asset and local IP Org asset id. + function registerIPAsset( + address ipOrg_, + Registration.RegisterIPAssetParams calldata params_, + bytes[] calldata preHooksData_, + bytes[] calldata postHooksData_ + ) public returns (uint256, uint256) { + bytes memory encodedParams = abi.encode( + Registration.REGISTER_IP_ASSET, + abi.encode(params_) + ); + bytes memory result = MODULE_REGISTRY.execute( + IIPOrg(ipOrg_), + msg.sender, + ModuleRegistryKeys.REGISTRATION_MODULE, + abi.encode(params_), + preHooksData_, + postHooksData_ + ); + return abi.decode(result, (uint256, uint256)); + } + + /// @notice Transfers an IP asset to another owner. + /// @param ipOrg_ The IP Org which the IP asset is associated with. + /// @param from_ The address of the current owner of the IP asset. + /// @param to_ The address of the new owner of the IP asset. + /// @param ipAssetId_ The global id of the IP asset being transferred. + function transferIPAsset( + address ipOrg_, + address from_, + address to_, + uint256 ipAssetId_, + bytes[] calldata preHooksData_, + bytes[] calldata postHooksData_ + ) public { + bytes memory encodedParams = abi.encode( + Registration.TRANSFER_IP_ASSET, + abi.encode(from_, to_, ipAssetId_) + ); + bytes memory result = MODULE_REGISTRY.execute( + IIPOrg(ipOrg_), + msg.sender, + ModuleRegistryKeys.REGISTRATION_MODULE, + encodedParams, + preHooksData_, + postHooksData_ + ); } //////////////////////////////////////////////////////////////////////////// diff --git a/contracts/interfaces/IIPAssetRegistry.sol b/contracts/interfaces/IIPAssetRegistry.sol index e17dc2e1..e3af62f0 100644 --- a/contracts/interfaces/IIPAssetRegistry.sol +++ b/contracts/interfaces/IIPAssetRegistry.sol @@ -3,27 +3,40 @@ import { IPAsset } from "contracts/lib/IPAsset.sol"; // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.19; -/// @title Global IP Asset Registry Contract Interface +/// @title Global IP Asset Registry Interface interface IIPAssetRegistry { /// @notice Emits when a new IP asset is registered. - event IPAssetRegistered( + /// @param ipAssetId_ The global IP asset identifier. + /// @param name_ The assigned name for the IP asset. + /// @param ipAssetType_ The status indicator for the IP asset. + /// @param ipOrg_ The registering governing body for the IP asset. + /// @param registrant_ The initial individual registrant of the IP asset. + /// @param hash_ The content hash associated with the IP asset. + event Registered( uint256 ipAssetId_, + string name_, uint64 indexed ipAssetType_, - address indexed owner_, - address indexed ipAssetOrg_, + address indexed ipOrg_, + address indexed registrant_, bytes32 hash_ ); - /// @notice Emits when an IP asset is transferred to a new owner. - event IPAssetTransferred( + /// @notice Emits when an IP asset is transferred to a new IP Org. + /// @param ipAssetId_ The identifier of the IP asset being transferred. + /// @param oldIPOrg_ The original administering IP Org of the IP asset. + /// @param newIPOrg_ The new administering IP Org of the IP asset. + event IPOrgTransferred( uint256 indexed ipAssetId_, - address indexed from_, - address indexed to_ + address indexed oldIPOrg_, + address indexed newIPOrg_ ); /// @notice Emits when an IP asset has its status changed. - event IPAssetStatusChanged( + /// @param ipAssetId_ The identifier of the IP asset whose status changed. + /// @param oldStatus_ The original status associated with the IP asset. + /// @param newStatus_ The new status associated with the IP asset. + event StatusChanged( uint256 indexed ipAssetId_, uint8 oldStatus_, uint8 newStatus_ diff --git a/contracts/interfaces/ip-org/IIPOrg.sol b/contracts/interfaces/ip-org/IIPOrg.sol index f1fb095a..8f9df08c 100644 --- a/contracts/interfaces/ip-org/IIPOrg.sol +++ b/contracts/interfaces/ip-org/IIPOrg.sol @@ -1,16 +1,36 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; -import { IVersioned } from "../utils/IVersioned.sol"; -import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import { IPAsset } from "contracts/lib/IPAsset.sol"; -interface IIPOrg is - IVersioned, - IERC165Upgradeable -{ +/// @notice IP Org Interface +interface IIPOrg { + /// @notice Returns the current owner of the IP asset within th IP Org. + function ownerOf(uint256 id) external view returns (address); + + /// @notice Transfers ownership of the IP asset wrapper within an IP Org. + /// @param from The previous owner of the wrapped IP asset. + /// @param to The new owner of the wrapped IP asset. + /// @param id The identifier of the IP Org asset. + function transferFrom(address from, address to, uint256 id) external; + + /// @notice Burns an IP asset wrapper within the IP Org. + /// @dev This function is only callable by the IP Org registration module. + /// @param id The local identifier of the IP asset within the IP Org. + function burn(uint256 id) external; + + /// @notice Mints an IP Asset wrapper for the IP Org. + /// @dev This function is only callable by the IP Org registration module. + /// @param owner Address of the current owner of the local IP Org asset. + /// @return id The local identifier of the minted IP Org wrapped asset. + function mint(address owner) external returns (uint256 id); + + /// @notice Gets the current owner of the IP Org. function owner() external view returns (address); + /// @notice Returns contract-level metadata for the IP Org. + function contractURI() external view returns (string memory); + } diff --git a/contracts/interfaces/ip-org/IIPOrgController.sol b/contracts/interfaces/ip-org/IIPOrgController.sol new file mode 100644 index 00000000..ac19fb6c --- /dev/null +++ b/contracts/interfaces/ip-org/IIPOrgController.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { IVersioned } from "../utils/IVersioned.sol"; +import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; + +/// @notice IP Org Controller Interface +interface IIPOrgController { + + /// @notice Emits when a new IP Org is registered. + /// @param owner_ The address of the IP Org owner. + /// @param ipAssetOrg_ The address of the new IP Org contract. + /// @param name_ Descriptive name for the new IP Org contract. + /// @param symbol_ A describe symbol for the new IP Org contract. + event IPOrgRegistered( + address owner_, + address ipAssetOrg_, + string name_, + string symbol_ + ); + + /// @notice Emits when an IP Org is transferred to a new owner. + /// @param ipOrg_ The address of the IP Org. + /// @param prevOwner_ The address of the previous owner of the IP Org. + /// @param newOwner_ The address of the new owner of the IP Org. + event IPOrgTransferred( + address ipOrg_, + address prevOwner_, + address newOwner_ + ); + + /// @notice Emits when an ownership transfer is initialized for a new owner. + /// @param ipOrg_ The address of the IP Org. + /// @param pendingOwner_ The pending owner to set for the IP Org. + event IPOrgPendingOwnerSet( + address ipOrg_, + address pendingOwner_ + ); + + /// @notice Registers a new IP Org. + /// @param owner_ The address of the IP Org owner. + /// @param name_ Metadata name to attach to the IP Org. + /// @param symbol_ Metadata symbol to attach to the IP Org. + function registerIpOrg( + address owner_, + string calldata name_, + string calldata symbol_ + ) external returns(address); + + /// @notice Checks whether an IP Org exists. + function isIpOrg(address ipOrg_) external view returns (bool); +} diff --git a/contracts/interfaces/ip-org/IIPOrgFactory.sol b/contracts/interfaces/ip-org/IIPOrgFactory.sol index c977e96f..e3fb8dc0 100644 --- a/contracts/interfaces/ip-org/IIPOrgFactory.sol +++ b/contracts/interfaces/ip-org/IIPOrgFactory.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import { IVersioned } from "../utils/IVersioned.sol"; import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; -interface IIPOrgFactory is IVersioned { +interface IIPOrgController is IVersioned { event IPOrgRegistered( address owner_, diff --git a/contracts/interfaces/modules/IModuleRegistry.sol b/contracts/interfaces/modules/IModuleRegistry.sol new file mode 100644 index 00000000..9ced5dfd --- /dev/null +++ b/contracts/interfaces/modules/IModuleRegistry.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +/// @title IModuleRegistry +/// @notice Module Registry Interface +interface IModuleRegistry { + + /// @notice Emits when a new module is added for a specific IP Org. + event ModuleAdded( + address indexed ipOrg, + string indexed moduleKey, + address indexed module + ); + + /// @notice Emits when a module is removed for an IP Org. + event ModuleRemoved( + address indexed ipOrg, + string indexed moduleKey, + address indexed module + ); + + /// @notice Emits when a module is executed for an IP Org. + event ModuleExecuted ( + address indexed ipOrg, + string indexed moduleKey, + address indexed caller, + bytes selfParams, + bytes[] preHookParams, + bytes[] postHookParams + ); + + /// @notice Emits when a module is configured for an IP Org. + event ModuleConfigured( + address indexed ipOrg, + string indexed moduleKey, + address indexed caller, + bytes params + ); + + /// @notice Fetches the latest protocol module bound to a specific key. + function protocolModule(string calldata moduleKey) external view returns (address); +} diff --git a/contracts/interfaces/modules/base/IModule.sol b/contracts/interfaces/modules/base/IModule.sol index deca0ff2..7a496aad 100644 --- a/contracts/interfaces/modules/base/IModule.sol +++ b/contracts/interfaces/modules/base/IModule.sol @@ -39,4 +39,4 @@ interface IModule { /// @param params_ encoded params for module configuration function configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) external; -} \ No newline at end of file +} diff --git a/contracts/interfaces/modules/licensing/ILicenseRegistry.sol b/contracts/interfaces/modules/licensing/ILicenseRegistry.sol index 5d0703f0..2f788435 100644 --- a/contracts/interfaces/modules/licensing/ILicenseRegistry.sol +++ b/contracts/interfaces/modules/licensing/ILicenseRegistry.sol @@ -15,4 +15,4 @@ interface ILicenseRegistry is IERC721 { function symbol() external view returns (string memory); function getRightsManager() external view returns (address); -} \ No newline at end of file +} diff --git a/contracts/interfaces/modules/licensing/ILicensingModule.sol b/contracts/interfaces/modules/licensing/ILicensingModule.sol index 02960973..5d531f05 100644 --- a/contracts/interfaces/modules/licensing/ILicensingModule.sol +++ b/contracts/interfaces/modules/licensing/ILicensingModule.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; import { ZeroAddress, Unauthorized } from "contracts/errors/General.sol"; -import { IPOrgFactory } from "contracts/ip-org/IPOrgFactory.sol"; +import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; import { ITermsProcessor } from "./terms/ITermsProcessor.sol"; diff --git a/contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol b/contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol index 9804d740..794c0071 100644 --- a/contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol +++ b/contracts/interfaces/modules/licensing/terms/ITermsProcessor.sol @@ -18,4 +18,4 @@ interface ITermsProcessor is IERC165 { /// returns true if the terms have been executed successfully or they don't need to be executed, false otherwise function termsExecutedSuccessfully(bytes calldata data_) external view returns(bool); -} \ No newline at end of file +} diff --git a/contracts/interfaces/modules/registration/IRegistrationModule.sol b/contracts/interfaces/modules/registration/IRegistrationModule.sol new file mode 100644 index 00000000..230c218d --- /dev/null +++ b/contracts/interfaces/modules/registration/IRegistrationModule.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { Registration } from "contracts/lib/modules/Registration.sol"; + +/// @title IRegistrationModule +interface IRegistrationModule { + + /// @notice Emits when an IPOrg updates metadata associated with its IPA. + /// @param ipOrg_ The address of the IP Org whose metadata was updated. + /// @param baseURI_ The base token URI to be used for token metadata. + /// @param contractURI_ The contract URI to be used for contract metadata. + event MetadataUpdated( + address indexed ipOrg_, + string baseURI_, + string contractURI_ + ); + + /// @notice Emits when a new IP asset is registered. + /// @param ipAssetId_ The identifier of the newly registered IP asset. + /// @param ipOrg_ The address of the IP Org of the IP asset. + /// @param ipOrgAssetId_ The IP Org localized id of the IP asset. + /// @param owner_ The address of the new IP asset owner. + /// @param name_ The name of the IP asset being registered. + /// @param ipAssetType_ The numerical id of the IP asset type. + /// @param hash_ The content hash of the registered IP asset. + event IPAssetRegistered( + uint256 ipAssetId_, + address indexed ipOrg_, + uint256 ipOrgAssetId_, + address indexed owner_, + string name_, + uint64 indexed ipAssetType_, + bytes32 hash_ + ); + + /// @notice Emits when an IP asset is transferred to a new owner. + /// @param ipAssetId_ The identifier of the IP asset being transferred. + /// @param ipOrg_ The address of the IP Org which administers the IP asset. + /// @param ipOrgAssetId_ The local id of the wrapped IP within the IP Org. + /// @param prevOwner_ The address of the previous owner of the IP asset. + /// @param newOwner_ The address of the new owner of the IP asset. + event IPAssetTransferred( + uint256 indexed ipAssetId_, + address indexed ipOrg_, + uint256 ipOrgAssetId_, + address prevOwner_, + address newOwner_ + ); + + /// @notice Returns the current owner of an IP asset. + /// @param ipAssetId_ The global identifier of the IP asset within the GIPR. + function ownerOf(uint256 ipAssetId_) external view returns (address); + + /// @notice Gets the IP asset id associated with an IP Org asset. + /// @param ipOrg_ The address of the governing IP asset IP Org. + /// @param ipOrgAssetId_ The localized id of the IP asset within the IP Org. + function ipAssetId(address ipOrg_, uint256 ipOrgAssetId_) external returns (uint256); + + /// @notice Renders metadata of an IP Asset localized for an IP Org. + /// @param ipOrg_ The address of the IP Org of the IP asset. + /// @param ipOrgAssetId_ The local id of the IP asset within the IP Org. + /// @return The token URI associated with the IP Org. + function tokenURI(address ipOrg_, uint256 ipOrgAssetId_) external view returns (string memory); + + /// @notice Gets the contract URI for an IP Org. + /// @param ipOrg_ The address of the IP Org. + /// @return The contract URI associated with the IP Org. + function contractURI(address ipOrg_) external view returns (string memory); + +} diff --git a/contracts/interfaces/modules/royalties/IRoyaltyPolicy.sol b/contracts/interfaces/modules/royalties/IRoyaltyPolicy.sol index b561d843..45146baa 100644 --- a/contracts/interfaces/modules/royalties/IRoyaltyPolicy.sol +++ b/contracts/interfaces/modules/royalties/IRoyaltyPolicy.sol @@ -19,4 +19,4 @@ interface IRoyaltyPolicy { /// @param account_ IP Account associated with the policy. /// @param token_ The ERC20 token for royalty. function distribute(address account_, address token_) external; -} \ No newline at end of file +} diff --git a/contracts/interfaces/modules/royalties/policies/IRoyaltyPolicy.sol b/contracts/interfaces/modules/royalties/policies/IRoyaltyPolicy.sol index b561d843..45146baa 100644 --- a/contracts/interfaces/modules/royalties/policies/IRoyaltyPolicy.sol +++ b/contracts/interfaces/modules/royalties/policies/IRoyaltyPolicy.sol @@ -19,4 +19,4 @@ interface IRoyaltyPolicy { /// @param account_ IP Account associated with the policy. /// @param token_ The ERC20 token for royalty. function distribute(address account_, address token_) external; -} \ No newline at end of file +} diff --git a/contracts/ip-org/IPOrg.sol b/contracts/ip-org/IPOrg.sol index 171f0d8f..c88b9b7b 100644 --- a/contracts/ip-org/IPOrg.sol +++ b/contracts/ip-org/IPOrg.sol @@ -2,78 +2,127 @@ pragma solidity ^0.8.13; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { IModuleRegistry } from "contracts/interfaces/modules/IModuleRegistry.sol"; +import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; -import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; +import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; import { Errors } from "contracts/lib/Errors.sol"; /// @notice IP Asset Organization +/// TODO(leeren): Deprecate upgradeability once the IPOrg contracts is finalized. contract IPOrg is IIPOrg, - ERC721Upgradeable, - MulticallUpgradeable, - OwnableUpgradeable + ERC721Upgradeable { - /// @custom:storage-location erc7201:story-protocol.ip-asset-org.storage - // TODO: Refactor IP asset types to be specified through the IP Asset Registry or one of its modules. - struct IPOrgStorage { - uint256 placeholder; - } + /// @notice Tracks the last index of the IP asset wrapper. + uint256 lastIndex = 0; - IPAssetRegistry public REGISTRY; + /// @notice Tracks the total number of IP Assets owned by the IP org. + uint256 totalSupply = 0; - // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.ip-org-registry.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = 0x1a0b8fa444ff575656111a4368b8e6a743b70cbf31ffb9ee2c7afe1983f0e378; - string private constant _VERSION = "0.1.0"; + // Address of the module registry. + IModuleRegistry public immutable MODULE_REGISTRY; - // TODO(ramarti): Refactor to configure IP Asset types via registry modules. - uint256 private constant _ROOT_IP_ASSET = 0; + // Address of the IP Org Controller. + address public immutable CONTROLLER; - /// @notice Returns the current version of the IP asset org contract. - function version() external pure virtual returns (string memory) { - return _VERSION; + /// @notice Restricts calls to being through the registration module. + modifier onlyRegistrationModule() { + if (IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) != msg.sender) { + revert Errors.Unauthorized(); + } + _; } - function initialize(IPOrgParams.InitIPOrgParams memory params_) public initializer { - - // TODO(ramarti) Decouple IPOrg from the RightsManager and make sure to move `__ERC721_init` here. - __ERC721_init(params_.name, params_.symbol); - - __Multicall_init(); - __Ownable_init(); - // TODO: Weird bug does not allow OZ to specify owner in init... - _transferOwnership(params_.owner); + /// @notice Creates the IP Org implementation contract. + /// @param controller_ Address of the IP Org controller. + /// @param moduleRegistry_ Address of the IP asset module registry. + constructor( + address controller_, + address moduleRegistry_ + ) initializer { + CONTROLLER = controller_; + MODULE_REGISTRY = IModuleRegistry(moduleRegistry_); + } - if (params_.registry == address(0)) revert Errors.ZeroAddress(); - REGISTRY = IPAssetRegistry(params_.registry); + /// @notice Retrieves the current owner of the IP Org. + function owner() external view returns (address) { + return IPOrgController(CONTROLLER).ownerOf(msg.sender); } + /// @notice Gets the current owner of an IP asset within the IP Org. + function ownerOf(uint256 id) public view override(IIPOrg, ERC721Upgradeable) returns (address) { + return ERC721Upgradeable.ownerOf(id); + } /// @notice Retrieves the token URI for an IP Asset within the IP Asset Org. /// @param tokenId_ The id of the IP Asset within the IP Asset Org. function tokenURI( uint256 tokenId_ ) public view override returns (string memory) { - // TODO: should this reference the license too? - return "TODO"; + address registrationModule = IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + return IRegistrationModule(registrationModule).tokenURI(address(this), tokenId_); + } + + /// @notice Retrieves the contract URI for the IP Org collection. + function contractURI() public view override returns (string memory) { + address registrationModule = IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + return IRegistrationModule(registrationModule).contractURI(address(this)); } - function owner() public view override(IIPOrg, OwnableUpgradeable) returns (address) { - return super.owner(); + /// @notice Gets the global IP asset id associated with this IP Org asset. + /// @param id The local id of the IP Org wrapped IP asset. + /// @return The global identifier of the IP asset. + function ipAssetId(uint256 id) public returns (uint256) { + address registrationModule = MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); + return IRegistrationModule(registrationModule).ipAssetId(address(this), id); } - /// @dev Gets the storage associated with the IPOrg contract. - function _getIPOrgStorage() - private - pure - returns (IPOrgStorage storage $) - { - assembly { - $.slot := _STORAGE_LOCATION + /// @notice Initializes an IP Org. + /// @param name_ Name to assign to the IP Org. + /// @param symbol_ Symbol to assign to the IP Org. + function initialize( + string calldata name_, + string calldata symbol_ + ) public initializer { + + if (msg.sender != CONTROLLER) { + revert Errors.Unauthorized(); } + + __ERC721_init(name_, symbol_); + } + + /// @notice Registers a new IP Asset wrapper for the IP Org. + function mint(address owner_) public onlyRegistrationModule returns (uint256 id) { + totalSupply++; + id = lastIndex++; + _mint(owner_, id); + } + + /// @notice Burns an IP Asset wrapper of the IP Org. + /// @param id The identifier of the IP asset wrapper being burned. + function burn(uint256 id) public onlyRegistrationModule { + totalSupply--; + _burn(id); + } + + /// @notice Transfers ownership of an IP Asset within an Org to a new owner. + /// @param from_ The original owner of the IP asset in the IP Org. + /// @param to_ The new owner of the IP asset in the IP Org. + /// @param id_ The identifier of the IP asset within the IP Org. + function transferFrom( + address from_, + address to_, + uint256 id_ + ) public override(IIPOrg, ERC721Upgradeable) onlyRegistrationModule { + _transfer(from_, to_, id_); } + } diff --git a/contracts/ip-org/IPOrgController.sol b/contracts/ip-org/IPOrgController.sol new file mode 100644 index 00000000..0ba341ea --- /dev/null +++ b/contracts/ip-org/IPOrgController.sol @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { Clones } from '@openzeppelin/contracts/proxy/Clones.sol'; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; +import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; +import { Errors } from "contracts/lib/Errors.sol"; +import { IIPOrgController } from "contracts/interfaces/ip-org/IIPOrgController.sol"; +import { IPOrg } from "contracts/ip-org/IPOrg.sol"; +import { AccessControl } from "contracts/lib/AccessControl.sol"; + +/// @title IP Org Controller Contract +/// @custom:version 0.1.0 +/// TODO(leeren): Deprecate upgradeability once IPOrg contracts are finalized. +contract IPOrgController is + UUPSUpgradeable, + AccessControlledUpgradeable, + IIPOrgController +{ + + /// @notice Tracks ownership and registration of IPOrgs. + /// TODO(leeren): Add tracking for allowlisted callers of each ipOrg. + /// TODO(leeren): Add deterministic identifiers for ipOrgs using CREATE2. + struct IPOrgRecord { + bool registered; + address owner; + address pendingOwner; + } + + /// @custom:storage-location erc7201:story-protocol.ip-org-factory.storage + struct IPOrgControllerStorage { + /// @dev Tracks registered IP Orgs through records of ownership. + mapping(address => IPOrgRecord) ipOrgs; + /// @dev Tracks owner of the IP Org Controller. + address owner; + } + + /// @notice The IP asset module registry. + address public immutable MODULE_REGISTRY; + + /// @notice The IP Org implementation address. + address public IP_ORG_IMPL; + + bytes32 private constant _STORAGE_LOCATION = bytes32(uint256(keccak256("story-protocol.ip-org-factory.storage")) - 1); + + + /// @notice Creates the IP Org Controller contract. + /// @param moduleRegistry_ Address of the IP asset module registry. + constructor(address moduleRegistry_) { + MODULE_REGISTRY = moduleRegistry_; + } + + /// @notice Initializes the IP Org Controller + /// @param accessControl_ Address of the contract responsible for access control. + /// TODO(leeren): Deprecate this function in favor of an immutable factory. + function initialize(address accessControl_) public initializer { + IP_ORG_IMPL = address(new IPOrg(address(this), MODULE_REGISTRY)); + __UUPSUpgradeable_init(); + __AccessControlledUpgradeable_init(accessControl_); + } + + /// @notice Retrieves the current owner of an IP Org. + /// @param ipOrg_ The address of the IP Org being queried. + function ownerOf(address ipOrg_) external view returns (address) { + IPOrgRecord storage record = _ipOrgRecord(ipOrg_); + return record.owner; + } + + /// @notice Returns whether an IP Org has been officially registered. + /// @param ipOrg_ The address of the IP Org being queried. + function isIpOrg(address ipOrg_) external view returns (bool) { + IPOrgControllerStorage storage $ = _getIpOrgControllerStorage(); + return $.ipOrgs[ipOrg_].registered; + } + + /// @notice Retrieves the pending owner of an IP Org. + /// @dev A zero return address implies no ownership transfer is in process. + /// @param ipOrg_ The address of the IP Org being queried. + function pendingOwnerOf(address ipOrg_) external view returns (address pendingOwner) { + IPOrgRecord storage record = _ipOrgRecord(ipOrg_); + return record.pendingOwner; + } + + /// @notice Initiates transfer of ownership for an IP Org. + /// @param ipOrg_ The address of the IP Org transferring ownership. + /// @param newOwner_ The address of the new IP Org owner. + function transferOwner(address ipOrg_, address newOwner_) external { + IPOrgRecord storage record = _ipOrgRecord(ipOrg_); + + // Ensure the current IP Org owner is initiating the transfer. + if (record.owner != msg.sender) { + revert Errors.IPOrgController_InvalidIPOrgOwner(); + } + + // Ensure the proposed new owner is not the zero address. + if (newOwner_ == address(0)) { + revert Errors.IPOrgController_InvalidNewIPOrgOwner(); + } + + record.pendingOwner = newOwner_; + emit IPOrgPendingOwnerSet(ipOrg_, newOwner_); + } + + /// @notice Cancels the transferring of ownership of an IP Org. + /// @param ipOrg_ The address of the IP Org transferring ownership. + function cancelOwnerTransfer(address ipOrg_) external { + IPOrgRecord storage record = _ipOrgRecord(ipOrg_); + + // Ensure the current IP Org owner is canceling the transfer. + if (record.owner != msg.sender) { + revert Errors.IPOrgController_InvalidIPOrgOwner(); + } + + // Ensure an ongoing ownership transfer has actually initiated. + if (record.pendingOwner == address(0)) { + revert Errors.IPOrgController_OwnerTransferUninitialized(); + } + + delete record.pendingOwner; + emit IPOrgPendingOwnerSet(ipOrg_, address(0)); + } + + /// @notice Accepts the transferring of ownership of an IP Org. + /// @param ipOrg_ The address of the IP Org being transferred. + function acceptOwnerTransfer(address ipOrg_) external { + IPOrgRecord storage record = _ipOrgRecord(ipOrg_); + + // Ensure the pending IP Org owner is accepting the ownership transfer. + if (record.pendingOwner != msg.sender) { + revert Errors.IPOrgController_InvalidIPOrgOwner(); + } + + // Reset the pending owner. + delete record.pendingOwner; + record.owner = msg.sender; + + emit IPOrgPendingOwnerSet(ipOrg_, address(0)); + emit IPOrgTransferred(ipOrg_, record.owner, msg.sender); + } + + /// @notice Registers a new IP Org. + /// @param owner_ The address of the IP Org to be registered. + /// @param name_ The name to associated with the new IP Org. + /// @param symbol_ The symbol to associate with the new IP Org. + /// TODO: Add module configurations to the IP Org registration process. + /// TODO: Add authorization for IP Org registration. + function registerIpOrg( + address owner_, + string calldata name_, + string calldata symbol_ + ) public returns (address ipOrg_) { + // Check that the owner is a non-zero address. + if (owner_ == address(0)) { + revert Errors.ZeroAddress(); + } + + ipOrg_ = Clones.clone(IP_ORG_IMPL); + IPOrg(ipOrg_).initialize( + name_, + symbol_ + ); + + // Set the registration status of the IP Asset Org to be true. + IPOrgControllerStorage storage $ = _getIpOrgControllerStorage(); + $.ipOrgs[ipOrg_] = IPOrgRecord({ + registered: true, + owner: owner_, + pendingOwner: address(0) + }); + + emit IPOrgRegistered( + msg.sender, + ipOrg_, + name_, + symbol_ + ); + } + + /// @dev Gets the ownership record of an IP Org. + /// @param ipOrg_ The address of the IP Org being queried. + function _ipOrgRecord(address ipOrg_) internal view returns (IPOrgRecord storage record) { + IPOrgControllerStorage storage $ = _getIpOrgControllerStorage(); + record = $.ipOrgs[ipOrg_]; + if (record.owner == address(0)) { + revert Errors.IPOrgController_IPOrgNonExistent(); + } + } + + /// @dev Checks whether an IP Org exists, throwing if not. + /// @param ipOrg_ The address of the IP Org being queried. + function _assertIPOrgExists(address ipOrg_) internal view { + IPOrgControllerStorage storage $ = _getIpOrgControllerStorage(); + if ($.ipOrgs[ipOrg_].registered) { + revert Errors.IPOrgController_IPOrgNonExistent(); + } + } + + /// @dev Authorizes upgrade to a new contract address via UUPS. + function _authorizeUpgrade(address) + internal + virtual + override + onlyRole(AccessControl.UPGRADER_ROLE) {} + + + /// @dev Retrieves the ERC-1967 storage slot for the IP Org Controller. + function _getIpOrgControllerStorage() + private + pure + returns (IPOrgControllerStorage storage $) + { + bytes32 storageLocation = _STORAGE_LOCATION; + assembly { + $.slot := storageLocation + } + } +} diff --git a/contracts/ip-org/IPOrgFactory.sol b/contracts/ip-org/IPOrgFactory.sol deleted file mode 100644 index 4de5085a..00000000 --- a/contracts/ip-org/IPOrgFactory.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { Clones } from '@openzeppelin/contracts/proxy/Clones.sol'; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; -import { Errors } from "contracts/lib/Errors.sol"; -import { LicenseRegistry } from "contracts/modules/licensing/LicenseRegistry.sol"; -import { IIPOrgFactory } from "contracts/interfaces/ip-org/IIPOrgFactory.sol"; -import { IPOrg } from "contracts/ip-org/IPOrg.sol"; -import { AccessControl } from "contracts/lib/AccessControl.sol"; - -/// @notice IP Organization Factory Contract -/// TODO(ramarti): Extend the base hooks contract utilized by SP modules. -/// TODO: Converge on upgradeability and IPOrg template setting -contract IPOrgFactory is - UUPSUpgradeable, - AccessControlledUpgradeable, - IIPOrgFactory -{ - - /// @notice Base template implementation contract used for new IP Asset Org creation. - address public immutable IP_ORG_IMPL = address(new IPOrg()); - - string private constant _VERSION = "0.1.0"; - - // TODO(@leeren): Fix storage hash - // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.ip-asset-org-factory.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = 0x1b0b8fa444ff575656111a4368b8e6a743b70cbf31ffb9ee2c7afe1983f0e378; - - /// @custom:storage-location erc7201:story-protocol.ip-asset-org-factory.storage - // TODO: Extend IP asset org storage to support other relevant configurations - struct IPOrgFactoryStorage { - /// @dev Tracks mappings from ipAssetOrg to whether they were registered. - mapping(address => bool) registered; - } - - /// @notice Checks if an address is a valid IP Asset Organization. - /// @param ipAssetOrg_ the address to check - /// @return true if `ipAssetOrg_` is a valid IP Asset Organization, false otherwise - function isIpOrg( - address ipAssetOrg_ - ) external view returns (bool) { - IPOrgFactoryStorage storage $ = _getIpOrgFactoryStorage(); - return $.registered[ipAssetOrg_]; - } - - /// @notice Returns the current version of the factory contract. - function version() external pure override returns (string memory) { - return _VERSION; - } - - /// @notice Registers a new ipAssetOrg for IP asset collection management. - /// @param params_ Parameters required for ipAssetOrg creation. - /// TODO: Converge on core primitives utilized for ipAssetOrg management. - /// TODO: Add ipAssetOrg-wide module configurations to the registration process. - /// TODO: Converge on access control for this method - function registerIpOrg( - IPOrgParams.RegisterIPOrgParams calldata params_ - ) public onlyRole(AccessControl.IPORG_CREATOR_ROLE) returns (address) { - address ipAssetOrg = Clones.clone(IP_ORG_IMPL); - IPOrg(ipAssetOrg).initialize(IPOrgParams.InitIPOrgParams({ - registry: params_.registry, - owner: msg.sender, - name: params_.name, - symbol: params_.symbol - })); - - // Set the registration status of the IP Asset Org to be true. - IPOrgFactoryStorage storage $ = _getIpOrgFactoryStorage(); - $.registered[ipAssetOrg] = true; - - emit IPOrgRegistered( - msg.sender, - ipAssetOrg, - params_.name, - params_.symbol, - params_.metadataUrl - ); - return ipAssetOrg; - - } - - /// @notice Initializes the IPOrgFactory contract. - /// @param accessControl_ Address of the contract responsible for access control. - function initialize(address accessControl_) public initializer { - __UUPSUpgradeable_init(); - __AccessControlledUpgradeable_init(accessControl_); - } - - function _authorizeUpgrade( - address newImplementation_ - ) internal virtual override onlyRole(AccessControl.UPGRADER_ROLE) {} - - function _getIpOrgFactoryStorage() - private - pure - returns (IPOrgFactoryStorage storage $) - { - assembly { - $.slot := _STORAGE_LOCATION - } - } -} diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 9e217d8c..8a67ecbb 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -196,7 +196,7 @@ library Errors { error LibUintArrayMask_InvalidType(IPAsset.IPAssetType ipAsset); //////////////////////////////////////////////////////////////////////////// - // IPOrg // + // IPOrg // //////////////////////////////////////////////////////////////////////////// /// @notice IP identifier is over bounds. @@ -205,6 +205,25 @@ library Errors { /// @notice Licensing is not configured. error IPOrg_LicensingNotConfigured(); + //////////////////////////////////////////////////////////////////////////// + // IPOrgController // + //////////////////////////////////////////////////////////////////////////// + + /// @notice The caller is not the owner of the IP Org Controller. + error IPOrgController_InvalidOwner(); + + /// @notice IP Org does not exist. + error IPOrgController_IPOrgNonExistent(); + + /// @notice The caller is not the authorized IP Org owner. + error IPOrgController_InvalidIPOrgOwner(); + + /// @notice The new owner for an IP Org may not be the zero address. + error IPOrgController_InvalidNewIPOrgOwner(); + + /// @notice The owner transfer has not yet been initialized. + error IPOrgController_OwnerTransferUninitialized(); + //////////////////////////////////////////////////////////////////////////// // LibDuration // //////////////////////////////////////////////////////////////////////////// @@ -281,6 +300,28 @@ library Errors { /// @notice Too many terms were selected. error MultiTermsProcessor_TooManyTermsProcessors(); + //////////////////////////////////////////////////////////////////////////// + // RegistrationModule // + //////////////////////////////////////////////////////////////////////////// + + /// @notice The caller is not authorized to perform registration. + error RegistrationModule_CallerNotAuthorized(); + + /// @notice The configured caller is invalid. + error RegistrationModule_InvalidCaller(); + + /// @notice The IP asset does not exist. + error RegistrationModule_IPAssetNonExistent(); + + /// @notice The registration module for the IP Org was not yet configured. + error RegistrationModule_IPOrgNotConfigured(); + + /// @notice The registration configuration action is not valid. + error RegistrationModule_InvalidConfigOperation(); + + /// @notice The registration execution action is not valid. + error RegistrationModule_InvalidExecutionOperation(); + //////////////////////////////////////////////////////////////////////////// // RelationshipModule // //////////////////////////////////////////////////////////////////////////// diff --git a/contracts/lib/IPAsset.sol b/contracts/lib/IPAsset.sol index 01e0115e..368cc470 100644 --- a/contracts/lib/IPAsset.sol +++ b/contracts/lib/IPAsset.sol @@ -26,20 +26,16 @@ library IPAsset { string name; uint64 ipAssetType; address owner; - address ipOrg; bytes32 hash; - string url; - bytes data; } struct CreateIpAssetParams { - IPAsset.IPAssetType ipAssetType; + IPAsset.IPAssetType ipOrgAssetType; + uint64 ipAssetType; string name; - string description; + bytes32 hash; string mediaUrl; - address to; - uint256 parentIpOrgId; - bytes collectData; + bytes ipData; } } diff --git a/contracts/lib/modules/ModuleRegistryKeys.sol b/contracts/lib/modules/ModuleRegistryKeys.sol index 7d120d7c..b6589b7b 100644 --- a/contracts/lib/modules/ModuleRegistryKeys.sol +++ b/contracts/lib/modules/ModuleRegistryKeys.sol @@ -4,4 +4,5 @@ pragma solidity ^0.8.19; library ModuleRegistryKeys { string public constant RELATIONSHIP_MODULE = "RELATIONSHIP_MODULE"; string public constant LICENSING_MODULE = "LICENSING_MODULE"; + string public constant REGISTRATION_MODULE = "REGISTRATION_MODULE"; } diff --git a/contracts/lib/modules/Registration.sol b/contracts/lib/modules/Registration.sol new file mode 100644 index 00000000..c76b4b47 --- /dev/null +++ b/contracts/lib/modules/Registration.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +/// @title Relationship Module Library +library Registration { + + /// @notice IPOrg configuration settings. + struct IPOrgConfig { + string baseURI; + string contractURI; + } + + /// @notice Struct used for IP asset registration. + struct RegisterIPAssetParams { + address owner; + string name; + uint64 ipAssetType; + bytes32 hash; + } + + // TODO(leeren): Change in favor of granular function-selector based auth. + + // Constants used for determining module configuration logic. + bytes32 public constant SET_IP_ORG_METADATA = keccak256("SET_IP_ORG_METADATA"); + + // Constants used for determining module execution logic. + bytes32 public constant REGISTER_IP_ASSET = keccak256("REGISTER_IP_ASSET"); + bytes32 public constant TRANSFER_IP_ASSET = keccak256("TRANSFER_IP_ASSET"); + +} diff --git a/contracts/modules/ModuleRegistry.sol b/contracts/modules/ModuleRegistry.sol index 95a8aa90..392fbc33 100644 --- a/contracts/modules/ModuleRegistry.sol +++ b/contracts/modules/ModuleRegistry.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.19; +import { IModuleRegistry } from "contracts/interfaces/modules/IModuleRegistry.sol"; import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; import { Errors } from "contracts/lib/Errors.sol"; @@ -12,31 +13,20 @@ import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; /// @notice This contract is the source of truth for all modules that are registered in the protocol. /// It's also the entrypoint for execution and configuration of modules, either directly by users /// or by MODULE_EXECUTOR_ROLE holders. -contract ModuleRegistry is AccessControlled, Multicall { - - event ModuleAdded(address indexed ipOrg, string indexed moduleKey, BaseModule module); - event ModuleRemoved(address indexed ipOrg, string indexed moduleKey, BaseModule module); - event ModuleExecuted ( - address indexed ipOrg, - string indexed moduleKey, - address indexed caller, - bytes selfParams, - bytes[] preHookParams, - bytes[] postHookParams - ); - event ModuleConfigured( - address indexed ipOrg, - string indexed moduleKey, - address indexed caller, - bytes params - ); - - mapping(string => BaseModule) private _protocolModules; +contract ModuleRegistry is IModuleRegistry, AccessControlled, Multicall { address public constant PROTOCOL_LEVEL = address(0); + mapping(string => BaseModule) internal _protocolModules; + constructor(address accessControl_) AccessControlled(accessControl_) { } + /// @notice Gets the protocol-wide module associated with a module key. + /// @param moduleKey_ The unique module key used to identify the module. + function protocolModule(string calldata moduleKey_) public view returns (address) { + return address(_protocolModules[moduleKey_]); + } + /// Add a module to the protocol, that will be available for all IPOrgs. /// This is only callable by MODULE_REGISTRAR_ROLE holders. /// @param moduleKey short module descriptor @@ -50,7 +40,7 @@ contract ModuleRegistry is AccessControlled, Multicall { revert Errors.ZeroAddress(); } _protocolModules[moduleKey] = moduleAddress; - emit ModuleAdded(PROTOCOL_LEVEL, moduleKey, moduleAddress); + emit ModuleAdded(PROTOCOL_LEVEL, moduleKey, address(moduleAddress)); } /// Remove a module from the protocol (all IPOrgs) @@ -62,7 +52,7 @@ contract ModuleRegistry is AccessControlled, Multicall { if (address(_protocolModules[moduleKey]) == address(0)) { revert Errors.ModuleRegistry_ModuleNotRegistered(moduleKey); } - BaseModule moduleAddress = _protocolModules[moduleKey]; + address moduleAddress = address(_protocolModules[moduleKey]); delete _protocolModules[moduleKey]; emit ModuleRemoved(PROTOCOL_LEVEL, moduleKey, moduleAddress); } @@ -167,4 +157,4 @@ contract ModuleRegistry is AccessControlled, Multicall { module.configure(ipOrg_, caller_, params_); emit ModuleConfigured(address(ipOrg_), moduleKey_, caller_, params_); } -} \ No newline at end of file +} diff --git a/contracts/modules/base/BaseModule.sol b/contracts/modules/base/BaseModule.sol index 7342a2d6..85702d48 100644 --- a/contracts/modules/base/BaseModule.sol +++ b/contracts/modules/base/BaseModule.sol @@ -136,4 +136,4 @@ abstract contract BaseModule is IModule, HookRegistry { /// @param params_ The encoded parameters for module action. /// @return The generated registry key. function _hookRegistryKey(IIPOrg ipOrg_, address caller_, bytes calldata params_) internal view virtual returns(bytes32); -} \ No newline at end of file +} diff --git a/contracts/modules/base/HookRegistry.sol b/contracts/modules/base/HookRegistry.sol index d03ed031..3a424e8a 100644 --- a/contracts/modules/base/HookRegistry.sol +++ b/contracts/modules/base/HookRegistry.sol @@ -11,6 +11,7 @@ import { IHook } from "contracts/interfaces/hooks/base/IHook.sol"; /// The HookRegistry supports multiple arrays of hooks, each associated with a different configuration, separated by a `registryKey` /// Each module can define its own approach to generate its unique registryKey. abstract contract HookRegistry { + enum HookType { PreAction, PostAction diff --git a/contracts/modules/collect/CollectModuleBase.sol b/contracts/modules/collect/CollectModuleBase.sol index 204b6c2e..038567ba 100644 --- a/contracts/modules/collect/CollectModuleBase.sol +++ b/contracts/modules/collect/CollectModuleBase.sol @@ -11,7 +11,7 @@ import { ICollectNFT } from "contracts/interfaces/modules/collect/ICollectNFT.so import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; import { Collect } from "contracts/lib/modules/Collect.sol"; import { Errors } from "contracts/lib/Errors.sol"; -import { IPOrgFactory } from "contracts/ip-org/IPOrgFactory.sol"; +import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; diff --git a/contracts/modules/collect/SimpleCollectModule.sol b/contracts/modules/collect/SimpleCollectModule.sol index 4227eb6b..0d2fd019 100644 --- a/contracts/modules/collect/SimpleCollectModule.sol +++ b/contracts/modules/collect/SimpleCollectModule.sol @@ -25,7 +25,7 @@ contract SimpleCollectModule is CollectModuleBase { function _authorizeUpgrade(address newImplementation_) internal override onlyRole(AccessControl.UPGRADER_ROLE) {} /// @dev Checks whether the collect action is authorized for an IP asset. - function _isCollectAuthorized(uint256 ipAssetId_) internal override returns (bool) { + function _isCollectAuthorized(uint256 ipAssetId_) internal view override returns (bool) { address ipAssetOrg = REGISTRY.ipAssetOrg(ipAssetId_); return msg.sender == ipAssetOrg; } diff --git a/contracts/modules/licensing/LicensingModule.sol b/contracts/modules/licensing/LicensingModule.sol index 2c6ed415..44a59ce8 100644 --- a/contracts/modules/licensing/LicensingModule.sol +++ b/contracts/modules/licensing/LicensingModule.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; import { Errors } from "contracts/lib/Errors.sol"; -import { IPOrgFactory } from "contracts/ip-org/IPOrgFactory.sol"; +import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; @@ -28,13 +28,13 @@ contract LicensingModule is ILicensingModule, AccessControlledUpgradeable { // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.licensing-module.storage")) - 1))) bytes32 private constant _STORAGE_LOCATION = 0x80b4ea8c21e869c68acfd93c8ef2c0d867835b92e2fded15a1d74d7e7ff3312d; - IPOrgFactory public immutable IP_ASSET_ORG_FACTORY; + IPOrgController public immutable IP_ASSET_ORG_FACTORY; constructor(address franchise_) { if (franchise_ == address(0)) { revert Errors.ZeroAddress(); } - IP_ASSET_ORG_FACTORY = IPOrgFactory(franchise_); + IP_ASSET_ORG_FACTORY = IPOrgController(franchise_); _disableInitializers(); } diff --git a/contracts/modules/licensing/RightsManager.sol b/contracts/modules/licensing/RightsManager.sol index 1fcf09bf..f48559ed 100644 --- a/contracts/modules/licensing/RightsManager.sol +++ b/contracts/modules/licensing/RightsManager.sol @@ -119,7 +119,7 @@ abstract contract RightsManager is /// Creates the root licenses that all other licenses of a IPOrg may be based on. - /// @dev Throws if caller not owner of the IPOrgFactory NFt. + /// @dev Throws if caller not owner of the IPOrgController NFt. /// @param licenseHolder_ The address of the sublicense holder, will own the ILicenseRegistry NFT. /// @param uri_ License terms URI. /// @param revoker_ address that can revoke the license. @@ -163,9 +163,9 @@ abstract contract RightsManager is // TODO: should revoker come from allowed revoker list? if (revoker_ == address(0)) revert Errors.RightsManager_ZeroRevokerAddress(); RightsManagerStorage storage $ = _getRightsManagerStorage(); - // Only licenses minted to the IPOrgFactory Owner as a root license should + // Only licenses minted to the IPOrgController Owner as a root license should // have tokenId = ROOT_LICENSE_ID, otherwise the tokenId should be a minted NFT (IPAsset.IPAssetType) - // Checks for the IPOrgFactory Owner should be done in the calling function + // Checks for the IPOrgController Owner should be done in the calling function if (tokenId_ != ROOT_LICENSE_ID) { if (!_exists(tokenId_)) { revert Errors.NonExistentID(tokenId_); diff --git a/contracts/modules/registration/RegistrationModule.sol b/contracts/modules/registration/RegistrationModule.sol new file mode 100644 index 00000000..3c9c181f --- /dev/null +++ b/contracts/modules/registration/RegistrationModule.sol @@ -0,0 +1,299 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { Base64 } from "@openzeppelin/contracts/utils/Base64.sol"; +import { BaseModule } from "contracts/modules/base/BaseModule.sol"; +import { IPAssetRegistry } from "contracts/IPAssetRegistry.sol"; +import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; +import { IIPOrg } from "contracts/interfaces/ip-org/IIPOrg.sol"; +import { AccessControlled } from "contracts/access-control/AccessControlled.sol"; +import { AccessControl } from "contracts/lib/AccessControl.sol"; +import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; +import { Registration } from "contracts/lib/modules/Registration.sol"; +import { LibUintArrayMask } from "contracts/lib/LibUintArrayMask.sol"; +import { Errors } from "contracts/lib/Errors.sol"; + +/// @title Registration Module +/// @notice Handles registration and transferring of IP assets.. +contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled { + + using Strings for uint256; + + /// @notice Representation of a wrapped IP asset within an IP Org. + struct IPOrgAsset { + address ipOrg; + uint256 ipOrgAssetId; + } + + /// @notice Maps global IP asset Ids to IP Org wrapped assets. + mapping(uint256 => IPOrgAsset) ipOrgAssets; + + /// @notice Maps IP Orgs to their IPA configuration settings. + mapping(address => Registration.IPOrgConfig) ipOrgConfigs; + + /// @notice Reverse lookup from IP Org asset to global IP asset ids. + mapping(address => mapping(uint256 => uint256)) public ipAssetId; + + /// @notice Initializes the registration module. + constructor( + BaseModule.ModuleConstruction memory params_, + address accessControl_ + ) BaseModule(params_) AccessControlled(accessControl_) {} + + /// @notice Gets the contract URI for an IP Org. + /// @param ipOrg_ The address of the IP Org. + function contractURI(address ipOrg_) public view returns (string memory) { + string memory uri = ipOrgConfigs[ipOrg_].contractURI; + if (bytes(uri).length == 0) { + revert Errors.RegistrationModule_IPOrgNotConfigured(); + } + return uri; + } + + /// @notice Renders metadata of an IP Asset localized for an IP Org. + /// @param ipOrg_ The address of the IP Org of the IP asset. + /// @param ipOrgAssetId_ The local id of the IP asset within the IP Org. + function tokenURI(address ipOrg_, uint256 ipOrgAssetId_) public view returns (string memory) { + uint256 id = ipAssetId[ipOrg_][ipOrgAssetId_]; + address owner = IIPOrg(ipOrg_).ownerOf(ipOrgAssetId_); + if (owner == address(0)) { + revert Errors.RegistrationModule_IPAssetNonExistent(); + } + + Registration.IPOrgConfig memory config = ipOrgConfigs[ipOrg_]; + if (bytes(config.baseURI).length != 0) { + return string(abi.encodePacked(config.baseURI, Strings.toString(id))); + } + + IPAssetRegistry.IPA memory ipAsset = IPA_REGISTRY.ipAsset(id); + + // Construct the base JSON metadata with custom name format + string memory baseJson = string(abi.encodePacked( + '{"name": "Global IP Asset #', Strings.toString(id), + ': ', ipAsset.name, + '", "description": "IP Org Asset Registration Details", "attributes": [' + )); + + + string memory ipOrgAttributes = string(abi.encodePacked( + '{"trait_type": "IP Org", "value": "', Strings.toHexString(uint160(ipAsset.ipOrg), 20), '"},', + '{"trait_type": "Current IP Owner", "value": "', Strings.toHexString(uint160(owner), 20), '"},' + )); + + string memory ipAssetAttributes = string(abi.encodePacked( + '{"trait_type": "Initial Registrant", "value": "', Strings.toHexString(uint160(ipAsset.registrant), 20), '"},', + '{"trait_type": "IP Asset Type", "value": "', Strings.toString(ipAsset.ipAssetType), '"},', + '{"trait_type": "Status", "value": "', Strings.toString(ipAsset.status), '"},', + '{"trait_type": "Hash", "value": "', Strings.toHexString(uint256(ipAsset.hash), 32), '"},', + '{"trait_type": "Registration Date", "value": "', Strings.toString(ipAsset.registrationDate), '"}' + )); + + return string(abi.encodePacked( + "data:application/json;base64,", + Base64.encode( + bytes( + string(abi.encodePacked( + baseJson, + ipOrgAttributes, + ipAssetAttributes, + ']}' + ) + ) + )) + )); + } + + /// @notice Gets the current owner of an IP asset. + /// @param ipAssetId_ The global IP asset id being queried. + function ownerOf(uint256 ipAssetId_) public view returns (address) { + IPOrgAsset memory ipOrgAsset = ipOrgAssets[ipAssetId_]; + return IIPOrg(ipOrgAsset.ipOrg).ownerOf(ipOrgAsset.ipOrgAssetId); + } + + /// Verifies that the relationship execute() wants to set is valid according to its type definition + /// @param ipOrg_ IPOrg address or zero address for protocol level relationships + /// @param params_ encoded params for module action + function _verifyExecution(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual override internal { + (bytes32 executionType, bytes memory executionData) = abi.decode(params_, (bytes32, bytes)); + + if (executionType == Registration.TRANSFER_IP_ASSET) { + (address from, address to, uint256 id) = abi.decode(executionData, (address, address, uint256)); + if (caller_ != from || ownerOf(id) != caller_) { + revert Errors.RegistrationModule_InvalidCaller(); + } + } else if (executionType == Registration.REGISTER_IP_ASSET) { + Registration.RegisterIPAssetParams memory params = abi.decode(executionData, (Registration.RegisterIPAssetParams)); + if (params.owner != caller_) { + revert Errors.RegistrationModule_InvalidCaller(); + } + } else { + revert Errors.RegistrationModule_InvalidExecutionOperation(); + } + + // TODO(leeren): Perform additional vetting on name, IP type, and CID. + } + + /// @dev Configures the registration settings for a specific IP Org. + /// @param ipOrg_ The IP Org being configured. + /// @param caller_ The caller authorized to perform configuration. + /// @param params_ Parameters passed for registration configuration. + function _configure(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual override internal { + _verifyConfigCaller(ipOrg_, caller_); + (bytes32 configType, bytes memory configData) = abi.decode(params_, (bytes32, bytes)); + if (configType == Registration.SET_IP_ORG_METADATA) { + (string memory baseURI, string memory contractURI) = abi.decode(configData, (string, string)); + _setMetadata(address(ipOrg_), baseURI, contractURI); + } else { + revert Errors.RegistrationModule_InvalidConfigOperation(); + } + } + + /// @notice Registers an IP Asset. + /// @param params_ encoded RegisterIPAParams for module action + /// @return encoded registry and IP Org id of the IP asset. + function _performAction(IIPOrg ipOrg_, address caller_, bytes calldata params_) virtual override internal returns (bytes memory) { + (bytes32 executionType, bytes memory executionData) = abi.decode(params_, (bytes32, bytes)); + if (executionType == Registration.TRANSFER_IP_ASSET) { + (address from, address to, uint256 id) = abi.decode(executionData, (address, address, uint256)); + _transferIPAsset(ipOrg_, id, from, to); + return ""; + } else if (executionType == Registration.REGISTER_IP_ASSET) { + Registration.RegisterIPAssetParams memory params = abi.decode(executionData, (Registration.RegisterIPAssetParams)); + (uint256 ipAssetId, uint256 ipOrgAssetId) = _registerIPAsset(ipOrg_, params.owner, params.name, params.ipAssetType, params.hash); + return abi.encode(ipAssetId, ipOrgAssetId); + } + return ""; + } + + /// @dev Registers a new IP asset and wraps it under the provided IP Org. + /// @param ipOrg_ The governing entity of the IP asset being registered. + /// @param owner_ The initial registrant and owner of the IP asset. + /// @param name_ A descriptive name for the IP asset being registered. + /// @param ipAssetType_ A numerical identifier for the IP asset type. + /// @param hash_ The content hash of the IP asset being registered. + function _registerIPAsset( + IIPOrg ipOrg_, + address owner_, + string memory name_, + uint64 ipAssetType_, + bytes32 hash_ + ) internal returns (uint256 ipAssetId_, uint256 ipOrgAssetId_) { + ipAssetId_ = IPA_REGISTRY.register( + owner_, + name_, + ipAssetType_, + hash_ + ); + ipOrgAssetId_ = ipOrg_.mint(owner_); + ipAssetId[address(ipOrg_)][ipOrgAssetId_] = ipAssetId_; + IPOrgAsset memory ipOrgAsset = IPOrgAsset(address(ipOrg_), ipOrgAssetId_); + ipOrgAssets[ipAssetId_] = ipOrgAsset; + emit IPAssetRegistered( + ipAssetId_, + address(ipOrg_), + ipOrgAssetId_, + owner_, + name_, + ipAssetType_, + hash_ + ); + } + + /// @dev Transfers ownership of an IP asset to a new owner. + /// @param ipOrg_ The address of the currently governing IP Org. + /// @param ipOrgAssetId_ The local id of the IP asset within the IP Org. + /// @param from_ The current owner of the IP asset within the IP Org. + /// @param to_ The new owner of the IP asset within the IP Org. + function _transferIPAsset( + IIPOrg ipOrg_, + uint256 ipOrgAssetId_, + address from_, + address to_ + ) internal { + ipOrg_.transferFrom(from_, to_, ipOrgAssetId_); + uint256 id = ipAssetId[address(ipOrg_)][ipOrgAssetId_]; + emit IPAssetTransferred( + id, + address(ipOrg_), + ipOrgAssetId_, + from_, + to_ + ); + } + + /// @dev Transfers an IP asset to a new governing IP Org. + /// @param fromIpOrg_ The address of the original governing IP Org. + /// @param fromIpOrgAssetId_ The existing id of the IP asset within the IP Org. + /// @param toIpOrg_ The address of the new governing IP Org. + /// TODO(leeren) Expose this function to FE once IP Orgs are finalized. + function _transferIPAssetToIPOrg( + address fromIpOrg_, + uint256 fromIpOrgAssetId_, + address toIpOrg_, + address from_, + address to_ + ) internal returns (uint256 ipAssetId_, uint256 ipOrgAssetId_) { + uint256 id = ipAssetId[address(fromIpOrg_)][fromIpOrgAssetId_]; + + address owner = IIPOrg(fromIpOrg_).ownerOf(ipOrgAssetId_); + + delete ipAssetId[address(fromIpOrg_)][fromIpOrgAssetId_]; + delete ipOrgAssets[id]; + IIPOrg(fromIpOrg_).burn(ipOrgAssetId_); + IPA_REGISTRY.transferIPOrg( + ipAssetId_, + toIpOrg_ + ); + ipOrgAssetId_ = IIPOrg(toIpOrg_).mint(owner); + IPOrgAsset memory ipOrgAsset = IPOrgAsset(toIpOrg_, ipOrgAssetId_); + ipOrgAssets[id] = ipOrgAsset; + ipAssetId[address(toIpOrg_)][ipOrgAssetId_] = id; + } + + + /// @dev Sets the IPOrg token and contract metadata. + /// @param ipOrg_ The address of the IP Org whose metadata is changing. + /// @param baseURI_ The new base URI to assign for the IP Org. + /// @param contractURI_ The new base contract URI to assign for the IP Org. + function _setMetadata( + address ipOrg_, + string memory baseURI_, + string memory contractURI_ + ) internal { + ipOrgConfigs[ipOrg_] = Registration.IPOrgConfig({ + baseURI: baseURI_, + contractURI: contractURI_ + }); + emit MetadataUpdated(ipOrg_, baseURI_, contractURI_); + } + + /// @dev Verifies the caller of a configuration action. + /// TODO(leeren): Deprecate in favor of policy-based function auth. + function _verifyConfigCaller(IIPOrg ipOrg_, address caller_) private view { + if (ipOrg_.owner() != caller_) { + revert Errors.Unauthorized(); + } + } + + /// @dev Returns the administrator for the registration module hooks. + /// TODO(kingter) Define the administrator for this call. + function _hookRegistryAdmin() + internal + view + virtual + override + returns (address) + { + return address(0); + } + + function _hookRegistryKey( + IIPOrg ipOrg_, + address, + bytes calldata params_ + ) internal view virtual override returns(bytes32) { + return keccak256(abi.encode(address(ipOrg_), "REGISTRATION")); + } + +} diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index 0b8e8a57..4054c0f3 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -245,4 +245,4 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled LibRelationship.CreateRelationshipParams memory createParams = abi.decode(params_, (LibRelationship.CreateRelationshipParams)); return keccak256(abi.encode(address(ipOrg_), createParams.relType)); } -} \ No newline at end of file +} diff --git a/lib/forge-std/src/console.sol b/lib/forge-std/src/console.sol index ad57e536..86ea1382 100644 --- a/lib/forge-std/src/console.sol +++ b/lib/forge-std/src/console.sol @@ -1530,4 +1530,4 @@ library console { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } -} \ No newline at end of file +} diff --git a/lib/forge-std/src/console2.sol b/lib/forge-std/src/console2.sol index 8596233d..87423cfb 100644 --- a/lib/forge-std/src/console2.sol +++ b/lib/forge-std/src/console2.sol @@ -1543,4 +1543,4 @@ library console2 { _sendLogPayload(abi.encodeWithSignature("log(address,address,address,address)", p0, p1, p2, p3)); } -} \ No newline at end of file +} diff --git a/script/foundry/deployment/Main.s.sol b/script/foundry/deployment/Main.s.sol index a7bf6441..138b090b 100644 --- a/script/foundry/deployment/Main.s.sol +++ b/script/foundry/deployment/Main.s.sol @@ -7,7 +7,7 @@ import "script/foundry/utils/StringUtil.sol"; import "script/foundry/utils/BroadcastManager.s.sol"; import "script/foundry/utils/JsonDeploymentHandler.s.sol"; import "contracts/ip-org/IPOrg.sol"; -import "contracts/ip-org/IPOrgFactory.sol"; +import "contracts/ip-org/IPOrgController.sol"; import "contracts/access-control/AccessControlSingleton.sol"; import "contracts/modules/licensing/LicensingModule.sol"; import "test/foundry/mocks/MockCollectNFT.sol"; @@ -65,14 +65,14 @@ import { AccessControl } from "contracts/lib/AccessControl.sol"; // accessControl = newAddress; // /// IP_ORG_FACTORY REGISTRY -// contractKey = "IPOrgFactory-Impl"; +// contractKey = "IPOrgController-Impl"; // console.log(string.concat("Deploying ", contractKey, "...")); -// newAddress = address(new IPOrgFactory()); +// newAddress = address(new IPOrgController()); // _writeAddress(contractKey, newAddress); // console.log(string.concat(contractKey, " deployed to:"), newAddress); -// contractKey = "IPOrgFactory-Proxy"; +// contractKey = "IPOrgController-Proxy"; // console.log(string.concat("Deploying ", contractKey, "...")); // newAddress = _deployUUPSProxy( diff --git a/test/foundry/IPOrgTest.t.sol b/test/foundry/IPOrgTest.t.sol index 807813aa..07534f15 100644 --- a/test/foundry/IPOrgTest.t.sol +++ b/test/foundry/IPOrgTest.t.sol @@ -3,7 +3,8 @@ pragma solidity ^0.8.13; import { Errors } from "contracts/lib/Errors.sol"; import { IPOrg } from "contracts/ip-org/IPOrg.sol"; -import { IPOrgFactory } from "contracts/ip-org/IPOrgFactory.sol"; +import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; +import { ModuleRegistry } from "contracts/modules/ModuleRegistry.sol"; import { IPOrgParams } from "contracts/lib/IPOrgParams.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; import { AccessControlSingleton } from "contracts/access-control/AccessControlSingleton.sol"; @@ -12,7 +13,7 @@ import { AccessControlHelper } from "./utils/AccessControlHelper.sol"; import { MockCollectNFT } from "./mocks/MockCollectNFT.sol"; import { MockCollectModule } from "./mocks/MockCollectModule.sol"; import { MockLicensingModule } from "./mocks/MockLicensingModule.sol"; -import { MockIPOrgFactory } from "./mocks/MockIPOrgFactory.sol"; +import { MockIPOrgController } from "./mocks/MockIPOrgController.sol"; import 'test/foundry/utils/ProxyHelper.sol'; import "forge-std/Test.sol"; @@ -25,7 +26,7 @@ contract IPOrgTest is Test, ProxyHelper, AccessControlHelper { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); IPAssetRegistry public registry; - IPOrgFactory public ipOrgFactory; + IPOrgController public ipOrgController; IPOrg public ipOrg; uint256 internal ipOrgOwnerPk = 0xa11ce; @@ -34,10 +35,12 @@ contract IPOrgTest is Test, ProxyHelper, AccessControlHelper { function setUp() public { _setupAccessControl(); _grantRole(vm, AccessControl.IPORG_CREATOR_ROLE, ipOrgOwner); - registry = new IPAssetRegistry(); - address implementation = address(new IPOrgFactory()); - ipOrgFactory = IPOrgFactory( + address moduleRegistry = address(new ModuleRegistry(address(accessControl))); + registry = new IPAssetRegistry(moduleRegistry); + + address implementation = address(new IPOrgController(moduleRegistry)); + ipOrgController = IPOrgController( _deployUUPSProxy( implementation, abi.encodeWithSelector( @@ -47,16 +50,9 @@ contract IPOrgTest is Test, ProxyHelper, AccessControlHelper { ); } - function test_ipOrgFactory_registerIpOrg() public { - IPOrgParams.RegisterIPOrgParams memory ipOrgParams = IPOrgParams.RegisterIPOrgParams( - address(registry), - "name", - "symbol", - "description", - "uri" - ); + function test_ipOrgController_registerIpOrg() public { vm.prank(ipOrgOwner); - ipOrg = IPOrg(ipOrgFactory.registerIpOrg(ipOrgParams)); + ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol")); } } diff --git a/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol b/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol index e8212b4d..67d056f3 100644 --- a/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol +++ b/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol @@ -59,85 +59,85 @@ contract BaseCollectModuleTest is BaseTest { /// @notice Tests whether collect reverts if the IP asset being collected from does not exist. - function test_CollectModuleCollectNonExistentIPAssetReverts(uint256 nonExistentipAssetId, uint8 ipAssetType) createIpAsset(collector, ipAssetType) public virtual { - vm.assume(nonExistentipAssetId != ipAssetId); - vm.expectRevert(Errors.CollectModule_IPAssetNonExistent.selector); - _collect(nonExistentipAssetId); - } - - /// @notice Tests that collects with the module-default collect NFT succeed. - function test_CollectModuleCollectDefaultCollectNFT(uint8 ipAssetType) createIpAsset(collector, ipAssetType) public { - assertEq(collectModule.getCollectNFT(ipAssetId), address(0)); - vm.expectEmit(true, true, false, false, address(collectModule)); - emit NewCollectNFT( - ipAssetId, - defaultCollectNftImpl - ); - vm.expectEmit(true, true, true, false, address(collectModule)); - emit Collected( - ipAssetId, - collector, - defaultCollectNftImpl, - 0, - "", - "" - ); - (address collectNft, uint256 collectNftId) = _collect(ipAssetId); - assertEq(collectModule.getCollectNFT(ipAssetId), collectNft); - assertTrue(ICollectNFT(collectNft).ownerOf(collectNftId) == cal); - assertEq(collectModule.getCollectNFT(ipAssetId), collectNft); - } - - /// @notice Tests that collects with customized collect NFTs succeed. - function test_CollectModuleCollectCustomCollectNFT(uint8 ipAssetType) public createIpAsset(collector, ipAssetType) { - assertEq(collectModule.getCollectNFT(ipAssetId), address(0)); - vm.expectEmit(true, true, false, false, address(collectModule)); - emit NewCollectNFT( - ipAssetId, - defaultCollectNftImpl - ); - vm.expectEmit(true, true, true, false, address(collectModule)); - emit Collected( - ipAssetId, - collector, - defaultCollectNftImpl, - 0, - "", - "" - ); - (address collectNft, uint256 collectNftId) = _collect(ipAssetId); - assertEq(collectModule.getCollectNFT(ipAssetId), collectNft); - assertTrue(ICollectNFT(collectNft).ownerOf(collectNftId) == cal); - } - - /// @notice Tests expected behavior of the collect module constructor. - function test_CollectModuleConstructor() public { - MockCollectModule mockCollectModule = new MockCollectModule(address(registry), defaultCollectNftImpl); - assertEq(address(mockCollectModule.REGISTRY()), address(registry)); - } - - /// @notice Tests expected behavior of collect module initialization. - function test_CollectModuleInit() public { - assertEq(address(0), collectModule.getCollectNFT(ipAssetId)); - } - - /// @notice Tests collect module reverts on unauthorized calls. - function test_CollectModuleInitCollectInvalidCallerReverts(uint256 nonExistentIPOrgId, uint8 ipAssetType) public createIpAsset(collector, ipAssetType) { - vm.expectRevert(Errors.CollectModule_CallerUnauthorized.selector); - vm.prank(address(this)); - collectModule.initCollect(Collect.InitCollectParams({ - ipAssetId: ipAssetId, - collectNftImpl: defaultCollectNftImpl, - data: "" - })); - } - - /// @notice Tests collect module reverts on duplicate initialization. - function test_CollectModuleDuplicateInitReverts(uint8 ipAssetType) createIpAsset(collector, ipAssetType) public { - vm.expectRevert(Errors.CollectModule_IPAssetAlreadyInitialized.selector); - vm.prank(address(ipOrg)); - _initCollectModule(defaultCollectNftImpl); - } + // function test_CollectModuleCollectNonExistentIPAssetReverts(uint256 nonExistentipAssetId, uint8 ipAssetType) createIpAsset(collector, ipAssetType) public virtual { + // vm.assume(nonExistentipAssetId != ipAssetId); + // vm.expectRevert(Errors.CollectModule_IPAssetNonExistent.selector); + // _collect(nonExistentipAssetId); + // } + + // /// @notice Tests that collects with the module-default collect NFT succeed. + // function test_CollectModuleCollectDefaultCollectNFT(uint8 ipAssetType) createIpAsset(collector, ipAssetType) public { + // assertEq(collectModule.getCollectNFT(ipAssetId), address(0)); + // vm.expectEmit(true, true, false, false, address(collectModule)); + // emit NewCollectNFT( + // ipAssetId, + // defaultCollectNftImpl + // ); + // vm.expectEmit(true, true, true, false, address(collectModule)); + // emit Collected( + // ipAssetId, + // collector, + // defaultCollectNftImpl, + // 0, + // "", + // "" + // ); + // (address collectNft, uint256 collectNftId) = _collect(ipAssetId); + // assertEq(collectModule.getCollectNFT(ipAssetId), collectNft); + // assertTrue(ICollectNFT(collectNft).ownerOf(collectNftId) == cal); + // assertEq(collectModule.getCollectNFT(ipAssetId), collectNft); + // } + + // /// @notice Tests that collects with customized collect NFTs succeed. + // function test_CollectModuleCollectCustomCollectNFT(uint8 ipAssetType) public createIpAsset(collector, ipAssetType) { + // assertEq(collectModule.getCollectNFT(ipAssetId), address(0)); + // vm.expectEmit(true, true, false, false, address(collectModule)); + // emit NewCollectNFT( + // ipAssetId, + // defaultCollectNftImpl + // ); + // vm.expectEmit(true, true, true, false, address(collectModule)); + // emit Collected( + // ipAssetId, + // collector, + // defaultCollectNftImpl, + // 0, + // "", + // "" + // ); + // (address collectNft, uint256 collectNftId) = _collect(ipAssetId); + // assertEq(collectModule.getCollectNFT(ipAssetId), collectNft); + // assertTrue(ICollectNFT(collectNft).ownerOf(collectNftId) == cal); + // } + + // /// @notice Tests expected behavior of the collect module constructor. + // function test_CollectModuleConstructor() public { + // MockCollectModule mockCollectModule = new MockCollectModule(address(registry), defaultCollectNftImpl); + // assertEq(address(mockCollectModule.REGISTRY()), address(registry)); + // } + + // /// @notice Tests expected behavior of collect module initialization. + // function test_CollectModuleInit() public { + // assertEq(address(0), collectModule.getCollectNFT(ipAssetId)); + // } + + // /// @notice Tests collect module reverts on unauthorized calls. + // function test_CollectModuleInitCollectInvalidCallerReverts(uint256 nonExistentIPOrgId, uint8 ipAssetType) public createIpAsset(collector, ipAssetType) { + // vm.expectRevert(Errors.CollectModule_CallerUnauthorized.selector); + // vm.prank(address(this)); + // collectModule.initCollect(Collect.InitCollectParams({ + // ipAssetId: ipAssetId, + // collectNftImpl: defaultCollectNftImpl, + // data: "" + // })); + // } + + // /// @notice Tests collect module reverts on duplicate initialization. + // function test_CollectModuleDuplicateInitReverts(uint8 ipAssetType) createIpAsset(collector, ipAssetType) public { + // vm.expectRevert(Errors.CollectModule_IPAssetAlreadyInitialized.selector); + // vm.prank(address(ipOrg)); + // _initCollectModule(defaultCollectNftImpl); + // } /// @dev Helper function that initializes a collect module. /// @param collectNftImpl Collect NFT impl address used for collecting. diff --git a/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol b/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol index dae11621..5387afcc 100644 --- a/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol +++ b/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol @@ -94,233 +94,233 @@ contract CollectPaymentModuleBaseTest is BaseCollectModuleTest { vm.stopPrank(); } - /// @notice Tests that the collect payment module is correctly initialized. - function test_CollectPaymentModuleInit() public parameterizePaymentInfo(paymentSuite()) { - Collect.CollectPaymentInfo memory p = collectPaymentModule.getPaymentInfo(ipAssetId); - assertEq(p.paymentToken, paymentInfo.paymentToken); - assertEq(uint8(p.paymentType), uint8(paymentInfo.paymentType)); - assertEq(p.paymentAmount, paymentInfo.paymentAmount); - assertEq(p.paymentRecipient, paymentInfo.paymentRecipient); - } - - /// @notice Tests that native payments with no sent funds revert. - function test_CollectPaymentModuleZeroPaymentReverts() public { - paymentInfo = Collect.CollectPaymentInfo(address(0), Collect.PaymentType.NATIVE, 0 ether, alice); - vm.expectRevert(Errors.CollectPaymentModule_AmountInvalid.selector); - _createIpAsset(collector, 1, abi.encode(paymentInfo)); - } - - /// @notice Tests that payments with invalid settings revert. - function test_CollectPaymentModuleInvalidSettingsReverts() public { - paymentInfo = Collect.CollectPaymentInfo(address(erc20), Collect.PaymentType.NATIVE, 1 ether, alice); - vm.expectRevert(Errors.CollectPaymentModule_InvalidSettings.selector); - _createIpAsset(collector, 1, abi.encode(paymentInfo)); - } - - /// @notice Tests that payments with invalid tokens revert. - function test_CollectPaymentModuleInvalidTokenReverts() public { - paymentInfo = Collect.CollectPaymentInfo(bob, Collect.PaymentType.ERC20, 1 ether, alice); - vm.expectRevert(Errors.CollectPaymentModule_TokenInvalid.selector); - _createIpAsset(collector, 1, abi.encode(paymentInfo)); - } - - /// @notice Tests that native payments work as expected. - function test_CollectPaymentModuleNativeCollect() public parameterizePaymentInfo(paymentSuiteNative()) { - uint256 recipientStartingBalance = paymentRecipient.balance; - uint256 collectorStartingBalance = collector.balance; - paymentAmount = paymentParams.paymentAmount; - _collect(ipAssetId); - assertEq(collector.balance, collectorStartingBalance - paymentAmount); - assertEq(paymentRecipient.balance, recipientStartingBalance + paymentAmount); - } - - /// @notice Tests that native payments that fail revert. - function test_CollectPaymentModuleNativeTransferFailReverts() public { - address payable throwingReceiver = payable(address(new MockNativeTokenNonReceiver())); - - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(0), - paymentType: Collect.PaymentType.NATIVE, - paymentAmount: 10, - paymentRecipient: throwingReceiver - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(0), - paymentType: Collect.PaymentType.NATIVE, - paymentAmount: 10 - }); - ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); - - vm.prank(collector); - vm.expectRevert(Errors.CollectPaymentModule_NativeTransferFailed.selector); - collectModule.collect{value: 10}(Collect.CollectParams({ - ipAssetId: ipAssetId, - collector: collector, - collectData: abi.encode(paymentParams), - collectNftInitData: "", - collectNftData: "" - })); - } - - /// @notice Tests that payments with invalid parameters revert. - function test_CollectPaymentModuleInvalidPaymentParamsReverts() public { - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(erc20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10, - paymentRecipient: paymentRecipient - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(erc20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 1 - }); - ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); - vm.expectRevert(Errors.CollectPaymentModule_PaymentParamsInvalid.selector); - _collect(ipAssetId); - } - - /// @notice Tests that ERC20 payments with failing transfers revert. - function test_CollectPaymentModuleERC20TransferFailReverts() public { - MockThrowingERC20 throwingERC20 = new MockThrowingERC20("Story Protocol Mock Token", "SP", 18, MockThrowingERC20.TransferBehavior.Fail); - vm.prank(collector); - throwingERC20.mint(999999); - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(throwingERC20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10, - paymentRecipient: paymentRecipient - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(throwingERC20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10 - }); - ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); - vm.expectRevert(Errors.CollectPaymentModule_ERC20TransferFailed.selector); - _collect(ipAssetId); - } - - /// @notice Tests that ERC20 payments with invalid payments revert. - function test_CollectPaymentModuleERC20InvalidPaymentReverts() public { - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(erc20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10, - paymentRecipient: paymentRecipient - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(erc20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10 - }); - ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); - vm.expectRevert(Errors.CollectPaymentModule_NativeTokenNotAllowed.selector); - collectModule.collect{value: 10}(Collect.CollectParams({ - ipAssetId: ipAssetId, - collector: collector, - collectData: abi.encode(paymentParams), - collectNftInitData: "", - collectNftData: "" - })); - } - - /// @notice Tests that ERC20 payments with insufficient funds revert. - function test_CollectPaymentModuleERC20InsufficientFundsReverts() public { - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(erc20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 9999999, - paymentRecipient: paymentRecipient - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(erc20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 9999999 - }); - ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); - vm.expectRevert(Errors.CollectPaymentModule_PaymentInsufficient.selector); - _collect(ipAssetId); - - } - - /// @notice Tests that ERC20 payments with invalid ABI encoding revert. - function test_CollectPaymentModuleERC20TransferInvalidABIReverts() public { - MockThrowingERC20 throwingERC20 = new MockThrowingERC20("Story Protocol Mock Token", "SP", 18, MockThrowingERC20.TransferBehavior.ReturnInvalidABI); - vm.prank(collector); - throwingERC20.mint(999999); - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(throwingERC20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10, - paymentRecipient: paymentRecipient - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(throwingERC20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10 - }); - ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); - vm.expectRevert(Errors.CollectPaymentModule_ERC20TransferInvalidABIEncoding.selector); - _collect(ipAssetId); - } - - /// @notice Tests that ERC20 payments with invalid return values revert. - function test_CollectPaymentModuleERC20TransferInvalidReturnReverts() public { - MockThrowingERC20 throwingERC20 = new MockThrowingERC20("Story Protocol Mock Token", "SP", 18, MockThrowingERC20.TransferBehavior.ReturnFalse); - vm.prank(collector); - throwingERC20.mint(999999); - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(throwingERC20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10, - paymentRecipient: paymentRecipient - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(throwingERC20), - paymentType: Collect.PaymentType.ERC20, - paymentAmount: 10 - }); - ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); - vm.expectRevert(Errors.CollectPaymentModule_ERC20TransferInvalidReturnValue.selector); - _collect(ipAssetId); - } - - /// @notice Tests that ERC20 payments work as expected. - function test_CollectPaymentModuleERC20Collect() public parameterizePaymentInfo(paymentSuiteERC20()) { - uint256 recipientStartingBalance = erc20.balanceOf(paymentRecipient); - uint256 collectorStartingBalance = erc20.balanceOf(collector); - paymentAmount = paymentParams.paymentAmount; - _collect(ipAssetId); - assertEq(erc20.balanceOf(paymentRecipient), recipientStartingBalance + paymentAmount); - assertEq(erc20.balanceOf(collector), collectorStartingBalance - paymentAmount); - } - - /// @notice Tests that payments without sufficient funds revert. - function test_CollectPaymentModuleInsufficientFunds() public { - paymentInfo = Collect.CollectPaymentInfo({ - paymentToken: address(0), - paymentType: Collect.PaymentType.NATIVE, - paymentAmount: 10, - paymentRecipient: paymentRecipient - }); - paymentParams = Collect.CollectPaymentParams({ - paymentToken: address(0), - paymentType: Collect.PaymentType.NATIVE, - paymentAmount: 10 - }); - ipAssetId = _createIpAsset(alice, 1, abi.encode(paymentInfo)); - - vm.prank(collector); - vm.expectRevert(Errors.CollectPaymentModule_PaymentInsufficient.selector); - collectModule.collect{value: 0}(Collect.CollectParams({ - ipAssetId: ipAssetId, - collector: collector, - collectData: abi.encode(paymentParams), - collectNftInitData: "", - collectNftData: "" - })); - } + // /// @notice Tests that the collect payment module is correctly initialized. + // function test_CollectPaymentModuleInit() public parameterizePaymentInfo(paymentSuite()) { + // Collect.CollectPaymentInfo memory p = collectPaymentModule.getPaymentInfo(ipAssetId); + // assertEq(p.paymentToken, paymentInfo.paymentToken); + // assertEq(uint8(p.paymentType), uint8(paymentInfo.paymentType)); + // assertEq(p.paymentAmount, paymentInfo.paymentAmount); + // assertEq(p.paymentRecipient, paymentInfo.paymentRecipient); + // } + + // /// @notice Tests that native payments with no sent funds revert. + // function test_CollectPaymentModuleZeroPaymentReverts() public { + // paymentInfo = Collect.CollectPaymentInfo(address(0), Collect.PaymentType.NATIVE, 0 ether, alice); + // vm.expectRevert(Errors.CollectPaymentModule_AmountInvalid.selector); + // _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // } + + // /// @notice Tests that payments with invalid settings revert. + // function test_CollectPaymentModuleInvalidSettingsReverts() public { + // paymentInfo = Collect.CollectPaymentInfo(address(erc20), Collect.PaymentType.NATIVE, 1 ether, alice); + // vm.expectRevert(Errors.CollectPaymentModule_InvalidSettings.selector); + // _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // } + + // /// @notice Tests that payments with invalid tokens revert. + // function test_CollectPaymentModuleInvalidTokenReverts() public { + // paymentInfo = Collect.CollectPaymentInfo(bob, Collect.PaymentType.ERC20, 1 ether, alice); + // vm.expectRevert(Errors.CollectPaymentModule_TokenInvalid.selector); + // _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // } + + // /// @notice Tests that native payments work as expected. + // function test_CollectPaymentModuleNativeCollect() public parameterizePaymentInfo(paymentSuiteNative()) { + // uint256 recipientStartingBalance = paymentRecipient.balance; + // uint256 collectorStartingBalance = collector.balance; + // paymentAmount = paymentParams.paymentAmount; + // _collect(ipAssetId); + // assertEq(collector.balance, collectorStartingBalance - paymentAmount); + // assertEq(paymentRecipient.balance, recipientStartingBalance + paymentAmount); + // } + + // /// @notice Tests that native payments that fail revert. + // function test_CollectPaymentModuleNativeTransferFailReverts() public { + // address payable throwingReceiver = payable(address(new MockNativeTokenNonReceiver())); + + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(0), + // paymentType: Collect.PaymentType.NATIVE, + // paymentAmount: 10, + // paymentRecipient: throwingReceiver + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(0), + // paymentType: Collect.PaymentType.NATIVE, + // paymentAmount: 10 + // }); + // ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); + + // vm.prank(collector); + // vm.expectRevert(Errors.CollectPaymentModule_NativeTransferFailed.selector); + // collectModule.collect{value: 10}(Collect.CollectParams({ + // ipAssetId: ipAssetId, + // collector: collector, + // collectData: abi.encode(paymentParams), + // collectNftInitData: "", + // collectNftData: "" + // })); + // } + + // /// @notice Tests that payments with invalid parameters revert. + // function test_CollectPaymentModuleInvalidPaymentParamsReverts() public { + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(erc20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10, + // paymentRecipient: paymentRecipient + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(erc20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 1 + // }); + // ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // vm.expectRevert(Errors.CollectPaymentModule_PaymentParamsInvalid.selector); + // _collect(ipAssetId); + // } + + // /// @notice Tests that ERC20 payments with failing transfers revert. + // function test_CollectPaymentModuleERC20TransferFailReverts() public { + // MockThrowingERC20 throwingERC20 = new MockThrowingERC20("Story Protocol Mock Token", "SP", 18, MockThrowingERC20.TransferBehavior.Fail); + // vm.prank(collector); + // throwingERC20.mint(999999); + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(throwingERC20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10, + // paymentRecipient: paymentRecipient + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(throwingERC20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10 + // }); + // ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // vm.expectRevert(Errors.CollectPaymentModule_ERC20TransferFailed.selector); + // _collect(ipAssetId); + // } + + // /// @notice Tests that ERC20 payments with invalid payments revert. + // function test_CollectPaymentModuleERC20InvalidPaymentReverts() public { + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(erc20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10, + // paymentRecipient: paymentRecipient + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(erc20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10 + // }); + // ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // vm.expectRevert(Errors.CollectPaymentModule_NativeTokenNotAllowed.selector); + // collectModule.collect{value: 10}(Collect.CollectParams({ + // ipAssetId: ipAssetId, + // collector: collector, + // collectData: abi.encode(paymentParams), + // collectNftInitData: "", + // collectNftData: "" + // })); + // } + + // /// @notice Tests that ERC20 payments with insufficient funds revert. + // function test_CollectPaymentModuleERC20InsufficientFundsReverts() public { + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(erc20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 9999999, + // paymentRecipient: paymentRecipient + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(erc20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 9999999 + // }); + // ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // vm.expectRevert(Errors.CollectPaymentModule_PaymentInsufficient.selector); + // _collect(ipAssetId); + + // } + + // /// @notice Tests that ERC20 payments with invalid ABI encoding revert. + // function test_CollectPaymentModuleERC20TransferInvalidABIReverts() public { + // MockThrowingERC20 throwingERC20 = new MockThrowingERC20("Story Protocol Mock Token", "SP", 18, MockThrowingERC20.TransferBehavior.ReturnInvalidABI); + // vm.prank(collector); + // throwingERC20.mint(999999); + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(throwingERC20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10, + // paymentRecipient: paymentRecipient + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(throwingERC20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10 + // }); + // ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // vm.expectRevert(Errors.CollectPaymentModule_ERC20TransferInvalidABIEncoding.selector); + // _collect(ipAssetId); + // } + + // /// @notice Tests that ERC20 payments with invalid return values revert. + // function test_CollectPaymentModuleERC20TransferInvalidReturnReverts() public { + // MockThrowingERC20 throwingERC20 = new MockThrowingERC20("Story Protocol Mock Token", "SP", 18, MockThrowingERC20.TransferBehavior.ReturnFalse); + // vm.prank(collector); + // throwingERC20.mint(999999); + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(throwingERC20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10, + // paymentRecipient: paymentRecipient + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(throwingERC20), + // paymentType: Collect.PaymentType.ERC20, + // paymentAmount: 10 + // }); + // ipAssetId = _createIpAsset(collector, 1, abi.encode(paymentInfo)); + // vm.expectRevert(Errors.CollectPaymentModule_ERC20TransferInvalidReturnValue.selector); + // _collect(ipAssetId); + // } + + // /// @notice Tests that ERC20 payments work as expected. + // function test_CollectPaymentModuleERC20Collect() public parameterizePaymentInfo(paymentSuiteERC20()) { + // uint256 recipientStartingBalance = erc20.balanceOf(paymentRecipient); + // uint256 collectorStartingBalance = erc20.balanceOf(collector); + // paymentAmount = paymentParams.paymentAmount; + // _collect(ipAssetId); + // assertEq(erc20.balanceOf(paymentRecipient), recipientStartingBalance + paymentAmount); + // assertEq(erc20.balanceOf(collector), collectorStartingBalance - paymentAmount); + // } + + // /// @notice Tests that payments without sufficient funds revert. + // function test_CollectPaymentModuleInsufficientFunds() public { + // paymentInfo = Collect.CollectPaymentInfo({ + // paymentToken: address(0), + // paymentType: Collect.PaymentType.NATIVE, + // paymentAmount: 10, + // paymentRecipient: paymentRecipient + // }); + // paymentParams = Collect.CollectPaymentParams({ + // paymentToken: address(0), + // paymentType: Collect.PaymentType.NATIVE, + // paymentAmount: 10 + // }); + // ipAssetId = _createIpAsset(alice, 1, abi.encode(paymentInfo)); + + // vm.prank(collector); + // vm.expectRevert(Errors.CollectPaymentModule_PaymentInsufficient.selector); + // collectModule.collect{value: 0}(Collect.CollectParams({ + // ipAssetId: ipAssetId, + // collector: collector, + // collectData: abi.encode(paymentParams), + // collectNftInitData: "", + // collectNftData: "" + // })); + // } /// @notice Returns a list of parameterized payment test cases. function paymentSuite() internal returns (CollectPaymentSet[] memory) { diff --git a/test/foundry/_old_modules/collect/SimpleCollectModule.t.sol b/test/foundry/_old_modules/collect/SimpleCollectModule.t.sol index 5ac772f1..87389239 100644 --- a/test/foundry/_old_modules/collect/SimpleCollectModule.t.sol +++ b/test/foundry/_old_modules/collect/SimpleCollectModule.t.sol @@ -14,43 +14,43 @@ contract SimpleCollectModuleTest is BaseCollectModuleTest { } /// @notice Tests that unauthorized collects revert. - function test_CollectModuleCollectUnauthorizedReverts(uint8 ipAssetType) createIpAsset(collector, ipAssetType) public { - vm.prank(alice); - vm.expectRevert(Errors.CollectModule_CollectUnauthorized.selector); - collectModule.collect(Collect.CollectParams({ - ipAssetId: ipAssetId, - collector: collector, - collectData: "", - collectNftInitData: "", - collectNftData: "" - })); - } + // function test_CollectModuleCollectUnauthorizedReverts(uint8 ipAssetType) createIpAsset(collector, ipAssetType) public { + // vm.prank(alice); + // vm.expectRevert(Errors.CollectModule_CollectUnauthorized.selector); + // collectModule.collect(Collect.CollectParams({ + // ipAssetId: ipAssetId, + // collector: collector, + // collectData: "", + // collectNftInitData: "", + // collectNftData: "" + // })); + // } - /// @notice Tests that upgrades work as expected. - function test_CollectModuleUpgrade() public { - address newCollectModuleImpl = address(new SimpleCollectModule(address(registry), defaultCollectNftImpl)); - vm.prank(upgrader); + // /// @notice Tests that upgrades work as expected. + // function test_CollectModuleUpgrade() public { + // address newCollectModuleImpl = address(new SimpleCollectModule(address(registry), defaultCollectNftImpl)); + // vm.prank(upgrader); - bytes memory data = abi.encodeWithSelector( - bytes4(keccak256(bytes("DEFAULT_COLLECT_NFT_IMPL()"))) - ); - (bool success, ) = address(collectModule).call( - abi.encodeWithSignature( - "upgradeToAndCall(address,bytes)", - newCollectModuleImpl, - data - ) - ); - assertTrue(success); - } + // bytes memory data = abi.encodeWithSelector( + // bytes4(keccak256(bytes("DEFAULT_COLLECT_NFT_IMPL()"))) + // ); + // (bool success, ) = address(collectModule).call( + // abi.encodeWithSignature( + // "upgradeToAndCall(address,bytes)", + // newCollectModuleImpl, + // data + // ) + // ); + // assertTrue(success); + // } - /// @notice Tests whether collect reverts if the IP asset being collected from does not exist. - function test_CollectModuleCollectNonExistentIPAssetReverts(uint256 nonExistentipAssetId, uint8 ipAssetType) createIpAsset(collector, ipAssetType) public virtual override { - vm.assume(nonExistentipAssetId != ipAssetId); - vm.expectRevert(); - _collect(99); - } + // /// @notice Tests whether collect reverts if the IP asset being collected from does not exist. + // function test_CollectModuleCollectNonExistentIPAssetReverts(uint256 nonExistentipAssetId, uint8 ipAssetType) createIpAsset(collector, ipAssetType) public virtual override { + // vm.assume(nonExistentipAssetId != ipAssetId); + // vm.expectRevert(); + // _collect(99); + // } /// @notice Changes the base testing collect module deployment to deploy the mock payment collect module instead. function _deployCollectModule(address collectNftImpl) internal virtual override returns (address) { diff --git a/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol b/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol index a9e33124..72c3c5cf 100644 --- a/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol +++ b/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol @@ -45,58 +45,58 @@ contract CollectNFTBaseTest is BaseERC721Test, BaseTest { } /// @notice Tests whether collect module collection is successful. - function test_CollectNFTCollect(uint8 ipAssetType) public createCollectNFT(cal, ipAssetType) { - uint256 aliceBalance = collectNft.balanceOf(alice); - uint256 bobBalance = collectNft.balanceOf(bob); - uint256 totalSupply = collectNft.totalSupply(); - vm.startPrank(address(collectModule)); - collectNft.collect(alice, ""); - collectNft.collect(alice, ""); - collectNft.collect(bob, ""); - assertEq(collectNft.totalSupply(), totalSupply + 3); - assertEq(collectNft.balanceOf(alice), aliceBalance + 2); - assertEq(collectNft.balanceOf(bob), bobBalance + 1); - } - - /// @notice Tests whether collect on non-existent IP assets revert. - function test_CollectNFTNonExistentIPAssetReverts() public { - collectNft = ICollectNFT(Clones.clone(defaultCollectNftImpl)); - vm.expectRevert(Errors.CollectNFT_IPAssetNonExistent.selector); - collectNft.initialize(Collect.InitCollectNFTParams({ - registry: address(registry), - ipAssetOrg: address(ipOrg), - ipAssetId: 99, - data: "" - })); - } + // function test_CollectNFTCollect(uint8 ipAssetType) public createCollectNFT(cal, ipAssetType) { + // uint256 aliceBalance = collectNft.balanceOf(alice); + // uint256 bobBalance = collectNft.balanceOf(bob); + // uint256 totalSupply = collectNft.totalSupply(); + // vm.startPrank(address(collectModule)); + // collectNft.collect(alice, ""); + // collectNft.collect(alice, ""); + // collectNft.collect(bob, ""); + // assertEq(collectNft.totalSupply(), totalSupply + 3); + // assertEq(collectNft.balanceOf(alice), aliceBalance + 2); + // assertEq(collectNft.balanceOf(bob), bobBalance + 1); + // } + // + // /// @notice Tests whether collect on non-existent IP assets revert. + // function test_CollectNFTNonExistentIPAssetReverts() public { + // collectNft = ICollectNFT(Clones.clone(defaultCollectNftImpl)); + // vm.expectRevert(Errors.CollectNFT_IPAssetNonExistent.selector); + // collectNft.initialize(Collect.InitCollectNFTParams({ + // registry: address(registry), + // ipAssetOrg: address(ipOrg), + // ipAssetId: 99, + // data: "" + // })); + // } - /// @notice Tests whether initialization on a deployed collect NFT reverts. - function test_CollectNFTConstructorInitializeReverts() public { - collectNft = new MockCollectNFT(); - vm.expectRevert(Errors.CollectNFT_AlreadyInitialized.selector); - collectNft.initialize(Collect.InitCollectNFTParams({ - registry: address(registry), - ipAssetOrg: address(ipOrg), - ipAssetId: ipAssetId, - data: "" - })); - } + // /// @notice Tests whether initialization on a deployed collect NFT reverts. + // function test_CollectNFTConstructorInitializeReverts() public { + // collectNft = new MockCollectNFT(); + // vm.expectRevert(Errors.CollectNFT_AlreadyInitialized.selector); + // collectNft.initialize(Collect.InitCollectNFTParams({ + // registry: address(registry), + // ipAssetOrg: address(ipOrg), + // ipAssetId: ipAssetId, + // data: "" + // })); + // } - /// @notice Tests whether collect calls not made by the collect module revert. - function test_CollectNFTNonCollectModuleCallerReverts(uint8 ipAssetType) public createCollectNFT(cal, ipAssetType) { - vm.expectRevert(Errors.CollectNFT_CallerUnauthorized.selector); - collectNft.collect(address(this), ""); - } + // /// @notice Tests whether collect calls not made by the collect module revert. + // function test_CollectNFTNonCollectModuleCallerReverts(uint8 ipAssetType) public createCollectNFT(cal, ipAssetType) { + // vm.expectRevert(Errors.CollectNFT_CallerUnauthorized.selector); + // collectNft.collect(address(this), ""); + // } - /// @notice Tests whether re-initialization of collect module settings revert. - function test_CollectNFTInitializeTwiceReverts(uint8 ipAssetType) public createCollectNFT(cal, ipAssetType) { - vm.expectRevert(Errors.CollectNFT_AlreadyInitialized.selector); - collectNft.initialize(Collect.InitCollectNFTParams({ - registry: address(registry), - ipAssetOrg: address(ipOrg), - ipAssetId: ipAssetId, - data: "" - })); - } + // /// @notice Tests whether re-initialization of collect module settings revert. + // function test_CollectNFTInitializeTwiceReverts(uint8 ipAssetType) public createCollectNFT(cal, ipAssetType) { + // vm.expectRevert(Errors.CollectNFT_AlreadyInitialized.selector); + // collectNft.initialize(Collect.InitCollectNFTParams({ + // registry: address(registry), + // ipAssetOrg: address(ipOrg), + // ipAssetId: ipAssetId, + // data: "" + // })); + // } } diff --git a/test/foundry/_old_modules/licensing/RightsManager.Internal.t.sol b/test/foundry/_old_modules/licensing/RightsManager.Internal.t.sol index 4b50d41f..dd833137 100644 --- a/test/foundry/_old_modules/licensing/RightsManager.Internal.t.sol +++ b/test/foundry/_old_modules/licensing/RightsManager.Internal.t.sol @@ -14,7 +14,7 @@ // contract RightsManagerInternalTest is Test, ProxyHelper { // IPAssetRegistry registry; -// MockERC721 mockIPOrgFactory; +// MockERC721 mockIPOrgController; // RightsManagerHarness rightsManager; // address constant mockEventEmitter = address(0x1234567); // address constant mockLicensingModule = address(0x23445); @@ -25,7 +25,7 @@ // function setUp() public { // registry = new IPAssetRegistry(); -// mockIPOrgFactory = new MockERC721(); +// mockIPOrgController = new MockERC721(); // RightsManagerHarness impl = new RightsManagerHarness(); // rightsManager = RightsManagerHarness( // _deployUUPSProxy( diff --git a/test/foundry/hooks/TestBaseHook.t.sol b/test/foundry/hooks/TestBaseHook.t.sol index 7560e8e5..169c89c7 100644 --- a/test/foundry/hooks/TestBaseHook.t.sol +++ b/test/foundry/hooks/TestBaseHook.t.sol @@ -22,7 +22,7 @@ contract TestBaseHook is BaseTest { hook = new MockBaseHook(address(accessControl)); } - function test_baseHook_validateGoodConfig() public { + function test_baseHook_validateGoodConfig() public view { hook.validateConfig(abi.encode("GoodConfig")); } diff --git a/test/foundry/mocks/MockCallbackHandler.sol b/test/foundry/mocks/MockCallbackHandler.sol index b7fc8d1a..0762fc68 100644 --- a/test/foundry/mocks/MockCallbackHandler.sol +++ b/test/foundry/mocks/MockCallbackHandler.sol @@ -29,4 +29,4 @@ contract MockCallbackHandler is ERC165, ICallbackHandler { // Check if the interface ID is for the ICallbackHandler interface return interfaceId == type(ICallbackHandler).interfaceId || super.supportsInterface(interfaceId); } -} \ No newline at end of file +} diff --git a/test/foundry/mocks/MockIPAssetOrgFactory.sol b/test/foundry/mocks/MockIPAssetOrgFactory.sol index cb50c7ab..2ad91bfa 100644 --- a/test/foundry/mocks/MockIPAssetOrgFactory.sol +++ b/test/foundry/mocks/MockIPAssetOrgFactory.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; -contract MockIPOrgFactory { +contract MockIPOrgController { address ipAssetOrgAddress; diff --git a/test/foundry/mocks/MockIPOrg.sol b/test/foundry/mocks/MockIPOrg.sol index d7215ef7..ef11f9c6 100644 --- a/test/foundry/mocks/MockIPOrg.sol +++ b/test/foundry/mocks/MockIPOrg.sol @@ -11,15 +11,21 @@ contract MockIPOrg is IIPOrg { _owner = owner_; } - function version() external pure override returns (string memory) { - return "1"; + function ownerOf(uint256 id) external view returns (address) { + return _owner; } - function supportsInterface(bytes4) external pure override returns (bool) { - return true; + function burn(uint256 id) external override(IIPOrg) {} + + function contractURI() external pure returns (string memory) { + return ""; } - function owner() external view override returns (address) { + function transferFrom(address from, address to, uint256 id) external {} + + function mint(address owner_) external override(IIPOrg) returns (uint256 id) {} + + function owner() external view override(IIPOrg) returns (address) { return _owner; } } diff --git a/test/foundry/mocks/MockIPOrgFactory.sol b/test/foundry/mocks/MockIPOrgController.sol similarity index 93% rename from test/foundry/mocks/MockIPOrgFactory.sol rename to test/foundry/mocks/MockIPOrgController.sol index cb50c7ab..2ad91bfa 100644 --- a/test/foundry/mocks/MockIPOrgFactory.sol +++ b/test/foundry/mocks/MockIPOrgController.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; -contract MockIPOrgFactory { +contract MockIPOrgController { address ipAssetOrgAddress; diff --git a/test/foundry/mocks/MockSplit.sol b/test/foundry/mocks/MockSplit.sol index cf785b85..c6644c37 100644 --- a/test/foundry/mocks/MockSplit.sol +++ b/test/foundry/mocks/MockSplit.sol @@ -16,4 +16,4 @@ contract MockSplit { { token.transfer(address(splitMain), amount); } -} \ No newline at end of file +} diff --git a/test/foundry/mocks/MockTermsProcessor.sol b/test/foundry/mocks/MockTermsProcessor.sol index 3f57aa41..90a86050 100644 --- a/test/foundry/mocks/MockTermsProcessor.sol +++ b/test/foundry/mocks/MockTermsProcessor.sol @@ -31,4 +31,4 @@ contract MockTermsProcessor is ITermsProcessor, ERC165 { ) external view override returns (bool) { return _success; } -} \ No newline at end of file +} diff --git a/test/foundry/mocks/RightsManagerHarness.sol b/test/foundry/mocks/RightsManagerHarness.sol index 8861b566..96bbb284 100644 --- a/test/foundry/mocks/RightsManagerHarness.sol +++ b/test/foundry/mocks/RightsManagerHarness.sol @@ -10,6 +10,11 @@ import { Licensing } from "contracts/lib/modules/Licensing.sol"; contract RightsManagerHarness is IPOrg { + constructor( + address ipOrgController_, + address moduleRegistry_ + ) IPOrg(ipOrgController_, moduleRegistry_) {} + function mockMint(address to, uint256 tokenId) external { _mint(to, tokenId); } diff --git a/test/foundry/modules/relationships/LibUintArrayMask.t.sol b/test/foundry/modules/relationships/LibUintArrayMask.t.sol index 4cef4ad0..59f0ffbb 100644 --- a/test/foundry/modules/relationships/LibUintArrayMask.t.sol +++ b/test/foundry/modules/relationships/LibUintArrayMask.t.sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { LibUintArrayMask } from "contracts/lib/LibUintArrayMask.sol"; -import { IPOrgFactory } from "contracts/ip-org/IPOrgFactory.sol"; +import { IPOrgController } from "contracts/ip-org/IPOrgController.sol"; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { MockERC721 } from "test/foundry/mocks/MockERC721.sol"; diff --git a/test/foundry/utils/BaseTest.sol b/test/foundry/utils/BaseTest.sol index 0207666c..6bbbf9b4 100644 --- a/test/foundry/utils/BaseTest.sol +++ b/test/foundry/utils/BaseTest.sol @@ -7,7 +7,7 @@ import 'test/foundry/utils/AccessControlHelper.sol'; import "test/foundry/mocks/MockCollectNFT.sol"; import "test/foundry/mocks/MockCollectModule.sol"; import "contracts/StoryProtocol.sol"; -import "contracts/ip-org/IPOrgFactory.sol"; +import "contracts/ip-org/IPOrgController.sol"; import "contracts/ip-org/IPOrg.sol"; import "contracts/lib/IPOrgParams.sol"; @@ -33,8 +33,7 @@ import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { IPOrg public ipOrg; - address ipAssetOrgImpl; - IPOrgFactory public ipOrgFactory; + IPOrgController public ipOrgController; ModuleRegistry public moduleRegistry; // LicensingModule public licensingModule; // ILicenseRegistry public licenseRegistry; @@ -63,15 +62,17 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { _setupAccessControl(); _grantRole(vm, AccessControl.UPGRADER_ROLE, upgrader); + // Setup module registry + moduleRegistry = new ModuleRegistry(address(accessControl)); + // Create IPAssetRegistry - registry = new IPAssetRegistry(); + registry = new IPAssetRegistry(address(moduleRegistry)); - // Create IPOrg Factory - ipOrgFactory = new IPOrgFactory(); - address ipOrgFactoryImpl = address(new IPOrgFactory()); - ipOrgFactory = IPOrgFactory( + // Create IPOrgController + address ipOrgControllerImpl = address(new IPOrgController(address(moduleRegistry))); + ipOrgController = IPOrgController( _deployUUPSProxy( - ipOrgFactoryImpl, + ipOrgControllerImpl, abi.encodeWithSelector( bytes4(keccak256(bytes("initialize(address)"))), address(accessControl) @@ -79,12 +80,12 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { ) ); - moduleRegistry = new ModuleRegistry(address(accessControl)); - spg = new StoryProtocol(ipOrgFactory, moduleRegistry); + spg = new StoryProtocol(ipOrgController, moduleRegistry); _grantRole(vm, AccessControl.IPORG_CREATOR_ROLE, address(spg)); _grantRole(vm, AccessControl.MODULE_EXECUTOR_ROLE, address(spg)); _grantRole(vm, AccessControl.MODULE_REGISTRAR_ROLE, address(this)); + // Create Relationship Module relationshipModule = new RelationshipModule( BaseModule.ModuleConstruction({ @@ -120,7 +121,11 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { ); vm.startPrank(ipAssetOrgOwner); - ipOrg = IPOrg(spg.registerIpOrg(ipAssetOrgParams)); + ipOrg = IPOrg(spg.registerIpOrg( + ipAssetOrgOwner, + ipAssetOrgParams.name, + ipAssetOrgParams.symbol + )); // licenseRegistry = ILicenseRegistry(ipOrg.getLicenseRegistry());