// Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; import "@openzeppelin/contracts/proxy/Proxy.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/StorageSlot.sol"; /// @notice An extension to OZ's ERC1967Upgrade implementation to support two logic contracts abstract contract DoubleLogicERC1967Upgrade is ERC1967Upgrade { // This is the keccak-256 hash of "eip1967.proxy.implementation.secondary" subtracted by 1 bytes32 internal constant _IMPLEMENTATION_SECONDARY_SLOT = 0x2b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546d; // This is the keccak-256 hash of "eip1967.proxy.rollback.secondary" subtracted by 1 bytes32 private constant _ROLLBACK_SECONDARY_SLOT = 0x49bd798cd84788856140a4cd5030756b4d08a9e4d55db725ec195f232d262a89; /** * @dev Emitted when the secondary implementation is upgraded. */ event UpgradedSecondary(address indexed implementation); /** * @dev Returns the current secondary implementation address. */ function _getSecondaryImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(_IMPLEMENTATION_SECONDARY_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setSecondaryImplementation(address newImplementation) private { require( Address.isContract(newImplementation), "ERC1967: new secondary implementation is not a contract" ); StorageSlot.getAddressSlot(_IMPLEMENTATION_SECONDARY_SLOT).value = newImplementation; } /** * @dev Perform secondary implementation upgrade * * Emits an {UpgradedSecondary} event. */ function _upgradeSecondaryTo(address newImplementation) internal { _setSecondaryImplementation(newImplementation); emit UpgradedSecondary(newImplementation); } /** * @dev Perform secondary implementation upgrade with additional setup call. * * Emits an {UpgradedSecondary} event. */ function _upgradeSecondaryToAndCall( address newImplementation, bytes memory data, bool forceCall ) internal { _upgradeSecondaryTo(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } } /** * @dev Perform secondary implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {UpgradedSecondary} event. */ function _upgradeSecondaryToAndCallUUPS( address newImplementation, bytes memory data, bool forceCall ) internal { // Upgrades from old implementations will perform a rollback test. This test requires the new // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing // this special case will break upgrade paths from old UUPS implementation to new ones. if (StorageSlot.getBooleanSlot(_ROLLBACK_SECONDARY_SLOT).value) { _setSecondaryImplementation(newImplementation); } else { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { require( slot == _IMPLEMENTATION_SECONDARY_SLOT, "ERC1967Upgrade: unsupported secondary proxiableUUID" ); } catch { revert("ERC1967Upgrade: new secondary implementation is not UUPS"); } _upgradeSecondaryToAndCall(newImplementation, data, forceCall); } } } /// @notice similar to TransparentUpgradeableProxy but allows the admin to fallback to a separate logic contract using DoubleLogicERC1967Upgrade /// @dev this follows the UUPS pattern for upgradeability - read more at https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.5.0/contracts/proxy#transparent-vs-uups-proxies contract AdminFallbackProxy is Proxy, DoubleLogicERC1967Upgrade { /** * @dev Initializes the upgradeable proxy with an initial implementation specified by `adminLogic` and a secondary * logic implementation specified by `userLogic` * * Only the `adminAddr` is able to use the `adminLogic` functions * All other addresses can interact with the `userLogic` functions */ function _initialize( address adminLogic, bytes memory adminData, address userLogic, bytes memory userData, address adminAddr ) internal { assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1)); assert( _IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1) ); assert( _IMPLEMENTATION_SECONDARY_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation.secondary")) - 1) ); _changeAdmin(adminAddr); _upgradeToAndCall(adminLogic, adminData, false); _upgradeSecondaryToAndCall(userLogic, userData, false); } /// @inheritdoc Proxy function _implementation() internal view override returns (address) { require(msg.data.length >= 4, "NO_FUNC_SIG"); // if the sender is the proxy's admin, delegate to admin logic // if the admin is disabled, all calls will be forwarded to user logic // admin affordances can be disabled by setting to a no-op smart contract // since there is a check for contract code before updating the value address target = _getAdmin() != msg.sender ? DoubleLogicERC1967Upgrade._getSecondaryImplementation() : ERC1967Upgrade._getImplementation(); // implementation setters do an existence check, but we protect against selfdestructs this way require(Address.isContract(target), "TARGET_NOT_CONTRACT"); return target; } /** * @dev unlike transparent upgradeable proxies, this does allow the admin to fallback to a logic contract * the admin is expected to interact only with the primary logic contract, which handles contract * upgrades using the UUPS approach */ function _beforeFallback() internal override { super._beforeFallback(); } }