Title: RIP_7728__L1SLOAD_precompile
Category: RIPs

Author: icemelon
Date: 2024-06-26T08:24:34.868Z

This proposal introduces a new precompiled contract L1SLOAD to L2s that can load storage slots from L1 given a contract address and storage keys. The motivation is to provide a convenient and trustless way for smart contracts deployed on an L2 chain to read storage values from L1. With the L1SLOAD precompile, the developers don’t need to generate and submit MPT proofs themselves.


github.com/ethereum/RIPs








RIP 7728: L1SLOAD precompile


master ← icemelon:l1sload



          opened 08:22AM - 24 Jun 24 UTC




            icemelon
          



+121
-0






This proposal introduces a new precompiled contract `L1SLOAD` to L2s that can lo…ad storage slots from L1 given a contract address and storage keys.

Author: FilosofiaCodigo
Date: 2024-07-02T03:50:43.852Z

Being able to query L1 blocks from a precompile is great from the DevEx perspective and also from the end user perspective since it can bring new use cases with fast and cheap transactions.
I noticed that earlier versions of this proposal included the L1 block number as a parameter, but it has now been fixed to the latest block. While this precompile was primarily designed with keystores in mind, reintroducing the block number parameter could unlock additional use cases such as:

Snapshots for token governance, whitelists and airdrops. For example: only if you were a holder in a specific time in the past you will be able to vote on a proposal or claim an airdrop
ZK merkle inclusion proofs for cross-chain privacy applications. Proving that you have been part of an historic merkle tree can help saving gas because right now we have to store all historic roots in the contracts. So for example this can help saving gas if for example, you are building a L2 voting mechanism for L1 tokens holder, or a mixer where tokens are deposited in L1 and retrieved in L2.
100% on-chain DeFi data to feed financial algorithms. Access to historical block data can feed financial algorithms for DeFi applications. For instance, algorithms could be designed to react to specific market conditions, such as “sell if volatility decreases and price drops.” Projects like Maker and Aave are exploring stuff like this, for example the new Aave V4 proposal expressed the need to include different data points to their fuzzy interest rates.

I like the idea of reintroducing the block number and inviting new ways of saving gas fees. Any thoughts?

Author: toml
Date: 2024-08-13T14:23:09.009Z

@FilosofiaCodigo great points! Was thinking the same about providing an optional L1 block number, and you brought up solid usecases.
Should be possible, assuming:

as per RIP, you will need an archive L1 node for L2 node catch-up,
the L1SLOAD precompile can receive an optional block number, if none was provided -  fallback to default (i.e. take the latest L1 block known to the L2 sequencer),
a given L2 can choose to ignore a block height parameter if this is not an important feature to them and they want to avoid having to hold an archive node at all times,
alternatively, have two different precompiles, e.g. L1SLOAD and L1SLOAD_at_block with the second one being optional (or something similar).

For us it would be an important aspect of L1SLOAD. This would make it more generic and would unlock more use cases, without compromising on much.
Happy to hear thoughts!

Author: mralj
Date: 2024-09-24T07:57:17.721Z

Hello everyone!
I’ve been reading through the RIP, and I have couple questions.
In the RIP it is stated:

Prerequisite 1: The L2 sequencer has access to an L1 node. Given that the sequencer needs to monitor deposit transactions from L1, it already embeds an L1 node inside (preferred) or has access to an L1 PRC endpoint.


The overhead to L2 sequencers from additional RPC latency
The L1SLOAD precompile introduces risks of additional RPC latency and intermittent RPC errors. Both risks can be mitigated by running a L1 node in the same cluster as the L2 sequencer. It is preferrable for a L2 operator to run their own L1 node instead of using third party to get better security and reliability. We will perform more benchmarks to quantify the latency overhead in such setting.

