The L1SLOAD
precompile allows smart contracts to read a L1 storage slots without providing Merkle Inclusion Proofs. The L2 protocol is required to provide the correct storage value on Ethereum and constrains the precompile in its proofs (fraud proofs and zk proofs).
The introduciton of L1SLOAD
precompile lifts the needs for dapps to provide the Merkle Inclusion proof and thus reduces the tx data size and tx building complexity. More importantly, it saves the gas cost of verifying the Merkle path in the smart contract.
We propose adding a precompiled contract L1SLOAD
at address 0x101
. The gas price is set to 4000
temporarily.
The inputs to L1SLOAD
are the L1 block number, contract address, and the storage key. The output is the storage value at the designated L1 block number.
Inputs
Byte range | Name | Description |
---|---|---|
[0: 31] (32 bytes) | l1BlockNumber |
The L1 block number |
[32: 51] (20 bytes) | address |
The contract address |
[52: 83] (32 bytes) | key |
The storage key |
Requirement:
l1BlockNumber
needs to be within the range of [latestBlockNumber - RING_BUFFER_SIZE + 1, latestBlockNumber]
where the latestBlockNumber
is the latest block number in the L1Blocks
contract and the RING_BUFFER_SIZE
is the constant value in the L1Blocks
contract.Output
Byte range | Name | Description |
---|---|---|
[0: 31] (32 bytes) | value |
The storage value at the L1 block number |
If the input is invalid, the gas provided is consumed and there is no return data, indicating a precompile contract error. If not enough gas was given, there is no return data.
Gas Price
The gas price of L1SLOAD
will be higher than SLOAD
because (a) the sequencer needs to fetch the value from L1 client, and (b) the proving cost of L1 MPT in the zkEVM is higher due to the use of Keccak hash. We will treat all keys to be cold keys. The tentative gas of L1SLOAD
is 4000 (subject to change after we have more data from proving cost).
function sloadFromL1(uint256 l1BlockNum, address l1Address, uint256 slot) public view returns (uint256) {
address L1_SLOAD_ADDRESS = 0x101;
(bool success, bytes memory ret) = L1_SLOAD_ADDRESS.staticcall(
abi.encodePacked(l1BlockNum, l1Address, slot)
);
if (!success) {
revert("L1SLOAD failed");
}
return abi.decode(ret, (uint256));
}
The sequencer is responsible for providing the correct storage value at the given L1 block number. To prevent the sequencer from giving wrong values, the L2 proof system (either fraud proof or validity proof) needs to construct a valid Merkle inclusion proof from the value to the corresponding state root stored in the L1Blocks
contract.