Deposited transactions, also known as deposits are transactions which are initiated on L1, and executed on L2. This document outlines a new transaction type for deposits. It also describes how deposits are initiated on L1, along with the authorization and validation conditions on L2.
Vocabulary note: deposited transaction refers specifically to an L2 transaction, while deposit can refer to the transaction at various stages (for instance when it is deposited on L1).
Table of Contents
- The Deposited Transaction Type
- Deposit Receipt
- L1 Attributes Deposited Transaction
- Special Accounts on L2
- User-Deposited Transactions
Deposited transactions have the following notable distinctions from existing transaction types:
- They are derived from Layer 1 blocks, and must be included as part of the protocol.
- They do not include signature validation (see User-Deposited Transactions for the rationale).
- They buy their L2 gas on L1 and, as such, the L2 gas is not refundable.
We define a new EIP-2718 compatible transaction type with the prefix 0x7E
to represent a deposit transaction.
A deposit has the following fields (rlp encoded in the order they appear here):
bytes32 sourceHash
: the source-hash, uniquely identifies the origin of the deposit.address from
: The address of the sender account.address to
: The address of the recipient account, or the null (zero-length) address if the deposited transaction is a contract creation.uint256 mint
: The ETH value to mint on L2.uint256 value
: The ETH value to send to the recipient account.uint64 gas
: The gas limit for the L2 transaction.bool isSystemTx
: If true, the transaction does not interact with the L2 block gas pool.- Note: boolean is disabled (enforced to be
false
) starting from the Regolith upgrade.
- Note: boolean is disabled (enforced to be
bytes data
: The calldata.
In contrast to EIP-155 transactions, this transaction type:
- Does not include a
nonce
, since it is identified by thesourceHash
. API responses still include anonce
attribute:- Before Regolith: the
nonce
is always0
- With Regolith: the
nonce
is set to thedepositNonce
attribute of the corresponding transaction receipt.
- Before Regolith: the
- Does not include signature information, and makes the
from
address explicit. API responses contain zeroed signaturev
,r
,s
values for backwards compatibility. - Includes new
sourceHash
,from
,mint
, andisSystemTx
attributes. API responses contain these as additional fields.
We select 0x7E
because transaction type identifiers are currently allowed to go up to 0x7F
.
Picking a high identifier minimizes the risk that the identifier will be used be claimed by another
transaction type on the L1 chain in the future. We don't pick 0x7F
itself in case it becomes used
for a variable-length encoding scheme.
The sourceHash
of a deposit transaction is computed based on the origin:
- User-deposited:
keccak256(bytes32(uint256(0)), keccak256(l1BlockHash, bytes32(uint256(l1LogIndex))))
. Where thel1BlockHash
, andl1LogIndex
all refer to the inclusion of the deposit log event on L1.l1LogIndex
is the index of the deposit event log in the combined list of log events of the block. - L1 attributes deposited:
keccak256(bytes32(uint256(1)), keccak256(l1BlockHash, bytes32(uint256(seqNumber))))
. Wherel1BlockHash
refers to the L1 block hash of which the info attributes are deposited. AndseqNumber = l2BlockNum - l2EpochStartBlockNum
, wherel2BlockNum
is the L2 block number of the inclusion of the deposit tx in L2, andl2EpochStartBlockNum
is the L2 block number of the first L2 block in the epoch. - Upgrade-deposited:
keccak256(bytes32(uint256(2)), keccak256(intent))
. Whereintent
is a UTF-8 byte string, identifying the upgrade intent.
Without a sourceHash
in a deposit, two different deposited transactions could have the same exact hash.
The outer keccak256
hashes the actual uniquely identifying information with a domain,
to avoid collisions between different types of sources.
We do not use the sender's nonce to ensure uniqueness because this would require an extra L2 EVM state read from the execution engine during block-derivation.
Although we define only one new transaction type, we can distinguish between two kinds of deposited transactions, based on their positioning in the L2 block:
- The first transaction MUST be a L1 attributes deposited transaction, followed by
- an array of zero-or-more user-deposited transactions
submitted to the deposit feed contract on L1 (called
OptimismPortal
). User-deposited transactions are only present in the first block of a L2 epoch.
We only define a single new transaction type in order to minimize modifications to L1 client software, and complexity in general.
As noted above, the deposited transaction type does not include a signature for validation. Rather,
authorization is handled by the L2 chain derivation process, which when correctly
applied will only derive transactions with a from
address attested to by the logs of the L1
deposit contract.
In order to execute a deposited transaction:
First, the balance of the from
account MUST be increased by the amount of mint
.
This is unconditional, and does not revert on deposit failure.
Then, the execution environment for a deposited transaction is initialized based on the transaction's attributes, in exactly the same manner as it would be for an EIP-155 transaction.
The deposit transaction is processed exactly like a type-3 (EIP-1559) transaction, with the exception of:
- No fee fields are verified: the deposit does not have any, as it pays for gas on L1.
- No
nonce
field is verified: the deposit does not have any, it's uniquely identified by itssourceHash
. - No access-list is processed: the deposit has no access-list, and it is thus processed as if the access-list is empty.
- No check if
from
is an Externally Owner Account (EOA): the deposit is ensured not to be an EOA through L1 address masking, this may change in future L1 contract-deployments to e.g. enable an account-abstraction like mechanism. - Before the Regolith upgrade:
- The execution output states a non-standard gas usage:
- If
isSystemTx
is false: execution output states it usesgasLimit
gas. - If
isSystemTx
is true: execution output states it uses0
gas.
- If
- The execution output states a non-standard gas usage:
- No gas is refunded as ETH. (either by not refunding or utilizing the fact the gas-price of the deposit is
0
) - No transaction priority fee is charged. No payment is made to the block fee-recipient.
- No L1-cost fee is charged, as deposits are derived from L1 and do not have to be submitted as data back to it.
- No base fee is charged. The total base fee accounting does not change.
Note that this includes contract-deployment behavior like with regular transactions, and gas metering is the same (with the exception of fee related changes above), including metering of intrinsic gas.
Any non-EVM state-transition error emitted by the EVM execution is processed in a special way:
- It is transformed into an EVM-error:
i.e. the deposit will always be included, but its receipt will indicate a failure
if it runs into a non-EVM state-transition error, e.g. failure to transfer the specified
value
amount of ETH due to insufficient account-balance. - The world state is rolled back to the start of the EVM processing, after the minting part of the deposit.
- The
nonce
offrom
in the world state is incremented by 1, making the error equivalent to a native EVM failure. Note that a previousnonce
increment may have happened during EVM processing, but this would be rolled back first.
Finally, after the above processing, the execution post-processing runs the same:
i.e. the gas pool and receipt are processed identical to a regular transaction.
Starting with the Regolith upgrade however, the receipt of deposit transactions is extended with an additional
depositNonce
value, storing the nonce
value of the from
sender as registered before the EVM processing.
Note that the gas used as stated by the execution output is subtracted from the gas pool, but this execution output value has special edge cases before the Regolith upgrade.
Note for application developers: because CALLER
and ORIGIN
are set to from
, the
semantics of using the tx.origin == msg.sender
check will not work to determine whether
or not a caller is an EOA during a deposit transaction. Instead, the check could only be useful for
identifying the first call in the L2 deposit transaction. However this check does still satisfy
the common case in which developers are using this check to ensure that the CALLER
is unable to
execute code before and after the call.
Despite the lack of signature validation, we still increment the nonce of the from
account when a
deposit transaction is executed. In the context of a deposit-only roll up, this is not necessary
for transaction ordering or replay prevention, however it maintains consistency with the use of
nonces during contract creation. It may also simplify integration with downstream
tooling (such as wallets and block explorers).
Transaction receipts use standard typing as per EIP-2718.
The Deposit transaction receipt type is equal to a regular receipt,
but extended with an optional depositNonce
field.
The RLP-encoded consensus-enforced fields are:
postStateOrStatus
(standard): this contains the transaction status, see EIP-658.cumulativeGasUsed
(standard): gas used in the block thus far, including this transaction.- The actual gas used is derived from the difference in
CumulativeGasUsed
with the previous transaction. - Starting with Regolith, this accounts for the actual gas usage by the deposit, like regular transactions.
- The actual gas used is derived from the difference in
bloom
(standard): bloom filter of the transaction logs.logs
(standard): log events emitted by the EVM processing.depositNonce
(unique extension): Optional field. The deposit transaction persists the nonce used during execution.depositNonceVersion
(unique extension): Optional field. The value must be 1 if the field is present- Before Canyon, these
depositNonce
&depositNonceVersion
fields must always be omitted. - With Canyon, these
depositNonce
&depositNonceVersion
fields must always be included.
- Before Canyon, these
Starting with Regolith, the receipt API responses utilize the receipt changes for more accurate response data:
- The
depositNonce
is included in the receipt JSON data in API responses - For contract-deployments (when
to == null
), thedepositNonce
helps derive the correctcontractAddress
meta-data, instead of assuming the nonce was zero. - The
cumulativeGasUsed
accounts for the actual gas usage, as metered in the EVM processing.
An L1 attributes deposited transaction is a deposit transaction sent to the L1 attributes predeployed contract.
This transaction MUST have the following values:
from
is0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001
(the address of the L1 Attributes depositor account)to
is0x4200000000000000000000000000000000000015
(the address of the L1 attributes predeployed contract).mint
is0
value
is0
gasLimit
is set to 150,000,000 prior to the Regolith upgrade, and 1,000,000 after.isSystemTx
is set totrue
prior to the Regolith upgrade, andfalse
after.data
is an encoded call to the L1 attributes predeployed contract that depends on the upgrades that are active (see below).
This system-initiated transaction for L1 attributes is not charged any ETH for its allocated
gasLimit
, as it is considered part of state-transition processing.
The data
field of the L1 attributes deposited transaction is an ABI encoded call to the
setL1BlockValues()
function with correct values associated with the corresponding L1 block
(cf. reference implementation).
On the Ecotone activation block, and if Ecotone is not activated at Genesis,
the L1 Attributes Transaction includes a call to setL1BlockValues()
because the L1 Attributes transaction precedes the Ecotone Upgrade Transactions,
meaning that setL1BlockValuesEcotone
is not guaranteed to exist yet.
Every subsequent L1 Attributes transaction should include a call to the setL1BlockValuesEcotone()
function.
The input args are no longer ABI encoded function parameters,
but are instead packed into 5 32-byte aligned segments (starting after the function selector).
Each unsigned integer argument is encoded as big-endian using a number of bytes corresponding to the underlying type.
The overall calldata layout is as follows:
Input arg | Type | Calldata bytes | Segment |
---|---|---|---|
{0x440a5e20} | 0-3 | n/a | |
baseFeeScalar | uint32 | 4-7 | 1 |
blobBaseFeeScalar | uint32 | 8-11 | |
sequenceNumber | uint64 | 12-19 | |
l1BlockTimestamp | uint64 | 20-27 | |
l1BlockNumber | uint64 | 28-35 | |
basefee | uint256 | 36-67 | 2 |
blobBaseFee | uint256 | 68-99 | 3 |
l1BlockHash | bytes32 | 100-131 | 4 |
batcherHash | bytes32 | 132-163 | 5 |
Total calldata length MUST be exactly 164 bytes, implying the sixth and final segment is only partially filled. This helps to slow database growth as every L2 block includes a L1 Attributes deposit transaction.
The L1 attributes deposit transaction involves two special purpose accounts:
- The L1 attributes depositor account
- The L1 attributes predeployed contract
The depositor account is an EOA with no known private key. It has the address
0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001
. Its value is returned by the CALLER
and ORIGIN
opcodes during execution of the L1 attributes deposited transaction.
A predeployed contract on L2 at address 0x4200000000000000000000000000000000000015
, which holds
certain block variables from the corresponding L1 block in storage, so that they may be accessed
during the execution of the subsequent deposited transactions.
The predeploy stores the following values:
- L1 block attributes:
number
(uint64
)timestamp
(uint64
)basefee
(uint256
)hash
(bytes32
)
sequenceNumber
(uint64
): This equals the L2 block number relative to the start of the epoch, i.e. the L2 block distance to the L2 block height that the L1 attributes last changed, and reset to 0 at the start of a new epoch.- System configurables tied to the L1 block, see System configuration specification:
batcherHash
(bytes32
): A versioned commitment to the batch-submitter(s) currently operating.overhead
(uint256
): The L1 fee overhead to apply to L1 cost computation of transactions in this L2 block.scalar
(uint256
): The L1 fee scalar to apply to L1 cost computation of transactions in this L2 block.
- With the Ecotone upgrade, the predeploy additionally stores:
blobBaseFee
(uint256
)baseFeeScalar
(uint32
): system configurable to scale thebasefee
in the Ecotone l1 cost computationblobBasefeeScalar
(uint32
): system configurable to scale theblobBaseFee
in the Ecotone l1 cost computation
Following the Ecotone upgrade, overhead
and scalar
are frozen at the values they had on the
block immediately prior to the fork.
The contract implements an authorization scheme, such that it only accepts state-changing calls from the depositor account.
The contract has the following solidity interface, and can be interacted with according to the contract ABI specification.
A reference implementation of the L1 Attributes predeploy contract can be found in L1Block.sol.
After running pnpm build
in the packages/contracts-bedrock
directory, the bytecode to add to
the genesis file will be located in the deployedBytecode
field of the build artifacts file at
/packages/contracts-bedrock/forge-artifacts/L1Block.sol/L1Block.json
.
The L1 Attributes Predeployed contract, L1Block.sol
, is upgraded as part of the Ecotone upgrade.
The version is incremented to 1.2.0
, one new storage slot is introduced, and one existing slot
begins to store additional data:
blobBaseFee
(uint256
): The L1 blob base fee.blobBaseFeeScalar
(uint32
): The scalar value applied to the L1 blob base fee portion of the L1 cost.baseFeeScalar
(uint32
): The scalar value applied to the L1 base fee portion of the L1 cost.
Additionally, the setL1BlockValues
function is deprecated and MUST never be called when the L2 block number
is greater than the Ecotone activation block number. setL1BlockValues
MUST be called on the Ecotone hardfork
activation block, except if activated at genesis.
The setL1BlockValuesEcotone
MUST be called when the L2 block number is greater than the Ecotone hardfork
activation block.
setL1BlockValuesEcotone
uses a tightly packed encoding for its parameters, which is described in
L1 Attributes Deposited Transaction Calldata.
User-deposited transactions are deposited transactions
generated by the L2 Chain Derivation process. The content of each user-deposited
transaction are determined by the corresponding TransactionDeposited
event emitted by the
deposit contract on L1.
from
is unchanged from the emitted value (though it may have been transformed to an alias inOptimismPortal
, the deposit feed contract).to
is any 20-byte address (including the zero address)- In case of a contract creation (cf.
isCreation
), this address is set tonull
.
- In case of a contract creation (cf.
mint
is set to the emitted value.value
is set to the emitted value.gaslimit
is unchanged from the emitted value. It must be at least 21000.isCreation
is set totrue
if the transaction is a contract creation,false
otherwise.data
is unchanged from the emitted value. Depending on the value ofisCreation
it is handled as either calldata or contract initialization code.isSystemTx
is set by the rollup node for certain transactions that have unmetered execution. It isfalse
for user deposited transactions
The deposit contract is deployed to L1. Deposited transactions are derived from the values in
the TransactionDeposited
event(s) emitted by the deposit contract.
The deposit contract is responsible for maintaining the guaranteed gas market, charging deposits for gas to be used on L2, and ensuring that the total amount of guaranteed gas in a single L1 block does not exceed the L2 block gas limit.
The deposit contract handles two special cases:
- A contract creation deposit, which is indicated by setting the
isCreation
flag totrue
. In the event that theto
address is non-zero, the contract will revert. - A call from a contract account, in which case the
from
value is transformed to its L2 alias.
If the caller is a contract, the address will be transformed by adding
0x1111000000000000000000000000000000001111
to it. The math is unchecked
and done on a
Solidity uint160
so the value will overflow. This prevents attacks in which a
contract on L1 has the same address as a contract on L2 but doesn't have the same code. We can safely ignore this
for EOAs because they're guaranteed to have the same "code" (i.e. no code at all). This also makes
it possible for users to interact with contracts on L2 even when the Sequencer is down.
A reference implementation of the deposit contract can be found in OptimismPortal.sol.