Q1
I could be missing something, but this prerequisite/overhead is not just for sequencers, it’s for any L2 node. Am I correct? (if so, IMHO, the RIP description should be updated so that mentions of “sequencer” is replaced with eg. “L2 node”).
Which ties to my next question/observation:
Q2
Assuming that I was correct, by making L1SLOAD accept any block number, we would impose requirement of running full archive L1 node on any node which wants to participate in L2 (not just sequencers). Am I correct?
Q3
I’m not sure if this is actual problem in practice, ie. was it observed that indeed, most L2 nodes do run archive nodes either way, making this issue “non-issue”?

Author: thegaram33
Date: 2024-09-24T17:11:59.576Z

Hi @mralj, both your observations are correct. These were briefly discussed during RollCall #6 (slides: L1SLOAD @ RollCall #6 - Google Slides).
Currently for most rollups it’s possible to run an L2 node without connecting to an L1 node. In this case the node would need to trust the sequencer for any L1-derived info (most importantly deposit transactions), which is not ideal, but possible.
This is in contrast with this RIP. With L1SLOAD follower nodes must connect to an L1 node, since the sequencer has no standard way to relay the result of the L1 state reads to other nodes.
I think requiring L2 nodes to connect to an L1 full node is acceptable. But requiring them to connect to an L1 archive node (necessary during initial sync) might make it much more challenging to run a node.

Author: koloz193
Date: 2024-11-18T15:59:31.985Z

could we make this opcode settlement layer agnostic and save it be something like SLSLOAD (settlement layer SLOAD)? i can see some benefits of having this available for L3s on the L2 and then the naming doesn’t make much sense

Author: icemelon
Date: 2024-11-23T00:15:01.011Z

It makes sense. We will take it into consideration and open to better naming. I feel SLSLOAD may be confusing to people who don’t have much context.

Author: thegaram33
Date: 2024-12-04T15:31:08.904Z

Here is a wip reference implementation of the current draft spec in rollup-geth: Implements RIP-7728 by mralj · Pull Request #2 · NethermindEth/rollup-geth · GitHub by @mralj.

Author: swp0x0
Date: 2024-12-05T09:50:43.405Z

thegaram33:

iring L2 nodes to connect to an L1 full node is acceptable. But requiring them to connect to an L1 archive node (necessary during initial sync) might make it much more challenging to run a node.


Hi sorry, I am bit to late to the party.
Has there been any discussion about generalising this from just a storage read opcode to producing the Base layer’s block hash, which can be used to verify storage reads and other fields in the block header, like the base fee, timestamp and other things?
Also echoing @koloz193’s sentiment of changing the name to imply the the settlement layer read, maybe BASE_SLOAD might be confusing with the base chain tho.

Author: mralj
Date: 2024-12-05T09:54:26.607Z

thegaram33:

I think requiring L2 nodes to connect to an L1 full node is acceptable. But requiring them to connect to an L1 archive node (necessary during initial sync) might make it much more challenging to run a node.


Hey quick question about this, since it was also mentioned during roll-call.
I understand needing archive node during the initial “full sync”.
But what I’m missing is: why is archive node needed during snap sync? Or is it issue that L2 nodes are always doing full sync (and it indeed wouldn’t be requirement for snap)?

Author: thegaram33
Date: 2024-12-05T20:21:38.142Z

swp0x0:

Has there been any discussion about generalising this from just a storage read opcode to producing the Base layer’s block hash, which can be used to verify storage reads and other fields in the block header, like the base fee, timestamp and other things?


Actually that feature is a prerequisite of L1SLOAD.
The L2 needs to have a notion of the latest known L1 block height. The way we implemented this in the L1SLOAD devnet is that the sequencer relays all L1 block headers to a system contract that verifies that they are chained correctly and stores the L1 block hash in L2 state. (The correctness of this step is later verified as part of the zk proof.)
So yes, this definitely can be used to access other L1 block header fields. But strictly speaking this is not in scope for RIP-7728.



 mralj:

But what I’m missing is: why is archive node needed during snap sync? Or is it issue that L2 nodes are always doing full sync (and it indeed wouldn’t be requirement for snap)?


