336 Views
July 02, 25
スライド概要
[EthCC 2025] We present an L1<>L2 composablity solution that allows users to seamlessly deposit, transact, and withdraw across L1 and L2 in one atomic bundle within the same L1 slot.
都内在住のエンジニア
Same Slot L1 <> L2 Composability Lin Oshitani (Nethermind)
Suppose you have assets in L1. Now suppose you want to: 1. Deposit into an L2 2. Do a complex DEX trade in L2 3. Withdraw back to L1
1. Deposit into an L2 <= 2-5 min 2. Do a complex DEX trade in L2 3. Withdraw back to L1 <= 30min ~ 7 days
1. Deposit into an L2 <= 2-5 min 2. Do a complex DEX trade in L2 3. Withdraw back to L1 <= 30min ~ 7 days L2s, which are supposed to scale the L1, feel like a completely different execution environment
What if you can do: 1. Deposit into an L2 2. Do a complex DEX trade in L2 3. Withdraw back to L1 All in the same L1 slot? Make L2s feel much more like a natural extention of L1
Background: L1 and L2
The L1 block is a list of L1 txs L1 block tx tx tx
The L1 state stores account balances and smart contract state L1 state Alice DEX Carol Bob L1 block tx tx tx
L1 txs in the block changes L1 state Alice 1 ETH DEX Carol Bob Alice sends Bob 1 ETH tx tx tx
L1 provides a single thread of synchronous execution. Alice 1 ETH 1 ETH DEX Carol Bob tx tx tx
This is possible because L1 txs can depend on the result of the previous L1 tx tx tx dependencies tx
L2s (rollups) embed L2 blocks within the L1 block. L1 block tx tx L2 block tx tx tx tx
L2 txs are posted to the "L2 inbox" contract in L1 L2 inbox tx tx tx tx tx tx
L2 introduce a separate layer for L2 state/execution L2 state L1 state L2 inbox tx tx tx tx tx tx
L2 txs are sent to L2 and executed only in L2. L2s scale L1 by outsourcing execution to the L2. L2 state tx tx tx L1 state L2 inbox tx tx tx tx tx tx
However, L1 and L2 are now different threads of execution/dependencies. How do we introduce dependency between L2 txs and L1 txs? tx tx tx tx tx tx
One way is to inject past L1 state roots into L2. However, L1 state root of an L1 slot is not available during that slot, hence "same-slot" is not possible.
Same Slot L1->L2 Message Passing
We want to have L2 txs depend on an L1 tx (e.g., deposit) in the same L1 slot. deposit tx tx tx tx tx
L1 tx calls into an L1 messeging contract to store messages (e.g., deposits). Contract stores the hashes of messages. Message service L2 inbox [hashes] [messages] (e.g. deposit) tx tx tx tx tx tx
L2 batch submission specifies message hashes that the batch depends upon. Message service Message service L2 inbox [hashes] [hashes] tx tx tx tx tx tx
The batch submission reverts if any of the messages are missing. Message service condition on [hashes] Message service L2 inbox [hashes] [hashes] tx tx tx tx tx tx
The hashes are then pushed to the L2 messaging service Message service [hashes] [hashes] , tx tx tx condition on [hashes] Message service L2 inbox [hashes] [hashes] tx tx tx tx tx tx
L2 txs can now depend on the messages injected in the same L1 slot. Message service [hashes]? [hashes] [hashes] , tx tx tx Message service L2 inbox [hashes] tx tx tx tx tx tx
We got 1 leg of the dependency in the same slot by conditioning the L2 batch to a previous L1 tx. Note that this was only possible if the L2 is willing to reorg with L1, hence most likely "based". This enables same-slot L1->L2 deposits. How about the other leg? deposit tx tx tx tx tx
Fast (and Slow) L2→L1 Withdrawals
Suppose L2 batch contains an L2->L1 withdrawal tx. tx tx tx tx tx with draw
Withdrawal tx sends asset to withdraw contract in L2. 1 ETH A withdraw contract tx tx with draw L2 inbox tx tx tx tx tx with draw
L2 state is eventually settled in L1, unlocking the deposited tokens. This is the "slow" path for withdrawal. A 1 ETH withdraw contract Settle Deposit contract L2 inbox tx tx tx tx tx with draw 1 ETH A
How can we make withdrawals faster, in the same-slot? One way is real-time proving, but when/how is unclear. Another way is to rely on solvers.
Solver backruns the L2 batch submission (which incl withdrawal) with solver tx. solver tx tx tx tx tx with draw
L2 inbox stores the hash of the L2 batch. A withdraw contract Store [L2 batch hash] Solver Contract L2 inbox [L2 batch hash] solver tx tx tx tx tx with draw
Withdrawal is processed in L2 A 1 ETH withdraw contract tx tx with draw Store [L2 batch hash] Solver Contract L2 inbox [L2 batch hash] solver tx tx tx tx tx with draw
Solver fronts the withdrawed liquidity to user. Solver can condition their solver tx to the L2 batch hash, hence condition to the withdrawal happening in L2. A 1 ETH withdraw contract tx tx Condition on [L2 batch hash] with draw Solver Contract L2 inbox [L2 batch hash] 1 ETH A 1 ETH S [L2 batch hash] solver tx tx tx tx tx with draw
Solver gets compensated through the slow path (plus some tip) A 1 ETH withdraw contract Settle Deposit contract L2 inbox Solver Contract tx tx tx tx tx with draw Check if solved 1 ETH S
Now combine this with L1->L2 solution, revert protection or shared sequencer/builder between L1 and L2 (= based rollup) and we got a nice atomic bundle of L1<>L2 composable txs! deposit tx Solver tx tx tx with draw
Conclusion - Dependency and conditioning is key to composability. - Based rollups are great for L1<>L2 composability. - Can make boundaries between L1 and L2 virtually disappear - We are implementing these solutions (and more!) in Taiko and Surge.
References: Same-Slot L1→L2 Message Passing https://ethresear.ch/t/same-slot-l1-l2-message-passing/21186 Fast (and Slow) L2→L1 Withdrawals https://ethresear.ch/t/fast-and-slow-l2-l1-withdrawals/21161 Signal-Boost: L1 Interop Plugin for Rollups https://ethresear.ch/t/signal-boost-l1-interop-plugin-for-rollups/22354 ULTRA TX - Programmable blocks: One transaction is all you need for a unified and extendable Ethereum https://ethresear.ch/t/ultra-tx-programmable-blocks-one-transaction-is-all-you-need-for-a-unified-and-extendable-ethereum/21673 State Contention Rules Everything Around Me https://www.youtube.com/watch?v=QrbJbjWKNX4
End of Slides
What if you can do: 1. Deposit into an L2 2. Do a complex DEX trade in L2 3. Withdraw back to L1 All in the same L1 block (~12s)? We start with this part.
What if you can do: 1. Deposit into an L2 2. Do a complex DEX trade in L2 3. Withdraw back to L1 All in the same L1 block (~12s)? Then this part.
1 Slot Delay L1->L2 Composability with State Roots
How do we introduce dependency from L2 to L1? tx tx tx L2 inbox tx tx tx tx tx tx
L2 inbox can fetch past L1 state root and inject it into L2. , tx tx past L1 state root tx L2 inbox Fetch past L1 state root tx tx tx tx tx tx
L2 can read arbitrary L1 state using the state root and Merkle proofs. Now, L2 txs can depend on past L1 state. merkle proof , tx tx tx L2 inbox tx tx tx tx tx tx
How much "in the past" are the state root? For centralized sequencers, few minutes as they do not want to reorg with L1. For based rollups like Taiko, they do reorg with the L1, so it can be 1 L1 slot delay. => One of the very powerful feature of based rollups!
Even with based rollups, we have 1 slot delay. How can we bring this to "same slot"?