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 }