No, you are right, it is not needed for snap sync (assuming you can sync up to a very recent block), only for full sync. As an example, Scroll currently does not support snap sync, but it might support it in the future, there is nothing inherent in L2 nodes that would make this impossible.
Edit: But rollups (at least ones that post transaction data to Ethereum) need to be able to reproduce the state root by replaying the posted data, and for this kind of sync (“L1 follower node”) snap sync is not an option.

Author: mralj
Date: 2024-12-06T18:45:53.994Z

thegaram33:

No, you are right, it is not needed for snap sync (assuming you can sync up to a very recent block), only for full sync. As an example, Scroll currently does not support snap sync, but it might support it in the future, there is nothing inherent in L2 nodes that would make this impossible.
Edit: But rollups (at least ones that post transaction data to Ethereum) need to be able to reproduce the state root by replaying the posted data, and for this kind of sync (“L1 follower node”) snap sync is not an option.


Thank you very much for explaining this to me!

Author: PatrickAlphaC
Date: 2025-01-24T22:25:42.689Z

Wow, the more I think about this, the more excited I get. As of today, it’s rough how many users/apps need to have a “home base” of some kind. If we could do an L1SLOAD, we could have the home base be a one-time L1 cost and then do everything on any L2 without having to “transfer” all the data from the L1 to the L2.
On drawbacks/risks, the main drawbacks outlined in the google slides are around latency, which makes sense. A chain re-org is definitely a bit spooky too.
I’m sort of thinking out loud here, and maybe this is beyond my current knowledge, but would this make decentralizing the sequencer harder? Since a cluster of sequencers could all get different blocks based on latency, and that’s going to become more important with this RIP?

Author: sbacha
Date: 2025-01-26T09:54:39.096Z

How were the gas costs determined? How does this vary per difference in rollup implementation? Based from our assessment from the L1STATICCALL proposal that was under priced in terms of L2 Gas costs, so seeking some evidence to substantiate the values defined in this proposal.

Author: thegaram33
Date: 2025-01-28T14:01:41.508Z

PatrickAlphaC:

would this make decentralizing the sequencer harder? Since a cluster of sequencers could all get different blocks based on latency, and that’s going to become more important with this RIP?


Yeah, since L1SLOAD is part of the replicated state machine, it is assumed that the L2 has a notion of “latest seen L1 block”. This is not part of this RIP but rather a prerequisite to it.
In our L1SLOAD devnet it is simply the centralized sequencer who relays the L1 headers to L2. This can be done in a fully verifiable manner: We verify the chaining of blocks on L2, and we connect this with the L1 block hash when we submit the validity proof (it is part of the public input).
But you’re right, this relaying of L1 block headers becomes challenging with multiple sequencers, in multiple cases:

Sequencer relays an incorrect L1 block header
Sequencer relays a correct L1 block header but with insufficient confirmations
Local node has a delayed view of L1
etc.

Author: thegaram33
Date: 2025-01-28T14:06:59.660Z

sbacha:

How were the gas costs determined? How does this vary per difference in rollup implementation? Based from our assessment from the L1STATICCALL proposal that was under priced in terms of L2 Gas costs, so seeking some evidence to substantiate the values defined in this proposal.


The gas costs in the current draft are placeholders. Would love to hear your input and learnings from L1STATICCALL!
The actual execution cost on the L2 node side is not substantial. These are the operations that we need to factor in:

Cost of producing Merkle proofs on the L1 node.
Cost of verifying Merkle proofs as part of the rollup validity proof.
Latency of querying L1 RPC. It is unclear how we should price in latency. One approach is to estimate the opportunity cost of the sequencer not processing other transactions in the meantime.

Author: sbacha
Date: 2025-01-28T14:48:35.024Z

Scenarios for testing could include:


simulating a request such that the request is sent in order to reach the maximum possible stack depth, triggering an overflow for the entire block


