github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/legacy/L1ChugSplashProxy.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Constants } from "src/libraries/Constants.sol";
     5  
     6  /// @title IL1ChugSplashDeployer
     7  interface IL1ChugSplashDeployer {
     8      function isUpgrading() external view returns (bool);
     9  }
    10  
    11  /// @custom:legacy
    12  /// @title L1ChugSplashProxy
    13  /// @notice Basic ChugSplash proxy contract for L1. Very close to being a normal proxy but has added
    14  ///         functions `setCode` and `setStorage` for changing the code or storage of the contract.
    15  ///         Note for future developers: do NOT make anything in this contract 'public' unless you
    16  ///         know what you're doing. Anything public can potentially have a function signature that
    17  ///         conflicts with a signature attached to the implementation contract. Public functions
    18  ///         SHOULD always have the `proxyCallIfNotOwner` modifier unless there's some *really* good
    19  ///         reason not to have that modifier. And there almost certainly is not a good reason to not
    20  ///         have that modifier. Beware!
    21  contract L1ChugSplashProxy {
    22      /// @notice "Magic" prefix. When prepended to some arbitrary bytecode and used to create a
    23      ///         contract, the appended bytecode will be deployed as given.
    24      bytes13 internal constant DEPLOY_CODE_PREFIX = 0x600D380380600D6000396000f3;
    25  
    26      /// @notice Blocks a function from being called when the parent signals that the system should
    27      ///         be paused via an isUpgrading function.
    28      modifier onlyWhenNotPaused() {
    29          address owner = _getOwner();
    30  
    31          // We do a low-level call because there's no guarantee that the owner actually *is* an
    32          // L1ChugSplashDeployer contract and Solidity will throw errors if we do a normal call and
    33          // it turns out that it isn't the right type of contract.
    34          (bool success, bytes memory returndata) =
    35              owner.staticcall(abi.encodeWithSelector(IL1ChugSplashDeployer.isUpgrading.selector));
    36  
    37          // If the call was unsuccessful then we assume that there's no "isUpgrading" method and we
    38          // can just continue as normal. We also expect that the return value is exactly 32 bytes
    39          // long. If this isn't the case then we can safely ignore the result.
    40          if (success && returndata.length == 32) {
    41              // Although the expected value is a *boolean*, it's safer to decode as a uint256 in the
    42              // case that the isUpgrading function returned something other than 0 or 1. But we only
    43              // really care about the case where this value is 0 (= false).
    44              uint256 ret = abi.decode(returndata, (uint256));
    45              require(ret == 0, "L1ChugSplashProxy: system is currently being upgraded");
    46          }
    47  
    48          _;
    49      }
    50  
    51      /// @notice Makes a proxy call instead of triggering the given function when the caller is
    52      ///         either the owner or the zero address. Caller can only ever be the zero address if
    53      ///         this function is being called off-chain via eth_call, which is totally fine and can
    54      ///         be convenient for client-side tooling. Avoids situations where the proxy and
    55      ///         implementation share a sighash and the proxy function ends up being called instead
    56      ///         of the implementation one.
    57      ///         Note: msg.sender == address(0) can ONLY be triggered off-chain via eth_call. If
    58      ///         there's a way for someone to send a transaction with msg.sender == address(0) in any
    59      ///         real context then we have much bigger problems. Primary reason to include this
    60      ///         additional allowed sender is because the owner address can be changed dynamically
    61      ///         and we do not want clients to have to keep track of the current owner in order to
    62      ///         make an eth_call that doesn't trigger the proxied contract.
    63      // slither-disable-next-line incorrect-modifier
    64      modifier proxyCallIfNotOwner() {
    65          if (msg.sender == _getOwner() || msg.sender == address(0)) {
    66              _;
    67          } else {
    68              // This WILL halt the call frame on completion.
    69              _doProxyCall();
    70          }
    71      }
    72  
    73      /// @param _owner Address of the initial contract owner.
    74      constructor(address _owner) {
    75          _setOwner(_owner);
    76      }
    77  
    78      // slither-disable-next-line locked-ether
    79      receive() external payable {
    80          // Proxy call by default.
    81          _doProxyCall();
    82      }
    83  
    84      // slither-disable-next-line locked-ether
    85      fallback() external payable {
    86          // Proxy call by default.
    87          _doProxyCall();
    88      }
    89  
    90      /// @notice Sets the code that should be running behind this proxy.
    91      ///         Note: This scheme is a bit different from the standard proxy scheme where one would
    92      ///         typically deploy the code separately and then set the implementation address. We're
    93      ///         doing it this way because it gives us a lot more freedom on the client side. Can
    94      ///         only be triggered by the contract owner.
    95      /// @param _code New contract code to run inside this contract.
    96      function setCode(bytes memory _code) external proxyCallIfNotOwner {
    97          // Get the code hash of the current implementation.
    98          address implementation = _getImplementation();
    99  
   100          // If the code hash matches the new implementation then we return early.
   101          if (keccak256(_code) == _getAccountCodeHash(implementation)) {
   102              return;
   103          }
   104  
   105          // Create the deploycode by appending the magic prefix.
   106          bytes memory deploycode = abi.encodePacked(DEPLOY_CODE_PREFIX, _code);
   107  
   108          // Deploy the code and set the new implementation address.
   109          address newImplementation;
   110          assembly {
   111              newImplementation := create(0x0, add(deploycode, 0x20), mload(deploycode))
   112          }
   113  
   114          // Check that the code was actually deployed correctly. I'm not sure if you can ever
   115          // actually fail this check. Should only happen if the contract creation from above runs
   116          // out of gas but this parent execution thread does NOT run out of gas. Seems like we
   117          // should be doing this check anyway though.
   118          require(
   119              _getAccountCodeHash(newImplementation) == keccak256(_code),
   120              "L1ChugSplashProxy: code was not correctly deployed"
   121          );
   122  
   123          _setImplementation(newImplementation);
   124      }
   125  
   126      /// @notice Modifies some storage slot within the proxy contract. Gives us a lot of power to
   127      ///         perform upgrades in a more transparent way. Only callable by the owner.
   128      /// @param _key   Storage key to modify.
   129      /// @param _value New value for the storage key.
   130      function setStorage(bytes32 _key, bytes32 _value) external proxyCallIfNotOwner {
   131          assembly {
   132              sstore(_key, _value)
   133          }
   134      }
   135  
   136      /// @notice Changes the owner of the proxy contract. Only callable by the owner.
   137      /// @param _owner New owner of the proxy contract.
   138      function setOwner(address _owner) external proxyCallIfNotOwner {
   139          _setOwner(_owner);
   140      }
   141  
   142      /// @notice Queries the owner of the proxy contract. Can only be called by the owner OR by
   143      ///         making an eth_call and setting the "from" address to address(0).
   144      /// @return Owner address.
   145      function getOwner() external proxyCallIfNotOwner returns (address) {
   146          return _getOwner();
   147      }
   148  
   149      /// @notice Queries the implementation address. Can only be called by the owner OR by making an
   150      ///         eth_call and setting the "from" address to address(0).
   151      /// @return Implementation address.
   152      function getImplementation() external proxyCallIfNotOwner returns (address) {
   153          return _getImplementation();
   154      }
   155  
   156      /// @notice Sets the implementation address.
   157      /// @param _implementation New implementation address.
   158      function _setImplementation(address _implementation) internal {
   159          bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
   160          assembly {
   161              sstore(proxyImplementation, _implementation)
   162          }
   163      }
   164  
   165      /// @notice Changes the owner of the proxy contract.
   166      /// @param _owner New owner of the proxy contract.
   167      function _setOwner(address _owner) internal {
   168          bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
   169          assembly {
   170              sstore(proxyOwner, _owner)
   171          }
   172      }
   173  
   174      /// @notice Performs the proxy call via a delegatecall.
   175      function _doProxyCall() internal onlyWhenNotPaused {
   176          address implementation = _getImplementation();
   177  
   178          require(implementation != address(0), "L1ChugSplashProxy: implementation is not set yet");
   179  
   180          assembly {
   181              // Copy calldata into memory at 0x0....calldatasize.
   182              calldatacopy(0x0, 0x0, calldatasize())
   183  
   184              // Perform the delegatecall, make sure to pass all available gas.
   185              let success := delegatecall(gas(), implementation, 0x0, calldatasize(), 0x0, 0x0)
   186  
   187              // Copy returndata into memory at 0x0....returndatasize. Note that this *will*
   188              // overwrite the calldata that we just copied into memory but that doesn't really
   189              // matter because we'll be returning in a second anyway.
   190              returndatacopy(0x0, 0x0, returndatasize())
   191  
   192              // Success == 0 means a revert. We'll revert too and pass the data up.
   193              if iszero(success) { revert(0x0, returndatasize()) }
   194  
   195              // Otherwise we'll just return and pass the data up.
   196              return(0x0, returndatasize())
   197          }
   198      }
   199  
   200      /// @notice Queries the implementation address.
   201      /// @return Implementation address.
   202      function _getImplementation() internal view returns (address) {
   203          address implementation;
   204          bytes32 proxyImplementation = Constants.PROXY_IMPLEMENTATION_ADDRESS;
   205          assembly {
   206              implementation := sload(proxyImplementation)
   207          }
   208          return implementation;
   209      }
   210  
   211      /// @notice Queries the owner of the proxy contract.
   212      /// @return Owner address.
   213      function _getOwner() internal view returns (address) {
   214          address owner;
   215          bytes32 proxyOwner = Constants.PROXY_OWNER_ADDRESS;
   216          assembly {
   217              owner := sload(proxyOwner)
   218          }
   219          return owner;
   220      }
   221  
   222      /// @notice Gets the code hash for a given account.
   223      /// @param _account Address of the account to get a code hash for.
   224      /// @return Code hash for the account.
   225      function _getAccountCodeHash(address _account) internal view returns (bytes32) {
   226          bytes32 codeHash;
   227          assembly {
   228              codeHash := extcodehash(_account)
   229          }
   230          return codeHash;
   231      }
   232  }