Skip to content

Commit

Permalink
Merges latest ACL changes (storyprotocol#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
leeren authored Jan 22, 2024
1 parent a009d6e commit 5f78dd7
Show file tree
Hide file tree
Showing 20 changed files with 725 additions and 184 deletions.
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();
}
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

0 comments on commit 5f78dd7

Please sign in to comment.