Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immutable x integration #155

Open
wants to merge 9 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions dependecies/immutable/allowlist/IOperatorAllowlist.sol
Original file line number Diff line number Diff line change
@@ -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);
}
136 changes: 136 additions & 0 deletions dependecies/immutable/allowlist/OperatorAllowlistEnforced.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
// 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";

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);

}

/*
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) {
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)) {
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) {
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)
) {
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(_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();
}

}
// slither-disable-end calls-loop
59 changes: 59 additions & 0 deletions dependecies/immutable/errors/Errors.sol
Original file line number Diff line number Diff line change
@@ -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();
}
182 changes: 182 additions & 0 deletions dependecies/immutable/test/allowlist/OperatorAllowlist.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// Copyright Immutable Pty Ltd 2018 - 2023
// SPDX-License-Identifier: Apache 2.0
pragma solidity ^0.8.24;

// Access Control
import {Role} from "../../../../src/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 ERC165 {

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, OwnableRoles, 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 `Role._MANAGER_ROLE` to the supplied `admin` address
* @param admin the address to grant `Role._MANAGER_ROLE` to
*/
constructor(address admin) {
_initializeOwner(admin);
_grantRoles(admin, Role._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(Role._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(Role._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(Role._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(Role._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` `Role._REGISTRAR_ROLE` role
* @param user the address that `Role._REGISTRAR_ROLE` will be granted to
*/
function grantRegistrarRole(address user) external onlyRoles(Role._MANAGER_ROLE) {
grantRoles(user, Role._REGISTRAR_ROLE);
}

/**
* @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(Role._MANAGER_ROLE) {
revokeRoles(user, Role._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) returns (bool) {
return interfaceId == type(IOperatorAllowlist).interfaceId;
}

}
Loading