diff --git a/src/module/token/royalty/RoyaltyERC1155.sol b/src/module/token/royalty/RoyaltyERC1155.sol index 2b4958cf..befdd601 100644 --- a/src/module/token/royalty/RoyaltyERC1155.sol +++ b/src/module/token/royalty/RoyaltyERC1155.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; +import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; import {BeforeBatchTransferCallbackERC1155} from "../../../callback/BeforeBatchTransferCallbackERC1155.sol"; import {BeforeTransferCallbackERC1155} from "../../../callback/BeforeTransferCallbackERC1155.sol"; @@ -48,6 +49,12 @@ contract RoyaltyERC1155 is ICreatorToken { + /*////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////*/ + + bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00; + /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -80,7 +87,10 @@ contract RoyaltyERC1155 is error RoyaltyExceedsMaxBps(); /// @notice Revert with an error if the transfer validator is not valid - error InvalidTransferValidatorContract(); + error RoyaltyInvalidTransferValidatorContract(); + + /// @notice Revert with an error if the transfer validator is not valid + error RoyaltyNotTransferValidator(); /*////////////////////////////////////////////////////////////// MODULE CONFIG @@ -89,7 +99,7 @@ contract RoyaltyERC1155 is /// @notice Returns all implemented callback and module functions. function getModuleConfig() external pure virtual override returns (ModuleConfig memory config) { config.callbackFunctions = new CallbackFunction[](2); - config.fallbackFunctions = new FallbackFunction[](8); + config.fallbackFunctions = new FallbackFunction[](9); config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC1155.selector); config.callbackFunctions[1] = CallbackFunction(this.beforeBatchTransferERC1155.selector); @@ -103,11 +113,12 @@ contract RoyaltyERC1155 is FallbackFunction({selector: this.getTransferValidator.selector, permissionBits: 0}); config.fallbackFunctions[4] = FallbackFunction({selector: this.getTransferValidationFunction.selector, permissionBits: 0}); - config.fallbackFunctions[5] = - FallbackFunction({selector: this.setDefaultRoyaltyInfo.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[5] = FallbackFunction({selector: this.hasRole.selector, permissionBits: 0}); config.fallbackFunctions[6] = - FallbackFunction({selector: this.setRoyaltyInfoForToken.selector, permissionBits: Role._MANAGER_ROLE}); + FallbackFunction({selector: this.setDefaultRoyaltyInfo.selector, permissionBits: Role._MANAGER_ROLE}); config.fallbackFunctions[7] = + FallbackFunction({selector: this.setRoyaltyInfoForToken.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[8] = FallbackFunction({selector: this.setTransferValidator.selector, permissionBits: Role._MANAGER_ROLE}); config.requiredInterfaces = new bytes4[](1); @@ -250,6 +261,16 @@ contract RoyaltyERC1155 is _setTransferValidator(validator); } + function hasRole(bytes32 role, address account) external view returns (bool) { + if (msg.sender != _royaltyStorage().transferValidator) { + revert RoyaltyNotTransferValidator(); + } + if (role == DEFAULT_ACCESS_CONTROL_ADMIN_ROLE) { + return OwnableRoles(address(this)).hasAllRoles(account, Role._MANAGER_ROLE); + } + return OwnableRoles(address(this)).hasAllRoles(account, uint256(role)); + } + /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -268,7 +289,7 @@ contract RoyaltyERC1155 is bool isValidTransferValidator = validator.code.length > 0; if (validator != address(0) && !isValidTransferValidator) { - revert InvalidTransferValidatorContract(); + revert RoyaltyInvalidTransferValidatorContract(); } emit TransferValidatorUpdated(address(getTransferValidator()), validator); diff --git a/src/module/token/royalty/RoyaltyERC721.sol b/src/module/token/royalty/RoyaltyERC721.sol index c8b7c1f6..21c67bdf 100644 --- a/src/module/token/royalty/RoyaltyERC721.sol +++ b/src/module/token/royalty/RoyaltyERC721.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.20; import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; +import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; import {BeforeTransferCallbackERC721} from "../../../callback/BeforeTransferCallbackERC721.sol"; import {IInstallationCallback} from "../../../interface/IInstallationCallback.sol"; @@ -41,6 +42,12 @@ library RoyaltyStorage { contract RoyaltyERC721 is Module, IInstallationCallback, BeforeTransferCallbackERC721, ICreatorToken { + /*////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////*/ + + bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00; + /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -73,7 +80,10 @@ contract RoyaltyERC721 is Module, IInstallationCallback, BeforeTransferCallbackE error RoyaltyExceedsMaxBps(); /// @notice Revert with an error if the transfer validator is not valid - error InvalidTransferValidatorContract(); + error RoyaltyInvalidTransferValidatorContract(); + + /// @notice Revert with an error if the transfer validator is not valid + error RoyaltyNotTransferValidator(); /*////////////////////////////////////////////////////////////// MODULE CONFIG @@ -82,7 +92,7 @@ contract RoyaltyERC721 is Module, IInstallationCallback, BeforeTransferCallbackE /// @notice Returns all implemented callback and module functions. function getModuleConfig() external pure virtual override returns (ModuleConfig memory config) { config.callbackFunctions = new CallbackFunction[](1); - config.fallbackFunctions = new FallbackFunction[](8); + config.fallbackFunctions = new FallbackFunction[](9); config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC721.selector); @@ -95,11 +105,12 @@ contract RoyaltyERC721 is Module, IInstallationCallback, BeforeTransferCallbackE FallbackFunction({selector: this.getTransferValidator.selector, permissionBits: 0}); config.fallbackFunctions[4] = FallbackFunction({selector: this.getTransferValidationFunction.selector, permissionBits: 0}); - config.fallbackFunctions[5] = - FallbackFunction({selector: this.setDefaultRoyaltyInfo.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[5] = FallbackFunction({selector: this.hasRole.selector, permissionBits: 0}); config.fallbackFunctions[6] = - FallbackFunction({selector: this.setRoyaltyInfoForToken.selector, permissionBits: Role._MANAGER_ROLE}); + FallbackFunction({selector: this.setDefaultRoyaltyInfo.selector, permissionBits: Role._MANAGER_ROLE}); config.fallbackFunctions[7] = + FallbackFunction({selector: this.setRoyaltyInfoForToken.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[8] = FallbackFunction({selector: this.setTransferValidator.selector, permissionBits: Role._MANAGER_ROLE}); config.requiredInterfaces = new bytes4[](1); @@ -227,6 +238,16 @@ contract RoyaltyERC721 is Module, IInstallationCallback, BeforeTransferCallbackE _setTransferValidator(validator); } + function hasRole(bytes32 role, address account) external view returns (bool) { + if (msg.sender != _royaltyStorage().transferValidator) { + revert RoyaltyNotTransferValidator(); + } + if (role == DEFAULT_ACCESS_CONTROL_ADMIN_ROLE) { + return OwnableRoles(address(this)).hasAllRoles(account, Role._MANAGER_ROLE); + } + return OwnableRoles(address(this)).hasAllRoles(account, uint256(role)); + } + /*////////////////////////////////////////////////////////////// INTERNAL FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -245,7 +266,7 @@ contract RoyaltyERC721 is Module, IInstallationCallback, BeforeTransferCallbackE bool isValidTransferValidator = validator.code.length > 0; if (validator != address(0) && !isValidTransferValidator) { - revert InvalidTransferValidatorContract(); + revert RoyaltyInvalidTransferValidatorContract(); } emit TransferValidatorUpdated(address(getTransferValidator()), validator); diff --git a/test/module/royalty/RoyaltyERC1155.t.sol b/test/module/royalty/RoyaltyERC1155.t.sol index 960c3a44..7c81e742 100644 --- a/test/module/royalty/RoyaltyERC1155.t.sol +++ b/test/module/royalty/RoyaltyERC1155.t.sol @@ -41,6 +41,31 @@ contract TransferToken { } +struct CollectionSecurityPolicyV3 { + bool disableAuthorizationMode; + bool authorizersCannotSetWildcardOperators; + uint8 transferSecurityLevel; + uint120 listId; + bool enableAccountFreezingMode; + uint16 tokenType; +} + +interface CreatorTokenTransferValidator is ITransferValidator { + + function setTransferSecurityLevelOfCollection( + address collection, + uint8 transferSecurityLevel, + bool isTransferRestricted, + bool isTransferWithRestrictedRecipient, + bool isTransferWithRestrictedToken + ) external; + function getCollectionSecurityPolicy(address collection) + external + view + returns (CollectionSecurityPolicyV3 memory); + +} + contract RoyaltyERC1155Test is Test { ERC1155Core public core; @@ -49,7 +74,8 @@ contract RoyaltyERC1155Test is Test { MintableERC1155 public mintableModuleImplementation; TransferToken public transferTokenContract; - ITransferValidator public mockTransferValidator; + CreatorTokenTransferValidator public mockTransferValidator; + uint8 TRANSFER_SECURITY_LEVEL_SEVEN = 7; uint256 ownerPrivateKey = 1; address public owner; @@ -133,7 +159,7 @@ contract RoyaltyERC1155Test is Test { core.grantRoles(owner, Role._MINTER_ROLE); // set up transfer validator - mockTransferValidator = ITransferValidator(0x721C0078c2328597Ca70F5451ffF5A7B38D4E947); + mockTransferValidator = CreatorTokenTransferValidator(0x721C0078c2328597Ca70F5451ffF5A7B38D4E947); vm.etch(address(mockTransferValidator), TRANSFER_VALIDATOR_DEPLOYED_BYTECODE); } @@ -247,7 +273,7 @@ contract RoyaltyERC1155Test is Test { function test_revert_setTransferValidator_invalidContract() public { // attempt to set the transfer validator to an invalid contract vm.prank(owner); - vm.expectRevert(RoyaltyERC1155.InvalidTransferValidatorContract.selector); + vm.expectRevert(RoyaltyERC1155.RoyaltyInvalidTransferValidatorContract.selector); RoyaltyERC1155(address(core)).setTransferValidator(address(11_111)); } @@ -323,6 +349,61 @@ contract RoyaltyERC1155Test is Test { assertEq(0, core.balanceOf(permissionedActor, 1)); } + /*/////////////////////////////////////////////////////////////// + Unit tests: `setTransferPolicy` + //////////////////////////////////////////////////////////////*/ + + function test_setTransferSecurityLevel() public { + if (evmVersionHash != keccak256(abi.encode('evm_version = "cancun"'))) { + //skip test if evm version is not cancun + return; + } + + // set transfer validator + vm.prank(owner); + RoyaltyERC1155(address(core)).setTransferValidator(address(mockTransferValidator)); + + vm.prank(owner); + core.grantRoles(permissionedActor, Role._MANAGER_ROLE); + + vm.prank(permissionedActor); + mockTransferValidator.setTransferSecurityLevelOfCollection( + address(core), TRANSFER_SECURITY_LEVEL_SEVEN, true, false, false + ); + + assertEq( + mockTransferValidator.getCollectionSecurityPolicy(address(core)).transferSecurityLevel, + TRANSFER_SECURITY_LEVEL_SEVEN + ); + } + + function test_revert_setTransferSecurityLevel() public { + if (evmVersionHash != keccak256(abi.encode('evm_version = "cancun"'))) { + //skip test if evm version is not cancun + return; + } + vm.prank(owner); + core.grantRoles(permissionedActor, Role._MANAGER_ROLE); + + // revert due to msg.sender not being the transfer validator + vm.expectRevert(); + vm.prank(permissionedActor); + mockTransferValidator.setTransferSecurityLevelOfCollection( + address(core), TRANSFER_SECURITY_LEVEL_SEVEN, true, false, false + ); + + // set transfer validator + vm.prank(owner); + RoyaltyERC1155(address(core)).setTransferValidator(address(mockTransferValidator)); + + // revert due to incorrect permissions + vm.prank(unpermissionedActor); + vm.expectRevert(); + mockTransferValidator.setTransferSecurityLevelOfCollection( + address(core), TRANSFER_SECURITY_LEVEL_SEVEN, true, false, false + ); + } + /*/////////////////////////////////////////////////////////////// UTILITY FUNCTIONS //////////////////////////////////////////////////////////////*/ diff --git a/test/module/royalty/RoyaltyERC721.t.sol b/test/module/royalty/RoyaltyERC721.t.sol index f0a2368f..56da40c8 100644 --- a/test/module/royalty/RoyaltyERC721.t.sol +++ b/test/module/royalty/RoyaltyERC721.t.sol @@ -29,6 +29,31 @@ contract TransferToken { } +struct CollectionSecurityPolicyV3 { + bool disableAuthorizationMode; + bool authorizersCannotSetWildcardOperators; + uint8 transferSecurityLevel; + uint120 listId; + bool enableAccountFreezingMode; + uint16 tokenType; +} + +interface CreatorTokenTransferValidator is ITransferValidator { + + function setTransferSecurityLevelOfCollection( + address collection, + uint8 transferSecurityLevel, + bool isTransferRestricted, + bool isTransferWithRestrictedRecipient, + bool isTransferWithRestrictedToken + ) external; + function getCollectionSecurityPolicy(address collection) + external + view + returns (CollectionSecurityPolicyV3 memory); + +} + contract RoyaltyERC721Test is Test { ERC721Core public core; @@ -37,7 +62,8 @@ contract RoyaltyERC721Test is Test { MintableERC721 public mintablemoduleImplementation; TransferToken public transferTokenContract; - ITransferValidator public mockTransferValidator; + CreatorTokenTransferValidator public mockTransferValidator; + uint8 TRANSFER_SECURITY_LEVEL_SEVEN = 7; uint256 ownerPrivateKey = 1; address public owner; @@ -121,7 +147,7 @@ contract RoyaltyERC721Test is Test { core.grantRoles(owner, Role._MINTER_ROLE); // set up transfer validator - mockTransferValidator = ITransferValidator(0x721C0078c2328597Ca70F5451ffF5A7B38D4E947); + mockTransferValidator = CreatorTokenTransferValidator(0x721C0078c2328597Ca70F5451ffF5A7B38D4E947); vm.etch(address(mockTransferValidator), TRANSFER_VALIDATOR_DEPLOYED_BYTECODE); } @@ -235,7 +261,7 @@ contract RoyaltyERC721Test is Test { function test_revert_setTransferValidator_invalidContract() public { // attempt to set the transfer validator to an invalid contract vm.prank(owner); - vm.expectRevert(RoyaltyERC721.InvalidTransferValidatorContract.selector); + vm.expectRevert(RoyaltyERC721.RoyaltyInvalidTransferValidatorContract.selector); RoyaltyERC721(address(core)).setTransferValidator(address(11_111)); } @@ -280,6 +306,61 @@ contract RoyaltyERC721Test is Test { assertEq(owner, core.ownerOf(0)); } + /*/////////////////////////////////////////////////////////////// + Unit tests: `setTransferPolicy` + //////////////////////////////////////////////////////////////*/ + + function test_setTransferSecurityLevel() public { + if (evmVersionHash != keccak256(abi.encode('evm_version = "cancun"'))) { + //skip test if evm version is not cancun + return; + } + + // set transfer validator + vm.prank(owner); + RoyaltyERC721(address(core)).setTransferValidator(address(mockTransferValidator)); + + vm.prank(owner); + core.grantRoles(permissionedActor, Role._MANAGER_ROLE); + + vm.prank(permissionedActor); + mockTransferValidator.setTransferSecurityLevelOfCollection( + address(core), TRANSFER_SECURITY_LEVEL_SEVEN, true, false, false + ); + + assertEq( + mockTransferValidator.getCollectionSecurityPolicy(address(core)).transferSecurityLevel, + TRANSFER_SECURITY_LEVEL_SEVEN + ); + } + + function test_revert_setTransferSecurityLevel() public { + if (evmVersionHash != keccak256(abi.encode('evm_version = "cancun"'))) { + //skip test if evm version is not cancun + return; + } + vm.prank(owner); + core.grantRoles(permissionedActor, Role._MANAGER_ROLE); + + // revert due to msg.sender not being the transfer validator + vm.expectRevert(); + vm.prank(permissionedActor); + mockTransferValidator.setTransferSecurityLevelOfCollection( + address(core), TRANSFER_SECURITY_LEVEL_SEVEN, true, false, false + ); + + // set transfer validator + vm.prank(owner); + RoyaltyERC721(address(core)).setTransferValidator(address(mockTransferValidator)); + + // revert due to incorrect permissions + vm.prank(unpermissionedActor); + vm.expectRevert(); + mockTransferValidator.setTransferSecurityLevelOfCollection( + address(core), TRANSFER_SECURITY_LEVEL_SEVEN, true, false, false + ); + } + /*/////////////////////////////////////////////////////////////// UTILITY FUNCTIONS //////////////////////////////////////////////////////////////*/