From 345bc15988e7ea5de2e032c46cfd96ebca81087e Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 10 Sep 2024 10:40:14 -0400 Subject: [PATCH 1/9] Forking over immutable OperatorAllowlistEnforced contract --- .../allowlist/IOperatorAllowlist.sol | 14 +++ .../allowlist/OperatorAllowlistEnforced.sol | 104 ++++++++++++++++++ dependecies/immutable/errors/Errors.sol | 59 ++++++++++ 3 files changed, 177 insertions(+) create mode 100644 dependecies/immutable/allowlist/IOperatorAllowlist.sol create mode 100644 dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol create mode 100644 dependecies/immutable/errors/Errors.sol diff --git a/dependecies/immutable/allowlist/IOperatorAllowlist.sol b/dependecies/immutable/allowlist/IOperatorAllowlist.sol new file mode 100644 index 00000000..8d0e9007 --- /dev/null +++ b/dependecies/immutable/allowlist/IOperatorAllowlist.sol @@ -0,0 +1,14 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.24; + +/** + * @notice Required interface of an OperatorAllowlist compliant contract + */ +interface IOperatorAllowlist { + /** + * @notice Returns true if an address is Allowlisted false otherwise + * @param target the address to be checked against the Allowlist + */ + function isAllowlisted(address target) external view returns (bool); +} diff --git a/dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol b/dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol new file mode 100644 index 00000000..7c585525 --- /dev/null +++ b/dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol @@ -0,0 +1,104 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache 2.0 +// slither-disable-start calls-loop +pragma solidity ^0.8.24; + +// Allowlist Registry +import {IOperatorAllowlist} from "./IOperatorAllowlist.sol"; + +// Errors +import {OperatorAllowlistEnforcementErrors} from "../errors/Errors.sol"; + +interface IERC165 { + + function supportsInterface(bytes4 interfaceId) external view returns (bool); + +} + +/* + OperatorAllowlistEnforced is an abstract contract that token contracts can inherit in order to set the + address of the OperatorAllowlist registry that it will interface with, so that the token contract may + enable the restriction of approvals and transfers to allowlisted users. + OperatorAllowlistEnforced is not designed to be upgradeable or extended. +*/ + +abstract contract OperatorAllowlistEnforced is OperatorAllowlistEnforcementErrors { + + /// ===== Events ===== + + /// @notice Emitted whenever the transfer Allowlist registry is updated + event OperatorAllowlistRegistryUpdated(address oldRegistry, address newRegistry); + + /// ===== Modifiers ===== + + /** + * @notice Internal function to validate an approval, according to whether the target is an EOA or Allowlisted + * @param targetApproval the address of the approval target to be validated + */ + modifier validateApproval(address targetApproval) { + // Check for: + // 1. approver is an EOA. Contract constructor is handled as transfers 'from' are blocked + // 2. approver is address or bytecode is allowlisted + if (msg.sender.code.length != 0 && !operatorAllowlist.isAllowlisted(msg.sender)) { + revert ApproverNotInAllowlist(msg.sender); + } + + // Check for: + // 1. approval target is an EOA + // 2. approval target address is Allowlisted or target address bytecode is Allowlisted + if (targetApproval.code.length != 0 && !operatorAllowlist.isAllowlisted(targetApproval)) { + revert ApproveTargetNotInAllowlist(targetApproval); + } + _; + } + + /** + * @notice Internal function to validate a transfer, according to whether the calling address, + * from address and to address is an EOA or Allowlisted + * @param from the address of the from target to be validated + * @param to the address of the to target to be validated + */ + modifier validateTransfer(address from, address to) { + // Check for: + // 1. caller is an EOA + // 2. caller is Allowlisted or is the calling address bytecode is Allowlisted + if ( + msg.sender != tx.origin // solhint-disable-line avoid-tx-origin + && !operatorAllowlist.isAllowlisted(msg.sender) + ) { + revert CallerNotInAllowlist(msg.sender); + } + + // Check for: + // 1. from is an EOA + // 2. from is Allowlisted or from address bytecode is Allowlisted + if (from.code.length != 0 && !operatorAllowlist.isAllowlisted(from)) { + revert TransferFromNotInAllowlist(from); + } + + // Check for: + // 1. to is an EOA + // 2. to is Allowlisted or to address bytecode is Allowlisted + if (to.code.length != 0 && !operatorAllowlist.isAllowlisted(to)) { + revert TransferToNotInAllowlist(to); + } + _; + } + + /// ===== External functions ===== + + /** + * @notice Internal function to set the operator allowlist the calling contract will interface with + * @param _operatorAllowlist the address of the Allowlist registry + */ + function _setOperatorAllowlistRegistry(address _operatorAllowlist) internal { + if (!IERC165(_operatorAllowlist).supportsInterface(type(IOperatorAllowlist).interfaceId)) { + revert AllowlistDoesNotImplementIOperatorAllowlist(); + } + + emit OperatorAllowlistRegistryUpdated(address(operatorAllowlist), _operatorAllowlist); + operatorAllowlist = IOperatorAllowlist(_operatorAllowlist); + } + +} +// slither-disable-end calls-loop diff --git a/dependecies/immutable/errors/Errors.sol b/dependecies/immutable/errors/Errors.sol new file mode 100644 index 00000000..17e48ee8 --- /dev/null +++ b/dependecies/immutable/errors/Errors.sol @@ -0,0 +1,59 @@ +//SPDX-License-Identifier: Apache 2.0 +pragma solidity ^0.8.24; + +interface IImmutableERC721Errors { + /// @dev Caller tried to mint an already burned token + error IImmutableERC721TokenAlreadyBurned(uint256 tokenId); + + /// @dev Caller tried to mint an already burned token + error IImmutableERC721SendingToZerothAddress(); + + /// @dev Caller tried to mint an already burned token + error IImmutableERC721MismatchedTransferLengths(); + + /// @dev Caller tried to mint a tokenid that is above the hybrid threshold + error IImmutableERC721IDAboveThreshold(uint256 tokenId); + + /// @dev Caller is not approved or owner + error IImmutableERC721NotOwnerOrOperator(uint256 tokenId); + + /// @dev Current token owner is not what was expected + error IImmutableERC721MismatchedTokenOwner(uint256 tokenId, address currentOwner); + + /// @dev Signer is zeroth address + error SignerCannotBeZerothAddress(); + + /// @dev Deadline exceeded for permit + error PermitExpired(); + + /// @dev Derived signature is invalid (EIP721 and EIP1271) + error InvalidSignature(); +} + +interface OperatorAllowlistEnforcementErrors { + /// @dev Error thrown when the operatorAllowlist address does not implement the IOperatorAllowlist interface + error AllowlistDoesNotImplementIOperatorAllowlist(); + + /// @dev Error thrown when calling address is not OperatorAllowlist + error CallerNotInAllowlist(address caller); + + /// @dev Error thrown when 'from' address is not OperatorAllowlist + error TransferFromNotInAllowlist(address from); + + /// @dev Error thrown when 'to' address is not OperatorAllowlist + error TransferToNotInAllowlist(address to); + + /// @dev Error thrown when approve target is not OperatorAllowlist + error ApproveTargetNotInAllowlist(address target); + + /// @dev Error thrown when approve target is not OperatorAllowlist + error ApproverNotInAllowlist(address approver); +} + +interface IImmutableERC1155Errors { + /// @dev Deadline exceeded for permit + error PermitExpired(); + + /// @dev Derived signature is invalid (EIP721 and EIP1271) + error InvalidSignature(); +} From 4a0a32c41457489f8ae6b820adc0af4dcdacf880 Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 2 Sep 2024 12:09:20 -0400 Subject: [PATCH 2/9] implemented ERC-721 version --- remappings.txt | 1 + .../immutable/ImmutableAllowlistERC721.sol | 163 ++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 src/module/token/immutable/ImmutableAllowlistERC721.sol diff --git a/remappings.txt b/remappings.txt index 413cecc2..86346b81 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,3 +5,4 @@ forge-std/=lib/forge-std/src/ @erc721a-upgradeable/=lib/ERC721A-Upgradeable/contracts/ @limitbreak/creator-token-standards/=lib/creator-token-standards/src/ @limitbreak/permit-c/=lib/PermitC/src/ +@imtbl/contracts/=lib/contracts.git/contracts/ diff --git a/src/module/token/immutable/ImmutableAllowlistERC721.sol b/src/module/token/immutable/ImmutableAllowlistERC721.sol new file mode 100644 index 00000000..4dc2fbe8 --- /dev/null +++ b/src/module/token/immutable/ImmutableAllowlistERC721.sol @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Module} from "../../../Module.sol"; +import {Role} from "../../../Role.sol"; + +import {BeforeApproveCallbackERC721} from "../../../callback/BeforeApproveCallbackERC721.sol"; +import {BeforeApproveForAllCallback} from "../../../callback/BeforeApproveForAllCallback.sol"; +import {BeforeTransferCallbackERC721} from "../../../callback/BeforeTransferCallbackERC721.sol"; +import {OperatorAllowlistEnforced} from "@imtbl/contracts/allowlist/OperatorAllowlistEnforced.sol"; + +library ImmutableAllowlistStorage { + + /// @custom:storage-location erc7201:token.immutableallowlist + bytes32 public constant IMMUTABLE_ALLOWLIST_STORAGE_POSITION = + keccak256(abi.encode(uint256(keccak256("token.immutableAllowlist.ERC721")) - 1)) & ~bytes32(uint256(0xff)); + + struct Data { + address operatorAllowlistRegistry; + } + + function data() internal pure returns (Data storage data_) { + bytes32 position = IMMUTABLE_ALLOWLIST_STORAGE_POSITION; + assembly { + data_.slot := position + } + } + +} + +contract ImmutableAllowlistERC721 is + Module, + BeforeApproveCallbackERC721, + BeforeApproveForAllCallback, + BeforeTransferCallbackERC721, + OperatorAllowlistEnforced +{ + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /// @notice Emitted when an unauthorized approval is attempted. + error OperatorAllowlistUnauthorizedApproval(address operator); + + /// @notice Emitted when an unauthorized transfer is attempted. + error OperatorAllowlistUnauthorizedTransfer(address from, address to, address operator); + + /// @notice Emitted when the operator allowlist is not set. + error OperatorAllowlistNotSet(); + + /*////////////////////////////////////////////////////////////// + MODULE CONFIG + //////////////////////////////////////////////////////////////*/ + + /// @notice Returns all implemented callback and module functions. + function getModuleConfig() external pure override returns (ModuleConfig memory config) { + config.callbackFunctions = new CallbackFunction[](3); + config.fallbackFunctions = new FallbackFunction[](2); + + config.callbackFunctions[0] = CallbackFunction(this.beforeApproveERC721.selector); + config.callbackFunctions[1] = CallbackFunction(this.beforeApproveForAll.selector); + config.callbackFunctions[2] = CallbackFunction(this.beforeTransferERC721.selector); + + config.fallbackFunctions[0] = + FallbackFunction({selector: this.setOperatorAllowlistRegistry.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[1] = + FallbackFunction({selector: this.getOperatorAllowlistRegistry.selector, permissionBits: 0}); + + config.requiredInterfaces = new bytes4[](1); + config.requiredInterfaces[0] = 0x80ac58cd; // ERC721 + + config.registerInstallationCallback = true; + } + + /*////////////////////////////////////////////////////////////// + MODIFIERS + //////////////////////////////////////////////////////////////*/ + + modifier isOperatorAllowlistSet() { + if (_immutableAllowlistStorage().operatorAllowlistRegistry == address(0)) { + revert OperatorAllowlistNotSet(); + } + _; + } + + /*////////////////////////////////////////////////////////////// + CALLBACK FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Callback function for ERC721.approve + function beforeApproveERC721(address _from, address _to, uint256 _tokenId, bool _approve) + external + override + isOperatorAllowlistSet + validateApproval(_to) + returns (bytes memory) + {} + + /// @notice Callback function for ERC721.setApprovalForAll + function beforeApproveForAll(address _from, address _to, bool _approved) + external + override + isOperatorAllowlistSet + validateApproval(_to) + returns (bytes memory) + {} + + /// @notice Callback function for ERC721.transferFrom/safeTransferFrom + function beforeTransferERC721(address _from, address _to, uint256 _tokenId) + external + override + isOperatorAllowlistSet + validateTransfer(_from, _to) + returns (bytes memory) + {} + + /// @dev Called by a Core into an Module during the installation of the Module. + function onInstall(bytes calldata data) external { + address registry = abi.decode(data, (address)); + _immutableAllowlistStorage().operatorAllowlistRegistry = registry; + } + + /// @dev Called by a Core into an Module during the uninstallation of the Module. + function onUninstall(bytes calldata data) external {} + + /*////////////////////////////////////////////////////////////// + Encode install / uninstall data + //////////////////////////////////////////////////////////////*/ + + /// @dev Returns bytes encoded install params, to be sent to `onInstall` function + function encodeBytesOnInstall(address operatorAllowlistRegistry) external pure returns (bytes memory) { + return abi.encode(operatorAllowlistRegistry); + } + + /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function + function encodeBytesOnUninstall() external pure returns (bytes memory) { + return ""; + } + + /*////////////////////////////////////////////////////////////// + FALLBACK FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Set the operator allowlist registry address + function setOperatorAllowlistRegistry(address newRegistry) external { + _immutableAllowlistStorage().operatorAllowlistRegistry = newRegistry; + } + + /// @notice Get the current operator allowlist registry address + function getOperatorAllowlistRegistry() external view returns (address) { + return _immutableAllowlistStorage().operatorAllowlistRegistry; + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function _immutableAllowlistStorage() internal pure returns (ImmutableAllowlistStorage.Data storage) { + return ImmutableAllowlistStorage.data(); + } + +} From 026f0c4976ca0654b411785ebfc84dcf4bd23b92 Mon Sep 17 00:00:00 2001 From: Stanley Date: Mon, 2 Sep 2024 12:15:06 -0400 Subject: [PATCH 3/9] implemented ERC-1155 version --- .../immutable/ImmutableAllowlistERC1155.sol | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 src/module/token/immutable/ImmutableAllowlistERC1155.sol diff --git a/src/module/token/immutable/ImmutableAllowlistERC1155.sol b/src/module/token/immutable/ImmutableAllowlistERC1155.sol new file mode 100644 index 00000000..63a75ecf --- /dev/null +++ b/src/module/token/immutable/ImmutableAllowlistERC1155.sol @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Module} from "../../../Module.sol"; +import {Role} from "../../../Role.sol"; + +import {BeforeApproveForAllCallback} from "../../../callback/BeforeApproveForAllCallback.sol"; + +import {BeforeBatchTransferCallbackERC1155} from "../../../callback/BeforeBatchTransferCallbackERC1155.sol"; +import {BeforeTransferCallbackERC1155} from "../../../callback/BeforeTransferCallbackERC1155.sol"; +import {OperatorAllowlistEnforced} from "@imtbl/contracts/allowlist/OperatorAllowlistEnforced.sol"; + +library ImmutableAllowlistStorage { + + /// @custom:storage-location erc7201:token.immutableallowlist + bytes32 public constant IMMUTABLE_ALLOWLIST_STORAGE_POSITION = + keccak256(abi.encode(uint256(keccak256("token.immutableAllowlist.ERC1155")) - 1)) & ~bytes32(uint256(0xff)); + + struct Data { + address operatorAllowlistRegistry; + } + + function data() internal pure returns (Data storage data_) { + bytes32 position = IMMUTABLE_ALLOWLIST_STORAGE_POSITION; + assembly { + data_.slot := position + } + } + +} + +contract ImmutableAllowlistERC1155 is + Module, + BeforeApproveForAllCallback, + BeforeTransferCallbackERC1155, + BeforeBatchTransferCallbackERC1155, + OperatorAllowlistEnforced +{ + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /// @notice Emitted when an unauthorized approval is attempted. + error OperatorAllowlistUnauthorizedApproval(address operator); + + /// @notice Emitted when an unauthorized transfer is attempted. + error OperatorAllowlistUnauthorizedTransfer(address from, address to, address operator); + + /// @notice Emitted when the operator allowlist is not set. + error OperatorAllowlistNotSet(); + + /*////////////////////////////////////////////////////////////// + MODULE CONFIG + //////////////////////////////////////////////////////////////*/ + + /// @notice Returns all implemented callback and module functions. + function getModuleConfig() external pure override returns (ModuleConfig memory config) { + config.callbackFunctions = new CallbackFunction[](3); + config.fallbackFunctions = new FallbackFunction[](2); + + config.callbackFunctions[1] = CallbackFunction(this.beforeApproveForAll.selector); + config.callbackFunctions[2] = CallbackFunction(this.beforeTransferERC1155.selector); + config.callbackFunctions[3] = CallbackFunction(this.beforeBatchTransferERC1155.selector); + + config.fallbackFunctions[0] = + FallbackFunction({selector: this.setOperatorAllowlistRegistry.selector, permissionBits: Role._MANAGER_ROLE}); + config.fallbackFunctions[1] = + FallbackFunction({selector: this.getOperatorAllowlistRegistry.selector, permissionBits: 0}); + + config.requiredInterfaces = new bytes4[](1); + config.requiredInterfaces[0] = 0x80ac58cd; // ERC1155 + + config.registerInstallationCallback = true; + } + + /*////////////////////////////////////////////////////////////// + MODIFIERS + //////////////////////////////////////////////////////////////*/ + + modifier isOperatorAllowlistSet() { + if (_immutableAllowlistStorage().operatorAllowlistRegistry == address(0)) { + revert OperatorAllowlistNotSet(); + } + _; + } + + /*////////////////////////////////////////////////////////////// + CALLBACK FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Callback function for ERC1155.setApprovalForAll + function beforeApproveForAll(address _from, address _to, bool _approved) + external + override + isOperatorAllowlistSet + validateApproval(_to) + returns (bytes memory) + {} + + /// @notice Callback function for ERC1155.transferFrom/safeTransferFrom + function beforeTransferERC1155(address _from, address _to, uint256 _id, uint256 _value) + external + override + isOperatorAllowlistSet + validateTransfer(_from, _to) + returns (bytes memory) + {} + + /// @notice Callback function for ERC1155.transferFrom/safeTransferFrom + function beforeBatchTransferERC1155(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values) + external + override + isOperatorAllowlistSet + validateTransfer(_from, _to) + returns (bytes memory) + {} + + /// @dev Called by a Core into an Module during the installation of the Module. + function onInstall(bytes calldata data) external { + address registry = abi.decode(data, (address)); + _immutableAllowlistStorage().operatorAllowlistRegistry = registry; + } + + /// @dev Called by a Core into an Module during the uninstallation of the Module. + function onUninstall(bytes calldata data) external {} + + /*////////////////////////////////////////////////////////////// + Encode install / uninstall data + //////////////////////////////////////////////////////////////*/ + + /// @dev Returns bytes encoded install params, to be sent to `onInstall` function + function encodeBytesOnInstall(address operatorAllowlistRegistry) external pure returns (bytes memory) { + return abi.encode(operatorAllowlistRegistry); + } + + /// @dev Returns bytes encoded uninstall params, to be sent to `onUninstall` function + function encodeBytesOnUninstall() external pure returns (bytes memory) { + return ""; + } + + /*////////////////////////////////////////////////////////////// + FALLBACK FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Set the operator allowlist registry address + function setOperatorAllowlistRegistry(address newRegistry) external { + _immutableAllowlistStorage().operatorAllowlistRegistry = newRegistry; + } + + /// @notice Get the current operator allowlist registry address + function getOperatorAllowlistRegistry() external view returns (address) { + return _immutableAllowlistStorage().operatorAllowlistRegistry; + } + + /*////////////////////////////////////////////////////////////// + INTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + function _immutableAllowlistStorage() internal pure returns (ImmutableAllowlistStorage.Data storage) { + return ImmutableAllowlistStorage.data(); + } + +} From 22ce6e88775f4f547204ea7adc1774e15a54c53d Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 10 Sep 2024 10:53:16 -0400 Subject: [PATCH 4/9] updated immutable modules to use the forked version --- .../allowlist/OperatorAllowlistEnforced.sol | 46 +++++++++++-- .../immutable/ImmutableAllowlistERC1155.sol | 65 +++---------------- .../immutable/ImmutableAllowlistERC721.sol | 58 ++--------------- 3 files changed, 56 insertions(+), 113 deletions(-) diff --git a/dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol b/dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol index 7c585525..463accff 100644 --- a/dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol +++ b/dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol @@ -9,6 +9,25 @@ import {IOperatorAllowlist} from "./IOperatorAllowlist.sol"; // Errors import {OperatorAllowlistEnforcementErrors} from "../errors/Errors.sol"; +library OperatorAllowlistEnforcedStorage { + + /// @custom:storage-location erc7201:operator.allowlist.enforced + bytes32 public constant OPERATOR_ALLOWLIST_ENFORCED_STORAGE_POSITION = + keccak256(abi.encode(uint256(keccak256("operator.allowlist.enforced")) - 1)) & ~bytes32(uint256(0xff)); + + struct Data { + address operatorAllowlist; + } + + function data() internal pure returns (Data storage data_) { + bytes32 position = OPERATOR_ALLOWLIST_ENFORCED_STORAGE_POSITION; + assembly { + data_.slot := position + } + } + +} + interface IERC165 { function supportsInterface(bytes4 interfaceId) external view returns (bool); @@ -36,17 +55,19 @@ abstract contract OperatorAllowlistEnforced is OperatorAllowlistEnforcementError * @param targetApproval the address of the approval target to be validated */ modifier validateApproval(address targetApproval) { + IOperatorAllowlist _operatorAllowlist = IOperatorAllowlist(_operatorAllowlistStorage().operatorAllowlist); + // Check for: // 1. approver is an EOA. Contract constructor is handled as transfers 'from' are blocked // 2. approver is address or bytecode is allowlisted - if (msg.sender.code.length != 0 && !operatorAllowlist.isAllowlisted(msg.sender)) { + if (msg.sender.code.length != 0 && !_operatorAllowlist.isAllowlisted(msg.sender)) { revert ApproverNotInAllowlist(msg.sender); } // Check for: // 1. approval target is an EOA // 2. approval target address is Allowlisted or target address bytecode is Allowlisted - if (targetApproval.code.length != 0 && !operatorAllowlist.isAllowlisted(targetApproval)) { + if (targetApproval.code.length != 0 && !_operatorAllowlist.isAllowlisted(targetApproval)) { revert ApproveTargetNotInAllowlist(targetApproval); } _; @@ -59,12 +80,14 @@ abstract contract OperatorAllowlistEnforced is OperatorAllowlistEnforcementError * @param to the address of the to target to be validated */ modifier validateTransfer(address from, address to) { + IOperatorAllowlist _operatorAllowlist = IOperatorAllowlist(_operatorAllowlistStorage().operatorAllowlist); + // Check for: // 1. caller is an EOA // 2. caller is Allowlisted or is the calling address bytecode is Allowlisted if ( msg.sender != tx.origin // solhint-disable-line avoid-tx-origin - && !operatorAllowlist.isAllowlisted(msg.sender) + && !_operatorAllowlist.isAllowlisted(msg.sender) ) { revert CallerNotInAllowlist(msg.sender); } @@ -72,14 +95,14 @@ abstract contract OperatorAllowlistEnforced is OperatorAllowlistEnforcementError // Check for: // 1. from is an EOA // 2. from is Allowlisted or from address bytecode is Allowlisted - if (from.code.length != 0 && !operatorAllowlist.isAllowlisted(from)) { + if (from.code.length != 0 && !_operatorAllowlist.isAllowlisted(from)) { revert TransferFromNotInAllowlist(from); } // Check for: // 1. to is an EOA // 2. to is Allowlisted or to address bytecode is Allowlisted - if (to.code.length != 0 && !operatorAllowlist.isAllowlisted(to)) { + if (to.code.length != 0 && !_operatorAllowlist.isAllowlisted(to)) { revert TransferToNotInAllowlist(to); } _; @@ -96,8 +119,17 @@ abstract contract OperatorAllowlistEnforced is OperatorAllowlistEnforcementError revert AllowlistDoesNotImplementIOperatorAllowlist(); } - emit OperatorAllowlistRegistryUpdated(address(operatorAllowlist), _operatorAllowlist); - operatorAllowlist = IOperatorAllowlist(_operatorAllowlist); + emit OperatorAllowlistRegistryUpdated(_operatorAllowlistStorage().operatorAllowlist, _operatorAllowlist); + _operatorAllowlistStorage().operatorAllowlist = _operatorAllowlist; + } + + /// @notice Get the current operator allowlist registry address + function operatorAllowlist() external view returns (address) { + return _operatorAllowlistStorage().operatorAllowlist; + } + + function _operatorAllowlistStorage() internal pure returns (OperatorAllowlistEnforcedStorage.Data storage) { + return OperatorAllowlistEnforcedStorage.data(); } } diff --git a/src/module/token/immutable/ImmutableAllowlistERC1155.sol b/src/module/token/immutable/ImmutableAllowlistERC1155.sol index 63a75ecf..75137596 100644 --- a/src/module/token/immutable/ImmutableAllowlistERC1155.sol +++ b/src/module/token/immutable/ImmutableAllowlistERC1155.sol @@ -4,30 +4,13 @@ pragma solidity ^0.8.20; import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; +import { + OperatorAllowlistEnforced, + OperatorAllowlistEnforcedStorage +} from "../../../../dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol"; import {BeforeApproveForAllCallback} from "../../../callback/BeforeApproveForAllCallback.sol"; - import {BeforeBatchTransferCallbackERC1155} from "../../../callback/BeforeBatchTransferCallbackERC1155.sol"; import {BeforeTransferCallbackERC1155} from "../../../callback/BeforeTransferCallbackERC1155.sol"; -import {OperatorAllowlistEnforced} from "@imtbl/contracts/allowlist/OperatorAllowlistEnforced.sol"; - -library ImmutableAllowlistStorage { - - /// @custom:storage-location erc7201:token.immutableallowlist - bytes32 public constant IMMUTABLE_ALLOWLIST_STORAGE_POSITION = - keccak256(abi.encode(uint256(keccak256("token.immutableAllowlist.ERC1155")) - 1)) & ~bytes32(uint256(0xff)); - - struct Data { - address operatorAllowlistRegistry; - } - - function data() internal pure returns (Data storage data_) { - bytes32 position = IMMUTABLE_ALLOWLIST_STORAGE_POSITION; - assembly { - data_.slot := position - } - } - -} contract ImmutableAllowlistERC1155 is Module, @@ -59,14 +42,13 @@ contract ImmutableAllowlistERC1155 is config.callbackFunctions = new CallbackFunction[](3); config.fallbackFunctions = new FallbackFunction[](2); - config.callbackFunctions[1] = CallbackFunction(this.beforeApproveForAll.selector); - config.callbackFunctions[2] = CallbackFunction(this.beforeTransferERC1155.selector); - config.callbackFunctions[3] = CallbackFunction(this.beforeBatchTransferERC1155.selector); + config.callbackFunctions[0] = CallbackFunction(this.beforeApproveForAll.selector); + config.callbackFunctions[1] = CallbackFunction(this.beforeTransferERC1155.selector); + config.callbackFunctions[2] = CallbackFunction(this.beforeBatchTransferERC1155.selector); config.fallbackFunctions[0] = FallbackFunction({selector: this.setOperatorAllowlistRegistry.selector, permissionBits: Role._MANAGER_ROLE}); - config.fallbackFunctions[1] = - FallbackFunction({selector: this.getOperatorAllowlistRegistry.selector, permissionBits: 0}); + config.fallbackFunctions[1] = FallbackFunction({selector: this.operatorAllowlist.selector, permissionBits: 0}); config.requiredInterfaces = new bytes4[](1); config.requiredInterfaces[0] = 0x80ac58cd; // ERC1155 @@ -74,17 +56,6 @@ contract ImmutableAllowlistERC1155 is config.registerInstallationCallback = true; } - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - - modifier isOperatorAllowlistSet() { - if (_immutableAllowlistStorage().operatorAllowlistRegistry == address(0)) { - revert OperatorAllowlistNotSet(); - } - _; - } - /*////////////////////////////////////////////////////////////// CALLBACK FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -93,7 +64,6 @@ contract ImmutableAllowlistERC1155 is function beforeApproveForAll(address _from, address _to, bool _approved) external override - isOperatorAllowlistSet validateApproval(_to) returns (bytes memory) {} @@ -102,7 +72,6 @@ contract ImmutableAllowlistERC1155 is function beforeTransferERC1155(address _from, address _to, uint256 _id, uint256 _value) external override - isOperatorAllowlistSet validateTransfer(_from, _to) returns (bytes memory) {} @@ -111,7 +80,6 @@ contract ImmutableAllowlistERC1155 is function beforeBatchTransferERC1155(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values) external override - isOperatorAllowlistSet validateTransfer(_from, _to) returns (bytes memory) {} @@ -119,7 +87,7 @@ contract ImmutableAllowlistERC1155 is /// @dev Called by a Core into an Module during the installation of the Module. function onInstall(bytes calldata data) external { address registry = abi.decode(data, (address)); - _immutableAllowlistStorage().operatorAllowlistRegistry = registry; + _setOperatorAllowlistRegistry(registry); } /// @dev Called by a Core into an Module during the uninstallation of the Module. @@ -145,20 +113,7 @@ contract ImmutableAllowlistERC1155 is /// @notice Set the operator allowlist registry address function setOperatorAllowlistRegistry(address newRegistry) external { - _immutableAllowlistStorage().operatorAllowlistRegistry = newRegistry; - } - - /// @notice Get the current operator allowlist registry address - function getOperatorAllowlistRegistry() external view returns (address) { - return _immutableAllowlistStorage().operatorAllowlistRegistry; - } - - /*////////////////////////////////////////////////////////////// - INTERNAL FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function _immutableAllowlistStorage() internal pure returns (ImmutableAllowlistStorage.Data storage) { - return ImmutableAllowlistStorage.data(); + _setOperatorAllowlistRegistry(newRegistry); } } diff --git a/src/module/token/immutable/ImmutableAllowlistERC721.sol b/src/module/token/immutable/ImmutableAllowlistERC721.sol index 4dc2fbe8..d47f90c9 100644 --- a/src/module/token/immutable/ImmutableAllowlistERC721.sol +++ b/src/module/token/immutable/ImmutableAllowlistERC721.sol @@ -4,29 +4,13 @@ pragma solidity ^0.8.20; import {Module} from "../../../Module.sol"; import {Role} from "../../../Role.sol"; +import { + OperatorAllowlistEnforced, + OperatorAllowlistEnforcedStorage +} from "../../../../dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol"; import {BeforeApproveCallbackERC721} from "../../../callback/BeforeApproveCallbackERC721.sol"; import {BeforeApproveForAllCallback} from "../../../callback/BeforeApproveForAllCallback.sol"; import {BeforeTransferCallbackERC721} from "../../../callback/BeforeTransferCallbackERC721.sol"; -import {OperatorAllowlistEnforced} from "@imtbl/contracts/allowlist/OperatorAllowlistEnforced.sol"; - -library ImmutableAllowlistStorage { - - /// @custom:storage-location erc7201:token.immutableallowlist - bytes32 public constant IMMUTABLE_ALLOWLIST_STORAGE_POSITION = - keccak256(abi.encode(uint256(keccak256("token.immutableAllowlist.ERC721")) - 1)) & ~bytes32(uint256(0xff)); - - struct Data { - address operatorAllowlistRegistry; - } - - function data() internal pure returns (Data storage data_) { - bytes32 position = IMMUTABLE_ALLOWLIST_STORAGE_POSITION; - assembly { - data_.slot := position - } - } - -} contract ImmutableAllowlistERC721 is Module, @@ -64,8 +48,7 @@ contract ImmutableAllowlistERC721 is config.fallbackFunctions[0] = FallbackFunction({selector: this.setOperatorAllowlistRegistry.selector, permissionBits: Role._MANAGER_ROLE}); - config.fallbackFunctions[1] = - FallbackFunction({selector: this.getOperatorAllowlistRegistry.selector, permissionBits: 0}); + config.fallbackFunctions[1] = FallbackFunction({selector: this.operatorAllowlist.selector, permissionBits: 0}); config.requiredInterfaces = new bytes4[](1); config.requiredInterfaces[0] = 0x80ac58cd; // ERC721 @@ -73,17 +56,6 @@ contract ImmutableAllowlistERC721 is config.registerInstallationCallback = true; } - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - - modifier isOperatorAllowlistSet() { - if (_immutableAllowlistStorage().operatorAllowlistRegistry == address(0)) { - revert OperatorAllowlistNotSet(); - } - _; - } - /*////////////////////////////////////////////////////////////// CALLBACK FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -92,7 +64,6 @@ contract ImmutableAllowlistERC721 is function beforeApproveERC721(address _from, address _to, uint256 _tokenId, bool _approve) external override - isOperatorAllowlistSet validateApproval(_to) returns (bytes memory) {} @@ -101,7 +72,6 @@ contract ImmutableAllowlistERC721 is function beforeApproveForAll(address _from, address _to, bool _approved) external override - isOperatorAllowlistSet validateApproval(_to) returns (bytes memory) {} @@ -110,7 +80,6 @@ contract ImmutableAllowlistERC721 is function beforeTransferERC721(address _from, address _to, uint256 _tokenId) external override - isOperatorAllowlistSet validateTransfer(_from, _to) returns (bytes memory) {} @@ -118,7 +87,7 @@ contract ImmutableAllowlistERC721 is /// @dev Called by a Core into an Module during the installation of the Module. function onInstall(bytes calldata data) external { address registry = abi.decode(data, (address)); - _immutableAllowlistStorage().operatorAllowlistRegistry = registry; + _setOperatorAllowlistRegistry(registry); } /// @dev Called by a Core into an Module during the uninstallation of the Module. @@ -144,20 +113,7 @@ contract ImmutableAllowlistERC721 is /// @notice Set the operator allowlist registry address function setOperatorAllowlistRegistry(address newRegistry) external { - _immutableAllowlistStorage().operatorAllowlistRegistry = newRegistry; - } - - /// @notice Get the current operator allowlist registry address - function getOperatorAllowlistRegistry() external view returns (address) { - return _immutableAllowlistStorage().operatorAllowlistRegistry; - } - - /*////////////////////////////////////////////////////////////// - INTERNAL FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function _immutableAllowlistStorage() internal pure returns (ImmutableAllowlistStorage.Data storage) { - return ImmutableAllowlistStorage.data(); + _setOperatorAllowlistRegistry(newRegistry); } } From 274c331e6b52d6e3493fe4013cd7cc4bf43d014a Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 10 Sep 2024 11:54:58 -0400 Subject: [PATCH 5/9] imported over test OperatorAllowlist --- .../test/allowlist/OperatorAllowlist.sol | 182 ++++++++++++++++++ src/Role.sol | 1 + 2 files changed, 183 insertions(+) create mode 100644 dependecies/immutable/test/allowlist/OperatorAllowlist.sol diff --git a/dependecies/immutable/test/allowlist/OperatorAllowlist.sol b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol new file mode 100644 index 00000000..5bc33f20 --- /dev/null +++ b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol @@ -0,0 +1,182 @@ +// Copyright Immutable Pty Ltd 2018 - 2023 +// SPDX-License-Identifier: Apache 2.0 +pragma solidity 0.8.19; + +// Access Control + +import {Role} from "../../../Role.sol"; +import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; + +// Interfaces +import {IOperatorAllowlist} from "../../allowlist/IOperatorAllowlist.sol"; + +// Interface to retrieve the implemention stored inside the Proxy contract +interface IProxy { + + // Returns the current implementation address used by the proxy contract + // solhint-disable-next-line func-name-mixedcase + function PROXY_getImplementation() external view returns (address); + +} + +interface IERC165 { + + function supportsInterface(bytes4 interfaceId) external view returns (bool); + +} + +/* + OperatorAllowlist is an implementation of a Allowlist registry, storing addresses and bytecode + which are allowed to be approved operators and execute transfers of interfacing token contracts (admin, ). + The registry will be a deployed contract that tokens may interface with and point to. + OperatorAllowlist is not designed to be upgradeable or extended. +*/ + +contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { + + /// @notice Mapping of Allowlisted addresses + mapping(address aContract => bool allowed) private addressAllowlist; + + /// @notice Mapping of Allowlisted implementation addresses + mapping(address impl => bool allowed) private addressImplementationAllowlist; + + /// @notice Mapping of Allowlisted bytecodes + mapping(bytes32 bytecodeHash => bool allowed) private bytecodeAllowlist; + + /// ===== Events ===== + + /// @notice Emitted when a target address is added or removed from the Allowlist + event AddressAllowlistChanged(address indexed target, bool added); + + /// @notice Emitted when a target smart contract wallet is added or removed from the Allowlist + event WalletAllowlistChanged(bytes32 indexed targetBytes, address indexed targetAddress, bool added); + + /// ===== Constructor ===== + + /** + * @notice Grants `_MANAGER_ROLE` to the supplied `admin` address + * @param admin the address to grant `_MANAGER_ROLE` to + */ + constructor(address admin) { + _grantRoles(admin, _MANAGER_ROLE); + } + + /// ===== External functions ===== + + /** + * @notice Add a target address to Allowlist + * @param addressTargets the addresses to be added to the allowlist + */ + function addAddressToAllowlist(address[] calldata addressTargets) external onlyRoles(_REGISTRAR_ROLE) { + for (uint256 i; i < addressTargets.length; i++) { + addressAllowlist[addressTargets[i]] = true; + emit AddressAllowlistChanged(addressTargets[i], true); + } + } + + /** + * @notice Remove a target address from Allowlist + * @param addressTargets the addresses to be removed from the allowlist + */ + function removeAddressFromAllowlist(address[] calldata addressTargets) external onlyRoles(_REGISTRAR_ROLE) { + for (uint256 i; i < addressTargets.length; i++) { + delete addressAllowlist[addressTargets[i]]; + emit AddressAllowlistChanged(addressTargets[i], false); + } + } + + /** + * @notice Add a smart contract wallet to the Allowlist. + * This will allowlist the proxy and implementation contract pair. + * First, the bytecode of the proxy is added to the bytecode allowlist. + * Second, the implementation address stored in the proxy is stored in the + * implementation address allowlist. + * @param walletAddr the wallet address to be added to the allowlist + */ + function addWalletToAllowlist(address walletAddr) external onlyRoles(_REGISTRAR_ROLE) { + // get bytecode of wallet + bytes32 codeHash; + // solhint-disable-next-line no-inline-assembly + assembly { + codeHash := extcodehash(walletAddr) + } + bytecodeAllowlist[codeHash] = true; + // get address of wallet module + address impl = IProxy(walletAddr).PROXY_getImplementation(); + addressImplementationAllowlist[impl] = true; + + emit WalletAllowlistChanged(codeHash, walletAddr, true); + } + + /** + * @notice Remove a smart contract wallet from the Allowlist + * This will remove the proxy bytecode hash and implementation contract address pair from the allowlist + * @param walletAddr the wallet address to be removed from the allowlist + */ + function removeWalletFromAllowlist(address walletAddr) external onlyRoles(_REGISTRAR_ROLE) { + // get bytecode of wallet + bytes32 codeHash; + // solhint-disable-next-line no-inline-assembly + assembly { + codeHash := extcodehash(walletAddr) + } + delete bytecodeAllowlist[codeHash]; + // get address of wallet module + address impl = IProxy(walletAddr).PROXY_getImplementation(); + delete addressImplementationAllowlist[impl]; + + emit WalletAllowlistChanged(codeHash, walletAddr, false); + } + + /** + * @notice Allows admin to grant `user` `_REGISTRAR_ROLE` role + * @param user the address that `_REGISTRAR_ROLE` will be granted to + */ + function grantRegistrarRole(address user) external onlyRoles(_MANAGER_ROLE) { + grantRoles(user, _REGISTRAR_ROLE); + } + + /** + * @notice Allows admin to revoke `_REGISTRAR_ROLE` role from `user` + * @param user the address that `_REGISTRAR_ROLE` will be revoked from + */ + function revokeRegistrarRole(address user) external onlyRoles(_MANAGER_ROLE) { + revokeRole(user, _REGISTRAR_ROLE); + } + + /// ===== View functions ===== + + /** + * @notice Returns true if an address is Allowlisted, false otherwise + * @param target the address that will be checked for presence in the allowlist + */ + function isAllowlisted(address target) external view override returns (bool) { + if (addressAllowlist[target]) { + return true; + } + + // Check if caller is a Allowlisted smart contract wallet + bytes32 codeHash; + // solhint-disable-next-line no-inline-assembly + assembly { + codeHash := extcodehash(target) + } + if (bytecodeAllowlist[codeHash]) { + // If wallet proxy bytecode is approved, check addr of implementation contract + address impl = IProxy(target).PROXY_getImplementation(); + + return addressImplementationAllowlist[impl]; + } + + return false; + } + + /** + * @notice ERC-165 interface support + * @param interfaceId The interface identifier, which is a 4-byte selector. + */ + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, AccessControl) returns (bool) { + return interfaceId == type(IOperatorAllowlist).interfaceId || super.supportsInterface(interfaceId); + } + +} diff --git a/src/Role.sol b/src/Role.sol index 4026a657..45248029 100644 --- a/src/Role.sol +++ b/src/Role.sol @@ -9,6 +9,7 @@ library Role { uint256 internal constant _MINTER_ROLE = 1 << 0; uint256 internal constant _MANAGER_ROLE = 1 << 1; + uint256 internal constant _REGISTRAR_ROLE = 1 << 2; uint256 internal constant _INSTALLER_ROLE = 1 << 255; From 643675a51472b7cc6d5c6f2efb69caeb1da908ae Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 10 Sep 2024 12:57:25 -0400 Subject: [PATCH 6/9] initial tests pass --- .../test/allowlist/OperatorAllowlist.sol | 42 +++---- .../immutable/ImmutableAllowlistERC721.t.sol | 115 ++++++++++++++++++ 2 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 test/module/immutable/ImmutableAllowlistERC721.t.sol diff --git a/dependecies/immutable/test/allowlist/OperatorAllowlist.sol b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol index 5bc33f20..3e9b96e9 100644 --- a/dependecies/immutable/test/allowlist/OperatorAllowlist.sol +++ b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol @@ -1,10 +1,10 @@ // Copyright Immutable Pty Ltd 2018 - 2023 // SPDX-License-Identifier: Apache 2.0 -pragma solidity 0.8.19; +pragma solidity ^0.8.24; // Access Control -import {Role} from "../../../Role.sol"; +import {Role} from "../../../../src/Role.sol"; import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; // Interfaces @@ -19,7 +19,7 @@ interface IProxy { } -interface IERC165 { +interface ERC165 { function supportsInterface(bytes4 interfaceId) external view returns (bool); @@ -32,7 +32,7 @@ interface IERC165 { OperatorAllowlist is not designed to be upgradeable or extended. */ -contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { +contract OperatorAllowlist is ERC165, OwnableRoles, IOperatorAllowlist { /// @notice Mapping of Allowlisted addresses mapping(address aContract => bool allowed) private addressAllowlist; @@ -54,11 +54,11 @@ contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { /// ===== Constructor ===== /** - * @notice Grants `_MANAGER_ROLE` to the supplied `admin` address - * @param admin the address to grant `_MANAGER_ROLE` to + * @notice Grants `Role._MANAGER_ROLE` to the supplied `admin` address + * @param admin the address to grant `Role._MANAGER_ROLE` to */ constructor(address admin) { - _grantRoles(admin, _MANAGER_ROLE); + _grantRoles(admin, Role._MANAGER_ROLE); } /// ===== External functions ===== @@ -67,7 +67,7 @@ contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { * @notice Add a target address to Allowlist * @param addressTargets the addresses to be added to the allowlist */ - function addAddressToAllowlist(address[] calldata addressTargets) external onlyRoles(_REGISTRAR_ROLE) { + function addAddressToAllowlist(address[] calldata addressTargets) external onlyRoles(Role._REGISTRAR_ROLE) { for (uint256 i; i < addressTargets.length; i++) { addressAllowlist[addressTargets[i]] = true; emit AddressAllowlistChanged(addressTargets[i], true); @@ -78,7 +78,7 @@ contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { * @notice Remove a target address from Allowlist * @param addressTargets the addresses to be removed from the allowlist */ - function removeAddressFromAllowlist(address[] calldata addressTargets) external onlyRoles(_REGISTRAR_ROLE) { + function removeAddressFromAllowlist(address[] calldata addressTargets) external onlyRoles(Role._REGISTRAR_ROLE) { for (uint256 i; i < addressTargets.length; i++) { delete addressAllowlist[addressTargets[i]]; emit AddressAllowlistChanged(addressTargets[i], false); @@ -93,7 +93,7 @@ contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { * implementation address allowlist. * @param walletAddr the wallet address to be added to the allowlist */ - function addWalletToAllowlist(address walletAddr) external onlyRoles(_REGISTRAR_ROLE) { + function addWalletToAllowlist(address walletAddr) external onlyRoles(Role._REGISTRAR_ROLE) { // get bytecode of wallet bytes32 codeHash; // solhint-disable-next-line no-inline-assembly @@ -113,7 +113,7 @@ contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { * This will remove the proxy bytecode hash and implementation contract address pair from the allowlist * @param walletAddr the wallet address to be removed from the allowlist */ - function removeWalletFromAllowlist(address walletAddr) external onlyRoles(_REGISTRAR_ROLE) { + function removeWalletFromAllowlist(address walletAddr) external onlyRoles(Role._REGISTRAR_ROLE) { // get bytecode of wallet bytes32 codeHash; // solhint-disable-next-line no-inline-assembly @@ -129,19 +129,19 @@ contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { } /** - * @notice Allows admin to grant `user` `_REGISTRAR_ROLE` role - * @param user the address that `_REGISTRAR_ROLE` will be granted to + * @notice Allows admin to grant `user` `Role._REGISTRAR_ROLE` role + * @param user the address that `Role._REGISTRAR_ROLE` will be granted to */ - function grantRegistrarRole(address user) external onlyRoles(_MANAGER_ROLE) { - grantRoles(user, _REGISTRAR_ROLE); + function grantRegistrarRole(address user) external onlyRoles(Role._MANAGER_ROLE) { + grantRoles(user, Role._REGISTRAR_ROLE); } /** - * @notice Allows admin to revoke `_REGISTRAR_ROLE` role from `user` - * @param user the address that `_REGISTRAR_ROLE` will be revoked from + * @notice Allows admin to revoke `Role._REGISTRAR_ROLE` role from `user` + * @param user the address that `Role._REGISTRAR_ROLE` will be revoked from */ - function revokeRegistrarRole(address user) external onlyRoles(_MANAGER_ROLE) { - revokeRole(user, _REGISTRAR_ROLE); + function revokeRegistrarRole(address user) external onlyRoles(Role._MANAGER_ROLE) { + revokeRoles(user, Role._REGISTRAR_ROLE); } /// ===== View functions ===== @@ -175,8 +175,8 @@ contract OperatorAllowlist is ERC165, AccessControl, IOperatorAllowlist { * @notice ERC-165 interface support * @param interfaceId The interface identifier, which is a 4-byte selector. */ - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, AccessControl) returns (bool) { - return interfaceId == type(IOperatorAllowlist).interfaceId || super.supportsInterface(interfaceId); + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165) returns (bool) { + return interfaceId == type(IOperatorAllowlist).interfaceId; } } diff --git a/test/module/immutable/ImmutableAllowlistERC721.t.sol b/test/module/immutable/ImmutableAllowlistERC721.t.sol new file mode 100644 index 00000000..1622c773 --- /dev/null +++ b/test/module/immutable/ImmutableAllowlistERC721.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import "lib/forge-std/src/console.sol"; + +import {Test} from "forge-std/Test.sol"; +import {Role} from "src/Role.sol"; + +// Target contract + +import {Module} from "src/Module.sol"; +import {ERC721Core} from "src/core/token/ERC721Core.sol"; + +import {ICore} from "src/interface/ICore.sol"; +import {IModuleConfig} from "src/interface/IModuleConfig.sol"; +import {ImmutableAllowlistERC721} from "src/module/token/immutable/ImmutableAllowlistERC721.sol"; + +import {OperatorAllowlistEnforced} from "dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol"; +import {OperatorAllowlistEnforcementErrors} from "dependecies/immutable/errors/Errors.sol"; +import {OperatorAllowlist} from "dependecies/immutable/test/allowlist/OperatorAllowlist.sol"; + +contract Core is ERC721Core { + + constructor( + string memory name, + string memory symbol, + string memory contractURI, + address owner, + address[] memory modules, + bytes[] memory moduleInstallData + ) ERC721Core(name, symbol, contractURI, owner, modules, moduleInstallData) {} + + // disable mint and approve callbacks for these tests + function _beforeMint(address to, uint256 startTokenId, uint256 quantity, bytes calldata data) internal override {} + +} + +contract TransferableERC721Test is Test { + + Core public core; + + ImmutableAllowlistERC721 public immutableAllowlistModule; + OperatorAllowlist public operatorAllowlist; + + address public owner = address(0x1); + address public actorOne = address(0x2); + address public actorTwo = address(0x3); + address public actorThree = address(0x4); + + event OperatorAllowlistRegistryUpdated(address oldRegistry, address newRegistry); + + function setUp() public { + address[] memory modules; + bytes[] memory moduleData; + + core = new Core("test", "TEST", "", owner, modules, moduleData); + immutableAllowlistModule = new ImmutableAllowlistERC721(); + operatorAllowlist = new OperatorAllowlist(owner); + + // install module + vm.startPrank(owner); + bytes memory encodedOperatorAllowlist = + immutableAllowlistModule.encodeBytesOnInstall(address(operatorAllowlist)); + core.installModule(address(immutableAllowlistModule), encodedOperatorAllowlist); + vm.stopPrank(); + + // mint tokens + core.mint(actorOne, 1, string(""), ""); // tokenId 0 + core.mint(actorTwo, 1, string(""), ""); // tokenId 1 + core.mint(actorThree, 1, string(""), ""); // tokenId 2 + + vm.prank(owner); + core.grantRoles(owner, Role._MANAGER_ROLE); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `setOperatorAllowlistRegistry` + //////////////////////////////////////////////////////////////*/ + + function test_state_setOperatorAllowlistRegistry() public { + OperatorAllowlist operatorAllowlist2 = new OperatorAllowlist(owner); + + vm.prank(owner); + vm.expectEmit(true, true, true, true); + emit OperatorAllowlistRegistryUpdated(address(operatorAllowlist), address(operatorAllowlist2)); + ImmutableAllowlistERC721(address(core)).setOperatorAllowlistRegistry(address(operatorAllowlist2)); + + assertEq(ImmutableAllowlistERC721(address(core)).operatorAllowlist(), address(operatorAllowlist2)); + } + + function test_revert_setOperatorAllowlistRegistry() public { + vm.prank(owner); + // should revert since the allowlist does not implement the IOperatorAllowlist interface + // and that it doesn't implement supportsInterface + vm.expectRevert(); + ImmutableAllowlistERC721(address(core)).setOperatorAllowlistRegistry(address(0x123)); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `setTransferable` + //////////////////////////////////////////////////////////////*/ + + /// @notice Callback function for ERC721.approve + function beforeApproveERC721(address _from, address _to, uint256 _tokenId, bool _approve) + external + returns (bytes memory) + {} + + /// @notice Callback function for ERC721.setApprovalForAll + function beforeApproveForAll(address _from, address _to, bool _approved) external returns (bytes memory) {} + + /// @notice Callback function for ERC721.transferFrom/safeTransferFrom + function beforeTransferERC721(address _from, address _to, uint256 _tokenId) external returns (bytes memory) {} + +} From 82b8c5996f9d37968378d28c2310eb7dccc14793 Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 10 Sep 2024 14:46:13 -0400 Subject: [PATCH 7/9] ERC721 tests pass --- .../test/allowlist/OperatorAllowlist.sol | 2 +- .../immutable/ImmutableAllowlistERC721.t.sol | 167 +++++++++++++++++- 2 files changed, 161 insertions(+), 8 deletions(-) diff --git a/dependecies/immutable/test/allowlist/OperatorAllowlist.sol b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol index 3e9b96e9..3ecaa0d0 100644 --- a/dependecies/immutable/test/allowlist/OperatorAllowlist.sol +++ b/dependecies/immutable/test/allowlist/OperatorAllowlist.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.24; // Access Control - import {Role} from "../../../../src/Role.sol"; import {OwnableRoles} from "@solady/auth/OwnableRoles.sol"; @@ -58,6 +57,7 @@ contract OperatorAllowlist is ERC165, OwnableRoles, IOperatorAllowlist { * @param admin the address to grant `Role._MANAGER_ROLE` to */ constructor(address admin) { + _initializeOwner(admin); _grantRoles(admin, Role._MANAGER_ROLE); } diff --git a/test/module/immutable/ImmutableAllowlistERC721.t.sol b/test/module/immutable/ImmutableAllowlistERC721.t.sol index 1622c773..3f09b74f 100644 --- a/test/module/immutable/ImmutableAllowlistERC721.t.sol +++ b/test/module/immutable/ImmutableAllowlistERC721.t.sol @@ -35,12 +35,36 @@ contract Core is ERC721Core { } -contract TransferableERC721Test is Test { +contract DummyContract { + + ERC721Core public immutable erc721Core; + + constructor(address payable _erc721Core) { + erc721Core = ERC721Core(_erc721Core); + } + + function approve(address _to, uint256 _tokenId) external { + erc721Core.approve(_to, _tokenId); + } + + function setApprovalForAll(address _operator) external { + erc721Core.setApprovalForAll(_operator, true); + } + + function transfer(address _to, uint256 _tokenId) external { + erc721Core.transferFrom(address(this), _to, _tokenId); + } + +} + +contract ImmutableAllowlistERC721Test is Test { Core public core; ImmutableAllowlistERC721 public immutableAllowlistModule; OperatorAllowlist public operatorAllowlist; + DummyContract public dummyContract1; + DummyContract public dummyContract2; address public owner = address(0x1); address public actorOne = address(0x2); @@ -55,6 +79,8 @@ contract TransferableERC721Test is Test { core = new Core("test", "TEST", "", owner, modules, moduleData); immutableAllowlistModule = new ImmutableAllowlistERC721(); + + vm.prank(owner); operatorAllowlist = new OperatorAllowlist(owner); // install module @@ -64,6 +90,14 @@ contract TransferableERC721Test is Test { core.installModule(address(immutableAllowlistModule), encodedOperatorAllowlist); vm.stopPrank(); + // set registrar role for owner + vm.prank(owner); + operatorAllowlist.grantRegistrarRole(owner); + + // deploy dummy contract + dummyContract1 = new DummyContract(payable(address(core))); + dummyContract2 = new DummyContract(payable(address(core))); + // mint tokens core.mint(actorOne, 1, string(""), ""); // tokenId 0 core.mint(actorTwo, 1, string(""), ""); // tokenId 1 @@ -73,6 +107,13 @@ contract TransferableERC721Test is Test { core.grantRoles(owner, Role._MANAGER_ROLE); } + function allowlist(address _target) internal { + address[] memory allowlist = new address[](1); + allowlist[0] = _target; + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + } + /*/////////////////////////////////////////////////////////////// Unit tests: `setOperatorAllowlistRegistry` //////////////////////////////////////////////////////////////*/ @@ -97,14 +138,126 @@ contract TransferableERC721Test is Test { } /*/////////////////////////////////////////////////////////////// - Unit tests: `setTransferable` + Unit tests: `beforeApproveERC721` //////////////////////////////////////////////////////////////*/ - /// @notice Callback function for ERC721.approve - function beforeApproveERC721(address _from, address _to, uint256 _tokenId, bool _approve) - external - returns (bytes memory) - {} + function test_state_beforeApproveERC721() public { + // passes when msg.sender is an EOA and targetApproval is an EOA + vm.prank(actorOne); + core.approve(actorTwo, 0); + + // set allowlist for dummy contract + address[] memory allowlist = new address[](3); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.startPrank(actorThree); + core.mint(actorThree, 2, string(""), ""); // tokenId 3 + core.transferFrom(actorThree, address(dummyContract1), 3); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.approve(address(dummyContract2), 3); + } + + function test_revert_beforeApproveERC721() public { + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.ApproveTargetNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.approve(address(dummyContract1), 0); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `beforeApproveForAll` + //////////////////////////////////////////////////////////////*/ + + function test_state_beforeApproveForAllERC721() public { + // passes when msg.sender is an EOA and targetApproval is an EOA + vm.prank(actorOne); + core.setApprovalForAll(actorTwo, true); + + // set allowlist for dummy contract + address[] memory allowlist = new address[](3); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.startPrank(actorThree); + core.mint(actorThree, 1, string(""), ""); // tokenId 3 + core.transferFrom(actorThree, address(dummyContract1), 3); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.setApprovalForAll(address(dummyContract2)); + } + + function test_revert_beforeApproveForAllERC721() public { + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.ApproveTargetNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.setApprovalForAll(address(dummyContract1), true); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `beforeTransferERC721` + //////////////////////////////////////////////////////////////*/ + + function test_state_beforeTransferERC721() public { + // set allowlist + address[] memory allowlist = new address[](5); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorOne); + allowlist[3] = address(actorTwo); + allowlist[4] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.prank(actorOne); + core.transferFrom(actorOne, actorTwo, 0); + + // passes when msg.sender is an EOA and targetApproval is a contract and is allowlisted + core.mint(actorThree, 1, string(""), ""); // tokenId 3 + vm.startPrank(actorThree); + core.transferFrom(actorThree, address(dummyContract1), 3); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.transfer(address(dummyContract2), 3); + } + + function test_revert_beforeTransferERC721() public { + // fails when msg.sender is not allowlisted + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector(OperatorAllowlistEnforcementErrors.CallerNotInAllowlist.selector, actorOne) + ); + core.transferFrom(actorOne, actorTwo, 0); + + // fails when target is not allowlisted + allowlist(actorOne); + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.TransferToNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.transferFrom(actorOne, address(dummyContract1), 0); + } /// @notice Callback function for ERC721.setApprovalForAll function beforeApproveForAll(address _from, address _to, bool _approved) external returns (bytes memory) {} From ea32b38c9f6faf45b792f2331e9d49ff668953a7 Mon Sep 17 00:00:00 2001 From: Stanley Date: Tue, 10 Sep 2024 15:17:18 -0400 Subject: [PATCH 8/9] ERC1155 tests pass --- remappings.txt | 1 - .../immutable/ImmutableAllowlistERC1155.sol | 2 +- .../immutable/ImmutableAllowlistERC1155.t.sol | 320 ++++++++++++++++++ .../immutable/ImmutableAllowlistERC721.t.sol | 6 - test/module/minting/ClaimableERC1155.t.sol | 2 - 5 files changed, 321 insertions(+), 10 deletions(-) create mode 100644 test/module/immutable/ImmutableAllowlistERC1155.t.sol diff --git a/remappings.txt b/remappings.txt index 86346b81..413cecc2 100644 --- a/remappings.txt +++ b/remappings.txt @@ -5,4 +5,3 @@ forge-std/=lib/forge-std/src/ @erc721a-upgradeable/=lib/ERC721A-Upgradeable/contracts/ @limitbreak/creator-token-standards/=lib/creator-token-standards/src/ @limitbreak/permit-c/=lib/PermitC/src/ -@imtbl/contracts/=lib/contracts.git/contracts/ diff --git a/src/module/token/immutable/ImmutableAllowlistERC1155.sol b/src/module/token/immutable/ImmutableAllowlistERC1155.sol index 75137596..1fcffaa4 100644 --- a/src/module/token/immutable/ImmutableAllowlistERC1155.sol +++ b/src/module/token/immutable/ImmutableAllowlistERC1155.sol @@ -51,7 +51,7 @@ contract ImmutableAllowlistERC1155 is config.fallbackFunctions[1] = FallbackFunction({selector: this.operatorAllowlist.selector, permissionBits: 0}); config.requiredInterfaces = new bytes4[](1); - config.requiredInterfaces[0] = 0x80ac58cd; // ERC1155 + config.requiredInterfaces[0] = 0xd9b67a26; // ERC1155 config.registerInstallationCallback = true; } diff --git a/test/module/immutable/ImmutableAllowlistERC1155.t.sol b/test/module/immutable/ImmutableAllowlistERC1155.t.sol new file mode 100644 index 00000000..b229d1d3 --- /dev/null +++ b/test/module/immutable/ImmutableAllowlistERC1155.t.sol @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import "lib/forge-std/src/console.sol"; + +import {Test} from "forge-std/Test.sol"; +import {Role} from "src/Role.sol"; + +// Target contract + +import {Module} from "src/Module.sol"; +import {ERC1155Core} from "src/core/token/ERC1155Core.sol"; + +import {ICore} from "src/interface/ICore.sol"; +import {IModuleConfig} from "src/interface/IModuleConfig.sol"; +import {ImmutableAllowlistERC1155} from "src/module/token/immutable/ImmutableAllowlistERC1155.sol"; + +import {OperatorAllowlistEnforced} from "dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol"; +import {OperatorAllowlistEnforcementErrors} from "dependecies/immutable/errors/Errors.sol"; +import {OperatorAllowlist} from "dependecies/immutable/test/allowlist/OperatorAllowlist.sol"; + +contract Core is ERC1155Core { + + constructor( + string memory name, + string memory symbol, + string memory contractURI, + address owner, + address[] memory modules, + bytes[] memory moduleInstallData + ) ERC1155Core(name, symbol, contractURI, owner, modules, moduleInstallData) {} + + // disable mint, approve and tokenId callbacks for these tests + function _beforeMint(address to, uint256 tokenId, uint256 value, bytes memory data) internal override {} + + function _updateTokenId(uint256 tokenId) internal override returns (uint256) { + return tokenId; + } + +} + +contract DummyContract { + + ERC1155Core public immutable erc1155Core; + + constructor(address payable _erc1155Core) { + erc1155Core = ERC1155Core(_erc1155Core); + } + + // Implement the IERC1155Receiver functions to accept ERC1155 tokens + + function onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes calldata data) + external + returns (bytes4) + { + return this.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address operator, + address from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external returns (bytes4) { + return this.onERC1155BatchReceived.selector; + } + + // Required to declare support for the ERC1155Receiver interface + function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { + return interfaceId == 0x4e2312e0; + } + + // Set approval for the operator to manage tokens + function setApprovalForAll(address _operator) external { + erc1155Core.setApprovalForAll(_operator, true); + } + + // Transfer a single token + function transfer(address _to, uint256 _tokenId) external { + erc1155Core.safeTransferFrom(address(this), _to, _tokenId, 1, ""); + } + + // Batch transfer multiple tokens + function batchTransfer(address _to, uint256[] calldata tokenIds, uint256[] calldata amounts) external { + erc1155Core.safeBatchTransferFrom(address(this), _to, tokenIds, amounts, ""); + } + +} + +contract ImmutableAllowlistERC1155Test is Test { + + Core public core; + + ImmutableAllowlistERC1155 public immutableAllowlistModule; + OperatorAllowlist public operatorAllowlist; + DummyContract public dummyContract1; + DummyContract public dummyContract2; + + address public owner = address(0x1); + address public actorOne = address(0x2); + address public actorTwo = address(0x3); + address public actorThree = address(0x4); + + event OperatorAllowlistRegistryUpdated(address oldRegistry, address newRegistry); + + function setUp() public { + address[] memory modules; + bytes[] memory moduleData; + + core = new Core("test", "TEST", "", owner, modules, moduleData); + immutableAllowlistModule = new ImmutableAllowlistERC1155(); + + vm.prank(owner); + operatorAllowlist = new OperatorAllowlist(owner); + + // install module + vm.startPrank(owner); + bytes memory encodedOperatorAllowlist = + immutableAllowlistModule.encodeBytesOnInstall(address(operatorAllowlist)); + core.installModule(address(immutableAllowlistModule), encodedOperatorAllowlist); + vm.stopPrank(); + + // set registrar role for owner + vm.prank(owner); + operatorAllowlist.grantRegistrarRole(owner); + + // deploy dummy contract + dummyContract1 = new DummyContract(payable(address(core))); + dummyContract2 = new DummyContract(payable(address(core))); + + // mint tokens + core.mint(actorOne, 0, 1, string(""), ""); // tokenId 0 + core.mint(actorTwo, 1, 1, string(""), ""); // tokenId 1 + core.mint(actorThree, 3, 1, string(""), ""); // tokenId 2 + + vm.prank(owner); + core.grantRoles(owner, Role._MANAGER_ROLE); + } + + function allowlist(address _target) internal { + address[] memory allowlist = new address[](1); + allowlist[0] = _target; + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `setOperatorAllowlistRegistry` + //////////////////////////////////////////////////////////////*/ + + function test_state_setOperatorAllowlistRegistry() public { + OperatorAllowlist operatorAllowlist2 = new OperatorAllowlist(owner); + + vm.prank(owner); + vm.expectEmit(true, true, true, true); + emit OperatorAllowlistRegistryUpdated(address(operatorAllowlist), address(operatorAllowlist2)); + ImmutableAllowlistERC1155(address(core)).setOperatorAllowlistRegistry(address(operatorAllowlist2)); + + assertEq(ImmutableAllowlistERC1155(address(core)).operatorAllowlist(), address(operatorAllowlist2)); + } + + function test_revert_setOperatorAllowlistRegistry() public { + vm.prank(owner); + // should revert since the allowlist does not implement the IOperatorAllowlist interface + // and that it doesn't implement supportsInterface + vm.expectRevert(); + ImmutableAllowlistERC1155(address(core)).setOperatorAllowlistRegistry(address(0x123)); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `beforeApproveForAll` + //////////////////////////////////////////////////////////////*/ + + function test_state_beforeApproveForAllERC1155() public { + // passes when msg.sender is an EOA and targetApproval is an EOA + vm.prank(actorOne); + core.setApprovalForAll(actorTwo, true); + + // set allowlist for dummy contract + address[] memory allowlist = new address[](3); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.startPrank(actorThree); + core.mint(actorThree, 3, 1, string(""), ""); // tokenId 3 + core.safeTransferFrom(actorThree, address(dummyContract1), 3, 1, ""); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.setApprovalForAll(address(dummyContract2)); + } + + function test_revert_beforeApproveForAllERC1155() public { + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.ApproveTargetNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.setApprovalForAll(address(dummyContract1), true); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `beforeTransferERC1155` + //////////////////////////////////////////////////////////////*/ + + function test_state_beforeTransferERC1155() public { + // set allowlist + address[] memory allowlist = new address[](5); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorOne); + allowlist[3] = address(actorTwo); + allowlist[4] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + vm.prank(actorOne); + core.safeTransferFrom(actorOne, actorTwo, 0, 1, ""); + + // passes when msg.sender is an EOA and targetApproval is a contract and is allowlisted + core.mint(actorThree, 3, 1, string(""), ""); // tokenId 3 + vm.startPrank(actorThree); + core.safeTransferFrom(actorThree, address(dummyContract1), 3, 1, ""); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + dummyContract1.transfer(address(dummyContract2), 3); + } + + function test_revert_beforeTransferERC1155() public { + // fails when msg.sender is not allowlisted + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector(OperatorAllowlistEnforcementErrors.CallerNotInAllowlist.selector, actorOne) + ); + core.safeTransferFrom(actorOne, actorTwo, 0, 1, ""); + + // fails when target is not allowlisted + allowlist(actorOne); + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.TransferToNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.safeTransferFrom(actorOne, address(dummyContract1), 0, 1, ""); + } + + /*/////////////////////////////////////////////////////////////// + Unit tests: `beforeTransferERC1155` + //////////////////////////////////////////////////////////////*/ + + function test_state_beforeBatchTransferERC1155() public { + // set allowlist + address[] memory allowlist = new address[](5); + allowlist[0] = address(dummyContract1); + allowlist[1] = address(dummyContract2); + allowlist[2] = address(actorOne); + allowlist[3] = address(actorTwo); + allowlist[4] = address(actorThree); + vm.prank(owner); + operatorAllowlist.addAddressToAllowlist(allowlist); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 0; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + vm.prank(actorOne); + core.safeBatchTransferFrom(actorOne, actorTwo, tokenIds, amounts, ""); + + // passes when msg.sender is an EOA and targetApproval is a contract and is allowlisted + core.mint(actorThree, 3, 1, string(""), ""); // tokenId 3 + vm.startPrank(actorThree); + core.safeTransferFrom(actorThree, address(dummyContract1), 3, 1, ""); + vm.stopPrank(); + + // passes when msg.sender is a contract and is allowlisted + // and when targetApproval is a contract and is allowlisted + uint256[] memory _tokenIds = new uint256[](1); + tokenIds[0] = 3; + uint256[] memory _amounts = new uint256[](1); + amounts[0] = 1; + dummyContract1.batchTransfer(address(dummyContract2), _tokenIds, _amounts); + } + + function test_revert_beforeBatchTransferERC1155() public { + // fails when msg.sender is not allowlisted + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 0; + uint256[] memory amounts = new uint256[](1); + amounts[0] = 1; + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector(OperatorAllowlistEnforcementErrors.CallerNotInAllowlist.selector, actorOne) + ); + core.safeBatchTransferFrom(actorOne, actorTwo, tokenIds, amounts, ""); + + // fails when target is not allowlisted + uint256[] memory _tokenIds = new uint256[](1); + tokenIds[0] = 3; + uint256[] memory _amounts = new uint256[](1); + amounts[0] = 1; + allowlist(actorOne); + vm.prank(actorOne); + vm.expectRevert( + abi.encodeWithSelector( + OperatorAllowlistEnforcementErrors.TransferToNotInAllowlist.selector, address(dummyContract1) + ) + ); + core.safeBatchTransferFrom(actorOne, address(dummyContract1), _tokenIds, _amounts, ""); + } + +} diff --git a/test/module/immutable/ImmutableAllowlistERC721.t.sol b/test/module/immutable/ImmutableAllowlistERC721.t.sol index 3f09b74f..1750299c 100644 --- a/test/module/immutable/ImmutableAllowlistERC721.t.sol +++ b/test/module/immutable/ImmutableAllowlistERC721.t.sol @@ -259,10 +259,4 @@ contract ImmutableAllowlistERC721Test is Test { core.transferFrom(actorOne, address(dummyContract1), 0); } - /// @notice Callback function for ERC721.setApprovalForAll - function beforeApproveForAll(address _from, address _to, bool _approved) external returns (bytes memory) {} - - /// @notice Callback function for ERC721.transferFrom/safeTransferFrom - function beforeTransferERC721(address _from, address _to, uint256 _tokenId) external returns (bytes memory) {} - } diff --git a/test/module/minting/ClaimableERC1155.t.sol b/test/module/minting/ClaimableERC1155.t.sol index c8107c0f..620cdfd9 100644 --- a/test/module/minting/ClaimableERC1155.t.sol +++ b/test/module/minting/ClaimableERC1155.t.sol @@ -293,8 +293,6 @@ contract ClaimableERC1155Test is Test { uid: bytes32("1") }); bytes memory sig = signMintRequest(claimRequest, permissionedActorPrivateKey); - console.log("permissoned actor address"); - console.logAddress(permissionedActor); uint256 balBefore = tokenRecipient.balance; assertEq(balBefore, 100 ether); From 8bdd6e0020c6e65363a8d7f0b412f4726ee574fd Mon Sep 17 00:00:00 2001 From: Stanley Date: Wed, 25 Sep 2024 08:33:12 -0400 Subject: [PATCH 9/9] removed dead file --- src/interface/ICrosschain.sol | 52 ----------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 src/interface/ICrosschain.sol diff --git a/src/interface/ICrosschain.sol b/src/interface/ICrosschain.sol deleted file mode 100644 index b8de6212..00000000 --- a/src/interface/ICrosschain.sol +++ /dev/null @@ -1,52 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -interface ICrosschain { - - /** - * @notice Sends a cross-chain transaction. - * @param _destinationChain The destination chain ID. - * @param _callAddress The address of the contract on the destination chain. - * @param _payload The payload to send to the destination chain. - * @param _extraArgs The extra arguments to pass - * @dev extraArgs may contain items such as token, amount, feeTokenAddress, receipient, gasLimit, etc - */ - function sendCrossChainTransaction( - uint64 _destinationChain, - address _callAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) external payable; - - /** - * @notice callback function for when a cross-chain transaction is sent. - * @param _destinationChain The destination chain ID. - * @param _callAddress The address of the contract on the destination chain. - * @param _payload The payload sent to the destination chain. - * @param _extraArgs The extra arguments sent to the callAddress on the destination chain. - */ - function onCrossChainTransactionSent( - uint64 _destinationChain, - address _callAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) internal; - - /** - * @notice callback function for when a cross-chain transaction is received. - * @param _sourceChain The source chain ID. - * @param _sourceAddress The address of the contract on the source chain. - * @param _payload The payload sent to the destination chain. - * @param _extraArgs The extra arguments sent to the callAddress on the destination chain. - */ - function onCrossChainTransactionReceived( - uint64 _sourceChain, - address _sourceAddress, - bytes calldata _payload, - bytes calldata _extraArgs - ) internal; - - function setRouter(address _router) external; - function getRouter() external view returns (address); - -}