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.

Specification

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:

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).

Example Usage in Solidity

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));
}

Protocol Changes

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.