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

Adds the licensing registry and registration module #14

Merged
merged 1 commit into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions contracts/AccessController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.21;

import { IModuleRegistry } from "contracts/interfaces/registries/IModuleRegistry.sol";
import { IAccessController } from "contracts/interfaces/IAccessController.sol";
import { IIPAccountRegistry } from "contracts/interfaces/registries/IIPAccountRegistry.sol";
import { IModuleRegistry } from "contracts/interfaces/registries/IModuleRegistry.sol";
Expand Down Expand Up @@ -75,13 +76,13 @@ contract AccessController is IAccessController {
if (!IIPAccountRegistry(IP_ACCOUNT_REGISTRY).isIpAccount(ipAccount_)) {
revert Errors.AccessController__IPAccountIsNotValid();
}
if (ipAccount_ != msg.sender) {
revert Errors.AccessController__CallerIsNotIPAccount();
}
// permission must be one of ABSTAIN, ALLOW, DENY
if (permission_ > 2) {
revert Errors.AccessController__PermissionIsNotValid();
}
if (!IModuleRegistry(MODULE_REGISTRY).isRegistered(msg.sender) && ipAccount_ != msg.sender) {
revert Errors.AccessController__CallerIsNotIPAccount();
}
leeren marked this conversation as resolved.
Show resolved Hide resolved
permissions[ipAccount_][signer_][to_][func_] = permission_;

// TODO: emit event
Expand Down
3 changes: 3 additions & 0 deletions contracts/interfaces/modules/base/IModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

/// @notice Module Interface
interface IModule {

/// @notice Returns the string identifier associated with the module.
function name() external returns (string memory);
}
2 changes: 1 addition & 1 deletion contracts/interfaces/registries/IIPRecordRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ interface IIPRecordRegistry {
uint256 tokenId,
address resolverAddr,
bool createAccount
) external;
) external returns (address);

/// @notice Creates the IP account for the specified IP.
/// @param chainId The chain identifier of where the IP resides.
Expand Down
9 changes: 4 additions & 5 deletions contracts/interfaces/resolvers/IResolver.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.21;
pragma solidity ^0.8.23;

