Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/L1/OptimismPortalCGT.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Contracts
import {Initializable} from "lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol";
import {ResourceMetering} from "src/L1/ResourceMetering.sol";
// Libraries
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {SafeCall} from "src/libraries/SafeCall.sol";
import {Constants} from "src/libraries/Constants.sol";
import {Types} from "src/libraries/Types.sol";
import {Hashing} from "src/libraries/Hashing.sol";
import {SecureMerkleTrie} from "src/libraries/trie/SecureMerkleTrie.sol";
import {Predeploys} from "src/libraries/Predeploys.sol";
import {AddressAliasHelper} from "src/vendor/AddressAliasHelper.sol";
import {BadTarget, LargeCalldata, SmallGasLimit, TransferFailed, OnlyCustomGasToken, NoValue, Unauthorized, CallPaused, GasEstimation, NonReentrant, Unproven} from "src/libraries/PortalErrors.sol";
// Interfaces
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ISemver} from "interfaces/universal/ISemver.sol";
import {IL2OutputOracle} from "interfaces/L1/IL2OutputOracle.sol";
import {ISystemConfig} from "interfaces/L1/ISystemConfig.sol";
import {IResourceMetering} from "interfaces/L1/IResourceMetering.sol";
import {ISuperchainConfig} from "interfaces/L1/ISuperchainConfig.sol";
import {IL1Block} from "interfaces/L2/IL1Block.sol";
/// @custom:proxied true
/// @title OptimismPortal
/// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1
/// and L2. Messages sent directly to the OptimismPortal have no form of replayability.
/// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
contract OptimismPortalCGT is Initializable, ResourceMetering, ISemver {
/// @notice Allows for interactions with non standard ERC20 tokens.
using SafeERC20 for IERC20;
/// @notice Represents a proven withdrawal.
/// @custom:field outputRoot Root of the L2 output this was proven against.
/// @custom:field timestamp Timestamp at whcih the withdrawal was proven.
/// @custom:field l2OutputIndex Index of the output this was proven against.
struct ProvenWithdrawal {
bytes32 outputRoot;
uint128 timestamp;
uint128 l2OutputIndex;
}
/// @notice Version of the deposit event.
uint256 internal constant DEPOSIT_VERSION = 0;
/// @notice The L2 gas limit set when eth is deposited using the receive() function.
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/// @notice The L2 gas limit for system deposit transactions that are initiated from L1.
uint32 internal constant SYSTEM_DEPOSIT_GAS_LIMIT = 200_000;
/// @notice Address of the L2 account which initiated a withdrawal in this transaction.
/// If the of this variable is the default L2 sender address, then we are NOT inside of
/// a call to finalizeWithdrawalTransaction.
address public l2Sender;
/// @notice A list of withdrawal hashes which have been successfully finalized.
mapping(bytes32 => bool) public finalizedWithdrawals;
/// @notice A mapping of withdrawal hashes to `ProvenWithdrawal` data.
mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;
/// @custom:legacy
/// @custom:spacer paused
/// @notice Spacer for backwards compatibility.
bool private spacer_53_0_1;
/// @notice Contract of the Superchain Config.
ISuperchainConfig public superchainConfig;
/// @notice Contract of the L2OutputOracle.
/// @custom:network-specific
IL2OutputOracle public l2Oracle;
/// @notice Contract of the SystemConfig.
/// @custom:network-specific
ISystemConfig public systemConfig;
/// @custom:spacer disputeGameFactory
/// @notice Spacer for backwards compatibility.
address private spacer_56_0_20;
/// @custom:spacer provenWithdrawals
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_57_0_32;
/// @custom:spacer disputeGameBlacklist
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_58_0_32;
/// @custom:spacer respectedGameType + respectedGameTypeUpdatedAt
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_59_0_32;
/// @custom:spacer proofSubmitters
/// @notice Spacer for backwards compatibility.
bytes32 private spacer_60_0_32;
/// @notice Represents the amount of native asset minted in L2. This may not
/// be 100% accurate due to the ability to send ether to the contract
/// without triggering a deposit transaction. It also is used to prevent
/// overflows for L2 account balances when custom gas tokens are used.
/// It is not safe to trust `ERC20.balanceOf` as it may lie.
uint256 internal _balance;
/// @notice Reserve extra slots in the storage layout for future upgrades.
/// @dev Added by Frax Finance
uint256[50] private __gap;
// Kept for preventing old approval abuse
address public FRXETH;
address public immutable FRAX_COMPTROLLER;
/// @notice Emitted when a transaction is deposited from L1 to L2.
/// The parameters of this event are read by the rollup node and used to derive deposit
/// transactions on L2.
/// @param from Address that triggered the deposit transaction.
/// @param to Address that the deposit transaction is directed to.
/// @param version Version of this deposit transaction event.
/// @param opaqueData ABI encoded deposit data to be parsed off-chain.
event TransactionDeposited(
address indexed from,
address indexed to,
uint256 indexed version,
bytes opaqueData
);
/// @notice Emitted when a withdrawal transaction is proven.
/// @param withdrawalHash Hash of the withdrawal transaction.
/// @param from Address that triggered the withdrawal transaction.
/// @param to Address that the withdrawal transaction is directed to.
event WithdrawalProven(
bytes32 indexed withdrawalHash,
address indexed from,
address indexed to
);
/// @notice Emitted when a withdrawal transaction is finalized.
/// @param withdrawalHash Hash of the withdrawal transaction.
/// @param success Whether the withdrawal transaction was successful.
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
/// @notice Reverts when paused.
modifier whenNotPaused() {
if (paused()) revert CallPaused();
_;
}
/// @notice Semantic version.
/// @custom:semver 2.8.1-beta.4
function version() public pure virtual returns (string memory) {
return "2.8.1-beta.4";
}
/// @notice Constructs the OptimismPortal contract.
constructor(address _fraxComptroller) {
FRAX_COMPTROLLER = _fraxComptroller;
initialize({
_l2Oracle: IL2OutputOracle(address(0)),
_systemConfig: ISystemConfig(address(0)),
_superchainConfig: ISuperchainConfig(address(0)),
_frxEthErc20Address: address(0)
});
}
/// @notice Initializer.
/// @param _l2Oracle Contract of the L2OutputOracle.
/// @param _systemConfig Contract of the SystemConfig.
/// @param _superchainConfig Contract of the SuperchainConfig.
/// @param _frxEthErc20Address Address of frxETH
function initialize(
IL2OutputOracle _l2Oracle,
ISystemConfig _systemConfig,
ISuperchainConfig _superchainConfig,
address _frxEthErc20Address
) public initializer {
l2Oracle = _l2Oracle;
systemConfig = _systemConfig;
superchainConfig = _superchainConfig;
FRXETH = _frxEthErc20Address;
if (l2Sender == address(0)) {
l2Sender = Constants.DEFAULT_L2_SENDER;
}
__ResourceMetering_init();
}
function mint(uint256 _amount, address _to, uint64 _gasLimit) public {
require(msg.sender == FRAX_COMPTROLLER, "only comptroller");
_balance += _amount;
_depositTransaction({
_to: _to,
_mint: _amount,
_value: _amount,
_gasLimit: _gasLimit,
_isCreation: false,
_data: ""
});
}
/// @notice Getter for the balance of the contract.
function balance() public view returns (uint256) {
(address token, ) = gasPayingToken();
if (token == Constants.ETHER) {
return address(this).balance;
} else {
return _balance;
}
}
/// @notice Getter function for the address of the guardian.
/// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead.
/// @return Address of the guardian.
/// @custom:legacy
function guardian() public view returns (address) {
return superchainConfig.guardian();
}
/// @notice Getter for the current paused status.
/// @return paused_ Whether or not the contract is paused.
function paused() public view returns (bool paused_) {
paused_ = superchainConfig.paused();
}
/// @notice Computes the minimum gas limit for a deposit.
/// The minimum gas limit linearly increases based on the size of the calldata.
/// This is to prevent users from creating L2 resource usage without paying for it.
/// This function can be used when interacting with the portal to ensure forwards
/// compatibility.
/// @param _byteCount Number of bytes in the calldata.
/// @return The minimum gas limit for a deposit.
function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
return _byteCount * 16 + 21_000;
}
/// @notice Accepts value so that users can send ETH directly to this contract and have the
/// funds be deposited to their address on L2. This is intended as a convenience
/// function for EOAs. Contracts should call the depositTransaction() function directly
/// otherwise any deposited funds will be lost due to address aliasing.
receive() external payable {
depositTransaction(
msg.sender,
msg.value,
RECEIVE_DEFAULT_GAS_LIMIT,
false,
bytes("")
);
}
/// @notice Accepts ETH value without triggering a deposit to L2.
/// This function mainly exists for the sake of the migration between the legacy
/// Optimism system and Bedrock.
function donateETH() external payable {
// Intentionally empty.
}
/// @notice Returns the gas paying token and its decimals.
function gasPayingToken()
public
view
returns (address addr_, uint8 decimals_)
{
(addr_, decimals_) = systemConfig.gasPayingToken();
}
/// @notice Getter for the resource config.
/// Used internally by the ResourceMetering contract.
/// The SystemConfig is the source of truth for the resource config.
/// @return ResourceMetering ResourceConfig
function _resourceConfig()
internal
view
override
returns (ResourceConfig memory)
{
IResourceMetering.ResourceConfig memory config = systemConfig
.resourceConfig();
return
ResourceConfig({
maxResourceLimit: config.maxResourceLimit,
elasticityMultiplier: config.elasticityMultiplier,
baseFeeMaxChangeDenominator: config.baseFeeMaxChangeDenominator,
minimumBaseFee: config.minimumBaseFee,
systemTxMaxGas: config.systemTxMaxGas,
maximumBaseFee: config.maximumBaseFee
});
}
/// @notice Proves a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
/// @param _l2OutputIndex L2 output index to prove against.
/// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root.
/// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract.
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _l2OutputIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
) external whenNotPaused {
// Prevent users from creating a deposit transaction where this address is the message
// sender on L2. Because this is checked here, we do not need to check again in
// `finalizeWithdrawalTransaction`.
if (_tx.target == address(this)) revert BadTarget();
// Get the output root and load onto the stack to prevent multiple mloads. This will
// revert if there is no output root for the given block number.
bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot;
// Verify that the output root can be generated with the elements in the proof.
require(
outputRoot == Hashing.hashOutputRootProof(_outputRootProof),
"OptimismPortal: invalid output root proof"
);
// Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[
withdrawalHash
];
// We generally want to prevent users from proving the same withdrawal multiple times
// because each successive proof will update the timestamp. A malicious user can take
// advantage of this to prevent other users from finalizing their withdrawal. However,
// since withdrawals are proven before an output root is finalized, we need to allow users
// to re-prove their withdrawal only in the case that the output root for their specified
// output index has been updated.
require(
provenWithdrawal.timestamp == 0 ||
l2Oracle
.getL2Output(provenWithdrawal.l2OutputIndex)
.outputRoot !=
provenWithdrawal.outputRoot,
"OptimismPortal: withdrawal hash has already been proven"
);
// Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
// Refer to the Solidity documentation for more information on how storage layouts are
// computed for mappings.
bytes32 storageKey = keccak256(
abi.encode(
withdrawalHash,
uint256(0) // The withdrawals mapping is at the first slot in the layout.
)
);
// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
// on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
// bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
// be relayed on L1.
require(
SecureMerkleTrie.verifyInclusionProof({
_key: abi.encode(storageKey),
_value: hex"01",
_proof: _withdrawalProof,
_root: _outputRootProof.messagePasserStorageRoot
}),
"OptimismPortal: invalid withdrawal inclusion proof"
);
// Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and
// `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be
// proven once unless it is submitted again with a different outputRoot.
provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
outputRoot: outputRoot,
timestamp: uint128(block.timestamp),
l2OutputIndex: uint128(_l2OutputIndex)
});
// Emit a `WithdrawalProven` event.
emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
}
/// @notice Finalizes a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
function finalizeWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx
) external whenNotPaused {
// Prevent old approval abuse
require(_tx.target != FRXETH, "FraxchainPortal: can not target frxETH");
// Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
// than the default value when a withdrawal transaction is being finalized. This check is
// a defacto reentrancy guard.
if (l2Sender != Constants.DEFAULT_L2_SENDER) revert NonReentrant();
// Grab the proven withdrawal from the `provenWithdrawals` map.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[
withdrawalHash
];
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero.
require(
provenWithdrawal.timestamp != 0,
"OptimismPortal: withdrawal has not been proven yet"
);
// As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step.
require(
provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(),
"OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
);
// A proven withdrawal must wait at least the finalization period before it can be
// finalized. This waiting period can elapse in parallel with the waiting period for the
// output the withdrawal was proven against. In effect, this means that the minimum
// withdrawal time is proposal submission time + finalization period.
require(
_isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
"OptimismPortal: proven withdrawal finalization period has not elapsed"
);
// Grab the OutputProposal from the L2OutputOracle, will revert if the output that
// corresponds to the given index has not been proposed yet.
Types.OutputProposal memory proposal = l2Oracle.getL2Output(
provenWithdrawal.l2OutputIndex
);
// Check that the output root that was used to prove the withdrawal is the same as the
// current output root for the given output index. An output root may change if it is
// deleted by the challenger address and then re-proposed.
require(
proposal.outputRoot == provenWithdrawal.outputRoot,
"OptimismPortal: output root proven is not the same as current output root"
);
// Check that the output proposal has also been finalized.
require(
_isFinalizationPeriodElapsed(proposal.timestamp),
"OptimismPortal: output proposal finalization period has not elapsed"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
require(
finalizedWithdrawals[withdrawalHash] == false,
"OptimismPortal: withdrawal has already been finalized"
);
// Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true;
// Set the l2Sender so contracts know who triggered this withdrawal on L2.
// This acts as a reentrancy guard.
l2Sender = _tx.sender;
bool success;
(address token, ) = gasPayingToken();
if (token == Constants.ETHER) {
// Trigger the call to the target contract. We use a custom low level method
// SafeCall.callWithMinGas to ensure two key properties
// 1. Target contracts cannot force this call to run out of gas by returning a very large
// amount of data (and this is OK because we don't care about the returndata here).
// 2. The amount of gas provided to the execution context of the target is at least the
// gas limit specified by the user. If there is not enough gas in the current context
// to accomplish this, `callWithMinGas` will revert.
success = SafeCall.callWithMinGas(
_tx.target,
_tx.gasLimit,
_tx.value,
_tx.data
);
} else {
// Cannot call the token contract directly from the portal. This would allow an attacker
// to call approve from a withdrawal and drain the balance of the portal.
if (_tx.target == token) revert BadTarget();
// Only transfer value when a non zero value is specified. This saves gas in the case of
// using the standard bridge or arbitrary message passing.
if (_tx.value != 0) {
// Update the contracts internal accounting of the amount of native asset in L2.
_balance -= _tx.value;
// Read the balance of the target contract before the transfer so the consistency
// of the transfer can be checked afterwards.
uint256 startBalance = IERC20(token).balanceOf(address(this));
// Transfer the ERC20 balance to the target, accounting for non standard ERC20
// implementations that may not return a boolean. This reverts if the low level
// call is not successful.
IERC20(token).safeTransfer({to: _tx.target, value: _tx.value});
// The balance must be transferred exactly.
if (
IERC20(token).balanceOf(address(this)) !=
startBalance - _tx.value
) {
revert TransferFailed();
}
}
// Make a call to the target contract only if there is calldata.
if (_tx.data.length != 0) {
success = SafeCall.callWithMinGas(
_tx.target,
_tx.gasLimit,
0,
_tx.data
);
} else {
success = true;
}
}
// Reset the l2Sender back to the default value.
l2Sender = Constants.DEFAULT_L2_SENDER;
// All withdrawals are immediately finalized. Replayability can
// be achieved through contracts built on top of this contract
emit WithdrawalFinalized(withdrawalHash, success);
// Reverting here is useful for determining the exact gas cost to successfully execute the
// sub call to the target contract if the minimum gas limit specified by the user would not
// be sufficient to execute the sub call.
if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) {
revert GasEstimation();
}
}
/// @notice Entrypoint to depositing an ERC20 token as a custom gas token.
/// This function depends on a well formed ERC20 token. There are only
/// so many checks that can be done on chain for this so it is assumed
/// that chain operators will deploy chains with well formed ERC20 tokens.
/// @param _to Target address on L2.
/// @param _mint Units of ERC20 token to deposit into L2.
/// @param _value Units of ERC20 token to send on L2 to the recipient.
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
function depositERC20Transaction(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
) public metered(_gasLimit) {
// Can only be called if an ERC20 token is used for gas paying on L2
(address token, ) = gasPayingToken();
if (token == Constants.ETHER) revert OnlyCustomGasToken();
// Gives overflow protection for L2 account balances.
_balance += _mint;
// Get the balance of the portal before the transfer.
uint256 startBalance = IERC20(token).balanceOf(address(this));
// Take ownership of the token. It is assumed that the user has given the portal an approval.
IERC20(token).safeTransferFrom({
from: msg.sender,
to: address(this),
value: _mint
});
// Double check that the portal now has the exact amount of token.
if (IERC20(token).balanceOf(address(this)) != startBalance + _mint) {
revert TransferFailed();
}
_depositTransaction({
_to: _to,
_mint: _mint,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in
/// deriving deposit transactions. Note that if a deposit is made by a contract, its
/// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider
/// using the CrossDomainMessenger contracts for a simpler developer experience.
/// @param _to Target address on L2.
/// @param _value ETH value to send to the recipient.
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
function depositTransaction(
address _to,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
) public payable metered(_gasLimit) {
(address token, ) = gasPayingToken();
if (token != Constants.ETHER && msg.value != 0) revert NoValue();
_depositTransaction({
_to: _to,
_mint: msg.value,
_value: _value,
_gasLimit: _gasLimit,
_isCreation: _isCreation,
_data: _data
});
}
/// @notice Common logic for creating deposit transactions.
/// @param _to Target address on L2.
/// @param _mint Units of asset to deposit into L2.
/// @param _value Units of asset to send on L2 to the recipient.
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
function _depositTransaction(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes memory _data
) internal {
// Just to be safe, make sure that people specify address(0) as the target when doing
// contract creations.
if (_isCreation && _to != address(0)) revert BadTarget();
// Prevent depositing transactions that have too small of a gas limit. Users should pay
// more for more resource usage.
if (_gasLimit < minimumGasLimit(uint64(_data.length)))
revert SmallGasLimit();
// Prevent the creation of deposit transactions that have too much calldata. This gives an
// upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
// that the transaction can fit into the p2p network policy of 128kb even though deposit
// transactions are not gossipped over the p2p network.
if (_data.length > 120_000) revert LargeCalldata();
// Transform the from-address to its alias if the caller is a contract.
address from = msg.sender;
if (msg.sender != tx.origin) {
from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
}
// Compute the opaque data that will be emitted as part of the TransactionDeposited event.
// We use opaque data so that we can update the TransactionDeposited event in the future
// without breaking the current interface.
bytes memory opaqueData = abi.encodePacked(
_mint,
_value,
_gasLimit,
_isCreation,
_data
);
// Emit a TransactionDeposited event so that the rollup node can derive a deposit
// transaction for this deposit.
emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
}
/// @notice Sets the gas paying token for the L2 system. This token is used as the
/// L2 native asset. Only the SystemConfig contract can call this function.
function setGasPayingToken(
address _token,
uint8 _decimals,
bytes32 _name,
bytes32 _symbol
) external {
if (msg.sender != address(systemConfig)) revert Unauthorized();
// Set L2 deposit gas as used without paying burning gas. Ensures that deposits cannot use too much L2 gas.
// This value must be large enough to cover the cost of calling `L1Block.setGasPayingToken`.
useGas(SYSTEM_DEPOSIT_GAS_LIMIT);
// Emit the special deposit transaction directly that sets the gas paying
// token in the L1Block predeploy contract.
emit TransactionDeposited(
Constants.DEPOSITOR_ACCOUNT,
Predeploys.L1_BLOCK_ATTRIBUTES,
DEPOSIT_VERSION,
abi.encodePacked(
uint256(0), // mint
uint256(0), // value
uint64(SYSTEM_DEPOSIT_GAS_LIMIT), // gasLimit
false, // isCreation,
abi.encodeCall(
IL1Block.setGasPayingToken,
(_token, _decimals, _name, _symbol)
)
)
);
}
/// @notice Determine if a given output is finalized.
/// Reverts if the call to l2Oracle.getL2Output reverts.
/// Returns a boolean otherwise.
/// @param _l2OutputIndex Index of the L2 output to check.
/// @return Whether or not the output is finalized.
function isOutputFinalized(
uint256 _l2OutputIndex
) external view returns (bool) {
return
_isFinalizationPeriodElapsed(
l2Oracle.getL2Output(_l2OutputIndex).timestamp
);
}
/// @notice Determines whether the finalization period has elapsed with respect to
/// the provided block timestamp.
/// @param _timestamp Timestamp to check.
/// @return Whether or not the finalization period has elapsed.
function _isFinalizationPeriodElapsed(
uint256 _timestamp
) internal view returns (bool) {
return
block.timestamp >
_timestamp + l2Oracle.FINALIZATION_PERIOD_SECONDS();
}
}
"
},
"lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/Address.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!Address.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original
* initialization step. This is essential to configure modules that are added through upgrades and that require
* initialization.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized < type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
}
"
},
"src/L1/ResourceMetering.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Contracts
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
// Libraries
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
import { Burn } from "src/libraries/Burn.sol";
import { Arithmetic } from "src/libraries/Arithmetic.sol";
/// @custom:upgradeable
/// @title ResourceMetering
/// @notice ResourceMetering implements an EIP-1559 style resource metering system where pricing
/// updates automatically based on current demand.
abstract contract ResourceMetering is Initializable {
/// @notice Error returned when too much gas resource is consumed.
error OutOfGas();
/// @notice Represents the various parameters that control the way in which resources are
/// metered. Corresponds to the EIP-1559 resource metering system.
/// @custom:field prevBaseFee Base fee from the previous block(s).
/// @custom:field prevBoughtGas Amount of gas bought so far in the current block.
/// @custom:field prevBlockNum Last block number that the base fee was updated.
struct ResourceParams {
uint128 prevBaseFee;
uint64 prevBoughtGas;
uint64 prevBlockNum;
}
/// @notice Represents the configuration for the EIP-1559 based curve for the deposit gas
/// market. These values should be set with care as it is possible to set them in
/// a way that breaks the deposit gas market. The target resource limit is defined as
/// maxResourceLimit / elasticityMultiplier. This struct was designed to fit within a
/// single word. There is additional space for additions in the future.
/// @custom:field maxResourceLimit Represents the maximum amount of deposit gas that
/// can be purchased per block.
/// @custom:field elasticityMultiplier Determines the target resource limit along with
/// the resource limit.
/// @custom:field baseFeeMaxChangeDenominator Determines max change on fee per block.
/// @custom:field minimumBaseFee The min deposit base fee, it is clamped to this
/// value.
/// @custom:field systemTxMaxGas The amount of gas supplied to the system
/// transaction. This should be set to the same
/// number that the op-node sets as the gas limit
/// for the system transaction.
/// @custom:field maximumBaseFee The max deposit base fee, it is clamped to this
/// value.
struct ResourceConfig {
uint32 maxResourceLimit;
uint8 elasticityMultiplier;
uint8 baseFeeMaxChangeDenominator;
uint32 minimumBaseFee;
uint32 systemTxMaxGas;
uint128 maximumBaseFee;
}
/// @notice EIP-1559 style gas parameters.
ResourceParams public params;
/// @notice Reserve extra slots (to a total of 50) in the storage layout for future upgrades.
uint256[48] private __gap;
/// @notice Meters access to a function based an amount of a requested resource.
/// @param _amount Amount of the resource requested.
modifier metered(uint64 _amount) {
// Record initial gas amount so we can refund for it later.
uint256 initialGas = gasleft();
// Run the underlying function.
_;
// Run the metering function.
_metered(_amount, initialGas);
}
/// @notice An internal function that holds all of the logic for metering a resource.
/// @param _amount Amount of the resource requested.
/// @param _initialGas The amount of gas before any modifier execution.
function _metered(uint64 _amount, uint256 _initialGas) internal {
// Update block number and base fee if necessary.
uint256 blockDiff = block.number - params.prevBlockNum;
ResourceConfig memory config = _resourceConfig();
int256 targetResourceLimit =
int256(uint256(config.maxResourceLimit)) / int256(uint256(config.elasticityMultiplier));
if (blockDiff > 0) {
// Handle updating EIP-1559 style gas parameters. We use EIP-1559 to restrict the rate
// at which deposits can be created and therefore limit the potential for deposits to
// spam the L2 system. Fee scheme is very similar to EIP-1559 with minor changes.
int256 gasUsedDelta = int256(uint256(params.prevBoughtGas)) - targetResourceLimit;
int256 baseFeeDelta = (int256(uint256(params.prevBaseFee)) * gasUsedDelta)
/ (targetResourceLimit * int256(uint256(config.baseFeeMaxChangeDenominator)));
// Update base fee by adding the base fee delta and clamp the resulting value between
// min and max.
int256 newBaseFee = Arithmetic.clamp({
_value: int256(uint256(params.prevBaseFee)) + baseFeeDelta,
_min: int256(uint256(config.minimumBaseFee)),
_max: int256(uint256(config.maximumBaseFee))
});
// If we skipped more than one block, we also need to account for every empty block.
// Empty block means there was no demand for deposits in that block, so we should
// reflect this lack of demand in the fee.
if (blockDiff > 1) {
// Update the base fee by repeatedly applying the exponent 1-(1/change_denominator)
// blockDiff - 1 times. Simulates multiple empty blocks. Clamp the resulting value
// between min and max.
newBaseFee = Arithmetic.clamp({
_value: Arithmetic.cdexp({
_coefficient: newBaseFee,
_denominator: int256(uint256(config.baseFeeMaxChangeDenominator)),
_exponent: int256(blockDiff - 1)
}),
_min: int256(uint256(config.minimumBaseFee)),
_max: int256(uint256(config.maximumBaseFee))
});
}
// Update new base fee, reset bought gas, and update block number.
params.prevBaseFee = uint128(uint256(newBaseFee));
params.prevBoughtGas = 0;
params.prevBlockNum = uint64(block.number);
}
// Make sure we can actually buy the resource amount requested by the user.
params.prevBoughtGas += _amount;
if (int256(uint256(params.prevBoughtGas)) > int256(uint256(config.maxResourceLimit))) {
revert OutOfGas();
}
// Determine the amount of ETH to be paid.
uint256 resourceCost = uint256(_amount) * uint256(params.prevBaseFee);
// We currently charge for this ETH amount as an L1 gas burn, so we convert the ETH amount
// into gas by dividing by the L1 base fee. We assume a minimum base fee of 1 gwei to avoid
// division by zero for L1s that don't support 1559 or to avoid excessive gas burns during
// periods of extremely low L1 demand. One-day average gas fee hasn't dipped below 1 gwei
// during any 1 day period in the last 5 years, so should be fine.
uint256 gasCost = resourceCost / Math.max(block.basefee, 1 gwei);
// Give the user a refund based on the amount of gas they used to do all of the work up to
// this point. Since we're at the end of the modifier, this should be pretty accurate. Acts
// effectively like a dynamic stipend (with a minimum value).
uint256 usedGas = _initialGas - gasleft();
if (gasCost > usedGas) {
Burn.gas(gasCost - usedGas);
}
}
/// @notice Adds an amount of L2 gas consumed to the prev bought gas params. This is meant to be used
/// when L2 system transactions are generated from L1.
/// @param _amount Amount of the L2 gas resource requested.
function useGas(uint32 _amount) internal {
params.prevBoughtGas += uint64(_amount);
}
/// @notice Virtual function that returns the resource config.
/// Contracts that inherit this contract must implement this function.
/// @return ResourceConfig
function _resourceConfig() internal virtual returns (ResourceConfig memory);
/// @notice Sets initial resource parameter values.
/// This function must either be called by the initializer function of an upgradeable
/// child contract.
function __ResourceMetering_init() internal onlyInitializing {
if (params.prevBlockNum == 0) {
params = ResourceParams({ prevBaseFee: 1 gwei, prevBoughtGas: 0, prevBlockNum: uint64(block.number) });
}
}
}
"
},
"lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
"
},
"src/libraries/SafeCall.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title SafeCall
/// @notice Perform low level safe calls
library SafeCall {
/// @notice Performs a low level call without copying any returndata.
/// @dev Passes no calldata to the call context.
/// @param _target Address to call
/// @param _gas Amount of gas to pass to the call
/// @param _value Amount of value to pass to the call
function send(address _target, uint256 _gas, uint256 _value) internal returns (bool success_) {
assembly {
success_ :=
call(
_gas, // gas
_target, // recipient
_value, // ether value
0, // inloc
0, // inlen
0, // outloc
0 // outlen
)
}
}
/// @notice Perform a low level call with all gas without copying any returndata
/// @param _target Address to call
/// @param _value Amount of value to pass to the call
function send(address _target, uint256 _value) internal returns (bool success_) {
success_ = send(_target, gasleft(), _value);
}
/// @notice Perform a low level call without copying any returndata
/// @param _target Address to call
/// @param _gas Amount of gas to pass to the call
/// @param _value Amount of value to pass to the call
/// @param _calldata Calldata to pass to the call
function call(
address _target,
uint256 _gas,
uint256 _value,
bytes memory _calldata
)
internal
returns (bool success_)
{
assembly {
success_ :=
call(
_gas, // gas
_target, // recipient
_value, // ether value
add(_calldata, 32), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
}
}
/// @notice Perform a low level call without copying any returndata
/// @param _target Address to call
/// @param _value Amount of value to pass to the call
/// @param _calldata Calldata to pass to the call
function call(address _target, uint256 _value, bytes memory _calldata) internal returns (bool success_) {
success_ = call({ _target: _target, _gas: gasleft(), _value: _value, _calldata: _calldata });
}
/// @notice Perform a low level call without copying any returndata
/// @param _target Address to call
/// @param _calldata Calldata to pass to the call
function call(address _target, bytes memory _calldata) internal returns (bool success_) {
success_ = call({ _target: _target, _gas: gasleft(), _value: 0, _calldata: _calldata });
}
/// @notice Helper function to determine if there is sufficient gas remaining within the context
/// to guarantee that the minimum gas requirement for a call will be met as well as
/// optionally reserving a specified amount of gas for after the call has concluded.
/// @param _minGas The minimum amount of gas that may be passed to the target context.
/// @param _reservedGas Optional amount of gas to reserve for the caller after the execution
/// of the target context.
/// @return `true` if there is enough gas remaining to safely supply `_minGas` to the target
/// context as well as reserve `_reservedGas` for the caller after the execution of
/// the target context.
/// @dev !!!!! FOOTGUN ALERT !!!!!
/// 1.) The 40_000 base buffer is to account for the worst case of the dynamic cost of the
/// `CALL` opcode's `address_access_cost`, `positive_value_cost`, and
/// `value_to_empty_account_cost` factors with an added buffer of 5,700 gas. It is
/// still possible to self-rekt by initiating a withdrawal with a minimum gas limit
/// that does not account for the `memory_expansion_cost` & `code_execution_cost`
/// factors of the dynamic cost of the `CALL` opcode.
/// 2.) This function should *directly* precede the external call if possible. There is an
/// added buffer to account for gas consumed between this check and the call, but it
/// is only 5,700 gas.
/// 3.) Because EIP-150 ensures that a maximum of 63/64ths of the remaining gas in the call
/// frame may be passed to a subcontext, we need to ensure that the gas will not be
/// truncated.
/// 4.) Use wisely. This function is not a silver bullet.
function hasMinGas(uint256 _minGas, uint256 _reservedGas) internal view returns (bool) {
bool _hasMinGas;
assembly {
// Equation: gas × 63 ≥ minGas × 64 + 63(40_000 + reservedGas)
_hasMinGas := iszero(lt(mul(gas(), 63), add(mul(_minGas, 64), mul(add(40000, _reservedGas), 63))))
}
return _hasMinGas;
}
/// @notice Perform a low level call without copying any returndata. This function
/// will revert if the call cannot be performed with the specified minimum
/// gas.
/// @param _target Address to call
/// @param _minGas The minimum amount of gas that may be passed to the call
/// @param _value Amount of value to pass to the call
/// @param _calldata Calldata to pass to the call
function callWithMinGas(
address _target,
uint256 _minGas,
uint256 _value,
bytes memory _calldata
)
internal
returns (bool)
{
bool _success;
bool _hasMinGas = hasMinGas(_minGas, 0);
assembly {
// Assertion: gasleft() >= (_minGas * 64) / 63 + 40_000
if iszero(_hasMinGas) {
// Store the "Error(string)" selector in scratch space.
mstore(0, 0x08c379a0)
// Store the pointer to the string length in scratch space.
mstore(32, 32)
// Store the string.
//
// SAFETY:
// - We pad the beginning of the string with two zero bytes as well as the
// length (24) to ensure that we override the free memory pointer at offset
// 0x40. This is necessary because the free memory pointer is likely to
// be greater than 1 byte when this function is called, but it is incredibly
// unlikely that it will be greater than 3 bytes. As for the data within
// 0x60, it is ensured that it is 0 due to 0x60 being the zero offset.
// - It's fine to clobber the free memory pointer, we're reverting.
mstore(88, 0x0000185361666543616c6c3a204e6f7420656e6f75676820676173)
// Revert with 'Error("SafeCall: Not enough gas")'
revert(28, 100)
}
// The call will be supplied at least ((_minGas * 64) / 63) gas due to the
// above assertion. This ensures that, in all circumstances (except for when the
// `_minGas` does not account for the `memory_expansion_cost` and `code_execution_cost`
// factors of the dynamic cost of the `CALL` opcode), the call will receive at least
// the minimum amount of gas specified.
_success :=
call(
gas(), // gas
_target, // recipient
_value, // ether value
add(_calldata, 32), // inloc
mload(_calldata), // inlen
0x00, // outloc
0x00 // outlen
)
}
return _success;
}
}
"
},
"src/libraries/Constants.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// Interfaces
import { IResourceMetering } from "interfaces/L1/IResourceMetering.sol";
/// @title Constants
/// @notice Constants is a library for storing constants. Simple! Don't put everything in here, just
/// the stuff used in multiple contracts. Constants that only apply to a single contract
/// should be defined in that contract instead.
library Constants {
/// @notice Special address to be used as the tx origin for gas estimation calls in the
/// OptimismPortal and CrossDomainMessenger calls. You only need to use this address if
/// the minimum gas limit specified by the user is not actually enough to execute the
/// given message and you're attempting to estimate the actual necessary gas limit. We
/// use address(1) because it's the ecrecover precompile and therefore guaranteed to
/// never have any code on any EVM chain.
address internal constant ESTIMATION_ADDRESS = address(1);
/// @notice Value used for the L2 sender storage slot in both the OptimismPortal and the
/// CrossDomainMessenger contracts before an actual sender is set. This value is
/// non-zero to reduce the gas cost of message passing transactions.
address internal constant DEFAULT_L2_SENDER = 0x000000000000000000000000000000000000dEaD;
/// @notice The storage slot that holds the address of a proxy implementation.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)`
bytes32 internal constant PROXY_IMPLEMENTATION_ADDRESS =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @notice The storage slot that holds the address of the owner.
/// @dev `bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1)`
bytes32 internal constant PROXY_OWNER_ADDRESS = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/// @notice The address that represents ether when dealing with ERC20 token addresses.
address internal constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
/// @notice The address that represents the system caller responsible for L1 attributes
/// transactions.
address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001;
/// @notice Returns the default values for the ResourceConfig. These are the recommended values
/// for a production network.
function DEFAULT_RESOURCE_CONFIG() internal pure returns (IResourceMetering.ResourceConfig memory) {
IResourceMetering.ResourceConfig memory config = IResourceMetering.ResourceConfig({
maxResourceLimit: 20_000_000,
elasticityMultiplier: 10,
baseFeeMaxChangeDenominator: 8,
minimumBaseFee: 1 gwei,
systemTxMaxGas: 1_000_000,
maximumBaseFee: type(uint128).max
});
return config;
}
}
"
},
"src/libraries/Types.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title Types
/// @notice Contains various types used throughout the Optimism contract system.
library Types {
/// @notice OutputProposal represents a commitment to the L2 state. The timestamp is the L1
/// timestamp that the output root is posted. This timestamp is used to verify that the
/// finalization period has passed since the output root was submitted.
/// @custom:field outputRoot Hash of the L2 output.
/// @custom:field timestamp Timestamp of the L1 block that the output root was submitted in.
/// @custom:field l2BlockNumber L2 block number that the output corresponds to.
struct OutputProposal {
bytes32 outputRoot;
uint128 timestamp;
uint128 l2BlockNumber;
}
/// @notice Struct representing the elements that are hashed together to generate an output root
/// which itself represents a snapshot of the L2 state.
/// @custom:field version Version of the output root.
/// @custom:field stateRoot Root of the state trie at the block of this output.
/// @custom:field messagePasserStorageRoot Root of the message passer storage trie.
/// @custom:field latestBlockhash Hash of the block this output was generated from.
struct OutputRootProof {
bytes32 version;
bytes32 stateRoot;
bytes32 messagePasserStorageRoot;
bytes32 latestBlockhash;
}
/// @notice Struct representing a deposit transaction (L1 => L2 transaction) created by an end
/// user (as opposed to a system deposit transaction generated by the system).
/// @custom:field from Address of the sender of the transaction.
/// @custom:field to Address of the recipient of the transaction.
/// @custom:field isCreation True if the transaction is a contract creation.
/// @custom:field value Value to send to the recipient.
/// @custom:field mint Amount of ETH to mint.
/// @custom:field gasLimit Gas limit of the transaction.
/// @custom
Submitted on: 2025-09-21 02:07:49
Comments
Log in to comment.
No comments yet.