From 2813cfa44a0d76b6e23add2edbc943f9da2e561f Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 29 Jun 2023 20:50:04 +0200 Subject: [PATCH 01/30] simplify errors --- contracts/FranchiseRegistry.sol | 2 +- contracts/access-control/AccessControlSingleton.sol | 2 +- contracts/errors/General.sol | 4 ++-- contracts/story-blocks/StoryBlocksRegistry.sol | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/FranchiseRegistry.sol b/contracts/FranchiseRegistry.sol index 23879a7d..28e30ce6 100644 --- a/contracts/FranchiseRegistry.sol +++ b/contracts/FranchiseRegistry.sol @@ -34,7 +34,7 @@ contract FranchiseRegistry is constructor(address _factory) { _disableInitializers(); - if (_factory == address(0)) revert ZeroAddress("factory"); + if (_factory == address(0)) revert ZeroAddress(); FACTORY = StoryBlocksRegistryFactory(_factory); } diff --git a/contracts/access-control/AccessControlSingleton.sol b/contracts/access-control/AccessControlSingleton.sol index 8721ea8f..6c80c01e 100644 --- a/contracts/access-control/AccessControlSingleton.sol +++ b/contracts/access-control/AccessControlSingleton.sol @@ -20,7 +20,7 @@ contract AccessControlSingleton is AccessControlUpgradeable, UUPSUpgradeable, Mu * @param _admin address to be the PROTOCOL_ADMIN_ROLE. */ function initialize(address _admin) external initializer { - if (_admin == address(0)) revert ZeroAddress("_admin"); + if (_admin == address(0)) revert ZeroAddress(); __AccessControl_init(); __UUPSUpgradeable_init(); _grantRole(PROTOCOL_ADMIN_ROLE, _admin); diff --git a/contracts/errors/General.sol b/contracts/errors/General.sol index 347fc036..e1205aad 100644 --- a/contracts/errors/General.sol +++ b/contracts/errors/General.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.13; -error ZeroAddress(string name); -error ZeroAmount(string name); +error ZeroAddress(); +error ZeroAmount(); error UnsupportedInterface(string name); error Unauthorized(); error NonExistentID(uint256 id); \ No newline at end of file diff --git a/contracts/story-blocks/StoryBlocksRegistry.sol b/contracts/story-blocks/StoryBlocksRegistry.sol index ae821ea5..f4240963 100644 --- a/contracts/story-blocks/StoryBlocksRegistry.sol +++ b/contracts/story-blocks/StoryBlocksRegistry.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; //import "forge-std/console.sol"; import { IStoryBlocksRegistry } from "./IStoryBlocksRegistry.sol"; import { LibStoryBlockId } from "./LibStoryBlockId.sol"; -import { Unauthorized, ZeroAddress } from "../errors/General.sol"; +import { Unauthorized, ZeroAmount } from "../errors/General.sol"; import { StoryBlockStorage } from "./data-access-modules/storage/StoryBlockStorage.sol"; import { StoryBlock } from "contracts/StoryBlock.sol"; import { GroupDAM } from "./data-access-modules/group/GroupDAM.sol"; @@ -47,7 +47,7 @@ contract StoryBlocksRegistry is ) public initializer { __ERC721_init(_name, _symbol); __Multicall_init(); - if (_franchiseId == 0) revert ZeroAddress("franchiseId"); + if (_franchiseId == 0) revert ZeroAmount(); franchiseId = _franchiseId; description = _description; // _setBaseURI("https://api.splinterlands.io/asset/"); From 5e8ac2c1c7eda018fcaae2af2dc80e8d66afd8c5 Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 29 Jun 2023 21:30:28 +0200 Subject: [PATCH 02/30] added linking module POC --- contracts/access-control/ProtocolRoles.sol | 2 +- contracts/modules/linking/LinkingModule.sol | 113 ++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 contracts/modules/linking/LinkingModule.sol diff --git a/contracts/access-control/ProtocolRoles.sol b/contracts/access-control/ProtocolRoles.sol index 6320a031..57698290 100644 --- a/contracts/access-control/ProtocolRoles.sol +++ b/contracts/access-control/ProtocolRoles.sol @@ -4,4 +4,4 @@ pragma solidity ^0.8.13; bytes32 constant PROTOCOL_ADMIN_ROLE = bytes32(0); bytes32 constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); -bytes32 constant DAM_APPROVER = keccak256("DAM_APPROVER"); \ No newline at end of file +bytes32 constant LINK_MANAGER_ROLE = keccak256("LINK_MANAGER_ROLE"); \ No newline at end of file diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol new file mode 100644 index 00000000..472c10ed --- /dev/null +++ b/contracts/modules/linking/LinkingModule.sol @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; +import { ZeroAddress, Unauthorized } from "contracts/errors/General.sol"; +import { UPGRADER_ROLE, LINK_MANAGER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; + +contract LinkingModule is AccessControlledUpgradeable { + + event Linked( + address col1, + uint256 id1, + address col2, + uint256 id2, + bytes32 intent + ); + event Unlinked( + address col1, + uint256 id1, + address col2, + uint256 id2, + bytes32 intent + ); + event AddedIntentRole(bytes32 intent, bytes32 role); + event RemovedIntentRole(bytes32 intent, bytes32 role); + + error LinkingNonExistentToken(); + error IntentAlreadyRegistered(); + error NonExistentIntent(); + + mapping(bytes32 => bool) public links; + mapping(bytes32 => bytes32) public intentRoles; + + + bytes32 public constant PERMISSIONLESS_INTENT = + keccak256("PERMISSIONLESS_INTENT"); + + constructor() { + _disableInitializers(); + } + + function initialize(address accessControl) public initializer { + __AccessControlledUpgradeable_init(accessControl); + } + + function link( + address col1, + uint256 id1, + address col2, + uint256 id2, + bytes32 intent + ) external { + _verifyLink(col1, id1, col2, id2); + + bytes32 intentRole = intentRoles[intent]; + if (intentRole != PERMISSIONLESS_INTENT) { + if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); + } + links[keccak256(abi.encode(col1, id1, col2, id2, intent))] = true; + emit Linked(col1, id1, col2, id2, intent); + } + + function unlink( + address col1, + uint256 id1, + address col2, + uint256 id2, + bytes32 intent + ) external { + // Whoever has the role of intent can unlink...does this make sense? + bytes32 intentRole = intentRoles[intent]; + if (intentRole != PERMISSIONLESS_INTENT) { + if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); + } + delete links[keccak256(abi.encode(col1, id1, col2, id2, intent))]; + emit Unlinked(col1, id1, col2, id2, intent); + } + + function _verifyLink( + address col1, + uint256 id1, + address col2, + uint256 id2 + ) internal view { + if (IERC721(col1).ownerOf(id1) == address(0)) revert LinkingNonExistentToken(); + if (IERC721(col2).ownerOf(id2) == address(0)) revert LinkingNonExistentToken(); + } + + function areTheyLinked( + address col1, + uint256 id1, + address col2, + uint256 id2, + bytes32 intent + ) external view returns (bool) { + return links[keccak256(abi.encode(col1, id1, col2, id2, intent))]; + } + + function addIntentRole(bytes32 intent, bytes32 role) external onlyRole(LINK_MANAGER_ROLE) { + if (intentRoles[intent] != bytes32(0)) revert IntentAlreadyRegistered(); + intentRoles[intent] = role; + emit AddedIntentRole(intent, role); + } + + function removeIntentRole(bytes32 intent) external onlyRole(LINK_MANAGER_ROLE) { + if (intentRoles[intent] == bytes32(0)) revert NonExistentIntent(); + delete intentRoles[intent]; + emit RemovedIntentRole(intent, intentRoles[intent]); + } + + function _authorizeUpgrade(address newImplementation) internal virtual override onlyRole(UPGRADER_ROLE) {} +} \ No newline at end of file From 62dc0f94e8b3b5169e0a07bf76d6125429c60895 Mon Sep 17 00:00:00 2001 From: Raul Date: Wed, 5 Jul 2023 18:50:33 +0200 Subject: [PATCH 03/30] eip-7201 helper task --- hardhat.config.js | 7 +++++++ script/hardhat/namespacedStorageKey.js | 20 ++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 script/hardhat/namespacedStorageKey.js diff --git a/hardhat.config.js b/hardhat.config.js index 2e9bbc83..bf8ed947 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -14,6 +14,8 @@ const createStoryBlock = require("./script/hardhat/createStoryBlock.js"); const getStoryBlockRegistryAddress = require("./script/hardhat/getStoryBlockRegistryAddress.js"); const getStoryBlock = require("./script/hardhat/getStoryBlock.js"); const sbUploader = require("./script/hardhat/sbUploader.js"); +const namespacedStorageKey = require("./script/hardhat/namespacedStorageKey.js"); +const { task } = require("hardhat/config"); // This is a sample Hardhat task. To learn how to create your own go to // https://hardhat.org/guides/create-task.html @@ -68,6 +70,11 @@ task('sp:update-blocks') .setDescription('Update ids for blocks in the Json file') .setAction(sbUploader.updateIds); +task('sp:eip7201-key') + .addPositionalParam('namespace', 'Namespace, for example erc7201:example.main') + .setDescription('Get the namespaced storage key for https://eips.ethereum.org/EIPS/eip-7201') + .setAction(namespacedStorageKey); + // You need to export an object to set up your config // Go to https://hardhat.org/config/ to learn more diff --git a/script/hardhat/namespacedStorageKey.js b/script/hardhat/namespacedStorageKey.js new file mode 100644 index 00000000..23b95b78 --- /dev/null +++ b/script/hardhat/namespacedStorageKey.js @@ -0,0 +1,20 @@ + +async function main(args, hre) { + const { ethers } = hre; + const { id, keccak256, solidityPack } = ethers.utils; + const BigNumber = ethers.BigNumber; + const { namespace } = args; + // See https://eips.ethereum.org/EIPS/eip-7201 + const result = keccak256( + solidityPack( + ["bytes32"], + [ + BigNumber.from(id(namespace)).sub("1") + ] + ) + ); + console.log(result); + return result; +} + +module.exports = main; From 1d72fa4a6bc1ab3454c363b6d74d6eee3ecdbc3d Mon Sep 17 00:00:00 2001 From: Raul Date: Wed, 5 Jul 2023 19:59:02 +0200 Subject: [PATCH 04/30] removed __gaps in favour of namespaced storage --- contracts/FranchiseRegistry.sol | 32 +++++++--- .../access-control/AccessControlSingleton.sol | 1 - .../AccessControlledUpgradeable.sol | 32 ++++++---- .../story-blocks/IStoryBlocksRegistry.sol | 4 +- .../story-blocks/StoryBlocksRegistry.sol | 59 +++++++++++++------ .../data-access-modules/group/GroupDAM.sol | 40 ++++++++----- .../data-access-modules/group/IGroupDAM.sol | 4 +- ...ryBlockStorage.sol => IStoryBlockData.sol} | 2 +- ...oryBlockStorage.sol => StoryBlockData.sol} | 27 +++++++-- 9 files changed, 137 insertions(+), 64 deletions(-) rename contracts/story-blocks/data-access-modules/storage/{IStoryBlockStorage.sol => IStoryBlockData.sol} (95%) rename contracts/story-blocks/data-access-modules/storage/{StoryBlockStorage.sol => StoryBlockData.sol} (60%) diff --git a/contracts/FranchiseRegistry.sol b/contracts/FranchiseRegistry.sol index 23879a7d..a86a1373 100644 --- a/contracts/FranchiseRegistry.sol +++ b/contracts/FranchiseRegistry.sol @@ -23,11 +23,17 @@ contract FranchiseRegistry is ); error AlreadyRegistered(); - uint256 _franchiseIds; - /// Franchise id => StoryBlockRegistry address - mapping(uint256 => address) public _storyBlockRegistries; + /// @custom:storage-location erc7201:story-protocol.franchise-registry.storage + struct FranchiseStorage { + uint256 franchiseIds; + /// Franchise id => StoryBlockRegistry address + mapping(uint256 => address) storyBlockRegistries; + } StoryBlocksRegistryFactory public immutable FACTORY; + + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.franchise-registry.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x5648324915b730d22cca7279385130ad43fd4829d795fb20e9ab398bfe537e8f; uint256 public constant PROTOCOL_ROOT_ID = 0; address public constant PROTOCOL_ROOT_ADDRESS = address(0); string private constant _VERSION = "0.1.0"; @@ -44,6 +50,12 @@ contract FranchiseRegistry is __ERC721_init("Story Protocol", "SP"); } + function _getFranchiseStorage() private pure returns (FranchiseStorage storage $) { + assembly { + $.slot := _STORAGE_LOCATION + } + } + function version() external pure override returns (string memory) { return _VERSION; } @@ -53,22 +65,24 @@ contract FranchiseRegistry is string calldata symbol, string calldata description ) external returns (uint256, address) { + FranchiseStorage storage $ = _getFranchiseStorage(); address storyBlocksRegistry = FACTORY.createFranchiseBlocks( - ++_franchiseIds, + ++$.franchiseIds, name, symbol, description ); - _storyBlockRegistries[_franchiseIds] = storyBlocksRegistry; - _safeMint(msg.sender, _franchiseIds); - emit FranchiseRegistered(msg.sender, _franchiseIds, storyBlocksRegistry); - return (_franchiseIds, storyBlocksRegistry); + $.storyBlockRegistries[$.franchiseIds] = storyBlocksRegistry; + _safeMint(msg.sender, $.franchiseIds); + emit FranchiseRegistered(msg.sender, $.franchiseIds, storyBlocksRegistry); + return ($.franchiseIds, storyBlocksRegistry); } function storyBlockRegistryForId( uint256 franchiseId ) public view returns (address) { - return _storyBlockRegistries[franchiseId]; + FranchiseStorage storage $ = _getFranchiseStorage(); + return $.storyBlockRegistries[franchiseId]; } function _authorizeUpgrade( diff --git a/contracts/access-control/AccessControlSingleton.sol b/contracts/access-control/AccessControlSingleton.sol index 8721ea8f..dfed319b 100644 --- a/contracts/access-control/AccessControlSingleton.sol +++ b/contracts/access-control/AccessControlSingleton.sol @@ -42,5 +42,4 @@ contract AccessControlSingleton is AccessControlUpgradeable, UUPSUpgradeable, Mu function _authorizeUpgrade(address newImplementation) internal virtual override onlyRole(UPGRADER_ROLE) { } - uint256[50] private __gap; } \ No newline at end of file diff --git a/contracts/access-control/AccessControlledUpgradeable.sol b/contracts/access-control/AccessControlledUpgradeable.sol index c613cb0d..5f06c25d 100644 --- a/contracts/access-control/AccessControlledUpgradeable.sol +++ b/contracts/access-control/AccessControlledUpgradeable.sol @@ -12,11 +12,17 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable { using ERC165CheckerUpgradeable for address; - IAccessControl private _accessControl; - event AccessControlUpdated(address indexed accessControl); error MissingRole(bytes32 role, address account); + /// @custom:storage-location erc7201:story-protocol.access-controlled-upgradeable.storage + struct AccessControlledStorage { + IAccessControl accessControl; + } + + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.access-controlled-upgradeable.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x06c308ca3b780cede1217f5877d0c7fbf50796d93f836cb3b60e6457b0cf03b6; + /** * @notice Checks if msg.sender has `role`, reverts if not. * @param role the role to be tested, defined in Roles.sol and set in AccessManager instance. @@ -34,10 +40,17 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable { */ function __AccessControlledUpgradeable_init(address accessControl) internal initializer { if (!accessControl.supportsInterface(type(IAccessControl).interfaceId)) revert UnsupportedInterface("IAccessControl"); - _accessControl = IAccessControl(accessControl); + AccessControlledStorage storage $ = _getAccessControlledUpgradeable(); + $.accessControl = IAccessControl(accessControl); emit AccessControlUpdated(accessControl); } + function _getAccessControlledUpgradeable() private pure returns (AccessControlledStorage storage $) { + assembly { + $.slot := _STORAGE_LOCATION + } + } + /** * @notice Checks if `account has `role` assigned. * @param role the role to be tested, defined in Roles.sol and set in AccessManager instance. @@ -45,7 +58,8 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable { * @return return true if account has role, false otherwise. */ function hasRole(bytes32 role, address account) internal view returns (bool) { - return _accessControl.hasRole(role, account); + AccessControlledStorage storage $ = _getAccessControlledUpgradeable(); + return $.accessControl.hasRole(role, account); } /** @@ -54,15 +68,9 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable { */ function setAccessControl(address accessControl) public onlyRole(PROTOCOL_ADMIN_ROLE) { if (!accessControl.supportsInterface(type(IAccessControl).interfaceId)) revert UnsupportedInterface("IAccessControl"); - _accessControl = IAccessControl(accessControl); + AccessControlledStorage storage $ = _getAccessControlledUpgradeable(); + $.accessControl = IAccessControl(accessControl); emit AccessControlUpdated(accessControl); } - /** - * 50 - * - 1 _accessControl; - * -------------------------- - * 49 __gap - */ - uint256[49] private __gap; } \ No newline at end of file diff --git a/contracts/story-blocks/IStoryBlocksRegistry.sol b/contracts/story-blocks/IStoryBlocksRegistry.sol index a5fa81d3..ef49eb26 100644 --- a/contracts/story-blocks/IStoryBlocksRegistry.sol +++ b/contracts/story-blocks/IStoryBlocksRegistry.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import { IVersioned } from "../utils/IVersioned.sol"; -import { IStoryBlockStorage } from './data-access-modules/storage/IStoryBlockStorage.sol'; +import { IStoryBlockData } from './data-access-modules/storage/IStoryBlockData.sol'; import { IGroupDAM } from "./data-access-modules/group/IGroupDAM.sol"; import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; @@ -12,6 +12,6 @@ interface IStoryBlocksRegistry is IVersioned, IERC165Upgradeable, IERC721Upgradeable, - IStoryBlockStorage, + IStoryBlockData, IGroupDAM { } diff --git a/contracts/story-blocks/StoryBlocksRegistry.sol b/contracts/story-blocks/StoryBlocksRegistry.sol index ae821ea5..b865f259 100644 --- a/contracts/story-blocks/StoryBlocksRegistry.sol +++ b/contracts/story-blocks/StoryBlocksRegistry.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.13; import { IStoryBlocksRegistry } from "./IStoryBlocksRegistry.sol"; import { LibStoryBlockId } from "./LibStoryBlockId.sol"; import { Unauthorized, ZeroAddress } from "../errors/General.sol"; -import { StoryBlockStorage } from "./data-access-modules/storage/StoryBlockStorage.sol"; +import { StoryBlockData } from "./data-access-modules/storage/StoryBlockData.sol"; import { StoryBlock } from "contracts/StoryBlock.sol"; import { GroupDAM } from "./data-access-modules/group/GroupDAM.sol"; import { ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; @@ -24,21 +24,23 @@ contract StoryBlocksRegistry is ); error IdOverBounds(); - /// @dev storyBlockId => id counter - mapping(StoryBlock => uint256) private _ids; - string public description; - uint256 public franchiseId; + /// @custom:storage-location erc7201:story-protocol.story-blocks-registry.storage + struct StoryBlockRegistryStorage { + /// @dev storyBlockId => id counter + mapping(StoryBlock => uint256) ids; + string description; + uint256 franchiseId; + } + + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.story-blocks-registry.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x13165038cecff7e5e516182f2f44ae706f1efd51262640024578ea7d73b994bd; string private constant _VERSION = "0.1.0"; constructor() { _disableInitializers(); } - function version() external pure virtual override returns (string memory) { - return _VERSION; - } - function initialize( uint256 _franchiseId, string calldata _name, @@ -48,25 +50,39 @@ contract StoryBlocksRegistry is __ERC721_init(_name, _symbol); __Multicall_init(); if (_franchiseId == 0) revert ZeroAddress("franchiseId"); - franchiseId = _franchiseId; - description = _description; + StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + $.franchiseId = _franchiseId; + $.description = _description; // _setBaseURI("https://api.splinterlands.io/asset/"); } + function _getStoryBlockRegistryStorage() + private + pure + returns (StoryBlockRegistryStorage storage $) + { + assembly { + $.slot := _STORAGE_LOCATION + } + } + + function version() external pure virtual override returns (string memory) { + return _VERSION; + } + function _mintBlock(address to, StoryBlock sb) internal override returns (uint256) { - // console.log("mint block", uint8(sb)); uint256 nextId = currentIdFor(sb) + 1; - // console.log("nextId", nextId); if (nextId > LibStoryBlockId._lastId(sb)) revert IdOverBounds(); - _ids[sb] = nextId; - // console.log("saved", _ids[sb]); + StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + $.ids[sb] = nextId; _safeMint(to, nextId); emit StoryBlockMinted(to, sb, nextId); return nextId; } function currentIdFor(StoryBlock sb) public view returns (uint256) { - uint256 currentId = _ids[sb]; + StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + uint256 currentId = $.ids[sb]; if (currentId == 0) { return LibStoryBlockId._zeroId(sb); } else { @@ -74,6 +90,16 @@ contract StoryBlocksRegistry is } } + function description() external view returns (string memory) { + StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + return $.description; + } + + function franchiseId() external view returns (uint256) { + StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + return $.franchiseId; + } + function supportsInterface( bytes4 interfaceId ) @@ -88,5 +114,4 @@ contract StoryBlocksRegistry is super.supportsInterface(interfaceId); } - uint256[47] private __gap; } diff --git a/contracts/story-blocks/data-access-modules/group/GroupDAM.sol b/contracts/story-blocks/data-access-modules/group/GroupDAM.sol index 60736997..84b6ad92 100644 --- a/contracts/story-blocks/data-access-modules/group/GroupDAM.sol +++ b/contracts/story-blocks/data-access-modules/group/GroupDAM.sol @@ -2,37 +2,49 @@ pragma solidity ^0.8.19; import { IGroupDAM } from "./IGroupDAM.sol"; -import { StoryBlockStorage } from "../storage/StoryBlockStorage.sol"; +import { StoryBlockData } from "../storage/StoryBlockData.sol"; import { LibStoryBlockId } from "contracts/story-blocks/LibStoryBlockId.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import { StoryBlock } from "contracts/StoryBlock.sol"; -abstract contract GroupDAM is IGroupDAM, StoryBlockStorage { +abstract contract GroupDAM is IGroupDAM, StoryBlockData { using EnumerableSet for EnumerableSet.UintSet; - struct GroupData { - StoryBlock linkedType; - EnumerableSet.UintSet linkedItems; - } - event GroupedItems( uint256 indexed id, StoryBlock linkedType, uint256[] linkedItems ); + error TooManyLinkedItems(); error GroupedTypeNotGroupType(); - mapping(uint256 => GroupData) private _groupData; + struct GroupData { + StoryBlock linkedType; + EnumerableSet.UintSet linkedItems; + } + /// @custom:storage-location erc7201:story-protocol.group-dam.storage + struct GroupDAMStorage { + mapping(uint256 => GroupData) groupData; + } + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.group-dam.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x472b429d656ecfb82e6e0cff4b4090577a009a43252b08434ed2201373a9647d; uint256 public constant MAX_LINKED_AT_ONCE = 20; function __GroupDAM_init() internal initializer {} + function _getGroupDAMStorage() private pure returns (GroupDAMStorage storage $) { + assembly { + $.slot := _STORAGE_LOCATION + } + } + function createGroup(string calldata name, string calldata _description, string calldata mediaUrl, StoryBlock linkedType, uint256[] calldata linkedItems) external returns(uint256) { uint256 id = createStoryBlock(StoryBlock.GROUP, name, _description, mediaUrl); - _groupData[id].linkedType = linkedType; + GroupDAMStorage storage $ = _getGroupDAMStorage(); + $.groupData[id].linkedType = linkedType; groupItems(id, linkedItems); return id; } @@ -40,10 +52,11 @@ abstract contract GroupDAM is IGroupDAM, StoryBlockStorage { function groupItems(uint256 id, uint256[] calldata linkedItems) public { uint256 length = linkedItems.length; if (length > MAX_LINKED_AT_ONCE) revert TooManyLinkedItems(); - StoryBlock linkedType = _groupData[id].linkedType; + GroupDAMStorage storage $ = _getGroupDAMStorage(); + StoryBlock linkedType = $.groupData[id].linkedType; for(uint256 i = 0; i < linkedItems.length;) { if (LibStoryBlockId._storyBlockTypeFor(linkedItems[i]) != linkedType) revert GroupedTypeNotGroupType(); - _groupData[id].linkedItems.add(linkedItems[i]); + $.groupData[id].linkedItems.add(linkedItems[i]); unchecked { i++; } @@ -53,11 +66,10 @@ abstract contract GroupDAM is IGroupDAM, StoryBlockStorage { function readGroup(uint256 id) public view returns (StoryBlockData memory blockData, StoryBlock linkedType, uint256[] memory linkedItems) { blockData = readStoryBlock(id); - GroupData storage gd = _groupData[id]; + GroupDAMStorage storage $ = _getGroupDAMStorage(); + GroupData storage gd = $.groupData[id]; linkedType = gd.linkedType; linkedItems = gd.linkedItems.values(); } - - } \ No newline at end of file diff --git a/contracts/story-blocks/data-access-modules/group/IGroupDAM.sol b/contracts/story-blocks/data-access-modules/group/IGroupDAM.sol index 6b0a47c4..f0c36961 100644 --- a/contracts/story-blocks/data-access-modules/group/IGroupDAM.sol +++ b/contracts/story-blocks/data-access-modules/group/IGroupDAM.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.19; import { StoryBlock } from "contracts/StoryBlock.sol"; -import { IStoryBlockStorage } from "../storage/IStoryBlockStorage.sol"; +import { IStoryBlockData } from "../storage/IStoryBlockData.sol"; -interface IGroupDAM is IStoryBlockStorage { +interface IGroupDAM is IStoryBlockData { function createGroup(string calldata name, string calldata _description, string calldata mediaUrl, StoryBlock linkedType, uint256[] calldata linkedItems) external returns(uint256); function readGroup(uint256 id) external view returns (StoryBlockData memory blockData, StoryBlock linkedType, uint256[] memory linkedItems); diff --git a/contracts/story-blocks/data-access-modules/storage/IStoryBlockStorage.sol b/contracts/story-blocks/data-access-modules/storage/IStoryBlockData.sol similarity index 95% rename from contracts/story-blocks/data-access-modules/storage/IStoryBlockStorage.sol rename to contracts/story-blocks/data-access-modules/storage/IStoryBlockData.sol index e29f739a..1579ed95 100644 --- a/contracts/story-blocks/data-access-modules/storage/IStoryBlockStorage.sol +++ b/contracts/story-blocks/data-access-modules/storage/IStoryBlockData.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import { StoryBlock } from "contracts/StoryBlock.sol"; -interface IStoryBlockStorage { +interface IStoryBlockData { event StoryBlockWritten( uint256 indexed storyBlockId, diff --git a/contracts/story-blocks/data-access-modules/storage/StoryBlockStorage.sol b/contracts/story-blocks/data-access-modules/storage/StoryBlockData.sol similarity index 60% rename from contracts/story-blocks/data-access-modules/storage/StoryBlockStorage.sol rename to contracts/story-blocks/data-access-modules/storage/StoryBlockData.sol index cb7d3ff3..db1312dd 100644 --- a/contracts/story-blocks/data-access-modules/storage/StoryBlockStorage.sol +++ b/contracts/story-blocks/data-access-modules/storage/StoryBlockData.sol @@ -3,16 +3,29 @@ pragma solidity ^0.8.19; import { LibStoryBlockId } from "contracts/story-blocks/LibStoryBlockId.sol"; import { Unauthorized, NonExistentID } from "contracts/errors/General.sol"; -import { IStoryBlockStorage } from "./IStoryBlockStorage.sol"; +import { IStoryBlockData } from "./IStoryBlockData.sol"; import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import { StoryBlock } from "contracts/StoryBlock.sol"; -abstract contract StoryBlockStorage is Initializable, IStoryBlockStorage { +abstract contract StoryBlockData is Initializable, IStoryBlockData { error InvalidBlockType(); - // storyblockId -> data - mapping(uint256 => StoryBlockData) private _storyBlocks; + /// @custom:storage-location erc7201:story-protocol.story-block-data.storage + struct StoryBlockDataStorage { + mapping(uint256 => StoryBlockData) storyBlocks; + } + + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.story-block-data.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x966cf815c9bea5bfddba7716d33fe72d80930319e8b0b6565972e93128571d28; + + function __StoryBlockData_init() public initializer {} + + function _getStoryBlockDataStorage() private pure returns (StoryBlockDataStorage storage $) { + assembly { + $.slot := _STORAGE_LOCATION + } + } function createStoryBlock( StoryBlock sb, @@ -32,7 +45,8 @@ abstract contract StoryBlockStorage is Initializable, IStoryBlockStorage { string calldata description, string calldata mediaUrl ) private returns (StoryBlock) { - StoryBlockData storage sbd = _storyBlocks[storyBlockId]; + StoryBlockDataStorage storage $ = _getStoryBlockDataStorage(); + StoryBlockData storage sbd = $.storyBlocks[storyBlockId]; if (sbd.blockType == StoryBlock.UNDEFINED) { sbd.blockType = LibStoryBlockId._storyBlockTypeFor(storyBlockId); } @@ -44,7 +58,8 @@ abstract contract StoryBlockStorage is Initializable, IStoryBlockStorage { } function readStoryBlock(uint256 storyBlockId) public view returns (StoryBlockData memory) { - return _storyBlocks[storyBlockId]; + StoryBlockDataStorage storage $ = _getStoryBlockDataStorage(); + return $.storyBlocks[storyBlockId]; } function _mintBlock(address to, StoryBlock sb) internal virtual returns (uint256); From 9adc8a5bc34a31ce3afde8d7a050c28427fe4ad3 Mon Sep 17 00:00:00 2001 From: Raul Date: Wed, 5 Jul 2023 20:39:29 +0200 Subject: [PATCH 05/30] refactor --- README.md | 6 +- broadcast/Deploy.s.sol/5/run-1687961941.json | 6 +- broadcast/Deploy.s.sol/5/run-1687961966.json | 6 +- broadcast/Deploy.s.sol/5/run-1687983334.json | 2 +- broadcast/Deploy.s.sol/5/run-1687983378.json | 2 +- broadcast/Deploy.s.sol/5/run-latest.json | 2 +- contracts/FranchiseRegistry.sol | 26 +++--- contracts/{StoryBlock.sol => IPAsset.sol} | 2 +- .../IIPAssetRegistry.sol} | 6 +- .../IPAssetRegistry.sol} | 50 +++++------ .../IPAssetRegistryFactory.sol} | 10 +-- contracts/ip-assets/LibIPAssetId.sol | 29 ++++++ .../data-access-modules/group/GroupDAM.sol | 24 ++--- .../data-access-modules/group/IGroupDAM.sol | 13 +++ .../storage/IIPAssetData.sol | 32 +++++++ .../storage/IPAssetData.sol | 66 ++++++++++++++ contracts/story-blocks/LibStoryBlockId.sol | 29 ------ .../data-access-modules/group/IGroupDAM.sol | 13 --- .../storage/IStoryBlockData.sol | 32 ------- .../storage/StoryBlockData.sol | 66 -------------- deployment-public.json | 2 +- hardhat.config.js | 50 +++++------ script/foundry/Deploy.s.sol | 14 +-- script/hardhat/createFranchise.js | 2 +- .../{createStoryBlock.js => createIPAsset.js} | 22 ++--- script/hardhat/getIPAsset.js | 17 ++++ ...ddress.js => getIPAssetRegistryAddress.js} | 8 +- script/hardhat/getStoryBlock.js | 17 ---- script/hardhat/loadDeployment.js | 2 +- script/hardhat/sbUploader.js | 36 ++++---- test/foundry/FranchiseRegistry.t.sol | 14 +-- test/foundry/LibStoryBlockId.t.sol | 80 ++++++++--------- test/foundry/StoryBlocksRegistry.t.sol | 90 +++++++++---------- test/foundry/StoryBlocksRegistryFactory.t.sol | 22 ++--- 34 files changed, 399 insertions(+), 399 deletions(-) rename contracts/{StoryBlock.sol => IPAsset.sol} (89%) rename contracts/{story-blocks/IStoryBlocksRegistry.sol => ip-assets/IIPAssetRegistry.sol} (78%) rename contracts/{story-blocks/StoryBlocksRegistry.sol => ip-assets/IPAssetRegistry.sol} (59%) rename contracts/{story-blocks/StoryBlocksRegistryFactory.sol => ip-assets/IPAssetRegistryFactory.sol} (80%) create mode 100644 contracts/ip-assets/LibIPAssetId.sol rename contracts/{story-blocks => ip-assets}/data-access-modules/group/GroupDAM.sol (69%) create mode 100644 contracts/ip-assets/data-access-modules/group/IGroupDAM.sol create mode 100644 contracts/ip-assets/data-access-modules/storage/IIPAssetData.sol create mode 100644 contracts/ip-assets/data-access-modules/storage/IPAssetData.sol delete mode 100644 contracts/story-blocks/LibStoryBlockId.sol delete mode 100644 contracts/story-blocks/data-access-modules/group/IGroupDAM.sol delete mode 100644 contracts/story-blocks/data-access-modules/storage/IStoryBlockData.sol delete mode 100644 contracts/story-blocks/data-access-modules/storage/StoryBlockData.sol rename script/hardhat/{createStoryBlock.js => createIPAsset.js} (54%) create mode 100644 script/hardhat/getIPAsset.js rename script/hardhat/{getStoryBlockRegistryAddress.js => getIPAssetRegistryAddress.js} (68%) delete mode 100644 script/hardhat/getStoryBlock.js diff --git a/README.md b/README.md index d4707aac..dd0963bb 100644 --- a/README.md +++ b/README.md @@ -73,17 +73,17 @@ npx hardhat --network sp:create-franchise -name="Test" -descriptio Get Franchise Address for ID ``` -npx hardhat sp:get-story-block-registry-address --network +npx hardhat sp:get-ip-asset-registry-address --network ``` Create Story Block ``` -npx hardhat sp:create-block --network "https://www.youtube.com/watch\?v\=dQw4w9WgXcQ" +npx hardhat sp:create-ip-asset --network "https://www.youtube.com/watch\?v\=dQw4w9WgXcQ" ``` Read Story Block ``` -npx hardhat sp:read-block--network +npx hardhat sp:read-ip-asset--network ``` ### Working with a local network diff --git a/broadcast/Deploy.s.sol/5/run-1687961941.json b/broadcast/Deploy.s.sol/5/run-1687961941.json index 0f01d929..c5096fa1 100644 --- a/broadcast/Deploy.s.sol/5/run-1687961941.json +++ b/broadcast/Deploy.s.sol/5/run-1687961941.json @@ -3,7 +3,7 @@ { "hash": "0x3e3807da5c8dea555008863b9b7bd6da2e6c110b6e0b0a24f6463e83b177af31", "transactionType": "CREATE", - "contractName": "LibStoryBlockId", + "contractName": "LibIPAssetId", "contractAddress": "0xDefbA4b68Fb683eA2F2e40651408D4DF2BE10ca7", "function": null, "arguments": null, @@ -22,7 +22,7 @@ { "hash": "0x72a377ba11242048c0ba545097c2e6e520a2e4aba8f7ef9df80619f7a1337699", "transactionType": "CREATE", - "contractName": "StoryBlocksRegistryFactory", + "contractName": "IPAssetsRegistryFactory", "contractAddress": "0xe381609EF4bA8729e875FC84a410976FdA90B175", "function": null, "arguments": null, @@ -115,7 +115,7 @@ ], "receipts": [], "libraries": [ - "contracts/story-blocks/LibStoryBlockId.sol:LibStoryBlockId:0xdefba4b68fb683ea2f2e40651408d4df2be10ca7" + "contracts/story-blocks/LibIPAssetId.sol:LibIPAssetId:0xdefba4b68fb683ea2f2e40651408d4df2be10ca7" ], "pending": [ "0x3e3807da5c8dea555008863b9b7bd6da2e6c110b6e0b0a24f6463e83b177af31", diff --git a/broadcast/Deploy.s.sol/5/run-1687961966.json b/broadcast/Deploy.s.sol/5/run-1687961966.json index 4377a21e..33f77b63 100644 --- a/broadcast/Deploy.s.sol/5/run-1687961966.json +++ b/broadcast/Deploy.s.sol/5/run-1687961966.json @@ -3,7 +3,7 @@ { "hash": "0x3e3807da5c8dea555008863b9b7bd6da2e6c110b6e0b0a24f6463e83b177af31", "transactionType": "CREATE", - "contractName": "LibStoryBlockId", + "contractName": "LibIPAssetId", "contractAddress": "0xDefbA4b68Fb683eA2F2e40651408D4DF2BE10ca7", "function": null, "arguments": null, @@ -22,7 +22,7 @@ { "hash": "0x72a377ba11242048c0ba545097c2e6e520a2e4aba8f7ef9df80619f7a1337699", "transactionType": "CREATE", - "contractName": "StoryBlocksRegistryFactory", + "contractName": "IPAssetsRegistryFactory", "contractAddress": "0xe381609EF4bA8729e875FC84a410976FdA90B175", "function": null, "arguments": null, @@ -296,7 +296,7 @@ } ], "libraries": [ - "contracts/story-blocks/LibStoryBlockId.sol:LibStoryBlockId:0xdefba4b68fb683ea2f2e40651408d4df2be10ca7" + "contracts/story-blocks/LibIPAssetId.sol:LibIPAssetId:0xdefba4b68fb683ea2f2e40651408d4df2be10ca7" ], "pending": [], "path": "/Users/drmanhattan/Workspace/StoryProtocol/protocol-contracts/broadcast/Deploy.s.sol/5/run-latest.json", diff --git a/broadcast/Deploy.s.sol/5/run-1687983334.json b/broadcast/Deploy.s.sol/5/run-1687983334.json index dffd10a7..884a3319 100644 --- a/broadcast/Deploy.s.sol/5/run-1687983334.json +++ b/broadcast/Deploy.s.sol/5/run-1687983334.json @@ -3,7 +3,7 @@ { "hash": "0x0c5f8b856b03ddbadf2fa18eb738a9a44b0726b3b1f8745a43d0ec67d8b450db", "transactionType": "CREATE", - "contractName": "StoryBlocksRegistryFactory", + "contractName": "IPAssetsRegistryFactory", "contractAddress": "0xa8164aF085F0D62A1d27a5A92f0E2336C35fE73d", "function": null, "arguments": null, diff --git a/broadcast/Deploy.s.sol/5/run-1687983378.json b/broadcast/Deploy.s.sol/5/run-1687983378.json index c70533df..a20dc236 100644 --- a/broadcast/Deploy.s.sol/5/run-1687983378.json +++ b/broadcast/Deploy.s.sol/5/run-1687983378.json @@ -3,7 +3,7 @@ { "hash": "0x0c5f8b856b03ddbadf2fa18eb738a9a44b0726b3b1f8745a43d0ec67d8b450db", "transactionType": "CREATE", - "contractName": "StoryBlocksRegistryFactory", + "contractName": "IPAssetsRegistryFactory", "contractAddress": "0xa8164aF085F0D62A1d27a5A92f0E2336C35fE73d", "function": null, "arguments": null, diff --git a/broadcast/Deploy.s.sol/5/run-latest.json b/broadcast/Deploy.s.sol/5/run-latest.json index c70533df..a20dc236 100644 --- a/broadcast/Deploy.s.sol/5/run-latest.json +++ b/broadcast/Deploy.s.sol/5/run-latest.json @@ -3,7 +3,7 @@ { "hash": "0x0c5f8b856b03ddbadf2fa18eb738a9a44b0726b3b1f8745a43d0ec67d8b450db", "transactionType": "CREATE", - "contractName": "StoryBlocksRegistryFactory", + "contractName": "IPAssetsRegistryFactory", "contractAddress": "0xa8164aF085F0D62A1d27a5A92f0E2336C35fE73d", "function": null, "arguments": null, diff --git a/contracts/FranchiseRegistry.sol b/contracts/FranchiseRegistry.sol index a86a1373..6b2c37eb 100644 --- a/contracts/FranchiseRegistry.sol +++ b/contracts/FranchiseRegistry.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; -import { StoryBlock } from "./StoryBlock.sol"; -import { StoryBlocksRegistryFactory } from "./story-blocks/StoryBlocksRegistryFactory.sol"; +import { IPAsset } from "./IPAsset.sol"; +import { IPAssetRegistryFactory } from "./ip-assets/IPAssetRegistryFactory.sol"; import { AccessControlledUpgradeable } from "./access-control/AccessControlledUpgradeable.sol"; import { UPGRADER_ROLE } from "./access-control/ProtocolRoles.sol"; import { ZeroAddress } from "./errors/General.sol"; @@ -19,18 +19,18 @@ contract FranchiseRegistry is event FranchiseRegistered( address owner, uint256 id, - address storyBlockRegistryForId + address IPAssetRegistryForId ); error AlreadyRegistered(); /// @custom:storage-location erc7201:story-protocol.franchise-registry.storage struct FranchiseStorage { uint256 franchiseIds; - /// Franchise id => StoryBlockRegistry address - mapping(uint256 => address) storyBlockRegistries; + /// Franchise id => IPAssetRegistry address + mapping(uint256 => address) IPAssetRegistries; } - StoryBlocksRegistryFactory public immutable FACTORY; + IPAssetRegistryFactory public immutable FACTORY; // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.franchise-registry.storage")) - 1))) bytes32 private constant _STORAGE_LOCATION = 0x5648324915b730d22cca7279385130ad43fd4829d795fb20e9ab398bfe537e8f; @@ -41,7 +41,7 @@ contract FranchiseRegistry is constructor(address _factory) { _disableInitializers(); if (_factory == address(0)) revert ZeroAddress("factory"); - FACTORY = StoryBlocksRegistryFactory(_factory); + FACTORY = IPAssetRegistryFactory(_factory); } function initialize(address accessControl) public initializer { @@ -66,23 +66,23 @@ contract FranchiseRegistry is string calldata description ) external returns (uint256, address) { FranchiseStorage storage $ = _getFranchiseStorage(); - address storyBlocksRegistry = FACTORY.createFranchiseBlocks( + address IPAssetRegistry = FACTORY.createFranchiseBlocks( ++$.franchiseIds, name, symbol, description ); - $.storyBlockRegistries[$.franchiseIds] = storyBlocksRegistry; + $.IPAssetRegistries[$.franchiseIds] = IPAssetRegistry; _safeMint(msg.sender, $.franchiseIds); - emit FranchiseRegistered(msg.sender, $.franchiseIds, storyBlocksRegistry); - return ($.franchiseIds, storyBlocksRegistry); + emit FranchiseRegistered(msg.sender, $.franchiseIds, IPAssetRegistry); + return ($.franchiseIds, IPAssetRegistry); } - function storyBlockRegistryForId( + function IPAssetRegistryForId( uint256 franchiseId ) public view returns (address) { FranchiseStorage storage $ = _getFranchiseStorage(); - return $.storyBlockRegistries[franchiseId]; + return $.IPAssetRegistries[franchiseId]; } function _authorizeUpgrade( diff --git a/contracts/StoryBlock.sol b/contracts/IPAsset.sol similarity index 89% rename from contracts/StoryBlock.sol rename to contracts/IPAsset.sol index 5c455e6b..c7698019 100644 --- a/contracts/StoryBlock.sol +++ b/contracts/IPAsset.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; -enum StoryBlock { +enum IPAsset { UNDEFINED, STORY, CHARACTER, diff --git a/contracts/story-blocks/IStoryBlocksRegistry.sol b/contracts/ip-assets/IIPAssetRegistry.sol similarity index 78% rename from contracts/story-blocks/IStoryBlocksRegistry.sol rename to contracts/ip-assets/IIPAssetRegistry.sol index ef49eb26..537b921b 100644 --- a/contracts/story-blocks/IStoryBlocksRegistry.sol +++ b/contracts/ip-assets/IIPAssetRegistry.sol @@ -2,16 +2,16 @@ pragma solidity ^0.8.13; import { IVersioned } from "../utils/IVersioned.sol"; -import { IStoryBlockData } from './data-access-modules/storage/IStoryBlockData.sol'; +import { IIPAssetData } from './data-access-modules/storage/IIPAssetData.sol'; import { IGroupDAM } from "./data-access-modules/group/IGroupDAM.sol"; import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; import { IERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; -interface IStoryBlocksRegistry is +interface IIPAssetRegistry is IVersioned, IERC165Upgradeable, IERC721Upgradeable, - IStoryBlockData, + IIPAssetData, IGroupDAM { } diff --git a/contracts/story-blocks/StoryBlocksRegistry.sol b/contracts/ip-assets/IPAssetRegistry.sol similarity index 59% rename from contracts/story-blocks/StoryBlocksRegistry.sol rename to contracts/ip-assets/IPAssetRegistry.sol index f18f650b..864a48ef 100644 --- a/contracts/story-blocks/StoryBlocksRegistry.sol +++ b/contracts/ip-assets/IPAssetRegistry.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; //import "forge-std/console.sol"; -import { IStoryBlocksRegistry } from "./IStoryBlocksRegistry.sol"; -import { LibStoryBlockId } from "./LibStoryBlockId.sol"; +import { IIPAssetRegistry } from "./IIPAssetRegistry.sol"; +import { LibIPAssetId } from "./LibIPAssetId.sol"; import { Unauthorized, ZeroAddress } from "../errors/General.sol"; -import { StoryBlockData } from "./data-access-modules/storage/StoryBlockData.sol"; -import { StoryBlock } from "contracts/StoryBlock.sol"; +import { IPAssetData } from "./data-access-modules/storage/IPAssetData.sol"; +import { IPAsset } from "contracts/IPAsset.sol"; import { GroupDAM } from "./data-access-modules/group/GroupDAM.sol"; import { ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; import { IERC165Upgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol"; import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; -contract StoryBlocksRegistry is - IStoryBlocksRegistry, +contract IPAssetRegistry is + IIPAssetRegistry, ERC721Upgradeable, MulticallUpgradeable, GroupDAM @@ -20,16 +20,16 @@ contract StoryBlocksRegistry is error IdOverBounds(); - /// @custom:storage-location erc7201:story-protocol.story-blocks-registry.storage - struct StoryBlockRegistryStorage { - /// @dev storyBlockId => id counter - mapping(StoryBlock => uint256) ids; + /// @custom:storage-location erc7201:story-protocol.ip-assets-registry.storage + struct IPAssetRegistryStorage { + /// @dev IPAssetId => id counter + mapping(IPAsset => uint256) ids; string description; uint256 franchiseId; } - // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.story-blocks-registry.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = 0x13165038cecff7e5e516182f2f44ae706f1efd51262640024578ea7d73b994bd; + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.ip-assets-registry.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x1a0b8fa444ff575656111a4368b8e6a743b70cbf31ffb9ee2c7afe1983f0e378; string private constant _VERSION = "0.1.0"; constructor() { @@ -45,15 +45,15 @@ contract StoryBlocksRegistry is __ERC721_init(_name, _symbol); __Multicall_init(); if (_franchiseId == 0) revert ZeroAddress("franchiseId"); - StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + IPAssetRegistryStorage storage $ = _getIPAssetRegistryStorage(); $.franchiseId = _franchiseId; $.description = _description; } - function _getStoryBlockRegistryStorage() + function _getIPAssetRegistryStorage() private pure - returns (StoryBlockRegistryStorage storage $) + returns (IPAssetRegistryStorage storage $) { assembly { $.slot := _STORAGE_LOCATION @@ -64,37 +64,37 @@ contract StoryBlocksRegistry is return _VERSION; } - function _mintBlock(address to, StoryBlock sb) internal override returns (uint256) { + function _mintBlock(address to, IPAsset sb) internal override returns (uint256) { uint256 nextId = currentIdFor(sb) + 1; - if (nextId > LibStoryBlockId._lastId(sb)) revert IdOverBounds(); - StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + if (nextId > LibIPAssetId._lastId(sb)) revert IdOverBounds(); + IPAssetRegistryStorage storage $ = _getIPAssetRegistryStorage(); $.ids[sb] = nextId; _safeMint(to, nextId); return nextId; } - function currentIdFor(StoryBlock sb) public view returns (uint256) { - StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + function currentIdFor(IPAsset sb) public view returns (uint256) { + IPAssetRegistryStorage storage $ = _getIPAssetRegistryStorage(); uint256 currentId = $.ids[sb]; if (currentId == 0) { - return LibStoryBlockId._zeroId(sb); + return LibIPAssetId._zeroId(sb); } else { return currentId; } } function description() external view returns (string memory) { - StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + IPAssetRegistryStorage storage $ = _getIPAssetRegistryStorage(); return $.description; } function franchiseId() external view returns (uint256) { - StoryBlockRegistryStorage storage $ = _getStoryBlockRegistryStorage(); + IPAssetRegistryStorage storage $ = _getIPAssetRegistryStorage(); return $.franchiseId; } function tokenURI(uint256 tokenId) public view override returns (string memory) { - return readStoryBlock(tokenId).mediaUrl; + return readIPAsset(tokenId).mediaUrl; } function supportsInterface( @@ -107,7 +107,7 @@ contract StoryBlocksRegistry is returns (bool) { return - interfaceId == type(IStoryBlocksRegistry).interfaceId || + interfaceId == type(IIPAssetRegistry).interfaceId || super.supportsInterface(interfaceId); } diff --git a/contracts/story-blocks/StoryBlocksRegistryFactory.sol b/contracts/ip-assets/IPAssetRegistryFactory.sol similarity index 80% rename from contracts/story-blocks/StoryBlocksRegistryFactory.sol rename to contracts/ip-assets/IPAssetRegistryFactory.sol index 680e4532..6d39f983 100644 --- a/contracts/story-blocks/StoryBlocksRegistryFactory.sol +++ b/contracts/ip-assets/IPAssetRegistryFactory.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.13; -import { IStoryBlocksRegistry } from "./IStoryBlocksRegistry.sol"; -import { StoryBlocksRegistry } from "./StoryBlocksRegistry.sol"; +import { IIPAssetRegistry } from "./IIPAssetRegistry.sol"; +import { IPAssetRegistry } from "./IPAssetRegistry.sol"; import { ZeroAddress } from "../errors/General.sol"; import { IVersioned } from "../utils/IVersioned.sol"; import { UnsupportedInterface } from "../errors/General.sol"; @@ -13,7 +13,7 @@ import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC16 import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; -contract StoryBlocksRegistryFactory is Ownable { +contract IPAssetRegistryFactory is Ownable { using ERC165Checker for address; event FranchiseCreated(address indexed collection, string name, string indexed symbol); @@ -22,7 +22,7 @@ contract StoryBlocksRegistryFactory is Ownable { UpgradeableBeacon public immutable BEACON; constructor() { - BEACON = new UpgradeableBeacon(address(new StoryBlocksRegistry())); + BEACON = new UpgradeableBeacon(address(new IPAssetRegistry())); } function createFranchiseBlocks( @@ -44,7 +44,7 @@ contract StoryBlocksRegistryFactory is Ownable { } function upgradeFranchises(address newImplementation) external onlyOwner { - if (!newImplementation.supportsInterface(type(IStoryBlocksRegistry).interfaceId)) revert UnsupportedInterface("IStoryBlocksRegistry"); + if (!newImplementation.supportsInterface(type(IIPAssetRegistry).interfaceId)) revert UnsupportedInterface("IIPAssetRegistry"); BEACON.upgradeTo(newImplementation); emit FranchisesUpgraded(address(newImplementation), IVersioned(newImplementation).version()); } diff --git a/contracts/ip-assets/LibIPAssetId.sol b/contracts/ip-assets/LibIPAssetId.sol new file mode 100644 index 00000000..14095c34 --- /dev/null +++ b/contracts/ip-assets/LibIPAssetId.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IPAsset } from "contracts/IPAsset.sol"; + +library LibIPAssetId { + + error InvalidIPAsset(IPAsset sb); + + uint256 private constant _ID_RANGE = 10 ** 12; + + function _zeroId(IPAsset sb) internal pure returns (uint256) { + if (sb == IPAsset.UNDEFINED) revert InvalidIPAsset(sb); + return _ID_RANGE * (uint256(sb) - 1); + } + + function _lastId(IPAsset sb) internal pure returns (uint256) { + if (sb == IPAsset.UNDEFINED) revert InvalidIPAsset(sb); + return (_ID_RANGE * uint256(sb)) - 1; + } + + function _IPAssetTypeFor(uint256 id) internal pure returns (IPAsset) { + // End of _ID_RANGE is zero (undefined) for each IPAsset + // Also, we don't support ids higher than the last IPAsset enum item + if (id % _ID_RANGE == 0 || id > _ID_RANGE * (uint256(IPAsset.ITEM))) return IPAsset.UNDEFINED; + return IPAsset((id / _ID_RANGE) + 1); + } + +} \ No newline at end of file diff --git a/contracts/story-blocks/data-access-modules/group/GroupDAM.sol b/contracts/ip-assets/data-access-modules/group/GroupDAM.sol similarity index 69% rename from contracts/story-blocks/data-access-modules/group/GroupDAM.sol rename to contracts/ip-assets/data-access-modules/group/GroupDAM.sol index 84b6ad92..a67d25ce 100644 --- a/contracts/story-blocks/data-access-modules/group/GroupDAM.sol +++ b/contracts/ip-assets/data-access-modules/group/GroupDAM.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.19; import { IGroupDAM } from "./IGroupDAM.sol"; -import { StoryBlockData } from "../storage/StoryBlockData.sol"; -import { LibStoryBlockId } from "contracts/story-blocks/LibStoryBlockId.sol"; +import { IPAssetData } from "../storage/IPAssetData.sol"; +import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import { StoryBlock } from "contracts/StoryBlock.sol"; +import { IPAsset } from "contracts/IPAsset.sol"; -abstract contract GroupDAM is IGroupDAM, StoryBlockData { +abstract contract GroupDAM is IGroupDAM, IPAssetData { using EnumerableSet for EnumerableSet.UintSet; event GroupedItems( uint256 indexed id, - StoryBlock linkedType, + IPAsset linkedType, uint256[] linkedItems ); @@ -21,7 +21,7 @@ abstract contract GroupDAM is IGroupDAM, StoryBlockData { error GroupedTypeNotGroupType(); struct GroupData { - StoryBlock linkedType; + IPAsset linkedType; EnumerableSet.UintSet linkedItems; } /// @custom:storage-location erc7201:story-protocol.group-dam.storage @@ -41,8 +41,8 @@ abstract contract GroupDAM is IGroupDAM, StoryBlockData { } } - function createGroup(string calldata name, string calldata _description, string calldata mediaUrl, StoryBlock linkedType, uint256[] calldata linkedItems) external returns(uint256) { - uint256 id = createStoryBlock(StoryBlock.GROUP, name, _description, mediaUrl); + function createGroup(string calldata name, string calldata _description, string calldata mediaUrl, IPAsset linkedType, uint256[] calldata linkedItems) external returns(uint256) { + uint256 id = createIPAsset(IPAsset.GROUP, name, _description, mediaUrl); GroupDAMStorage storage $ = _getGroupDAMStorage(); $.groupData[id].linkedType = linkedType; groupItems(id, linkedItems); @@ -53,9 +53,9 @@ abstract contract GroupDAM is IGroupDAM, StoryBlockData { uint256 length = linkedItems.length; if (length > MAX_LINKED_AT_ONCE) revert TooManyLinkedItems(); GroupDAMStorage storage $ = _getGroupDAMStorage(); - StoryBlock linkedType = $.groupData[id].linkedType; + IPAsset linkedType = $.groupData[id].linkedType; for(uint256 i = 0; i < linkedItems.length;) { - if (LibStoryBlockId._storyBlockTypeFor(linkedItems[i]) != linkedType) revert GroupedTypeNotGroupType(); + if (LibIPAssetId._IPAssetTypeFor(linkedItems[i]) != linkedType) revert GroupedTypeNotGroupType(); $.groupData[id].linkedItems.add(linkedItems[i]); unchecked { i++; @@ -64,8 +64,8 @@ abstract contract GroupDAM is IGroupDAM, StoryBlockData { emit GroupedItems(id, linkedType, linkedItems); } - function readGroup(uint256 id) public view returns (StoryBlockData memory blockData, StoryBlock linkedType, uint256[] memory linkedItems) { - blockData = readStoryBlock(id); + function readGroup(uint256 id) public view returns (IPAssetData memory blockData, IPAsset linkedType, uint256[] memory linkedItems) { + blockData = readIPAsset(id); GroupDAMStorage storage $ = _getGroupDAMStorage(); GroupData storage gd = $.groupData[id]; linkedType = gd.linkedType; diff --git a/contracts/ip-assets/data-access-modules/group/IGroupDAM.sol b/contracts/ip-assets/data-access-modules/group/IGroupDAM.sol new file mode 100644 index 00000000..713aed6f --- /dev/null +++ b/contracts/ip-assets/data-access-modules/group/IGroupDAM.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { IPAsset } from "contracts/IPAsset.sol"; +import { IIPAssetData } from "../storage/IIPAssetData.sol"; + +interface IGroupDAM is IIPAssetData { + + function createGroup(string calldata name, string calldata _description, string calldata mediaUrl, IPAsset linkedType, uint256[] calldata linkedItems) external returns(uint256); + function readGroup(uint256 id) external view returns (IPAssetData memory blockData, IPAsset linkedType, uint256[] memory linkedItems); + function groupItems(uint256 id, uint256[] calldata linkedItems) external; + +} \ No newline at end of file diff --git a/contracts/ip-assets/data-access-modules/storage/IIPAssetData.sol b/contracts/ip-assets/data-access-modules/storage/IIPAssetData.sol new file mode 100644 index 00000000..0343fda9 --- /dev/null +++ b/contracts/ip-assets/data-access-modules/storage/IIPAssetData.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { IPAsset } from "contracts/IPAsset.sol"; + +interface IIPAssetData { + + event IPAssetWritten( + uint256 indexed IPAssetId, + IPAsset indexed blockType, + string name, + string description, + string mediaUrl + ); + + struct IPAssetData { + string name; + string description; + string mediaUrl; + IPAsset blockType; + } + + function readIPAsset(uint256 IPAssetId) external view returns (IPAssetData memory); + + function createIPAsset( + IPAsset sb, + string calldata name, + string calldata _description, + string calldata mediaUrl + ) external returns (uint256); + +} \ No newline at end of file diff --git a/contracts/ip-assets/data-access-modules/storage/IPAssetData.sol b/contracts/ip-assets/data-access-modules/storage/IPAssetData.sol new file mode 100644 index 00000000..eeb68696 --- /dev/null +++ b/contracts/ip-assets/data-access-modules/storage/IPAssetData.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.19; + +import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; +import { Unauthorized, NonExistentID } from "contracts/errors/General.sol"; +import { IIPAssetData } from "./IIPAssetData.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { IPAsset } from "contracts/IPAsset.sol"; + +abstract contract IPAssetData is Initializable, IIPAssetData { + + error InvalidBlockType(); + + /// @custom:storage-location erc7201:story-protocol.ip-asset-data.storage + struct IPAssetDataStorage { + mapping(uint256 => IPAssetData) IPAsset; + } + + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.ip-asset-data.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x9d56f7e7ee381479643316c052563e5824551ecde3f8bff0aa320131666f6879; + + function __IPAssetData_init() public initializer {} + + function _getIPAssetDataStorage() private pure returns (IPAssetDataStorage storage $) { + assembly { + $.slot := _STORAGE_LOCATION + } + } + + function createIPAsset( + IPAsset sb, + string calldata name, + string calldata _description, + string calldata mediaUrl + ) public returns (uint256) { + if (sb == IPAsset.UNDEFINED) revert InvalidBlockType(); + uint256 sbId = _mintBlock(msg.sender, sb); + _writeIPAsset(sbId, name, _description, mediaUrl); + return sbId; + } + + function _writeIPAsset( + uint256 IPAssetId, + string calldata name, + string calldata description, + string calldata mediaUrl + ) private returns (IPAsset) { + IPAssetDataStorage storage $ = _getIPAssetDataStorage(); + IPAssetData storage sbd = $.IPAsset[IPAssetId]; + if (sbd.blockType == IPAsset.UNDEFINED) { + sbd.blockType = LibIPAssetId._IPAssetTypeFor(IPAssetId); + } + sbd.name = name; + sbd.description = description; + sbd.mediaUrl = mediaUrl; + emit IPAssetWritten(IPAssetId, sbd.blockType, name, description, mediaUrl); + return sbd.blockType; + } + + function readIPAsset(uint256 IPAssetId) public view returns (IPAssetData memory) { + IPAssetDataStorage storage $ = _getIPAssetDataStorage(); + return $.IPAsset[IPAssetId]; + } + + function _mintBlock(address to, IPAsset sb) internal virtual returns (uint256); +} \ No newline at end of file diff --git a/contracts/story-blocks/LibStoryBlockId.sol b/contracts/story-blocks/LibStoryBlockId.sol deleted file mode 100644 index 53c2b828..00000000 --- a/contracts/story-blocks/LibStoryBlockId.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { StoryBlock } from "contracts/StoryBlock.sol"; - -library LibStoryBlockId { - - error InvalidStoryBlock(StoryBlock sb); - - uint256 private constant _ID_RANGE = 10 ** 12; - - function _zeroId(StoryBlock sb) internal pure returns (uint256) { - if (sb == StoryBlock.UNDEFINED) revert InvalidStoryBlock(sb); - return _ID_RANGE * (uint256(sb) - 1); - } - - function _lastId(StoryBlock sb) internal pure returns (uint256) { - if (sb == StoryBlock.UNDEFINED) revert InvalidStoryBlock(sb); - return (_ID_RANGE * uint256(sb)) - 1; - } - - function _storyBlockTypeFor(uint256 id) internal pure returns (StoryBlock) { - // End of _ID_RANGE is zero (undefined) for each StoryBlock - // Also, we don't support ids higher than the last StoryBlock enum item - if (id % _ID_RANGE == 0 || id > _ID_RANGE * (uint256(StoryBlock.ITEM))) return StoryBlock.UNDEFINED; - return StoryBlock((id / _ID_RANGE) + 1); - } - -} \ No newline at end of file diff --git a/contracts/story-blocks/data-access-modules/group/IGroupDAM.sol b/contracts/story-blocks/data-access-modules/group/IGroupDAM.sol deleted file mode 100644 index f0c36961..00000000 --- a/contracts/story-blocks/data-access-modules/group/IGroupDAM.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.19; - -import { StoryBlock } from "contracts/StoryBlock.sol"; -import { IStoryBlockData } from "../storage/IStoryBlockData.sol"; - -interface IGroupDAM is IStoryBlockData { - - function createGroup(string calldata name, string calldata _description, string calldata mediaUrl, StoryBlock linkedType, uint256[] calldata linkedItems) external returns(uint256); - function readGroup(uint256 id) external view returns (StoryBlockData memory blockData, StoryBlock linkedType, uint256[] memory linkedItems); - function groupItems(uint256 id, uint256[] calldata linkedItems) external; - -} \ No newline at end of file diff --git a/contracts/story-blocks/data-access-modules/storage/IStoryBlockData.sol b/contracts/story-blocks/data-access-modules/storage/IStoryBlockData.sol deleted file mode 100644 index 1579ed95..00000000 --- a/contracts/story-blocks/data-access-modules/storage/IStoryBlockData.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.19; - -import { StoryBlock } from "contracts/StoryBlock.sol"; - -interface IStoryBlockData { - - event StoryBlockWritten( - uint256 indexed storyBlockId, - StoryBlock indexed blockType, - string name, - string description, - string mediaUrl - ); - - struct StoryBlockData { - string name; - string description; - string mediaUrl; - StoryBlock blockType; - } - - function readStoryBlock(uint256 storyBlockId) external view returns (StoryBlockData memory); - - function createStoryBlock( - StoryBlock sb, - string calldata name, - string calldata _description, - string calldata mediaUrl - ) external returns (uint256); - -} \ No newline at end of file diff --git a/contracts/story-blocks/data-access-modules/storage/StoryBlockData.sol b/contracts/story-blocks/data-access-modules/storage/StoryBlockData.sol deleted file mode 100644 index db1312dd..00000000 --- a/contracts/story-blocks/data-access-modules/storage/StoryBlockData.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.19; - -import { LibStoryBlockId } from "contracts/story-blocks/LibStoryBlockId.sol"; -import { Unauthorized, NonExistentID } from "contracts/errors/General.sol"; -import { IStoryBlockData } from "./IStoryBlockData.sol"; -import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import { StoryBlock } from "contracts/StoryBlock.sol"; - -abstract contract StoryBlockData is Initializable, IStoryBlockData { - - error InvalidBlockType(); - - /// @custom:storage-location erc7201:story-protocol.story-block-data.storage - struct StoryBlockDataStorage { - mapping(uint256 => StoryBlockData) storyBlocks; - } - - // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.story-block-data.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = 0x966cf815c9bea5bfddba7716d33fe72d80930319e8b0b6565972e93128571d28; - - function __StoryBlockData_init() public initializer {} - - function _getStoryBlockDataStorage() private pure returns (StoryBlockDataStorage storage $) { - assembly { - $.slot := _STORAGE_LOCATION - } - } - - function createStoryBlock( - StoryBlock sb, - string calldata name, - string calldata _description, - string calldata mediaUrl - ) public returns (uint256) { - if (sb == StoryBlock.UNDEFINED) revert InvalidBlockType(); - uint256 sbId = _mintBlock(msg.sender, sb); - _writeStoryBlock(sbId, name, _description, mediaUrl); - return sbId; - } - - function _writeStoryBlock( - uint256 storyBlockId, - string calldata name, - string calldata description, - string calldata mediaUrl - ) private returns (StoryBlock) { - StoryBlockDataStorage storage $ = _getStoryBlockDataStorage(); - StoryBlockData storage sbd = $.storyBlocks[storyBlockId]; - if (sbd.blockType == StoryBlock.UNDEFINED) { - sbd.blockType = LibStoryBlockId._storyBlockTypeFor(storyBlockId); - } - sbd.name = name; - sbd.description = description; - sbd.mediaUrl = mediaUrl; - emit StoryBlockWritten(storyBlockId, sbd.blockType, name, description, mediaUrl); - return sbd.blockType; - } - - function readStoryBlock(uint256 storyBlockId) public view returns (StoryBlockData memory) { - StoryBlockDataStorage storage $ = _getStoryBlockDataStorage(); - return $.storyBlocks[storyBlockId]; - } - - function _mintBlock(address to, StoryBlock sb) internal virtual returns (uint256); -} \ No newline at end of file diff --git a/deployment-public.json b/deployment-public.json index fa875d27..d6626e12 100644 --- a/deployment-public.json +++ b/deployment-public.json @@ -3,6 +3,6 @@ "accessControlSingleton": "0xbd9eAF2bEe73148C6e21481ade29FD9c73c29B80", "franchiseRegistry-impl": "0xA78068f87f2102ccE894edB704518f27a1283390", "franchiseRegistry-proxy": "0xcDc37d81dAd12A9885Ea979d660588DA4752fa6B", - "storyBlocksRegistryFactory": "0xa8164aF085F0D62A1d27a5A92f0E2336C35fE73d" + "IPAssetsRegistryFactory": "0xa8164aF085F0D62A1d27a5A92f0E2336C35fE73d" } } \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index bf8ed947..eb2be0d2 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -10,9 +10,9 @@ require("@nomiclabs/hardhat-etherscan"); require('@openzeppelin/hardhat-upgrades'); const createFranchise = require("./script/hardhat/createFranchise.js"); -const createStoryBlock = require("./script/hardhat/createStoryBlock.js"); -const getStoryBlockRegistryAddress = require("./script/hardhat/getStoryBlockRegistryAddress.js"); -const getStoryBlock = require("./script/hardhat/getStoryBlock.js"); +const createIPAsset = require("./script/hardhat/createIPAsset.js"); +const getIPAssetRegistryAddress = require("./script/hardhat/getIPAssetRegistryAddress.js"); +const getIPAsset = require("./script/hardhat/getIPAsset.js"); const sbUploader = require("./script/hardhat/sbUploader.js"); const namespacedStorageKey = require("./script/hardhat/namespacedStorageKey.js"); const { task } = require("hardhat/config"); @@ -32,39 +32,39 @@ task('sp:create-franchise') .addPositionalParam('symbol', 'Franchise symbol') .addPositionalParam('description', 'Franchise description') .addOptionalParam('events', 'Show events in the tx receipt', false, types.boolean) - .setDescription('Mint Franchise NFT and create StoryBlocksRegistry contract') + .setDescription('Mint Franchise NFT and create IPAssetsRegistry contract') .setAction(createFranchise); -task('sp:get-story-block-registry-address') - .addPositionalParam('franchiseId', 'Id of the Franchise to create the Story Block in, as given by FranchiseRegistry contract') - .setDescription('Get the address of the StoryBlocksRegistry contract for the given Franchise') - .setAction(getStoryBlockRegistryAddress); +task('sp:get-ip-asset-registry-address') + .addPositionalParam('franchiseId', 'Id of the Franchise to create the IP Asset in, as given by FranchiseRegistry contract') + .setDescription('Get the address of the IPAssetsRegistry contract for the given Franchise') + .setAction(getIPAssetRegistryAddress); -task('sp:create-block') - .addPositionalParam('franchiseId', 'Id of the Franchise to create the Story Block in, as given by FranchiseRegistry contract') - .addPositionalParam('storyBlockType', 'STORY, CHARACTER, ART, GROUP, LOCATION or ITEM') - .addPositionalParam('name', 'Story Block name') - .addPositionalParam('description', 'Story Block description') - .addPositionalParam('mediaURL', 'Story Block media URL') +task('sp:create-ip-asset') + .addPositionalParam('franchiseId', 'Id of the Franchise to create the IP Asset in, as given by FranchiseRegistry contract') + .addPositionalParam('ipAssetType', 'STORY, CHARACTER, ART, GROUP, LOCATION or ITEM') + .addPositionalParam('name', 'IP Asset name') + .addPositionalParam('description', 'IP Asset description') + .addPositionalParam('mediaURL', 'IP Asset media URL') .addOptionalParam('events', 'Show events in the tx receipt', false, types.boolean) - .setDescription('Mint Story Block NFT and create StoryBlocksRegistry contract') - .setAction(createStoryBlock); + .setDescription('Mint IP Asset NFT and create IPAssetsRegistry contract') + .setAction(createIPAsset); -task('sp:read-block') - .addPositionalParam('franchiseId', 'Id of the Franchise to create the Story Block in, as given by FranchiseRegistry contract') - .addPositionalParam('storyBlockId', 'Id of the Story Block to read') - .setDescription('Get the Story Block details') - .setAction(getStoryBlock); +task('sp:read-ip-asset') + .addPositionalParam('franchiseId', 'Id of the Franchise to create the IP Asset in, as given by FranchiseRegistry contract') + .addPositionalParam('ipAssetId', 'Id of the IP Asset to read') + .setDescription('Get the IP Asset details') + .setAction(getIPAsset); task('sp:uploader') - .addPositionalParam('franchiseId', 'Id of the Franchise to create the Story Blocks in, as given by FranchiseRegistry contract') + .addPositionalParam('franchiseId', 'Id of the Franchise to create the IP Assets in, as given by FranchiseRegistry contract') .addPositionalParam('filePath', 'path to the Json data') .addOptionalParam('batchSize', 'Number of blocks to upload in each batch', 100, types.int) - .setDescription('Mass upload Story Blocks from a Json file') + .setDescription('Mass upload IP Assets from a Json file') .setAction(sbUploader); -task('sp:update-blocks') - .addPositionalParam('franchiseId', 'Id of the Franchise to create the Story Blocks in, as given by FranchiseRegistry contract') +task('sp:update-ip-assets') + .addPositionalParam('franchiseId', 'Id of the Franchise to create the IP Assets in, as given by FranchiseRegistry contract') .addPositionalParam('tx', 'tx hash that created blocks') .addPositionalParam('filePath', 'path to the Json data') .setDescription('Update ids for blocks in the Json file') diff --git a/script/foundry/Deploy.s.sol b/script/foundry/Deploy.s.sol index eaf52724..36e02d0b 100644 --- a/script/foundry/Deploy.s.sol +++ b/script/foundry/Deploy.s.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.18; import "forge-std/Script.sol"; import "test/foundry/utils/ProxyHelper.sol"; import "script/foundry/utils/StringUtil.sol"; -import "contracts/story-blocks/StoryBlocksRegistryFactory.sol"; +import "contracts/ip-assets/IPAssetRegistryFactory.sol"; import "contracts/FranchiseRegistry.sol"; import "contracts/access-control/AccessControlSingleton.sol"; @@ -13,7 +13,7 @@ contract Deploy is Script, ProxyHelper { using StringUtil for uint256; using stdJson for string; - StoryBlocksRegistryFactory public factory; + IPAssetRegistryFactory public factory; FranchiseRegistry public registry; AccessControlSingleton public access; @@ -33,11 +33,11 @@ contract Deploy is Script, ProxyHelper { string memory chainId = (block.chainid).toString(); string memory contractGroup; - /// DEPLOY STORY BLOCKS REGISTRY FACTORY - console.log("Deploying Story Blocks Registry Factory..."); - factory = new StoryBlocksRegistryFactory(); - string memory contractOutput = vm.serializeAddress(contractGroup, "storyBlocksRegistryFactory", address(factory)); - console.log("Story blocks registry factory deployed to:", address(factory)); + /// DEPLOY IP ASSETS REGISTRY FACTORY + console.log("Deploying IP Assets Registry Factory..."); + factory = new IPAssetRegistryFactory(); + string memory contractOutput = vm.serializeAddress(contractGroup, "IPAssetRegistryFactory", address(factory)); + console.log("IP Assets registry factory deployed to:", address(factory)); /// DEPLOY ACCESS CONTROL SINGLETON console.log("Deploying Access Control Singleton..."); diff --git a/script/hardhat/createFranchise.js b/script/hardhat/createFranchise.js index c76e4379..dbff4e5f 100644 --- a/script/hardhat/createFranchise.js +++ b/script/hardhat/createFranchise.js @@ -5,7 +5,7 @@ function findIdAndAddress(events) { const event = events.find((e) => e.event === "FranchiseRegistered"); return { id: event.args.id.toString(), - address: event.args.storyBlockRegistryForId, + address: event.args.IPAssetRegistryForId, }; } diff --git a/script/hardhat/createStoryBlock.js b/script/hardhat/createIPAsset.js similarity index 54% rename from script/hardhat/createStoryBlock.js rename to script/hardhat/createIPAsset.js index 327167ad..6a6b5d4e 100644 --- a/script/hardhat/createStoryBlock.js +++ b/script/hardhat/createIPAsset.js @@ -1,13 +1,13 @@ const loadDeployment = require('./loadDeployment.js'); -const { getStoryBlockRegistryAddress } = require('./getStoryBlockRegistryAddress.js'); +const { getIPAssetRegistryAddress } = require('./getIPAssetRegistryAddress.js'); function findId(events) { const event = events.find((e) => e.event === "Transfer"); return event.args.tokenId.toString() } -function validateStoryBlockType(storyBlockType) { - switch (storyBlockType) { +function validateIPAssetType(IPAssetType) { + switch (IPAssetType) { case "STORY": return 1; case "CHARACTER": @@ -21,20 +21,20 @@ function validateStoryBlockType(storyBlockType) { case "ITEM": return 6; default: - throw new Error("Invalid story block type: " + storyBlockType); + throw new Error("Invalid story block type: " + IPAssetType); } } async function main(args, hre) { const { ethers } = hre; const { chainId, contracts } = await loadDeployment(hre); - const { franchiseId, storyBlockType, name, description, mediaURL, events } = args; - const sbType = validateStoryBlockType(storyBlockType); - const { address } = await getStoryBlockRegistryAddress(ethers, franchiseId, contracts); + const { franchiseId, IPAssetType, name, description, mediaURL, events } = args; + const sbType = validateIPAssetType(IPAssetType); + const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); - const storyBlockRegistry = await contracts.StoryBlocksRegistry.attach(address); - console.log("Creating story block: ", storyBlockType, name, description, mediaURL); - const tx = await storyBlockRegistry.createStoryBlock(sbType, name, description, mediaURL); + const IPAssetRegistry = await contracts.IPAssetsRegistry.attach(address); + console.log("Creating story block: ", IPAssetType, name, description, mediaURL); + const tx = await IPAssetRegistry.createIPAsset(sbType, name, description, mediaURL); console.log("tx: ", tx.hash); console.log("Waiting for tx to be mined..."); const receipt = await tx.wait(); @@ -49,4 +49,4 @@ async function main(args, hre) { } module.exports = main; -module.exports.validateStoryBlockType = validateStoryBlockType; +module.exports.validateIPAssetType = validateIPAssetType; diff --git a/script/hardhat/getIPAsset.js b/script/hardhat/getIPAsset.js new file mode 100644 index 00000000..62b8c68b --- /dev/null +++ b/script/hardhat/getIPAsset.js @@ -0,0 +1,17 @@ +const loadDeployment = require('./loadDeployment.js'); +const { getIPAssetRegistryAddress } = require('./getIPAssetRegistryAddress.js'); + + +async function main(args, hre) { + const { ethers } = hre; + const { chainId, contracts } = await loadDeployment(hre); + const { franchiseId, IPAssetId } = args; + const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); + + const IPAssetRegistry = await contracts.IPAssetsRegistry.attach(address); + const sb = await IPAssetRegistry.callStatic.readIPAsset(IPAssetId); + console.log(sb); + return sb; +} + +module.exports = main; diff --git a/script/hardhat/getStoryBlockRegistryAddress.js b/script/hardhat/getIPAssetRegistryAddress.js similarity index 68% rename from script/hardhat/getStoryBlockRegistryAddress.js rename to script/hardhat/getIPAssetRegistryAddress.js index ed7cf54c..c7eaf8d1 100644 --- a/script/hardhat/getStoryBlockRegistryAddress.js +++ b/script/hardhat/getIPAssetRegistryAddress.js @@ -1,9 +1,9 @@ const loadDeployment = require('./loadDeployment.js'); -async function getStoryBlockRegistryAddress(ethers, franchiseId, contracts) { +async function getIPAssetRegistryAddress(ethers, franchiseId, contracts) { console.log("Getting story block registry for franchise id: ", franchiseId); - const address = await contracts.franchiseRegistry.callStatic.storyBlockRegistryForId(franchiseId) + const address = await contracts.franchiseRegistry.callStatic.IPAssetRegistryForId(franchiseId) if (address === ethers.constants.AddressZero) { throw new Error("Story block registry not found for franchise id: " + franchiseId); } @@ -16,8 +16,8 @@ async function main(args, hre) { const { contracts } = await loadDeployment(hre); const { franchiseId } = args; - return await getStoryBlockRegistryAddress(ethers, franchiseId, contracts); + return await getIPAssetRegistryAddress(ethers, franchiseId, contracts); } module.exports = main; -module.exports.getStoryBlockRegistryAddress = getStoryBlockRegistryAddress; +module.exports.getIPAssetRegistryAddress = getIPAssetRegistryAddress; diff --git a/script/hardhat/getStoryBlock.js b/script/hardhat/getStoryBlock.js deleted file mode 100644 index c87b4081..00000000 --- a/script/hardhat/getStoryBlock.js +++ /dev/null @@ -1,17 +0,0 @@ -const loadDeployment = require('./loadDeployment.js'); -const { getStoryBlockRegistryAddress } = require('./getStoryBlockRegistryAddress.js'); - - -async function main(args, hre) { - const { ethers } = hre; - const { chainId, contracts } = await loadDeployment(hre); - const { franchiseId, storyBlockId } = args; - const { address } = await getStoryBlockRegistryAddress(ethers, franchiseId, contracts); - - const storyBlockRegistry = await contracts.StoryBlocksRegistry.attach(address); - const sb = await storyBlockRegistry.callStatic.readStoryBlock(storyBlockId); - console.log(sb); - return sb; -} - -module.exports = main; diff --git a/script/hardhat/loadDeployment.js b/script/hardhat/loadDeployment.js index c4d00c64..047c3909 100644 --- a/script/hardhat/loadDeployment.js +++ b/script/hardhat/loadDeployment.js @@ -16,7 +16,7 @@ async function main(hre) { const contracts = {} contracts.FranchiseRegistry = await ethers.getContractFactory("FranchiseRegistry"); contracts.franchiseRegistry = await contracts.FranchiseRegistry.attach(deployment["franchiseRegistry-proxy"]); - contracts.StoryBlocksRegistry = await ethers.getContractFactory("StoryBlocksRegistry"); + contracts.IPAssetsRegistry = await ethers.getContractFactory("IPAssetsRegistry"); return { chainId, contracts }; } diff --git a/script/hardhat/sbUploader.js b/script/hardhat/sbUploader.js index a8a889d9..36ef13b6 100644 --- a/script/hardhat/sbUploader.js +++ b/script/hardhat/sbUploader.js @@ -1,6 +1,6 @@ const loadDeployment = require('./loadDeployment.js'); -const { getStoryBlockRegistryAddress } = require('./getStoryBlockRegistryAddress.js'); -const { createStoryBlock, validateStoryBlockType } = require('./createStoryBlock.js'); +const { getIPAssetRegistryAddress } = require('./getIPAssetRegistryAddress.js'); +const { createIPAsset, validateIPAssetType } = require('./createIPAsset.js'); const { readFileSync, writeFileSync } = require('fs'); Array.range = function (n) { @@ -13,13 +13,13 @@ Array.prototype.chunk = function (size) { return Array.range(Math.ceil(this.length / size)).map((i) => this.slice(i * size, i * size + size)); }; -function getCreatedBlocks(receipt, storyBlockRegistry) { +function getCreatedBlocks(receipt, IPAssetRegistry) { if (receipt.events) { - return events.filter((e) => e.event === "StoryBlockWritten").map((e) => e.args); + return events.filter((e) => e.event === "IPAssetWritten").map((e) => e.args); } else { const events = receipt.logs.map((log) => { - return storyBlockRegistry.interface.parseLog(log); - }).filter((e) => e.name === "StoryBlockWritten") + return IPAssetRegistry.interface.parseLog(log); + }).filter((e) => e.name === "IPAssetWritten") .map((e) => { const ev = Object.keys(e.args).reduce((acc, key) => { acc[key] = e.args[key]; @@ -28,18 +28,18 @@ function getCreatedBlocks(receipt, storyBlockRegistry) { return ev; }) .map((e) => { - e.id = e.storyBlockId.toNumber(); + e.id = e.IPAssetId.toNumber(); return e; }); return events; } } -async function updateIds(ethers, txHash, data, filePath, storyBlockRegistry) { +async function updateIds(ethers, txHash, data, filePath, IPAssetRegistry) { const provider = ethers.provider; const receipt = await provider.getTransactionReceipt(txHash); - const createdBlocks = getCreatedBlocks(receipt, storyBlockRegistry); + const createdBlocks = getCreatedBlocks(receipt, IPAssetRegistry); const mapBlocks = (blocks, createdBlocks) => { return blocks.map((b) => { @@ -85,18 +85,18 @@ async function main(args, hre) { const { chainId, contracts } = await loadDeployment(hre); const { franchiseId, filePath, batchSize } = args; const data = JSON.parse(readFileSync(filePath, 'utf8')); - const { address } = await getStoryBlockRegistryAddress(ethers, franchiseId, contracts); + const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); const blocks = Object.keys(data.blocks) .map((key) => data.blocks[key]) .reduce((acc, val) => acc.concat(val), []) - .map((block) => { return { ...block, numBlockType: validateStoryBlockType(block.blockType) }}) + .map((block) => { return { ...block, numBlockType: validateIPAssetType(block.blockType) }}) .filter((block) => block.id === null); console.log("Will upload: ", blocks.length, "story blocks"); - const storyBlockRegistry = await contracts.StoryBlocksRegistry.attach(address); + const IPAssetRegistry = await contracts.IPAssetsRegistry.attach(address); const calls = blocks.map((block) => { - return storyBlockRegistry.interface.encodeFunctionData('createStoryBlock', [block.numBlockType, block.name, block.description, block.mediaURL ?? '']) + return IPAssetRegistry.interface.encodeFunctionData('createIPAsset', [block.numBlockType, block.name, block.description, block.mediaURL ?? '']) }); console.log('Batches: ', Math.ceil(calls.length / batchSize)); @@ -106,7 +106,7 @@ async function main(args, hre) { console.log('Uploading batch of ', callChunk.length, ' story blocks'); let tx; try { - tx = await storyBlockRegistry.multicall(callChunk); + tx = await IPAssetRegistry.multicall(callChunk); } catch (e) { console.log('ERROR sbUploader'); console.log('chainId', chainId); @@ -115,7 +115,7 @@ async function main(args, hre) { console.log('tx: ', tx.hash); console.log('Waiting for tx to be mined...'); const receipt = await tx.wait(); - return updateIds(ethers, tx.hash, data, filePath, storyBlockRegistry); + return updateIds(ethers, tx.hash, data, filePath, IPAssetRegistry); }) ).then(() => console.log('Blocks created!')); @@ -126,9 +126,9 @@ async function updateIdsTask(args, hre) { const { franchiseId, tx, filePath } = args; const data = JSON.parse(readFileSync(filePath, 'utf8')); const { chainId, contracts } = await loadDeployment(hre); - const { address } = await getStoryBlockRegistryAddress(ethers, franchiseId, contracts); - const storyBlockRegistry = await contracts.StoryBlocksRegistry.attach(address); - await updateIds(ethers, tx, data, filePath, storyBlockRegistry); + const { address } = await getIPAssetRegistryAddress(ethers, franchiseId, contracts); + const IPAssetRegistry = await contracts.IPAssetsRegistry.attach(address); + await updateIds(ethers, tx, data, filePath, IPAssetRegistry); } diff --git a/test/foundry/FranchiseRegistry.t.sol b/test/foundry/FranchiseRegistry.t.sol index d84d19de..a173de7d 100644 --- a/test/foundry/FranchiseRegistry.t.sol +++ b/test/foundry/FranchiseRegistry.t.sol @@ -5,17 +5,17 @@ import "forge-std/Test.sol"; import './utils/ProxyHelper.sol'; import "contracts/FranchiseRegistry.sol"; import "contracts/access-control/AccessControlSingleton.sol"; -import "contracts/story-blocks/StoryBlocksRegistryFactory.sol"; +import "contracts/ip-assets/IPAssetRegistryFactory.sol"; contract FranchiseRegistryTest is Test, ProxyHelper { event FranchiseRegistered( address owner, uint256 id, - address storyBlockRegistryForId + address IPAssetRegistryForId ); - StoryBlocksRegistryFactory public factory; + IPAssetRegistryFactory public factory; FranchiseRegistry public register; address admin = address(123); @@ -24,7 +24,7 @@ contract FranchiseRegistryTest is Test, ProxyHelper { AccessControlSingleton acs; function setUp() public { - factory = new StoryBlocksRegistryFactory(); + factory = new IPAssetRegistryFactory(); vm.prank(admin); acs = new AccessControlSingleton(); address accessControl = address(acs); @@ -61,10 +61,10 @@ contract FranchiseRegistryTest is Test, ProxyHelper { ); vm.expectEmit(false, true, false, false); emit FranchiseRegistered(address(0x123), 1, address(0x234)); - (uint256 id, address storyBlocks) = register.registerFranchise("name", "symbol", "description"); + (uint256 id, address ipAsset) = register.registerFranchise("name", "symbol", "description"); assertEq(id, 1); - assertFalse(storyBlocks == address(0)); - assertEq(storyBlocks, register.storyBlockRegistryForId(id)); + assertFalse(ipAsset == address(0)); + assertEq(ipAsset, register.IPAssetRegistryForId(id)); assertEq(register.ownerOf(id), franchiseOwner); } } diff --git a/test/foundry/LibStoryBlockId.t.sol b/test/foundry/LibStoryBlockId.t.sol index 517eb3df..bab57fed 100644 --- a/test/foundry/LibStoryBlockId.t.sol +++ b/test/foundry/LibStoryBlockId.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import "contracts/story-blocks/LibStoryBlockId.sol"; -import "contracts/StoryBlock.sol"; +import "contracts/ip-assets/LibIPAssetId.sol"; +import "contracts/IPAsset.sol"; contract FranchiseRegistryTest is Test { @@ -19,55 +19,55 @@ contract FranchiseRegistryTest is Test { function test_zeroIds() public { - assertEq(LibStoryBlockId._zeroId(StoryBlock.STORY), _ZERO_ID_STORY); - assertEq(LibStoryBlockId._zeroId(StoryBlock.CHARACTER), _ZERO_ID_CHARACTER); - assertEq(LibStoryBlockId._zeroId(StoryBlock.ART), _ZERO_ID_ART); - assertEq(LibStoryBlockId._zeroId(StoryBlock.GROUP), _ZERO_ID_GROUP); - assertEq(LibStoryBlockId._zeroId(StoryBlock.LOCATION), _ZERO_ID_LOCATION); - assertEq(LibStoryBlockId._zeroId(StoryBlock.ITEM), _ZERO_ID_ITEM); + assertEq(LibIPAssetId._zeroId(IPAsset.STORY), _ZERO_ID_STORY); + assertEq(LibIPAssetId._zeroId(IPAsset.CHARACTER), _ZERO_ID_CHARACTER); + assertEq(LibIPAssetId._zeroId(IPAsset.ART), _ZERO_ID_ART); + assertEq(LibIPAssetId._zeroId(IPAsset.GROUP), _ZERO_ID_GROUP); + assertEq(LibIPAssetId._zeroId(IPAsset.LOCATION), _ZERO_ID_LOCATION); + assertEq(LibIPAssetId._zeroId(IPAsset.ITEM), _ZERO_ID_ITEM); } function test_lastIds() public { - assertEq(LibStoryBlockId._lastId(StoryBlock.STORY), _ZERO_ID_CHARACTER - 1); - assertEq(LibStoryBlockId._lastId(StoryBlock.CHARACTER), _ZERO_ID_ART - 1); - assertEq(LibStoryBlockId._lastId(StoryBlock.ART), _ZERO_ID_GROUP - 1); - assertEq(LibStoryBlockId._lastId(StoryBlock.GROUP), _ZERO_ID_LOCATION - 1); - assertEq(LibStoryBlockId._lastId(StoryBlock.LOCATION), _ZERO_ID_ITEM - 1); - assertEq(LibStoryBlockId._lastId(StoryBlock.ITEM), _LAST_ID_ITEM); + assertEq(LibIPAssetId._lastId(IPAsset.STORY), _ZERO_ID_CHARACTER - 1); + assertEq(LibIPAssetId._lastId(IPAsset.CHARACTER), _ZERO_ID_ART - 1); + assertEq(LibIPAssetId._lastId(IPAsset.ART), _ZERO_ID_GROUP - 1); + assertEq(LibIPAssetId._lastId(IPAsset.GROUP), _ZERO_ID_LOCATION - 1); + assertEq(LibIPAssetId._lastId(IPAsset.LOCATION), _ZERO_ID_ITEM - 1); + assertEq(LibIPAssetId._lastId(IPAsset.ITEM), _LAST_ID_ITEM); } - function test_storyBlockTypes() public { - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_STORY)), uint8(StoryBlock.UNDEFINED)); + function test_IPAssetTypes() public { + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_STORY)), uint8(IPAsset.UNDEFINED)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_STORY + 1)), uint8(StoryBlock.STORY)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_STORY + _HALF_ID_RANGE)), uint8(StoryBlock.STORY)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_CHARACTER - 1)), uint8(StoryBlock.STORY)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_STORY + 1)), uint8(IPAsset.STORY)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_STORY + _HALF_ID_RANGE)), uint8(IPAsset.STORY)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_CHARACTER - 1)), uint8(IPAsset.STORY)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_CHARACTER)), uint8(StoryBlock.UNDEFINED)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_CHARACTER + 1)), uint8(StoryBlock.CHARACTER)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_CHARACTER + _HALF_ID_RANGE)), uint8(StoryBlock.CHARACTER)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ART - 1)), uint8(StoryBlock.CHARACTER)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_CHARACTER)), uint8(IPAsset.UNDEFINED)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_CHARACTER + 1)), uint8(IPAsset.CHARACTER)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_CHARACTER + _HALF_ID_RANGE)), uint8(IPAsset.CHARACTER)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ART - 1)), uint8(IPAsset.CHARACTER)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ART)), uint8(StoryBlock.UNDEFINED)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ART + 1)), uint8(StoryBlock.ART)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ART + _HALF_ID_RANGE)), uint8(StoryBlock.ART)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_GROUP - 1)), uint8(StoryBlock.ART)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ART)), uint8(IPAsset.UNDEFINED)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ART + 1)), uint8(IPAsset.ART)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ART + _HALF_ID_RANGE)), uint8(IPAsset.ART)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_GROUP - 1)), uint8(IPAsset.ART)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_GROUP)), uint8(StoryBlock.UNDEFINED)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_GROUP + 1)), uint8(StoryBlock.GROUP)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_GROUP + _HALF_ID_RANGE)), uint8(StoryBlock.GROUP)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_LOCATION - 1)), uint8(StoryBlock.GROUP)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_GROUP)), uint8(IPAsset.UNDEFINED)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_GROUP + 1)), uint8(IPAsset.GROUP)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_GROUP + _HALF_ID_RANGE)), uint8(IPAsset.GROUP)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_LOCATION - 1)), uint8(IPAsset.GROUP)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_LOCATION)), uint8(StoryBlock.UNDEFINED)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_LOCATION + 1)), uint8(StoryBlock.LOCATION)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_LOCATION + _HALF_ID_RANGE)), uint8(StoryBlock.LOCATION)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ITEM - 1)), uint8(StoryBlock.LOCATION)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_LOCATION)), uint8(IPAsset.UNDEFINED)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_LOCATION + 1)), uint8(IPAsset.LOCATION)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_LOCATION + _HALF_ID_RANGE)), uint8(IPAsset.LOCATION)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ITEM - 1)), uint8(IPAsset.LOCATION)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ITEM)), uint8(StoryBlock.UNDEFINED)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ITEM + 1)), uint8(StoryBlock.ITEM)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_ZERO_ID_ITEM + _HALF_ID_RANGE)), uint8(StoryBlock.ITEM)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_LAST_ID_ITEM)), uint8(StoryBlock.ITEM)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ITEM)), uint8(IPAsset.UNDEFINED)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ITEM + 1)), uint8(IPAsset.ITEM)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_ZERO_ID_ITEM + _HALF_ID_RANGE)), uint8(IPAsset.ITEM)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_LAST_ID_ITEM)), uint8(IPAsset.ITEM)); - assertEq(uint8(LibStoryBlockId._storyBlockTypeFor(_LAST_ID_ITEM + 1)), uint8(StoryBlock.UNDEFINED)); + assertEq(uint8(LibIPAssetId._IPAssetTypeFor(_LAST_ID_ITEM + 1)), uint8(IPAsset.UNDEFINED)); } } diff --git a/test/foundry/StoryBlocksRegistry.t.sol b/test/foundry/StoryBlocksRegistry.t.sol index 76e4c776..e2e098cb 100644 --- a/test/foundry/StoryBlocksRegistry.t.sol +++ b/test/foundry/StoryBlocksRegistry.t.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: BUSDL-1.1 pragma solidity ^0.8.13; -import { StoryBlocksRegistry } from "../../contracts/story-blocks/StoryBlocksRegistry.sol"; -import { StoryBlocksRegistryFactory } from "../../contracts/story-blocks/StoryBlocksRegistryFactory.sol"; -import { StoryBlock } from "../../contracts/StoryBlock.sol"; -import { LibStoryBlockId } from "../../contracts/story-blocks/LibStoryBlockId.sol"; +import { IPAssetRegistry } from "../../contracts/ip-assets/IPAssetRegistry.sol"; +import { IPAssetRegistryFactory } from "../../contracts/ip-assets/IPAssetRegistryFactory.sol"; +import { IPAsset } from "../../contracts/IPAsset.sol"; +import { LibIPAssetId } from "../../contracts/ip-assets/LibIPAssetId.sol"; import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import { IERC1967 } from "@openzeppelin/contracts/interfaces/IERC1967.sol"; import "forge-std/Test.sol"; -contract StoryBlocksRegistryTest is Test { +contract IPAssetRegistryTest is Test { using stdStorage for StdStorage; event CollectionCreated(address indexed collection, string name, string indexed symbol); @@ -21,8 +21,8 @@ contract StoryBlocksRegistryTest is Test { error IdOverBounds(); error InvalidBlockType(); - StoryBlocksRegistryFactory public factory; - StoryBlocksRegistry public storyBlocks; + IPAssetRegistryFactory public factory; + IPAssetRegistry public ipAssetRegistry; address owner = address(this); address mintee = address(1); address mintee2 = address(2); @@ -36,73 +36,73 @@ contract StoryBlocksRegistryTest is Test { uint256 private constant _LAST_ID = _ID_RANGE + _FIRST_ID_LOCATION; function setUp() public { - factory = new StoryBlocksRegistryFactory(); - storyBlocks = StoryBlocksRegistry(factory.createFranchiseBlocks(1, "name", "symbol", "description")); + factory = new IPAssetRegistryFactory(); + ipAssetRegistry = IPAssetRegistry(factory.createFranchiseBlocks(1, "name", "symbol", "description")); } function test_setUp() public { - assertEq(storyBlocks.name(), "name"); - assertEq(storyBlocks.symbol(), "symbol"); - assertEq(storyBlocks.description(), "description"); - assertEq(storyBlocks.version(), "0.1.0"); + assertEq(ipAssetRegistry.name(), "name"); + assertEq(ipAssetRegistry.symbol(), "symbol"); + assertEq(ipAssetRegistry.description(), "description"); + assertEq(ipAssetRegistry.version(), "0.1.0"); } function test_mintIdAssignment() public { - uint8 firstStoryBlockType = uint8(StoryBlock.STORY); - uint8 lastStoryBlockTypeId = uint8(StoryBlock.ITEM); - for(uint8 i = firstStoryBlockType; i < lastStoryBlockTypeId; i++) { - StoryBlock sb = StoryBlock(i); - uint256 zero = LibStoryBlockId._zeroId(sb); - assertEq(storyBlocks.currentIdFor(sb), zero, "starts with zero"); + uint8 firstIPAssetType = uint8(IPAsset.STORY); + uint8 lastIPAssetTypeId = uint8(IPAsset.ITEM); + for(uint8 i = firstIPAssetType; i < lastIPAssetTypeId; i++) { + IPAsset sb = IPAsset(i); + uint256 zero = LibIPAssetId._zeroId(sb); + assertEq(ipAssetRegistry.currentIdFor(sb), zero, "starts with zero"); vm.prank(mintee); - uint256 blockId1 = storyBlocks.createStoryBlock(sb, "name", "description", "mediaUrl"); + uint256 blockId1 = ipAssetRegistry.createIPAsset(sb, "name", "description", "mediaUrl"); assertEq(blockId1, zero + 1, "returned blockId is incremented by one"); - assertEq(storyBlocks.currentIdFor(sb), zero + 1, "mint increments currentIdFor by one"); + assertEq(ipAssetRegistry.currentIdFor(sb), zero + 1, "mint increments currentIdFor by one"); vm.prank(mintee); - uint256 blockId2 = storyBlocks.createStoryBlock(sb, "name2", "description2", "mediaUrl2"); + uint256 blockId2 = ipAssetRegistry.createIPAsset(sb, "name2", "description2", "mediaUrl2"); assertEq(blockId2, zero + 2, "returned blockId is incremented by one again"); - assertEq(storyBlocks.currentIdFor(sb), zero + 2, "2 mint increments currentIdFor by one again"); + assertEq(ipAssetRegistry.currentIdFor(sb), zero + 2, "2 mint increments currentIdFor by one again"); } } function test_mintStoryOwnership() public { - uint8 firstStoryBlockType = uint8(StoryBlock.STORY); - uint8 lastStoryBlockTypeId = uint8(StoryBlock.ITEM); - for(uint8 i = firstStoryBlockType; i < lastStoryBlockTypeId; i++) { - StoryBlock sb = StoryBlock(i); - uint256 loopBalance = storyBlocks.balanceOf(mintee); + uint8 firstIPAssetType = uint8(IPAsset.STORY); + uint8 lastIPAssetTypeId = uint8(IPAsset.ITEM); + for(uint8 i = firstIPAssetType; i < lastIPAssetTypeId; i++) { + IPAsset sb = IPAsset(i); + uint256 loopBalance = ipAssetRegistry.balanceOf(mintee); assertEq(loopBalance, (i - 1) * 2, "balance is zero for block type"); vm.prank(mintee); - uint256 blockId1 = storyBlocks.createStoryBlock(sb, "name", "description", "mediaUrl"); - assertEq(storyBlocks.balanceOf(mintee), loopBalance + 1, "balance is incremented by one"); - assertEq(storyBlocks.ownerOf(blockId1), mintee); + uint256 blockId1 = ipAssetRegistry.createIPAsset(sb, "name", "description", "mediaUrl"); + assertEq(ipAssetRegistry.balanceOf(mintee), loopBalance + 1, "balance is incremented by one"); + assertEq(ipAssetRegistry.ownerOf(blockId1), mintee); vm.prank(mintee); - uint256 blockId2 = storyBlocks.createStoryBlock(sb, "name", "description", "mediaUrl"); - assertEq(storyBlocks.balanceOf(mintee), loopBalance + 2, "balance is incremented by one again"); - assertEq(storyBlocks.ownerOf(blockId2), mintee); + uint256 blockId2 = ipAssetRegistry.createIPAsset(sb, "name", "description", "mediaUrl"); + assertEq(ipAssetRegistry.balanceOf(mintee), loopBalance + 2, "balance is incremented by one again"); + assertEq(ipAssetRegistry.ownerOf(blockId2), mintee); } } - function test_revertMintUnknownStoryBlock() public { + function test_revertMintUnknownIPAsset() public { vm.startPrank(mintee); vm.expectRevert(InvalidBlockType.selector); - storyBlocks.createStoryBlock(StoryBlock.UNDEFINED, "name", "description", "mediaUrl"); + ipAssetRegistry.createIPAsset(IPAsset.UNDEFINED, "name", "description", "mediaUrl"); } - function test_storyBlockCreationData() public { + function test_IPAssetCreationData() public { vm.prank(mintee); - uint256 blockId = storyBlocks.createStoryBlock(StoryBlock.STORY, "name", "description", "mediaUrl"); - StoryBlocksRegistry.StoryBlockData memory data = storyBlocks.readStoryBlock(blockId); - assertEq(uint8(data.blockType), uint8(StoryBlock.STORY)); + uint256 blockId = ipAssetRegistry.createIPAsset(IPAsset.STORY, "name", "description", "mediaUrl"); + IPAssetRegistry.IPAssetData memory data = ipAssetRegistry.readIPAsset(blockId); + assertEq(uint8(data.blockType), uint8(IPAsset.STORY)); assertEq(data.name, "name"); assertEq(data.description, "description"); assertEq(data.mediaUrl, "mediaUrl"); } - function test_emptyStoryBlockRead() public { - StoryBlocksRegistry.StoryBlockData memory data = storyBlocks.readStoryBlock(12312313); - assertEq(uint8(data.blockType), uint8(StoryBlock.UNDEFINED)); + function test_emptyIPAssetRead() public { + IPAssetRegistry.IPAssetData memory data = ipAssetRegistry.readIPAsset(12312313); + assertEq(uint8(data.blockType), uint8(IPAsset.UNDEFINED)); assertEq(data.name, ""); assertEq(data.description, ""); assertEq(data.mediaUrl, ""); @@ -110,8 +110,8 @@ contract StoryBlocksRegistryTest is Test { function test_tokenUriReturnsMediaURL() public { vm.prank(mintee); - uint256 blockId = storyBlocks.createStoryBlock(StoryBlock.STORY, "name", "description", "https://mediaUrl.xyz"); - assertEq(storyBlocks.tokenURI(blockId), "https://mediaUrl.xyz"); + uint256 blockId = ipAssetRegistry.createIPAsset(IPAsset.STORY, "name", "description", "https://mediaUrl.xyz"); + assertEq(ipAssetRegistry.tokenURI(blockId), "https://mediaUrl.xyz"); } } \ No newline at end of file diff --git a/test/foundry/StoryBlocksRegistryFactory.t.sol b/test/foundry/StoryBlocksRegistryFactory.t.sol index e4605bea..2c671bd9 100644 --- a/test/foundry/StoryBlocksRegistryFactory.t.sol +++ b/test/foundry/StoryBlocksRegistryFactory.t.sol @@ -2,34 +2,34 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; -import "contracts/story-blocks/StoryBlocksRegistryFactory.sol"; +import "contracts/ip-assets/IPAssetRegistryFactory.sol"; import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import { IERC1967 } from "@openzeppelin/contracts/interfaces/IERC1967.sol"; -contract StoryBlocksRegistryv2 is StoryBlocksRegistry { +contract IPAssetRegistryv2 is IPAssetRegistry { function version() virtual override external pure returns (string memory) { return "2.0.0"; } } -contract StoryBlocksRegistryFactoryTest is Test { +contract IPAssetRegistryFactoryTest is Test { event FranchiseCreated(address indexed collection, string name, string indexed symbol); event FranchisessUpgraded(address indexed newImplementation, string version); event BeaconUpgraded(address indexed beacon); address notOwner = address(0x123); - StoryBlocksRegistryFactory public factory; + IPAssetRegistryFactory public factory; function setUp() public { - factory = new StoryBlocksRegistryFactory(); + factory = new IPAssetRegistryFactory(); } function test_Contructor() public { assertTrue(address(factory.BEACON()) != address(0)); UpgradeableBeacon beacon = factory.BEACON(); assertTrue(address(beacon.implementation()) != address(0)); - assertEq(StoryBlocksRegistry(beacon.implementation()).version(), "0.1.0"); + assertEq(IPAssetRegistry(beacon.implementation()).version(), "0.1.0"); } function test_CreateFranchiseBlocks() public { @@ -40,21 +40,21 @@ contract StoryBlocksRegistryFactoryTest is Test { // emit BeaconUpgraded(address(0x123)); address collection = factory.createFranchiseBlocks(1, "name", "symbol", "description"); assertTrue(collection != address(0)); - assertEq(StoryBlocksRegistry(collection).name(), "name"); - assertEq(StoryBlocksRegistry(collection).symbol(), "symbol"); + assertEq(IPAssetRegistry(collection).name(), "name"); + assertEq(IPAssetRegistry(collection).symbol(), "symbol"); } function test_UpgradeCollections() public { - StoryBlocksRegistryv2 newImplementation = new StoryBlocksRegistryv2(); + IPAssetRegistryv2 newImplementation = new IPAssetRegistryv2(); //vm.expectEmit(true, true, true, true); //emit CollectionsUpgraded(address(newImplementation), "2.0.0"); factory.upgradeFranchises(address(newImplementation)); UpgradeableBeacon beacon = factory.BEACON(); - assertEq(StoryBlocksRegistry(beacon.implementation()).version(), "2.0.0"); + assertEq(IPAssetRegistry(beacon.implementation()).version(), "2.0.0"); } function test_revertIfNotOwnerUpgrades() public { - StoryBlocksRegistryv2 newImplementation = new StoryBlocksRegistryv2(); + IPAssetRegistryv2 newImplementation = new IPAssetRegistryv2(); vm.prank(notOwner); vm.expectRevert("Ownable: caller is not the owner"); factory.upgradeFranchises(address(newImplementation)); From cb5182e2c8be3380f64044d52157561edfd1fe99 Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 6 Jul 2023 18:45:38 +0200 Subject: [PATCH 06/30] add directional linking --- contracts/modules/linking/LinkingModule.sol | 72 +++++++++++---------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index 472c10ed..1b3c4da7 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -9,17 +9,17 @@ import { UPGRADER_ROLE, LINK_MANAGER_ROLE } from "contracts/access-control/Proto contract LinkingModule is AccessControlledUpgradeable { event Linked( - address col1, - uint256 id1, - address col2, - uint256 id2, + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, bytes32 intent ); event Unlinked( - address col1, - uint256 id1, - address col2, - uint256 id2, + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, bytes32 intent ); event AddedIntentRole(bytes32 intent, bytes32 role); @@ -27,7 +27,7 @@ contract LinkingModule is AccessControlledUpgradeable { error LinkingNonExistentToken(); error IntentAlreadyRegistered(); - error NonExistentIntent(); + error UndefinedIntent(); mapping(bytes32 => bool) public links; mapping(bytes32 => bytes32) public intentRoles; @@ -45,27 +45,27 @@ contract LinkingModule is AccessControlledUpgradeable { } function link( - address col1, - uint256 id1, - address col2, - uint256 id2, + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, bytes32 intent ) external { - _verifyLink(col1, id1, col2, id2); - + _verifyLink(sourceContract, sourceId, destContract, destId, intent); bytes32 intentRole = intentRoles[intent]; + if (intentRole == bytes32(0)) revert UndefinedIntent(); if (intentRole != PERMISSIONLESS_INTENT) { if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); } - links[keccak256(abi.encode(col1, id1, col2, id2, intent))] = true; - emit Linked(col1, id1, col2, id2, intent); + links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))] = true; + emit Linked(sourceContract, sourceId, destContract, destId, intent); } function unlink( - address col1, - uint256 id1, - address col2, - uint256 id2, + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, bytes32 intent ) external { // Whoever has the role of intent can unlink...does this make sense? @@ -73,28 +73,30 @@ contract LinkingModule is AccessControlledUpgradeable { if (intentRole != PERMISSIONLESS_INTENT) { if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); } - delete links[keccak256(abi.encode(col1, id1, col2, id2, intent))]; - emit Unlinked(col1, id1, col2, id2, intent); + delete links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; + emit Unlinked(sourceContract, sourceId, destContract, destId, intent); } function _verifyLink( - address col1, - uint256 id1, - address col2, - uint256 id2 + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, + bytes32 intent ) internal view { - if (IERC721(col1).ownerOf(id1) == address(0)) revert LinkingNonExistentToken(); - if (IERC721(col2).ownerOf(id2) == address(0)) revert LinkingNonExistentToken(); + if (IERC721(sourceContract).ownerOf(sourceId) == address(0)) revert LinkingNonExistentToken(); + if (IERC721(destContract).ownerOf(destId) == address(0)) revert LinkingNonExistentToken(); + } function areTheyLinked( - address col1, - uint256 id1, - address col2, - uint256 id2, + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, bytes32 intent ) external view returns (bool) { - return links[keccak256(abi.encode(col1, id1, col2, id2, intent))]; + return links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; } function addIntentRole(bytes32 intent, bytes32 role) external onlyRole(LINK_MANAGER_ROLE) { @@ -104,7 +106,7 @@ contract LinkingModule is AccessControlledUpgradeable { } function removeIntentRole(bytes32 intent) external onlyRole(LINK_MANAGER_ROLE) { - if (intentRoles[intent] == bytes32(0)) revert NonExistentIntent(); + if (intentRoles[intent] == bytes32(0)) revert UndefinedIntent(); delete intentRoles[intent]; emit RemovedIntentRole(intent, intentRoles[intent]); } From cb457e6516dce16e2213f00e805fcb32b096acc6 Mon Sep 17 00:00:00 2001 From: Raul Date: Fri, 7 Jul 2023 18:58:20 +0200 Subject: [PATCH 07/30] franchise level intents --- contracts/ip-assets/IIPAssetRegistry.sol | 4 +- contracts/modules/linking/LinkingModule.sol | 107 ++++++++++++++------ 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/contracts/ip-assets/IIPAssetRegistry.sol b/contracts/ip-assets/IIPAssetRegistry.sol index 2c1ee05c..c7dc93d7 100644 --- a/contracts/ip-assets/IIPAssetRegistry.sol +++ b/contracts/ip-assets/IIPAssetRegistry.sol @@ -13,4 +13,6 @@ interface IIPAssetRegistry is IERC721Upgradeable, IIPAssetData, IGroupDAM - { } + { + function franchiseId() external view returns (uint256); + } diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index 1b3c4da7..bfafdc49 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -2,11 +2,18 @@ pragma solidity ^0.8.13; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { ZeroAddress, Unauthorized } from "contracts/errors/General.sol"; +import { ZeroAddress, Unauthorized, UnsupportedInterface } from "contracts/errors/General.sol"; import { UPGRADER_ROLE, LINK_MANAGER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; +import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; contract LinkingModule is AccessControlledUpgradeable { + + using ERC165Checker for address; + using EnumerableSet for EnumerableSet.Bytes32Set; event Linked( address sourceContract, @@ -22,21 +29,29 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 destId, bytes32 intent ); - event AddedIntentRole(bytes32 intent, bytes32 role); - event RemovedIntentRole(bytes32 intent, bytes32 role); + + event AddedFranchiseIntentRole(address franchise, bytes32 intent, bytes32 role); + event RemovedFranchiseIntentRole(address franchise, bytes32 intent, bytes32 role); + event AddedProtocolIntentRole(bytes32 intent, bytes32 role); + event RemovedProtocolIntentRole(bytes32 intent, bytes32 role); error LinkingNonExistentToken(); error IntentAlreadyRegistered(); error UndefinedIntent(); mapping(bytes32 => bool) public links; - mapping(bytes32 => bytes32) public intentRoles; + EnumerableSet.Bytes32Set private _franchiseIntents; + mapping(bytes32 => bytes32) public franchiseIntentRole; + mapping(bytes32 => bytes32) public protocolIntentRoles; bytes32 public constant PERMISSIONLESS_INTENT = keccak256("PERMISSIONLESS_INTENT"); + FranchiseRegistry public immutable FRANCHISE_REGISTRY; - constructor() { + constructor(address franchiseRegistry) { + if (franchiseRegistry == address(0)) revert ZeroAddress(); + FRANCHISE_REGISTRY = FranchiseRegistry(franchiseRegistry); _disableInitializers(); } @@ -51,11 +66,12 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 destId, bytes32 intent ) external { - _verifyLink(sourceContract, sourceId, destContract, destId, intent); - bytes32 intentRole = intentRoles[intent]; - if (intentRole == bytes32(0)) revert UndefinedIntent(); - if (intentRole != PERMISSIONLESS_INTENT) { - if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); + if (IERC721(sourceContract).ownerOf(sourceId) == address(0)) revert LinkingNonExistentToken(); + if (IERC721(destContract).ownerOf(destId) == address(0)) revert LinkingNonExistentToken(); + if (sourceContract == destContract && _franchiseIntents.contains(intent)) { + _verifyRole(franchiseIntentRole[keccak256(abi.encode(sourceContract, intent))]); + } else { + _verifyRole(protocolIntentRoles[intent]); } links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))] = true; emit Linked(sourceContract, sourceId, destContract, destId, intent); @@ -68,27 +84,19 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 destId, bytes32 intent ) external { + if (IERC721(sourceContract).ownerOf(sourceId) == address(0)) revert LinkingNonExistentToken(); + if (IERC721(destContract).ownerOf(destId) == address(0)) revert LinkingNonExistentToken(); + // Whoever has the role of intent can unlink...does this make sense? - bytes32 intentRole = intentRoles[intent]; - if (intentRole != PERMISSIONLESS_INTENT) { - if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); + if (sourceContract == destContract && _franchiseIntents.contains(intent)) { + _verifyRole(franchiseIntentRole[keccak256(abi.encode(sourceContract, intent))]); + } else { + _verifyRole(protocolIntentRoles[intent]); } delete links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; emit Unlinked(sourceContract, sourceId, destContract, destId, intent); } - function _verifyLink( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 intent - ) internal view { - if (IERC721(sourceContract).ownerOf(sourceId) == address(0)) revert LinkingNonExistentToken(); - if (IERC721(destContract).ownerOf(destId) == address(0)) revert LinkingNonExistentToken(); - - } - function areTheyLinked( address sourceContract, uint256 sourceId, @@ -99,16 +107,49 @@ contract LinkingModule is AccessControlledUpgradeable { return links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; } - function addIntentRole(bytes32 intent, bytes32 role) external onlyRole(LINK_MANAGER_ROLE) { - if (intentRoles[intent] != bytes32(0)) revert IntentAlreadyRegistered(); - intentRoles[intent] = role; - emit AddedIntentRole(intent, role); + function _verifyRole(bytes32 intentRole) private view { + if (intentRole == bytes32(0)) revert UndefinedIntent(); + if (intentRole != PERMISSIONLESS_INTENT) { + if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); + } + } + + function _verifyIPAssetRegistry(address ipAssetRegistry, address franchiseOwner) private view { + if (!ipAssetRegistry.supportsInterface(type(IIPAssetRegistry).interfaceId)) revert UnsupportedInterface("IIPAssetRegistry"); + uint256 franchiseId = IIPAssetRegistry(ipAssetRegistry).franchiseId(); + if (FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) != ipAssetRegistry) revert Unauthorized(); + if (FRANCHISE_REGISTRY.ownerOf(franchiseId) != franchiseOwner) revert Unauthorized(); + } + + /********* Franchise level intents *********/ + + function addFranchiseIntentRole(address ipAssetRegistry, bytes32 intent, bytes32 role) external { + if (franchiseIntentRole[intent] != bytes32(0)) revert IntentAlreadyRegistered(); + if (protocolIntentRoles[intent] == bytes32(0)) revert IntentAlreadyRegistered(); + _verifyIPAssetRegistry(ipAssetRegistry, msg.sender); + franchiseIntentRole[keccak256(abi.encode(ipAssetRegistry, intent))] = role; + emit AddedFranchiseIntentRole(ipAssetRegistry, intent, role); + } + + function removedFranchiseIntentRole(address ipAssetRegistry, bytes32 intent) external { + if (franchiseIntentRole[intent] == bytes32(0)) revert UndefinedIntent(); + _verifyIPAssetRegistry(ipAssetRegistry, msg.sender); + delete franchiseIntentRole[keccak256(abi.encode(ipAssetRegistry, intent))]; + emit RemovedFranchiseIntentRole(ipAssetRegistry, intent, franchiseIntentRole[intent]); + } + + /********* Protocol level intents *********/ + + function addProtocolIntentRole(bytes32 intent, bytes32 role) external onlyRole(LINK_MANAGER_ROLE) { + if (protocolIntentRoles[intent] != bytes32(0)) revert IntentAlreadyRegistered(); + protocolIntentRoles[intent] = role; + emit AddedProtocolIntentRole(intent, role); } - function removeIntentRole(bytes32 intent) external onlyRole(LINK_MANAGER_ROLE) { - if (intentRoles[intent] == bytes32(0)) revert UndefinedIntent(); - delete intentRoles[intent]; - emit RemovedIntentRole(intent, intentRoles[intent]); + function removeProtocolIntentRole(bytes32 intent) external onlyRole(LINK_MANAGER_ROLE) { + if (protocolIntentRoles[intent] == bytes32(0)) revert UndefinedIntent(); + delete protocolIntentRoles[intent]; + emit RemovedProtocolIntentRole(intent, protocolIntentRoles[intent]); } function _authorizeUpgrade(address newImplementation) internal virtual override onlyRole(UPGRADER_ROLE) {} From a004a0f27600db12996722433e4b3d4f0a2a0c74 Mon Sep 17 00:00:00 2001 From: Raul Date: Tue, 11 Jul 2023 17:17:21 +0200 Subject: [PATCH 08/30] missing refactor files and method --- contracts/FranchiseRegistry.sol | 2 +- contracts/ip-assets/IPAssetRegistryFactory.sol | 2 +- test/foundry/FranchiseRegistry.t.sol | 2 +- .../{StoryBlocksRegistry.t.sol => IPAssetsRegistry.t.sol} | 2 +- ...locksRegistryFactory.t.sol => IPAssetsRegistryFactory.t.sol} | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename test/foundry/{StoryBlocksRegistry.t.sol => IPAssetsRegistry.t.sol} (99%) rename test/foundry/{StoryBlocksRegistryFactory.t.sol => IPAssetsRegistryFactory.t.sol} (96%) diff --git a/contracts/FranchiseRegistry.sol b/contracts/FranchiseRegistry.sol index 14a4df5b..7b747075 100644 --- a/contracts/FranchiseRegistry.sol +++ b/contracts/FranchiseRegistry.sol @@ -66,7 +66,7 @@ contract FranchiseRegistry is string calldata description ) external returns (uint256, address) { FranchiseStorage storage $ = _getFranchiseStorage(); - address ipAssetRegistry = FACTORY.createFranchiseBlocks( + address ipAssetRegistry = FACTORY.createFranchiseIPAssets( ++$.franchiseIds, name, symbol, diff --git a/contracts/ip-assets/IPAssetRegistryFactory.sol b/contracts/ip-assets/IPAssetRegistryFactory.sol index 6d39f983..aed2b198 100644 --- a/contracts/ip-assets/IPAssetRegistryFactory.sol +++ b/contracts/ip-assets/IPAssetRegistryFactory.sol @@ -25,7 +25,7 @@ contract IPAssetRegistryFactory is Ownable { BEACON = new UpgradeableBeacon(address(new IPAssetRegistry())); } - function createFranchiseBlocks( + function createFranchiseIPAssets( uint256 franchiseId, string calldata name, string calldata symbol, diff --git a/test/foundry/FranchiseRegistry.t.sol b/test/foundry/FranchiseRegistry.t.sol index dd3ce1e2..8e515243 100644 --- a/test/foundry/FranchiseRegistry.t.sol +++ b/test/foundry/FranchiseRegistry.t.sol @@ -50,7 +50,7 @@ contract FranchiseRegistryTest is Test, ProxyHelper { vm.startPrank(franchiseOwner); vm.expectCall(address(factory), abi.encodeCall( - factory.createFranchiseBlocks, + factory.createFranchiseIPAssets, ( 1, "name", diff --git a/test/foundry/StoryBlocksRegistry.t.sol b/test/foundry/IPAssetsRegistry.t.sol similarity index 99% rename from test/foundry/StoryBlocksRegistry.t.sol rename to test/foundry/IPAssetsRegistry.t.sol index e2e098cb..99d82da6 100644 --- a/test/foundry/StoryBlocksRegistry.t.sol +++ b/test/foundry/IPAssetsRegistry.t.sol @@ -37,7 +37,7 @@ contract IPAssetRegistryTest is Test { function setUp() public { factory = new IPAssetRegistryFactory(); - ipAssetRegistry = IPAssetRegistry(factory.createFranchiseBlocks(1, "name", "symbol", "description")); + ipAssetRegistry = IPAssetRegistry(factory.createFranchiseIPAssets(1, "name", "symbol", "description")); } function test_setUp() public { diff --git a/test/foundry/StoryBlocksRegistryFactory.t.sol b/test/foundry/IPAssetsRegistryFactory.t.sol similarity index 96% rename from test/foundry/StoryBlocksRegistryFactory.t.sol rename to test/foundry/IPAssetsRegistryFactory.t.sol index 2c671bd9..2df7de07 100644 --- a/test/foundry/StoryBlocksRegistryFactory.t.sol +++ b/test/foundry/IPAssetsRegistryFactory.t.sol @@ -38,7 +38,7 @@ contract IPAssetRegistryFactoryTest is Test { // TODO: figure why this is not matching correctly, the event is emitted according to traces // vm.expectEmit(); // emit BeaconUpgraded(address(0x123)); - address collection = factory.createFranchiseBlocks(1, "name", "symbol", "description"); + address collection = factory.createFranchiseIPAssets(1, "name", "symbol", "description"); assertTrue(collection != address(0)); assertEq(IPAssetRegistry(collection).name(), "name"); assertEq(IPAssetRegistry(collection).symbol(), "symbol"); From f4cb48f802ea7b947d9f4766c2dc55a008397629 Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 13 Jul 2023 12:34:41 +0200 Subject: [PATCH 09/30] remove franchise level links --- contracts/modules/linking/LinkingModule.sol | 52 +++------------------ 1 file changed, 7 insertions(+), 45 deletions(-) diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index bfafdc49..d050191e 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -30,8 +30,6 @@ contract LinkingModule is AccessControlledUpgradeable { bytes32 intent ); - event AddedFranchiseIntentRole(address franchise, bytes32 intent, bytes32 role); - event RemovedFranchiseIntentRole(address franchise, bytes32 intent, bytes32 role); event AddedProtocolIntentRole(bytes32 intent, bytes32 role); event RemovedProtocolIntentRole(bytes32 intent, bytes32 role); @@ -40,13 +38,16 @@ contract LinkingModule is AccessControlledUpgradeable { error UndefinedIntent(); mapping(bytes32 => bool) public links; - EnumerableSet.Bytes32Set private _franchiseIntents; - mapping(bytes32 => bytes32) public franchiseIntentRole; mapping(bytes32 => bytes32) public protocolIntentRoles; + /* + struct LinkParams { + BitMaps.BitMap sourceIPAssetTypeMask; + BitMaps.BitMap destIPAssetTypeMask; + bool sameFranchiseOnly; + address permissionChecker; + }*/ - bytes32 public constant PERMISSIONLESS_INTENT = - keccak256("PERMISSIONLESS_INTENT"); FranchiseRegistry public immutable FRANCHISE_REGISTRY; constructor(address franchiseRegistry) { @@ -66,13 +67,6 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 destId, bytes32 intent ) external { - if (IERC721(sourceContract).ownerOf(sourceId) == address(0)) revert LinkingNonExistentToken(); - if (IERC721(destContract).ownerOf(destId) == address(0)) revert LinkingNonExistentToken(); - if (sourceContract == destContract && _franchiseIntents.contains(intent)) { - _verifyRole(franchiseIntentRole[keccak256(abi.encode(sourceContract, intent))]); - } else { - _verifyRole(protocolIntentRoles[intent]); - } links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))] = true; emit Linked(sourceContract, sourceId, destContract, destId, intent); } @@ -84,15 +78,6 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 destId, bytes32 intent ) external { - if (IERC721(sourceContract).ownerOf(sourceId) == address(0)) revert LinkingNonExistentToken(); - if (IERC721(destContract).ownerOf(destId) == address(0)) revert LinkingNonExistentToken(); - - // Whoever has the role of intent can unlink...does this make sense? - if (sourceContract == destContract && _franchiseIntents.contains(intent)) { - _verifyRole(franchiseIntentRole[keccak256(abi.encode(sourceContract, intent))]); - } else { - _verifyRole(protocolIntentRoles[intent]); - } delete links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; emit Unlinked(sourceContract, sourceId, destContract, destId, intent); } @@ -107,12 +92,6 @@ contract LinkingModule is AccessControlledUpgradeable { return links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; } - function _verifyRole(bytes32 intentRole) private view { - if (intentRole == bytes32(0)) revert UndefinedIntent(); - if (intentRole != PERMISSIONLESS_INTENT) { - if (!hasRole(intentRole, msg.sender)) revert Unauthorized(); - } - } function _verifyIPAssetRegistry(address ipAssetRegistry, address franchiseOwner) private view { if (!ipAssetRegistry.supportsInterface(type(IIPAssetRegistry).interfaceId)) revert UnsupportedInterface("IIPAssetRegistry"); @@ -120,23 +99,6 @@ contract LinkingModule is AccessControlledUpgradeable { if (FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) != ipAssetRegistry) revert Unauthorized(); if (FRANCHISE_REGISTRY.ownerOf(franchiseId) != franchiseOwner) revert Unauthorized(); } - - /********* Franchise level intents *********/ - - function addFranchiseIntentRole(address ipAssetRegistry, bytes32 intent, bytes32 role) external { - if (franchiseIntentRole[intent] != bytes32(0)) revert IntentAlreadyRegistered(); - if (protocolIntentRoles[intent] == bytes32(0)) revert IntentAlreadyRegistered(); - _verifyIPAssetRegistry(ipAssetRegistry, msg.sender); - franchiseIntentRole[keccak256(abi.encode(ipAssetRegistry, intent))] = role; - emit AddedFranchiseIntentRole(ipAssetRegistry, intent, role); - } - - function removedFranchiseIntentRole(address ipAssetRegistry, bytes32 intent) external { - if (franchiseIntentRole[intent] == bytes32(0)) revert UndefinedIntent(); - _verifyIPAssetRegistry(ipAssetRegistry, msg.sender); - delete franchiseIntentRole[keccak256(abi.encode(ipAssetRegistry, intent))]; - emit RemovedFranchiseIntentRole(ipAssetRegistry, intent, franchiseIntentRole[intent]); - } /********* Protocol level intents *********/ From 6f5bca295e830faccec9743f232928d446b92a10 Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 13 Jul 2023 12:37:52 +0200 Subject: [PATCH 10/30] removed intent references in favour of link id --- contracts/modules/linking/LinkingModule.sol | 44 ++++++++++----------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index d050191e..040eb9c8 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -20,25 +20,25 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 sourceId, address destContract, uint256 destId, - bytes32 intent + bytes32 linkId ); event Unlinked( address sourceContract, uint256 sourceId, address destContract, uint256 destId, - bytes32 intent + bytes32 linkId ); - event AddedProtocolIntentRole(bytes32 intent, bytes32 role); - event RemovedProtocolIntentRole(bytes32 intent, bytes32 role); + event AddedProtocolLink(bytes32 linkId, bytes32 role); + event RemovedProtocolLink(bytes32 linkId, bytes32 role); error LinkingNonExistentToken(); error IntentAlreadyRegistered(); error UndefinedIntent(); mapping(bytes32 => bool) public links; - mapping(bytes32 => bytes32) public protocolIntentRoles; + mapping(bytes32 => bytes32) public protocolLinks; /* struct LinkParams { @@ -65,10 +65,10 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 sourceId, address destContract, uint256 destId, - bytes32 intent + bytes32 linkId ) external { - links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))] = true; - emit Linked(sourceContract, sourceId, destContract, destId, intent); + links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, linkId))] = true; + emit Linked(sourceContract, sourceId, destContract, destId, linkId); } function unlink( @@ -76,10 +76,10 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 sourceId, address destContract, uint256 destId, - bytes32 intent + bytes32 linkId ) external { - delete links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; - emit Unlinked(sourceContract, sourceId, destContract, destId, intent); + delete links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, linkId))]; + emit Unlinked(sourceContract, sourceId, destContract, destId, linkId); } function areTheyLinked( @@ -87,9 +87,9 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 sourceId, address destContract, uint256 destId, - bytes32 intent + bytes32 linkId ) external view returns (bool) { - return links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, intent))]; + return links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, linkId))]; } @@ -100,18 +100,18 @@ contract LinkingModule is AccessControlledUpgradeable { if (FRANCHISE_REGISTRY.ownerOf(franchiseId) != franchiseOwner) revert Unauthorized(); } - /********* Protocol level intents *********/ + /********* Protocol level linkIds *********/ - function addProtocolIntentRole(bytes32 intent, bytes32 role) external onlyRole(LINK_MANAGER_ROLE) { - if (protocolIntentRoles[intent] != bytes32(0)) revert IntentAlreadyRegistered(); - protocolIntentRoles[intent] = role; - emit AddedProtocolIntentRole(intent, role); + function addProtocolLink(bytes32 linkId, bytes32 role) external onlyRole(LINK_MANAGER_ROLE) { + if (protocolLinks[linkId] != bytes32(0)) revert IntentAlreadyRegistered(); + protocolLinks[linkId] = role; + emit AddedProtocolLink(linkId, role); } - function removeProtocolIntentRole(bytes32 intent) external onlyRole(LINK_MANAGER_ROLE) { - if (protocolIntentRoles[intent] == bytes32(0)) revert UndefinedIntent(); - delete protocolIntentRoles[intent]; - emit RemovedProtocolIntentRole(intent, protocolIntentRoles[intent]); + function removeProtocolLink(bytes32 linkId) external onlyRole(LINK_MANAGER_ROLE) { + if (protocolLinks[linkId] == bytes32(0)) revert UndefinedIntent(); + delete protocolLinks[linkId]; + emit RemovedProtocolLink(linkId, protocolLinks[linkId]); } function _authorizeUpgrade(address newImplementation) internal virtual override onlyRole(UPGRADER_ROLE) {} From e12675d2f90a82b55b38e9bb650aaaac0bb0d621 Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 13 Jul 2023 18:27:25 +0200 Subject: [PATCH 11/30] link types mask checker --- contracts/access-control/ProtocolRoles.sol | 3 +- .../linking/LinkIPAssetTypeChecker.sol | 47 ++++++ contracts/modules/linking/LinkingModule.sol | 146 ++++++++++++------ .../linking/LinkIPAssetTypeChecker.t..sol | 133 ++++++++++++++++ .../linking/LinkingModule.franchise.t.sol | 70 +++++++++ 5 files changed, 354 insertions(+), 45 deletions(-) create mode 100644 contracts/modules/linking/LinkIPAssetTypeChecker.sol create mode 100644 test/foundry/linking/LinkIPAssetTypeChecker.t..sol create mode 100644 test/foundry/linking/LinkingModule.franchise.t.sol diff --git a/contracts/access-control/ProtocolRoles.sol b/contracts/access-control/ProtocolRoles.sol index 57698290..a6b8edfe 100644 --- a/contracts/access-control/ProtocolRoles.sol +++ b/contracts/access-control/ProtocolRoles.sol @@ -4,4 +4,5 @@ pragma solidity ^0.8.13; bytes32 constant PROTOCOL_ADMIN_ROLE = bytes32(0); bytes32 constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); -bytes32 constant LINK_MANAGER_ROLE = keccak256("LINK_MANAGER_ROLE"); \ No newline at end of file +bytes32 constant LINK_MANAGER_ROLE = keccak256("LINK_MANAGER_ROLE"); +bytes32 constant LINK_DISPUTER_ROLE = keccak256("LINK_DISPUTER_ROLE"); diff --git a/contracts/modules/linking/LinkIPAssetTypeChecker.sol b/contracts/modules/linking/LinkIPAssetTypeChecker.sol new file mode 100644 index 00000000..1e68d0e2 --- /dev/null +++ b/contracts/modules/linking/LinkIPAssetTypeChecker.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IPAsset } from "contracts/IPAsset.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; +import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; + +abstract contract LinkIPAssetTypeChecker { + + error InvalidIPAssetArray(); + + uint8 private constant _EXTERNAL_ASSET_TYPE = type(uint8).max; + + + function _checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) internal view returns (bool) { + if (IERC721(collection).ownerOf(id) == address(0)) return false; + if (_isAssetRegistry(collection)) { + return _supportsIPAssetType(assetTypeMask, _EXTERNAL_ASSET_TYPE); + } else { + return _supportsIPAssetType(assetTypeMask, uint8(LibIPAssetId._ipAssetTypeFor(id))); + } + } + + function _isAssetRegistry(address ipAssetRegistry) internal virtual view returns(bool); + + function _convertToMask(IPAsset[] calldata ipAssets, bool allowsExternal) internal pure returns (uint256) { + if (ipAssets.length == 0) revert InvalidIPAssetArray(); + uint256 mask = 0; + for (uint256 i = 0; i < ipAssets.length;) { + if (ipAssets[i] == IPAsset.UNDEFINED) revert InvalidIPAssetArray(); + mask |= uint256(ipAssets[i]); + unchecked { + i++; + } + } + if (allowsExternal) { + mask |= uint256(_EXTERNAL_ASSET_TYPE); + } + return mask; + } + + function _supportsIPAssetType(uint256 mask, uint8 assetType) internal pure returns (bool) { + return (mask & uint256(assetType)) != 0; + } + +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index 040eb9c8..abf75e8b 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -2,19 +2,15 @@ pragma solidity ^0.8.13; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; -import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { ZeroAddress, Unauthorized, UnsupportedInterface } from "contracts/errors/General.sol"; -import { UPGRADER_ROLE, LINK_MANAGER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; +import { ZeroAddress } from "contracts/errors/General.sol"; +import { UPGRADER_ROLE, LINK_MANAGER_ROLE, LINK_DISPUTER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; +import { IPAsset } from "contracts/IPAsset.sol"; +import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; +import { LinkIPAssetTypeChecker } from "./LinkIPAssetTypeChecker.sol"; -contract LinkingModule is AccessControlledUpgradeable { - - using ERC165Checker for address; - using EnumerableSet for EnumerableSet.Bytes32Set; - +contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { event Linked( address sourceContract, uint256 sourceId, @@ -30,29 +26,42 @@ contract LinkingModule is AccessControlledUpgradeable { bytes32 linkId ); - event AddedProtocolLink(bytes32 linkId, bytes32 role); - event RemovedProtocolLink(bytes32 linkId, bytes32 role); + event AddedProtocolLink( + bytes32 linkId, + uint256 sourceIPAssetTypeMask, + uint256 destIPAssetTypeMask, + bool linkOnlySameFranchise + ); + event RemovedProtocolLink(bytes32 linkId); - error LinkingNonExistentToken(); + error NonExistingLink(); error IntentAlreadyRegistered(); - error UndefinedIntent(); + error UndefinedLink(); + error UnsupportedLinkSource(); + error UnsupportedLinkDestination(); + + struct LinkConfig { + uint256 sourceIPAssetTypeMask; + uint256 destIPAssetTypeMask; + bool linkOnlySameFranchise; + } - mapping(bytes32 => bool) public links; - mapping(bytes32 => bytes32) public protocolLinks; + struct AddLinkParams { + IPAsset[] sourceIPAssets; + bool allowedExternalSource; + IPAsset[] destIPAssets; + bool allowedExternalDest; + bool linkOnlySameFranchise; + } - /* - struct LinkParams { - BitMaps.BitMap sourceIPAssetTypeMask; - BitMaps.BitMap destIPAssetTypeMask; - bool sameFranchiseOnly; - address permissionChecker; - }*/ + mapping(bytes32 => bool) public links; + mapping(bytes32 => LinkConfig) public protocolLinks; FranchiseRegistry public immutable FRANCHISE_REGISTRY; - constructor(address franchiseRegistry) { - if (franchiseRegistry == address(0)) revert ZeroAddress(); - FRANCHISE_REGISTRY = FranchiseRegistry(franchiseRegistry); + constructor(address _franchiseRegistry) { + if (_franchiseRegistry == address(0)) revert ZeroAddress(); + FRANCHISE_REGISTRY = FranchiseRegistry(_franchiseRegistry); _disableInitializers(); } @@ -67,7 +76,25 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 destId, bytes32 linkId ) external { - links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, linkId))] = true; + LinkConfig storage config = protocolLinks[linkId]; + if (_checkLinkEnd( + sourceContract, + sourceId, + config.sourceIPAssetTypeMask + )) revert UnsupportedLinkSource(); + if (_checkLinkEnd(destContract, destId, config.destIPAssetTypeMask)) + revert UnsupportedLinkDestination(); + links[ + keccak256( + abi.encode( + sourceContract, + sourceId, + destContract, + destId, + linkId + ) + ) + ] = true; emit Linked(sourceContract, sourceId, destContract, destId, linkId); } @@ -77,8 +104,12 @@ contract LinkingModule is AccessControlledUpgradeable { address destContract, uint256 destId, bytes32 linkId - ) external { - delete links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, linkId))]; + ) external onlyRole(LINK_DISPUTER_ROLE) { + bytes32 key = keccak256( + abi.encode(sourceContract, sourceId, destContract, destId, linkId) + ); + if (!links[key]) revert NonExistingLink(); + delete links[key]; emit Unlinked(sourceContract, sourceId, destContract, destId, linkId); } @@ -89,30 +120,57 @@ contract LinkingModule is AccessControlledUpgradeable { uint256 destId, bytes32 linkId ) external view returns (bool) { - return links[keccak256(abi.encode(sourceContract, sourceId, destContract, destId, linkId))]; + return + links[ + keccak256( + abi.encode( + sourceContract, + sourceId, + destContract, + destId, + linkId + ) + ) + ]; } - - function _verifyIPAssetRegistry(address ipAssetRegistry, address franchiseOwner) private view { - if (!ipAssetRegistry.supportsInterface(type(IIPAssetRegistry).interfaceId)) revert UnsupportedInterface("IIPAssetRegistry"); + function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { uint256 franchiseId = IIPAssetRegistry(ipAssetRegistry).franchiseId(); - if (FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) != ipAssetRegistry) revert Unauthorized(); - if (FRANCHISE_REGISTRY.ownerOf(franchiseId) != franchiseOwner) revert Unauthorized(); + return FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) != ipAssetRegistry; } /********* Protocol level linkIds *********/ - - function addProtocolLink(bytes32 linkId, bytes32 role) external onlyRole(LINK_MANAGER_ROLE) { - if (protocolLinks[linkId] != bytes32(0)) revert IntentAlreadyRegistered(); - protocolLinks[linkId] = role; - emit AddedProtocolLink(linkId, role); + function addProtocolLink( + bytes32 linkId, + AddLinkParams calldata params + ) external onlyRole(LINK_MANAGER_ROLE) { + LinkConfig memory config = LinkConfig( + _convertToMask(params.sourceIPAssets, params.allowedExternalSource), + _convertToMask(params.destIPAssets, params.allowedExternalDest), + params.linkOnlySameFranchise + ); + protocolLinks[linkId] = config; + emit AddedProtocolLink( + linkId, + config.sourceIPAssetTypeMask, + config.destIPAssetTypeMask, + config.linkOnlySameFranchise + ); } - function removeProtocolLink(bytes32 linkId) external onlyRole(LINK_MANAGER_ROLE) { - if (protocolLinks[linkId] == bytes32(0)) revert UndefinedIntent(); + function removeProtocolLink( + bytes32 linkId + ) external onlyRole(LINK_MANAGER_ROLE) { + if ( + protocolLinks[linkId].sourceIPAssetTypeMask == 0 && + protocolLinks[linkId].destIPAssetTypeMask == 0 + ) revert UndefinedLink(); delete protocolLinks[linkId]; - emit RemovedProtocolLink(linkId, protocolLinks[linkId]); + emit RemovedProtocolLink(linkId); } - function _authorizeUpgrade(address newImplementation) internal virtual override onlyRole(UPGRADER_ROLE) {} + function _authorizeUpgrade( + address newImplementation + ) internal virtual override onlyRole(UPGRADER_ROLE) {} + } \ No newline at end of file diff --git a/test/foundry/linking/LinkIPAssetTypeChecker.t..sol b/test/foundry/linking/LinkIPAssetTypeChecker.t..sol new file mode 100644 index 00000000..e4cd1aad --- /dev/null +++ b/test/foundry/linking/LinkIPAssetTypeChecker.t..sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BUSDL-1.1 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; + +import { IPAssetRegistryFactory } from "contracts/ip-assets/IPAssetRegistryFactory.sol"; +import { LinkIPAssetTypeChecker } from "contracts/modules/linking/LinkIPAssetTypeChecker.sol"; +import { IPAsset } from "contracts/IPAsset.sol"; +import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; + +contract LinkIPAssetTypeCheckerHarness is LinkIPAssetTypeChecker { + + bool private _returnIsAssetRegistry; + + function setIsAssetRegistry(bool value) external { + _returnIsAssetRegistry = value; + } + + function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { + return _returnIsAssetRegistry; + } + + function checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) view external returns (bool) { + return _checkLinkEnd(collection, id, assetTypeMask); + } + + function convertToMask(IPAsset[] calldata ipAssets, bool allowsExternal) pure external returns (uint256) { + return _convertToMask(ipAssets, allowsExternal); + } + + function supportsIPAssetType(uint256 mask, uint8 assetType) pure external returns (bool) { + return _supportsIPAssetType(mask, assetType); + } +} + + +contract LinkIPAssetTypeCheckerConvertToMaskTest is Test { + + LinkIPAssetTypeCheckerHarness public checker; + + error InvalidIPAssetArray(); + + function setUp() public { + checker = new LinkIPAssetTypeCheckerHarness(); + } + + function test_convertToMaskWithoutExternal() public { + for (uint8 i = 1; i <= uint8(IPAsset.ITEM); i++) { + IPAsset[] memory ipAssets = new IPAsset[](i); + uint256 resultMask; + for (uint8 j = 1; j <= i; j++) { + ipAssets[j-1] = IPAsset(j); + resultMask |= uint256(IPAsset(j)); + } + uint256 mask = checker.convertToMask(ipAssets, false); + assertEq(mask, resultMask); + } + } + + function test_convertToMaskWithExternal() public { + for (uint8 i = 1; i <= uint8(IPAsset.ITEM); i++) { + IPAsset[] memory ipAssets = new IPAsset[](i); + uint256 resultMask; + for (uint8 j = 1; j <= i; j++) { + ipAssets[j-1] = IPAsset(j); + resultMask |= uint256(IPAsset(j)); + } + resultMask |= uint256(type(uint8).max); + uint256 mask = checker.convertToMask(ipAssets, true); + assertEq(mask, resultMask); + } + } + + function test_revert_convertToMaskWithExternal_ifEmptyArray() public { + IPAsset[] memory ipAssets = new IPAsset[](0); + vm.expectRevert(InvalidIPAssetArray.selector); + checker.convertToMask(ipAssets, false); + } + + function test_revert_convertToMaskWithExterna_ifZeroRow() public { + IPAsset[] memory ipAssets = new IPAsset[](1); + ipAssets[0] = IPAsset(0); + vm.expectRevert(InvalidIPAssetArray.selector); + checker.convertToMask(ipAssets, false); + } + +} + +contract LinkIPAssetTypeCheckerSupportsAssetTypeTest is Test { + + LinkIPAssetTypeCheckerHarness public checker; + + error InvalidIPAssetArray(); + + function setUp() public { + checker = new LinkIPAssetTypeCheckerHarness(); + } + + function test_supportsIPAssetType_true() public { + uint256 mask = 0; + for (uint8 i = 1; i <= uint8(IPAsset.ITEM); i++) { + mask |= uint256(IPAsset(i)); + } + mask |= uint256(type(uint8).max); + for (uint8 i = 1; i <= uint8(IPAsset.ITEM); i++) { + assertTrue(checker.supportsIPAssetType(mask, i)); + } + assertTrue(checker.supportsIPAssetType(mask, type(uint8).max)); + } + + function test_supportIPAssetType_false() public { + uint256 zeroMask; + for (uint8 i = 1; i <= uint8(IPAsset.ITEM); i++) { + assertFalse(checker.supportsIPAssetType(zeroMask, i)); + } + assertFalse(checker.supportsIPAssetType(zeroMask, type(uint8).max)); + } + +} + +contract LinkIPAssetTypeCheckerCheckLinkEndeTest is Test { + + LinkIPAssetTypeCheckerHarness public checker; + + error InvalidIPAssetArray(); + + function setUp() public { + checker = new LinkIPAssetTypeCheckerHarness(); + } + + + +} \ No newline at end of file diff --git a/test/foundry/linking/LinkingModule.franchise.t.sol b/test/foundry/linking/LinkingModule.franchise.t.sol new file mode 100644 index 00000000..53b96a61 --- /dev/null +++ b/test/foundry/linking/LinkingModule.franchise.t.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BUSDL-1.1 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import '../utils/ProxyHelper.sol'; +import "contracts/FranchiseRegistry.sol"; +import "contracts/access-control/AccessControlSingleton.sol"; +import "contracts/access-control/ProtocolRoles.sol"; +import "contracts/ip-assets/IPAssetRegistryFactory.sol"; +import "contracts/modules/linking/LinkingModule.sol"; + +contract LinkingModuleFranchiseTest is Test, ProxyHelper { + + IPAssetRegistryFactory public factory; + IPAssetRegistry public ipAssetRegistry; + FranchiseRegistry public register; + LinkingModule public linkingModule; + + address admin = address(123); + address linkManager = address(234); + address franchiseOwner = address(456); + + AccessControlSingleton acs; + + function setUp() public { + factory = new IPAssetRegistryFactory(); + vm.prank(admin); + acs = new AccessControlSingleton(); + acs.grantRole(LINK_MANAGER_ROLE, linkManager); + + address accessControl = address(acs); + + FranchiseRegistry impl = new FranchiseRegistry(address(factory)); + register = FranchiseRegistry( + _deployUUPSProxy( + address(impl), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), accessControl + ) + ) + ); + vm.startPrank(franchiseOwner); + (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); + ipAssetRegistry = IPAssetRegistry(ipAssets); + vm.stopPrank(); + linkingModule = new LinkingModule(address(register)); + + } + + function test_setProtocolLevelLink() public { + vm.prank(linkManager); + + } + + function test_revert_IfSettingProtocolLevelLinkUnauthorized() public { + vm.expectRevert(); + } + + function test_revert_IfMasksNotConfigured() public { + vm.expectRevert(); + } + + function test_revert_IfWrongPermissionChecker() public { + vm.expectRevert(); + } + + function test_linkMasks() public { + + } +} From 4a76f0392327e127a7e42858032c5cfd98400305 Mon Sep 17 00:00:00 2001 From: Raul Date: Thu, 13 Jul 2023 18:41:26 +0200 Subject: [PATCH 12/30] moret ipAssetTypeTests --- .../linking/LinkIPAssetTypeChecker.sol | 4 +- .../linking/LinkIPAssetTypeChecker.t..sol | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/contracts/modules/linking/LinkIPAssetTypeChecker.sol b/contracts/modules/linking/LinkIPAssetTypeChecker.sol index 1e68d0e2..20fccdeb 100644 --- a/contracts/modules/linking/LinkIPAssetTypeChecker.sol +++ b/contracts/modules/linking/LinkIPAssetTypeChecker.sol @@ -16,9 +16,9 @@ abstract contract LinkIPAssetTypeChecker { function _checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) internal view returns (bool) { if (IERC721(collection).ownerOf(id) == address(0)) return false; if (_isAssetRegistry(collection)) { - return _supportsIPAssetType(assetTypeMask, _EXTERNAL_ASSET_TYPE); - } else { return _supportsIPAssetType(assetTypeMask, uint8(LibIPAssetId._ipAssetTypeFor(id))); + } else { + return _supportsIPAssetType(assetTypeMask, _EXTERNAL_ASSET_TYPE); } } diff --git a/test/foundry/linking/LinkIPAssetTypeChecker.t..sol b/test/foundry/linking/LinkIPAssetTypeChecker.t..sol index e4cd1aad..5bc8d84e 100644 --- a/test/foundry/linking/LinkIPAssetTypeChecker.t..sol +++ b/test/foundry/linking/LinkIPAssetTypeChecker.t..sol @@ -7,6 +7,8 @@ import { IPAssetRegistryFactory } from "contracts/ip-assets/IPAssetRegistryFacto import { LinkIPAssetTypeChecker } from "contracts/modules/linking/LinkIPAssetTypeChecker.sol"; import { IPAsset } from "contracts/IPAsset.sol"; import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; +import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; contract LinkIPAssetTypeCheckerHarness is LinkIPAssetTypeChecker { @@ -33,6 +35,14 @@ contract LinkIPAssetTypeCheckerHarness is LinkIPAssetTypeChecker { } } +contract MockERC721 is ERC721 { + constructor(string memory name, string memory symbol) ERC721(name, symbol) {} + + function mint(address to, uint256 tokenId) external { + _mint(to, tokenId); + } +} + contract LinkIPAssetTypeCheckerConvertToMaskTest is Test { @@ -121,13 +131,46 @@ contract LinkIPAssetTypeCheckerSupportsAssetTypeTest is Test { contract LinkIPAssetTypeCheckerCheckLinkEndeTest is Test { LinkIPAssetTypeCheckerHarness public checker; + MockERC721 public collection; + address public owner = address(0x1); error InvalidIPAssetArray(); function setUp() public { checker = new LinkIPAssetTypeCheckerHarness(); + collection = new MockERC721("Test", "TEST"); } + function test_checkLinkEnd_ipAsset_true() public { + uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; + collection.mint(owner, tokenId); + checker.setIsAssetRegistry(true); + assertTrue(checker.checkLinkEnd(address(collection), tokenId, uint256(IPAsset(1)))); + } + + function test_checkLinkEnd_ipAsset_false() public { + uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; + collection.mint(owner, tokenId); + checker.setIsAssetRegistry(true); + assertFalse(checker.checkLinkEnd(address(collection), tokenId, uint256(IPAsset(2)))); + } + + function test_checkLinkEnd_external_true() public { + uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; + collection.mint(owner, tokenId); + checker.setIsAssetRegistry(false); + assertTrue(checker.checkLinkEnd(address(collection), tokenId, uint256(type(uint8).max))); + } + + function test_revert_nonExistingToken() public { + vm.expectRevert("ERC721: invalid token ID"); + checker.checkLinkEnd(address(collection), 1, uint256(type(uint8).max)); + } + + function test_revert_notERC721() public { + vm.expectRevert(); + checker.checkLinkEnd(owner, 1, uint256(type(uint8).max)); + } } \ No newline at end of file From 7a694844458528c55a5fb4f9a2ebeeeac19f4512 Mon Sep 17 00:00:00 2001 From: Raul Date: Fri, 14 Jul 2023 18:33:44 +0200 Subject: [PATCH 13/30] tests for linking --- contracts/IPAsset.sol | 2 +- .../AccessControlledUpgradeable.sol | 5 + .../linking/LinkIPAssetTypeChecker.sol | 23 +- contracts/modules/linking/LinkingModule.sol | 90 ++++--- test/foundry/FranchiseRegistry.t.sol | 10 +- .../linking/LinkIPAssetTypeChecker.t..sol | 32 ++- .../linking/LinkingModule.Linking.t.sol | 151 ++++++++++++ .../linking/LinkingModule.SettingLinks.t.sol | 220 ++++++++++++++++++ .../linking/LinkingModule.franchise.t.sol | 70 ------ 9 files changed, 478 insertions(+), 125 deletions(-) create mode 100644 test/foundry/linking/LinkingModule.Linking.t.sol create mode 100644 test/foundry/linking/LinkingModule.SettingLinks.t.sol delete mode 100644 test/foundry/linking/LinkingModule.franchise.t.sol diff --git a/contracts/IPAsset.sol b/contracts/IPAsset.sol index c7698019..efbffc01 100644 --- a/contracts/IPAsset.sol +++ b/contracts/IPAsset.sol @@ -12,4 +12,4 @@ enum IPAsset { ITEM } - \ No newline at end of file +uint8 constant EXTERNAL_ASSET = type(uint8).max; \ No newline at end of file diff --git a/contracts/access-control/AccessControlledUpgradeable.sol b/contracts/access-control/AccessControlledUpgradeable.sol index 5f06c25d..a4b5810e 100644 --- a/contracts/access-control/AccessControlledUpgradeable.sol +++ b/contracts/access-control/AccessControlledUpgradeable.sol @@ -73,4 +73,9 @@ abstract contract AccessControlledUpgradeable is UUPSUpgradeable { emit AccessControlUpdated(accessControl); } + function getAccessControl() public view returns (address) { + AccessControlledStorage storage $ = _getAccessControlledUpgradeable(); + return address($.accessControl); + } + } \ No newline at end of file diff --git a/contracts/modules/linking/LinkIPAssetTypeChecker.sol b/contracts/modules/linking/LinkIPAssetTypeChecker.sol index 20fccdeb..1b41bfe6 100644 --- a/contracts/modules/linking/LinkIPAssetTypeChecker.sol +++ b/contracts/modules/linking/LinkIPAssetTypeChecker.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; -import { IPAsset } from "contracts/IPAsset.sol"; +import { IPAsset, EXTERNAL_ASSET } from "contracts/IPAsset.sol"; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; @@ -10,16 +10,15 @@ abstract contract LinkIPAssetTypeChecker { error InvalidIPAssetArray(); - uint8 private constant _EXTERNAL_ASSET_TYPE = type(uint8).max; - - - function _checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) internal view returns (bool) { - if (IERC721(collection).ownerOf(id) == address(0)) return false; - if (_isAssetRegistry(collection)) { - return _supportsIPAssetType(assetTypeMask, uint8(LibIPAssetId._ipAssetTypeFor(id))); + function _checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) internal view returns (bool result, bool isAssetRegistry) { + if (IERC721(collection).ownerOf(id) == address(0)) return (false, false); + isAssetRegistry = _isAssetRegistry(collection); + if (isAssetRegistry) { + result = _supportsIPAssetType(assetTypeMask, uint8(LibIPAssetId._ipAssetTypeFor(id))); } else { - return _supportsIPAssetType(assetTypeMask, _EXTERNAL_ASSET_TYPE); + result = _supportsIPAssetType(assetTypeMask, EXTERNAL_ASSET); } + return (result, isAssetRegistry); } function _isAssetRegistry(address ipAssetRegistry) internal virtual view returns(bool); @@ -29,19 +28,19 @@ abstract contract LinkIPAssetTypeChecker { uint256 mask = 0; for (uint256 i = 0; i < ipAssets.length;) { if (ipAssets[i] == IPAsset.UNDEFINED) revert InvalidIPAssetArray(); - mask |= uint256(ipAssets[i]); + mask |= 1 << (uint256(ipAssets[i]) & 0xff); unchecked { i++; } } if (allowsExternal) { - mask |= uint256(_EXTERNAL_ASSET_TYPE); + mask |= uint256(EXTERNAL_ASSET) << 248; } return mask; } function _supportsIPAssetType(uint256 mask, uint8 assetType) internal pure returns (bool) { - return (mask & uint256(assetType)) != 0; + return mask & (1 << (uint256(assetType) & 0xff)) != 0; } } \ No newline at end of file diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index abf75e8b..5d759fe5 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -9,6 +9,7 @@ import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; import { IPAsset } from "contracts/IPAsset.sol"; import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; import { LinkIPAssetTypeChecker } from "./LinkIPAssetTypeChecker.sol"; +import "forge-std/console.sol"; contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { event Linked( @@ -26,19 +27,20 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { bytes32 linkId ); - event AddedProtocolLink( + event ProtocolLinkSet( bytes32 linkId, uint256 sourceIPAssetTypeMask, uint256 destIPAssetTypeMask, bool linkOnlySameFranchise ); - event RemovedProtocolLink(bytes32 linkId); + event ProtocolLinkUnset(bytes32 linkId); error NonExistingLink(); error IntentAlreadyRegistered(); error UndefinedLink(); error UnsupportedLinkSource(); error UnsupportedLinkDestination(); + error CannotLinkToAnotherFranchise(); struct LinkConfig { uint256 sourceIPAssetTypeMask; @@ -46,7 +48,7 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { bool linkOnlySameFranchise; } - struct AddLinkParams { + struct SetLinkParams { IPAsset[] sourceIPAssets; bool allowedExternalSource; IPAsset[] destIPAssets; @@ -54,9 +56,14 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { bool linkOnlySameFranchise; } - mapping(bytes32 => bool) public links; - mapping(bytes32 => LinkConfig) public protocolLinks; + /// @custom:storage-location erc7201:story-protocol.linking-module.storage + struct LinkingModuleStorage { + mapping(bytes32 => bool) links; + mapping(bytes32 => LinkConfig) protocolLinks; + } + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.linking-module.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0x9c884c7910549c48b2f059441cfee4a973c8102bda86741fa2535981e323cf9e; FranchiseRegistry public immutable FRANCHISE_REGISTRY; constructor(address _franchiseRegistry) { @@ -69,6 +76,16 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { __AccessControlledUpgradeable_init(accessControl); } + function _getLinkingModuleStorage() + private + pure + returns (LinkingModuleStorage storage $) + { + assembly { + $.slot := _STORAGE_LOCATION + } + } + function link( address sourceContract, uint256 sourceId, @@ -76,15 +93,19 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { uint256 destId, bytes32 linkId ) external { - LinkConfig storage config = protocolLinks[linkId]; - if (_checkLinkEnd( - sourceContract, - sourceId, - config.sourceIPAssetTypeMask - )) revert UnsupportedLinkSource(); - if (_checkLinkEnd(destContract, destId, config.destIPAssetTypeMask)) - revert UnsupportedLinkDestination(); - links[ + LinkingModuleStorage storage $ = _getLinkingModuleStorage(); + LinkConfig storage config = $.protocolLinks[linkId]; + (bool sourceResult, bool sourceIsAssetRegistry) = _checkLinkEnd(sourceContract, sourceId, config.sourceIPAssetTypeMask); + if (!sourceResult) revert UnsupportedLinkSource(); + console.log("destContract", destContract); + console.log("destId", destId); + console.log("config.destIPAssetTypeMask", config.destIPAssetTypeMask); + (bool destResult, bool destIsAssetRegistry) = _checkLinkEnd(destContract, destId, config.destIPAssetTypeMask); + console.log("destResult", destResult); + console.log("destIsAssetRegistry", destIsAssetRegistry); + if (!destResult) revert UnsupportedLinkDestination(); + if(sourceIsAssetRegistry && destIsAssetRegistry && sourceContract != destContract && config.linkOnlySameFranchise) revert CannotLinkToAnotherFranchise(); + $.links[ keccak256( abi.encode( sourceContract, @@ -105,11 +126,12 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { uint256 destId, bytes32 linkId ) external onlyRole(LINK_DISPUTER_ROLE) { + LinkingModuleStorage storage $ = _getLinkingModuleStorage(); bytes32 key = keccak256( abi.encode(sourceContract, sourceId, destContract, destId, linkId) ); - if (!links[key]) revert NonExistingLink(); - delete links[key]; + if (!$.links[key]) revert NonExistingLink(); + delete $.links[key]; emit Unlinked(sourceContract, sourceId, destContract, destId, linkId); } @@ -120,8 +142,9 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { uint256 destId, bytes32 linkId ) external view returns (bool) { + LinkingModuleStorage storage $ = _getLinkingModuleStorage(); return - links[ + $.links[ keccak256( abi.encode( sourceContract, @@ -135,22 +158,26 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { } function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { - uint256 franchiseId = IIPAssetRegistry(ipAssetRegistry).franchiseId(); - return FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) != ipAssetRegistry; + try IIPAssetRegistry(ipAssetRegistry).franchiseId() returns (uint256 franchiseId) { + return FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) == ipAssetRegistry; + } catch { + return false; + } } - /********* Protocol level linkIds *********/ - function addProtocolLink( + /********* Setting Links *********/ + function setProtocolLink( bytes32 linkId, - AddLinkParams calldata params + SetLinkParams calldata params ) external onlyRole(LINK_MANAGER_ROLE) { LinkConfig memory config = LinkConfig( _convertToMask(params.sourceIPAssets, params.allowedExternalSource), _convertToMask(params.destIPAssets, params.allowedExternalDest), params.linkOnlySameFranchise ); - protocolLinks[linkId] = config; - emit AddedProtocolLink( + LinkingModuleStorage storage $ = _getLinkingModuleStorage(); + $.protocolLinks[linkId] = config; + emit ProtocolLinkSet( linkId, config.sourceIPAssetTypeMask, config.destIPAssetTypeMask, @@ -158,15 +185,20 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { ); } - function removeProtocolLink( + function unsetProtocolLink( bytes32 linkId ) external onlyRole(LINK_MANAGER_ROLE) { + LinkingModuleStorage storage $ = _getLinkingModuleStorage(); if ( - protocolLinks[linkId].sourceIPAssetTypeMask == 0 && - protocolLinks[linkId].destIPAssetTypeMask == 0 + $.protocolLinks[linkId].sourceIPAssetTypeMask == 0 ) revert UndefinedLink(); - delete protocolLinks[linkId]; - emit RemovedProtocolLink(linkId); + delete $.protocolLinks[linkId]; + emit ProtocolLinkUnset(linkId); + } + + function protocolLinks(bytes32 linkId) external view returns (LinkConfig memory) { + LinkingModuleStorage storage $ = _getLinkingModuleStorage(); + return $.protocolLinks[linkId]; } function _authorizeUpgrade( diff --git a/test/foundry/FranchiseRegistry.t.sol b/test/foundry/FranchiseRegistry.t.sol index 8e515243..ba635c89 100644 --- a/test/foundry/FranchiseRegistry.t.sol +++ b/test/foundry/FranchiseRegistry.t.sol @@ -25,8 +25,14 @@ contract FranchiseRegistryTest is Test, ProxyHelper { function setUp() public { factory = new IPAssetRegistryFactory(); - vm.prank(admin); - acs = new AccessControlSingleton(); + acs = AccessControlSingleton( + _deployUUPSProxy( + address(new AccessControlSingleton()), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), admin + ) + ) + ); address accessControl = address(acs); FranchiseRegistry impl = new FranchiseRegistry(address(factory)); diff --git a/test/foundry/linking/LinkIPAssetTypeChecker.t..sol b/test/foundry/linking/LinkIPAssetTypeChecker.t..sol index 5bc8d84e..8a624c29 100644 --- a/test/foundry/linking/LinkIPAssetTypeChecker.t..sol +++ b/test/foundry/linking/LinkIPAssetTypeChecker.t..sol @@ -5,7 +5,7 @@ import "forge-std/Test.sol"; import { IPAssetRegistryFactory } from "contracts/ip-assets/IPAssetRegistryFactory.sol"; import { LinkIPAssetTypeChecker } from "contracts/modules/linking/LinkIPAssetTypeChecker.sol"; -import { IPAsset } from "contracts/IPAsset.sol"; +import { IPAsset, EXTERNAL_ASSET } from "contracts/IPAsset.sol"; import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; @@ -22,7 +22,7 @@ contract LinkIPAssetTypeCheckerHarness is LinkIPAssetTypeChecker { return _returnIsAssetRegistry; } - function checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) view external returns (bool) { + function checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) view external returns (bool result, bool isAssetRegistry) { return _checkLinkEnd(collection, id, assetTypeMask); } @@ -60,7 +60,7 @@ contract LinkIPAssetTypeCheckerConvertToMaskTest is Test { uint256 resultMask; for (uint8 j = 1; j <= i; j++) { ipAssets[j-1] = IPAsset(j); - resultMask |= uint256(IPAsset(j)); + resultMask |= 1 << (uint256(IPAsset(j)) & 0xff); } uint256 mask = checker.convertToMask(ipAssets, false); assertEq(mask, resultMask); @@ -73,9 +73,9 @@ contract LinkIPAssetTypeCheckerConvertToMaskTest is Test { uint256 resultMask; for (uint8 j = 1; j <= i; j++) { ipAssets[j-1] = IPAsset(j); - resultMask |= uint256(IPAsset(j)); + resultMask |= 1 << (uint256(IPAsset(j)) & 0xff); } - resultMask |= uint256(type(uint8).max); + resultMask |= uint256(EXTERNAL_ASSET) << 248; uint256 mask = checker.convertToMask(ipAssets, true); assertEq(mask, resultMask); } @@ -109,9 +109,9 @@ contract LinkIPAssetTypeCheckerSupportsAssetTypeTest is Test { function test_supportsIPAssetType_true() public { uint256 mask = 0; for (uint8 i = 1; i <= uint8(IPAsset.ITEM); i++) { - mask |= uint256(IPAsset(i)); + mask |= 1 << (uint256(IPAsset(i)) & 0xff); } - mask |= uint256(type(uint8).max); + mask |= uint256(EXTERNAL_ASSET) << 248; for (uint8 i = 1; i <= uint8(IPAsset.ITEM); i++) { assertTrue(checker.supportsIPAssetType(mask, i)); } @@ -128,7 +128,7 @@ contract LinkIPAssetTypeCheckerSupportsAssetTypeTest is Test { } -contract LinkIPAssetTypeCheckerCheckLinkEndeTest is Test { +contract LinkIPAssetTypeCheckerCheckLinkEndTest is Test { LinkIPAssetTypeCheckerHarness public checker; MockERC721 public collection; @@ -143,23 +143,33 @@ contract LinkIPAssetTypeCheckerCheckLinkEndeTest is Test { function test_checkLinkEnd_ipAsset_true() public { uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; + console.log(tokenId); collection.mint(owner, tokenId); checker.setIsAssetRegistry(true); - assertTrue(checker.checkLinkEnd(address(collection), tokenId, uint256(IPAsset(1)))); + uint256 mask = 1 << (uint256(IPAsset(1)) & 0xff); + (bool result, bool isAssetRegistry) = checker.checkLinkEnd(address(collection), tokenId, mask); + assertTrue(result); + assertTrue(isAssetRegistry); } function test_checkLinkEnd_ipAsset_false() public { uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; collection.mint(owner, tokenId); checker.setIsAssetRegistry(true); - assertFalse(checker.checkLinkEnd(address(collection), tokenId, uint256(IPAsset(2)))); + uint256 mask = 1 << (uint256(IPAsset(2)) & 0xff); + (bool result, bool isAssetRegistry) = checker.checkLinkEnd(address(collection), tokenId, mask); + assertFalse(result); + assertTrue(isAssetRegistry); } function test_checkLinkEnd_external_true() public { uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; collection.mint(owner, tokenId); checker.setIsAssetRegistry(false); - assertTrue(checker.checkLinkEnd(address(collection), tokenId, uint256(type(uint8).max))); + uint256 mask = 1 << (uint256(EXTERNAL_ASSET) & 0xff); + (bool result, bool isAssetRegistry) = checker.checkLinkEnd(address(collection), tokenId, mask); + assertTrue(result); + assertFalse(isAssetRegistry); } function test_revert_nonExistingToken() public { diff --git a/test/foundry/linking/LinkingModule.Linking.t.sol b/test/foundry/linking/LinkingModule.Linking.t.sol new file mode 100644 index 00000000..f2bef0d9 --- /dev/null +++ b/test/foundry/linking/LinkingModule.Linking.t.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: BUSDL-1.1 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import '../utils/ProxyHelper.sol'; +import "contracts/FranchiseRegistry.sol"; +import "contracts/access-control/AccessControlSingleton.sol"; +import "contracts/access-control/ProtocolRoles.sol"; +import "contracts/ip-assets/IPAssetRegistryFactory.sol"; +import "contracts/modules/linking/LinkingModule.sol"; +import "contracts/IPAsset.sol"; +import "contracts/errors/General.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MockExternalAsset is ERC721 { + constructor() ERC721("MockExternalAsset", "MEA") {} + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } +} + +contract LinkingModuleLinkingTest is Test, ProxyHelper { + + IPAssetRegistryFactory public factory; + IPAssetRegistry public ipAssetRegistry; + FranchiseRegistry public register; + LinkingModule public linkingModule; + AccessControlSingleton acs; + + address admin = address(123); + address linkManager = address(234); + address franchiseOwner = address(456); + address ipAssetOwner = address(567); + + bytes32 protocolLink = keccak256("PROTOCOL_LINK"); + + mapping(uint8 => uint256) public ipAssetIds; + + MockExternalAsset public externalAsset; + + + function setUp() public { + factory = new IPAssetRegistryFactory(); + acs = AccessControlSingleton( + _deployUUPSProxy( + address(new AccessControlSingleton()), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), admin + ) + ) + ); + vm.prank(admin); + acs.grantRole(LINK_MANAGER_ROLE, linkManager); + + FranchiseRegistry impl = new FranchiseRegistry(address(factory)); + register = FranchiseRegistry( + _deployUUPSProxy( + address(impl), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), address(acs) + ) + ) + ); + vm.prank(franchiseOwner); + (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); + ipAssetRegistry = IPAssetRegistry(ipAssets); + + linkingModule = LinkingModule( + _deployUUPSProxy( + address(new LinkingModule(address(register))), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), address(acs) + ) + ) + ); + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.STORY; + IPAsset[] memory destIPAssets = new IPAsset[](2); + destIPAssets[0] = IPAsset.CHARACTER; + destIPAssets[1] = IPAsset.ART; + + LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + linkOnlySameFranchise: true + }); + vm.prank(linkManager); + linkingModule.setProtocolLink(protocolLink, params); + vm.startPrank(ipAssetOwner); + + ipAssetIds[uint8(IPAsset.STORY)] = ipAssetRegistry.createIPAsset(IPAsset.STORY, "name", "description", "mediaUrl"); + ipAssetIds[uint8(IPAsset.CHARACTER)] = ipAssetRegistry.createIPAsset(IPAsset.CHARACTER, "name", "description", "mediaUrl"); + ipAssetIds[uint8(IPAsset.ART)] = ipAssetRegistry.createIPAsset(IPAsset.ART, "name", "description", "mediaUrl"); + + externalAsset = new MockExternalAsset(); + ipAssetIds[EXTERNAL_ASSET] = 333; + externalAsset.mint(ipAssetOwner, 333); + vm.stopPrank(); + } + + function test_link() public { + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink); + assertTrue( + linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink) + ); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink); + assertTrue( + linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink) + ); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink); + assertTrue( + linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink) + ); + // TODO check for event + assertFalse( + linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, protocolLink) + ); + assertFalse( + linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG_LINK")) + ); + } + + function test_revert_linkingNotSameFranchise() public { + vm.prank(franchiseOwner); + (uint256 id, address otherIPAssets) = register.registerFranchise("name2", "symbol2", "description2"); + IPAssetRegistry otherIPAssetRegistry = IPAssetRegistry(otherIPAssets); + vm.prank(ipAssetOwner); + uint256 otherId = otherIPAssetRegistry.createIPAsset(IPAsset.CHARACTER, "name", "description", "mediaUrl"); + vm.expectRevert(LinkingModule.CannotLinkToAnotherFranchise.selector); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, protocolLink); + } + + function test_revert_linkUnsupportedSource() public { + vm.prank(ipAssetOwner); + uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); + vm.expectRevert(LinkingModule.UnsupportedLinkSource.selector); + linkingModule.link(address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink); + } + + function test_revert_linkUnsupportedDestination() public { + vm.prank(ipAssetOwner); + uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); + vm.expectRevert(LinkingModule.UnsupportedLinkDestination.selector); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, protocolLink); + } + + +} diff --git a/test/foundry/linking/LinkingModule.SettingLinks.t.sol b/test/foundry/linking/LinkingModule.SettingLinks.t.sol new file mode 100644 index 00000000..b0865007 --- /dev/null +++ b/test/foundry/linking/LinkingModule.SettingLinks.t.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: BUSDL-1.1 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import '../utils/ProxyHelper.sol'; +import "contracts/FranchiseRegistry.sol"; +import "contracts/access-control/AccessControlSingleton.sol"; +import "contracts/access-control/ProtocolRoles.sol"; +import "contracts/ip-assets/IPAssetRegistryFactory.sol"; +import "contracts/modules/linking/LinkingModule.sol"; +import "contracts/IPAsset.sol"; +import "contracts/errors/General.sol"; + +contract LinkingModuleSetupLinksTest is Test, ProxyHelper { + + IPAssetRegistryFactory public factory; + IPAssetRegistry public ipAssetRegistry; + FranchiseRegistry public register; + LinkingModule public linkingModule; + AccessControlSingleton acs; + + address admin = address(123); + address linkManager = address(234); + address franchiseOwner = address(456); + + bytes32 protocolLink = keccak256("PROTOCOL_LINK"); + + function setUp() public { + factory = new IPAssetRegistryFactory(); + acs = AccessControlSingleton( + _deployUUPSProxy( + address(new AccessControlSingleton()), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), admin + ) + ) + ); + vm.prank(admin); + acs.grantRole(LINK_MANAGER_ROLE, linkManager); + + address accessControl = address(acs); + + FranchiseRegistry impl = new FranchiseRegistry(address(factory)); + register = FranchiseRegistry( + _deployUUPSProxy( + address(impl), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), accessControl + ) + ) + ); + vm.startPrank(franchiseOwner); + (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); + ipAssetRegistry = IPAssetRegistry(ipAssets); + vm.stopPrank(); + linkingModule = LinkingModule( + _deployUUPSProxy( + address(new LinkingModule(address(register))), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), address(acs) + ) + ) + ); + } + + function test_setProtocolLevelLink() public { + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.STORY; + IPAsset[] memory destIPAssets = new IPAsset[](2); + destIPAssets[0] = IPAsset.CHARACTER; + destIPAssets[1] = IPAsset.ART; + + LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + linkOnlySameFranchise: true + }); + assertTrue(acs.hasRole(LINK_MANAGER_ROLE, linkManager)); + vm.prank(linkManager); + linkingModule.setProtocolLink(protocolLink, params); + + LinkingModule.LinkConfig memory config = linkingModule.protocolLinks(protocolLink); + assertEq(config.sourceIPAssetTypeMask, 1 << (uint256(IPAsset.STORY) & 0xff)); + assertEq(config.destIPAssetTypeMask, 1 << (uint256(IPAsset.CHARACTER) & 0xff) | 1 << (uint256(IPAsset.ART) & 0xff) | (uint256(EXTERNAL_ASSET) << 248)); + assertTrue(config.linkOnlySameFranchise); + // TODO: test for event + + } + + function test_revert_IfSettingProtocolLevelLinkUnauthorized() public { + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.STORY; + IPAsset[] memory destIPAssets = new IPAsset[](2); + destIPAssets[0] = IPAsset.CHARACTER; + destIPAssets[1] = IPAsset.ART; + + LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + linkOnlySameFranchise: true + }); + vm.expectRevert(); + vm.prank(franchiseOwner); + linkingModule.setProtocolLink(protocolLink, params); + } + + function test_revert_IfMasksNotConfigured() public { + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.UNDEFINED; + IPAsset[] memory destIPAssets = new IPAsset[](2); + + LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + linkOnlySameFranchise: true + }); + vm.startPrank(linkManager); + vm.expectRevert(); + linkingModule.setProtocolLink(protocolLink, params); + } + +} + +contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { + + IPAssetRegistryFactory public factory; + IPAssetRegistry public ipAssetRegistry; + FranchiseRegistry public register; + LinkingModule public linkingModule; + AccessControlSingleton acs; + + address admin = address(123); + address linkManager = address(234); + address franchiseOwner = address(456); + + bytes32 protocolLink = keccak256("PROTOCOL_LINK"); + + function setUp() public { + factory = new IPAssetRegistryFactory(); + acs = AccessControlSingleton( + _deployUUPSProxy( + address(new AccessControlSingleton()), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), admin + ) + ) + ); + vm.prank(admin); + acs.grantRole(LINK_MANAGER_ROLE, linkManager); + + address accessControl = address(acs); + + FranchiseRegistry impl = new FranchiseRegistry(address(factory)); + register = FranchiseRegistry( + _deployUUPSProxy( + address(impl), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), accessControl + ) + ) + ); + vm.startPrank(franchiseOwner); + (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); + ipAssetRegistry = IPAssetRegistry(ipAssets); + vm.stopPrank(); + linkingModule = LinkingModule( + _deployUUPSProxy( + address(new LinkingModule(address(register))), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), address(acs) + ) + ) + ); + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.STORY; + IPAsset[] memory destIPAssets = new IPAsset[](1); + destIPAssets[0] = IPAsset.CHARACTER; + LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + linkOnlySameFranchise: true + }); + vm.prank(linkManager); + linkingModule.setProtocolLink(protocolLink, params); + + } + + function test_unsetProtocolLink() public { + vm.prank(linkManager); + linkingModule.unsetProtocolLink(protocolLink); + + LinkingModule.LinkConfig memory config = linkingModule.protocolLinks(protocolLink); + assertEq(config.sourceIPAssetTypeMask, 0); + assertEq(config.destIPAssetTypeMask, 0); + assertFalse(config.linkOnlySameFranchise); + // TODO: test for event + } + + function test_revert_unsetProtocolLinkNotAuthorized() public { + vm.expectRevert(); + linkingModule.unsetProtocolLink(protocolLink); + } + + function test_revert_unsetProtocolLinkUndefinedLink() public { + vm.prank(linkManager); + vm.expectRevert(LinkingModule.UndefinedLink.selector); + linkingModule.unsetProtocolLink(keccak256("UNDEFINED_LINK")); + } + + + +} diff --git a/test/foundry/linking/LinkingModule.franchise.t.sol b/test/foundry/linking/LinkingModule.franchise.t.sol deleted file mode 100644 index 53b96a61..00000000 --- a/test/foundry/linking/LinkingModule.franchise.t.sol +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-License-Identifier: BUSDL-1.1 -pragma solidity ^0.8.13; - -import "forge-std/Test.sol"; -import '../utils/ProxyHelper.sol'; -import "contracts/FranchiseRegistry.sol"; -import "contracts/access-control/AccessControlSingleton.sol"; -import "contracts/access-control/ProtocolRoles.sol"; -import "contracts/ip-assets/IPAssetRegistryFactory.sol"; -import "contracts/modules/linking/LinkingModule.sol"; - -contract LinkingModuleFranchiseTest is Test, ProxyHelper { - - IPAssetRegistryFactory public factory; - IPAssetRegistry public ipAssetRegistry; - FranchiseRegistry public register; - LinkingModule public linkingModule; - - address admin = address(123); - address linkManager = address(234); - address franchiseOwner = address(456); - - AccessControlSingleton acs; - - function setUp() public { - factory = new IPAssetRegistryFactory(); - vm.prank(admin); - acs = new AccessControlSingleton(); - acs.grantRole(LINK_MANAGER_ROLE, linkManager); - - address accessControl = address(acs); - - FranchiseRegistry impl = new FranchiseRegistry(address(factory)); - register = FranchiseRegistry( - _deployUUPSProxy( - address(impl), - abi.encodeWithSelector( - bytes4(keccak256(bytes("initialize(address)"))), accessControl - ) - ) - ); - vm.startPrank(franchiseOwner); - (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); - ipAssetRegistry = IPAssetRegistry(ipAssets); - vm.stopPrank(); - linkingModule = new LinkingModule(address(register)); - - } - - function test_setProtocolLevelLink() public { - vm.prank(linkManager); - - } - - function test_revert_IfSettingProtocolLevelLinkUnauthorized() public { - vm.expectRevert(); - } - - function test_revert_IfMasksNotConfigured() public { - vm.expectRevert(); - } - - function test_revert_IfWrongPermissionChecker() public { - vm.expectRevert(); - } - - function test_linkMasks() public { - - } -} From 846ef3fd2b4d75312427ee0c2c49a1488641081e Mon Sep 17 00:00:00 2001 From: Raul Date: Fri, 14 Jul 2023 19:45:44 +0200 Subject: [PATCH 14/30] check for link existance and unify errors --- contracts/modules/linking/LinkingModule.sol | 9 ++------- test/foundry/linking/LinkingModule.Linking.t.sol | 5 +++++ test/foundry/linking/LinkingModule.SettingLinks.t.sol | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index 5d759fe5..4aec6fea 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -37,7 +37,6 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { error NonExistingLink(); error IntentAlreadyRegistered(); - error UndefinedLink(); error UnsupportedLinkSource(); error UnsupportedLinkDestination(); error CannotLinkToAnotherFranchise(); @@ -95,14 +94,10 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { ) external { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); LinkConfig storage config = $.protocolLinks[linkId]; + if (config.sourceIPAssetTypeMask == 0) revert NonExistingLink(); (bool sourceResult, bool sourceIsAssetRegistry) = _checkLinkEnd(sourceContract, sourceId, config.sourceIPAssetTypeMask); if (!sourceResult) revert UnsupportedLinkSource(); - console.log("destContract", destContract); - console.log("destId", destId); - console.log("config.destIPAssetTypeMask", config.destIPAssetTypeMask); (bool destResult, bool destIsAssetRegistry) = _checkLinkEnd(destContract, destId, config.destIPAssetTypeMask); - console.log("destResult", destResult); - console.log("destIsAssetRegistry", destIsAssetRegistry); if (!destResult) revert UnsupportedLinkDestination(); if(sourceIsAssetRegistry && destIsAssetRegistry && sourceContract != destContract && config.linkOnlySameFranchise) revert CannotLinkToAnotherFranchise(); $.links[ @@ -191,7 +186,7 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); if ( $.protocolLinks[linkId].sourceIPAssetTypeMask == 0 - ) revert UndefinedLink(); + ) revert NonExistingLink(); delete $.protocolLinks[linkId]; emit ProtocolLinkUnset(linkId); } diff --git a/test/foundry/linking/LinkingModule.Linking.t.sol b/test/foundry/linking/LinkingModule.Linking.t.sol index f2bef0d9..9010766e 100644 --- a/test/foundry/linking/LinkingModule.Linking.t.sol +++ b/test/foundry/linking/LinkingModule.Linking.t.sol @@ -123,6 +123,11 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { ); } + function test_revert_unknown_link() public { + vm.expectRevert(LinkingModule.NonExistingLink.selector); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG_LINK")); + } + function test_revert_linkingNotSameFranchise() public { vm.prank(franchiseOwner); (uint256 id, address otherIPAssets) = register.registerFranchise("name2", "symbol2", "description2"); diff --git a/test/foundry/linking/LinkingModule.SettingLinks.t.sol b/test/foundry/linking/LinkingModule.SettingLinks.t.sol index b0865007..6482b587 100644 --- a/test/foundry/linking/LinkingModule.SettingLinks.t.sol +++ b/test/foundry/linking/LinkingModule.SettingLinks.t.sol @@ -209,9 +209,9 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { linkingModule.unsetProtocolLink(protocolLink); } - function test_revert_unsetProtocolLinkUndefinedLink() public { + function test_revert_unsetProtocolLinkNonExistingLink() public { vm.prank(linkManager); - vm.expectRevert(LinkingModule.UndefinedLink.selector); + vm.expectRevert(LinkingModule.NonExistingLink.selector); linkingModule.unsetProtocolLink(keccak256("UNDEFINED_LINK")); } From 72cf6709639353576a2535f394bc7112aab53205 Mon Sep 17 00:00:00 2001 From: Raul Date: Fri, 14 Jul 2023 21:12:02 +0200 Subject: [PATCH 15/30] refactor regrouping --- contracts/modules/linking/ILinkingModule.sol | 69 ++++++++ .../LinkProcessors/BaseLinkProcessor.sol | 31 ++++ .../LinkProcessors/DstOwnerLinkProcessor.sol | 19 +++ .../linking/LinkProcessors/ILinkProcessor.sol | 10 ++ .../PermissionlessLinkProcessor.sol | 14 ++ .../SrcDstOwnerLinkProcessor.sol | 21 +++ .../LinkProcessors/SrcOwnerLinkProcessor.sol | 19 +++ contracts/modules/linking/LinkingModule.sol | 158 ++++++------------ .../linking/LinkingModule.Linking.t.sol | 20 ++- .../linking/LinkingModule.SettingLinks.t.sol | 19 ++- 10 files changed, 256 insertions(+), 124 deletions(-) create mode 100644 contracts/modules/linking/ILinkingModule.sol create mode 100644 contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol create mode 100644 contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol create mode 100644 contracts/modules/linking/LinkProcessors/ILinkProcessor.sol create mode 100644 contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol create mode 100644 contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol create mode 100644 contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol diff --git a/contracts/modules/linking/ILinkingModule.sol b/contracts/modules/linking/ILinkingModule.sol new file mode 100644 index 00000000..0a19b133 --- /dev/null +++ b/contracts/modules/linking/ILinkingModule.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { ILinkProcessor } from "./LinkProcessors/ILinkProcessor.sol"; +import { IPAsset } from "contracts/IPAsset.sol"; + +interface ILinkingModule { + + event Linked( + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, + bytes32 linkId + ); + event Unlinked( + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, + bytes32 linkId + ); + + event ProtocolLinkSet( + bytes32 linkId, + uint256 sourceIPAssetTypeMask, + uint256 destIPAssetTypeMask, + bool linkOnlySameFranchise, + address linkProcessor + ); + event ProtocolLinkUnset(bytes32 linkId); + + error NonExistingLink(); + error IntentAlreadyRegistered(); + error UnsupportedLinkSource(); + error UnsupportedLinkDestination(); + error CannotLinkToAnotherFranchise(); + + struct LinkConfig { + uint256 sourceIPAssetTypeMask; + uint256 destIPAssetTypeMask; + bool linkOnlySameFranchise; + ILinkProcessor processor; + } + + struct SetLinkParams { + IPAsset[] sourceIPAssets; + bool allowedExternalSource; + IPAsset[] destIPAssets; + bool allowedExternalDest; + bool linkOnlySameFranchise; + address linkProcessor; + } + + struct LinkParams { + address sourceContract; + uint256 sourceId; + address destContract; + uint256 destId; + bytes32 linkId; + } + + function link(LinkParams calldata params, bytes calldata data) external; + function unlink(LinkParams calldata params) external; + function areTheyLinked(LinkParams calldata params) external view returns (bool); + function setProtocolLink(bytes32 linkId, SetLinkParams calldata params) external; + function unsetProtocolLink(bytes32 linkId) external; + function protocolLinks(bytes32 linkId) external view returns (LinkConfig memory); +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol new file mode 100644 index 00000000..97997ee1 --- /dev/null +++ b/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { ILinkProcessor } from "./ILinkProcessor.sol"; +import { ZeroAddress } from "contracts/errors/General.sol"; +import { ILinkingModule } from "../ILinkingModule.sol"; + +abstract contract BaseLinkProcessor is ILinkProcessor { + + address internal immutable _LINKING_MODULE; + error OnlyLinkingModule(); + + constructor(address _linkingModule) { + if(_linkingModule != address(0)) revert ZeroAddress(); + _LINKING_MODULE = _linkingModule; + } + + function processLink(ILinkingModule.LinkParams memory params, bytes calldata data, address linker) external override { + if(msg.sender != _LINKING_MODULE) revert OnlyLinkingModule(); + _processLink(params, data, linker); + } + + function _processLink(ILinkingModule.LinkParams memory params, bytes calldata data, address linker) internal virtual; + + function supportsInterface( + bytes4 interfaceId + ) external pure override returns (bool) { + return interfaceId == type(ILinkProcessor).interfaceId; + } + +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol new file mode 100644 index 00000000..bde45ab8 --- /dev/null +++ b/contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Unauthorized } from "contracts/errors/General.sol"; +import { ILinkingModule } from "../ILinkingModule.sol"; + +contract DstOwnerLinkProcessor is BaseLinkProcessor { + + constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} + + function _processLink(ILinkingModule.LinkParams memory params, bytes calldata, address linker) internal view virtual override { + if (IERC721(params.destContract).ownerOf(params.destId) != linker) { + revert Unauthorized(); + } + } + +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol b/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol new file mode 100644 index 00000000..01df242e --- /dev/null +++ b/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { ILinkingModule } from "../ILinkingModule.sol"; + +interface ILinkProcessor is IERC165 { + + function processLink(ILinkingModule.LinkParams memory params, bytes calldata data, address linker) external; +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol new file mode 100644 index 00000000..f5bc77cd --- /dev/null +++ b/contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; +import { ILinkingModule } from "../ILinkingModule.sol"; + +contract PermissionlessLinkProcessor is BaseLinkProcessor { + + constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} + + function _processLink(ILinkingModule.LinkParams memory, bytes calldata, address) internal virtual override { + // do nothing + } +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol new file mode 100644 index 00000000..0ed7e157 --- /dev/null +++ b/contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Unauthorized } from "contracts/errors/General.sol"; +import { ILinkingModule } from "../ILinkingModule.sol"; + +contract SrcDstOwnerLinkProcessor is BaseLinkProcessor { + + constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} + + function _processLink(ILinkingModule.LinkParams memory params, bytes calldata, address linker) internal view virtual override { + if ( + IERC721(params.sourceContract).ownerOf(params.sourceId) != linker || + IERC721(params.destContract).ownerOf(params.destId) != linker) { + revert Unauthorized(); + } + } + +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol new file mode 100644 index 00000000..14fe8c17 --- /dev/null +++ b/contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Unauthorized } from "contracts/errors/General.sol"; +import { ILinkingModule } from "../ILinkingModule.sol"; + +contract SrcOwnerLinkProcessor is BaseLinkProcessor { + + constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} + + function _processLink(ILinkingModule.LinkParams memory params, bytes calldata, address linker) internal view virtual override { + if (IERC721(params.sourceContract).ownerOf(params.sourceId) != linker) { + revert Unauthorized(); + } + } + +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index 4aec6fea..c0107eab 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -2,58 +2,18 @@ pragma solidity ^0.8.13; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { ZeroAddress } from "contracts/errors/General.sol"; +import { ZeroAddress, UnsupportedInterface } from "contracts/errors/General.sol"; import { UPGRADER_ROLE, LINK_MANAGER_ROLE, LINK_DISPUTER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; -import { IPAsset } from "contracts/IPAsset.sol"; import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; import { LinkIPAssetTypeChecker } from "./LinkIPAssetTypeChecker.sol"; -import "forge-std/console.sol"; - -contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { - event Linked( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 linkId - ); - event Unlinked( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 linkId - ); - - event ProtocolLinkSet( - bytes32 linkId, - uint256 sourceIPAssetTypeMask, - uint256 destIPAssetTypeMask, - bool linkOnlySameFranchise - ); - event ProtocolLinkUnset(bytes32 linkId); - - error NonExistingLink(); - error IntentAlreadyRegistered(); - error UnsupportedLinkSource(); - error UnsupportedLinkDestination(); - error CannotLinkToAnotherFranchise(); - - struct LinkConfig { - uint256 sourceIPAssetTypeMask; - uint256 destIPAssetTypeMask; - bool linkOnlySameFranchise; - } +import { ILinkingModule } from "./ILinkingModule.sol"; +import { ILinkProcessor } from "./LinkProcessors/ILinkProcessor.sol"; - struct SetLinkParams { - IPAsset[] sourceIPAssets; - bool allowedExternalSource; - IPAsset[] destIPAssets; - bool allowedExternalDest; - bool linkOnlySameFranchise; - } +contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAssetTypeChecker { + using ERC165CheckerUpgradeable for address; /// @custom:storage-location erc7201:story-protocol.linking-module.storage struct LinkingModuleStorage { @@ -85,71 +45,37 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { } } - function link( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 linkId - ) external { + function link(LinkParams calldata params, bytes calldata data) external { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - LinkConfig storage config = $.protocolLinks[linkId]; - if (config.sourceIPAssetTypeMask == 0) revert NonExistingLink(); - (bool sourceResult, bool sourceIsAssetRegistry) = _checkLinkEnd(sourceContract, sourceId, config.sourceIPAssetTypeMask); - if (!sourceResult) revert UnsupportedLinkSource(); - (bool destResult, bool destIsAssetRegistry) = _checkLinkEnd(destContract, destId, config.destIPAssetTypeMask); - if (!destResult) revert UnsupportedLinkDestination(); - if(sourceIsAssetRegistry && destIsAssetRegistry && sourceContract != destContract && config.linkOnlySameFranchise) revert CannotLinkToAnotherFranchise(); - $.links[ - keccak256( - abi.encode( - sourceContract, - sourceId, - destContract, - destId, - linkId - ) - ) - ] = true; - emit Linked(sourceContract, sourceId, destContract, destId, linkId); + LinkConfig storage config = $.protocolLinks[params.linkId]; + _verifyLinkParams(params, config); + + config.processor.processLink(params, data, msg.sender); + + $.links[_getLinkKey(params)] = true; + emit Linked(params.sourceContract, params.sourceId, params.destContract, params.destId, params.linkId); } - function unlink( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 linkId - ) external onlyRole(LINK_DISPUTER_ROLE) { + function unlink(LinkParams calldata params) external onlyRole(LINK_DISPUTER_ROLE) { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - bytes32 key = keccak256( - abi.encode(sourceContract, sourceId, destContract, destId, linkId) - ); + bytes32 key = _getLinkKey(params); if (!$.links[key]) revert NonExistingLink(); delete $.links[key]; - emit Unlinked(sourceContract, sourceId, destContract, destId, linkId); + emit Unlinked(params.sourceContract, params.sourceId, params.destContract, params.destId, params.linkId); } - function areTheyLinked( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 linkId - ) external view returns (bool) { + function areTheyLinked(LinkParams calldata params) external view returns (bool) { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - return - $.links[ - keccak256( - abi.encode( - sourceContract, - sourceId, - destContract, - destId, - linkId - ) - ) - ]; + return $.links[_getLinkKey(params)]; + } + + function _verifyLinkParams(LinkParams calldata params, LinkConfig memory config) private { + if (config.sourceIPAssetTypeMask == 0) revert NonExistingLink(); + (bool sourceResult, bool sourceIsAssetRegistry) = _checkLinkEnd(params.sourceContract, params.sourceId, config.sourceIPAssetTypeMask); + if (!sourceResult) revert UnsupportedLinkSource(); + (bool destResult, bool destIsAssetRegistry) = _checkLinkEnd(params.destContract, params.destId, config.destIPAssetTypeMask); + if (!destResult) revert UnsupportedLinkDestination(); + if(sourceIsAssetRegistry && destIsAssetRegistry && params.sourceContract != params.destContract && config.linkOnlySameFranchise) revert CannotLinkToAnotherFranchise(); } function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { @@ -160,15 +86,26 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { } } + function _getLinkKey(LinkParams calldata params) private pure returns (bytes32) { + return keccak256( + abi.encode( + params.sourceContract, + params.sourceId, + params.destContract, + params.destId, + params.linkId + ) + ); + } + /********* Setting Links *********/ - function setProtocolLink( - bytes32 linkId, - SetLinkParams calldata params - ) external onlyRole(LINK_MANAGER_ROLE) { + function setProtocolLink(bytes32 linkId, SetLinkParams calldata params) external onlyRole(LINK_MANAGER_ROLE) { + if (!params.linkProcessor.supportsInterface(type(ILinkProcessor).interfaceId)) revert UnsupportedInterface("ILinkProcessor"); LinkConfig memory config = LinkConfig( _convertToMask(params.sourceIPAssets, params.allowedExternalSource), _convertToMask(params.destIPAssets, params.allowedExternalDest), - params.linkOnlySameFranchise + params.linkOnlySameFranchise, + ILinkProcessor(params.linkProcessor) ); LinkingModuleStorage storage $ = _getLinkingModuleStorage(); $.protocolLinks[linkId] = config; @@ -176,13 +113,12 @@ contract LinkingModule is AccessControlledUpgradeable, LinkIPAssetTypeChecker { linkId, config.sourceIPAssetTypeMask, config.destIPAssetTypeMask, - config.linkOnlySameFranchise + config.linkOnlySameFranchise, + params.linkProcessor ); } - function unsetProtocolLink( - bytes32 linkId - ) external onlyRole(LINK_MANAGER_ROLE) { + function unsetProtocolLink(bytes32 linkId) external onlyRole(LINK_MANAGER_ROLE) { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); if ( $.protocolLinks[linkId].sourceIPAssetTypeMask == 0 diff --git a/test/foundry/linking/LinkingModule.Linking.t.sol b/test/foundry/linking/LinkingModule.Linking.t.sol index 9010766e..2719e265 100644 --- a/test/foundry/linking/LinkingModule.Linking.t.sol +++ b/test/foundry/linking/LinkingModule.Linking.t.sol @@ -10,6 +10,7 @@ import "contracts/ip-assets/IPAssetRegistryFactory.sol"; import "contracts/modules/linking/LinkingModule.sol"; import "contracts/IPAsset.sol"; import "contracts/errors/General.sol"; +import "contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MockExternalAsset is ERC721 { @@ -27,6 +28,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { FranchiseRegistry public register; LinkingModule public linkingModule; AccessControlSingleton acs; + PermissionlessLinkProcessor public linkProcessor; address admin = address(123); address linkManager = address(234); @@ -80,12 +82,14 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; + linkProcessor = new PermissionlessLinkProcessor(address(linkingModule)); LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true + linkOnlySameFranchise: true, + linkProcessor: address(linkProcessor) }); vm.prank(linkManager); linkingModule.setProtocolLink(protocolLink, params); @@ -102,15 +106,15 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { } function test_link() public { - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink, ""); assertTrue( linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink) ); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink, ""); assertTrue( linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink) ); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink, ""); assertTrue( linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink) ); @@ -125,7 +129,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { function test_revert_unknown_link() public { vm.expectRevert(LinkingModule.NonExistingLink.selector); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG_LINK")); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG_LINK"), ""); } function test_revert_linkingNotSameFranchise() public { @@ -135,21 +139,21 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { vm.prank(ipAssetOwner); uint256 otherId = otherIPAssetRegistry.createIPAsset(IPAsset.CHARACTER, "name", "description", "mediaUrl"); vm.expectRevert(LinkingModule.CannotLinkToAnotherFranchise.selector); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, protocolLink); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, protocolLink, ""); } function test_revert_linkUnsupportedSource() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); vm.expectRevert(LinkingModule.UnsupportedLinkSource.selector); - linkingModule.link(address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink); + linkingModule.link(address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink, ""); } function test_revert_linkUnsupportedDestination() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); vm.expectRevert(LinkingModule.UnsupportedLinkDestination.selector); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, protocolLink); + linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, protocolLink, ""); } diff --git a/test/foundry/linking/LinkingModule.SettingLinks.t.sol b/test/foundry/linking/LinkingModule.SettingLinks.t.sol index 6482b587..b79b71d2 100644 --- a/test/foundry/linking/LinkingModule.SettingLinks.t.sol +++ b/test/foundry/linking/LinkingModule.SettingLinks.t.sol @@ -10,6 +10,7 @@ import "contracts/ip-assets/IPAssetRegistryFactory.sol"; import "contracts/modules/linking/LinkingModule.sol"; import "contracts/IPAsset.sol"; import "contracts/errors/General.sol"; +import "contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol"; contract LinkingModuleSetupLinksTest is Test, ProxyHelper { @@ -18,6 +19,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { FranchiseRegistry public register; LinkingModule public linkingModule; AccessControlSingleton acs; + PermissionlessLinkProcessor public linkProcessor; address admin = address(123); address linkManager = address(234); @@ -61,6 +63,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { ) ) ); + linkProcessor = new PermissionlessLinkProcessor(address(linkingModule)); } function test_setProtocolLevelLink() public { @@ -69,13 +72,14 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { IPAsset[] memory destIPAssets = new IPAsset[](2); destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - + LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true + linkOnlySameFranchise: true, + linkProcessor: address(linkProcessor) }); assertTrue(acs.hasRole(LINK_MANAGER_ROLE, linkManager)); vm.prank(linkManager); @@ -101,7 +105,8 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true + linkOnlySameFranchise: true, + linkProcessor: address(linkProcessor) }); vm.expectRevert(); vm.prank(franchiseOwner); @@ -118,7 +123,8 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true + linkOnlySameFranchise: true, + linkProcessor: address(linkProcessor) }); vm.startPrank(linkManager); vm.expectRevert(); @@ -134,6 +140,7 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { FranchiseRegistry public register; LinkingModule public linkingModule; AccessControlSingleton acs; + PermissionlessLinkProcessor public linkProcessor; address admin = address(123); address linkManager = address(234); @@ -177,6 +184,7 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { ) ) ); + linkProcessor = new PermissionlessLinkProcessor(address(linkingModule)); IPAsset[] memory sourceIPAssets = new IPAsset[](1); sourceIPAssets[0] = IPAsset.STORY; IPAsset[] memory destIPAssets = new IPAsset[](1); @@ -186,7 +194,8 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true + linkOnlySameFranchise: true, + linkProcessor: address(linkProcessor) }); vm.prank(linkManager); linkingModule.setProtocolLink(protocolLink, params); From fe8fe280453aa9c3eab06237ac6b9281b8d6ac1b Mon Sep 17 00:00:00 2001 From: Raul Date: Sat, 15 Jul 2023 01:17:07 +0200 Subject: [PATCH 16/30] test fixing --- .../LinkProcessors/BaseLinkProcessor.sol | 10 ++- .../linking/LinkProcessors/ILinkProcessor.sol | 4 +- contracts/modules/linking/LinkingModule.sol | 3 +- .../linking/LinkingModule.Linking.t.sol | 88 +++++++++++++++---- .../linking/LinkingModule.SettingLinks.t.sol | 12 ++- 5 files changed, 85 insertions(+), 32 deletions(-) diff --git a/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol index 97997ee1..563aaf8c 100644 --- a/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol +++ b/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol @@ -4,14 +4,16 @@ pragma solidity ^0.8.13; import { ILinkProcessor } from "./ILinkProcessor.sol"; import { ZeroAddress } from "contracts/errors/General.sol"; import { ILinkingModule } from "../ILinkingModule.sol"; +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "forge-std/console.sol"; -abstract contract BaseLinkProcessor is ILinkProcessor { +abstract contract BaseLinkProcessor is ILinkProcessor, ERC165 { address internal immutable _LINKING_MODULE; error OnlyLinkingModule(); constructor(address _linkingModule) { - if(_linkingModule != address(0)) revert ZeroAddress(); + if(_linkingModule == address(0)) revert ZeroAddress(); _LINKING_MODULE = _linkingModule; } @@ -24,8 +26,8 @@ abstract contract BaseLinkProcessor is ILinkProcessor { function supportsInterface( bytes4 interfaceId - ) external pure override returns (bool) { - return interfaceId == type(ILinkProcessor).interfaceId; + ) public view override(ERC165) returns (bool) { + return super.supportsInterface(interfaceId) || interfaceId == type(ILinkProcessor).interfaceId; } } \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol b/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol index 01df242e..bb050f22 100644 --- a/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol +++ b/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol @@ -1,10 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.13; -import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import { ILinkingModule } from "../ILinkingModule.sol"; -interface ILinkProcessor is IERC165 { - +interface ILinkProcessor { function processLink(ILinkingModule.LinkParams memory params, bytes calldata data, address linker) external; } \ No newline at end of file diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index c0107eab..3df4fef4 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -11,6 +11,7 @@ import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; import { LinkIPAssetTypeChecker } from "./LinkIPAssetTypeChecker.sol"; import { ILinkingModule } from "./ILinkingModule.sol"; import { ILinkProcessor } from "./LinkProcessors/ILinkProcessor.sol"; +import "forge-std/console.sol"; contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAssetTypeChecker { using ERC165CheckerUpgradeable for address; @@ -69,7 +70,7 @@ contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAss return $.links[_getLinkKey(params)]; } - function _verifyLinkParams(LinkParams calldata params, LinkConfig memory config) private { + function _verifyLinkParams(LinkParams calldata params, LinkConfig memory config) private view { if (config.sourceIPAssetTypeMask == 0) revert NonExistingLink(); (bool sourceResult, bool sourceIsAssetRegistry) = _checkLinkEnd(params.sourceContract, params.sourceId, config.sourceIPAssetTypeMask); if (!sourceResult) revert UnsupportedLinkSource(); diff --git a/test/foundry/linking/LinkingModule.Linking.t.sol b/test/foundry/linking/LinkingModule.Linking.t.sol index 2719e265..b208fc47 100644 --- a/test/foundry/linking/LinkingModule.Linking.t.sol +++ b/test/foundry/linking/LinkingModule.Linking.t.sol @@ -83,7 +83,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { destIPAssets[1] = IPAsset.ART; linkProcessor = new PermissionlessLinkProcessor(address(linkingModule)); - LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, @@ -106,30 +106,69 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { } function test_link() public { - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink, ""); + linkingModule.link( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink + ), + "" + ); assertTrue( - linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink) + linkingModule.areTheyLinked( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink + ) + ) + ); + + linkingModule.link( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink + ), + "" ); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink, ""); assertTrue( - linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink) + linkingModule.areTheyLinked( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink + ) + ) + ); + linkingModule.link( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink + ), + "" ); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink, ""); assertTrue( - linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink) + linkingModule.areTheyLinked( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink + ) + ) ); // TODO check for event assertFalse( - linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, protocolLink) + linkingModule.areTheyLinked( + ILinkingModule.LinkParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, protocolLink) + ) ); assertFalse( - linkingModule.areTheyLinked(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG_LINK")) + linkingModule.areTheyLinked( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG_LINK") + ) + ) ); } function test_revert_unknown_link() public { - vm.expectRevert(LinkingModule.NonExistingLink.selector); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG_LINK"), ""); + vm.expectRevert(ILinkingModule.NonExistingLink.selector); + linkingModule.link( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG_LINK") + ), + "" + ); } function test_revert_linkingNotSameFranchise() public { @@ -138,22 +177,37 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { IPAssetRegistry otherIPAssetRegistry = IPAssetRegistry(otherIPAssets); vm.prank(ipAssetOwner); uint256 otherId = otherIPAssetRegistry.createIPAsset(IPAsset.CHARACTER, "name", "description", "mediaUrl"); - vm.expectRevert(LinkingModule.CannotLinkToAnotherFranchise.selector); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, protocolLink, ""); + vm.expectRevert(ILinkingModule.CannotLinkToAnotherFranchise.selector); + linkingModule.link( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, protocolLink + ), + "" + ); } function test_revert_linkUnsupportedSource() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); - vm.expectRevert(LinkingModule.UnsupportedLinkSource.selector); - linkingModule.link(address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink, ""); + vm.expectRevert(ILinkingModule.UnsupportedLinkSource.selector); + linkingModule.link( + ILinkingModule.LinkParams( + address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink + ), + "" + ); } function test_revert_linkUnsupportedDestination() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); - vm.expectRevert(LinkingModule.UnsupportedLinkDestination.selector); - linkingModule.link(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, protocolLink, ""); + vm.expectRevert(ILinkingModule.UnsupportedLinkDestination.selector); + linkingModule.link( + ILinkingModule.LinkParams( + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, protocolLink + ), + "" + ); } diff --git a/test/foundry/linking/LinkingModule.SettingLinks.t.sol b/test/foundry/linking/LinkingModule.SettingLinks.t.sol index b79b71d2..022501e7 100644 --- a/test/foundry/linking/LinkingModule.SettingLinks.t.sol +++ b/test/foundry/linking/LinkingModule.SettingLinks.t.sol @@ -73,7 +73,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, @@ -100,7 +100,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, @@ -118,7 +118,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { sourceIPAssets[0] = IPAsset.UNDEFINED; IPAsset[] memory destIPAssets = new IPAsset[](2); - LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, @@ -189,7 +189,7 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { sourceIPAssets[0] = IPAsset.STORY; IPAsset[] memory destIPAssets = new IPAsset[](1); destIPAssets[0] = IPAsset.CHARACTER; - LinkingModule.SetLinkParams memory params = LinkingModule.SetLinkParams({ + LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, @@ -220,10 +220,8 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { function test_revert_unsetProtocolLinkNonExistingLink() public { vm.prank(linkManager); - vm.expectRevert(LinkingModule.NonExistingLink.selector); + vm.expectRevert(ILinkingModule.NonExistingLink.selector); linkingModule.unsetProtocolLink(keccak256("UNDEFINED_LINK")); } - - } From 40fd1c10675eaf41abca84b8096b8785bf355fd0 Mon Sep 17 00:00:00 2001 From: Raul Date: Sat, 15 Jul 2023 01:27:03 +0200 Subject: [PATCH 17/30] refactor protocol out of link --- contracts/modules/linking/ILinkingModule.sol | 10 +++---- contracts/modules/linking/LinkingModule.sol | 22 +++++++-------- .../linking/LinkingModule.Linking.t.sol | 28 +++++++++---------- .../linking/LinkingModule.SettingLinks.t.sol | 28 +++++++++---------- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/contracts/modules/linking/ILinkingModule.sol b/contracts/modules/linking/ILinkingModule.sol index 0a19b133..e0bfb878 100644 --- a/contracts/modules/linking/ILinkingModule.sol +++ b/contracts/modules/linking/ILinkingModule.sol @@ -21,14 +21,14 @@ interface ILinkingModule { bytes32 linkId ); - event ProtocolLinkSet( + event LinkConfigSet( bytes32 linkId, uint256 sourceIPAssetTypeMask, uint256 destIPAssetTypeMask, bool linkOnlySameFranchise, address linkProcessor ); - event ProtocolLinkUnset(bytes32 linkId); + event LinkConfigUnset(bytes32 linkId); error NonExistingLink(); error IntentAlreadyRegistered(); @@ -63,7 +63,7 @@ interface ILinkingModule { function link(LinkParams calldata params, bytes calldata data) external; function unlink(LinkParams calldata params) external; function areTheyLinked(LinkParams calldata params) external view returns (bool); - function setProtocolLink(bytes32 linkId, SetLinkParams calldata params) external; - function unsetProtocolLink(bytes32 linkId) external; - function protocolLinks(bytes32 linkId) external view returns (LinkConfig memory); + function setLinkConfig(bytes32 linkId, SetLinkParams calldata params) external; + function unsetLinkConfig(bytes32 linkId) external; + function linkConfig(bytes32 linkId) external view returns (LinkConfig memory); } \ No newline at end of file diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol index 3df4fef4..ac79cc69 100644 --- a/contracts/modules/linking/LinkingModule.sol +++ b/contracts/modules/linking/LinkingModule.sol @@ -19,7 +19,7 @@ contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAss /// @custom:storage-location erc7201:story-protocol.linking-module.storage struct LinkingModuleStorage { mapping(bytes32 => bool) links; - mapping(bytes32 => LinkConfig) protocolLinks; + mapping(bytes32 => LinkConfig) linkConfigs; } // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.linking-module.storage")) - 1))) @@ -48,7 +48,7 @@ contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAss function link(LinkParams calldata params, bytes calldata data) external { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - LinkConfig storage config = $.protocolLinks[params.linkId]; + LinkConfig storage config = $.linkConfigs[params.linkId]; _verifyLinkParams(params, config); config.processor.processLink(params, data, msg.sender); @@ -100,7 +100,7 @@ contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAss } /********* Setting Links *********/ - function setProtocolLink(bytes32 linkId, SetLinkParams calldata params) external onlyRole(LINK_MANAGER_ROLE) { + function setLinkConfig(bytes32 linkId, SetLinkParams calldata params) external onlyRole(LINK_MANAGER_ROLE) { if (!params.linkProcessor.supportsInterface(type(ILinkProcessor).interfaceId)) revert UnsupportedInterface("ILinkProcessor"); LinkConfig memory config = LinkConfig( _convertToMask(params.sourceIPAssets, params.allowedExternalSource), @@ -109,8 +109,8 @@ contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAss ILinkProcessor(params.linkProcessor) ); LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - $.protocolLinks[linkId] = config; - emit ProtocolLinkSet( + $.linkConfigs[linkId] = config; + emit LinkConfigSet( linkId, config.sourceIPAssetTypeMask, config.destIPAssetTypeMask, @@ -119,18 +119,18 @@ contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAss ); } - function unsetProtocolLink(bytes32 linkId) external onlyRole(LINK_MANAGER_ROLE) { + function unsetLinkConfig(bytes32 linkId) external onlyRole(LINK_MANAGER_ROLE) { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); if ( - $.protocolLinks[linkId].sourceIPAssetTypeMask == 0 + $.linkConfigs[linkId].sourceIPAssetTypeMask == 0 ) revert NonExistingLink(); - delete $.protocolLinks[linkId]; - emit ProtocolLinkUnset(linkId); + delete $.linkConfigs[linkId]; + emit LinkConfigUnset(linkId); } - function protocolLinks(bytes32 linkId) external view returns (LinkConfig memory) { + function linkConfig(bytes32 linkId) external view returns (LinkConfig memory) { LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - return $.protocolLinks[linkId]; + return $.linkConfigs[linkId]; } function _authorizeUpgrade( diff --git a/test/foundry/linking/LinkingModule.Linking.t.sol b/test/foundry/linking/LinkingModule.Linking.t.sol index b208fc47..b355efa9 100644 --- a/test/foundry/linking/LinkingModule.Linking.t.sol +++ b/test/foundry/linking/LinkingModule.Linking.t.sol @@ -35,7 +35,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { address franchiseOwner = address(456); address ipAssetOwner = address(567); - bytes32 protocolLink = keccak256("PROTOCOL_LINK"); + bytes32 relationship = keccak256("RELATIONSHIP"); mapping(uint8 => uint256) public ipAssetIds; @@ -92,7 +92,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { linkProcessor: address(linkProcessor) }); vm.prank(linkManager); - linkingModule.setProtocolLink(protocolLink, params); + linkingModule.setLinkConfig(relationship, params); vm.startPrank(ipAssetOwner); ipAssetIds[uint8(IPAsset.STORY)] = ipAssetRegistry.createIPAsset(IPAsset.STORY, "name", "description", "mediaUrl"); @@ -108,54 +108,54 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { function test_link() public { linkingModule.link( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship ), "" ); assertTrue( linkingModule.areTheyLinked( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship ) ) ); linkingModule.link( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship ), "" ); assertTrue( linkingModule.areTheyLinked( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship ) ) ); linkingModule.link( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship ), "" ); assertTrue( linkingModule.areTheyLinked( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship ) ) ); // TODO check for event assertFalse( linkingModule.areTheyLinked( - ILinkingModule.LinkParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, protocolLink) + ILinkingModule.LinkParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, relationship) ) ); assertFalse( linkingModule.areTheyLinked( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG_LINK") + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG") ) ) ); @@ -165,7 +165,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { vm.expectRevert(ILinkingModule.NonExistingLink.selector); linkingModule.link( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG_LINK") + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG") ), "" ); @@ -180,7 +180,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { vm.expectRevert(ILinkingModule.CannotLinkToAnotherFranchise.selector); linkingModule.link( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, relationship ), "" ); @@ -192,7 +192,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { vm.expectRevert(ILinkingModule.UnsupportedLinkSource.selector); linkingModule.link( ILinkingModule.LinkParams( - address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], protocolLink + address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship ), "" ); @@ -204,7 +204,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { vm.expectRevert(ILinkingModule.UnsupportedLinkDestination.selector); linkingModule.link( ILinkingModule.LinkParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, protocolLink + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, relationship ), "" ); diff --git a/test/foundry/linking/LinkingModule.SettingLinks.t.sol b/test/foundry/linking/LinkingModule.SettingLinks.t.sol index 022501e7..1a35738a 100644 --- a/test/foundry/linking/LinkingModule.SettingLinks.t.sol +++ b/test/foundry/linking/LinkingModule.SettingLinks.t.sol @@ -25,7 +25,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { address linkManager = address(234); address franchiseOwner = address(456); - bytes32 protocolLink = keccak256("PROTOCOL_LINK"); + bytes32 relationship = keccak256("RELATIONSHIP"); function setUp() public { factory = new IPAssetRegistryFactory(); @@ -83,9 +83,9 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { }); assertTrue(acs.hasRole(LINK_MANAGER_ROLE, linkManager)); vm.prank(linkManager); - linkingModule.setProtocolLink(protocolLink, params); + linkingModule.setLinkConfig(relationship, params); - LinkingModule.LinkConfig memory config = linkingModule.protocolLinks(protocolLink); + LinkingModule.LinkConfig memory config = linkingModule.linkConfig(relationship); assertEq(config.sourceIPAssetTypeMask, 1 << (uint256(IPAsset.STORY) & 0xff)); assertEq(config.destIPAssetTypeMask, 1 << (uint256(IPAsset.CHARACTER) & 0xff) | 1 << (uint256(IPAsset.ART) & 0xff) | (uint256(EXTERNAL_ASSET) << 248)); assertTrue(config.linkOnlySameFranchise); @@ -110,7 +110,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { }); vm.expectRevert(); vm.prank(franchiseOwner); - linkingModule.setProtocolLink(protocolLink, params); + linkingModule.setLinkConfig(relationship, params); } function test_revert_IfMasksNotConfigured() public { @@ -128,7 +128,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { }); vm.startPrank(linkManager); vm.expectRevert(); - linkingModule.setProtocolLink(protocolLink, params); + linkingModule.setLinkConfig(relationship, params); } } @@ -146,7 +146,7 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { address linkManager = address(234); address franchiseOwner = address(456); - bytes32 protocolLink = keccak256("PROTOCOL_LINK"); + bytes32 relationship = keccak256("PROTOCOL_LINK"); function setUp() public { factory = new IPAssetRegistryFactory(); @@ -198,30 +198,30 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { linkProcessor: address(linkProcessor) }); vm.prank(linkManager); - linkingModule.setProtocolLink(protocolLink, params); + linkingModule.setLinkConfig(relationship, params); } - function test_unsetProtocolLink() public { + function test_unsetLinkConfig() public { vm.prank(linkManager); - linkingModule.unsetProtocolLink(protocolLink); + linkingModule.unsetLinkConfig(relationship); - LinkingModule.LinkConfig memory config = linkingModule.protocolLinks(protocolLink); + LinkingModule.LinkConfig memory config = linkingModule.linkConfig(relationship); assertEq(config.sourceIPAssetTypeMask, 0); assertEq(config.destIPAssetTypeMask, 0); assertFalse(config.linkOnlySameFranchise); // TODO: test for event } - function test_revert_unsetProtocolLinkNotAuthorized() public { + function test_revert_unsetLinkConfigNotAuthorized() public { vm.expectRevert(); - linkingModule.unsetProtocolLink(protocolLink); + linkingModule.unsetLinkConfig(relationship); } - function test_revert_unsetProtocolLinkNonExistingLink() public { + function test_revert_unsetLinkConfigNonExistingLink() public { vm.prank(linkManager); vm.expectRevert(ILinkingModule.NonExistingLink.selector); - linkingModule.unsetProtocolLink(keccak256("UNDEFINED_LINK")); + linkingModule.unsetLinkConfig(keccak256("UNDEFINED_LINK")); } } From 01b0d5d49683a3e68750584529c3d7645ab35752 Mon Sep 17 00:00:00 2001 From: Raul Date: Sat, 15 Jul 2023 02:03:19 +0200 Subject: [PATCH 18/30] refactor link to relationship --- contracts/access-control/ProtocolRoles.sol | 4 +- contracts/modules/linking/ILinkingModule.sol | 69 --------- .../LinkProcessors/BaseLinkProcessor.sol | 33 ----- .../LinkProcessors/DstOwnerLinkProcessor.sol | 19 --- .../linking/LinkProcessors/ILinkProcessor.sol | 8 - .../PermissionlessLinkProcessor.sol | 14 -- .../SrcDstOwnerLinkProcessor.sol | 21 --- .../LinkProcessors/SrcOwnerLinkProcessor.sol | 19 --- contracts/modules/linking/LinkingModule.sol | 140 ------------------ .../relationships/IRelationshipModule.sol | 69 +++++++++ .../relationships/RelationshipModule.sol | 139 +++++++++++++++++ .../BaseRelationshipProcessor.sol | 32 ++++ .../DstOwnerRelationshipProcessor.sol | 19 +++ .../IRelationshipProcessor.sol | 8 + .../PermissionlessRelationshipProcessor.sol | 14 ++ .../SrcDstOwnerRelationshipProcessor.sol | 21 +++ .../SrcOwnerRelationshipProcessor.sol | 19 +++ .../RelationshipTypeChecker.sol} | 4 +- .../RelationShipTypeChecker.t.sol} | 42 +++--- .../RelationshipModule.Config.t.sol} | 108 +++++++------- .../RelationshipModule.Relating.t.sol} | 96 ++++++------ 21 files changed, 448 insertions(+), 450 deletions(-) delete mode 100644 contracts/modules/linking/ILinkingModule.sol delete mode 100644 contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol delete mode 100644 contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol delete mode 100644 contracts/modules/linking/LinkProcessors/ILinkProcessor.sol delete mode 100644 contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol delete mode 100644 contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol delete mode 100644 contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol delete mode 100644 contracts/modules/linking/LinkingModule.sol create mode 100644 contracts/modules/relationships/IRelationshipModule.sol create mode 100644 contracts/modules/relationships/RelationshipModule.sol create mode 100644 contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol create mode 100644 contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol create mode 100644 contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol create mode 100644 contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol create mode 100644 contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol create mode 100644 contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol rename contracts/modules/{linking/LinkIPAssetTypeChecker.sol => relationships/RelationshipTypeChecker.sol} (89%) rename test/foundry/{linking/LinkIPAssetTypeChecker.t..sol => relationships/RelationShipTypeChecker.t.sol} (76%) rename test/foundry/{linking/LinkingModule.SettingLinks.t.sol => relationships/RelationshipModule.Config.t.sol} (60%) rename test/foundry/{linking/LinkingModule.Linking.t.sol => relationships/RelationshipModule.Relating.t.sol} (68%) diff --git a/contracts/access-control/ProtocolRoles.sol b/contracts/access-control/ProtocolRoles.sol index a6b8edfe..bf9b2aec 100644 --- a/contracts/access-control/ProtocolRoles.sol +++ b/contracts/access-control/ProtocolRoles.sol @@ -4,5 +4,5 @@ pragma solidity ^0.8.13; bytes32 constant PROTOCOL_ADMIN_ROLE = bytes32(0); bytes32 constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); -bytes32 constant LINK_MANAGER_ROLE = keccak256("LINK_MANAGER_ROLE"); -bytes32 constant LINK_DISPUTER_ROLE = keccak256("LINK_DISPUTER_ROLE"); +bytes32 constant RELATIONSHIP_MANAGER_ROLE = keccak256("RELATIONSHIP_MANAGER_ROLE"); +bytes32 constant RELATIONSHIP_DISPUTER_ROLE = keccak256("RELATIONSHIP_DISPUTER_ROLE"); diff --git a/contracts/modules/linking/ILinkingModule.sol b/contracts/modules/linking/ILinkingModule.sol deleted file mode 100644 index e0bfb878..00000000 --- a/contracts/modules/linking/ILinkingModule.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { ILinkProcessor } from "./LinkProcessors/ILinkProcessor.sol"; -import { IPAsset } from "contracts/IPAsset.sol"; - -interface ILinkingModule { - - event Linked( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 linkId - ); - event Unlinked( - address sourceContract, - uint256 sourceId, - address destContract, - uint256 destId, - bytes32 linkId - ); - - event LinkConfigSet( - bytes32 linkId, - uint256 sourceIPAssetTypeMask, - uint256 destIPAssetTypeMask, - bool linkOnlySameFranchise, - address linkProcessor - ); - event LinkConfigUnset(bytes32 linkId); - - error NonExistingLink(); - error IntentAlreadyRegistered(); - error UnsupportedLinkSource(); - error UnsupportedLinkDestination(); - error CannotLinkToAnotherFranchise(); - - struct LinkConfig { - uint256 sourceIPAssetTypeMask; - uint256 destIPAssetTypeMask; - bool linkOnlySameFranchise; - ILinkProcessor processor; - } - - struct SetLinkParams { - IPAsset[] sourceIPAssets; - bool allowedExternalSource; - IPAsset[] destIPAssets; - bool allowedExternalDest; - bool linkOnlySameFranchise; - address linkProcessor; - } - - struct LinkParams { - address sourceContract; - uint256 sourceId; - address destContract; - uint256 destId; - bytes32 linkId; - } - - function link(LinkParams calldata params, bytes calldata data) external; - function unlink(LinkParams calldata params) external; - function areTheyLinked(LinkParams calldata params) external view returns (bool); - function setLinkConfig(bytes32 linkId, SetLinkParams calldata params) external; - function unsetLinkConfig(bytes32 linkId) external; - function linkConfig(bytes32 linkId) external view returns (LinkConfig memory); -} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol deleted file mode 100644 index 563aaf8c..00000000 --- a/contracts/modules/linking/LinkProcessors/BaseLinkProcessor.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { ILinkProcessor } from "./ILinkProcessor.sol"; -import { ZeroAddress } from "contracts/errors/General.sol"; -import { ILinkingModule } from "../ILinkingModule.sol"; -import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "forge-std/console.sol"; - -abstract contract BaseLinkProcessor is ILinkProcessor, ERC165 { - - address internal immutable _LINKING_MODULE; - error OnlyLinkingModule(); - - constructor(address _linkingModule) { - if(_linkingModule == address(0)) revert ZeroAddress(); - _LINKING_MODULE = _linkingModule; - } - - function processLink(ILinkingModule.LinkParams memory params, bytes calldata data, address linker) external override { - if(msg.sender != _LINKING_MODULE) revert OnlyLinkingModule(); - _processLink(params, data, linker); - } - - function _processLink(ILinkingModule.LinkParams memory params, bytes calldata data, address linker) internal virtual; - - function supportsInterface( - bytes4 interfaceId - ) public view override(ERC165) returns (bool) { - return super.supportsInterface(interfaceId) || interfaceId == type(ILinkProcessor).interfaceId; - } - -} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol deleted file mode 100644 index bde45ab8..00000000 --- a/contracts/modules/linking/LinkProcessors/DstOwnerLinkProcessor.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { Unauthorized } from "contracts/errors/General.sol"; -import { ILinkingModule } from "../ILinkingModule.sol"; - -contract DstOwnerLinkProcessor is BaseLinkProcessor { - - constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} - - function _processLink(ILinkingModule.LinkParams memory params, bytes calldata, address linker) internal view virtual override { - if (IERC721(params.destContract).ownerOf(params.destId) != linker) { - revert Unauthorized(); - } - } - -} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol b/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol deleted file mode 100644 index bb050f22..00000000 --- a/contracts/modules/linking/LinkProcessors/ILinkProcessor.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { ILinkingModule } from "../ILinkingModule.sol"; - -interface ILinkProcessor { - function processLink(ILinkingModule.LinkParams memory params, bytes calldata data, address linker) external; -} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol deleted file mode 100644 index f5bc77cd..00000000 --- a/contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; -import { ILinkingModule } from "../ILinkingModule.sol"; - -contract PermissionlessLinkProcessor is BaseLinkProcessor { - - constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} - - function _processLink(ILinkingModule.LinkParams memory, bytes calldata, address) internal virtual override { - // do nothing - } -} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol deleted file mode 100644 index 0ed7e157..00000000 --- a/contracts/modules/linking/LinkProcessors/SrcDstOwnerLinkProcessor.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { Unauthorized } from "contracts/errors/General.sol"; -import { ILinkingModule } from "../ILinkingModule.sol"; - -contract SrcDstOwnerLinkProcessor is BaseLinkProcessor { - - constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} - - function _processLink(ILinkingModule.LinkParams memory params, bytes calldata, address linker) internal view virtual override { - if ( - IERC721(params.sourceContract).ownerOf(params.sourceId) != linker || - IERC721(params.destContract).ownerOf(params.destId) != linker) { - revert Unauthorized(); - } - } - -} \ No newline at end of file diff --git a/contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol b/contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol deleted file mode 100644 index 14fe8c17..00000000 --- a/contracts/modules/linking/LinkProcessors/SrcOwnerLinkProcessor.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { BaseLinkProcessor } from "./BaseLinkProcessor.sol"; -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { Unauthorized } from "contracts/errors/General.sol"; -import { ILinkingModule } from "../ILinkingModule.sol"; - -contract SrcOwnerLinkProcessor is BaseLinkProcessor { - - constructor(address linkingModule) BaseLinkProcessor(linkingModule) {} - - function _processLink(ILinkingModule.LinkParams memory params, bytes calldata, address linker) internal view virtual override { - if (IERC721(params.sourceContract).ownerOf(params.sourceId) != linker) { - revert Unauthorized(); - } - } - -} \ No newline at end of file diff --git a/contracts/modules/linking/LinkingModule.sol b/contracts/modules/linking/LinkingModule.sol deleted file mode 100644 index ac79cc69..00000000 --- a/contracts/modules/linking/LinkingModule.sol +++ /dev/null @@ -1,140 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.13; - -import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; -import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { ZeroAddress, UnsupportedInterface } from "contracts/errors/General.sol"; -import { UPGRADER_ROLE, LINK_MANAGER_ROLE, LINK_DISPUTER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; -import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; -import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; -import { LinkIPAssetTypeChecker } from "./LinkIPAssetTypeChecker.sol"; -import { ILinkingModule } from "./ILinkingModule.sol"; -import { ILinkProcessor } from "./LinkProcessors/ILinkProcessor.sol"; -import "forge-std/console.sol"; - -contract LinkingModule is ILinkingModule, AccessControlledUpgradeable, LinkIPAssetTypeChecker { - using ERC165CheckerUpgradeable for address; - - /// @custom:storage-location erc7201:story-protocol.linking-module.storage - struct LinkingModuleStorage { - mapping(bytes32 => bool) links; - mapping(bytes32 => LinkConfig) linkConfigs; - } - - // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.linking-module.storage")) - 1))) - bytes32 private constant _STORAGE_LOCATION = 0x9c884c7910549c48b2f059441cfee4a973c8102bda86741fa2535981e323cf9e; - FranchiseRegistry public immutable FRANCHISE_REGISTRY; - - constructor(address _franchiseRegistry) { - if (_franchiseRegistry == address(0)) revert ZeroAddress(); - FRANCHISE_REGISTRY = FranchiseRegistry(_franchiseRegistry); - _disableInitializers(); - } - - function initialize(address accessControl) public initializer { - __AccessControlledUpgradeable_init(accessControl); - } - - function _getLinkingModuleStorage() - private - pure - returns (LinkingModuleStorage storage $) - { - assembly { - $.slot := _STORAGE_LOCATION - } - } - - function link(LinkParams calldata params, bytes calldata data) external { - LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - LinkConfig storage config = $.linkConfigs[params.linkId]; - _verifyLinkParams(params, config); - - config.processor.processLink(params, data, msg.sender); - - $.links[_getLinkKey(params)] = true; - emit Linked(params.sourceContract, params.sourceId, params.destContract, params.destId, params.linkId); - } - - function unlink(LinkParams calldata params) external onlyRole(LINK_DISPUTER_ROLE) { - LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - bytes32 key = _getLinkKey(params); - if (!$.links[key]) revert NonExistingLink(); - delete $.links[key]; - emit Unlinked(params.sourceContract, params.sourceId, params.destContract, params.destId, params.linkId); - } - - function areTheyLinked(LinkParams calldata params) external view returns (bool) { - LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - return $.links[_getLinkKey(params)]; - } - - function _verifyLinkParams(LinkParams calldata params, LinkConfig memory config) private view { - if (config.sourceIPAssetTypeMask == 0) revert NonExistingLink(); - (bool sourceResult, bool sourceIsAssetRegistry) = _checkLinkEnd(params.sourceContract, params.sourceId, config.sourceIPAssetTypeMask); - if (!sourceResult) revert UnsupportedLinkSource(); - (bool destResult, bool destIsAssetRegistry) = _checkLinkEnd(params.destContract, params.destId, config.destIPAssetTypeMask); - if (!destResult) revert UnsupportedLinkDestination(); - if(sourceIsAssetRegistry && destIsAssetRegistry && params.sourceContract != params.destContract && config.linkOnlySameFranchise) revert CannotLinkToAnotherFranchise(); - } - - function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { - try IIPAssetRegistry(ipAssetRegistry).franchiseId() returns (uint256 franchiseId) { - return FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) == ipAssetRegistry; - } catch { - return false; - } - } - - function _getLinkKey(LinkParams calldata params) private pure returns (bytes32) { - return keccak256( - abi.encode( - params.sourceContract, - params.sourceId, - params.destContract, - params.destId, - params.linkId - ) - ); - } - - /********* Setting Links *********/ - function setLinkConfig(bytes32 linkId, SetLinkParams calldata params) external onlyRole(LINK_MANAGER_ROLE) { - if (!params.linkProcessor.supportsInterface(type(ILinkProcessor).interfaceId)) revert UnsupportedInterface("ILinkProcessor"); - LinkConfig memory config = LinkConfig( - _convertToMask(params.sourceIPAssets, params.allowedExternalSource), - _convertToMask(params.destIPAssets, params.allowedExternalDest), - params.linkOnlySameFranchise, - ILinkProcessor(params.linkProcessor) - ); - LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - $.linkConfigs[linkId] = config; - emit LinkConfigSet( - linkId, - config.sourceIPAssetTypeMask, - config.destIPAssetTypeMask, - config.linkOnlySameFranchise, - params.linkProcessor - ); - } - - function unsetLinkConfig(bytes32 linkId) external onlyRole(LINK_MANAGER_ROLE) { - LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - if ( - $.linkConfigs[linkId].sourceIPAssetTypeMask == 0 - ) revert NonExistingLink(); - delete $.linkConfigs[linkId]; - emit LinkConfigUnset(linkId); - } - - function linkConfig(bytes32 linkId) external view returns (LinkConfig memory) { - LinkingModuleStorage storage $ = _getLinkingModuleStorage(); - return $.linkConfigs[linkId]; - } - - function _authorizeUpgrade( - address newImplementation - ) internal virtual override onlyRole(UPGRADER_ROLE) {} - -} \ No newline at end of file diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol new file mode 100644 index 00000000..6e172804 --- /dev/null +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IRelationshipProcessor } from "./RelationshipProcessors/IRelationshipProcessor.sol"; +import { IPAsset } from "contracts/IPAsset.sol"; + +interface IRelationshipModule { + + event RelationSet( + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, + bytes32 relationshipId + ); + event RelationUnset( + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, + bytes32 relationshipId + ); + + event RelationshipConfigSet( + bytes32 relationshipId, + uint256 sourceIPAssetTypeMask, + uint256 destIPAssetTypeMask, + bool onlySameFranchise, + address processor + ); + event RelationshipConfigUnset(bytes32 relationshipId); + + error NonExistingRelationship(); + error IntentAlreadyRegistered(); + error UnsupportedRelationshipSource(); + error UnsupportedRelationshipDestination(); + error CannotRelationshipToAnotherFranchise(); + + struct RelationshipConfig { + uint256 sourceIPAssetTypeMask; + uint256 destIPAssetTypeMask; + bool onlySameFranchise; + IRelationshipProcessor processor; + } + + struct SetRelationshipParams { + IPAsset[] sourceIPAssets; + bool allowedExternalSource; + IPAsset[] destIPAssets; + bool allowedExternalDest; + bool onlySameFranchise; + address processor; + } + + struct RelationshipParams { + address sourceContract; + uint256 sourceId; + address destContract; + uint256 destId; + bytes32 relationshipId; + } + + function relate(RelationshipParams calldata params, bytes calldata data) external; + function unrelate(RelationshipParams calldata params) external; + function areTheyRelated(RelationshipParams calldata params) external view returns (bool); + function setRelationshipConfig(bytes32 relationshipId, SetRelationshipParams calldata params) external; + function unsetConfig(bytes32 relationshipId) external; + function config(bytes32 relationshipId) external view returns (RelationshipConfig memory); +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol new file mode 100644 index 00000000..ee3dcd9c --- /dev/null +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; +import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; +import { ZeroAddress, UnsupportedInterface } from "contracts/errors/General.sol"; +import { UPGRADER_ROLE, RELATIONSHIP_MANAGER_ROLE, RELATIONSHIP_DISPUTER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; +import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; +import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; +import { RelationshipTypeChecker } from "./RelationshipTypeChecker.sol"; +import { IRelationshipModule } from "./IRelationshipModule.sol"; +import { IRelationshipProcessor } from "./RelationshipProcessors/IRelationshipProcessor.sol"; + +contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, RelationshipTypeChecker { + using ERC165CheckerUpgradeable for address; + + /// @custom:storage-location erc7201:story-protocol.relationship-module.storage + struct RelationshipModuleStorage { + mapping(bytes32 => bool) relationships; + mapping(bytes32 => RelationshipConfig) relConfigs; + } + + // keccak256(bytes.concat(bytes32(uint256(keccak256("story-protocol.relationship-module.storage")) - 1))) + bytes32 private constant _STORAGE_LOCATION = 0xd16687d5cf786234491b4cc484b2a64f24855aadee9b1b73824db1ed2840fd0b; + FranchiseRegistry public immutable FRANCHISE_REGISTRY; + + constructor(address _franchiseRegistry) { + if (_franchiseRegistry == address(0)) revert ZeroAddress(); + FRANCHISE_REGISTRY = FranchiseRegistry(_franchiseRegistry); + _disableInitializers(); + } + + function initialize(address accessControl) public initializer { + __AccessControlledUpgradeable_init(accessControl); + } + + function _getRelationshipModuleStorage() + private + pure + returns (RelationshipModuleStorage storage $) + { + assembly { + $.slot := _STORAGE_LOCATION + } + } + + function relate(RelationshipParams calldata params, bytes calldata data) external { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + RelationshipConfig storage config = $.relConfigs[params.relationshipId]; + _verifyRelationshipParams(params, config); + + config.processor.processRelationship(params, data, msg.sender); + + $.relationships[_getRelationshipKey(params)] = true; + emit RelationSet(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); + } + + function unrelate(RelationshipParams calldata params) external onlyRole(RELATIONSHIP_DISPUTER_ROLE) { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + bytes32 key = _getRelationshipKey(params); + if (!$.relationships[key]) revert NonExistingRelationship(); + delete $.relationships[key]; + emit RelationUnset(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); + } + + function areTheyRelated(RelationshipParams calldata params) external view returns (bool) { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + return $.relationships[_getRelationshipKey(params)]; + } + + function _verifyRelationshipParams(RelationshipParams calldata params, RelationshipConfig memory config) private view { + if (config.sourceIPAssetTypeMask == 0) revert NonExistingRelationship(); + (bool sourceResult, bool sourceIsAssetRegistry) = _checkRelationshipNode(params.sourceContract, params.sourceId, config.sourceIPAssetTypeMask); + if (!sourceResult) revert UnsupportedRelationshipSource(); + (bool destResult, bool destIsAssetRegistry) = _checkRelationshipNode(params.destContract, params.destId, config.destIPAssetTypeMask); + if (!destResult) revert UnsupportedRelationshipDestination(); + if(sourceIsAssetRegistry && destIsAssetRegistry && params.sourceContract != params.destContract && config.onlySameFranchise) revert CannotRelationshipToAnotherFranchise(); + } + + function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { + try IIPAssetRegistry(ipAssetRegistry).franchiseId() returns (uint256 franchiseId) { + return FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) == ipAssetRegistry; + } catch { + return false; + } + } + + function _getRelationshipKey(RelationshipParams calldata params) private pure returns (bytes32) { + return keccak256( + abi.encode( + params.sourceContract, + params.sourceId, + params.destContract, + params.destId, + params.relationshipId + ) + ); + } + + /********* Setting Relationships *********/ + function setRelationshipConfig(bytes32 relationshipId, SetRelationshipParams calldata params) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { + if (!params.processor.supportsInterface(type(IRelationshipProcessor).interfaceId)) revert UnsupportedInterface("IRelationshipProcessor"); + RelationshipConfig memory config = RelationshipConfig( + _convertToMask(params.sourceIPAssets, params.allowedExternalSource), + _convertToMask(params.destIPAssets, params.allowedExternalDest), + params.onlySameFranchise, + IRelationshipProcessor(params.processor) + ); + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + $.relConfigs[relationshipId] = config; + emit RelationshipConfigSet( + relationshipId, + config.sourceIPAssetTypeMask, + config.destIPAssetTypeMask, + config.onlySameFranchise, + params.processor + ); + } + + function unsetConfig(bytes32 relationshipId) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + if ( + $.relConfigs[relationshipId].sourceIPAssetTypeMask == 0 + ) revert NonExistingRelationship(); + delete $.relConfigs[relationshipId]; + emit RelationshipConfigUnset(relationshipId); + } + + function config(bytes32 relationshipId) external view returns (RelationshipConfig memory) { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + return $.relConfigs[relationshipId]; + } + + function _authorizeUpgrade( + address newImplementation + ) internal virtual override onlyRole(UPGRADER_ROLE) {} + +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol new file mode 100644 index 00000000..c81b1ffe --- /dev/null +++ b/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IRelationshipProcessor } from "./IRelationshipProcessor.sol"; +import { ZeroAddress } from "contracts/errors/General.sol"; +import { IRelationshipModule } from "../IRelationshipModule.sol"; +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; + +abstract contract BaseRelationshipProcessor is IRelationshipProcessor, ERC165 { + + address internal immutable _RELATIONSHIP_MODULE; + error OnlyRelationshipModule(); + + constructor(address _relationshipModule) { + if(_relationshipModule == address(0)) revert ZeroAddress(); + _RELATIONSHIP_MODULE = _relationshipModule; + } + + function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external override { + if(msg.sender != _RELATIONSHIP_MODULE) revert OnlyRelationshipModule(); + _processRelationship(params, data, caller); + } + + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) internal virtual; + + function supportsInterface( + bytes4 interfaceId + ) public view override(ERC165) returns (bool) { + return super.supportsInterface(interfaceId) || interfaceId == type(IRelationshipProcessor).interfaceId; + } + +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol new file mode 100644 index 00000000..9656c600 --- /dev/null +++ b/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseRelationshipProcessor } from "./BaseRelationshipProcessor.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Unauthorized } from "contracts/errors/General.sol"; +import { IRelationshipModule } from "../IRelationshipModule.sol"; + +contract DstRelationshipProcessor is BaseRelationshipProcessor { + + constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override { + if (IERC721(params.destContract).ownerOf(params.destId) != caller) { + revert Unauthorized(); + } + } + +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol new file mode 100644 index 00000000..2968ef2f --- /dev/null +++ b/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { IRelationshipModule } from "../IRelationshipModule.sol"; + +interface IRelationshipProcessor { + function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external; +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol new file mode 100644 index 00000000..edfc16b2 --- /dev/null +++ b/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseRelationshipProcessor } from "./BaseRelationshipProcessor.sol"; +import { IRelationshipModule } from "../IRelationshipModule.sol"; + +contract PermissionlessRelationshipProcessor is BaseRelationshipProcessor { + + constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + + function _processRelationship(IRelationshipModule.RelationshipParams memory, bytes calldata, address) internal virtual override { + // do nothing + } +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol new file mode 100644 index 00000000..aaa5c452 --- /dev/null +++ b/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseRelationshipProcessor } from "./BaseRelationshipProcessor.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Unauthorized } from "contracts/errors/General.sol"; +import { IRelationshipModule } from "../IRelationshipModule.sol"; + +contract SrcDstRelationshipProcessor is BaseRelationshipProcessor { + + constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override { + if ( + IERC721(params.sourceContract).ownerOf(params.sourceId) != caller || + IERC721(params.destContract).ownerOf(params.destId) != caller) { + revert Unauthorized(); + } + } + +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol new file mode 100644 index 00000000..f2e2be0e --- /dev/null +++ b/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { BaseRelationshipProcessor } from "./BaseRelationshipProcessor.sol"; +import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import { Unauthorized } from "contracts/errors/General.sol"; +import { IRelationshipModule } from "../IRelationshipModule.sol"; + +contract SrcRelationshipProcessor is BaseRelationshipProcessor { + + constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override { + if (IERC721(params.sourceContract).ownerOf(params.sourceId) != caller) { + revert Unauthorized(); + } + } + +} \ No newline at end of file diff --git a/contracts/modules/linking/LinkIPAssetTypeChecker.sol b/contracts/modules/relationships/RelationshipTypeChecker.sol similarity index 89% rename from contracts/modules/linking/LinkIPAssetTypeChecker.sol rename to contracts/modules/relationships/RelationshipTypeChecker.sol index 1b41bfe6..8c298df6 100644 --- a/contracts/modules/linking/LinkIPAssetTypeChecker.sol +++ b/contracts/modules/relationships/RelationshipTypeChecker.sol @@ -6,11 +6,11 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; -abstract contract LinkIPAssetTypeChecker { +abstract contract RelationshipTypeChecker { error InvalidIPAssetArray(); - function _checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) internal view returns (bool result, bool isAssetRegistry) { + function _checkRelationshipNode(address collection, uint256 id, uint256 assetTypeMask) internal view returns (bool result, bool isAssetRegistry) { if (IERC721(collection).ownerOf(id) == address(0)) return (false, false); isAssetRegistry = _isAssetRegistry(collection); if (isAssetRegistry) { diff --git a/test/foundry/linking/LinkIPAssetTypeChecker.t..sol b/test/foundry/relationships/RelationShipTypeChecker.t.sol similarity index 76% rename from test/foundry/linking/LinkIPAssetTypeChecker.t..sol rename to test/foundry/relationships/RelationShipTypeChecker.t.sol index 8a624c29..70a3b8d1 100644 --- a/test/foundry/linking/LinkIPAssetTypeChecker.t..sol +++ b/test/foundry/relationships/RelationShipTypeChecker.t.sol @@ -4,13 +4,13 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import { IPAssetRegistryFactory } from "contracts/ip-assets/IPAssetRegistryFactory.sol"; -import { LinkIPAssetTypeChecker } from "contracts/modules/linking/LinkIPAssetTypeChecker.sol"; +import { RelationshipTypeChecker } from "contracts/modules/relationships/RelationshipTypeChecker.sol"; import { IPAsset, EXTERNAL_ASSET } from "contracts/IPAsset.sol"; import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; -contract LinkIPAssetTypeCheckerHarness is LinkIPAssetTypeChecker { +contract RelationshipTypeCheckerHarness is RelationshipTypeChecker { bool private _returnIsAssetRegistry; @@ -22,8 +22,8 @@ contract LinkIPAssetTypeCheckerHarness is LinkIPAssetTypeChecker { return _returnIsAssetRegistry; } - function checkLinkEnd(address collection, uint256 id, uint256 assetTypeMask) view external returns (bool result, bool isAssetRegistry) { - return _checkLinkEnd(collection, id, assetTypeMask); + function checkRelationshipNode(address collection, uint256 id, uint256 assetTypeMask) view external returns (bool result, bool isAssetRegistry) { + return _checkRelationshipNode(collection, id, assetTypeMask); } function convertToMask(IPAsset[] calldata ipAssets, bool allowsExternal) pure external returns (uint256) { @@ -44,14 +44,14 @@ contract MockERC721 is ERC721 { } -contract LinkIPAssetTypeCheckerConvertToMaskTest is Test { +contract RelationshipTypeCheckerConvertToMaskTest is Test { - LinkIPAssetTypeCheckerHarness public checker; + RelationshipTypeCheckerHarness public checker; error InvalidIPAssetArray(); function setUp() public { - checker = new LinkIPAssetTypeCheckerHarness(); + checker = new RelationshipTypeCheckerHarness(); } function test_convertToMaskWithoutExternal() public { @@ -96,14 +96,14 @@ contract LinkIPAssetTypeCheckerConvertToMaskTest is Test { } -contract LinkIPAssetTypeCheckerSupportsAssetTypeTest is Test { +contract RelationshipTypeCheckerSupportsAssetTypeTest is Test { - LinkIPAssetTypeCheckerHarness public checker; + RelationshipTypeCheckerHarness public checker; error InvalidIPAssetArray(); function setUp() public { - checker = new LinkIPAssetTypeCheckerHarness(); + checker = new RelationshipTypeCheckerHarness(); } function test_supportsIPAssetType_true() public { @@ -128,58 +128,58 @@ contract LinkIPAssetTypeCheckerSupportsAssetTypeTest is Test { } -contract LinkIPAssetTypeCheckerCheckLinkEndTest is Test { +contract RelationshipTypeCheckerNodesTest is Test { - LinkIPAssetTypeCheckerHarness public checker; + RelationshipTypeCheckerHarness public checker; MockERC721 public collection; address public owner = address(0x1); error InvalidIPAssetArray(); function setUp() public { - checker = new LinkIPAssetTypeCheckerHarness(); + checker = new RelationshipTypeCheckerHarness(); collection = new MockERC721("Test", "TEST"); } - function test_checkLinkEnd_ipAsset_true() public { + function test_checkRelationshipNode_ipAsset_true() public { uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; console.log(tokenId); collection.mint(owner, tokenId); checker.setIsAssetRegistry(true); uint256 mask = 1 << (uint256(IPAsset(1)) & 0xff); - (bool result, bool isAssetRegistry) = checker.checkLinkEnd(address(collection), tokenId, mask); + (bool result, bool isAssetRegistry) = checker.checkRelationshipNode(address(collection), tokenId, mask); assertTrue(result); assertTrue(isAssetRegistry); } - function test_checkLinkEnd_ipAsset_false() public { + function test_checkRelationshipNode_ipAsset_false() public { uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; collection.mint(owner, tokenId); checker.setIsAssetRegistry(true); uint256 mask = 1 << (uint256(IPAsset(2)) & 0xff); - (bool result, bool isAssetRegistry) = checker.checkLinkEnd(address(collection), tokenId, mask); + (bool result, bool isAssetRegistry) = checker.checkRelationshipNode(address(collection), tokenId, mask); assertFalse(result); assertTrue(isAssetRegistry); } - function test_checkLinkEnd_external_true() public { + function test_checkRelationshipNode_external_true() public { uint256 tokenId = LibIPAssetId._zeroId(IPAsset(1)) + 1; collection.mint(owner, tokenId); checker.setIsAssetRegistry(false); uint256 mask = 1 << (uint256(EXTERNAL_ASSET) & 0xff); - (bool result, bool isAssetRegistry) = checker.checkLinkEnd(address(collection), tokenId, mask); + (bool result, bool isAssetRegistry) = checker.checkRelationshipNode(address(collection), tokenId, mask); assertTrue(result); assertFalse(isAssetRegistry); } function test_revert_nonExistingToken() public { vm.expectRevert("ERC721: invalid token ID"); - checker.checkLinkEnd(address(collection), 1, uint256(type(uint8).max)); + checker.checkRelationshipNode(address(collection), 1, uint256(type(uint8).max)); } function test_revert_notERC721() public { vm.expectRevert(); - checker.checkLinkEnd(owner, 1, uint256(type(uint8).max)); + checker.checkRelationshipNode(owner, 1, uint256(type(uint8).max)); } diff --git a/test/foundry/linking/LinkingModule.SettingLinks.t.sol b/test/foundry/relationships/RelationshipModule.Config.t.sol similarity index 60% rename from test/foundry/linking/LinkingModule.SettingLinks.t.sol rename to test/foundry/relationships/RelationshipModule.Config.t.sol index 1a35738a..0922029d 100644 --- a/test/foundry/linking/LinkingModule.SettingLinks.t.sol +++ b/test/foundry/relationships/RelationshipModule.Config.t.sol @@ -7,22 +7,22 @@ import "contracts/FranchiseRegistry.sol"; import "contracts/access-control/AccessControlSingleton.sol"; import "contracts/access-control/ProtocolRoles.sol"; import "contracts/ip-assets/IPAssetRegistryFactory.sol"; -import "contracts/modules/linking/LinkingModule.sol"; +import "contracts/modules/relationships/RelationshipModule.sol"; import "contracts/IPAsset.sol"; import "contracts/errors/General.sol"; -import "contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol"; +import "contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol"; -contract LinkingModuleSetupLinksTest is Test, ProxyHelper { +contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { IPAssetRegistryFactory public factory; IPAssetRegistry public ipAssetRegistry; FranchiseRegistry public register; - LinkingModule public linkingModule; + RelationshipModule public relationshipModule; AccessControlSingleton acs; - PermissionlessLinkProcessor public linkProcessor; + PermissionlessRelationshipProcessor public RelationshipProcessor; address admin = address(123); - address linkManager = address(234); + address relationshipManager = address(234); address franchiseOwner = address(456); bytes32 relationship = keccak256("RELATIONSHIP"); @@ -38,7 +38,7 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { ) ); vm.prank(admin); - acs.grantRole(LINK_MANAGER_ROLE, linkManager); + acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); address accessControl = address(acs); @@ -55,62 +55,62 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); ipAssetRegistry = IPAssetRegistry(ipAssets); vm.stopPrank(); - linkingModule = LinkingModule( + relationshipModule = RelationshipModule( _deployUUPSProxy( - address(new LinkingModule(address(register))), + address(new RelationshipModule(address(register))), abi.encodeWithSelector( bytes4(keccak256(bytes("initialize(address)"))), address(acs) ) ) ); - linkProcessor = new PermissionlessLinkProcessor(address(linkingModule)); + RelationshipProcessor = new PermissionlessRelationshipProcessor(address(relationshipModule)); } - function test_setProtocolLevelLink() public { + function test_setProtocolLevelRelationship() public { IPAsset[] memory sourceIPAssets = new IPAsset[](1); sourceIPAssets[0] = IPAsset.STORY; IPAsset[] memory destIPAssets = new IPAsset[](2); destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ + RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true, - linkProcessor: address(linkProcessor) + onlySameFranchise: true, + processor: address(RelationshipProcessor) }); - assertTrue(acs.hasRole(LINK_MANAGER_ROLE, linkManager)); - vm.prank(linkManager); - linkingModule.setLinkConfig(relationship, params); + assertTrue(acs.hasRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager)); + vm.prank(relationshipManager); + relationshipModule.setRelationshipConfig(relationship, params); - LinkingModule.LinkConfig memory config = linkingModule.linkConfig(relationship); + RelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); assertEq(config.sourceIPAssetTypeMask, 1 << (uint256(IPAsset.STORY) & 0xff)); assertEq(config.destIPAssetTypeMask, 1 << (uint256(IPAsset.CHARACTER) & 0xff) | 1 << (uint256(IPAsset.ART) & 0xff) | (uint256(EXTERNAL_ASSET) << 248)); - assertTrue(config.linkOnlySameFranchise); + assertTrue(config.onlySameFranchise); // TODO: test for event } - function test_revert_IfSettingProtocolLevelLinkUnauthorized() public { + function test_revert_IfSettingProtocolLevelRelationshipUnauthorized() public { IPAsset[] memory sourceIPAssets = new IPAsset[](1); sourceIPAssets[0] = IPAsset.STORY; IPAsset[] memory destIPAssets = new IPAsset[](2); destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ + RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true, - linkProcessor: address(linkProcessor) + onlySameFranchise: true, + processor: address(RelationshipProcessor) }); vm.expectRevert(); vm.prank(franchiseOwner); - linkingModule.setLinkConfig(relationship, params); + relationshipModule.setRelationshipConfig(relationship, params); } function test_revert_IfMasksNotConfigured() public { @@ -118,35 +118,35 @@ contract LinkingModuleSetupLinksTest is Test, ProxyHelper { sourceIPAssets[0] = IPAsset.UNDEFINED; IPAsset[] memory destIPAssets = new IPAsset[](2); - LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ + RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true, - linkProcessor: address(linkProcessor) + onlySameFranchise: true, + processor: address(RelationshipProcessor) }); - vm.startPrank(linkManager); + vm.startPrank(relationshipManager); vm.expectRevert(); - linkingModule.setLinkConfig(relationship, params); + relationshipModule.setRelationshipConfig(relationship, params); } } -contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { +contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { IPAssetRegistryFactory public factory; IPAssetRegistry public ipAssetRegistry; FranchiseRegistry public register; - LinkingModule public linkingModule; + RelationshipModule public relationshipModule; AccessControlSingleton acs; - PermissionlessLinkProcessor public linkProcessor; + PermissionlessRelationshipProcessor public RelationshipProcessor; address admin = address(123); - address linkManager = address(234); + address relationshipManager = address(234); address franchiseOwner = address(456); - bytes32 relationship = keccak256("PROTOCOL_LINK"); + bytes32 relationship = keccak256("PROTOCOL_Relationship"); function setUp() public { factory = new IPAssetRegistryFactory(); @@ -159,7 +159,7 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { ) ); vm.prank(admin); - acs.grantRole(LINK_MANAGER_ROLE, linkManager); + acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); address accessControl = address(acs); @@ -176,52 +176,52 @@ contract LinkingModuleUnsetLinksTest is Test, ProxyHelper { (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); ipAssetRegistry = IPAssetRegistry(ipAssets); vm.stopPrank(); - linkingModule = LinkingModule( + relationshipModule = RelationshipModule( _deployUUPSProxy( - address(new LinkingModule(address(register))), + address(new RelationshipModule(address(register))), abi.encodeWithSelector( bytes4(keccak256(bytes("initialize(address)"))), address(acs) ) ) ); - linkProcessor = new PermissionlessLinkProcessor(address(linkingModule)); + RelationshipProcessor = new PermissionlessRelationshipProcessor(address(relationshipModule)); IPAsset[] memory sourceIPAssets = new IPAsset[](1); sourceIPAssets[0] = IPAsset.STORY; IPAsset[] memory destIPAssets = new IPAsset[](1); destIPAssets[0] = IPAsset.CHARACTER; - LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ + RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true, - linkProcessor: address(linkProcessor) + onlySameFranchise: true, + processor: address(RelationshipProcessor) }); - vm.prank(linkManager); - linkingModule.setLinkConfig(relationship, params); + vm.prank(relationshipManager); + relationshipModule.setRelationshipConfig(relationship, params); } - function test_unsetLinkConfig() public { - vm.prank(linkManager); - linkingModule.unsetLinkConfig(relationship); + function test_unsetConfig() public { + vm.prank(relationshipManager); + relationshipModule.unsetConfig(relationship); - LinkingModule.LinkConfig memory config = linkingModule.linkConfig(relationship); + RelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); assertEq(config.sourceIPAssetTypeMask, 0); assertEq(config.destIPAssetTypeMask, 0); - assertFalse(config.linkOnlySameFranchise); + assertFalse(config.onlySameFranchise); // TODO: test for event } - function test_revert_unsetLinkConfigNotAuthorized() public { + function test_revert_unsetRelationshipConfigNotAuthorized() public { vm.expectRevert(); - linkingModule.unsetLinkConfig(relationship); + relationshipModule.unsetConfig(relationship); } - function test_revert_unsetLinkConfigNonExistingLink() public { - vm.prank(linkManager); - vm.expectRevert(ILinkingModule.NonExistingLink.selector); - linkingModule.unsetLinkConfig(keccak256("UNDEFINED_LINK")); + function test_revert_unsetRelationshipConfigNonExistingRelationship() public { + vm.prank(relationshipManager); + vm.expectRevert(IRelationshipModule.NonExistingRelationship.selector); + relationshipModule.unsetConfig(keccak256("UNDEFINED_Relationship")); } } diff --git a/test/foundry/linking/LinkingModule.Linking.t.sol b/test/foundry/relationships/RelationshipModule.Relating.t.sol similarity index 68% rename from test/foundry/linking/LinkingModule.Linking.t.sol rename to test/foundry/relationships/RelationshipModule.Relating.t.sol index b355efa9..de489e56 100644 --- a/test/foundry/linking/LinkingModule.Linking.t.sol +++ b/test/foundry/relationships/RelationshipModule.Relating.t.sol @@ -7,10 +7,10 @@ import "contracts/FranchiseRegistry.sol"; import "contracts/access-control/AccessControlSingleton.sol"; import "contracts/access-control/ProtocolRoles.sol"; import "contracts/ip-assets/IPAssetRegistryFactory.sol"; -import "contracts/modules/linking/LinkingModule.sol"; +import "contracts/modules/relationships/RelationshipModule.sol"; import "contracts/IPAsset.sol"; import "contracts/errors/General.sol"; -import "contracts/modules/linking/LinkProcessors/PermissionlessLinkProcessor.sol"; +import "contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol"; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; contract MockExternalAsset is ERC721 { @@ -21,17 +21,17 @@ contract MockExternalAsset is ERC721 { } } -contract LinkingModuleLinkingTest is Test, ProxyHelper { +contract RelationshipModuleRelationshipTest is Test, ProxyHelper { IPAssetRegistryFactory public factory; IPAssetRegistry public ipAssetRegistry; FranchiseRegistry public register; - LinkingModule public linkingModule; + RelationshipModule public relationshipModule; AccessControlSingleton acs; - PermissionlessLinkProcessor public linkProcessor; + PermissionlessRelationshipProcessor public processor; address admin = address(123); - address linkManager = address(234); + address relationshipManager = address(234); address franchiseOwner = address(456); address ipAssetOwner = address(567); @@ -53,7 +53,7 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { ) ); vm.prank(admin); - acs.grantRole(LINK_MANAGER_ROLE, linkManager); + acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); FranchiseRegistry impl = new FranchiseRegistry(address(factory)); register = FranchiseRegistry( @@ -68,9 +68,9 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); ipAssetRegistry = IPAssetRegistry(ipAssets); - linkingModule = LinkingModule( + relationshipModule = RelationshipModule( _deployUUPSProxy( - address(new LinkingModule(address(register))), + address(new RelationshipModule(address(register))), abi.encodeWithSelector( bytes4(keccak256(bytes("initialize(address)"))), address(acs) ) @@ -82,17 +82,17 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - linkProcessor = new PermissionlessLinkProcessor(address(linkingModule)); - LinkingModule.SetLinkParams memory params = ILinkingModule.SetLinkParams({ + processor = new PermissionlessRelationshipProcessor(address(relationshipModule)); + RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, - linkOnlySameFranchise: true, - linkProcessor: address(linkProcessor) + onlySameFranchise: true, + processor: address(processor) }); - vm.prank(linkManager); - linkingModule.setLinkConfig(relationship, params); + vm.prank(relationshipManager); + relationshipModule.setRelationshipConfig(relationship, params); vm.startPrank(ipAssetOwner); ipAssetIds[uint8(IPAsset.STORY)] = ipAssetRegistry.createIPAsset(IPAsset.STORY, "name", "description", "mediaUrl"); @@ -105,105 +105,105 @@ contract LinkingModuleLinkingTest is Test, ProxyHelper { vm.stopPrank(); } - function test_link() public { - linkingModule.link( - ILinkingModule.LinkParams( + function test_relate() public { + relationshipModule.relate( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship ), "" ); assertTrue( - linkingModule.areTheyLinked( - ILinkingModule.LinkParams( + relationshipModule.areTheyRelated( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship ) ) ); - linkingModule.link( - ILinkingModule.LinkParams( + relationshipModule.relate( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship ), "" ); assertTrue( - linkingModule.areTheyLinked( - ILinkingModule.LinkParams( + relationshipModule.areTheyRelated( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship ) ) ); - linkingModule.link( - ILinkingModule.LinkParams( + relationshipModule.relate( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship ), "" ); assertTrue( - linkingModule.areTheyLinked( - ILinkingModule.LinkParams( + relationshipModule.areTheyRelated( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship ) ) ); // TODO check for event assertFalse( - linkingModule.areTheyLinked( - ILinkingModule.LinkParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, relationship) + relationshipModule.areTheyRelated( + IRelationshipModule.RelationshipParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, relationship) ) ); assertFalse( - linkingModule.areTheyLinked( - ILinkingModule.LinkParams( + relationshipModule.areTheyRelated( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG") ) ) ); } - function test_revert_unknown_link() public { - vm.expectRevert(ILinkingModule.NonExistingLink.selector); - linkingModule.link( - ILinkingModule.LinkParams( + function test_revert_unknown_relationship() public { + vm.expectRevert(IRelationshipModule.NonExistingRelationship.selector); + relationshipModule.relate( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG") ), "" ); } - function test_revert_linkingNotSameFranchise() public { + function test_revert_relationshipsNotSameFranchise() public { vm.prank(franchiseOwner); (uint256 id, address otherIPAssets) = register.registerFranchise("name2", "symbol2", "description2"); IPAssetRegistry otherIPAssetRegistry = IPAssetRegistry(otherIPAssets); vm.prank(ipAssetOwner); uint256 otherId = otherIPAssetRegistry.createIPAsset(IPAsset.CHARACTER, "name", "description", "mediaUrl"); - vm.expectRevert(ILinkingModule.CannotLinkToAnotherFranchise.selector); - linkingModule.link( - ILinkingModule.LinkParams( + vm.expectRevert(IRelationshipModule.CannotRelationshipToAnotherFranchise.selector); + relationshipModule.relate( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, relationship ), "" ); } - function test_revert_linkUnsupportedSource() public { + function test_revert_relateUnsupportedSource() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); - vm.expectRevert(ILinkingModule.UnsupportedLinkSource.selector); - linkingModule.link( - ILinkingModule.LinkParams( + vm.expectRevert(IRelationshipModule.UnsupportedRelationshipSource.selector); + relationshipModule.relate( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship ), "" ); } - function test_revert_linkUnsupportedDestination() public { + function test_revert_relateUnsupportedDestination() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); - vm.expectRevert(ILinkingModule.UnsupportedLinkDestination.selector); - linkingModule.link( - ILinkingModule.LinkParams( + vm.expectRevert(IRelationshipModule.UnsupportedRelationshipDestination.selector); + relationshipModule.relate( + IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, relationship ), "" From eff34d84f94f43a508e369d25efbbf1e49bba471 Mon Sep 17 00:00:00 2001 From: Raul Date: Sat, 15 Jul 2023 02:11:46 +0200 Subject: [PATCH 19/30] rel processor flow control --- .../relationships/IRelationshipModule.sol | 8 +++++++ .../relationships/RelationshipModule.sol | 24 ++++++++++--------- .../BaseRelationshipProcessor.sol | 6 ++--- .../DstOwnerRelationshipProcessor.sol | 3 ++- .../IRelationshipProcessor.sol | 2 +- .../PermissionlessRelationshipProcessor.sol | 4 ++-- .../SrcDstOwnerRelationshipProcessor.sol | 3 ++- .../SrcOwnerRelationshipProcessor.sol | 3 ++- 8 files changed, 33 insertions(+), 20 deletions(-) diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol index 6e172804..4645f4a7 100644 --- a/contracts/modules/relationships/IRelationshipModule.sol +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -13,6 +13,13 @@ interface IRelationshipModule { uint256 destId, bytes32 relationshipId ); + event RelationPendingProcessor( + address sourceContract, + uint256 sourceId, + address destContract, + uint256 destId, + bytes32 relationshipId + ); event RelationUnset( address sourceContract, uint256 sourceId, @@ -28,6 +35,7 @@ interface IRelationshipModule { bool onlySameFranchise, address processor ); + event RelationshipConfigUnset(bytes32 relationshipId); error NonExistingRelationship(); diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index ee3dcd9c..bc5bc34e 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -47,13 +47,15 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, function relate(RelationshipParams calldata params, bytes calldata data) external { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); - RelationshipConfig storage config = $.relConfigs[params.relationshipId]; - _verifyRelationshipParams(params, config); + RelationshipConfig storage relConfig = $.relConfigs[params.relationshipId]; + _verifyRelationshipParams(params, relConfig); - config.processor.processRelationship(params, data, msg.sender); - - $.relationships[_getRelationshipKey(params)] = true; - emit RelationSet(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); + if (!relConfig.processor.processRelationship(params, data, msg.sender)) { + emit RelationPendingProcessor(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); + } else { + $.relationships[_getRelationshipKey(params)] = true; + emit RelationSet(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); + } } function unrelate(RelationshipParams calldata params) external onlyRole(RELATIONSHIP_DISPUTER_ROLE) { @@ -101,19 +103,19 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, /********* Setting Relationships *********/ function setRelationshipConfig(bytes32 relationshipId, SetRelationshipParams calldata params) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { if (!params.processor.supportsInterface(type(IRelationshipProcessor).interfaceId)) revert UnsupportedInterface("IRelationshipProcessor"); - RelationshipConfig memory config = RelationshipConfig( + RelationshipConfig memory relConfig = RelationshipConfig( _convertToMask(params.sourceIPAssets, params.allowedExternalSource), _convertToMask(params.destIPAssets, params.allowedExternalDest), params.onlySameFranchise, IRelationshipProcessor(params.processor) ); RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); - $.relConfigs[relationshipId] = config; + $.relConfigs[relationshipId] = relConfig; emit RelationshipConfigSet( relationshipId, - config.sourceIPAssetTypeMask, - config.destIPAssetTypeMask, - config.onlySameFranchise, + relConfig.sourceIPAssetTypeMask, + relConfig.destIPAssetTypeMask, + relConfig.onlySameFranchise, params.processor ); } diff --git a/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol index c81b1ffe..727bbfc1 100644 --- a/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol @@ -16,12 +16,12 @@ abstract contract BaseRelationshipProcessor is IRelationshipProcessor, ERC165 { _RELATIONSHIP_MODULE = _relationshipModule; } - function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external override { + function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external override returns(bool) { if(msg.sender != _RELATIONSHIP_MODULE) revert OnlyRelationshipModule(); - _processRelationship(params, data, caller); + return _processRelationship(params, data, caller); } - function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) internal virtual; + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) internal virtual returns(bool); function supportsInterface( bytes4 interfaceId diff --git a/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol index 9656c600..b79519d3 100644 --- a/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol @@ -10,10 +10,11 @@ contract DstRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} - function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override { + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override returns(bool) { if (IERC721(params.destContract).ownerOf(params.destId) != caller) { revert Unauthorized(); } + return true; } } \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol index 2968ef2f..f076ad55 100644 --- a/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol @@ -4,5 +4,5 @@ pragma solidity ^0.8.13; import { IRelationshipModule } from "../IRelationshipModule.sol"; interface IRelationshipProcessor { - function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external; + function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external returns(bool); } \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol index edfc16b2..3366d3e5 100644 --- a/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol @@ -8,7 +8,7 @@ contract PermissionlessRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} - function _processRelationship(IRelationshipModule.RelationshipParams memory, bytes calldata, address) internal virtual override { - // do nothing + function _processRelationship(IRelationshipModule.RelationshipParams memory, bytes calldata, address) internal virtual override returns(bool) { + return true; } } \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol index aaa5c452..5d7fd4c2 100644 --- a/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol @@ -10,12 +10,13 @@ contract SrcDstRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} - function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override { + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override returns(bool) { if ( IERC721(params.sourceContract).ownerOf(params.sourceId) != caller || IERC721(params.destContract).ownerOf(params.destId) != caller) { revert Unauthorized(); } + return true; } } \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol index f2e2be0e..819004ce 100644 --- a/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol @@ -10,10 +10,11 @@ contract SrcRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} - function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override { + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override returns(bool) { if (IERC721(params.sourceContract).ownerOf(params.sourceId) != caller) { revert Unauthorized(); } + return true; } } \ No newline at end of file From 0d58886cc75f2799a60cd8f169e788bb766846bd Mon Sep 17 00:00:00 2001 From: Raul Date: Sat, 15 Jul 2023 02:12:36 +0200 Subject: [PATCH 20/30] fix --- contracts/modules/relationships/RelationshipModule.sol | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index bc5bc34e..25983c8a 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -71,13 +71,13 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, return $.relationships[_getRelationshipKey(params)]; } - function _verifyRelationshipParams(RelationshipParams calldata params, RelationshipConfig memory config) private view { - if (config.sourceIPAssetTypeMask == 0) revert NonExistingRelationship(); - (bool sourceResult, bool sourceIsAssetRegistry) = _checkRelationshipNode(params.sourceContract, params.sourceId, config.sourceIPAssetTypeMask); + function _verifyRelationshipParams(RelationshipParams calldata params, RelationshipConfig memory relConfig) private view { + if (relConfig.sourceIPAssetTypeMask == 0) revert NonExistingRelationship(); + (bool sourceResult, bool sourceIsAssetRegistry) = _checkRelationshipNode(params.sourceContract, params.sourceId, relConfig.sourceIPAssetTypeMask); if (!sourceResult) revert UnsupportedRelationshipSource(); - (bool destResult, bool destIsAssetRegistry) = _checkRelationshipNode(params.destContract, params.destId, config.destIPAssetTypeMask); + (bool destResult, bool destIsAssetRegistry) = _checkRelationshipNode(params.destContract, params.destId, relConfig.destIPAssetTypeMask); if (!destResult) revert UnsupportedRelationshipDestination(); - if(sourceIsAssetRegistry && destIsAssetRegistry && params.sourceContract != params.destContract && config.onlySameFranchise) revert CannotRelationshipToAnotherFranchise(); + if(sourceIsAssetRegistry && destIsAssetRegistry && params.sourceContract != params.destContract && relConfig.onlySameFranchise) revert CannotRelationshipToAnotherFranchise(); } function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { From 04ebdf3dd07aa33b2597e2f98774a3d8ae08c72a Mon Sep 17 00:00:00 2001 From: Raul Date: Sat, 15 Jul 2023 03:11:31 +0200 Subject: [PATCH 21/30] time links --- .../relationships/IRelationshipModule.sol | 34 +++++++---- .../relationships/RelationshipModule.sol | 56 +++++++++++++++---- .../RelationshipModule.Config.t.sol | 36 +++++++++--- .../RelationshipModule.Relating.t.sol | 29 +++++----- 4 files changed, 111 insertions(+), 44 deletions(-) diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol index 4645f4a7..a0ec8417 100644 --- a/contracts/modules/relationships/IRelationshipModule.sol +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -11,53 +11,66 @@ interface IRelationshipModule { uint256 sourceId, address destContract, uint256 destId, - bytes32 relationshipId + bytes32 indexed relationshipId, + uint256 endTime ); - event RelationPendingProcessor( + event RelationUnset( address sourceContract, uint256 sourceId, address destContract, uint256 destId, - bytes32 relationshipId + bytes32 indexed relationshipId ); - event RelationUnset( + event RelationPendingProcessor( address sourceContract, uint256 sourceId, address destContract, uint256 destId, - bytes32 relationshipId + bytes32 indexed relationshipId ); event RelationshipConfigSet( - bytes32 relationshipId, + bytes32 indexed relationshipId, uint256 sourceIPAssetTypeMask, uint256 destIPAssetTypeMask, bool onlySameFranchise, - address processor + address processor, + uint256 maxTTL, + uint256 minTTL ); - event RelationshipConfigUnset(bytes32 relationshipId); + event RelationshipConfigUnset(bytes32 indexed relationshipId); error NonExistingRelationship(); error IntentAlreadyRegistered(); error UnsupportedRelationshipSource(); error UnsupportedRelationshipDestination(); error CannotRelationshipToAnotherFranchise(); + error InvalidTTL(); + error InvalidEndTimestamp(); + + struct TimeConfig { + uint112 maxTTL; + uint112 minTTL; + bool renewable; + } struct RelationshipConfig { uint256 sourceIPAssetTypeMask; uint256 destIPAssetTypeMask; bool onlySameFranchise; IRelationshipProcessor processor; + TimeConfig timeConfig; } - struct SetRelationshipParams { + struct SetRelationshipConfigParams { IPAsset[] sourceIPAssets; bool allowedExternalSource; IPAsset[] destIPAssets; bool allowedExternalDest; bool onlySameFranchise; address processor; + TimeConfig timeConfig; } struct RelationshipParams { @@ -66,12 +79,13 @@ interface IRelationshipModule { address destContract; uint256 destId; bytes32 relationshipId; + uint256 ttl; } function relate(RelationshipParams calldata params, bytes calldata data) external; function unrelate(RelationshipParams calldata params) external; function areTheyRelated(RelationshipParams calldata params) external view returns (bool); - function setRelationshipConfig(bytes32 relationshipId, SetRelationshipParams calldata params) external; + function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external; function unsetConfig(bytes32 relationshipId) external; function config(bytes32 relationshipId) external view returns (RelationshipConfig memory); } \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index 25983c8a..3593632c 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -18,6 +18,7 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, /// @custom:storage-location erc7201:story-protocol.relationship-module.storage struct RelationshipModuleStorage { mapping(bytes32 => bool) relationships; + mapping(bytes32 => uint256) relationshipEnds; mapping(bytes32 => RelationshipConfig) relConfigs; } @@ -25,6 +26,14 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, bytes32 private constant _STORAGE_LOCATION = 0xd16687d5cf786234491b4cc484b2a64f24855aadee9b1b73824db1ed2840fd0b; FranchiseRegistry public immutable FRANCHISE_REGISTRY; + modifier onlyValidTTL(RelationshipParams calldata params) { + RelationshipConfig storage relConfig = _getRelationshipModuleStorage().relConfigs[params.relationshipId]; + if (relConfig.timeConfig.maxTTL != 0 && params.ttl != 0) { + if (params.ttl > relConfig.timeConfig.maxTTL || params.ttl < relConfig.timeConfig.minTTL) revert InvalidEndTimestamp(); + } + _; + } + constructor(address _franchiseRegistry) { if (_franchiseRegistry == address(0)) revert ZeroAddress(); FRANCHISE_REGISTRY = FranchiseRegistry(_franchiseRegistry); @@ -45,7 +54,7 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, } } - function relate(RelationshipParams calldata params, bytes calldata data) external { + function relate(RelationshipParams calldata params, bytes calldata data) external onlyValidTTL(params) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); RelationshipConfig storage relConfig = $.relConfigs[params.relationshipId]; _verifyRelationshipParams(params, relConfig); @@ -53,9 +62,24 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, if (!relConfig.processor.processRelationship(params, data, msg.sender)) { emit RelationPendingProcessor(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); } else { - $.relationships[_getRelationshipKey(params)] = true; - emit RelationSet(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); + bytes32 relKey = _getRelationshipKey(params); + $.relationships[relKey] = true; + uint256 endTime = _updateEndTime(relKey, relConfig.timeConfig, params.ttl); + emit RelationSet(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId, endTime); + } + } + + function _updateEndTime(bytes32 relKey, TimeConfig memory timeConfig, uint256 ttl) private returns (uint256) { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + if (timeConfig.maxTTL != 0) { + uint256 endTime = $.relationshipEnds[relKey]; + if (endTime == 0 || timeConfig.renewable) { + endTime = block.timestamp + ttl; + $.relationshipEnds[relKey] = endTime; + return endTime; + } } + return 0; } function unrelate(RelationshipParams calldata params) external onlyRole(RELATIONSHIP_DISPUTER_ROLE) { @@ -101,14 +125,8 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, } /********* Setting Relationships *********/ - function setRelationshipConfig(bytes32 relationshipId, SetRelationshipParams calldata params) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { - if (!params.processor.supportsInterface(type(IRelationshipProcessor).interfaceId)) revert UnsupportedInterface("IRelationshipProcessor"); - RelationshipConfig memory relConfig = RelationshipConfig( - _convertToMask(params.sourceIPAssets, params.allowedExternalSource), - _convertToMask(params.destIPAssets, params.allowedExternalDest), - params.onlySameFranchise, - IRelationshipProcessor(params.processor) - ); + function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { + RelationshipConfig memory relConfig = _convertRelParams(params); RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); $.relConfigs[relationshipId] = relConfig; emit RelationshipConfigSet( @@ -116,7 +134,21 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, relConfig.sourceIPAssetTypeMask, relConfig.destIPAssetTypeMask, relConfig.onlySameFranchise, - params.processor + params.processor, + relConfig.timeConfig.maxTTL, + relConfig.timeConfig.minTTL + ); + } + + function _convertRelParams(SetRelationshipConfigParams calldata params) private view returns(RelationshipConfig memory) { + if (!params.processor.supportsInterface(type(IRelationshipProcessor).interfaceId)) revert UnsupportedInterface("IRelationshipProcessor"); + if (params.timeConfig.maxTTL < params.timeConfig.minTTL) revert InvalidTTL(); + return RelationshipConfig( + _convertToMask(params.sourceIPAssets, params.allowedExternalSource), + _convertToMask(params.destIPAssets, params.allowedExternalDest), + params.onlySameFranchise, + IRelationshipProcessor(params.processor), + params.timeConfig ); } diff --git a/test/foundry/relationships/RelationshipModule.Config.t.sol b/test/foundry/relationships/RelationshipModule.Config.t.sol index 0922029d..bf15c69a 100644 --- a/test/foundry/relationships/RelationshipModule.Config.t.sol +++ b/test/foundry/relationships/RelationshipModule.Config.t.sol @@ -73,13 +73,18 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ + RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, - processor: address(RelationshipProcessor) + processor: address(RelationshipProcessor), + timeConfig: IRelationshipModule.TimeConfig({ + minTTL: 0, + maxTTL: 0, + renewable: false + }) }); assertTrue(acs.hasRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager)); vm.prank(relationshipManager); @@ -100,13 +105,18 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ + RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, - processor: address(RelationshipProcessor) + processor: address(RelationshipProcessor), + timeConfig: IRelationshipModule.TimeConfig({ + minTTL: 0, + maxTTL: 0, + renewable: false + }) }); vm.expectRevert(); vm.prank(franchiseOwner); @@ -118,13 +128,18 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { sourceIPAssets[0] = IPAsset.UNDEFINED; IPAsset[] memory destIPAssets = new IPAsset[](2); - RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ + RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, - processor: address(RelationshipProcessor) + processor: address(RelationshipProcessor), + timeConfig: IRelationshipModule.TimeConfig({ + minTTL: 0, + maxTTL: 0, + renewable: false + }) }); vm.startPrank(relationshipManager); vm.expectRevert(); @@ -189,13 +204,18 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { sourceIPAssets[0] = IPAsset.STORY; IPAsset[] memory destIPAssets = new IPAsset[](1); destIPAssets[0] = IPAsset.CHARACTER; - RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ + RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, - processor: address(RelationshipProcessor) + processor: address(RelationshipProcessor), + timeConfig: IRelationshipModule.TimeConfig({ + minTTL: 0, + maxTTL: 0, + renewable: false + }) }); vm.prank(relationshipManager); relationshipModule.setRelationshipConfig(relationship, params); diff --git a/test/foundry/relationships/RelationshipModule.Relating.t.sol b/test/foundry/relationships/RelationshipModule.Relating.t.sol index de489e56..37a4721a 100644 --- a/test/foundry/relationships/RelationshipModule.Relating.t.sol +++ b/test/foundry/relationships/RelationshipModule.Relating.t.sol @@ -83,13 +83,14 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { destIPAssets[1] = IPAsset.ART; processor = new PermissionlessRelationshipProcessor(address(relationshipModule)); - RelationshipModule.SetRelationshipParams memory params = IRelationshipModule.SetRelationshipParams({ + RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, - processor: address(processor) + processor: address(processor), + timeConfig: IRelationshipModule.TimeConfig(0, 0, false) }); vm.prank(relationshipManager); relationshipModule.setRelationshipConfig(relationship, params); @@ -108,54 +109,54 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { function test_relate() public { relationshipModule.relate( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship, 0 ), "" ); assertTrue( relationshipModule.areTheyRelated( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship, 0 ) ) ); relationshipModule.relate( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship, 0 ), "" ); assertTrue( relationshipModule.areTheyRelated( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship, 0 ) ) ); relationshipModule.relate( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship, 0 ), "" ); assertTrue( relationshipModule.areTheyRelated( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship, 0 ) ) ); // TODO check for event assertFalse( relationshipModule.areTheyRelated( - IRelationshipModule.RelationshipParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, relationship) + IRelationshipModule.RelationshipParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, relationship, 0) ) ); assertFalse( relationshipModule.areTheyRelated( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG") + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], keccak256("WRONG"), 0 ) ) ); @@ -165,7 +166,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { vm.expectRevert(IRelationshipModule.NonExistingRelationship.selector); relationshipModule.relate( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG") + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], keccak256("WRONG"), 0 ), "" ); @@ -180,7 +181,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { vm.expectRevert(IRelationshipModule.CannotRelationshipToAnotherFranchise.selector); relationshipModule.relate( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, relationship, 0 ), "" ); @@ -192,7 +193,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { vm.expectRevert(IRelationshipModule.UnsupportedRelationshipSource.selector); relationshipModule.relate( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship + address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship, 0 ), "" ); @@ -204,7 +205,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { vm.expectRevert(IRelationshipModule.UnsupportedRelationshipDestination.selector); relationshipModule.relate( IRelationshipModule.RelationshipParams( - address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, relationship + address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, relationship, 0 ), "" ); From 6ea53d74c3ecb54a4c787b323220fd9326898275 Mon Sep 17 00:00:00 2001 From: Raul Date: Sat, 15 Jul 2023 03:23:31 +0200 Subject: [PATCH 22/30] fix tests --- contracts/modules/relationships/IRelationshipModule.sol | 1 + contracts/modules/relationships/RelationshipModule.sol | 8 +++++++- .../relationships/RelationshipModule.Relating.t.sol | 7 ++++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol index a0ec8417..246f7797 100644 --- a/contracts/modules/relationships/IRelationshipModule.sol +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -85,6 +85,7 @@ interface IRelationshipModule { function relate(RelationshipParams calldata params, bytes calldata data) external; function unrelate(RelationshipParams calldata params) external; function areTheyRelated(RelationshipParams calldata params) external view returns (bool); + function isLinkExpired(RelationshipParams calldata params) external view returns (bool); function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external; function unsetConfig(bytes32 relationshipId) external; function config(bytes32 relationshipId) external view returns (RelationshipConfig memory); diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModule.sol index 3593632c..aaaab46e 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModule.sol @@ -92,7 +92,13 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, function areTheyRelated(RelationshipParams calldata params) external view returns (bool) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); - return $.relationships[_getRelationshipKey(params)]; + return $.relationships[_getRelationshipKey(params)] && !isLinkExpired(params); + } + + function isLinkExpired(RelationshipParams calldata params) public view returns (bool) { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + uint256 endTime = $.relationshipEnds[_getRelationshipKey(params)]; + return endTime != 0 && endTime < block.timestamp; } function _verifyRelationshipParams(RelationshipParams calldata params, RelationshipConfig memory relConfig) private view { diff --git a/test/foundry/relationships/RelationshipModule.Relating.t.sol b/test/foundry/relationships/RelationshipModule.Relating.t.sol index 37a4721a..9e373a59 100644 --- a/test/foundry/relationships/RelationshipModule.Relating.t.sol +++ b/test/foundry/relationships/RelationshipModule.Relating.t.sol @@ -120,7 +120,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { ) ) ); - + relationshipModule.relate( IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.ART)], relationship, 0 @@ -134,6 +134,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { ) ) ); + relationshipModule.relate( IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(externalAsset), ipAssetIds[EXTERNAL_ASSET], relationship, 0 @@ -148,6 +149,10 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { ) ); // TODO check for event + + } + + function test_not_related() public { assertFalse( relationshipModule.areTheyRelated( IRelationshipModule.RelationshipParams(address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(1), 2, relationship, 0) From dfd1b5e9d8344bae94e32e6a0e530a3f060970de Mon Sep 17 00:00:00 2001 From: Raul Date: Fri, 21 Jul 2023 16:39:16 +0200 Subject: [PATCH 23/30] Protocol relationship module, abstracted base --- .../relationships/IRelationshipModule.sol | 2 + .../ProtocolRelationshipModule.sol | 29 +++ ...pModule.sol => RelationshipModuleBase.sol} | 38 ++- .../ProtocolRelationshipModule.Config.t.sol | 220 ++++++++++++++++++ .../RelationshipModule.Config.t.sol | 61 ++--- .../RelationshipModule.Relating.t.sol | 12 +- .../RelationshipModuleHarness.sol | 26 +++ 7 files changed, 316 insertions(+), 72 deletions(-) create mode 100644 contracts/modules/relationships/ProtocolRelationshipModule.sol rename contracts/modules/relationships/{RelationshipModule.sol => RelationshipModuleBase.sol} (90%) create mode 100644 test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol create mode 100644 test/foundry/relationships/RelationshipModuleHarness.sol diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol index 246f7797..3c062f35 100644 --- a/contracts/modules/relationships/IRelationshipModule.sol +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -60,6 +60,7 @@ interface IRelationshipModule { uint256 destIPAssetTypeMask; bool onlySameFranchise; IRelationshipProcessor processor; + address disputer; TimeConfig timeConfig; } @@ -70,6 +71,7 @@ interface IRelationshipModule { bool allowedExternalDest; bool onlySameFranchise; address processor; + address disputer; TimeConfig timeConfig; } diff --git a/contracts/modules/relationships/ProtocolRelationshipModule.sol b/contracts/modules/relationships/ProtocolRelationshipModule.sol new file mode 100644 index 00000000..c0d3154d --- /dev/null +++ b/contracts/modules/relationships/ProtocolRelationshipModule.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { RelationshipModuleBase } from "./RelationshipModuleBase.sol"; +import { UPGRADER_ROLE, RELATIONSHIP_MANAGER_ROLE, RELATIONSHIP_DISPUTER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; + +contract ProtocolRelationshipModule is RelationshipModuleBase { + + constructor(address _franchiseRegistry) RelationshipModuleBase(_franchiseRegistry) {} + + function initialize(address accessControl) public initializer { + __RelationshipModuleBase_init(accessControl); + } + + /********* Setting Relationships *********/ + function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { + _setRelationshipConfig(relationshipId, params); + } + + function unsetConfig(bytes32 relationshipId) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { + _unsetConfig(relationshipId); + } + + + function _authorizeUpgrade( + address newImplementation + ) internal virtual override onlyRole(UPGRADER_ROLE) {} + +} \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipModule.sol b/contracts/modules/relationships/RelationshipModuleBase.sol similarity index 90% rename from contracts/modules/relationships/RelationshipModule.sol rename to contracts/modules/relationships/RelationshipModuleBase.sol index aaaab46e..2b8f40a4 100644 --- a/contracts/modules/relationships/RelationshipModule.sol +++ b/contracts/modules/relationships/RelationshipModuleBase.sol @@ -4,15 +4,14 @@ pragma solidity ^0.8.13; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; -import { ZeroAddress, UnsupportedInterface } from "contracts/errors/General.sol"; -import { UPGRADER_ROLE, RELATIONSHIP_MANAGER_ROLE, RELATIONSHIP_DISPUTER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; +import { ZeroAddress, UnsupportedInterface, Unauthorized } from "contracts/errors/General.sol"; import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; import { RelationshipTypeChecker } from "./RelationshipTypeChecker.sol"; import { IRelationshipModule } from "./IRelationshipModule.sol"; import { IRelationshipProcessor } from "./RelationshipProcessors/IRelationshipProcessor.sol"; -contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, RelationshipTypeChecker { +abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlledUpgradeable, RelationshipTypeChecker { using ERC165CheckerUpgradeable for address; /// @custom:storage-location erc7201:story-protocol.relationship-module.storage @@ -40,7 +39,7 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, _disableInitializers(); } - function initialize(address accessControl) public initializer { + function __RelationshipModuleBase_init(address accessControl) public initializer { __AccessControlledUpgradeable_init(accessControl); } @@ -82,8 +81,9 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, return 0; } - function unrelate(RelationshipParams calldata params) external onlyRole(RELATIONSHIP_DISPUTER_ROLE) { + function unrelate(RelationshipParams calldata params) external { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + if ($.relConfigs[params.relationshipId].disputer != msg.sender) revert Unauthorized(); bytes32 key = _getRelationshipKey(params); if (!$.relationships[key]) revert NonExistingRelationship(); delete $.relationships[key]; @@ -118,7 +118,7 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, } } - function _getRelationshipKey(RelationshipParams calldata params) private pure returns (bytes32) { + function _getRelationshipKey(RelationshipParams calldata params) internal pure returns (bytes32) { return keccak256( abi.encode( params.sourceContract, @@ -131,7 +131,7 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, } /********* Setting Relationships *********/ - function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { + function _setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) internal { RelationshipConfig memory relConfig = _convertRelParams(params); RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); $.relConfigs[relationshipId] = relConfig; @@ -146,34 +146,32 @@ contract RelationshipModule is IRelationshipModule, AccessControlledUpgradeable, ); } + function _unsetConfig(bytes32 relationshipId) internal { + RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); + if ( + $.relConfigs[relationshipId].sourceIPAssetTypeMask == 0 + ) revert NonExistingRelationship(); + delete $.relConfigs[relationshipId]; + emit RelationshipConfigUnset(relationshipId); + } + function _convertRelParams(SetRelationshipConfigParams calldata params) private view returns(RelationshipConfig memory) { if (!params.processor.supportsInterface(type(IRelationshipProcessor).interfaceId)) revert UnsupportedInterface("IRelationshipProcessor"); if (params.timeConfig.maxTTL < params.timeConfig.minTTL) revert InvalidTTL(); + if (params.disputer == address(0)) revert ZeroAddress(); return RelationshipConfig( _convertToMask(params.sourceIPAssets, params.allowedExternalSource), _convertToMask(params.destIPAssets, params.allowedExternalDest), params.onlySameFranchise, IRelationshipProcessor(params.processor), + params.disputer, params.timeConfig ); } - function unsetConfig(bytes32 relationshipId) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { - RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); - if ( - $.relConfigs[relationshipId].sourceIPAssetTypeMask == 0 - ) revert NonExistingRelationship(); - delete $.relConfigs[relationshipId]; - emit RelationshipConfigUnset(relationshipId); - } - function config(bytes32 relationshipId) external view returns (RelationshipConfig memory) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); return $.relConfigs[relationshipId]; } - function _authorizeUpgrade( - address newImplementation - ) internal virtual override onlyRole(UPGRADER_ROLE) {} - } \ No newline at end of file diff --git a/test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol b/test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol new file mode 100644 index 00000000..4c4ede56 --- /dev/null +++ b/test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: BUSDL-1.1 +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import '../utils/ProxyHelper.sol'; +import "contracts/FranchiseRegistry.sol"; +import "contracts/access-control/AccessControlSingleton.sol"; +import "contracts/access-control/ProtocolRoles.sol"; +import "contracts/ip-assets/IPAssetRegistryFactory.sol"; +import "contracts/modules/relationships/ProtocolRelationshipModule.sol"; +import "contracts/IPAsset.sol"; +import "contracts/errors/General.sol"; +import "contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol"; + +contract ProtocolRelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { + + IPAssetRegistryFactory public factory; + IPAssetRegistry public ipAssetRegistry; + FranchiseRegistry public register; + ProtocolRelationshipModule public relationshipModule; + AccessControlSingleton acs; + PermissionlessRelationshipProcessor public RelationshipProcessor; + + address admin = address(123); + address relationshipManager = address(234); + address franchiseOwner = address(456); + + bytes32 relationship = keccak256("RELATIONSHIP"); + + function setUp() public { + factory = new IPAssetRegistryFactory(); + acs = AccessControlSingleton( + _deployUUPSProxy( + address(new AccessControlSingleton()), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), admin + ) + ) + ); + vm.prank(admin); + acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); + + address accessControl = address(acs); + + FranchiseRegistry impl = new FranchiseRegistry(address(factory)); + register = FranchiseRegistry( + _deployUUPSProxy( + address(impl), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), accessControl + ) + ) + ); + vm.startPrank(franchiseOwner); + (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); + ipAssetRegistry = IPAssetRegistry(ipAssets); + vm.stopPrank(); + relationshipModule = ProtocolRelationshipModule( + _deployUUPSProxy( + address(new ProtocolRelationshipModule(address(register))), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), address(acs) + ) + ) + ); + RelationshipProcessor = new PermissionlessRelationshipProcessor(address(relationshipModule)); + } + + function test_setProtocolLevelRelationship() public { + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.STORY; + IPAsset[] memory destIPAssets = new IPAsset[](2); + destIPAssets[0] = IPAsset.CHARACTER; + destIPAssets[1] = IPAsset.ART; + + IRelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + onlySameFranchise: true, + processor: address(RelationshipProcessor), + disputer: address(this), + timeConfig: IRelationshipModule.TimeConfig({ + minTTL: 0, + maxTTL: 0, + renewable: false + }) + }); + vm.prank(relationshipManager); + relationshipModule.setRelationshipConfig(relationship, params); + + IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + assertEq(config.sourceIPAssetTypeMask, 1 << (uint256(IPAsset.STORY) & 0xff)); + assertEq(config.destIPAssetTypeMask, 1 << (uint256(IPAsset.CHARACTER) & 0xff) | 1 << (uint256(IPAsset.ART) & 0xff) | (uint256(EXTERNAL_ASSET) << 248)); + assertTrue(config.onlySameFranchise); + // TODO: test for event + + } + + function test_revert_IfSettingProtocolLevelRelationshipUnauthorized() public { + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.STORY; + IPAsset[] memory destIPAssets = new IPAsset[](2); + destIPAssets[0] = IPAsset.CHARACTER; + destIPAssets[1] = IPAsset.ART; + + IRelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + onlySameFranchise: true, + processor: address(RelationshipProcessor), + disputer: address(this), + timeConfig: IRelationshipModule.TimeConfig({ + minTTL: 0, + maxTTL: 0, + renewable: false + }) + }); + vm.expectRevert(); + vm.prank(franchiseOwner); + relationshipModule.setRelationshipConfig(relationship, params); + } + +} + +contract ProtocolRelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { + + IPAssetRegistryFactory public factory; + IPAssetRegistry public ipAssetRegistry; + FranchiseRegistry public register; + ProtocolRelationshipModule public relationshipModule; + AccessControlSingleton acs; + PermissionlessRelationshipProcessor public RelationshipProcessor; + + address admin = address(123); + address relationshipManager = address(234); + address franchiseOwner = address(456); + + bytes32 relationship = keccak256("PROTOCOL_Relationship"); + + function setUp() public { + factory = new IPAssetRegistryFactory(); + acs = AccessControlSingleton( + _deployUUPSProxy( + address(new AccessControlSingleton()), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), admin + ) + ) + ); + vm.prank(admin); + acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); + address accessControl = address(acs); + + FranchiseRegistry impl = new FranchiseRegistry(address(factory)); + register = FranchiseRegistry( + _deployUUPSProxy( + address(impl), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), accessControl + ) + ) + ); + vm.startPrank(franchiseOwner); + (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); + ipAssetRegistry = IPAssetRegistry(ipAssets); + vm.stopPrank(); + relationshipModule = ProtocolRelationshipModule( + _deployUUPSProxy( + address(new ProtocolRelationshipModule(address(register))), + abi.encodeWithSelector( + bytes4(keccak256(bytes("initialize(address)"))), address(acs) + ) + ) + ); + RelationshipProcessor = new PermissionlessRelationshipProcessor(address(relationshipModule)); + IPAsset[] memory sourceIPAssets = new IPAsset[](1); + sourceIPAssets[0] = IPAsset.STORY; + IPAsset[] memory destIPAssets = new IPAsset[](1); + destIPAssets[0] = IPAsset.CHARACTER; + IRelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ + sourceIPAssets: sourceIPAssets, + allowedExternalSource: false, + destIPAssets: destIPAssets, + allowedExternalDest: true, + onlySameFranchise: true, + processor: address(RelationshipProcessor), + disputer: address(this), + timeConfig: IRelationshipModule.TimeConfig({ + minTTL: 0, + maxTTL: 0, + renewable: false + }) + }); + vm.prank(relationshipManager); + relationshipModule.setRelationshipConfig(relationship, params); + + } + + function test_unsetConfig() public { + vm.prank(relationshipManager); + relationshipModule.unsetConfig(relationship); + + IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + assertEq(config.sourceIPAssetTypeMask, 0); + assertEq(config.destIPAssetTypeMask, 0); + assertFalse(config.onlySameFranchise); + // TODO: test for event + } + + function test_revert_unsetRelationshipConfigNotAuthorized() public { + vm.expectRevert(); + vm.prank(franchiseOwner); + relationshipModule.unsetConfig(relationship); + } + +} diff --git a/test/foundry/relationships/RelationshipModule.Config.t.sol b/test/foundry/relationships/RelationshipModule.Config.t.sol index bf15c69a..9290861f 100644 --- a/test/foundry/relationships/RelationshipModule.Config.t.sol +++ b/test/foundry/relationships/RelationshipModule.Config.t.sol @@ -7,7 +7,7 @@ import "contracts/FranchiseRegistry.sol"; import "contracts/access-control/AccessControlSingleton.sol"; import "contracts/access-control/ProtocolRoles.sol"; import "contracts/ip-assets/IPAssetRegistryFactory.sol"; -import "contracts/modules/relationships/RelationshipModule.sol"; +import "./RelationshipModuleHarness.sol"; import "contracts/IPAsset.sol"; import "contracts/errors/General.sol"; import "contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol"; @@ -17,7 +17,7 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { IPAssetRegistryFactory public factory; IPAssetRegistry public ipAssetRegistry; FranchiseRegistry public register; - RelationshipModule public relationshipModule; + RelationshipModuleHarness public relationshipModule; AccessControlSingleton acs; PermissionlessRelationshipProcessor public RelationshipProcessor; @@ -38,8 +38,6 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { ) ); vm.prank(admin); - acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); - address accessControl = address(acs); FranchiseRegistry impl = new FranchiseRegistry(address(factory)); @@ -55,9 +53,9 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); ipAssetRegistry = IPAssetRegistry(ipAssets); vm.stopPrank(); - relationshipModule = RelationshipModule( + relationshipModule = RelationshipModuleHarness( _deployUUPSProxy( - address(new RelationshipModule(address(register))), + address(new RelationshipModuleHarness(address(register))), abi.encodeWithSelector( bytes4(keccak256(bytes("initialize(address)"))), address(acs) ) @@ -73,24 +71,24 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { destIPAssets[0] = IPAsset.CHARACTER; destIPAssets[1] = IPAsset.ART; - RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ + IRelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, processor: address(RelationshipProcessor), + disputer: address(this), timeConfig: IRelationshipModule.TimeConfig({ minTTL: 0, maxTTL: 0, renewable: false }) }); - assertTrue(acs.hasRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager)); vm.prank(relationshipManager); relationshipModule.setRelationshipConfig(relationship, params); - RelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); assertEq(config.sourceIPAssetTypeMask, 1 << (uint256(IPAsset.STORY) & 0xff)); assertEq(config.destIPAssetTypeMask, 1 << (uint256(IPAsset.CHARACTER) & 0xff) | 1 << (uint256(IPAsset.ART) & 0xff) | (uint256(EXTERNAL_ASSET) << 248)); assertTrue(config.onlySameFranchise); @@ -98,43 +96,19 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { } - function test_revert_IfSettingProtocolLevelRelationshipUnauthorized() public { - IPAsset[] memory sourceIPAssets = new IPAsset[](1); - sourceIPAssets[0] = IPAsset.STORY; - IPAsset[] memory destIPAssets = new IPAsset[](2); - destIPAssets[0] = IPAsset.CHARACTER; - destIPAssets[1] = IPAsset.ART; - - RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ - sourceIPAssets: sourceIPAssets, - allowedExternalSource: false, - destIPAssets: destIPAssets, - allowedExternalDest: true, - onlySameFranchise: true, - processor: address(RelationshipProcessor), - timeConfig: IRelationshipModule.TimeConfig({ - minTTL: 0, - maxTTL: 0, - renewable: false - }) - }); - vm.expectRevert(); - vm.prank(franchiseOwner); - relationshipModule.setRelationshipConfig(relationship, params); - } - function test_revert_IfMasksNotConfigured() public { IPAsset[] memory sourceIPAssets = new IPAsset[](1); sourceIPAssets[0] = IPAsset.UNDEFINED; IPAsset[] memory destIPAssets = new IPAsset[](2); - RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ + IRelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, processor: address(RelationshipProcessor), + disputer: address(this), timeConfig: IRelationshipModule.TimeConfig({ minTTL: 0, maxTTL: 0, @@ -153,7 +127,7 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { IPAssetRegistryFactory public factory; IPAssetRegistry public ipAssetRegistry; FranchiseRegistry public register; - RelationshipModule public relationshipModule; + RelationshipModuleHarness public relationshipModule; AccessControlSingleton acs; PermissionlessRelationshipProcessor public RelationshipProcessor; @@ -174,7 +148,6 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { ) ); vm.prank(admin); - acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); address accessControl = address(acs); @@ -191,9 +164,9 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); ipAssetRegistry = IPAssetRegistry(ipAssets); vm.stopPrank(); - relationshipModule = RelationshipModule( + relationshipModule = RelationshipModuleHarness( _deployUUPSProxy( - address(new RelationshipModule(address(register))), + address(new RelationshipModuleHarness(address(register))), abi.encodeWithSelector( bytes4(keccak256(bytes("initialize(address)"))), address(acs) ) @@ -204,13 +177,14 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { sourceIPAssets[0] = IPAsset.STORY; IPAsset[] memory destIPAssets = new IPAsset[](1); destIPAssets[0] = IPAsset.CHARACTER; - RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ + IRelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, processor: address(RelationshipProcessor), + disputer: address(this), timeConfig: IRelationshipModule.TimeConfig({ minTTL: 0, maxTTL: 0, @@ -226,18 +200,13 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { vm.prank(relationshipManager); relationshipModule.unsetConfig(relationship); - RelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); assertEq(config.sourceIPAssetTypeMask, 0); assertEq(config.destIPAssetTypeMask, 0); assertFalse(config.onlySameFranchise); // TODO: test for event } - function test_revert_unsetRelationshipConfigNotAuthorized() public { - vm.expectRevert(); - relationshipModule.unsetConfig(relationship); - } - function test_revert_unsetRelationshipConfigNonExistingRelationship() public { vm.prank(relationshipManager); vm.expectRevert(IRelationshipModule.NonExistingRelationship.selector); diff --git a/test/foundry/relationships/RelationshipModule.Relating.t.sol b/test/foundry/relationships/RelationshipModule.Relating.t.sol index 9e373a59..f099a468 100644 --- a/test/foundry/relationships/RelationshipModule.Relating.t.sol +++ b/test/foundry/relationships/RelationshipModule.Relating.t.sol @@ -7,7 +7,7 @@ import "contracts/FranchiseRegistry.sol"; import "contracts/access-control/AccessControlSingleton.sol"; import "contracts/access-control/ProtocolRoles.sol"; import "contracts/ip-assets/IPAssetRegistryFactory.sol"; -import "contracts/modules/relationships/RelationshipModule.sol"; +import "./RelationshipModuleHarness.sol"; import "contracts/IPAsset.sol"; import "contracts/errors/General.sol"; import "contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol"; @@ -26,7 +26,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { IPAssetRegistryFactory public factory; IPAssetRegistry public ipAssetRegistry; FranchiseRegistry public register; - RelationshipModule public relationshipModule; + RelationshipModuleHarness public relationshipModule; AccessControlSingleton acs; PermissionlessRelationshipProcessor public processor; @@ -53,7 +53,6 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { ) ); vm.prank(admin); - acs.grantRole(RELATIONSHIP_MANAGER_ROLE, relationshipManager); FranchiseRegistry impl = new FranchiseRegistry(address(factory)); register = FranchiseRegistry( @@ -68,9 +67,9 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { (uint256 id, address ipAssets) = register.registerFranchise("name", "symbol", "description"); ipAssetRegistry = IPAssetRegistry(ipAssets); - relationshipModule = RelationshipModule( + relationshipModule = RelationshipModuleHarness( _deployUUPSProxy( - address(new RelationshipModule(address(register))), + address(new RelationshipModuleHarness(address(register))), abi.encodeWithSelector( bytes4(keccak256(bytes("initialize(address)"))), address(acs) ) @@ -83,13 +82,14 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { destIPAssets[1] = IPAsset.ART; processor = new PermissionlessRelationshipProcessor(address(relationshipModule)); - RelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ + IRelationshipModule.SetRelationshipConfigParams memory params = IRelationshipModule.SetRelationshipConfigParams({ sourceIPAssets: sourceIPAssets, allowedExternalSource: false, destIPAssets: destIPAssets, allowedExternalDest: true, onlySameFranchise: true, processor: address(processor), + disputer: address(this), timeConfig: IRelationshipModule.TimeConfig(0, 0, false) }); vm.prank(relationshipManager); diff --git a/test/foundry/relationships/RelationshipModuleHarness.sol b/test/foundry/relationships/RelationshipModuleHarness.sol new file mode 100644 index 00000000..000baaab --- /dev/null +++ b/test/foundry/relationships/RelationshipModuleHarness.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.13; + +import { RelationshipModuleBase } from "contracts/modules/relationships/RelationshipModuleBase.sol"; + +contract RelationshipModuleHarness is RelationshipModuleBase { + + constructor(address _franchiseRegistry) RelationshipModuleBase(_franchiseRegistry) {} + + function initialize(address accessControl) public initializer { + __RelationshipModuleBase_init(accessControl); + } + + function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external { + _setRelationshipConfig(relationshipId, params); + } + + function unsetConfig(bytes32 relationshipId) external { + _unsetConfig(relationshipId); + } + + function _authorizeUpgrade( + address newImplementation + ) internal virtual override {} + +} \ No newline at end of file From 50adcc4d6c743daf5570b5f70770a89c88182943 Mon Sep 17 00:00:00 2001 From: Raul Date: Fri, 21 Jul 2023 16:54:27 +0200 Subject: [PATCH 24/30] multicall links --- contracts/modules/relationships/RelationshipModuleBase.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/modules/relationships/RelationshipModuleBase.sol b/contracts/modules/relationships/RelationshipModuleBase.sol index 2b8f40a4..c0404902 100644 --- a/contracts/modules/relationships/RelationshipModuleBase.sol +++ b/contracts/modules/relationships/RelationshipModuleBase.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.13; import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { ERC165CheckerUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165CheckerUpgradeable.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { AccessControlledUpgradeable } from "contracts/access-control/AccessControlledUpgradeable.sol"; import { ZeroAddress, UnsupportedInterface, Unauthorized } from "contracts/errors/General.sol"; import { FranchiseRegistry } from "contracts/FranchiseRegistry.sol"; @@ -11,7 +12,8 @@ import { RelationshipTypeChecker } from "./RelationshipTypeChecker.sol"; import { IRelationshipModule } from "./IRelationshipModule.sol"; import { IRelationshipProcessor } from "./RelationshipProcessors/IRelationshipProcessor.sol"; -abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlledUpgradeable, RelationshipTypeChecker { + +abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlledUpgradeable, RelationshipTypeChecker, Multicall { using ERC165CheckerUpgradeable for address; /// @custom:storage-location erc7201:story-protocol.relationship-module.storage From 5def3e88e79bd8bd8da079dbbca8c451835e96bc Mon Sep 17 00:00:00 2001 From: Raul Date: Mon, 24 Jul 2023 18:29:57 +0200 Subject: [PATCH 25/30] kingter comment 1 --- .../modules/relationships/IRelationshipModule.sol | 4 ++-- .../relationships/ProtocolRelationshipModule.sol | 4 ++-- .../modules/relationships/RelationshipModuleBase.sol | 4 ++-- .../ProtocolRelationshipModule.Config.t.sol | 10 +++++----- .../relationships/RelationshipModule.Config.t.sol | 10 +++++----- .../relationships/RelationshipModuleHarness.sol | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol index 3c062f35..6d1d0ad1 100644 --- a/contracts/modules/relationships/IRelationshipModule.sol +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -89,6 +89,6 @@ interface IRelationshipModule { function areTheyRelated(RelationshipParams calldata params) external view returns (bool); function isLinkExpired(RelationshipParams calldata params) external view returns (bool); function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external; - function unsetConfig(bytes32 relationshipId) external; - function config(bytes32 relationshipId) external view returns (RelationshipConfig memory); + function unsetRelationshipConfig(bytes32 relationshipId) external; + function relationshipConfig(bytes32 relationshipId) external view returns (RelationshipConfig memory); } \ No newline at end of file diff --git a/contracts/modules/relationships/ProtocolRelationshipModule.sol b/contracts/modules/relationships/ProtocolRelationshipModule.sol index c0d3154d..b4126bcc 100644 --- a/contracts/modules/relationships/ProtocolRelationshipModule.sol +++ b/contracts/modules/relationships/ProtocolRelationshipModule.sol @@ -17,8 +17,8 @@ contract ProtocolRelationshipModule is RelationshipModuleBase { _setRelationshipConfig(relationshipId, params); } - function unsetConfig(bytes32 relationshipId) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { - _unsetConfig(relationshipId); + function unsetRelationshipConfig(bytes32 relationshipId) external onlyRole(RELATIONSHIP_MANAGER_ROLE) { + _unsetRelationshipConfig(relationshipId); } diff --git a/contracts/modules/relationships/RelationshipModuleBase.sol b/contracts/modules/relationships/RelationshipModuleBase.sol index c0404902..f3f9dd9f 100644 --- a/contracts/modules/relationships/RelationshipModuleBase.sol +++ b/contracts/modules/relationships/RelationshipModuleBase.sol @@ -148,7 +148,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle ); } - function _unsetConfig(bytes32 relationshipId) internal { + function _unsetRelationshipConfig(bytes32 relationshipId) internal { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); if ( $.relConfigs[relationshipId].sourceIPAssetTypeMask == 0 @@ -171,7 +171,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle ); } - function config(bytes32 relationshipId) external view returns (RelationshipConfig memory) { + function relationshipConfig(bytes32 relationshipId) external view returns (RelationshipConfig memory) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); return $.relConfigs[relationshipId]; } diff --git a/test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol b/test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol index 4c4ede56..ee2a539c 100644 --- a/test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol +++ b/test/foundry/relationships/ProtocolRelationshipModule.Config.t.sol @@ -90,7 +90,7 @@ contract ProtocolRelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { vm.prank(relationshipManager); relationshipModule.setRelationshipConfig(relationship, params); - IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + IRelationshipModule.RelationshipConfig memory config = relationshipModule.relationshipConfig(relationship); assertEq(config.sourceIPAssetTypeMask, 1 << (uint256(IPAsset.STORY) & 0xff)); assertEq(config.destIPAssetTypeMask, 1 << (uint256(IPAsset.CHARACTER) & 0xff) | 1 << (uint256(IPAsset.ART) & 0xff) | (uint256(EXTERNAL_ASSET) << 248)); assertTrue(config.onlySameFranchise); @@ -200,11 +200,11 @@ contract ProtocolRelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { } - function test_unsetConfig() public { + function test_unsetRelationshipConfig() public { vm.prank(relationshipManager); - relationshipModule.unsetConfig(relationship); + relationshipModule.unsetRelationshipConfig(relationship); - IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + IRelationshipModule.RelationshipConfig memory config = relationshipModule.relationshipConfig(relationship); assertEq(config.sourceIPAssetTypeMask, 0); assertEq(config.destIPAssetTypeMask, 0); assertFalse(config.onlySameFranchise); @@ -214,7 +214,7 @@ contract ProtocolRelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { function test_revert_unsetRelationshipConfigNotAuthorized() public { vm.expectRevert(); vm.prank(franchiseOwner); - relationshipModule.unsetConfig(relationship); + relationshipModule.unsetRelationshipConfig(relationship); } } diff --git a/test/foundry/relationships/RelationshipModule.Config.t.sol b/test/foundry/relationships/RelationshipModule.Config.t.sol index 9290861f..87e84221 100644 --- a/test/foundry/relationships/RelationshipModule.Config.t.sol +++ b/test/foundry/relationships/RelationshipModule.Config.t.sol @@ -88,7 +88,7 @@ contract RelationshipModuleSetupRelationshipsTest is Test, ProxyHelper { vm.prank(relationshipManager); relationshipModule.setRelationshipConfig(relationship, params); - IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + IRelationshipModule.RelationshipConfig memory config = relationshipModule.relationshipConfig(relationship); assertEq(config.sourceIPAssetTypeMask, 1 << (uint256(IPAsset.STORY) & 0xff)); assertEq(config.destIPAssetTypeMask, 1 << (uint256(IPAsset.CHARACTER) & 0xff) | 1 << (uint256(IPAsset.ART) & 0xff) | (uint256(EXTERNAL_ASSET) << 248)); assertTrue(config.onlySameFranchise); @@ -196,11 +196,11 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { } - function test_unsetConfig() public { + function test_unsetRelationshipConfig() public { vm.prank(relationshipManager); - relationshipModule.unsetConfig(relationship); + relationshipModule.unsetRelationshipConfig(relationship); - IRelationshipModule.RelationshipConfig memory config = relationshipModule.config(relationship); + IRelationshipModule.RelationshipConfig memory config = relationshipModule.relationshipConfig(relationship); assertEq(config.sourceIPAssetTypeMask, 0); assertEq(config.destIPAssetTypeMask, 0); assertFalse(config.onlySameFranchise); @@ -210,7 +210,7 @@ contract RelationshipModuleUnsetRelationshipsTest is Test, ProxyHelper { function test_revert_unsetRelationshipConfigNonExistingRelationship() public { vm.prank(relationshipManager); vm.expectRevert(IRelationshipModule.NonExistingRelationship.selector); - relationshipModule.unsetConfig(keccak256("UNDEFINED_Relationship")); + relationshipModule.unsetRelationshipConfig(keccak256("UNDEFINED_Relationship")); } } diff --git a/test/foundry/relationships/RelationshipModuleHarness.sol b/test/foundry/relationships/RelationshipModuleHarness.sol index 000baaab..e9c1ec41 100644 --- a/test/foundry/relationships/RelationshipModuleHarness.sol +++ b/test/foundry/relationships/RelationshipModuleHarness.sol @@ -15,8 +15,8 @@ contract RelationshipModuleHarness is RelationshipModuleBase { _setRelationshipConfig(relationshipId, params); } - function unsetConfig(bytes32 relationshipId) external { - _unsetConfig(relationshipId); + function unsetRelationshipConfig(bytes32 relationshipId) external { + _unsetRelationshipConfig(relationshipId); } function _authorizeUpgrade( From 352570b61a9da9ed013f9d3602b74a5917b49910 Mon Sep 17 00:00:00 2001 From: Raul Date: Mon, 24 Jul 2023 18:31:50 +0200 Subject: [PATCH 26/30] kingter comment 2 --- .../modules/relationships/RelationshipModuleBase.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/modules/relationships/RelationshipModuleBase.sol b/contracts/modules/relationships/RelationshipModuleBase.sol index f3f9dd9f..820423df 100644 --- a/contracts/modules/relationships/RelationshipModuleBase.sol +++ b/contracts/modules/relationships/RelationshipModuleBase.sol @@ -19,7 +19,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle /// @custom:storage-location erc7201:story-protocol.relationship-module.storage struct RelationshipModuleStorage { mapping(bytes32 => bool) relationships; - mapping(bytes32 => uint256) relationshipEnds; + mapping(bytes32 => uint256) relationshipExpirations; mapping(bytes32 => RelationshipConfig) relConfigs; } @@ -73,10 +73,10 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle function _updateEndTime(bytes32 relKey, TimeConfig memory timeConfig, uint256 ttl) private returns (uint256) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); if (timeConfig.maxTTL != 0) { - uint256 endTime = $.relationshipEnds[relKey]; + uint256 endTime = $.relationshipExpirations[relKey]; if (endTime == 0 || timeConfig.renewable) { endTime = block.timestamp + ttl; - $.relationshipEnds[relKey] = endTime; + $.relationshipExpirations[relKey] = endTime; return endTime; } } @@ -99,7 +99,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle function isLinkExpired(RelationshipParams calldata params) public view returns (bool) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); - uint256 endTime = $.relationshipEnds[_getRelationshipKey(params)]; + uint256 endTime = $.relationshipExpirations[_getRelationshipKey(params)]; return endTime != 0 && endTime < block.timestamp; } From 66c824bda1c8b6dce3ba576bf333849eb03dab87 Mon Sep 17 00:00:00 2001 From: Raul Date: Mon, 24 Jul 2023 19:46:24 +0200 Subject: [PATCH 27/30] natspec --- .../relationships/IRelationshipModule.sol | 7 +- .../ProtocolRelationshipModule.sol | 8 ++ .../relationships/RelationshipModuleBase.sol | 88 ++++++++++++++++++- .../BaseRelationshipProcessor.sol | 13 +++ .../DstOwnerRelationshipProcessor.sol | 7 ++ .../IRelationshipProcessor.sol | 15 ++++ .../PermissionlessRelationshipProcessor.sol | 7 ++ .../SrcDstOwnerRelationshipProcessor.sol | 7 ++ .../SrcOwnerRelationshipProcessor.sol | 7 ++ .../relationships/RelationshipTypeChecker.sol | 21 +++++ deployment-31337.json | 14 +++ .../RelationshipModule.Relating.t.sol | 6 +- 12 files changed, 191 insertions(+), 9 deletions(-) create mode 100644 deployment-31337.json diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol index 6d1d0ad1..93c2ddcc 100644 --- a/contracts/modules/relationships/IRelationshipModule.sol +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.13; import { IRelationshipProcessor } from "./RelationshipProcessors/IRelationshipProcessor.sol"; import { IPAsset } from "contracts/IPAsset.sol"; + interface IRelationshipModule { event RelationSet( @@ -43,9 +44,9 @@ interface IRelationshipModule { error NonExistingRelationship(); error IntentAlreadyRegistered(); - error UnsupportedRelationshipSource(); - error UnsupportedRelationshipDestination(); - error CannotRelationshipToAnotherFranchise(); + error UnsupportedRelationshipSrc(); + error UnsupportedRelationshipDst(); + error CannotRelateToOtherFranchise(); error InvalidTTL(); error InvalidEndTimestamp(); diff --git a/contracts/modules/relationships/ProtocolRelationshipModule.sol b/contracts/modules/relationships/ProtocolRelationshipModule.sol index b4126bcc..8174d282 100644 --- a/contracts/modules/relationships/ProtocolRelationshipModule.sol +++ b/contracts/modules/relationships/ProtocolRelationshipModule.sol @@ -4,6 +4,14 @@ pragma solidity ^0.8.13; import { RelationshipModuleBase } from "./RelationshipModuleBase.sol"; import { UPGRADER_ROLE, RELATIONSHIP_MANAGER_ROLE, RELATIONSHIP_DISPUTER_ROLE } from "contracts/access-control/ProtocolRoles.sol"; +/** + * @title ProtocolRelationshipModule + * @dev Implementation of RelationshipModuleBase that allows relationship configs that will be used protocol wide. + * The meaning and parameters of the relationships are to be defined in Story Protocol Improvement proposals. + * Example: https://github.com/storyprotocol/protocol-contracts/issues/33 + * The relationship configs are set by the RELATIONSHIP_MANAGER_ROLE. + * Upgrades are done by the UPGRADER_ROLE. + */ contract ProtocolRelationshipModule is RelationshipModuleBase { constructor(address _franchiseRegistry) RelationshipModuleBase(_franchiseRegistry) {} diff --git a/contracts/modules/relationships/RelationshipModuleBase.sol b/contracts/modules/relationships/RelationshipModuleBase.sol index 820423df..0a096f4a 100644 --- a/contracts/modules/relationships/RelationshipModuleBase.sol +++ b/contracts/modules/relationships/RelationshipModuleBase.sol @@ -13,6 +13,22 @@ import { IRelationshipModule } from "./IRelationshipModule.sol"; import { IRelationshipProcessor } from "./RelationshipProcessors/IRelationshipProcessor.sol"; +/** + * @title RelationshipModuleBase + * @author Raul Martinez + * @notice The relationship module is responsible for managing relationships between IP assets, and/or between them and external ERC721 contracts. + * Relationships are defined by a relationship ID, which is a bytes32 value that represents a relationship type, for example (APPEARS_IN, CONTINUES_STORY, etc). + * The meaning of each relationship may have different side effects in Story Protocol, which could other modules could react on, and even legal implications if + * especified by the IPAsset licenses. + * To be able to relate two elements, a RelationshipConfig must be set for the relationship ID, which defines the following: + * - The IPAsset types that can be related as source and destination. + * - The processor that will be called when a relationship is set, which can be used to perform additional checks or actions (checking ownership, asking for fees...). + * - The disputer, which is the address that can unset a relationship. + * - The time config, which defines the maximum and minimum TTL (time to live) for the relationship, and if it can be renewed. (maxTTL = 0 means no expiration) + * - If the relationship can only be set between IPAssets of the same franchise, or it could link to IPAssets of other franchises. + * + * It's up to subclasses to define which addresses can set relationship configs. + */ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlledUpgradeable, RelationshipTypeChecker, Multicall { using ERC165CheckerUpgradeable for address; @@ -27,6 +43,10 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle bytes32 private constant _STORAGE_LOCATION = 0xd16687d5cf786234491b4cc484b2a64f24855aadee9b1b73824db1ed2840fd0b; FranchiseRegistry public immutable FRANCHISE_REGISTRY; + /** + * reverts if the TTL is not well configured for the relationship. + * @param params the relationship params + */ modifier onlyValidTTL(RelationshipParams calldata params) { RelationshipConfig storage relConfig = _getRelationshipModuleStorage().relConfigs[params.relationshipId]; if (relConfig.timeConfig.maxTTL != 0 && params.ttl != 0) { @@ -55,6 +75,14 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle } } + /** + * @notice Relates two IPAssets or an IPAsset and an external ERC721 contract. + * To not revert, the params must be valid according to the relationship config, and the processor must not revert. + * Processor can be used to perform additional checks or actions (checking ownership, asking for fees...). + * Processors returning false imply that the relationship is pending (multi step process), and the relationship will not be set yet. + * @param params the relationship params + * @param data optional data that will be passed to the processor + */ function relate(RelationshipParams calldata params, bytes calldata data) external onlyValidTTL(params) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); RelationshipConfig storage relConfig = $.relConfigs[params.relationshipId]; @@ -70,6 +98,13 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle } } + /** + * @notice Updates the end time of a relationship, if TimeConfig allows it. + * @param relKey the relationship key, given by _getRelationshipKey(params) + * @param timeConfig the relationship time config + * @param ttl the new ttl + * @return the new end time + */ function _updateEndTime(bytes32 relKey, TimeConfig memory timeConfig, uint256 ttl) private returns (uint256) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); if (timeConfig.maxTTL != 0) { @@ -83,6 +118,11 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle return 0; } + /** + * @notice Unrelates two IPAssets or an IPAsset and an external ERC721 contract. + * Only callable by the disputer of the relationship, as defined in the relationship config. + * @param params the relationship params + */ function unrelate(RelationshipParams calldata params) external { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); if ($.relConfigs[params.relationshipId].disputer != msg.sender) revert Unauthorized(); @@ -92,26 +132,46 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle emit RelationUnset(params.sourceContract, params.sourceId, params.destContract, params.destId, params.relationshipId); } + /** + * @notice Checks if two IPAssets or an IPAsset and an external ERC721 contract are related. + * @param params the relationship params + * @return true if they are related and the relationship has not expired, false otherwise + */ function areTheyRelated(RelationshipParams calldata params) external view returns (bool) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); return $.relationships[_getRelationshipKey(params)] && !isLinkExpired(params); } + /** + * @notice Checks if a relationship has expired. + * @param params the relationship params + * @return true if the relationship has expired, false if not expired or if it has no expiration + */ function isLinkExpired(RelationshipParams calldata params) public view returns (bool) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); uint256 endTime = $.relationshipExpirations[_getRelationshipKey(params)]; return endTime != 0 && endTime < block.timestamp; } + /** + * @notice validates the relationship params according to the relationship config. + * @param params the relationship params + * @param relConfig the relationship config + */ function _verifyRelationshipParams(RelationshipParams calldata params, RelationshipConfig memory relConfig) private view { if (relConfig.sourceIPAssetTypeMask == 0) revert NonExistingRelationship(); (bool sourceResult, bool sourceIsAssetRegistry) = _checkRelationshipNode(params.sourceContract, params.sourceId, relConfig.sourceIPAssetTypeMask); - if (!sourceResult) revert UnsupportedRelationshipSource(); + if (!sourceResult) revert UnsupportedRelationshipSrc(); (bool destResult, bool destIsAssetRegistry) = _checkRelationshipNode(params.destContract, params.destId, relConfig.destIPAssetTypeMask); - if (!destResult) revert UnsupportedRelationshipDestination(); - if(sourceIsAssetRegistry && destIsAssetRegistry && params.sourceContract != params.destContract && relConfig.onlySameFranchise) revert CannotRelationshipToAnotherFranchise(); + if (!destResult) revert UnsupportedRelationshipDst(); + if(sourceIsAssetRegistry && destIsAssetRegistry && params.sourceContract != params.destContract && relConfig.onlySameFranchise) revert CannotRelateToOtherFranchise(); } + /** + * @notice checks if an address is a valid SP IPAssetRegistry. + * @param ipAssetRegistry the address to check + * @return true if it's a valid SP IPAssetRegistry, false otherwise + */ function _isAssetRegistry(address ipAssetRegistry) internal virtual override view returns(bool) { try IIPAssetRegistry(ipAssetRegistry).franchiseId() returns (uint256 franchiseId) { return FRANCHISE_REGISTRY.ipAssetRegistryForId(franchiseId) == ipAssetRegistry; @@ -120,6 +180,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle } } + /// calculates the relationship key by keccak256 hashing srcContract, srcId, dstContract, dstId and relationshipId function _getRelationshipKey(RelationshipParams calldata params) internal pure returns (bytes32) { return keccak256( abi.encode( @@ -133,6 +194,12 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle } /********* Setting Relationships *********/ + + /** + * @notice Sets a relationship config for a relationship ID. + * @param relationshipId the relationship ID + * @param params the relationship config params + */ function _setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) internal { RelationshipConfig memory relConfig = _convertRelParams(params); RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); @@ -148,6 +215,10 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle ); } + /** + * @notice Unsets a relationship config for a relationship ID, reverts if it doesn't exist. + * @param relationshipId the relationship ID + */ function _unsetRelationshipConfig(bytes32 relationshipId) internal { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); if ( @@ -157,6 +228,16 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle emit RelationshipConfigUnset(relationshipId); } + /** + * @notice Converts the SetRelationshipConfigParams to a RelationshipConfig after validating them. + * @dev reverts if + * - the processor doesn't support IRelationshipProcessor interface + * - the TTL is not well configured. + * - the disputer is the zero address + * + * @param params the SetRelationshipConfigParams + * @return the RelationshipConfig + */ function _convertRelParams(SetRelationshipConfigParams calldata params) private view returns(RelationshipConfig memory) { if (!params.processor.supportsInterface(type(IRelationshipProcessor).interfaceId)) revert UnsupportedInterface("IRelationshipProcessor"); if (params.timeConfig.maxTTL < params.timeConfig.minTTL) revert InvalidTTL(); @@ -171,6 +252,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle ); } + /// returns a RelationshipConfig for the given relationshipId, or an empty one if it doesn't exist function relationshipConfig(bytes32 relationshipId) external view returns (RelationshipConfig memory) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); return $.relConfigs[relationshipId]; diff --git a/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol index 727bbfc1..94f09e48 100644 --- a/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/BaseRelationshipProcessor.sol @@ -6,6 +6,14 @@ import { ZeroAddress } from "contracts/errors/General.sol"; import { IRelationshipModule } from "../IRelationshipModule.sol"; import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +/** + * @title BaseRelationshipProcessor + * @dev Base contract for relationship processors. + * Relationship processors are used to process relationships between IP Assets before they are set. + * They are set per relationship config in a IRelationshipModule + * This base contracts implements ERC165 and checks if the caller is the relationship module. + * All relationship processors must inherit from this contract. + */ abstract contract BaseRelationshipProcessor is IRelationshipProcessor, ERC165 { address internal immutable _RELATIONSHIP_MODULE; @@ -16,11 +24,16 @@ abstract contract BaseRelationshipProcessor is IRelationshipProcessor, ERC165 { _RELATIONSHIP_MODULE = _relationshipModule; } + /** + * @inheritdoc IRelationshipProcessor + * @dev Checks if the caller is the relationship module and calls implementation. + */ function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external override returns(bool) { if(msg.sender != _RELATIONSHIP_MODULE) revert OnlyRelationshipModule(); return _processRelationship(params, data, caller); } + function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) internal virtual returns(bool); function supportsInterface( diff --git a/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol index b79519d3..a1b3a3f1 100644 --- a/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/DstOwnerRelationshipProcessor.sol @@ -6,10 +6,17 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { Unauthorized } from "contracts/errors/General.sol"; import { IRelationshipModule } from "../IRelationshipModule.sol"; +/** + * @title DstOwnerRelationshipProcessor + * @dev Relationship processor that checks if the caller (relationship setter) is the owner of the destination IP Asset. + */ contract DstRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + /** + * Returns true if the caller is the owner of the destination IP Asset, reverts otherwise. + */ function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override returns(bool) { if (IERC721(params.destContract).ownerOf(params.destId) != caller) { revert Unauthorized(); diff --git a/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol index f076ad55..81375384 100644 --- a/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/IRelationshipProcessor.sol @@ -3,6 +3,21 @@ pragma solidity ^0.8.13; import { IRelationshipModule } from "../IRelationshipModule.sol"; +/** + * @title IRelationshipProcessor + * @dev Interface for relationship processors. + * Relationship processors are used to process relationships between IP Assets before they are set. + * They are set per relationship config in a IRelationshipModule + */ interface IRelationshipProcessor { + + /** + * @dev Processes a relationship between two IP Assets before it is set. This can be use for validity checks, actions, etc. It must: + * - revert if the relationship is invalid + * - return true if the relationship is valid and the relationship should be set immediately in the relationship module. + * - return false if the relationship is valid but there is need for further processing. + * In this case, the relationship module will emit a RelationPendingProcessor event. + * This can be leveraged for multi-step relationship setting, e.g. for a relationship that requires approval from the destination IP Asset owner. + */ function processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata data, address caller) external returns(bool); } \ No newline at end of file diff --git a/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol index 3366d3e5..337c2c51 100644 --- a/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/PermissionlessRelationshipProcessor.sol @@ -4,10 +4,17 @@ pragma solidity ^0.8.13; import { BaseRelationshipProcessor } from "./BaseRelationshipProcessor.sol"; import { IRelationshipModule } from "../IRelationshipModule.sol"; +/** + * @title PermissionlessRelationshipProcessor + * @dev Relationship processor that always returns true. + */ contract PermissionlessRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + /** + * Returns true. + */ function _processRelationship(IRelationshipModule.RelationshipParams memory, bytes calldata, address) internal virtual override returns(bool) { return true; } diff --git a/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol index 5d7fd4c2..7a3efd1a 100644 --- a/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/SrcDstOwnerRelationshipProcessor.sol @@ -6,10 +6,17 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { Unauthorized } from "contracts/errors/General.sol"; import { IRelationshipModule } from "../IRelationshipModule.sol"; +/** + * @title SrcDstOwnerRelationshipProcessor + * @dev Relationship processor that checks if the caller (relationship setter) is the owner of the source and destination IP Assets. + */ contract SrcDstRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + /** + * Returns true if the caller is the owner of the source and destination IP Assets, reverts otherwise. + */ function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override returns(bool) { if ( IERC721(params.sourceContract).ownerOf(params.sourceId) != caller || diff --git a/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol b/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol index 819004ce..1f38b66d 100644 --- a/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol +++ b/contracts/modules/relationships/RelationshipProcessors/SrcOwnerRelationshipProcessor.sol @@ -6,10 +6,17 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { Unauthorized } from "contracts/errors/General.sol"; import { IRelationshipModule } from "../IRelationshipModule.sol"; +/** + * @title SrcOwnerRelationshipProcessor + * @dev Relationship processor that checks if the caller (relationship setter) is the owner of the source IP Asset. + */ contract SrcRelationshipProcessor is BaseRelationshipProcessor { constructor(address relationshipModule) BaseRelationshipProcessor(relationshipModule) {} + /** + * Returns true if the caller is the owner of the source IP Asset, reverts otherwise. + */ function _processRelationship(IRelationshipModule.RelationshipParams memory params, bytes calldata, address caller) internal view virtual override returns(bool) { if (IERC721(params.sourceContract).ownerOf(params.sourceId) != caller) { revert Unauthorized(); diff --git a/contracts/modules/relationships/RelationshipTypeChecker.sol b/contracts/modules/relationships/RelationshipTypeChecker.sol index 8c298df6..7fb9c63c 100644 --- a/contracts/modules/relationships/RelationshipTypeChecker.sol +++ b/contracts/modules/relationships/RelationshipTypeChecker.sol @@ -6,10 +6,22 @@ import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import { LibIPAssetId } from "contracts/ip-assets/LibIPAssetId.sol"; import { IIPAssetRegistry } from "contracts/ip-assets/IIPAssetRegistry.sol"; +/** + * @title RelationshipTypeChecker + * @dev Gives tools to check if the "endpoints" of a relationship are valid, according to the allowed asset types set in the relationship config. + */ abstract contract RelationshipTypeChecker { error InvalidIPAssetArray(); + /** + * @dev Checks if the source or destination type of a relationship is allowed by the relationship config. + * @param collection The address of the collection of the relationship endpoint + * @param id The id of the relationship endpoint + * @param assetTypeMask The asset type mask of the relationship config, which contains the allowed asset types and the external asset flag + * @return result Whether the relationship endpoint is valid + * @return isAssetRegistry Whether the relationship endpoint is a Story Protocol IP Asset Registry + */ function _checkRelationshipNode(address collection, uint256 id, uint256 assetTypeMask) internal view returns (bool result, bool isAssetRegistry) { if (IERC721(collection).ownerOf(id) == address(0)) return (false, false); isAssetRegistry = _isAssetRegistry(collection); @@ -21,8 +33,16 @@ abstract contract RelationshipTypeChecker { return (result, isAssetRegistry); } + /// must return true if the address is a Story Protocol IP Asset Registry function _isAssetRegistry(address ipAssetRegistry) internal virtual view returns(bool); + /** + * @dev converts an array of IPAssets types and the allows external flag to a mask, by setting the bits corresponding + * to the uint8 equivalent of the IPAsset types to 1. + * @param ipAssets The array of IPAsset types + * @param allowsExternal Whether the relationship config allows external (non SP ERC721) assets + * @return mask The mask representing the IPAsset types and the allows external flag + */ function _convertToMask(IPAsset[] calldata ipAssets, bool allowsExternal) internal pure returns (uint256) { if (ipAssets.length == 0) revert InvalidIPAssetArray(); uint256 mask = 0; @@ -39,6 +59,7 @@ abstract contract RelationshipTypeChecker { return mask; } + /// returns true if the asset type is supported by the mask, false otherwise function _supportsIPAssetType(uint256 mask, uint8 assetType) internal pure returns (bool) { return mask & (1 << (uint256(assetType) & 0xff)) != 0; } diff --git a/deployment-31337.json b/deployment-31337.json new file mode 100644 index 00000000..8aaa72c3 --- /dev/null +++ b/deployment-31337.json @@ -0,0 +1,14 @@ +{ + "AccessControlSingleton-Impl": "0xc4B957Cd61beB9b9afD76204b30683EDAaaB51Ec", + "AccessControlSingleton-Proxy": "0xFa2f6c96C30e7F652C9BD6fA4f1EF1D47a88C18f", + "DstOwnerRelationshipProcessor": "0x571F4b96Abc69429e1F112232BDe599160360b6B", + "FranchiseRegistry-Impl": "0x75e7d780d4Fd26f1787192d8939a9f69D723E79a", + "FranchiseRegistry-Proxy": "0xb84132FbB7a1f19987CE8536c9885392B932b05a", + "IPAssetRegistryFactory": "0x6cdBd1b486b8FBD4140e8cd6daAED05bE13eD914", + "PermissionlessRelationshipProcessor": "0x1705a2D33C95E22F627486d1151c034a851c14e0", + "ProtocolRelationshipModule-Impl": "0xE7c415001162206eF27d7cA29cc871f3c9eE6cf4", + "ProtocolRelationshipModule-Proxy": "0xC8751DBe333604f45b98f96125BAd88bedC5a021", + "SrcDstOwnerRelationshipProcessor": "0xdBBC352fF1aefB16be2a5982508fDE2070B20828", + "SrcOwnerRelationshipProcessor": "0x16Af8E10E1FcDdA9dF1935839bdED3Cf8C1b8A44", + "contracts": 9 +} \ No newline at end of file diff --git a/test/foundry/relationships/RelationshipModule.Relating.t.sol b/test/foundry/relationships/RelationshipModule.Relating.t.sol index f099a468..f66ef694 100644 --- a/test/foundry/relationships/RelationshipModule.Relating.t.sol +++ b/test/foundry/relationships/RelationshipModule.Relating.t.sol @@ -183,7 +183,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { IPAssetRegistry otherIPAssetRegistry = IPAssetRegistry(otherIPAssets); vm.prank(ipAssetOwner); uint256 otherId = otherIPAssetRegistry.createIPAsset(IPAsset.CHARACTER, "name", "description", "mediaUrl"); - vm.expectRevert(IRelationshipModule.CannotRelationshipToAnotherFranchise.selector); + vm.expectRevert(IRelationshipModule.CannotRelateToOtherFranchise.selector); relationshipModule.relate( IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], otherIPAssets, otherId, relationship, 0 @@ -195,7 +195,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { function test_revert_relateUnsupportedSource() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); - vm.expectRevert(IRelationshipModule.UnsupportedRelationshipSource.selector); + vm.expectRevert(IRelationshipModule.UnsupportedRelationshipSrc.selector); relationshipModule.relate( IRelationshipModule.RelationshipParams( address(ipAssetRegistry), wrongId, address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.CHARACTER)], relationship, 0 @@ -207,7 +207,7 @@ contract RelationshipModuleRelationshipTest is Test, ProxyHelper { function test_revert_relateUnsupportedDestination() public { vm.prank(ipAssetOwner); uint256 wrongId = ipAssetRegistry.createIPAsset(IPAsset.GROUP, "name", "description", "mediaUrl"); - vm.expectRevert(IRelationshipModule.UnsupportedRelationshipDestination.selector); + vm.expectRevert(IRelationshipModule.UnsupportedRelationshipDst.selector); relationshipModule.relate( IRelationshipModule.RelationshipParams( address(ipAssetRegistry), ipAssetIds[uint8(IPAsset.STORY)], address(ipAssetRegistry), wrongId, relationship, 0 From ee4ce7cd7b61f877368d0b59673bb6b26caeedc5 Mon Sep 17 00:00:00 2001 From: Raul Date: Tue, 25 Jul 2023 18:29:09 +0200 Subject: [PATCH 28/30] fix error --- contracts/ip-assets/IPAssetRegistry.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/ip-assets/IPAssetRegistry.sol b/contracts/ip-assets/IPAssetRegistry.sol index d1d6909f..05921624 100644 --- a/contracts/ip-assets/IPAssetRegistry.sol +++ b/contracts/ip-assets/IPAssetRegistry.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.13; //import "forge-std/console.sol"; import { IIPAssetRegistry } from "./IIPAssetRegistry.sol"; import { LibIPAssetId } from "./LibIPAssetId.sol"; -import { Unauthorized, ZeroAddress } from "../errors/General.sol"; +import { Unauthorized, ZeroAmount } from "../errors/General.sol"; import { IPAssetData } from "./data-access-modules/storage/IPAssetData.sol"; import { IPAsset } from "contracts/IPAsset.sol"; import { GroupDAM } from "./data-access-modules/group/GroupDAM.sol"; @@ -44,7 +44,7 @@ contract IPAssetRegistry is ) public initializer { __ERC721_init(_name, _symbol); __Multicall_init(); - if (_franchiseId == 0) revert ZeroAddress(); + if (_franchiseId == 0) revert ZeroAmount(); IPAssetRegistryStorage storage $ = _getIPAssetRegistryStorage(); $.franchiseId = _franchiseId; $.description = _description; From 10df5280ee3ddba00b9d4ec1254f7d608a9922c8 Mon Sep 17 00:00:00 2001 From: Raul Date: Tue, 25 Jul 2023 19:17:20 +0200 Subject: [PATCH 29/30] stray link reference --- contracts/modules/relationships/IRelationshipModule.sol | 2 +- contracts/modules/relationships/RelationshipModuleBase.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/modules/relationships/IRelationshipModule.sol b/contracts/modules/relationships/IRelationshipModule.sol index 93c2ddcc..bd580241 100644 --- a/contracts/modules/relationships/IRelationshipModule.sol +++ b/contracts/modules/relationships/IRelationshipModule.sol @@ -88,7 +88,7 @@ interface IRelationshipModule { function relate(RelationshipParams calldata params, bytes calldata data) external; function unrelate(RelationshipParams calldata params) external; function areTheyRelated(RelationshipParams calldata params) external view returns (bool); - function isLinkExpired(RelationshipParams calldata params) external view returns (bool); + function isRelationshipExpired(RelationshipParams calldata params) external view returns (bool); function setRelationshipConfig(bytes32 relationshipId, SetRelationshipConfigParams calldata params) external; function unsetRelationshipConfig(bytes32 relationshipId) external; function relationshipConfig(bytes32 relationshipId) external view returns (RelationshipConfig memory); diff --git a/contracts/modules/relationships/RelationshipModuleBase.sol b/contracts/modules/relationships/RelationshipModuleBase.sol index 0a096f4a..3c5daa64 100644 --- a/contracts/modules/relationships/RelationshipModuleBase.sol +++ b/contracts/modules/relationships/RelationshipModuleBase.sol @@ -139,7 +139,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle */ function areTheyRelated(RelationshipParams calldata params) external view returns (bool) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); - return $.relationships[_getRelationshipKey(params)] && !isLinkExpired(params); + return $.relationships[_getRelationshipKey(params)] && !isRelationshipExpired(params); } /** @@ -147,7 +147,7 @@ abstract contract RelationshipModuleBase is IRelationshipModule, AccessControlle * @param params the relationship params * @return true if the relationship has expired, false if not expired or if it has no expiration */ - function isLinkExpired(RelationshipParams calldata params) public view returns (bool) { + function isRelationshipExpired(RelationshipParams calldata params) public view returns (bool) { RelationshipModuleStorage storage $ = _getRelationshipModuleStorage(); uint256 endTime = $.relationshipExpirations[_getRelationshipKey(params)]; return endTime != 0 && endTime < block.timestamp; From 680ecbd28d991faa89b893b885335ff05410fb1a Mon Sep 17 00:00:00 2001 From: Ramarti Date: Tue, 25 Jul 2023 10:18:07 -0700 Subject: [PATCH 30/30] Update contracts/modules/relationships/ProtocolRelationshipModule.sol Co-authored-by: kingster-will <83567446+kingster-will@users.noreply.github.com> --- contracts/modules/relationships/ProtocolRelationshipModule.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/modules/relationships/ProtocolRelationshipModule.sol b/contracts/modules/relationships/ProtocolRelationshipModule.sol index 8174d282..77b5188c 100644 --- a/contracts/modules/relationships/ProtocolRelationshipModule.sol +++ b/contracts/modules/relationships/ProtocolRelationshipModule.sol @@ -7,7 +7,7 @@ import { UPGRADER_ROLE, RELATIONSHIP_MANAGER_ROLE, RELATIONSHIP_DISPUTER_ROLE } /** * @title ProtocolRelationshipModule * @dev Implementation of RelationshipModuleBase that allows relationship configs that will be used protocol wide. - * The meaning and parameters of the relationships are to be defined in Story Protocol Improvement proposals. + * The meaning and parameters of the relationships are to be defined in Story Protocol Improvement Proposals. * Example: https://github.com/storyprotocol/protocol-contracts/issues/33 * The relationship configs are set by the RELATIONSHIP_MANAGER_ROLE. * Upgrades are done by the UPGRADER_ROLE.