├── .gas-snapshot ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── foundry.toml ├── src ├── L1BlockSnapshotter.sol ├── interface │ └── IL1Block.sol └── lib │ ├── Constants.sol │ └── Structs.sol └── test ├── L1BlockSnapshotter.t.sol ├── L1BlockSnapshotterFork.t.sol ├── L1BlockSnapshotterGas.t.sol └── L1BlockSnapshotterSize.t.sol /.gas-snapshot: -------------------------------------------------------------------------------- 1 | L1BlockSnapshotterForkTest:testSnapshot_fork() (gas: 2338076) 2 | L1BlockSnapshotterGasTest:testBasefee_gasSnapshot() (gas: 3963) 3 | L1BlockSnapshotterGasTest:testBatcherHash_gasSnapshot() (gas: 4156) 4 | L1BlockSnapshotterGasTest:testHash_gasSnapshot() (gas: 4117) 5 | L1BlockSnapshotterGasTest:testL1FeeOverhead_gasSnapshot() (gas: 4032) 6 | L1BlockSnapshotterGasTest:testL1FeeScalar_gasSnapshot() (gas: 4051) 7 | L1BlockSnapshotterGasTest:testSequenceNumber_gasSnapshot() (gas: 4101) 8 | L1BlockSnapshotterGasTest:testSnapshotFallback_gasSnapshot() (gas: 159009) 9 | L1BlockSnapshotterGasTest:testSnapshot_gasSnapshot() (gas: 159447) 10 | L1BlockSnapshotterGasTest:testTimestamp_gasSnapshot() (gas: 4010) 11 | L1BlockSnapshotterSizeTest:test__codesize() (gas: 6941) 12 | L1BlockSnapshotterTest:testGetL1BlockBasefee() (gas: 320767) 13 | L1BlockSnapshotterTest:testGetL1BlockBatcherHash() (gas: 321277) 14 | L1BlockSnapshotterTest:testGetL1BlockHash() (gas: 321397) 15 | L1BlockSnapshotterTest:testGetL1BlockSequenceNumber() (gas: 320928) 16 | L1BlockSnapshotterTest:testGetL1BlockTimestamp() (gas: 321053) 17 | L1BlockSnapshotterTest:testGetL1FeeOverhead() (gas: 321009) 18 | L1BlockSnapshotterTest:testGetL1FeeScalar() (gas: 321185) 19 | L1BlockSnapshotterTest:testSnapshot() (gas: 337794) 20 | L1BlockSnapshotterTest:testSnapshot_NoSnapshotForBlock() (gas: 47291) 21 | L1BlockSnapshotterTest:testSnapshot_fallback() (gas: 336234) -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | html 16 | lcov.info 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/solady"] 5 | path = lib/solady 6 | url = https://github.com/vectorized/solady 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # L1BlockSnapshotter 2 | 3 | ## Overview 4 | 5 | OP Stack chains have an [L1Block](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1Block.sol) smart contract that makes information about the latest L1 block accessible to L2 smart contracts. This information is not historical, and is updated each epoch (each L1 block). 6 | 7 | This smart contract merely snapshots the current L1 block information and allows retrieval of that information at a later time. This is useful for applications that need to make assertions about the L1 state at a given block on the L2. 8 | 9 | ## Potential Use Cases 10 | - PBTs on L2s 11 | - [EIP-5791](https://eips.ethereum.org/EIPS/eip-5791) uses a signature of a message containing a recent blockhash to permit transfers of Physical Backed Tokens (PBTs). In an L1 context, this works because the 256 most recent blockhashes are available to the EVM, and blocks are produced at a slow enough cadence that network latency should not be an issue. In an L2 context, this is not necessarily the case. The L1BlockSnapshotter allows for the creation of a snapshot of the L1 blockhashes for historical retrieval on L2. It also prevents relying on a value that may be manipulated by a centralized sequencer (assuming the L2 still accurately records L1 blockhashes) 12 | - L1 State Proofs 13 | - The L1BlockSnapshotter acts as a central, ownerless source of truth for historical L1 block information. This means any user or smart contract can create a snapshot of current L1 Block metadata and make it available to other applications. This can be used in combination with state proofs to make arbitrary assertions about a particular L1 state at a given block on the L2. -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | remappings=['forge-std/=lib/forge-std/src/','solady-test/=lib/solady/test/'] 6 | optimizer_runs = 4294967295 7 | via_ir = true 8 | 9 | [rpc_endpoints] 10 | base_goerli = "https://goerli.base.org" 11 | zora = "https://rpc.zora.energy" 12 | zora_testnet = "https://testnet.rpc.zora.co" 13 | optimism = "https://mainnet.optimism.io" 14 | 15 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 16 | -------------------------------------------------------------------------------- /src/L1BlockSnapshotter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {IL1Block} from "./interface/IL1Block.sol"; 5 | import {L1BlockSnapshot} from "./lib/Structs.sol"; 6 | import { 7 | NUMBER_SELECTOR, 8 | TIMESTAMP_SELECTOR, 9 | BASEFEE_SELECTOR, 10 | HASH_SELECTOR, 11 | SEQUENCE_NUMBER_SELECTOR, 12 | BATCHER_HASH_SELECTOR, 13 | L1_FEE_OVERHEAD_SELECTOR, 14 | L1_FEE_SCALAR_SELECTOR 15 | } from "./lib/Constants.sol"; 16 | 17 | /** 18 | * @title L1BlockSnapshotter 19 | * @author emo.eth 20 | * @notice The L1BlockSnapshotter contract snapshots L1 block data as returned by the L1Block smart contract, allowing 21 | * for historical lookup by L2 applications. 22 | */ 23 | contract L1BlockSnapshotter { 24 | ///@dev Error thrown when no snapshot exists for a given L1 block number. 25 | error NoSnapshotForBlock(uint256 l1BlockNumber); 26 | 27 | ///@dev Event emitted when a new L1 block snapshot is created, for offchain indexing 28 | event Snapshot(uint256 indexed l1BlockNumber); 29 | 30 | ///@dev The L1 block contract to use for snapshots for all OP Stack chains. 31 | IL1Block public constant L1_BLOCK = IL1Block(0x4200000000000000000000000000000000000015); 32 | 33 | ///@dev Mapping of L1 block number to L1 block snapshot. 34 | mapping(uint256 l1BlockNumber => L1BlockSnapshot snapshot) snapshots; 35 | 36 | /** 37 | * @notice Fallback function to snapshot the current L1 block without calldata overhead. 38 | */ 39 | fallback() external { 40 | _snapshot(); 41 | } 42 | 43 | /** 44 | * @notice Snapshot the current L1 block. 45 | */ 46 | function snapshot() external { 47 | _snapshot(); 48 | } 49 | 50 | /** 51 | * @notice Get the entire snapshot for a given L1 block number. 52 | * @param l1BlockNumber The L1 block number to get a snapshot for. 53 | */ 54 | function getL1BlockSnapshot(uint256 l1BlockNumber) external view returns (L1BlockSnapshot memory) { 55 | return _getL1BlockSnapshot(l1BlockNumber); 56 | } 57 | 58 | /** 59 | * @notice Get the L1 block timestamp of a given L1 block number. 60 | * @param l1BlockNumber The L1 block number to get a snapshot for. 61 | */ 62 | function getL1BlockTimestamp(uint256 l1BlockNumber) external view returns (uint256) { 63 | return _getL1BlockSnapshot(l1BlockNumber).timestamp; 64 | } 65 | 66 | /** 67 | * @notice Get the L1 block basefee of a given L1 block number. 68 | * @param l1BlockNumber The L1 block number to get a snapshot for. 69 | */ 70 | function getL1BlockBasefee(uint256 l1BlockNumber) external view returns (uint256) { 71 | return _getL1BlockSnapshot(l1BlockNumber).basefee; 72 | } 73 | 74 | /** 75 | * @notice Get the L1 block hash of a given L1 block number. 76 | * @param l1BlockNumber The L1 block number to get a snapshot for. 77 | */ 78 | function getL1BlockHash(uint256 l1BlockNumber) external view returns (bytes32) { 79 | return _getL1BlockSnapshot(l1BlockNumber).hash; 80 | } 81 | 82 | /** 83 | * @notice Get the L1 block sequence number of a given L1 block number. 84 | * @param l1BlockNumber The L1 block number to get a snapshot for. 85 | */ 86 | function getL1BlockSequenceNumber(uint256 l1BlockNumber) external view returns (uint256) { 87 | return _getL1BlockSnapshot(l1BlockNumber).sequenceNumber; 88 | } 89 | 90 | /** 91 | * @notice Get the L1 block batcher hash of a given L1 block number. 92 | * @param l1BlockNumber The L1 block number to get a snapshot for. 93 | */ 94 | function getL1BlockBatcherHash(uint256 l1BlockNumber) external view returns (bytes32) { 95 | return _getL1BlockSnapshot(l1BlockNumber).batcherHash; 96 | } 97 | 98 | /** 99 | * @notice Get the L1 block fee overhead of a given L1 block number. 100 | * @param l1BlockNumber The L1 block number to get a snapshot for. 101 | */ 102 | function getL1BlockFeeOverhead(uint256 l1BlockNumber) external view returns (uint256) { 103 | return _getL1BlockSnapshot(l1BlockNumber).l1FeeOverhead; 104 | } 105 | 106 | /** 107 | * @notice Get the L1 block fee scalar of a given L1 block number. 108 | * @param l1BlockNumber The L1 block number to get a snapshot for. 109 | */ 110 | function getL1BlockFeeScalar(uint256 l1BlockNumber) external view returns (uint256) { 111 | return _getL1BlockSnapshot(l1BlockNumber).l1FeeScalar; 112 | } 113 | 114 | /** 115 | * @dev Get the entire snapshot for a given L1 block number and check that it exists. 116 | * @param l1BlockNumber The L1 block number to get a snapshot for. 117 | * @return The L1 block snapshot as a storage pointer to avoid unnecessary SLOADs when accessing individual fields. 118 | */ 119 | function _getL1BlockSnapshot(uint256 l1BlockNumber) internal view returns (L1BlockSnapshot storage) { 120 | L1BlockSnapshot storage existing = snapshots[l1BlockNumber]; 121 | if (existing.number == 0) { 122 | revert NoSnapshotForBlock(l1BlockNumber); 123 | } 124 | return existing; 125 | } 126 | 127 | /** 128 | * @dev Store the latest L1Block values in a snapshot, if one does not already exist. 129 | */ 130 | function _snapshot() internal { 131 | uint64 l1BlockNumber = uint64(_callL1Block(NUMBER_SELECTOR)); 132 | L1BlockSnapshot storage existing = snapshots[l1BlockNumber]; 133 | // if snapshot already exists, do nothing 134 | if (existing.number != 0) { 135 | return; 136 | } 137 | // otherwise, create a new snapshot 138 | snapshots[l1BlockNumber] = L1BlockSnapshot({ 139 | number: l1BlockNumber, 140 | timestamp: uint64(_callL1Block(TIMESTAMP_SELECTOR)), 141 | basefee: _callL1Block(BASEFEE_SELECTOR), 142 | hash: bytes32(_callL1Block(HASH_SELECTOR)), 143 | sequenceNumber: uint64(_callL1Block(SEQUENCE_NUMBER_SELECTOR)), 144 | batcherHash: bytes32(_callL1Block(BATCHER_HASH_SELECTOR)), 145 | l1FeeOverhead: _callL1Block(L1_FEE_OVERHEAD_SELECTOR), 146 | l1FeeScalar: _callL1Block(L1_FEE_SCALAR_SELECTOR) 147 | }); 148 | 149 | // emit an event for easier offchain indexing 150 | emit Snapshot(l1BlockNumber); 151 | } 152 | 153 | /** 154 | * @dev Call the L1 block contract with a given selector and return the first word of returndata. 155 | * @param selectorConst The selector to call. 156 | */ 157 | function _callL1Block(uint256 selectorConst) internal view returns (uint256 val) { 158 | address l1BlockAddress = address(L1_BLOCK); 159 | assembly ("memory-safe") { 160 | mstore(0, selectorConst) 161 | if iszero( 162 | staticcall( 163 | // forward all gass 164 | gas(), 165 | // call address 166 | l1BlockAddress, 167 | // read from 0x1c 168 | 0x1c, 169 | // read 4 bytes 170 | 0x04, 171 | // write returndata starting at 0x0 172 | 0, 173 | // write 32 bytes of returndata 174 | 0x20 175 | ) 176 | ) { 177 | // on failure, copy all returndata to memory 178 | returndatacopy(0, 0, returndatasize()) 179 | // revert with returndata 180 | revert(0, returndatasize()) 181 | } 182 | // on success, read the first word of memory onto the stack and return it 183 | val := mload(0) 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/interface/IL1Block.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | /// @notice modified from https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/contracts/L2/L1Block.sol 5 | /// @custom:predeploy 0x4200000000000000000000000000000000000015 6 | /// @title IL1Block 7 | /// @notice The L1Block predeploy gives users access to information about the last known L1 block. 8 | /// Values within this contract are updated once per epoch (every L1 block) and can only be 9 | /// set by the "depositor" account, a special system address. Depositor account transactions 10 | /// are created by the protocol whenever we move to a new epoch. 11 | interface IL1Block { 12 | /// @notice The address that is allowed to update the L1 block values. 13 | function DEPOSITOR_ACCOUNT() external view returns (address); 14 | /// @notice The latest L1 block number known by the L2 system. 15 | function number() external view returns (uint64); 16 | /// @notice The latest L1 timestamp known by the L2 system. 17 | function timestamp() external view returns (uint64); 18 | /// @notice The latest L1 basefee. 19 | function basefee() external view returns (uint256); 20 | /// @notice The latest L1 blockhash. 21 | function hash() external view returns (bytes32); 22 | /// @notice The number of L2 blocks in the same epoch. 23 | function sequenceNumber() external view returns (uint64); 24 | /// @notice The versioned hash to authenticate the batcher by. 25 | function batcherHash() external view returns (bytes32); 26 | /// @notice The overhead value applied to the L1 portion of the transaction fee. 27 | function l1FeeOverhead() external view returns (uint256); 28 | /// @notice The scalar value applied to the L1 portion of the transaction fee. 29 | function l1FeeScalar() external view returns (uint256); 30 | 31 | /// @notice Updates the L1 block values. 32 | /// @param _number L1 blocknumber. 33 | /// @param _timestamp L1 timestamp. 34 | /// @param _basefee L1 basefee. 35 | /// @param _hash L1 blockhash. 36 | /// @param _sequenceNumber Number of L2 blocks since epoch start. 37 | /// @param _batcherHash Versioned hash to authenticate batcher by. 38 | /// @param _l1FeeOverhead L1 fee overhead. 39 | /// @param _l1FeeScalar L1 fee scalar. 40 | function setL1BlockValues( 41 | uint64 _number, 42 | uint64 _timestamp, 43 | uint256 _basefee, 44 | bytes32 _hash, 45 | uint64 _sequenceNumber, 46 | bytes32 _batcherHash, 47 | uint256 _l1FeeOverhead, 48 | uint256 _l1FeeScalar 49 | ) external; 50 | } 51 | -------------------------------------------------------------------------------- /src/lib/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | uint256 constant NUMBER_SELECTOR = 0x8381f58a; 5 | uint256 constant TIMESTAMP_SELECTOR = 0xb80777ea; 6 | uint256 constant BASEFEE_SELECTOR = 0x5cf24969; 7 | uint256 constant HASH_SELECTOR = 0x09bd5a60; 8 | uint256 constant SEQUENCE_NUMBER_SELECTOR = 0x64ca23ef; 9 | uint256 constant BATCHER_HASH_SELECTOR = 0xe81b2c6d; 10 | uint256 constant L1_FEE_OVERHEAD_SELECTOR = 0x8b239f73; 11 | uint256 constant L1_FEE_SCALAR_SELECTOR = 0x9e8c4966; 12 | -------------------------------------------------------------------------------- /src/lib/Structs.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | struct L1BlockSnapshot { 5 | uint64 number; 6 | uint64 timestamp; 7 | uint64 sequenceNumber; 8 | uint256 basefee; 9 | bytes32 hash; 10 | bytes32 batcherHash; 11 | uint256 l1FeeOverhead; 12 | uint256 l1FeeScalar; 13 | } 14 | -------------------------------------------------------------------------------- /test/L1BlockSnapshotter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {L1BlockSnapshotter, L1BlockSnapshot, IL1Block} from "../src/L1BlockSnapshotter.sol"; 6 | 7 | contract L1BlockMock is IL1Block { 8 | L1BlockSnapshot snapshot; 9 | 10 | function DEPOSITOR_ACCOUNT() external pure override returns (address) {} 11 | 12 | function number() external view override returns (uint64) { 13 | return snapshot.number; 14 | } 15 | 16 | function timestamp() external view override returns (uint64) { 17 | return snapshot.timestamp; 18 | } 19 | 20 | function basefee() external view override returns (uint256) { 21 | return snapshot.basefee; 22 | } 23 | 24 | function hash() external view override returns (bytes32) { 25 | return snapshot.hash; 26 | } 27 | 28 | function sequenceNumber() external view override returns (uint64) { 29 | return snapshot.sequenceNumber; 30 | } 31 | 32 | function batcherHash() external view override returns (bytes32) { 33 | return snapshot.batcherHash; 34 | } 35 | 36 | function l1FeeOverhead() external view override returns (uint256) { 37 | return snapshot.l1FeeOverhead; 38 | } 39 | 40 | function l1FeeScalar() external view override returns (uint256) { 41 | return snapshot.l1FeeScalar; 42 | } 43 | 44 | function setL1BlockValues( 45 | uint64 _number, 46 | uint64 _timestamp, 47 | uint256 _basefee, 48 | bytes32 _hash, 49 | uint64 _sequenceNumber, 50 | bytes32 _batcherHash, 51 | uint256 _l1FeeOverhead, 52 | uint256 _l1FeeScalar 53 | ) external { 54 | snapshot = L1BlockSnapshot({ 55 | number: _number, 56 | timestamp: _timestamp, 57 | basefee: _basefee, 58 | hash: _hash, 59 | sequenceNumber: _sequenceNumber, 60 | batcherHash: _batcherHash, 61 | l1FeeOverhead: _l1FeeOverhead, 62 | l1FeeScalar: _l1FeeScalar 63 | }); 64 | } 65 | } 66 | 67 | contract L1BlockSnapshotterTest is Test { 68 | L1BlockSnapshotter test; 69 | IL1Block mock; 70 | 71 | function setUp() public { 72 | test = new L1BlockSnapshotter(); 73 | mock = new L1BlockMock(); 74 | vm.etch(address(test.L1_BLOCK()), address(mock).code); 75 | mock = test.L1_BLOCK(); 76 | mock.setL1BlockValues(1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 77 | } 78 | 79 | function testSnapshot() public { 80 | test.snapshot(); 81 | L1BlockSnapshot memory snapshot = test.getL1BlockSnapshot(1); 82 | _assertSnapshot(snapshot, 1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 83 | 84 | // subsequent call should write nothing; 85 | 86 | vm.record(); 87 | test.snapshot(); 88 | (, bytes32[] memory writeSlots) = vm.accesses(address(test)); 89 | assertEq(writeSlots.length, 0, "should not write anything"); 90 | 91 | snapshot = test.getL1BlockSnapshot(1); 92 | _assertSnapshot(snapshot, 1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 93 | 94 | // updated call should update correct block 95 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 96 | test.snapshot(); 97 | snapshot = test.getL1BlockSnapshot(2); 98 | _assertSnapshot(snapshot, 2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 99 | 100 | // old block should be unchanged 101 | snapshot = test.getL1BlockSnapshot(1); 102 | _assertSnapshot(snapshot, 1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 103 | } 104 | 105 | function _assertSnapshot( 106 | L1BlockSnapshot memory snapshot, 107 | uint64 _number, 108 | uint64 _timestamp, 109 | uint256 _basefee, 110 | bytes32 _hash, 111 | uint64 _sequenceNumber, 112 | bytes32 _batcherHash, 113 | uint256 _l1FeeOverhead, 114 | uint256 _l1FeeScalar 115 | ) internal { 116 | assertEq(snapshot.number, _number); 117 | assertEq(snapshot.timestamp, _timestamp); 118 | assertEq(snapshot.basefee, _basefee); 119 | assertEq(snapshot.hash, _hash); 120 | assertEq(snapshot.sequenceNumber, _sequenceNumber); 121 | assertEq(snapshot.batcherHash, _batcherHash); 122 | assertEq(snapshot.l1FeeOverhead, _l1FeeOverhead); 123 | assertEq(snapshot.l1FeeScalar, _l1FeeScalar); 124 | } 125 | 126 | function testSnapshot_fallback() public { 127 | (bool succ,) = address(test).call(""); 128 | assertTrue(succ); 129 | L1BlockSnapshot memory snapshot = test.getL1BlockSnapshot(1); 130 | _assertSnapshot(snapshot, 1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 131 | 132 | vm.record(); 133 | (succ,) = address(test).call(""); 134 | assertTrue(succ); 135 | (, bytes32[] memory writeSlots) = vm.accesses(address(test)); 136 | assertEq(writeSlots.length, 0, "should not write anything"); 137 | snapshot = test.getL1BlockSnapshot(1); 138 | _assertSnapshot(snapshot, 1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 139 | 140 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 141 | (succ,) = address(test).call(""); 142 | assertTrue(succ); 143 | snapshot = test.getL1BlockSnapshot(2); 144 | _assertSnapshot(snapshot, 2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 145 | 146 | snapshot = test.getL1BlockSnapshot(1); 147 | _assertSnapshot(snapshot, 1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 148 | } 149 | 150 | function testSnapshot_NoSnapshotForBlock() public { 151 | vm.expectRevert(abi.encodeWithSelector(L1BlockSnapshotter.NoSnapshotForBlock.selector, uint256(2))); 152 | test.getL1BlockSnapshot(2); 153 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 154 | } 155 | 156 | function testGetL1BlockTimestamp() public { 157 | test.snapshot(); 158 | assertEq(test.getL1BlockTimestamp(1), 2); 159 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 160 | test.snapshot(); 161 | assertEq(test.getL1BlockTimestamp(2), 3); 162 | } 163 | 164 | function testGetL1BlockBasefee() public { 165 | test.snapshot(); 166 | assertEq(test.getL1BlockBasefee(1), 3); 167 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 168 | test.snapshot(); 169 | assertEq(test.getL1BlockBasefee(2), 4); 170 | } 171 | 172 | function testGetL1BlockHash() public { 173 | test.snapshot(); 174 | assertEq(test.getL1BlockHash(1), bytes32(uint256(4))); 175 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 176 | test.snapshot(); 177 | assertEq(test.getL1BlockHash(2), bytes32(uint256(5))); 178 | } 179 | 180 | function testGetL1BlockSequenceNumber() public { 181 | test.snapshot(); 182 | assertEq(test.getL1BlockSequenceNumber(1), 5); 183 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 184 | test.snapshot(); 185 | assertEq(test.getL1BlockSequenceNumber(2), 6); 186 | } 187 | 188 | function testGetL1BlockBatcherHash() public { 189 | test.snapshot(); 190 | assertEq(test.getL1BlockBatcherHash(1), bytes32(uint256(6))); 191 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 192 | test.snapshot(); 193 | assertEq(test.getL1BlockBatcherHash(2), bytes32(uint256(7))); 194 | } 195 | 196 | function testGetL1FeeOverhead() public { 197 | test.snapshot(); 198 | assertEq(test.getL1BlockFeeOverhead(1), 7); 199 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 200 | test.snapshot(); 201 | assertEq(test.getL1BlockFeeOverhead(2), 8); 202 | } 203 | 204 | function testGetL1FeeScalar() public { 205 | test.snapshot(); 206 | assertEq(test.getL1BlockFeeScalar(1), 8); 207 | mock.setL1BlockValues(2, 3, 4, bytes32(uint256(5)), 6, bytes32(uint256(7)), 8, 9); 208 | test.snapshot(); 209 | assertEq(test.getL1BlockFeeScalar(2), 9); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /test/L1BlockSnapshotterFork.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {L1BlockSnapshotter, L1BlockSnapshot, IL1Block} from "../src/L1BlockSnapshotter.sol"; 6 | 7 | contract L1BlockSnapshotterForkTest is Test { 8 | L1BlockSnapshotter snapshot; 9 | IL1Block l1Block; 10 | 11 | function setUp() public { 12 | snapshot = new L1BlockSnapshotter(); 13 | l1Block = snapshot.L1_BLOCK(); 14 | } 15 | 16 | function testSnapshot_fork() public { 17 | string[] memory networks = new string[](4); 18 | networks[0] = "optimism"; 19 | networks[1] = "base_goerli"; 20 | networks[2] = "zora"; 21 | networks[3] = "zora_testnet"; 22 | for (uint256 i = 0; i < networks.length; i++) { 23 | vm.createSelectFork(vm.rpcUrl(networks[i])); 24 | _runFork(); 25 | } 26 | } 27 | 28 | function _runFork() internal { 29 | snapshot = new L1BlockSnapshotter(); 30 | snapshot.snapshot(); 31 | L1BlockSnapshot memory l1BlockSnapshot = snapshot.getL1BlockSnapshot(l1Block.number()); 32 | assertEq(l1BlockSnapshot.number, l1Block.number()); 33 | assertEq(l1BlockSnapshot.timestamp, l1Block.timestamp()); 34 | assertEq(l1BlockSnapshot.basefee, l1Block.basefee()); 35 | assertEq(l1BlockSnapshot.hash, l1Block.hash()); 36 | assertEq(l1BlockSnapshot.sequenceNumber, l1Block.sequenceNumber()); 37 | assertEq(l1BlockSnapshot.batcherHash, l1Block.batcherHash()); 38 | assertEq(l1BlockSnapshot.l1FeeOverhead, l1Block.l1FeeOverhead()); 39 | assertEq(l1BlockSnapshot.l1FeeScalar, l1Block.l1FeeScalar()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/L1BlockSnapshotterGas.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {L1BlockSnapshotter, L1BlockSnapshot, IL1Block} from "../src/L1BlockSnapshotter.sol"; 6 | import {L1BlockMock} from "./L1BlockSnapshotter.t.sol"; 7 | 8 | contract L1BlockSnapshotterGasTest is Test { 9 | L1BlockSnapshotter test; 10 | IL1Block mock; 11 | 12 | function setUp() public { 13 | vm.pauseGasMetering(); 14 | test = new L1BlockSnapshotter(); 15 | mock = new L1BlockMock(); 16 | vm.etch(address(test.L1_BLOCK()), address(mock).code); 17 | mock = test.L1_BLOCK(); 18 | mock.setL1BlockValues(1, 2, 3, bytes32(uint256(4)), 5, bytes32(uint256(6)), 7, 8); 19 | } 20 | 21 | modifier metered() { 22 | vm.resumeGasMetering(); 23 | _; 24 | // vm.pauseGasMetering(); 25 | } 26 | 27 | modifier snapshotMetered() { 28 | test.snapshot(); 29 | vm.resumeGasMetering(); 30 | _; 31 | // vm.pauseGasMetering(); 32 | } 33 | 34 | function _runSnapshot(L1BlockSnapshotter snapshot) internal metered { 35 | snapshot.snapshot(); 36 | } 37 | 38 | function _runSnapshotFallback(L1BlockSnapshotter snapshot) internal metered returns (bool succ) { 39 | (succ,) = address(snapshot).call(""); 40 | } 41 | 42 | function _runTimestamp(L1BlockSnapshotter snapshot) internal snapshotMetered { 43 | snapshot.getL1BlockTimestamp(1); 44 | } 45 | 46 | function _runBasefee(L1BlockSnapshotter snapshot) internal snapshotMetered { 47 | snapshot.getL1BlockBasefee(1); 48 | } 49 | 50 | function _runHash(L1BlockSnapshotter snapshot) internal snapshotMetered { 51 | snapshot.getL1BlockHash(1); 52 | } 53 | 54 | function _runSequenceNumber(L1BlockSnapshotter snapshot) internal snapshotMetered { 55 | snapshot.getL1BlockSequenceNumber(1); 56 | } 57 | 58 | function _runBatcherHash(L1BlockSnapshotter snapshot) internal snapshotMetered { 59 | snapshot.getL1BlockBatcherHash(1); 60 | } 61 | 62 | function _runL1FeeOverhead(L1BlockSnapshotter snapshot) internal snapshotMetered { 63 | snapshot.getL1BlockFeeOverhead(1); 64 | } 65 | 66 | function _runL1FeeScalar(L1BlockSnapshotter snapshot) internal snapshotMetered { 67 | snapshot.getL1BlockFeeScalar(1); 68 | } 69 | 70 | function testSnapshot_gasSnapshot() public { 71 | _runSnapshot(test); 72 | } 73 | 74 | function testSnapshotFallback_gasSnapshot() public { 75 | _runSnapshotFallback(test); 76 | } 77 | 78 | function testTimestamp_gasSnapshot() public { 79 | _runTimestamp(test); 80 | } 81 | 82 | function testBasefee_gasSnapshot() public { 83 | _runBasefee(test); 84 | } 85 | 86 | function testHash_gasSnapshot() public { 87 | _runHash(test); 88 | } 89 | 90 | function testSequenceNumber_gasSnapshot() public { 91 | _runSequenceNumber(test); 92 | } 93 | 94 | function testBatcherHash_gasSnapshot() public { 95 | _runBatcherHash(test); 96 | } 97 | 98 | function testL1FeeOverhead_gasSnapshot() public { 99 | _runL1FeeOverhead(test); 100 | } 101 | 102 | function testL1FeeScalar_gasSnapshot() public { 103 | _runL1FeeScalar(test); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/L1BlockSnapshotterSize.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {L1BlockSnapshotter} from "../src/L1BlockSnapshotter.sol"; 6 | import {TestPlus} from "solady-test/utils/TestPlus.sol"; 7 | 8 | contract L1BlockSnapshotterSizeTest is Test, TestPlus { 9 | L1BlockSnapshotter l1BlockSnapshotter; 10 | 11 | function setUp() public { 12 | l1BlockSnapshotter = new L1BlockSnapshotter(); 13 | } 14 | } 15 | --------------------------------------------------------------------------------