Skip to content
This repository has been archived by the owner on Apr 30, 2024. It is now read-only.

Commit

Permalink
Adds operator authorization to IPA registry and removes need of reg. …
Browse files Browse the repository at this point in the history
…module as a dependency
  • Loading branch information
leeren committed Feb 6, 2024
1 parent cbf1c65 commit fcfcdb0
Show file tree
Hide file tree
Showing 19 changed files with 317 additions and 36 deletions.
18 changes: 17 additions & 1 deletion contracts/interfaces/registries/IIPAssetRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface IIPAssetRegistry is IIPAccountRegistry {
/// @param resolver The address of the resolver linked to the IP.
/// @param provider The address of the metadata provider linked to the IP.
/// @param metadata Canonical metadata that was linked to the IP.
/// TODO: Add support for optional licenseIds.
event IPRegistered(
address ipId,
uint256 indexed chainId,
Expand All @@ -31,6 +32,16 @@ interface IIPAssetRegistry is IIPAccountRegistry {
/// @param resolver The address of the new resolver bound to the IP.
event IPResolverSet(address ipId, address resolver);

/// @notice Emits when an operator is approved for IP registration for an NFT owner.
/// @param owner The address of the IP owner.
/// @param operator The address of the operator the owneris authorizing.
/// @param approved Whether or not to approve that operator for registration.
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);

/// @notice Emits when metadata is set for an IP asset.
/// @param ipId The canonical identifier of the specified IP.
/// @param metadataProvider Address of the metadata provider associated with the IP.
Expand All @@ -52,7 +63,6 @@ interface IIPAssetRegistry is IIPAccountRegistry {
function setMetadataProvider(address metadataProvider) external;

/// @notice Registers an NFT as IP, creating a corresponding IP record.
/// @dev This is only callable by an authorized registration module.
/// @param chainId The chain identifier of where the IP resides.
/// @param tokenContract The address of the IP.
/// @param tokenId The token identifier of the IP.
Expand Down Expand Up @@ -81,6 +91,12 @@ interface IIPAssetRegistry is IIPAccountRegistry {
/// @return The address of the associated IP account.
function ipId(uint256 chainId, address tokenContract, uint256 tokenId) external view returns (address);

/// @notice Checks whether an operator is approved to register on behalf of an IP owner.
/// @param owner The address of the IP owner whose approval is being checked for.
/// @param operator The address of the operator the owner has approved for registration delgation.
/// @return Whether the operator is approved on behalf of the owner for registering.
function isApprovedForAll(address owner, address operator) external view returns (bool);

/// @notice Checks whether an IP was registered based on its ID.
/// @param id The canonical identifier for the IP.
/// @return Whether the IP was registered into the protocol.
Expand Down
3 changes: 3 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ library Errors {
/// @notice The IP asset has not yet been registered.
error IPAssetRegistry__NotYetRegistered();

/// @notice The IP asset registrant is not authorized.
error IPAssetRegistry__RegistrantUnauthorized();

/// @notice The specified IP resolver is not valid.
error IPAssetRegistry__ResolverInvalid();

Expand Down
3 changes: 3 additions & 0 deletions contracts/lib/modules/Module.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,8 @@ string constant IP_RESOLVER_MODULE_KEY = "IP_RESOLVER_MODULE";
// String values for core protocol modules.
string constant REGISTRATION_MODULE_KEY = "REGISTRATION_MODULE";

// String values for core protocol modules.
string constant LICENSING_MODULE_KEY = "LICENSING_MODULE";

// String values for core protocol modules.
string constant DISPUTE_MODULE_KEY = "DISPUTE_MODULE";
8 changes: 0 additions & 8 deletions contracts/modules/RegistrationModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,6 @@ contract RegistrationModule is BaseModule, IRegistrationModule {
uint256 tokenId,
bytes calldata metadata
) external returns (address) {
// Perform registrant authorization.
// Check that the caller is authorized to perform the registration.
// TODO: Perform additional registration authorization logic, allowing
// registrants or root-IP creators to specify their own auth logic.
if (IERC721(tokenContract).ownerOf(tokenId) != msg.sender) {
revert Errors.RegistrationModule__InvalidOwner();
}

// Perform core IP registration and IP account creation.
address ipId = IP_ASSET_REGISTRY.register(
block.chainid,
Expand Down
3 changes: 2 additions & 1 deletion contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC16
// contracts
import { IPolicyFrameworkManager } from "contracts/interfaces/modules/licensing/IPolicyFrameworkManager.sol";
import { ILicenseRegistry } from "contracts/interfaces/registries/ILicenseRegistry.sol";
import { LICENSING_MODULE_KEY } from "contracts/lib/modules/Module.sol";
import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol";
import { IIPAccountRegistry } from "contracts/interfaces/registries/IIPAccountRegistry.sol";
import { Errors } from "contracts/lib/Errors.sol";
Expand Down Expand Up @@ -302,7 +303,7 @@ contract LicensingModule is AccessControlled, ILicensingModule {
}

function name() external pure returns (string memory) {
return "LICENSING_MODULE";
return LICENSING_MODULE_KEY;
}

/// Returns amount of distinct licensing policies in LicenseRegistry
Expand Down
108 changes: 93 additions & 15 deletions contracts/registries/IPAssetRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ pragma solidity ^0.8.23;

import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import { LICENSING_MODULE_KEY } from "contracts/lib/modules/Module.sol";
import { IIPAccount } from "contracts/interfaces/IIPAccount.sol";
import { IMetadataProvider } from "contracts/interfaces/registries/metadata/IMetadataProvider.sol";
import { IModuleRegistry } from "contracts/interfaces/registries/IModuleRegistry.sol";
import { ILicensingModule } from "contracts/interfaces/modules/licensing/ILicensingModule.sol";
import { IIPAssetRegistry } from "contracts/interfaces/registries/IIPAssetRegistry.sol";
import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol";
import { IMetadataProviderMigratable } from "contracts/interfaces/registries/metadata/IMetadataProviderMigratable.sol";
Expand All @@ -27,15 +30,23 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry {
struct Record {
// Metadata provider for Story Protocol canonicalized metadata.
IMetadataProviderMigratable metadataProvider;
// Metadata resolver for custom metadata added by the IP owner.
// TODO: Deprecate this in favor of consolidation through the provider.
address resolver;
}

/// @notice The canonical module registry used by the protocol.
IModuleRegistry public immutable MODULE_REGISTRY;

/// @notice Tracks the total number of IP assets in existence.
uint256 public totalSupply = 0;

/// @notice Protocol governance administrator of the IP record registry.
address owner;

/// @notice Checks whether an operator is approved to register on behalf of an IP owner.
mapping(address owner => mapping(address operator => bool)) public isApprovedForAll;

/// @dev Maps an IP, identified by its IP ID, to an IP record.
mapping(address => Record) internal _records;

Expand All @@ -54,21 +65,36 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry {
/// @param erc6551Registry The address of the ERC6551 registry.
/// @param accessController The address of the access controller.
/// @param ipAccountImpl The address of the IP account implementation.
/// @param moduleRegistry The address of the module registry.
/// TODO: Utilize module registry for fetching different modules.
constructor(
address accessController,
address erc6551Registry,
address ipAccountImpl
address ipAccountImpl,
address moduleRegistry
) IPAccountRegistry(erc6551Registry, accessController, ipAccountImpl) {
// TODO: Migrate this to a parameterized governance owner address.
// TODO: Replace with OZ's 2StepOwnable
owner = msg.sender;
MODULE_REGISTRY = IModuleRegistry(moduleRegistry);
_metadataProvider = IMetadataProviderMigratable(new MetadataProviderV1(address(this)));
}

/// @notice Registers an NFT as an IP, creating a corresponding IP asset.
/// @notice Enables third party operators to register on behalf of an NFT owner.
/// @param operator The address of the operator the sender authorizes.
/// @param approved Whether or not to approve that operator for registration.
function setApprovalForAll(address operator, bool approved) external {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}

/// @notice Registers an NFT as an IP asset.
/// @param chainId The chain identifier of where the NFT resides.
/// @param tokenContract The address of the NFT.
/// @param tokenId The token identifier of the NFT.
/// @param resolverAddr The address of the resolver to associate with the IP.
/// @param createAccount Whether to create an IP account when registering.
/// @param data Canonical metadata to associate with the IP.
function register(
uint256 chainId,
address tokenContract,
Expand All @@ -77,17 +103,27 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry {
bool createAccount,
bytes calldata data
) external returns (address id) {
id = ipId(chainId, tokenContract, tokenId);
if (_records[id].resolver != address(0)) {
revert Errors.IPAssetRegistry__AlreadyRegistered();
}
id = _register(new uint256[](0), chainId, tokenContract, tokenId, resolverAddr, createAccount, data);
emit IPRegistered(id, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), data);
}

if (id.code.length == 0 && createAccount && id != registerIpAccount(chainId, tokenContract, tokenId)) {
revert Errors.IPAssetRegistry__InvalidAccount();
}
_setResolver(id, resolverAddr);
_setMetadata(id, _metadataProvider, data);
totalSupply++;
/// @notice Registers an NFT as an IP using licenses derived from parent IP asset(s).
/// @param licenseIds The parent IP asset licenses used to derive the new IP asset.
/// @param chainId The chain identifier of where the NFT resides.
/// @param tokenContract The address of the NFT.
/// @param tokenId The token identifier of the NFT.
/// @param createAccount Whether to create an IP account when registering.
/// @param data Canonical metadata to associate with the IP.
function register(
uint256[] calldata licenseIds,
uint256 chainId,
address tokenContract,
uint256 tokenId,
address resolverAddr,
bool createAccount,
bytes calldata data
) external returns (address id) {
id = _register(licenseIds, chainId, tokenContract, tokenId, resolverAddr, createAccount, data);
emit IPRegistered(id, chainId, tokenContract, tokenId, resolverAddr, address(_metadataProvider), data);
}

Expand Down Expand Up @@ -151,8 +187,8 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry {
/// @param id The canonical ID of the IP.
/// @param data Canonical metadata to associate with the IP.
function setMetadata(address id, address provider, bytes calldata data) external {
// Metadata is set on registration and immutable thereafter, with new fields
// only added during a migration to new protocol-approved metadata provider.
// Canonical metadata is set on registration and immutable thereafter, with new
// fields only added during a migration to new protocol-approved metadata provider.
if (address(_records[id].metadataProvider) != msg.sender) {
revert Errors.IPAssetRegistry__Unauthorized();
}
Expand All @@ -167,12 +203,54 @@ contract IPAssetRegistry is IIPAssetRegistry, IPAccountRegistry {
revert Errors.IPAssetRegistry__NotYetRegistered();
}
// TODO: Update authorization logic to use the access controller.
if (msg.sender != IIPAccount(payable(id)).owner()) {
address owner = IIPAccount(payable(id)).owner();
if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) {
revert Errors.IPAssetRegistry__Unauthorized();
}
_setResolver(id, resolverAddr);
}

/// @dev Registers an NFT as an IP.
/// @param licenseIds IP asset licenses used to derive the new IP asset, if any.
/// @param chainId The chain identifier of where the NFT resides.
/// @param tokenContract The address of the NFT.
/// @param tokenId The token identifier of the NFT.
/// @param resolverAddr The address of the resolver to associate with the IP.
/// @param createAccount Whether to create an IP account when registering.
/// @param data Canonical metadata to associate with the IP.
function _register(
uint256[] memory licenseIds,
uint256 chainId,
address tokenContract,
uint256 tokenId,
address resolverAddr,
bool createAccount,
bytes calldata data
) internal returns (address id) {
id = ipId(chainId, tokenContract, tokenId);
if (_records[id].resolver != address(0)) {
revert Errors.IPAssetRegistry__AlreadyRegistered();
}

address owner = IERC721(tokenContract).ownerOf(tokenId);
if (msg.sender != owner && !isApprovedForAll[owner][msg.sender]) {
revert Errors.IPAssetRegistry__RegistrantUnauthorized();
}

if (id.code.length == 0 && createAccount && id != registerIpAccount(chainId, tokenContract, tokenId)) {
revert Errors.IPAssetRegistry__InvalidAccount();
}
_setResolver(id, resolverAddr);
_setMetadata(id, _metadataProvider, data);
totalSupply++;

if (licenseIds.length != 0) {
ILicensingModule licensingModule = ILicensingModule(MODULE_REGISTRY.getModule(LICENSING_MODULE_KEY));
licensingModule.linkIpToParents(licenseIds, id, owner);
}
}


/// @dev Sets the resolver for the specified IP.
/// @param id The canonical ID of the IP.
/// @param resolverAddr The address of the resolver being set.
Expand Down
9 changes: 8 additions & 1 deletion script/foundry/deployment/Main.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler {
// TODO: deployment sequence
contractKey = "IPAssetRegistry";
_predeploy(contractKey);
ipAssetRegistry = new IPAssetRegistry(address(accessController), ERC6551_REGISTRY, address(implementation));
ipAssetRegistry = new IPAssetRegistry(address(accessController), ERC6551_REGISTRY, address(implementation), address(moduleRegistry));
_postdeploy(contractKey, address(ipAssetRegistry));

contractKey = "RoyaltyModule";
Expand Down Expand Up @@ -288,6 +288,13 @@ contract Main is Script, BroadcastManager, JsonDeploymentHandler {
)
);

accessController.setGlobalPermission(
address(ipAssetRegistry),
address(licensingModule),
bytes4(0),
1
);

accessController.setGlobalPermission(
address(registrationModule),
address(licenseRegistry),
Expand Down
Loading

0 comments on commit fcfcdb0

Please sign in to comment.