From f6d9079d3451a90ee361f061100b663ab72b7ee4 Mon Sep 17 00:00:00 2001 From: Ramarti Date: Mon, 27 Nov 2023 18:01:20 -0300 Subject: [PATCH] Connected IPOrg asset types to relationship module and removed IP Asset Type (#188) * ip org types and elimination of ip asset type * fixing tests * refactor registration tests * fixed existing tests * test iporg * test protocol relationship types configuration * test relationship setting * remove unused struct * removed commented out code * refactor tokenURI * fix --------- Co-authored-by: Raul --- contracts/IPAssetRegistry.sol | 5 - contracts/StoryProtocol.sol | 2 +- contracts/interfaces/IIPAssetRegistry.sol | 2 - contracts/interfaces/ip-org/IIPOrg.sol | 6 +- .../registration/IRegistrationModule.sol | 12 +- contracts/ip-org/IPOrg.sol | 22 +- contracts/lib/Errors.sol | 36 +++- contracts/lib/IPAsset.sol | 26 +-- contracts/lib/LibUintArrayMask.sol | 3 +- contracts/lib/modules/LibRelationship.sol | 2 - contracts/lib/modules/Registration.sol | 2 +- .../registration/RegistrationModule.sol | 60 ++++-- .../relationships/RelationshipModule.sol | 49 +++-- ...rgTest.t.sol => IPOrgControllerTest.t.sol} | 57 ++++- .../collect/BaseCollectModuleTest.sol | 2 +- .../collect/CollectPaymentModuleBase.t.sol | 4 +- .../collect/nft/CollectNFTBase.t.sol | 2 +- test/foundry/mocks/MockIPOrg.sol | 6 +- .../licensing/LicensingModule.Licensing.sol | 4 +- .../modules/registration/RegistrationTest.sol | 25 +-- .../relationships/LibUintArrayMask.t.sol | 7 - .../RelationshipModule.Config.t.sol | 104 ++++++++- .../RelationshipModule.Setting.t.sol | 198 +++++++++++++++--- test/foundry/utils/BaseTest.sol | 21 +- 24 files changed, 508 insertions(+), 149 deletions(-) rename test/foundry/{IPOrgTest.t.sol => IPOrgControllerTest.t.sol} (57%) diff --git a/contracts/IPAssetRegistry.sol b/contracts/IPAssetRegistry.sol index 898bbe12..c56eca2b 100644 --- a/contracts/IPAssetRegistry.sol +++ b/contracts/IPAssetRegistry.sol @@ -15,7 +15,6 @@ 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 ipOrg; // Address of the governing entity of the IP asset. @@ -57,13 +56,11 @@ contract IPAssetRegistry is IIPAssetRegistry { /// @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 ipOrg_, address registrant_, string memory name_, - uint64 ipAssetType_, bytes32 hash_ ) public onlyRegistrationModule returns (uint256 ipAssetId) { @@ -76,7 +73,6 @@ contract IPAssetRegistry is IIPAssetRegistry { uint64 registrationDate = uint64(block.timestamp); _ipAssets[ipAssetId] = IPA({ name: name_, - ipAssetType: ipAssetType_, // For now, let's assume 0 == unset, 1 is OK. TODO: Add status enum and synch with License status status: 1, registrant: registrant_, @@ -87,7 +83,6 @@ contract IPAssetRegistry is IIPAssetRegistry { emit Registered( ipAssetId, name_, - ipAssetType_, ipOrg_, registrant_, hash_ diff --git a/contracts/StoryProtocol.sol b/contracts/StoryProtocol.sol index fbe14fa7..119b297b 100644 --- a/contracts/StoryProtocol.sol +++ b/contracts/StoryProtocol.sol @@ -135,7 +135,7 @@ contract StoryProtocol is Multicall { Registration.TRANSFER_IP_ASSET, abi.encode(from_, to_, ipAssetId_) ); - bytes memory result = MODULE_REGISTRY.execute( + MODULE_REGISTRY.execute( IIPOrg(ipOrg_), msg.sender, ModuleRegistryKeys.REGISTRATION_MODULE, diff --git a/contracts/interfaces/IIPAssetRegistry.sol b/contracts/interfaces/IIPAssetRegistry.sol index 76422c8d..51e8f8d1 100644 --- a/contracts/interfaces/IIPAssetRegistry.sol +++ b/contracts/interfaces/IIPAssetRegistry.sol @@ -9,14 +9,12 @@ interface IIPAssetRegistry { /// @notice Emits when a new IP asset is registered. /// @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 ipOrg_, address indexed registrant_, bytes32 hash_ diff --git a/contracts/interfaces/ip-org/IIPOrg.sol b/contracts/interfaces/ip-org/IIPOrg.sol index ae0dddbc..1bcfbdaa 100644 --- a/contracts/interfaces/ip-org/IIPOrg.sol +++ b/contracts/interfaces/ip-org/IIPOrg.sol @@ -24,8 +24,9 @@ interface IIPOrg { /// @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. + /// @param assetType The IP Org asset type. /// @return id The local identifier of the minted IP Org wrapped asset. - function mint(address owner) external returns (uint256 id); + function mint(address owner, uint8 assetType) external returns (uint256 id); /// @notice Gets the current owner of the IP Org. function owner() external view returns (address); @@ -33,4 +34,7 @@ interface IIPOrg { /// @notice Returns contract-level metadata for the IP Org. function contractURI() external view returns (string memory); + /// @notice Returns the Ip Org asset type for a given IP Org asset. + function ipOrgAssetType(uint256 id_) external view returns (uint8); + } diff --git a/contracts/interfaces/modules/registration/IRegistrationModule.sol b/contracts/interfaces/modules/registration/IRegistrationModule.sol index fdabb032..fef0dd98 100644 --- a/contracts/interfaces/modules/registration/IRegistrationModule.sol +++ b/contracts/interfaces/modules/registration/IRegistrationModule.sol @@ -22,7 +22,7 @@ interface IRegistrationModule { /// @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 ipOrgAssetType_ The numerical id of the IP asset type. /// @param hash_ The content hash of the registered IP asset. /// @param mediaUrl_ The media URL of the registered IP asset. event IPAssetRegistered( @@ -31,7 +31,7 @@ interface IRegistrationModule { uint256 ipOrgAssetId_, address indexed owner_, string name_, - uint64 indexed ipAssetType_, + uint8 indexed ipOrgAssetType_, bytes32 hash_, string mediaUrl_ ); @@ -62,12 +62,18 @@ interface IRegistrationModule { /// @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. + /// @param ipOrgAssetType_ The IP Org asset type. /// @return The token URI associated with the IP Org. - function tokenURI(address ipOrg_, uint256 ipOrgAssetId_) external view returns (string memory); + function tokenURI(address ipOrg_, uint256 ipOrgAssetId_, uint8 ipOrgAssetType_) 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); + /// @notice get the ip Asset types of an ipOrg + function getIpOrgAssetTypes(address ipOrg_) external view returns (string[] memory); + + /// @notice Returns true if the index for an IP Org asset type is supported. + function isValidIpOrgAssetType(address ipOrg_, uint8 index) external view returns (bool); } diff --git a/contracts/ip-org/IPOrg.sol b/contracts/ip-org/IPOrg.sol index de426cf8..e67d7a67 100644 --- a/contracts/ip-org/IPOrg.sol +++ b/contracts/ip-org/IPOrg.sol @@ -21,10 +21,10 @@ contract IPOrg is { /// @notice Tracks the last index of the IP asset wrapper. - uint256 lastIndex = 0; + uint256 public lastIndex; /// @notice Tracks the total number of IP Assets owned by the IP org. - uint256 totalSupply = 0; + uint256 public totalSupply; // Address of the module registry. IModuleRegistry public immutable MODULE_REGISTRY; @@ -32,6 +32,9 @@ contract IPOrg is // Address of the IP Org Controller. address public immutable CONTROLLER; + /// @notice Tracks the IP asset types associated with the each IP asset wrapper. + mapping(uint256 => uint8) private _ipOrgAssetTypes; + /// @notice Restricts calls to being through the registration module. modifier onlyRegistrationModule() { if (IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) != msg.sender) { @@ -67,7 +70,7 @@ contract IPOrg is uint256 tokenId_ ) public view override returns (string memory) { address registrationModule = IModuleRegistry(MODULE_REGISTRY).protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE); - return IRegistrationModule(registrationModule).tokenURI(address(this), tokenId_); + return IRegistrationModule(registrationModule).tokenURI(address(this), tokenId_, ipOrgAssetType(tokenId_)); } /// @notice Retrieves the contract URI for the IP Org collection. @@ -100,9 +103,10 @@ contract IPOrg is } /// @notice Registers a new IP Asset wrapper for the IP Org. - function mint(address owner_) public onlyRegistrationModule returns (uint256 id) { + function mint(address owner_, uint8 assetType_) public onlyRegistrationModule returns (uint256 id) { totalSupply++; id = ++lastIndex; + _ipOrgAssetTypes[id] = assetType_; _mint(owner_, id); } @@ -125,4 +129,14 @@ contract IPOrg is _transfer(from_, to_, id_); } + /// Returns the IP Org asset type for a given IP Org asset. + /// @dev reverts if id does not exist. + function ipOrgAssetType(uint256 id_) public view returns (uint8) { + if (!_exists(id_)) { + revert Errors.IPOrg_IdDoesNotExist(); + } + return _ipOrgAssetTypes[id_]; + } + + } diff --git a/contracts/lib/Errors.sol b/contracts/lib/Errors.sol index 05bae324..89856bce 100644 --- a/contracts/lib/Errors.sol +++ b/contracts/lib/Errors.sol @@ -191,9 +191,6 @@ library Errors { //////////////////////////////////////////////////////////////////////////// error LibUintArrayMask_EmptyArray(); - error LibUintArrayMask_UndefinedArrayElement(); - /// @notice IP asset is invalid. - error LibUintArrayMask_InvalidType(IPAsset.IPAssetType ipAsset); //////////////////////////////////////////////////////////////////////////// // IPOrg // @@ -205,6 +202,9 @@ library Errors { /// @notice Licensing is not configured. error IPOrg_LicensingNotConfigured(); + /// @notice IP Org wrapper id does not exist. + error IPOrg_IdDoesNotExist(); + //////////////////////////////////////////////////////////////////////////// // IPOrgController // //////////////////////////////////////////////////////////////////////////// @@ -308,6 +308,13 @@ library Errors { /// @notice The registration execution action is not valid. error RegistrationModule_InvalidExecutionOperation(); + /// @notice IP asset type is not in the list of supported types for + /// the IP Org. + error RegistrationModule_InvalidIPAssetType(); + + /// @notice IPAsset types provided are more than the maximum allowed. + error RegistrationModule_TooManyAssetTypes(); + //////////////////////////////////////////////////////////////////////////// // RelationshipModule // //////////////////////////////////////////////////////////////////////////// @@ -333,16 +340,37 @@ library Errors { /// @notice The relationship destination IP type is not supported. error RelationshipModule_UnsupportedRelationshipDst(); + /// @notice Trying an unsupported config action error RelationshipModule_InvalidConfigOperation(); - + + /// @notice Unauthorized caller error RelationshipModule_CallerNotIpOrgOwner(); + + /// @notice Value not on Relatable enum error RelationshipModule_InvalidRelatable(); + + /// @notice Getting an invalid relationship type error RelationshipModule_RelTypeNotSet(string relType); + + /// @notice Relating invalid src addresss error RelationshipModule_InvalidSrcAddress(); + + /// @notice Relating invalid dst addresss error RelationshipModule_InvalidDstAddress(); + + /// @notice Relating unsupported src ipOrg asset type error RelationshipModule_InvalidSrcId(); + + /// @notice Relating unsupported dst ipOrg asset type error RelationshipModule_InvalidDstId(); + /// @notice For IPORG_ENTRY - IPORG_ENTRY relationships, + /// ipOrg address must be set + error RelationshipModule_IpOrgRelatableCannotBeProtocolLevel(); + + /// @notice Index is not found for the asset types of that IP Org. + error RelationshipModule_UnsupportedIpOrgIndexType(); + //////////////////////////////////////////////////////////////////////////// // RoyaltyNFT // //////////////////////////////////////////////////////////////////////////// diff --git a/contracts/lib/IPAsset.sol b/contracts/lib/IPAsset.sol index 0f8baeeb..2e48b6b3 100644 --- a/contracts/lib/IPAsset.sol +++ b/contracts/lib/IPAsset.sol @@ -7,14 +7,10 @@ import { Errors } from "./Errors.sol"; /// @title IP Asset Library /// @notice Library for constants, structs, and helper functions for IP assets. library IPAsset { - uint8 public constant EXTERNAL_ASSET = type(uint8).max; - - uint256 private constant _ID_RANGE = 10 ** 12; /// @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 ipOrg; // Address of the governing entity of the IP asset. @@ -22,32 +18,12 @@ library IPAsset { uint64 registrationDate; // Timestamp for which the IP asset was first registered. } - enum IPAssetType { - UNDEFINED, - STORY, - CHARACTER, - ART, - GROUP, - LOCATION, - ITEM - } - /// @notice Struct for packing parameters related to IP asset registration. struct RegisterIpAssetParams { string name; - uint64 ipAssetType; + uint8 ipOrgAssetType; address owner; bytes32 hash; string mediaUrl; } - - struct CreateIpAssetParams { - IPAsset.IPAssetType ipOrgAssetType; - uint64 ipAssetType; - string name; - bytes32 hash; - string mediaUrl; - bytes ipData; - } - } diff --git a/contracts/lib/LibUintArrayMask.sol b/contracts/lib/LibUintArrayMask.sol index 81a8b0d6..1b41a134 100644 --- a/contracts/lib/LibUintArrayMask.sol +++ b/contracts/lib/LibUintArrayMask.sol @@ -10,7 +10,7 @@ import { Errors } from "contracts/lib/Errors.sol"; /// @dev Gives tools to check if the "endpoints" of a relationship are valid, according to the allowed asset types set in the relationship config. library LibUintArrayMask { - uint8 public constant UNDEFINED = 0; + // uint8 public constant UNDEFINED = 0; /// @dev converts an array of types and the allows external flag to a mask, by setting the bits corresponding /// to the uint8 equivalent of the IPAsset types to 1. @@ -20,7 +20,6 @@ library LibUintArrayMask { if (assetTypes_.length == 0) revert Errors.LibUintArrayMask_EmptyArray(); uint256 mask = 0; for (uint256 i = 0; i < assetTypes_.length;) { - if (assetTypes_[i] == UNDEFINED) revert Errors.LibUintArrayMask_UndefinedArrayElement(); mask |= 1 << (uint256(assetTypes_[i]) & 0xff); unchecked { i++; diff --git a/contracts/lib/modules/LibRelationship.sol b/contracts/lib/modules/LibRelationship.sol index 1b0b0d06..b0925186 100644 --- a/contracts/lib/modules/LibRelationship.sol +++ b/contracts/lib/modules/LibRelationship.sol @@ -44,10 +44,8 @@ library LibRelationship { string relType; address srcAddress; uint srcId; - uint8 srcType; address dstAddress; uint dstId; - uint8 dstType; } address public constant PROTOCOL_LEVEL_RELATIONSHIP = address(0); diff --git a/contracts/lib/modules/Registration.sol b/contracts/lib/modules/Registration.sol index 0d3de7c0..aabf14de 100644 --- a/contracts/lib/modules/Registration.sol +++ b/contracts/lib/modules/Registration.sol @@ -14,8 +14,8 @@ library Registration { /// @notice Struct used for IP asset registration. struct RegisterIPAssetParams { address owner; + uint8 ipOrgAssetType; string name; - uint64 ipAssetType; bytes32 hash; string mediaUrl; } diff --git a/contracts/modules/registration/RegistrationModule.sol b/contracts/modules/registration/RegistrationModule.sol index 09f9d7a7..cc47875a 100644 --- a/contracts/modules/registration/RegistrationModule.sol +++ b/contracts/modules/registration/RegistrationModule.sol @@ -15,6 +15,7 @@ import { LibUintArrayMask } from "contracts/lib/LibUintArrayMask.sol"; import { Errors } from "contracts/lib/Errors.sol"; import { IPAsset } from "contracts/lib/IPAsset.sol"; + /// @title Registration Module /// @notice Handles registration and transferring of IP assets.. contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled { @@ -39,6 +40,9 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @notice IP Org asset to its tokenURI. mapping(address => mapping(uint256 => string)) public tokenUris; + /// @notice Maximum number of Ip Org asset types. + uint256 public constant MAX_IP_ORG_ASSET_TYPES = type(uint8).max; + /// @notice Initializes the registration module. constructor( BaseModule.ModuleConstruction memory params_, @@ -75,7 +79,8 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @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) { + /// @param ipOrgAssetType_ The IP Org asset type. + function tokenURI(address ipOrg_, uint256 ipOrgAssetId_, uint8 ipOrgAssetType_) public view returns (string memory) { uint256 id = ipAssetId[ipOrg_][ipOrgAssetId_]; address owner = IIPOrg(ipOrg_).ownerOf(ipOrgAssetId_); if (owner == address(0)) { @@ -109,7 +114,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled 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": "IP Org Asset Type", "value": "', config.assetTypes[ipOrgAssetType_], '"},', '{"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), '"}' @@ -130,10 +135,16 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled )); } - function getIPAssetTypes(address ipOrg_) public view returns (string[] memory) { + /// @notice Gets the asset types of an IP Org. + function getIpOrgAssetTypes(address ipOrg_) public view returns (string[] memory) { return ipOrgConfigs[ipOrg_].assetTypes; } + /// @notice returns true if the index for an IP Org asset type is supported. + function isValidIpOrgAssetType(address ipOrg_, uint8 assetTypeIndex_) public view returns (bool) { + return assetTypeIndex_ < ipOrgConfigs[ipOrg_].assetTypes.length; + } + /// @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) { @@ -157,6 +168,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled if (params.owner != caller_) { revert Errors.RegistrationModule_InvalidCaller(); } + _verifyIpOrgAssetType(address(ipOrg_), params.ipOrgAssetType); } else { revert Errors.RegistrationModule_InvalidExecutionOperation(); } @@ -194,7 +206,14 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled 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, params.mediaUrl); + (uint256 ipAssetId__, uint256 ipOrgAssetId) = _registerIPAsset( + ipOrg_, + params.owner, + params.name, + params.ipOrgAssetType, + params.hash, + params.mediaUrl + ); return abi.encode(ipAssetId__, ipOrgAssetId); } return ""; @@ -204,14 +223,14 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @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 ipOrgAssetType_ A numerical identifier for the IP asset type. /// @param hash_ The content hash of the IP asset being registered. /// @param mediaUrl_ The media URL of the IP asset being registered. function _registerIPAsset( IIPOrg ipOrg_, address owner_, string memory name_, - uint64 ipAssetType_, + uint8 ipOrgAssetType_, bytes32 hash_, string memory mediaUrl_ ) internal returns (uint256 ipAssetId_, uint256 ipOrgAssetId_) { @@ -219,10 +238,9 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled address(ipOrg_), owner_, name_, - ipAssetType_, hash_ ); - ipOrgAssetId_ = ipOrg_.mint(owner_); + ipOrgAssetId_ = ipOrg_.mint(owner_, ipOrgAssetType_); ipAssetId[address(ipOrg_)][ipOrgAssetId_] = ipAssetId_; IPOrgAsset memory ipOrgAsset = IPOrgAsset(address(ipOrg_), ipOrgAssetId_); ipOrgAssets[ipAssetId_] = ipOrgAsset; @@ -235,7 +253,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled ipOrgAssetId_, owner_, name_, - ipAssetType_, + ipOrgAssetType_, hash_, mediaUrl_ ); @@ -267,11 +285,13 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @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. + /// @param toIpOrgType_ The type of the IP asset within the new IP Org. /// TODO(leeren) Expose this function to FE once IP Orgs are finalized. function _transferIPAssetToIPOrg( address fromIpOrg_, uint256 fromIpOrgAssetId_, address toIpOrg_, + uint8 toIpOrgType_, address from_, address to_ ) internal returns (uint256 ipAssetId_, uint256 ipOrgAssetId_) { @@ -286,7 +306,7 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled ipAssetId_, toIpOrg_ ); - ipOrgAssetId_ = IIPOrg(toIpOrg_).mint(owner); + ipOrgAssetId_ = IIPOrg(toIpOrg_).mint(owner, toIpOrgType_); IPOrgAsset memory ipOrgAsset = IPOrgAsset(toIpOrg_, ipOrgAssetId_); ipOrgAssets[id] = ipOrgAsset; ipAssetId[address(toIpOrg_)][ipOrgAssetId_] = id; @@ -295,15 +315,20 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled /// @dev Adds new IP asset types to an IP Org. /// @param ipOrg_ The address of the IP Org whose asset types we are adding. - /// @param ipAssetTypes_ String descriptors of the asset types being added. + /// @param ipOrgTypes_ String descriptors of the asset types being added. /// TODO: Add ability to deprecate asset types. function _addIPAssetTypes( address ipOrg_, - string[] memory ipAssetTypes_ + string[] memory ipOrgTypes_ ) internal { + uint256 assetsLength = ipOrgTypes_.length; + if (assetsLength > MAX_IP_ORG_ASSET_TYPES) { + revert Errors.RegistrationModule_TooManyAssetTypes(); + } Registration.IPOrgConfig storage ipOrg = ipOrgConfigs[ipOrg_]; - for (uint i = 0; i < ipAssetTypes_.length; i++) { - ipOrg.assetTypes.push(ipAssetTypes_[i]); + for (uint i = 0; i < assetsLength; i++) { + // TODO: this should be a set, and check empty strings + ipOrg.assetTypes.push(ipOrgTypes_[i]); } } @@ -330,6 +355,13 @@ contract RegistrationModule is BaseModule, IRegistrationModule, AccessControlled } } + function _verifyIpOrgAssetType(address ipOrg_, uint8 ipOrgAssetType_) private view { + uint8 length = uint8(ipOrgConfigs[ipOrg_].assetTypes.length); + if (ipOrgAssetType_ >= length) { + revert Errors.RegistrationModule_InvalidIPAssetType(); + } + } + function _hookRegistryKey( IIPOrg ipOrg_, address, diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index e7893572..9d26b612 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -10,6 +10,8 @@ import { AccessControl } from "contracts/lib/AccessControl.sol"; import { LibRelationship } from "contracts/lib/modules/LibRelationship.sol"; import { LibUintArrayMask } from "contracts/lib/LibUintArrayMask.sol"; import { Errors } from "contracts/lib/Errors.sol"; +import { IRegistrationModule } from "contracts/interfaces/modules/registration/IRegistrationModule.sol"; +import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol"; /// @title Relationship Module /// @notice Contract that handles the creation and management of relationships between entities. @@ -22,7 +24,6 @@ import { Errors } from "contracts/lib/Errors.sol"; /// - Addresses /// - External NFTs /// And combinations of them. -/// This allows Story Protocol to track attribution, IP remixes, sublicensing... /// NOTE: This is an alpha version, a more efficient way of storing and verifying relationships will be implemented in the future. contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled { @@ -73,15 +74,12 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled if (result.src == address(0) || result.dst == address(0)) { revert Errors.RelationshipModule_RelTypeNotSet(relType_); } + return result; } /// Gets relationship definition for a given relationship id function getRelationship(uint256 relationshipId_) external view returns (LibRelationship.Relationship memory) { return _relationships[relationshipId_]; - // TODO: store hash(src) -> hash(dst), - // easier to check if a relation exist - // we can traverse the graph - // if we delete one end, it's easier to propagate } /// Gets relationship id for a given relationship @@ -136,7 +134,7 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled /// @param allowedTypes_ ipOrg related types, if applicable /// @return a tuple with the accepted address and the subtype mask for this node of a /// relationship type definition - function addressConfigFor( + function _addressConfigFor( LibRelationship.Relatables relatable_, address ipOrg_, uint8[] memory allowedTypes_ @@ -144,7 +142,11 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled if (relatable_ == LibRelationship.Relatables.IPA) { return (address(IPA_REGISTRY), 0); } else if (relatable_ == LibRelationship.Relatables.IPORG_ENTRY) { - return (address(ipOrg_), LibUintArrayMask._convertToMask(allowedTypes_)); + if (ipOrg_ == address(0)) { + revert Errors.RelationshipModule_IpOrgRelatableCannotBeProtocolLevel(); + } + _verifySupportedIpOrgIndexType(ipOrg_, allowedTypes_); + return (ipOrg_, LibUintArrayMask._convertToMask(allowedTypes_)); } else if (relatable_ == LibRelationship.Relatables.LICENSE) { return (address(LICENSE_REGISTRY), 0); } else if (relatable_ == LibRelationship.Relatables.ADDRESS) { @@ -154,13 +156,28 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled } revert Errors.RelationshipModule_InvalidRelatable(); } + + function _verifySupportedIpOrgIndexType( + address ipOrg_, + uint8[] memory allowedTypes_ + ) private view { + IRegistrationModule regModule = IRegistrationModule( + MODULE_REGISTRY.protocolModule(ModuleRegistryKeys.REGISTRATION_MODULE) + ); + uint256 length = allowedTypes_.length; + for (uint256 i = 0; i < length; i++) { + if (!regModule.isValidIpOrgAssetType(ipOrg_, allowedTypes_[i])) { + revert Errors.RelationshipModule_UnsupportedIpOrgIndexType(); + } + } + } /// Configures a Relationship Type from the more user friendly AddRelationshipTypeParams struct, /// and adds it to the appropriate mapping (protocol or IPOrg) /// @param params_ AddRelationshipTypeParams function _addRelationshipType(LibRelationship.AddRelationshipTypeParams memory params_) private { - (address src, uint256 srcSubtypesMask) = addressConfigFor(params_.allowedElements.src, params_.ipOrg, params_.allowedSrcs); - (address dst, uint256 dstSubtypesMask) = addressConfigFor(params_.allowedElements.dst, params_.ipOrg, params_.allowedDsts); + (address src, uint256 srcSubtypesMask) = _addressConfigFor(params_.allowedElements.src, params_.ipOrg, params_.allowedSrcs); + (address dst, uint256 dstSubtypesMask) = _addressConfigFor(params_.allowedElements.dst, params_.ipOrg, params_.allowedDsts); LibRelationship.RelationshipType memory relDef = LibRelationship.RelationshipType({ src: src, srcSubtypesMask: srcSubtypesMask, @@ -211,8 +228,11 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled revert Errors.RelationshipModule_InvalidSrcAddress(); } } - if (LibUintArrayMask._isAssetTypeOnMask(relType.srcSubtypesMask, createParams.srcType)) { - revert Errors.RelationshipModule_InvalidSrcId(); + if (relType.srcSubtypesMask != 0) { + uint8 srcType = ipOrg_.ipOrgAssetType(createParams.srcId); + if (!LibUintArrayMask._isAssetTypeOnMask(relType.srcSubtypesMask, srcType)) { + revert Errors.RelationshipModule_InvalidSrcId(); + } } // Destination checks if (createParams.dstAddress == address(0)) { @@ -223,8 +243,11 @@ contract RelationshipModule is BaseModule, IRelationshipModule, AccessControlled revert Errors.RelationshipModule_InvalidDstAddress(); } } - if (LibUintArrayMask._isAssetTypeOnMask(relType.srcSubtypesMask, createParams.dstType)) { - revert Errors.RelationshipModule_InvalidDstId(); + if (relType.dstSubtypesMask != 0) { + uint8 dstType = ipOrg_.ipOrgAssetType(createParams.dstId); + if (!LibUintArrayMask._isAssetTypeOnMask(relType.dstSubtypesMask, dstType)) { + revert Errors.RelationshipModule_InvalidDstId(); + } } } diff --git a/test/foundry/IPOrgTest.t.sol b/test/foundry/IPOrgControllerTest.t.sol similarity index 57% rename from test/foundry/IPOrgTest.t.sol rename to test/foundry/IPOrgControllerTest.t.sol index 595dd9de..4c30fdea 100644 --- a/test/foundry/IPOrgTest.t.sol +++ b/test/foundry/IPOrgControllerTest.t.sol @@ -20,7 +20,7 @@ import { ModuleRegistryKeys } from "contracts/lib/modules/ModuleRegistryKeys.sol import 'test/foundry/utils/ProxyHelper.sol'; import "forge-std/Test.sol"; -contract IPOrgTest is Test, ProxyHelper, AccessControlHelper { +contract IPOrgControllerTest is Test, ProxyHelper, AccessControlHelper { using stdStorage for StdStorage; event CollectionCreated(address indexed collection, string name, string indexed symbol); @@ -77,4 +77,59 @@ contract IPOrgTest is Test, ProxyHelper, AccessControlHelper { ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); } + function test_ipOrgController_revert_tooManyAssetTypes() public { + uint256 maxAssetTypes = registrationModule.MAX_IP_ORG_ASSET_TYPES() + 1; + string[] memory ipAssetTypes = new string[](maxAssetTypes); + vm.expectRevert(Errors.RegistrationModule_TooManyAssetTypes.selector); + ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); + } + + function test_ipOrg_mint() public { + string[] memory ipAssetTypes = new string[](2); + ipAssetTypes[0] = "type1"; + ipAssetTypes[1] = "type2"; + ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); + moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, BaseModule(address(this))); + uint256 ipAssetId = ipOrg.mint(ipOrgOwner, 1); + assertEq(ipOrg.ipOrgAssetType(ipAssetId), 1); + assertEq(ipOrg.ownerOf(ipAssetId), ipOrgOwner); + assertEq(ipOrg.totalSupply(), 1); + } + + function test_ipOrg_revert_mintWhenNotRegistrationModule() public { + string[] memory ipAssetTypes = new string[](2); + ipAssetTypes[0] = "type1"; + ipAssetTypes[1] = "type2"; + ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); + vm.expectRevert(Errors.Unauthorized.selector); + ipOrg.mint(ipOrgOwner, 1); + } + + function test_ipOrg_burn() public { + string[] memory ipAssetTypes = new string[](2); + ipAssetTypes[0] = "type1"; + ipAssetTypes[1] = "type2"; + ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); + moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, BaseModule(address(this))); + uint256 ipAssetId = ipOrg.mint(ipOrgOwner, 1); + ipOrg.burn(ipAssetId); + vm.expectRevert(Errors.IPOrg_IdDoesNotExist.selector); + ipOrg.ipOrgAssetType(ipAssetId); + vm.expectRevert(); + ipOrg.ownerOf(ipAssetId); + } + + function test_ipOrg_revert_burnWhenNotRegistrationModule() public { + string[] memory ipAssetTypes = new string[](2); + ipAssetTypes[0] = "type1"; + ipAssetTypes[1] = "type2"; + ipOrg = IPOrg(ipOrgController.registerIpOrg(msg.sender, "name", "symbol", ipAssetTypes)); + moduleRegistry.registerProtocolModule(ModuleRegistryKeys.REGISTRATION_MODULE, BaseModule(address(this))); + uint256 ipAssetId = ipOrg.mint(ipOrgOwner, 1); + vm.prank(ipOrgOwner); + vm.expectRevert(Errors.Unauthorized.selector); + ipOrg.burn(ipAssetId); + } + + } diff --git a/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol b/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol index 67d056f3..b99bec22 100644 --- a/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol +++ b/test/foundry/_old_modules/collect/BaseCollectModuleTest.sol @@ -47,7 +47,7 @@ contract BaseCollectModuleTest is BaseTest { /// @param ipAssetOwner The owner address for the new IP asset. /// @param ipAssetType The type of the IP asset being created. modifier createIpAsset(address ipAssetOwner, uint8 ipAssetType) virtual { - ipAssetId = _createIpAsset(ipAssetOwner, ipAssetType, ""); + (ipAssetId, ) = _createIpAsset(ipAssetOwner, ipAssetType, ""); _; } diff --git a/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol b/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol index 5387afcc..fd239732 100644 --- a/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol +++ b/test/foundry/_old_modules/collect/CollectPaymentModuleBase.t.sol @@ -49,7 +49,7 @@ contract CollectPaymentModuleBaseTest is BaseCollectModuleTest { for (uint256 i = 0; i < length; ) { paymentInfo = paymentInfoSuite[i].info; paymentParams = paymentInfoSuite[i].params; - ipAssetId = _createIpAsset(alice, 1, abi.encode(paymentInfo)); + (ipAssetId, ) = _createIpAsset(alice, 1, abi.encode(paymentInfo)); _; i += 1; } @@ -60,7 +60,7 @@ contract CollectPaymentModuleBaseTest is BaseCollectModuleTest { /// @param ipAssetOwner The owner address for the new IP asset. /// @param ipAssetType The type of the IP asset being created. modifier createIpAsset(address ipAssetOwner, uint8 ipAssetType) override { - ipAssetId = _createIpAsset(ipAssetOwner, ipAssetType, abi.encode(paymentInfo)); + (ipAssetId, ) = _createIpAsset(ipAssetOwner, ipAssetType, abi.encode(paymentInfo)); _; } diff --git a/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol b/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol index 72c3c5cf..ff927035 100644 --- a/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol +++ b/test/foundry/_old_modules/collect/nft/CollectNFTBase.t.sol @@ -28,7 +28,7 @@ contract CollectNFTBaseTest is BaseERC721Test, BaseTest { /// @param ipAssetOwner The owner address for the new IP asset. /// @param ipAssetType The type of the IP asset being created. modifier createCollectNFT(address ipAssetOwner, uint8 ipAssetType) { - ipAssetId = _createIpAsset(ipAssetOwner, ipAssetType, ""); + (ipAssetId, ) = _createIpAsset(ipAssetOwner, ipAssetType, ""); collectNft = ICollectNFT(Clones.clone(defaultCollectNftImpl)); vm.prank(address(collectModule)); collectNft.initialize(Collect.InitCollectNFTParams({ diff --git a/test/foundry/mocks/MockIPOrg.sol b/test/foundry/mocks/MockIPOrg.sol index ef11f9c6..92896fd9 100644 --- a/test/foundry/mocks/MockIPOrg.sol +++ b/test/foundry/mocks/MockIPOrg.sol @@ -23,9 +23,13 @@ contract MockIPOrg is IIPOrg { function transferFrom(address from, address to, uint256 id) external {} - function mint(address owner_) external override(IIPOrg) returns (uint256 id) {} + function mint(address owner_, uint8 type_) external override(IIPOrg) returns (uint256 id) {} function owner() external view override(IIPOrg) returns (address) { return _owner; } + + function ipOrgAssetType(uint256 id_) external view override(IIPOrg) returns (uint8) { + return 0; + } } diff --git a/test/foundry/modules/licensing/LicensingModule.Licensing.sol b/test/foundry/modules/licensing/LicensingModule.Licensing.sol index d6f50f21..099a131b 100644 --- a/test/foundry/modules/licensing/LicensingModule.Licensing.sol +++ b/test/foundry/modules/licensing/LicensingModule.Licensing.sol @@ -21,8 +21,8 @@ contract LicensingModuleLicensingTest is BaseLicensingTest { uint256 rootIpaId; function setUp() public override { - super.setUp(); - rootIpaId = _createIpAsset(ipaOwner, 2, bytes("")); + super.setUp(); + (rootIpaId, ) = _createIpAsset(ipaOwner, 1, bytes("")); } function test_LicensingModule_createNonCommercialIpaBoundLicense_licensorIpOrg() diff --git a/test/foundry/modules/registration/RegistrationTest.sol b/test/foundry/modules/registration/RegistrationTest.sol index e974e434..6b216bea 100644 --- a/test/foundry/modules/registration/RegistrationTest.sol +++ b/test/foundry/modules/registration/RegistrationTest.sol @@ -21,7 +21,6 @@ contract RegistrationModuleTest is BaseTest { event Registered( uint256 ipAssetId_, string name_, - uint64 indexed ipAssetType_, address indexed ipOrg_, address indexed registrant_, bytes32 hash_ @@ -33,7 +32,7 @@ contract RegistrationModuleTest is BaseTest { uint256 ipOrgAssetId_, address indexed owner_, string name_, - uint64 indexed ipAssetType_, + uint8 indexed ipOrgAssetType_, bytes32 hash_, string mediaUrl_ ); @@ -46,7 +45,7 @@ contract RegistrationModuleTest is BaseTest { /// @param ipAssetOwner The owner address for the new IP asset. /// @param ipAssetType The type of the IP asset being created. modifier createIpAsset(address ipAssetOwner, uint8 ipAssetType) virtual { - ipAssetId = _createIpAsset(ipAssetOwner, ipAssetType, ""); + (ipAssetId, ) = _createIpAsset(ipAssetOwner, ipAssetType, ""); _; } @@ -65,7 +64,7 @@ contract RegistrationModuleTest is BaseTest { "https://storyprotocol.xyz/", "https://storyprotocol.xyz" ); - assertEq(registrationModule.tokenURI(address(ipOrg), 1), "https://storyprotocol.xyz/1"); + assertEq(registrationModule.tokenURI(address(ipOrg), 1, 0), "https://storyprotocol.xyz/1"); } /// @notice Tests the default token URI for IPAs. @@ -83,7 +82,7 @@ contract RegistrationModuleTest is BaseTest { )); string memory part2 = string(abi.encodePacked( - '{"trait_type": "IP Asset Type", "value": "0"},', + '{"trait_type": "IP Org Asset Type", "value": "CHARACTER"},', '{"trait_type": "Status", "value": "1"},', '{"trait_type": "Hash", "value": "0x0000000000000000000000000000000000000000000000000000000000000000"},', '{"trait_type": "Registration Date", "value": "', Strings.toString(ipa.registrationDate), '"}' @@ -93,7 +92,7 @@ contract RegistrationModuleTest is BaseTest { "data:application/json;base64,", Base64.encode(bytes(string(abi.encodePacked(part1, part2)))) )); - assertEq(expectedURI, registrationModule.tokenURI(address(ipOrg), 1)); + assertEq(expectedURI, registrationModule.tokenURI(address(ipOrg), 1, 0)); } @@ -104,7 +103,6 @@ contract RegistrationModuleTest is BaseTest { emit Registered( 1, "TestIPA", - 0, address(ipOrg), cal, "" @@ -133,7 +131,6 @@ contract RegistrationModuleTest is BaseTest { emit Registered( 1, "TestIPA", - 0, address(ipOrg), cal, "" @@ -150,29 +147,29 @@ contract RegistrationModuleTest is BaseTest { mediaUrl ); _register(address(ipOrg), cal, "TestIPA", 0, "", mediaUrl); - assertEq(registry.ipAssetOwner(1), cal); - assertEq(ipOrg.ownerOf(1), cal); - assertEq(mediaUrl, registrationModule.tokenURI(address(ipOrg), 1)); + assertEq(registry.ipAssetOwner(1), cal, "ipa owner"); + assertEq(ipOrg.ownerOf(1), cal, "iporg owner"); + assertEq(mediaUrl, registrationModule.tokenURI(address(ipOrg), 1, 0), "media url"); } /// @dev Helper function that performs registration. /// @param ipOrg_ Address of the ipOrg of the IP asset. /// @param owner_ Address of the owner of the IP asset. /// @param name_ Name of the IP asset. - /// @param ipAssetType_ Type of the IP asset. + /// @param ipOrgAssetType_ Type of the IP asset. /// @param hash_ Content has of the IP Asset. function _register( address ipOrg_, address owner_, string memory name_, - uint64 ipAssetType_, + uint8 ipOrgAssetType_, bytes32 hash_, string memory mediaUrl_ ) internal virtual returns (uint256, uint256) { Registration.RegisterIPAssetParams memory params = Registration.RegisterIPAssetParams({ owner: owner_, name: name_, - ipAssetType: ipAssetType_, + ipOrgAssetType: ipOrgAssetType_, hash: hash_, mediaUrl: mediaUrl_ }); diff --git a/test/foundry/modules/relationships/LibUintArrayMask.t.sol b/test/foundry/modules/relationships/LibUintArrayMask.t.sol index 59f0ffbb..a43e564a 100644 --- a/test/foundry/modules/relationships/LibUintArrayMask.t.sol +++ b/test/foundry/modules/relationships/LibUintArrayMask.t.sol @@ -47,13 +47,6 @@ contract LibUintArrayMaskHarnessTest is Test { vm.expectRevert(Errors.LibUintArrayMask_EmptyArray.selector); checker.convertToMask(ipAssets); } - - function test_LibUintArrayMask_revert_UndefinedArrayElement() public { - uint8[] memory ipAssets = new uint8[](1); - ipAssets[0] = 0; - vm.expectRevert(Errors.LibUintArrayMask_UndefinedArrayElement.selector); - checker.convertToMask(ipAssets); - } } diff --git a/test/foundry/modules/relationships/RelationshipModule.Config.t.sol b/test/foundry/modules/relationships/RelationshipModule.Config.t.sol index c2d6d897..4ef1787e 100644 --- a/test/foundry/modules/relationships/RelationshipModule.Config.t.sol +++ b/test/foundry/modules/relationships/RelationshipModule.Config.t.sol @@ -6,6 +6,7 @@ import 'test/foundry/utils/BaseTest.sol'; import 'contracts/modules/relationships/RelationshipModule.sol'; import 'contracts/lib/modules/LibRelationship.sol'; import { AccessControl } from "contracts/lib/AccessControl.sol"; +import { LibUintArrayMask } from "contracts/lib/LibUintArrayMask.sol"; contract RelationshipModuleConfigTest is BaseTest { @@ -43,6 +44,108 @@ contract RelationshipModuleConfigTest is BaseTest { assertEq(relType.dstSubtypesMask, 0); } + function test_RelationshipModule_addIpOrgIpOrgRelationships() public { + LibRelationship.RelatedElements memory allowedElements = LibRelationship.RelatedElements({ + src: LibRelationship.Relatables.IPORG_ENTRY, + dst: LibRelationship.Relatables.IPORG_ENTRY + }); + uint8[] memory allowedSrcs = new uint8[](2); + allowedSrcs[0] = 0; + allowedSrcs[1] = 2; + uint8[] memory allowedDsts = new uint8[](1); + allowedDsts[0] = 1; + LibRelationship.AddRelationshipTypeParams memory params = LibRelationship.AddRelationshipTypeParams({ + relType: "TEST_RELATIONSHIP", + ipOrg: address(ipOrg), + allowedElements: allowedElements, + allowedSrcs: allowedSrcs, + allowedDsts: allowedDsts + }); + vm.prank(ipOrgOwner); + // Todo test event + spg.addRelationshipType(params); + LibRelationship.RelationshipType memory relType = relationshipModule.getRelationshipType( + address(ipOrg), + "TEST_RELATIONSHIP" + ); + assertEq(relType.src, address(ipOrg)); + assertEq(relType.srcSubtypesMask, LibUintArrayMask._convertToMask(allowedSrcs)); + assertEq(relType.dst, address(ipOrg)); + assertEq(relType.dstSubtypesMask, LibUintArrayMask._convertToMask(allowedDsts)); + + } + + function test_RelationshipModule_revert_addIpOrgIpOrgRelationships_UnsupportedTypes() public { + uint8[] memory allowedSrcs = new uint8[](0); + uint8[] memory allowedDsts = new uint8[](0); + allowedSrcs = new uint8[](3); + allowedSrcs[0] = 1; + allowedSrcs[1] = 2; + allowedSrcs[2] = 0; + allowedDsts = new uint8[](1); + allowedDsts[0] = 9; + + LibRelationship.RelatedElements memory allowedElements = LibRelationship.RelatedElements({ + src: LibRelationship.Relatables.IPORG_ENTRY, + dst: LibRelationship.Relatables.IPORG_ENTRY + }); + + LibRelationship.AddRelationshipTypeParams memory params = LibRelationship.AddRelationshipTypeParams({ + relType: "TEST_RELATIONSHIP", + ipOrg: address(ipOrg), + allowedElements: allowedElements, + allowedSrcs: allowedSrcs, + allowedDsts: allowedDsts + }); + vm.prank(ipOrgOwner); + // Todo test event + vm.expectRevert(Errors.RelationshipModule_UnsupportedIpOrgIndexType.selector); + spg.addRelationshipType(params); + } + + function test_RelationshipModule_revert_RelationshipModule_CallerNotIpOrgOwner() public { + LibRelationship.RelatedElements memory allowedElements = LibRelationship.RelatedElements({ + src: LibRelationship.Relatables.IPORG_ENTRY, + dst: LibRelationship.Relatables.IPORG_ENTRY + }); + uint8[] memory allowedSrcs = new uint8[](2); + allowedSrcs[0] = 0; + allowedSrcs[1] = 2; + uint8[] memory allowedDsts = new uint8[](1); + allowedDsts[0] = 1; + LibRelationship.AddRelationshipTypeParams memory params = LibRelationship.AddRelationshipTypeParams({ + relType: "TEST_RELATIONSHIP", + ipOrg: address(ipOrg), + allowedElements: allowedElements, + allowedSrcs: allowedSrcs, + allowedDsts: allowedDsts + }); + vm.expectRevert(Errors.RelationshipModule_CallerNotIpOrgOwner.selector); + spg.addRelationshipType(params); + } + + function test_RelationshipModule_revert_ipOrgRelatableCannotBeProtocolLevel() public { + LibRelationship.RelatedElements memory allowedElements = LibRelationship.RelatedElements({ + src: LibRelationship.Relatables.IPORG_ENTRY, + dst: LibRelationship.Relatables.IPORG_ENTRY + }); + uint8[] memory allowedSrcs = new uint8[](1); + allowedSrcs[0] = 1; + uint8[] memory allowedDsts = new uint8[](1); + allowedDsts[0] = 0; + LibRelationship.AddRelationshipTypeParams memory params = LibRelationship.AddRelationshipTypeParams({ + relType: "TEST_RELATIONSHIP", + ipOrg: LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, + allowedElements: allowedElements, + allowedSrcs: allowedSrcs, + allowedDsts: allowedDsts + }); + vm.prank(relCreator); + // Todo test event + vm.expectRevert(Errors.RelationshipModule_IpOrgRelatableCannotBeProtocolLevel.selector); + spg.addRelationshipType(params); + } + function test_RelationshipModule_removeProtocolRelationshipType() public { LibRelationship.RelatedElements memory allowedElements = LibRelationship.RelatedElements({ src: LibRelationship.Relatables.ADDRESS, @@ -70,6 +173,5 @@ contract RelationshipModuleConfigTest is BaseTest { } - // function test_RelationshipModule_revert_addRelationshipTypeIpaWithoutAllowedTypes() public {} } diff --git a/test/foundry/modules/relationships/RelationshipModule.Setting.t.sol b/test/foundry/modules/relationships/RelationshipModule.Setting.t.sol index 22e13191..c763e881 100644 --- a/test/foundry/modules/relationships/RelationshipModule.Setting.t.sol +++ b/test/foundry/modules/relationships/RelationshipModule.Setting.t.sol @@ -2,51 +2,47 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import 'test/foundry/utils/BaseTest.sol'; -import 'contracts/modules/relationships/RelationshipModule.sol'; -import 'contracts/lib/modules/LibRelationship.sol'; +import "test/foundry/utils/BaseTest.sol"; +import "contracts/modules/relationships/RelationshipModule.sol"; +import "contracts/lib/modules/LibRelationship.sol"; import { AccessControl } from "contracts/lib/AccessControl.sol"; contract RelationshipModuleSettingTest is BaseTest { - address relCreator = address(4444444); + address ipaOwner = address(123); - function setUp() override public { + function setUp() public override { super.setUp(); _grantRole(vm, AccessControl.RELATIONSHIP_MANAGER_ROLE, relCreator); - LibRelationship.RelatedElements memory allowedElements = LibRelationship.RelatedElements({ - src: LibRelationship.Relatables.ADDRESS, - dst: LibRelationship.Relatables.ADDRESS - }); - uint8[] memory allowedSrcs = new uint8[](0); - uint8[] memory allowedDsts = new uint8[](0); - LibRelationship.AddRelationshipTypeParams memory params = LibRelationship.AddRelationshipTypeParams({ - relType: "TEST_RELATIONSHIP", - ipOrg: LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, - allowedElements: allowedElements, - allowedSrcs: allowedSrcs, - allowedDsts: allowedDsts - }); - vm.prank(relCreator); - // Todo test event - spg.addRelationshipType(params); } - function test_RelationshipModule_createRelationshipAddressToAddress() public { - LibRelationship.CreateRelationshipParams memory params = LibRelationship.CreateRelationshipParams({ - relType: "TEST_RELATIONSHIP", - srcAddress: address(1111111), - srcId: 0, - srcType: 0, - dstAddress: address(2222222), - dstId: 0, - dstType: 0 - }); + function test_RelationshipModule_createAddressToAddress() public { + _addRelType( + LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, + LibRelationship.Relatables.ADDRESS, + LibRelationship.Relatables.ADDRESS, + 0 + ); + + LibRelationship.CreateRelationshipParams memory params = LibRelationship + .CreateRelationshipParams({ + relType: "TEST_RELATIONSHIP", + srcAddress: address(1111111), + srcId: 0, + dstAddress: address(2222222), + dstId: 0 + }); bytes[] memory preHooksData = new bytes[](0); bytes[] memory postHooksData = new bytes[](0); - uint256 id = spg.createRelationship(LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, params, preHooksData, postHooksData); + uint256 id = spg.createRelationship( + LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, + params, + preHooksData, + postHooksData + ); assertEq(id, 1); - LibRelationship.Relationship memory rel = relationshipModule.getRelationship(1); + LibRelationship.Relationship memory rel = relationshipModule + .getRelationship(1); assertEq(rel.relType, "TEST_RELATIONSHIP"); assertEq(rel.srcAddress, address(1111111)); assertEq(rel.dstAddress, address(2222222)); @@ -55,6 +51,140 @@ contract RelationshipModuleSettingTest is BaseTest { assertEq(relationshipModule.getRelationshipId(rel), 1); } + function test_RelationshipModule_createIpOrgToIpOrg() public { + _addRelType( + address(ipOrg), + LibRelationship.Relatables.IPORG_ENTRY, + LibRelationship.Relatables.IPORG_ENTRY, + 2 + ); + (, uint256 localId) = _createIpAsset(ipaOwner, 1, bytes("")); + (, uint256 localId2) = _createIpAsset(ipaOwner, 1, bytes("")); + LibRelationship.CreateRelationshipParams memory params = LibRelationship + .CreateRelationshipParams({ + relType: "TEST_RELATIONSHIP", + srcAddress: address(ipOrg), + srcId: localId, + dstAddress: address(ipOrg), + dstId: localId2 + }); + bytes[] memory preHooksData = new bytes[](0); + bytes[] memory postHooksData = new bytes[](0); + uint256 id = spg.createRelationship( + address(ipOrg), + params, + preHooksData, + postHooksData + ); + assertEq(id, 1); + LibRelationship.Relationship memory rel = relationshipModule + .getRelationship(1); + assertEq(rel.relType, "TEST_RELATIONSHIP"); + assertEq(rel.srcAddress, address(ipOrg)); + assertEq(rel.dstAddress, address(ipOrg)); + assertEq(rel.srcId, localId); + assertEq(rel.dstId, localId2); + assertEq(relationshipModule.getRelationshipId(rel), 1); + } -} + function test_RelationshipModule_revert_createIpOrgToIpOrg_InvalidSrcId() + public + { + _addRelType( + address(ipOrg), + LibRelationship.Relatables.IPORG_ENTRY, + LibRelationship.Relatables.IPORG_ENTRY, + 1 + ); + + (, uint256 localId) = _createIpAsset(ipaOwner, 2, bytes("")); + (, uint256 localId2) = _createIpAsset(ipaOwner, 1, bytes("")); + LibRelationship.CreateRelationshipParams memory params = LibRelationship + .CreateRelationshipParams({ + relType: "TEST_RELATIONSHIP", + srcAddress: address(ipOrg), + srcId: localId, + dstAddress: address(ipOrg), + dstId: localId2 + }); + bytes[] memory preHooksData = new bytes[](0); + bytes[] memory postHooksData = new bytes[](0); + vm.expectRevert(Errors.RelationshipModule_InvalidSrcId.selector); + spg.createRelationship( + address(ipOrg), + params, + preHooksData, + postHooksData + ); + } + function test_RelationshipModule_createAddressToIpa() public { + _addRelType( + LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, + LibRelationship.Relatables.ADDRESS, + LibRelationship.Relatables.IPA, + 0 + ); + (uint256 ipaId, ) = _createIpAsset(ipaOwner, 2, bytes("")); + LibRelationship.CreateRelationshipParams memory params = LibRelationship + .CreateRelationshipParams({ + relType: "TEST_RELATIONSHIP", + srcAddress: address(1111111), + srcId: 0, + dstAddress: address(registry), + dstId: ipaId + }); + bytes[] memory preHooksData = new bytes[](0); + bytes[] memory postHooksData = new bytes[](0); + uint256 id = spg.createRelationship( + LibRelationship.PROTOCOL_LEVEL_RELATIONSHIP, + params, + preHooksData, + postHooksData + ); + assertEq(id, 1); + LibRelationship.Relationship memory rel = relationshipModule + .getRelationship(1); + assertEq(rel.relType, "TEST_RELATIONSHIP"); + assertEq(rel.srcAddress, address(1111111)); + assertEq(rel.dstAddress, address(registry)); + assertEq(rel.srcId, 0); + assertEq(rel.dstId, ipaId); + assertEq(relationshipModule.getRelationshipId(rel), 1); + } + + function _addRelType( + address ipOrg, + LibRelationship.Relatables src, + LibRelationship.Relatables dst, + uint8 maxSrc + ) internal { + address caller = ipOrgOwner; + uint8[] memory allowedSrcs = new uint8[](0); + uint8[] memory allowedDsts = new uint8[](0); + if (ipOrg == address(0)) { + caller = relCreator; + } else { + allowedSrcs = new uint8[](3); + for (uint8 i = 0; i < maxSrc; i++) { + allowedSrcs[i] = uint8(i); + } + allowedDsts = new uint8[](1); + allowedDsts[0] = 1; + } + LibRelationship.RelatedElements memory allowedElements = LibRelationship + .RelatedElements({ src: src, dst: dst }); + + LibRelationship.AddRelationshipTypeParams + memory params = LibRelationship.AddRelationshipTypeParams({ + relType: "TEST_RELATIONSHIP", + ipOrg: ipOrg, + allowedElements: allowedElements, + allowedSrcs: allowedSrcs, + allowedDsts: allowedDsts + }); + vm.prank(caller); + // Todo test event + spg.addRelationshipType(params); + } +} diff --git a/test/foundry/utils/BaseTest.sol b/test/foundry/utils/BaseTest.sol index 6cf67db1..ede3de70 100644 --- a/test/foundry/utils/BaseTest.sol +++ b/test/foundry/utils/BaseTest.sol @@ -44,7 +44,7 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { address public collectModuleImpl; address constant upgrader = address(6969); - address constant ipAssetOrgOwner = address(456); + address constant ipOrgOwner = address(456); address constant relManager = address(9999); address constant termSetter = address(444); @@ -131,11 +131,13 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { "tokenURI" ); - vm.startPrank(ipAssetOrgOwner); - string[] memory ipAssetTypes = new string[](1); + vm.startPrank(ipOrgOwner); + string[] memory ipAssetTypes = new string[](3); ipAssetTypes[0] = "CHARACTER"; + ipAssetTypes[1] = "STORY"; + ipAssetTypes[2] = "LOCATION"; ipOrg = IPOrg(spg.registerIpOrg( - ipAssetOrgOwner, + ipOrgOwner, ipAssetOrgParams.name, ipAssetOrgParams.symbol, ipAssetTypes @@ -165,20 +167,23 @@ contract BaseTest is BaseTestUtils, ProxyHelper, AccessControlHelper { /// tested against. The reason this is currently added is that during /// fuzz testing, foundry may plug existing contracts as potential /// owners for IP asset creation. - function _createIpAsset(address ipAssetOwner, uint8 ipAssetType, bytes memory collectData) internal isValidReceiver(ipAssetOwner) returns (uint256) { + function _createIpAsset( + address ipAssetOwner, + uint8 ipOrgAssetType, + bytes memory collectData + ) internal isValidReceiver(ipAssetOwner) returns (uint256 globalId, uint256 localId) { // vm.assume(ipAssetType > uint8(type(IPAsset.IPAssetType).min)); // vm.assume(ipAssetType < uint8(type(IPAsset.IPAssetType).max)); vm.prank(address(ipAssetOwner)); Registration.RegisterIPAssetParams memory params = Registration.RegisterIPAssetParams({ owner: ipAssetOwner, name: "TestIPAsset", - ipAssetType: 0, + ipOrgAssetType: ipOrgAssetType, hash: "", mediaUrl: "" }); bytes[] memory hooks = new bytes[](0); - (uint256 globalId, uint256 localId) = spg.registerIPAsset(address(ipOrg), params, hooks, hooks); - return globalId; + return spg.registerIPAsset(address(ipOrg), params, hooks, hooks); } }