github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/L1/OptimismPortal.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; 5 import { SafeCall } from "src/libraries/SafeCall.sol"; 6 import { L2OutputOracle } from "src/L1/L2OutputOracle.sol"; 7 import { SystemConfig } from "src/L1/SystemConfig.sol"; 8 import { SuperchainConfig } from "src/L1/SuperchainConfig.sol"; 9 import { Constants } from "src/libraries/Constants.sol"; 10 import { Types } from "src/libraries/Types.sol"; 11 import { Hashing } from "src/libraries/Hashing.sol"; 12 import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol"; 13 import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol"; 14 import { ResourceMetering } from "src/L1/ResourceMetering.sol"; 15 import { ISemver } from "src/universal/ISemver.sol"; 16 import { Constants } from "src/libraries/Constants.sol"; 17 18 /// @custom:proxied 19 /// @title OptimismPortal 20 /// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1 21 /// and L2. Messages sent directly to the OptimismPortal have no form of replayability. 22 /// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface. 23 contract OptimismPortal is Initializable, ResourceMetering, ISemver { 24 /// @notice Represents a proven withdrawal. 25 /// @custom:field outputRoot Root of the L2 output this was proven against. 26 /// @custom:field timestamp Timestamp at whcih the withdrawal was proven. 27 /// @custom:field l2OutputIndex Index of the output this was proven against. 28 struct ProvenWithdrawal { 29 bytes32 outputRoot; 30 uint128 timestamp; 31 uint128 l2OutputIndex; 32 } 33 34 /// @notice Version of the deposit event. 35 uint256 internal constant DEPOSIT_VERSION = 0; 36 37 /// @notice The L2 gas limit set when eth is deposited using the receive() function. 38 uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000; 39 40 /// @notice Address of the L2 account which initiated a withdrawal in this transaction. 41 /// If the of this variable is the default L2 sender address, then we are NOT inside of 42 /// a call to finalizeWithdrawalTransaction. 43 address public l2Sender; 44 45 /// @notice A list of withdrawal hashes which have been successfully finalized. 46 mapping(bytes32 => bool) public finalizedWithdrawals; 47 48 /// @notice A mapping of withdrawal hashes to `ProvenWithdrawal` data. 49 mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals; 50 51 /// @custom:legacy 52 /// @custom:spacer paused 53 /// @notice Spacer for backwards compatibility. 54 bool private spacer_53_0_1; 55 56 /// @notice Contract of the Superchain Config. 57 SuperchainConfig public superchainConfig; 58 59 /// @notice Contract of the L2OutputOracle. 60 /// @custom:network-specific 61 L2OutputOracle public l2Oracle; 62 63 /// @notice Contract of the SystemConfig. 64 /// @custom:network-specific 65 SystemConfig public systemConfig; 66 67 /// @notice Emitted when a transaction is deposited from L1 to L2. 68 /// The parameters of this event are read by the rollup node and used to derive deposit 69 /// transactions on L2. 70 /// @param from Address that triggered the deposit transaction. 71 /// @param to Address that the deposit transaction is directed to. 72 /// @param version Version of this deposit transaction event. 73 /// @param opaqueData ABI encoded deposit data to be parsed off-chain. 74 event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData); 75 76 /// @notice Emitted when a withdrawal transaction is proven. 77 /// @param withdrawalHash Hash of the withdrawal transaction. 78 /// @param from Address that triggered the withdrawal transaction. 79 /// @param to Address that the withdrawal transaction is directed to. 80 event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); 81 82 /// @notice Emitted when a withdrawal transaction is finalized. 83 /// @param withdrawalHash Hash of the withdrawal transaction. 84 /// @param success Whether the withdrawal transaction was successful. 85 event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); 86 87 /// @notice Reverts when paused. 88 modifier whenNotPaused() { 89 require(paused() == false, "OptimismPortal: paused"); 90 _; 91 } 92 93 /// @notice Semantic version. 94 /// @custom:semver 2.5.0 95 string public constant version = "2.5.0"; 96 97 /// @notice Constructs the OptimismPortal contract. 98 constructor() { 99 initialize({ 100 _l2Oracle: L2OutputOracle(address(0)), 101 _systemConfig: SystemConfig(address(0)), 102 _superchainConfig: SuperchainConfig(address(0)) 103 }); 104 } 105 106 /// @notice Initializer. 107 /// @param _l2Oracle Contract of the L2OutputOracle. 108 /// @param _systemConfig Contract of the SystemConfig. 109 /// @param _superchainConfig Contract of the SuperchainConfig. 110 function initialize( 111 L2OutputOracle _l2Oracle, 112 SystemConfig _systemConfig, 113 SuperchainConfig _superchainConfig 114 ) 115 public 116 initializer 117 { 118 l2Oracle = _l2Oracle; 119 systemConfig = _systemConfig; 120 superchainConfig = _superchainConfig; 121 if (l2Sender == address(0)) { 122 l2Sender = Constants.DEFAULT_L2_SENDER; 123 } 124 __ResourceMetering_init(); 125 } 126 127 /// @notice Getter function for the contract of the L2OutputOracle on this chain. 128 /// Public getter is legacy and will be removed in the future. Use `l2Oracle()` instead. 129 /// @return Contract of the L2OutputOracle on this chain. 130 /// @custom:legacy 131 function L2_ORACLE() external view returns (L2OutputOracle) { 132 return l2Oracle; 133 } 134 135 /// @notice Getter function for the contract of the SystemConfig on this chain. 136 /// Public getter is legacy and will be removed in the future. Use `systemConfig()` instead. 137 /// @return Contract of the SystemConfig on this chain. 138 /// @custom:legacy 139 function SYSTEM_CONFIG() external view returns (SystemConfig) { 140 return systemConfig; 141 } 142 143 /// @notice Getter function for the address of the guardian. 144 /// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead. 145 /// @return Address of the guardian. 146 /// @custom:legacy 147 function GUARDIAN() external view returns (address) { 148 return guardian(); 149 } 150 151 /// @notice Getter function for the address of the guardian. 152 /// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead. 153 /// @return Address of the guardian. 154 /// @custom:legacy 155 function guardian() public view returns (address) { 156 return superchainConfig.guardian(); 157 } 158 159 /// @notice Getter for the current paused status. 160 /// @return paused_ Whether or not the contract is paused. 161 function paused() public view returns (bool paused_) { 162 paused_ = superchainConfig.paused(); 163 } 164 165 /// @notice Computes the minimum gas limit for a deposit. 166 /// The minimum gas limit linearly increases based on the size of the calldata. 167 /// This is to prevent users from creating L2 resource usage without paying for it. 168 /// This function can be used when interacting with the portal to ensure forwards 169 /// compatibility. 170 /// @param _byteCount Number of bytes in the calldata. 171 /// @return The minimum gas limit for a deposit. 172 function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) { 173 return _byteCount * 16 + 21000; 174 } 175 176 /// @notice Accepts value so that users can send ETH directly to this contract and have the 177 /// funds be deposited to their address on L2. This is intended as a convenience 178 /// function for EOAs. Contracts should call the depositTransaction() function directly 179 /// otherwise any deposited funds will be lost due to address aliasing. 180 receive() external payable { 181 depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes("")); 182 } 183 184 /// @notice Accepts ETH value without triggering a deposit to L2. 185 /// This function mainly exists for the sake of the migration between the legacy 186 /// Optimism system and Bedrock. 187 function donateETH() external payable { 188 // Intentionally empty. 189 } 190 191 /// @notice Getter for the resource config. 192 /// Used internally by the ResourceMetering contract. 193 /// The SystemConfig is the source of truth for the resource config. 194 /// @return ResourceMetering ResourceConfig 195 function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory) { 196 return systemConfig.resourceConfig(); 197 } 198 199 /// @notice Proves a withdrawal transaction. 200 /// @param _tx Withdrawal transaction to finalize. 201 /// @param _l2OutputIndex L2 output index to prove against. 202 /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root. 203 /// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract. 204 function proveWithdrawalTransaction( 205 Types.WithdrawalTransaction memory _tx, 206 uint256 _l2OutputIndex, 207 Types.OutputRootProof calldata _outputRootProof, 208 bytes[] calldata _withdrawalProof 209 ) 210 external 211 whenNotPaused 212 { 213 // Prevent users from creating a deposit transaction where this address is the message 214 // sender on L2. Because this is checked here, we do not need to check again in 215 // `finalizeWithdrawalTransaction`. 216 require(_tx.target != address(this), "OptimismPortal: you cannot send messages to the portal contract"); 217 218 // Get the output root and load onto the stack to prevent multiple mloads. This will 219 // revert if there is no output root for the given block number. 220 bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot; 221 222 // Verify that the output root can be generated with the elements in the proof. 223 require( 224 outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof" 225 ); 226 227 // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier. 228 bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); 229 ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash]; 230 231 // We generally want to prevent users from proving the same withdrawal multiple times 232 // because each successive proof will update the timestamp. A malicious user can take 233 // advantage of this to prevent other users from finalizing their withdrawal. However, 234 // since withdrawals are proven before an output root is finalized, we need to allow users 235 // to re-prove their withdrawal only in the case that the output root for their specified 236 // output index has been updated. 237 require( 238 provenWithdrawal.timestamp == 0 239 || l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot, 240 "OptimismPortal: withdrawal hash has already been proven" 241 ); 242 243 // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract. 244 // Refer to the Solidity documentation for more information on how storage layouts are 245 // computed for mappings. 246 bytes32 storageKey = keccak256( 247 abi.encode( 248 withdrawalHash, 249 uint256(0) // The withdrawals mapping is at the first slot in the layout. 250 ) 251 ); 252 253 // Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract 254 // on L2. If this is true, under the assumption that the SecureMerkleTrie does not have 255 // bugs, then we know that this withdrawal was actually triggered on L2 and can therefore 256 // be relayed on L1. 257 require( 258 SecureMerkleTrie.verifyInclusionProof( 259 abi.encode(storageKey), hex"01", _withdrawalProof, _outputRootProof.messagePasserStorageRoot 260 ), 261 "OptimismPortal: invalid withdrawal inclusion proof" 262 ); 263 264 // Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and 265 // `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be 266 // proven once unless it is submitted again with a different outputRoot. 267 provenWithdrawals[withdrawalHash] = ProvenWithdrawal({ 268 outputRoot: outputRoot, 269 timestamp: uint128(block.timestamp), 270 l2OutputIndex: uint128(_l2OutputIndex) 271 }); 272 273 // Emit a `WithdrawalProven` event. 274 emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target); 275 } 276 277 /// @notice Finalizes a withdrawal transaction. 278 /// @param _tx Withdrawal transaction to finalize. 279 function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused { 280 // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other 281 // than the default value when a withdrawal transaction is being finalized. This check is 282 // a defacto reentrancy guard. 283 require( 284 l2Sender == Constants.DEFAULT_L2_SENDER, "OptimismPortal: can only trigger one withdrawal per transaction" 285 ); 286 287 // Grab the proven withdrawal from the `provenWithdrawals` map. 288 bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx); 289 ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash]; 290 291 // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has 292 // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have 293 // a timestamp of zero. 294 require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet"); 295 296 // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than 297 // starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of 298 // safety against weird bugs in the proving step. 299 require( 300 provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(), 301 "OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp" 302 ); 303 304 // A proven withdrawal must wait at least the finalization period before it can be 305 // finalized. This waiting period can elapse in parallel with the waiting period for the 306 // output the withdrawal was proven against. In effect, this means that the minimum 307 // withdrawal time is proposal submission time + finalization period. 308 require( 309 _isFinalizationPeriodElapsed(provenWithdrawal.timestamp), 310 "OptimismPortal: proven withdrawal finalization period has not elapsed" 311 ); 312 313 // Grab the OutputProposal from the L2OutputOracle, will revert if the output that 314 // corresponds to the given index has not been proposed yet. 315 Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex); 316 317 // Check that the output root that was used to prove the withdrawal is the same as the 318 // current output root for the given output index. An output root may change if it is 319 // deleted by the challenger address and then re-proposed. 320 require( 321 proposal.outputRoot == provenWithdrawal.outputRoot, 322 "OptimismPortal: output root proven is not the same as current output root" 323 ); 324 325 // Check that the output proposal has also been finalized. 326 require( 327 _isFinalizationPeriodElapsed(proposal.timestamp), 328 "OptimismPortal: output proposal finalization period has not elapsed" 329 ); 330 331 // Check that this withdrawal has not already been finalized, this is replay protection. 332 require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized"); 333 334 // Mark the withdrawal as finalized so it can't be replayed. 335 finalizedWithdrawals[withdrawalHash] = true; 336 337 // Set the l2Sender so contracts know who triggered this withdrawal on L2. 338 l2Sender = _tx.sender; 339 340 // Trigger the call to the target contract. We use a custom low level method 341 // SafeCall.callWithMinGas to ensure two key properties 342 // 1. Target contracts cannot force this call to run out of gas by returning a very large 343 // amount of data (and this is OK because we don't care about the returndata here). 344 // 2. The amount of gas provided to the execution context of the target is at least the 345 // gas limit specified by the user. If there is not enough gas in the current context 346 // to accomplish this, `callWithMinGas` will revert. 347 bool success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data); 348 349 // Reset the l2Sender back to the default value. 350 l2Sender = Constants.DEFAULT_L2_SENDER; 351 352 // All withdrawals are immediately finalized. Replayability can 353 // be achieved through contracts built on top of this contract 354 emit WithdrawalFinalized(withdrawalHash, success); 355 356 // Reverting here is useful for determining the exact gas cost to successfully execute the 357 // sub call to the target contract if the minimum gas limit specified by the user would not 358 // be sufficient to execute the sub call. 359 if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) { 360 revert("OptimismPortal: withdrawal failed"); 361 } 362 } 363 364 /// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in 365 /// deriving deposit transactions. Note that if a deposit is made by a contract, its 366 /// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider 367 /// using the CrossDomainMessenger contracts for a simpler developer experience. 368 /// @param _to Target address on L2. 369 /// @param _value ETH value to send to the recipient. 370 /// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1. 371 /// @param _isCreation Whether or not the transaction is a contract creation. 372 /// @param _data Data to trigger the recipient with. 373 function depositTransaction( 374 address _to, 375 uint256 _value, 376 uint64 _gasLimit, 377 bool _isCreation, 378 bytes memory _data 379 ) 380 public 381 payable 382 metered(_gasLimit) 383 { 384 // Just to be safe, make sure that people specify address(0) as the target when doing 385 // contract creations. 386 if (_isCreation) { 387 require(_to == address(0), "OptimismPortal: must send to address(0) when creating a contract"); 388 } 389 390 // Prevent depositing transactions that have too small of a gas limit. Users should pay 391 // more for more resource usage. 392 require(_gasLimit >= minimumGasLimit(uint64(_data.length)), "OptimismPortal: gas limit too small"); 393 394 // Prevent the creation of deposit transactions that have too much calldata. This gives an 395 // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure 396 // that the transaction can fit into the p2p network policy of 128kb even though deposit 397 // transactions are not gossipped over the p2p network. 398 require(_data.length <= 120_000, "OptimismPortal: data too large"); 399 400 // Transform the from-address to its alias if the caller is a contract. 401 address from = msg.sender; 402 if (msg.sender != tx.origin) { 403 from = AddressAliasHelper.applyL1ToL2Alias(msg.sender); 404 } 405 406 // Compute the opaque data that will be emitted as part of the TransactionDeposited event. 407 // We use opaque data so that we can update the TransactionDeposited event in the future 408 // without breaking the current interface. 409 bytes memory opaqueData = abi.encodePacked(msg.value, _value, _gasLimit, _isCreation, _data); 410 411 // Emit a TransactionDeposited event so that the rollup node can derive a deposit 412 // transaction for this deposit. 413 emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData); 414 } 415 416 /// @notice Determine if a given output is finalized. 417 /// Reverts if the call to l2Oracle.getL2Output reverts. 418 /// Returns a boolean otherwise. 419 /// @param _l2OutputIndex Index of the L2 output to check. 420 /// @return Whether or not the output is finalized. 421 function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool) { 422 return _isFinalizationPeriodElapsed(l2Oracle.getL2Output(_l2OutputIndex).timestamp); 423 } 424 425 /// @notice Determines whether the finalization period has elapsed with respect to 426 /// the provided block timestamp. 427 /// @param _timestamp Timestamp to check. 428 /// @return Whether or not the finalization period has elapsed. 429 function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) { 430 return block.timestamp > _timestamp + l2Oracle.FINALIZATION_PERIOD_SECONDS(); 431 } 432 }