Try to reach a stack level where we can perform the maximum number of SLOADs in order to force a state cache data structure to query linearly over caches created at each depth, consuming all gas of block


observing how requests are handled by forwarded transactions (where the originator of the transaction is sponsored by a relayer/forwarding agent (e.g. GSN)).


Regarding latency, this can be calculated by calculating a transaction fee overhead due to the additional latency in the critical path (we can assume a constant value) and the marginal latency incurred for the call (as you stated) based for unit gas consumed. Then we can derive a p90/p95 probability for inclusion within the next block. This calculated amount is just added to the bribe as additional payment to the builder.
My telegram username is @sambacha if you would like to discuss more very welcome to chat!

Author: wminshew
Date: 2025-01-28T14:58:33.445Z

Is this still an issue w based rollups? [sorry if naive question … still learning]

Author: SanLeo461
Date: 2025-01-29T04:07:52.450Z

There was some discussion earlier in the thread about making L1SLOAD agnostic over exactly which layer it was on, instead just querying 1 layer below; e.g. an L3 querying state of an L2.
Do you think there’s enough value in this idea to also apply it to L1STATICCALL? With static calling, an L3 could call to the L1 via a contract chain. L3 static calls the L2, L2 static calls the L1.
Any ideas on the technical challenges that would be involved with trying to build something like this versus just L1STATICCALL for L2s? My guess is that it wouldn’t be too difficult, but I’d love to hear your thoughts or ideas on technical hurdles and any ideas for usecases you might have.

Author: dror
Date: 2025-01-30T23:04:16.026Z

L1SLOAD requires an rpc call into an external L1 node.
The gas cost attempts to reflect that, with a relatively high base fee, and additional marginal cost for extra fetched slots.
But what if the implementation can’t “batch” these calls ?
Consider a bundle of multiple SCA requests that are sent over ERC-4337 call: Each SCA is unaware of the others, and thus each will require a separate L1SLOAD call with one (or few) slots, which on extreme cases would cause the transaction to timeout, and fail inclusion.
My suggestion: Extend the accessList (EIP-2930) transaction field, to pre-fetch not only EVM state slots but also L1 slots.
The caller of the transaction (a bundler in ERC-4337 model) can collect those slot addresses and add them to the accessList
This requires to redefine the “address” member of the accessList as {20|21 bytes}: a 21-byte address (with a prefix “1”) is an L1 address, and a 20-byte normal address is an L2 (current evm state) address.
This format makes it backward compatible to the existing transaction type (at the RLP level)
Note that the L2 node/sequencer could even batch and pre-fetch those L1SLOADs for multiple transactions before evm-executing them.

Author: thegaram33
Date: 2025-02-03T11:55:46.928Z

wminshew:

Is this still an issue w based rollups? [sorry if naive question … still learning]


A based rollup could automatically (implicitly) use the previous L1 block hash as the latest known L1 block hash. If the L1 and L2 block proposers are identical, then the previous L1 block hash is known to the block builder, and L2 block inclusion is guaranteed.
My concern would be in these two cases:

Permissionless fallback: L1 proposer did not opt in to propose the next L2 block so someone else needs to propose it, inclusion in the next L1 block is not guaranteed.
L1 reorgs automatically lead to L2 reorgs.

In these cases, the “latest known L1 block hash” is either not known in advance or might change, which can invalidate preconfs.

Author: thegaram33
Date: 2025-02-03T11:58:58.282Z

SanLeo461:

With static calling, an L3 could call to the L1 via a contract chain. L3 static calls the L2, L2 static calls the L1.


That makes sense, at first sight I don’t see any issue here.
And that shows that L1STATICCALL has better composability than L1SLOAD. With L1SLOAD on an L3, you might need different precompiles for reading from L1 and L2.

Author: thegaram33
Date: 2025-02-03T12:03:57.794Z

dror:

My suggestion: Extend the accessList (EIP-2930) transaction field, to pre-fetch not only EVM state slots but also L1 slots.