/// @notice Resolver Interface
interface IResolver {
import { IModule } from "contracts/interfaces/modules/base/IModule.sol";

/// @notice Gets the address of the access controller for the resolver.
function accessController() view external returns (address);
/// @notice Resolver Interface
interface IResolver is IModule {

/// @notice Checks whether the resolver IP interface is supported.
function supportsInterface(bytes4 id) view external returns (bool);
Expand Down
14 changes: 14 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ pragma solidity ^0.8.19;
/// @title Errors Library
/// @notice Library for all Story Protocol contract errors.
library Errors {
////////////////////////////////////////////////////////////////////////////
// Module //
////////////////////////////////////////////////////////////////////////////

/// @notice The caller is not allowed to call the provided module.
error Module_Unauthorized();

////////////////////////////////////////////////////////////////////////////
// IPRecordRegistry //
////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -94,6 +101,13 @@ library Errors {
error ModuleRegistry__NameDoesNotMatch();
error ModuleRegistry__ModuleNotRegistered();

////////////////////////////////////////////////////////////////////////////
// RegistrationModule //
////////////////////////////////////////////////////////////////////////////

/// @notice The caller is not the owner of the root IP NFT.
error RegistrationModule__InvalidOwner();

////////////////////////////////////////////////////////////////////////////
// AccessController //
////////////////////////////////////////////////////////////////////////////
Expand Down
5 changes: 2 additions & 3 deletions contracts/lib/Licensing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ pragma solidity ^0.8.20;
import { IParamVerifier } from "../interfaces/licensing/IParamVerifier.sol";

library Licensing {

/// Identifies a license parameter (term) from a license framework
struct Parameter {
/// Contract that must check if the condition of the paremeter is set
Expand Down Expand Up @@ -51,7 +50,7 @@ library Licensing {
bytes[] linkParentParamDefaultValues;
string licenseUrl;
}

/// A particular configuration of a Licensing Framework, setting (or not) values for the licensing
/// terms (parameters) of the framework.
/// The lengths of the param value arrays must correspond to the Parameter[] of the framework.
Expand All @@ -66,7 +65,7 @@ library Licensing {
bytes[] activationParamValues;
/// If false, minted licenses will start activated and verification of activationParams will be skipped
bool needsActivation;
/// Array with values for parameters verifying conditions to link a license to a parent. Empty bytes for index if
/// Array with values for params verifying conditions to link a license to a parent. Empty bytes for index if
/// this policy wants to use the default value for the paremeter.
bytes[] linkParentParamValues;
}
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 @@ -2,5 +2,8 @@
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.21;

// String values for core protocol modules.
string constant METADATA_RESOLVER_MODULE_KEY = "METADATA_RESOLVER_MODULE";

// String values for core protocol modules.
string constant REGISTRATION_MODULE_KEY = "REGISTRATION_MODULE";
59 changes: 59 additions & 0 deletions contracts/modules/BaseModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

import { IModule } from "contracts/interfaces/modules/base/IModule.sol";
import { IAccessController } from "contracts/interfaces/IAccessController.sol";
import { IPRecordRegistry } from "contracts/registries/IPRecordRegistry.sol";
import { IPAccountRegistry } from "contracts/registries/IPAccountRegistry.sol";
import { LicenseRegistry } from "contracts/registries/LicenseRegistry.sol";
import { IResolver } from "contracts/interfaces/resolvers/IResolver.sol";
import { Errors } from "contracts/lib/Errors.sol";

/// @title BaseModule
/// @notice Base implementation for all modules in Story Protocol. This is to
/// ensure all modules share the same authorization through the access
/// controll manager.
abstract contract BaseModule is IModule {
/// @notice Gets the protocol-wide module access controller.
IAccessController public immutable ACCESS_CONTROLLER;

/// @notice Gets the protocol-wide IP account registry.
IPAccountRegistry public immutable IP_ACCOUNT_REGISTRY;

/// @notice Gets the protocol-wide IP record registry.
IPRecordRegistry public immutable IP_RECORD_REGISTRY;

/// @notice Gets the protocol-wide license registry.
LicenseRegistry public immutable LICENSE_REGISTRY;

/// @notice Modifier for authorizing the calling entity.
modifier onlyAuthorized(address ipId) {
_authenticate(ipId);
_;
}

/// @notice Initializes the base module contract.
/// @param controller The access controller used for IP authorization.
/// @param recordRegistry The address of the IP record registry.
/// @param accountRegistry The address of the IP account registry.
/// @param licenseRegistry The address of the license registry.
constructor(address controller, address recordRegistry, address accountRegistry, address licenseRegistry) {
// TODO: Add checks for interface support or at least zero address
ACCESS_CONTROLLER = IAccessController(controller);
IP_RECORD_REGISTRY = IPRecordRegistry(recordRegistry);
IP_ACCOUNT_REGISTRY = IPAccountRegistry(accountRegistry);
LICENSE_REGISTRY = LicenseRegistry(licenseRegistry);
}

/// @notice Gets the protocol string identifier associated with the module.
/// @return The string identifier of the module.
function name() public pure virtual override returns (string memory);

/// @notice Authenticates the caller entity through the access controller.
function _authenticate(address ipId) internal view {
if (!ACCESS_CONTROLLER.checkPermission(ipId, msg.sender, address(this), msg.sig)) {
revert Errors.Module_Unauthorized();
}
}
}
121 changes: 121 additions & 0 deletions contracts/modules/RegistrationModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: UNLICENSED
// See https://github.com/storyprotocol/protocol-contracts/blob/main/StoryProtocol-AlphaTestingAgreement-17942166.3.pdf
pragma solidity ^0.8.23;

import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import { BaseModule } from "contracts/modules/BaseModule.sol";
import { IIPMetadataResolver } from "contracts/interfaces/resolvers/IIPMetadataResolver.sol";
import { REGISTRATION_MODULE_KEY } from "contracts/lib/modules/Module.sol";
import { Errors } from "contracts/lib/Errors.sol";
import { IP } from "contracts/lib/IP.sol";

/// @title Registration Module
/// @notice The registration module is responsible for registration of IP into
/// the protocol. During registration, this module will register an IP
/// into the protocol, create a resolver, and bind to it any licenses
/// and terms specified by the IP registrant (IP account owner).
contract RegistrationModule is BaseModule {
/// @notice The metadata resolver used by the registration module.
IIPMetadataResolver public resolver;

/// @notice Initializes the registration module contract.
/// @param controller The access controller used for IP authorization.
/// @param recordRegistry The address of the IP record registry.
/// @param accountRegistry The address of the IP account registry.
/// @param licenseRegistry The address of the license registry.
/// @param resolverAddr The address of the IP metadata resolver.
constructor(
address controller,
address recordRegistry,
address accountRegistry,
address licenseRegistry,
address resolverAddr
) BaseModule(controller, recordRegistry, accountRegistry, licenseRegistry) {
resolver = IIPMetadataResolver(resolverAddr);
}

/// @notice Registers a root-level IP into the protocol. Root-level IPs can
/// be thought of as organizational hubs for encapsulating policies
/// that actual IPs can use to register through. As such, a
/// root-level IP is not an actual IP, but a container for IP policy
/// management for their child IP assets.
/// TODO: Rethink the semantics behind "root-level IPs" vs. "normal IPs".
/// TODO: Update function parameters to utilize a struct instead.
/// TODO: Revisit requiring binding an existing NFT to a "root-level IP".
/// If root-level IPs are an organizational primitive, why require NFTs?
/// TODO: Change to a different resolver optimized for root IP metadata.
/// @param policyId The policy that identifies the licensing terms of the IP.
/// @param tokenContract The address of the NFT bound to the root-level IP.
/// @param tokenId The token id of the NFT bound to the root-level IP.
function registerRootIp(uint256 policyId, address tokenContract, uint256 tokenId) 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_RECORD_REGISTRY.register(block.chainid, tokenContract, tokenId, address(resolver), true);

// Perform core IP policy creation.
if (policyId != 0) {
// If we know the policy ID, we can register it directly on creation.
// TODO: return policy index
LICENSE_REGISTRY.addPolicyToIp(ipId, policyId);
}

return ipId;
}

/// @notice Registers an IP derivative into the protocol.
/// @param licenseId The license to incorporate for the new IP.
/// @param tokenContract The address of the NFT bound to the derivative IP.
/// @param tokenId The token id of the NFT bound to the derivative IP.
/// @param ipName The name assigned to the new IP.
/// @param ipDescription A string description to assign to the IP.
/// @param hash The content hash of the IP being registered.
function registerDerivativeIp(
uint256 licenseId,
address tokenContract,
uint256 tokenId,
string memory ipName,
string memory ipDescription,
bytes32 hash
) external {
// Check that the caller is authorized to perform the registration.
// TODO: Perform additional registration authorization logic, allowing
// registrants or 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_RECORD_REGISTRY.register(block.chainid, tokenContract, tokenId, address(resolver), true);
ACCESS_CONTROLLER.setPermission(ipId, address(this), address(resolver), IIPMetadataResolver.setMetadata.selector, 1);

// Perform core IP derivative licensing - the license must be owned by the caller.
// TODO: return resulting policy index
LICENSE_REGISTRY.linkIpToParent(licenseId, ipId, msg.sender);

// Perform metadata attribution setting.
resolver.setMetadata(
ipId,
IP.MetadataRecord({
name: ipName,
description: ipDescription,
hash: hash,
registrationDate: uint64(block.timestamp),
registrant: msg.sender,
uri: ""
})
);
}

/// @notice Gets the protocol-wide module identifier for this module.
function name() public pure override returns (string memory) {
return REGISTRATION_MODULE_KEY;
}
}
8 changes: 4 additions & 4 deletions contracts/registries/IPRecordRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract IPRecordRegistry is IIPRecordRegistry {
/// @dev Maps an IP, identified by its IP ID, to a metadata resolver.
mapping(address => address) internal _resolvers;

/// @notice Restricts calls to only originate from the registration module.
/// @notice Restricts calls to only originate from a protocol-authorized caller.
modifier onlyRegistrationModule() {
if (address(MODULE_REGISTRY.getModule(REGISTRATION_MODULE_KEY)) != msg.sender) {
revert Errors.IPRecordRegistry_Unauthorized();
Expand All @@ -43,8 +43,8 @@ contract IPRecordRegistry is IIPRecordRegistry {
/// @param moduleRegistry The address of the protocol module registry.
/// @param ipAccountRegistry The address of the IP account registry.
constructor(address moduleRegistry, address ipAccountRegistry) {
IP_ACCOUNT_REGISTRY = IIPAccountRegistry(ipAccountRegistry);
MODULE_REGISTRY = IModuleRegistry(moduleRegistry);
IP_ACCOUNT_REGISTRY = IIPAccountRegistry(ipAccountRegistry);
}

/// @notice Gets the canonical IP identifier associated with an IP NFT.
Expand Down Expand Up @@ -102,15 +102,15 @@ contract IPRecordRegistry is IIPRecordRegistry {
uint256 tokenId,
address resolverAddr,
bool createAccount
) external onlyRegistrationModule {
) external onlyRegistrationModule returns (address account) {
address id = ipId(chainId, tokenContract, tokenId);
if (_resolvers[id] != address(0)) {
revert Errors.IPRecordRegistry_AlreadyRegistered();
}

// This is to emphasize the semantic differences between utilizing the
// IP account as an identifier versus as an account used for auth.
address account = id;
account = id;

if (account.code.length == 0 && createAccount) {
_createIPAccount(chainId, tokenContract, tokenId);
Expand Down
Loading