diff --git a/src/module/token/transferable/CreatorTokenERC20.sol b/src/module/token/transferable/CreatorTokenERC20.sol index 93bb56a3..cb769077 100644 --- a/src/module/token/transferable/CreatorTokenERC20.sol +++ b/src/module/token/transferable/CreatorTokenERC20.sol @@ -3,7 +3,9 @@ pragma solidity ^0.8.0; import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; + import {BeforeTransferCallbackERC20} from "../../../callback/BeforeTransferCallbackERC20.sol"; +import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; import {ICreatorToken} from "@limitbreak/creator-token-standards/interfaces/ICreatorToken.sol"; @@ -34,6 +36,12 @@ library CreatorTokenStorage { contract CreatorTokenERC20 is Module, BeforeTransferCallbackERC20, ICreatorToken { + /*////////////////////////////////////////////////////////////// + CONSTANTS + //////////////////////////////////////////////////////////////*/ + + bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00; + /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ @@ -41,6 +49,9 @@ contract CreatorTokenERC20 is Module, BeforeTransferCallbackERC20, ICreatorToken /// @notice Revert with an error if the transfer validator is not valid error InvalidTransferValidatorContract(); + /// @notice Revert with an error if the transfer validator is not valid + error NotTransferValidator(); + /*////////////////////////////////////////////////////////////// EXTENSION CONFIG //////////////////////////////////////////////////////////////*/ @@ -48,7 +59,7 @@ contract CreatorTokenERC20 is Module, BeforeTransferCallbackERC20, ICreatorToken /// @notice Returns all implemented callback and extension functions. function getModuleConfig() external pure override returns (ModuleConfig memory config) { config.callbackFunctions = new CallbackFunction[](1); - config.fallbackFunctions = new FallbackFunction[](3); + config.fallbackFunctions = new FallbackFunction[](4); config.callbackFunctions[0] = CallbackFunction(this.beforeTransferERC20.selector); @@ -58,6 +69,7 @@ contract CreatorTokenERC20 is Module, BeforeTransferCallbackERC20, ICreatorToken FallbackFunction({selector: this.getTransferValidationFunction.selector, permissionBits: 0}); config.fallbackFunctions[2] = FallbackFunction({selector: this.setTransferValidator.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[3] = FallbackFunction({selector: this.hasRole.selector, permissionBits: 0}); } /*////////////////////////////////////////////////////////////// @@ -99,6 +111,16 @@ contract CreatorTokenERC20 is Module, BeforeTransferCallbackERC20, ICreatorToken _setTransferValidator(validator); } + function hasRole(bytes32 role, address account) external view returns (bool) { + if (msg.sender != _creatorTokenStorage().transferValidator) { + revert NotTransferValidator(); + } + 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 //////////////////////////////////////////////////////////////*/ diff --git a/test/module/transferable/CreatorTokenERC20.t.sol b/test/module/transferable/CreatorTokenERC20.t.sol index 9f4ef547..1a8e2a88 100644 --- a/test/module/transferable/CreatorTokenERC20.t.sol +++ b/test/module/transferable/CreatorTokenERC20.t.sol @@ -33,6 +33,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 CreatorTokenERC20Test is Test { ERC20Core public core; @@ -43,7 +68,8 @@ contract CreatorTokenERC20Test is Test { TransferToken public transferTokenContract; - ITransferValidator public mockTransferValidator; + CreatorTokenTransferValidator public mockTransferValidator; + uint8 TRANSFER_SECURITY_LEVEL_SEVEN = 7; uint256 ownerPrivateKey = 1; address public owner; @@ -120,7 +146,7 @@ contract CreatorTokenERC20Test is Test { vm.prank(owner); core.grantRoles(owner, Role._MINTER_ROLE); - mockTransferValidator = ITransferValidator(0x721C0078c2328597Ca70F5451ffF5A7B38D4E947); + mockTransferValidator = CreatorTokenTransferValidator(0x721C0078c2328597Ca70F5451ffF5A7B38D4E947); vm.etch(address(mockTransferValidator), TRANSFER_VALIDATOR_DEPLOYED_BYTECODE); } @@ -176,7 +202,7 @@ contract CreatorTokenERC20Test is Test { } _mintToken(); - assertEq(1, core.balanceOf(owner)); + assertEq(100, core.balanceOf(owner)); // set transfer validator vm.prank(owner); @@ -192,6 +218,61 @@ contract CreatorTokenERC20Test is Test { assertEq(0, core.balanceOf(permissionedActor)); } + /*/////////////////////////////////////////////////////////////// + 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); + CreatorTokenERC20(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); + CreatorTokenERC20(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 + ); + } + function _mintToken() internal { address saleRecipient = address(0x987);