Great suggestion!
Indeed, the latency for each UserOp would add up which might lead to timeout.
The solution you suggested is backward-compatible in its format, but I assume we’d need different pricing / gas discounts for preloading L1 slots. That said, I don’t think it’s a very invasive change, we can consider adding it to the RIP.

Author: sbacha
Date: 2025-02-25T01:43:27.647Z

The proposal should explicitly state as well:

If the target address does not contain contract code, the operation returns zero
If the target address is a precompiled contract, the operation returns zero

Author: thegaram33
Date: 2025-02-25T12:42:22.468Z

sbacha:

If the target address does not contain contract code, the operation returns zero


That’s not necessarily the case. With EIP-7702 you could delegate to a contract, let it write to your account’s storage, then revoke the delegation.
Developers should think of L1SLOAD as eth_getStorageAt, but accessible from within the EVM and reading from the base chain.

Author: SanLeo461
Date: 2025-03-01T13:06:00.509Z

Just a thought – there’s a possible future where many chains are able to communicate with one another with no delay – full composability.
Restricting an opcode this useful to just one chain (the layer below) could be a mistake, would it not be better to include a chain id as part of the stack arguments consumed for this opcode?
For the base case of calling down a layer (L3 → L2, L2 → L1, etc.), it would largely work the same, however there is additional overhead of the additional chain ID stack argument, though performance in that area is not currently the major concern, and rollups have more lax performance requirements.
Which chain IDs are allowed to be called/loaded from is a choice that the rollup themselves would make, if an invalid chain ID is provided, the call frame could simply revert. Currently that would look like restricting the allowed chain IDs list to simply the ID of layer below.
Right now, this probably isn’t a game changer, but rollups using based sequencing or infrastructure such as Optimism’s Superchain could benefit immensely, if the chain is able to achieve synchronous composability with one or many chains.
In short, its a small change now that could be useless, but the downsides are very low, and the potential upside and future proofing could unlock massive possibilities.

Author: thegaram33
Date: 2025-03-03T07:21:17.218Z

Thanks for the suggestion. I agree there seems to be not much overhead. But I think this might be premature generalization, this RIP was not meant to be a general-purpose interop solution.



 SanLeo461:

if the chain is able to achieve synchronous composability with one or many chains.


I feel like L1SLOAD might not be the right tool for chains that synchronously compose with each other. They could easily use more sophisticated interop primitives, like cross-chain calls.



 SanLeo461:

rollups using based sequencing or infrastructure such as Optimism’s Superchain could benefit immensely


I’m curious to learn more about this. So the sequencer of one of the L2s in the Superchain would help dapps fetch storage entries from other L2s. Are all these chains settled on L1 in lockstep, so that the corresponding storage proofs can also easily be verified? (Verified by a full node, a fraud proof game, and later using a validity proof.)

Author: SanLeo461
Date: 2025-03-03T07:37:26.469Z

thegaram33:

I feel like L1SLOAD might not be the right tool for chains that synchronously compose with each other. They could easily use more sophisticated interop primitives, like cross-chain calls.


I agree SLOAD is probably not the right case for such a generalization – there was some discussion on L1STATICCALL earlier in the thread which is what I was intending should be generalized, apologies for not clarifying.
L1CALL is also another option, so we could in theory write between chains, but that has some really weird semantics with how ordering works – which chain runs first, two chains writing to each other in sync, etc.
I also have an inkling that cross chain SLOAD (L1 or otherwise) could lead to some weird possible security concerns? In the standard EVM, contracts cannot directly query another contracts internal state. This may be an absolute nothing-burger, after all nodes can directly query storage, but just a thought I had.



 thegaram33:

I’m curious to learn more about this. So the sequencer of one of the L2s in the Superchain would help dapps fetch storage entries from other L2s. Are all these chains settled on L1 in lockstep, so that the corresponding storage proofs can also easily be verified? (Verified by a full node, a fraud proof game, and later using a validity proof.)


