Skip to content

Commit

Permalink
[gms-1363] Add OALUpgradeable to be used behind proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
jasonzwli committed Jan 17, 2024
1 parent 70debe5 commit 89df76c
Show file tree
Hide file tree
Showing 6 changed files with 490 additions and 5 deletions.
189 changes: 189 additions & 0 deletions contracts/allowlist/OperatorAllowlistUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// Copyright Immutable Pty Ltd 2018 - 2023
// SPDX-License-Identifier: Apache 2.0
pragma solidity 0.8.19;

// Access Control
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";

// Introspection
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";

// Interfaces
import {IOperatorAllowlist} from "./IOperatorAllowlist.sol";

// Interface to retrieve the implemention stored inside the Proxy contract
interface IProxy {
// Returns the current implementation address used by the proxy contract
function PROXY_getImplementation() external view returns (address);
}

/*
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 (e.g. ERC721/ERC1155).
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 OperatorAllowlistUpgradeable is ERC165, AccessControlUpgradeable, UUPSUpgradeable, IOperatorAllowlist {
/// ===== State Variables =====

/// @notice Only REGISTRAR_ROLE can invoke white listing registration and removal
bytes32 public constant REGISTRAR_ROLE = bytes32("REGISTRAR_ROLE");

/// @notice Only UPGRADE_ROLE can upgrade the contract
bytes32 public constant UPGRADE_ROLE = bytes32("UPGRADE_ROLE");

/// @notice Mapping of Allowlisted addresses
mapping(address => bool) private addressAllowlist;

/// @notice Mapping of Allowlisted implementation addresses
mapping(address => bool) private addressImplementationAllowlist;

/// @notice Mapping of Allowlisted bytecodes
mapping(bytes32 => bool) private bytecodeAllowlist;

/// @notice storage gap for additional variables for upgrades
uint256[20] __gap;

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

/// ===== Initializer =====

/**
* @notice Grants `DEFAULT_ADMIN_ROLE` to the supplied `admin` address
* @param _roleAdmin the address to grant `DEFAULT_ADMIN_ROLE` to
*/
function initialize(address _roleAdmin, address _upgradeAdmin) public initializer {
__UUPSUpgradeable_init();
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, _roleAdmin);
_grantRole(UPGRADE_ROLE, _upgradeAdmin);
}

/// ===== 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 onlyRole(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 onlyRole(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 onlyRole(REGISTRAR_ROLE) {
// get bytecode of wallet
bytes32 codeHash;
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 onlyRole(REGISTRAR_ROLE) {
// get bytecode of wallet
bytes32 codeHash;
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 onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(REGISTRAR_ROLE, user);
}

/**
* @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 onlyRole(DEFAULT_ADMIN_ROLE) {
revokeRole(REGISTRAR_ROLE, user);
}

/// ===== 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;
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, AccessControlUpgradeable) returns (bool) {
return interfaceId == type(IOperatorAllowlist).interfaceId || super.supportsInterface(interfaceId);
}

// Override the _authorizeUpgrade function
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADE_ROLE) {}
}
195 changes: 195 additions & 0 deletions contracts/mocks/MockOAL.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Copyright Immutable Pty Ltd 2018 - 2023
// SPDX-License-Identifier: Apache 2.0
pragma solidity 0.8.19;

// Access Control
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";

import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";

// Introspection
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.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
function PROXY_getImplementation() external view returns (address);
}

/*
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 (e.g. ERC721/ERC1155).
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 MockOperatorAllowlistUpgradeable is ERC165, AccessControlUpgradeable, UUPSUpgradeable, IOperatorAllowlist {
/// ===== State Variables =====

/// @notice Only REGISTRAR_ROLE can invoke white listing registration and removal
bytes32 public constant REGISTRAR_ROLE = bytes32("REGISTRAR_ROLE");

/// @notice Only UPGRADE_ROLE can upgrade the contract
bytes32 public constant UPGRADE_ROLE = bytes32("UPGRADE_ROLE");

/// @notice Mapping of Allowlisted addresses
mapping(address => bool) private addressAllowlist;

/// @notice Mapping of Allowlisted implementation addresses
mapping(address => bool) private addressImplementationAllowlist;

/// @notice Mapping of Allowlisted bytecodes
mapping(bytes32 => bool) private bytecodeAllowlist;

uint256 public mockInt;

/// @notice storage gap for additional variables for upgrades
uint256[19] __gap;

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

/// ===== Initializer =====

/**
* @notice Grants `DEFAULT_ADMIN_ROLE` to the supplied `admin` address
* @param _roleAdmin the address to grant `DEFAULT_ADMIN_ROLE` to
*/
function initialize(address _roleAdmin, address _upgradeAdmin) public initializer {
__UUPSUpgradeable_init();
__AccessControl_init();
_grantRole(DEFAULT_ADMIN_ROLE, _roleAdmin);
_grantRole(UPGRADE_ROLE, _upgradeAdmin);
}

/// ===== 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 onlyRole(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 onlyRole(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 onlyRole(REGISTRAR_ROLE) {
// get bytecode of wallet
bytes32 codeHash;
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 onlyRole(REGISTRAR_ROLE) {
// get bytecode of wallet
bytes32 codeHash;
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 onlyRole(DEFAULT_ADMIN_ROLE) {
grantRole(REGISTRAR_ROLE, user);
}

/**
* @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 onlyRole(DEFAULT_ADMIN_ROLE) {
revokeRole(REGISTRAR_ROLE, user);
}

/// ===== 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;
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;
}

function setMockValue(uint256 val) public {
mockInt = val;
}

/**
* @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, AccessControlUpgradeable) returns (bool) {
return interfaceId == type(IOperatorAllowlist).interfaceId || super.supportsInterface(interfaceId);
}

// Override the _authorizeUpgrade function
function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADE_ROLE) {}
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
},
"dependencies": {
"@openzeppelin/contracts": "^4.9.3",
"@openzeppelin/contracts-upgradeable": "^4.9.3",
"@rari-capital/solmate": "^6.4.0",
"seaport": "https://github.com/immutable/seaport.git#1.5.0+im.1.3",
"solidity-bits": "^0.4.0",
Expand Down
Loading

0 comments on commit 89df76c

Please sign in to comment.