Yep thats correct, I’m less certain on the long term & technical plans for Optimism’s Superchain, but the long term goal with based rollups is that the proposer / builder will do all the processing for them in one bundle, so they would all be settled/committed to in lockstep, allowing cross chain calls to happen extremely seamlessly.
Verification for how that all works I would imagine is outside the scope of the RIP, its up to the rollup themselves to implement security measures, the point (in my opinion) of the RIP is to standardize an opcode/precompile for cross-chain calls, not conceive how many different types of rollups could secure the cross-chain call.

Author: hazim-j
Date: 2025-04-11T05:30:28.633Z

I definitely share the performance concerns with making RPCs a synchronous part of the L2 EVM - but this has already been covered several times so I won’t rehash it here.
My following thoughts are mainly from the perspective of making smart contract accounts (SCA) more chain agnostic via keystores.


Will this cause any violations on AA validation rules (ERC-7562)? In other words, will this be compliant with the current canonical UserOp mempool or will it require an alternative mempool?


Given that there will be some lag to prevent finality risk, do we expect any security concerns due to a significant delay for key rotations to take effect on all chains?


Does this enforce SCAs to be deployed to L1 in order to get the interop benefits or just the keystore contract? On a similar note, is a L1SLOAD also a pre-requisite to achieving a minimal keystore rollup?


If keystores go all in on L1SLOAD, does this lock out non-rollup networks (e.g. Polygon PoS, BSC, Avalanche C-Chain, etc) from getting the interop benefits?


I’m aware that some of these questions could be out of scope for this particular RIP discussion, but I think its worth calling out given the expectation that keystores are a major potential use case for this opcode.

Author: dror
Date: 2025-04-15T17:09:33.251Z

hazim-j:

Will this cause any violations on AA validation rules (ERC-7562)? In other words, will this be compliant with the current canonical UserOp mempool or will it require an alternative mempool?


The L1SLOAD will need a special rule in erc-7562, to white-list its address.
But still, it can be treated like storage access and thus accessing its data is an “associated storage” and thus allowed in account validation.



 hazim-j:


Given that there will be some lag to prevent finality risk, do we expect any security concerns due to a significant delay for key rotations to take effect on all chains?



Yes, there going to be a time-window that some networks use one key and others use a different key.
Don’t think of it as a “immediate kill switch”, but more like a maintainanace key rotation, and as such, there is no security concern.
I think it will only require users that makes complex cross-chain operations wait a little until it is fully propagated.



 hazim-j:

Does this enforce SCAs to be deployed to L1 in order to get the interop benefits or just the keystore contract?


For propagation to L2s, only the keystore needs to be installed on L1. For key modification, it either trust L2 [canonical] bridges, or require the account on L1.



 hazim-j:


If keystores go all in on L1SLOAD, does this lock out non-rollup networks (e.g. Polygon PoS, BSC, Avalanche C-Chain, etc) from getting the interop benefits?



I can’t see how you can permissionlessly trust L1 change on another L1. e.g if you had L1 block hashes, you could create a merkle proof for the account key, but that is expensive to check - and still depend on some trusted oracle to post L1 block hash on this alt L1

Author: mralj
Date: 2025-04-16T07:47:31.100Z

This is a pretty good idea, and I like it a lot, though I’m not sure if extending the accessList this way is the best approach.
I understand the benefit is backward compatibility, but TBH, this feels too hacky.
The only other approach that comes to my mind is the new transaction type, which is not without its own set of issues.
Still, I think it will work better here, especially if we batch and pre-fetch L1SLOADs before EVEM execution, as you suggested. We could have a new TX Pool specific to these L1SLOAD transactions, in which we pre-fetch the L1 state, completely removing the need for RPC calls during the EVM execution (AFAIK op-stack does smth. similar for their interop txs).
The main downside of the new TX type, in my view, would be the additional burden on the fronted (user-facing libraries, wallets, etc.). Still, even the proposed accessList requires the changes