├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── foundry.toml ├── src ├── Counter.sol ├── Part1_Counter.sol ├── Part2_Workshop │ ├── L1Contract.sol │ ├── L2Contract.sol │ └── L2ContractAdvanceType.sol ├── Part3_Keystore.sol └── l1sload │ ├── L1SLOAD.sol │ └── MockL1SLOAD.sol └── test ├── Counter.t.sol ├── L1SLOAD.t.sol ├── Part1_Counter.t.sol └── Part3_Keystore.sol /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | env: 9 | FOUNDRY_PROFILE: ci 10 | 11 | jobs: 12 | check: 13 | strategy: 14 | fail-fast: true 15 | 16 | name: Foundry project 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | 23 | - name: Install Foundry 24 | uses: foundry-rs/foundry-toolchain@v1 25 | with: 26 | version: nightly 27 | 28 | - name: Show Forge version 29 | run: | 30 | forge --version 31 | 32 | - name: Run Forge fmt 33 | run: | 34 | forge fmt --check 35 | id: fmt 36 | 37 | - name: Run Forge build 38 | run: | 39 | forge build --sizes 40 | id: build 41 | 42 | - name: Run Forge tests 43 | run: | 44 | forge test -vvv 45 | id: test 46 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "solidity.packageDefaultDependenciesContractsDirectory": "src", 3 | "solidity.packageDefaultDependenciesDirectory": "lib", 4 | "solidity.compileUsingRemoteVersion": "v0.8.13", 5 | "solidity.remappings": ["forge-std/=lib/forge-std/src/"] 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2024 Scroll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## L1SLOAD Workshop 2 | 3 | **L1SLOAD is a precompile that allows your L2 dapp to use L1 (Ethereum) data** 4 | 5 | What L1SLOAD unlocks: 6 | 7 | - Write normal Solidity code to get direct read access to L1 state from your L2 contract. 8 | - Developer-friendly building block for better L1/L2 interoperability. 9 | 10 | Read more about RIP-7728: L1SLOAD [here](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7728.md). 11 | 12 | ## Documentation 13 | 14 | This repository contains the resources which are covered in the [L1SLOAD Workshop](https://app.devcon.org/schedule/ERQ7N3) during Devcon: 15 | 16 | **Part 1**: Includes simple examples to get started. It also shows L1SLOAD utility contracts for configuring a local test environment in Foundry. 17 | 18 | **Part 2**: Explore various examples demonstrating how to interact with different data types and structures in Solidity. 19 | 20 | **Part 3**: Dive into a real-world use case by exploring [Keystore](https://scroll.io/blog/towards-the-wallet-endgame-with-keystore). 21 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /src/Counter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {L1SLOAD} from "./l1sload/L1SLOAD.sol"; 5 | 6 | contract Counter { 7 | uint256 public number; 8 | 9 | function increment() public { 10 | number++; 11 | } 12 | } 13 | 14 | contract CounterReader { 15 | address public immutable counter; 16 | 17 | constructor(address _counter) { 18 | counter = _counter; 19 | } 20 | 21 | function readCounter() external view returns (uint256) { 22 | uint256 val0 = L1SLOAD.readUint256(counter, bytes32(uint256(0))); 23 | return val0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Part1_Counter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {L1SLOAD} from "./l1sload/L1SLOAD.sol"; 5 | 6 | /// @title A simple counter contract 7 | /// @dev This contract should be deployed on L1 8 | contract Counter { 9 | uint256 public number; 10 | mapping(uint256 => uint256) public map; 11 | 12 | function increment() public { 13 | number++; 14 | map[123] = 2 * number; 15 | } 16 | } 17 | 18 | /// @title A simple contract that reads the counter through L1SLOAD 19 | /// @dev This contract should be deployed on L2 20 | contract CounterReader { 21 | /// @dev The L1 counter contract address 22 | address public immutable counter; 23 | 24 | constructor(address _counter) { 25 | counter = _counter; 26 | } 27 | 28 | /// @notice Reads `number` and `map[123]` from L1 using L1SLOAD 29 | /// @dev This function reads values from L1 30 | function readCounter() external view returns (uint256, uint256) { 31 | bytes32 mappingSlot = keccak256(abi.encode( /* mapping key: */ 123, /* mapping slot: */ 1)); 32 | (uint256 val0, uint256 val1) = L1SLOAD.readUint256(counter, bytes32(uint256(0)), mappingSlot); 33 | return (val0, val1); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Part2_Workshop/L1Contract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | contract L1Contract { 5 | ///////////////////////////////////////////////////////////////////////////////////////// 6 | ///////////////////////////// GLOBAL VARIABLES ////////////////////////////////////////// 7 | ///////////////////////////////////////////////////////////////////////////////////////// 8 | 9 | // uint256 10 | uint256 l1Number; //storage slot 0 11 | // bytes 12 | string l1Text; //storage slot 1 13 | // string 14 | bytes32 l1Bytes; //storage slot 2 15 | // fixed or dynamic array 16 | uint256[3] public l1FixedArray; 17 | uint64[4] public l1FixedArray2; 18 | uint256[] public l1DynamicArray; 19 | // dynamic mapping 20 | mapping(address => uint256) l1AddressToNumberMapping; 21 | mapping(address => mapping(string => uint256)) l1NestedMapping; 22 | // struct 23 | 24 | struct L1ProfileStruct { 25 | string name; 26 | uint256 age; 27 | } 28 | 29 | L1ProfileStruct[] public playerProfile; 30 | 31 | ///////////////////////////////////////////////////////////////////////////////////////// 32 | ////////////////////////////// WRITE FUNCTIONS ////////////////////////////////////////// 33 | ///////////////////////////////////////////////////////////////////////////////////////// 34 | 35 | constructor() { 36 | l1FixedArray = [3, 6, 9]; 37 | l1FixedArray2 = [10, 20, 0, 30]; 38 | l1DynamicArray = [100, 200, 300, 400]; 39 | 40 | l1AddressToNumberMapping[address(this)] = 100; 41 | l1NestedMapping[address(this)]["ABC"] = 1000; 42 | } 43 | 44 | function storeNumber(uint256 number) public { 45 | l1Number = number; 46 | } 47 | 48 | function storeText(string memory _stringText) public { 49 | l1Text = _stringText; 50 | } 51 | 52 | function storeBytes(bytes32 _bytes) public { 53 | l1Bytes = _bytes; 54 | } 55 | 56 | function pushFixedArray(uint256 _index, uint256 value) public { 57 | l1FixedArray[_index] = value; 58 | } 59 | 60 | function pushDynamicArray(uint256 value) public { 61 | l1DynamicArray.push(value); 62 | } 63 | 64 | function setl1AddressToNumberMapping(address _someAddress, uint256 _someValue) public { 65 | l1AddressToNumberMapping[_someAddress] = _someValue; 66 | } 67 | 68 | function setNestedMapping(address _nestedAddress, string memory _nestedString, uint256 _nestedValue) public { 69 | l1NestedMapping[_nestedAddress][_nestedString] = _nestedValue; 70 | } 71 | 72 | function setPlayerProfile(string memory _name, uint256 _age) public { 73 | L1ProfileStruct memory newProfile = L1ProfileStruct({name: _name, age: _age}); 74 | playerProfile.push(newProfile); 75 | } 76 | 77 | ///////////////////////////////////////////////////////////////////////////////////////// 78 | ////////////////////////////// READ FUNCTIONS /////////////////////////////////////////// 79 | ///////////////////////////////////////////////////////////////////////////////////////// 80 | 81 | function retrieveNumber() public view returns (uint256) { 82 | return l1Number; 83 | } 84 | 85 | function retrieveString() public view returns (string memory) { 86 | return l1Text; 87 | } 88 | 89 | function retrieveBytes() public view returns (bytes32) { 90 | return l1Bytes; 91 | } 92 | 93 | function retrieveFixedArray() public view returns (uint256[3] memory) { 94 | return l1FixedArray; 95 | } 96 | 97 | function retrieveFixedArray2() public view returns (uint64[4] memory) { 98 | return l1FixedArray2; 99 | } 100 | 101 | function retrieveDynamicArray() public view returns (uint256[] memory) { 102 | return l1DynamicArray; 103 | } 104 | 105 | function retrieveL1AddressToNumberMapping(address _someAddress) public view returns (uint256) { 106 | return l1AddressToNumberMapping[_someAddress]; 107 | } 108 | 109 | function retrieveL1NestedMapping(address _someAddress, string memory _someKey) public view returns (uint256) { 110 | return l1NestedMapping[_someAddress][_someKey]; 111 | } 112 | 113 | function retrieveStructArray() public view returns (L1ProfileStruct[] memory) { 114 | return playerProfile; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Part2_Workshop/L2Contract.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | contract L2Contract { 5 | address immutable L1SLOAD_PRECOMPILE = 0x0000000000000000000000000000000000000101; 6 | address immutable l1sloadContractAddress; 7 | 8 | constructor(address _l1sloadContractAddress) { 9 | l1sloadContractAddress = _l1sloadContractAddress; 10 | } 11 | 12 | function retrieveL1Number() public view returns (uint256) { 13 | uint256 l1SlotNumber = 0; 14 | (bool success, bytes memory data) = 15 | L1SLOAD_PRECOMPILE.staticcall(abi.encodePacked(l1sloadContractAddress, l1SlotNumber)); 16 | if (!success) { 17 | revert("Error retrieving L1 Number"); 18 | } 19 | return abi.decode(data, (uint256)); 20 | } 21 | 22 | function retrieveL1String() public view returns (string memory) { 23 | uint256 l1SlotNumber = 1; // string storage slot 24 | uint256 slotSequence = 0; // string slot ++ 25 | string memory stringResult = ""; 26 | 27 | while (true) { 28 | (bool success, bytes memory data) = L1SLOAD_PRECOMPILE.staticcall( 29 | abi.encodePacked( 30 | l1sloadContractAddress, uint256(keccak256(abi.encodePacked(l1SlotNumber))) + slotSequence 31 | ) 32 | ); 33 | if (!success) { 34 | revert("Error retrieving L1 String"); 35 | } 36 | 37 | string memory retrievedString = string(data); 38 | 39 | // Check if the retrieved string is empty 40 | if (keccak256(data) == keccak256(abi.encodePacked(uint256(0)))) { 41 | break; 42 | } 43 | 44 | stringResult = string(abi.encodePacked(stringResult, retrievedString)); 45 | slotSequence++; 46 | } 47 | return stringResult; 48 | } 49 | 50 | // variable tester 51 | function retrieveL1StringSlotBySequence(uint256 testSlots) public view returns (string memory) { 52 | uint256 l1SlotNumber = 1; // string storage slot 53 | (bool success, bytes memory data) = L1SLOAD_PRECOMPILE.staticcall( 54 | abi.encodePacked(l1sloadContractAddress, uint256(keccak256(abi.encodePacked(l1SlotNumber))) + testSlots) 55 | ); 56 | if (!success) { 57 | revert("Error retrieving L1 String"); 58 | } 59 | return string(data); 60 | } 61 | 62 | function retrieveL1Bytes() public view returns (bytes memory) { 63 | uint256 l1SlotNumber = 2; //storage of l1Bytes 64 | (bool success, bytes memory data) = 65 | L1SLOAD_PRECOMPILE.staticcall(abi.encodePacked(l1sloadContractAddress, l1SlotNumber)); 66 | if (!success) { 67 | revert("Error retrieving L1 Bytes"); 68 | } 69 | return bytes(data); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Part2_Workshop/L2ContractAdvanceType.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.26; 3 | 4 | contract L2ContractAdvanceType { 5 | address immutable L1SLOAD_PRECOMPILE = 0x0000000000000000000000000000000000000101; 6 | address immutable l1sloadContractAddress; 7 | 8 | struct L1ProfileStruct { 9 | string name; 10 | uint256 age; 11 | } 12 | 13 | constructor(address _l1sloadContractAddress) { 14 | l1sloadContractAddress = _l1sloadContractAddress; 15 | } 16 | 17 | // Retrieve fixed L1 Array 18 | function retrieveFixedArrayElement(uint256 variableStorageSlot, uint256 arraySlot) public view returns (uint256) { 19 | (bool success, bytes memory data) = 20 | L1SLOAD_PRECOMPILE.staticcall(abi.encodePacked(l1sloadContractAddress, variableStorageSlot + arraySlot)); 21 | if (!success) { 22 | revert("Error retrieving fixed array elements"); 23 | } 24 | return abi.decode(data, (uint256)); 25 | } 26 | 27 | // Retrieve Dynamic Array 28 | function retrieveDynamicArrayNumber(uint256 variableStorageSlot, uint256 arrayIndex) 29 | public 30 | view 31 | returns (uint256) 32 | { 33 | (bool success, bytes memory data) = L1SLOAD_PRECOMPILE.staticcall( 34 | abi.encodePacked( 35 | l1sloadContractAddress, uint256(keccak256(abi.encodePacked(variableStorageSlot))) + arrayIndex 36 | ) 37 | ); 38 | if (!success) { 39 | revert("Error retrieving L1 array"); 40 | } 41 | return abi.decode(data, (uint256)); 42 | } 43 | 44 | // Retrieve mapping 45 | function retrieveL1AddressToMapping(uint256 variableStorageSlot, address _mappingAddress) 46 | public 47 | view 48 | returns (uint256) 49 | { 50 | (bool success, bytes memory data) = L1SLOAD_PRECOMPILE.staticcall( 51 | abi.encodePacked( 52 | l1sloadContractAddress, 53 | uint256(keccak256(abi.encodePacked(uint256(uint160(_mappingAddress)), variableStorageSlot))) 54 | ) 55 | ); 56 | if (!success) { 57 | revert("Error extracting mapping data"); 58 | } 59 | return abi.decode(data, (uint256)); 60 | } 61 | 62 | // Retrieve nested mapping 63 | function retrieveL1NestedMapping(uint256 variableStorageSlot, address _mappingAddress, string memory _mappingString) 64 | public 65 | view 66 | returns (uint256) 67 | { 68 | bytes32 initialSlot = keccak256(abi.encodePacked(uint256(uint160(_mappingAddress)), variableStorageSlot)); 69 | bytes32 finalSlot = keccak256(abi.encodePacked(_mappingString, initialSlot)); 70 | 71 | (bool success, bytes memory data) = 72 | L1SLOAD_PRECOMPILE.staticcall(abi.encodePacked(l1sloadContractAddress, finalSlot)); 73 | if (!success) { 74 | revert("Error extracting nested mapping data"); 75 | } 76 | return abi.decode(data, (uint256)); 77 | } 78 | 79 | // Retrieve struct 80 | function retrieveStruct(uint256 variableStorageSlot, uint256 arrayIndex) public view returns (bytes memory) { 81 | (bool success, bytes memory data) = L1SLOAD_PRECOMPILE.staticcall( 82 | abi.encodePacked( 83 | l1sloadContractAddress, uint256(keccak256(abi.encodePacked(variableStorageSlot))) + arrayIndex 84 | ) 85 | ); 86 | if (!success) { 87 | revert("Error retrieving L1 array"); 88 | } 89 | return data; 90 | } 91 | 92 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 93 | ////////////////////////////////////////////// UTILS //////////////////////////////////////////////////////////////// 94 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 95 | 96 | function convertBytesToString(bytes memory _bytes) public pure returns (string memory) { 97 | return string(_bytes); 98 | } 99 | 100 | function convertBytesToUint(bytes memory _bytes) public pure returns (uint256) { 101 | return abi.decode(_bytes, (uint256)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Part3_Keystore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {L1SLOAD} from "./l1sload/L1SLOAD.sol"; 5 | 6 | /// @title A simple wallet that allows initiating transactions from one or more signers. 7 | abstract contract Wallet { 8 | error Unauthorized(); 9 | error CallFailed(); 10 | 11 | /// @notice Returns true if `signer` is authorized to use this wallet 12 | function isAuthorized(address signer) public virtual returns (bool); 13 | 14 | function executeTransaction(address to, uint256 value, bytes memory data, uint256 txGas) external { 15 | if (!isAuthorized(msg.sender)) { 16 | revert Unauthorized(); 17 | } 18 | 19 | bool success; 20 | 21 | assembly { 22 | success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) 23 | } 24 | 25 | if (!success) { 26 | revert CallFailed(); 27 | } 28 | } 29 | } 30 | 31 | contract L1Wallet is Wallet { 32 | /// @dev A mapping to store authorized signers in contract storage 33 | mapping(address => bool) public isAuthorizedSigner; 34 | 35 | constructor() { 36 | // creator is always authorized 37 | isAuthorizedSigner[msg.sender] = true; 38 | } 39 | 40 | /// @notice Adds a new signer to the authorized signer list 41 | function addSigner(address signer) external { 42 | if (!isAuthorized(msg.sender)) revert Unauthorized(); 43 | isAuthorizedSigner[signer] = true; 44 | } 45 | 46 | /// @inheritdoc Wallet 47 | function isAuthorized(address signer) public view override returns (bool) { 48 | return isAuthorizedSigner[signer]; 49 | } 50 | } 51 | 52 | contract L2Wallet is Wallet { 53 | /// @dev The L1 wallet contract address 54 | address public l1Wallet; 55 | 56 | constructor(address _l1Wallet) { 57 | l1Wallet = _l1Wallet; 58 | } 59 | 60 | /// @inheritdoc Wallet 61 | /// @dev This function reads the signer value from L1 62 | function isAuthorized(address signer) public view override returns (bool) { 63 | bytes32 mappingSlot = keccak256(abi.encode( /* mapping key: */ signer, /* mapping slot: */ 0)); 64 | bool authorized = L1SLOAD.readBool(l1Wallet, mappingSlot); 65 | return authorized; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/l1sload/L1SLOAD.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | library L1SLOAD { 5 | address public constant ADDRESS = 0x5300000000000000000000000000000000000001; 6 | 7 | function readAddress(address addr, bytes32 slot0) public view returns (address) { 8 | bytes memory call = abi.encodePacked(addr, slot0); 9 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 10 | if (!success) revert("failed to call l1sload"); 11 | return abi.decode(ret, (address)); 12 | } 13 | 14 | function readBool(address addr, bytes32 slot0) public view returns (bool) { 15 | bytes memory call = abi.encodePacked(addr, slot0); 16 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 17 | if (!success) revert("failed to call l1sload"); 18 | return abi.decode(ret, (bool)); 19 | } 20 | 21 | function readUint256(address addr, bytes32 slot0) public view returns (uint256) { 22 | bytes memory call = abi.encodePacked(addr, slot0); 23 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 24 | if (!success) revert("failed to call l1sload"); 25 | return abi.decode(ret, (uint256)); 26 | } 27 | 28 | function readUint256(address addr, bytes32 slot0, bytes32 slot1) public view returns (uint256, uint256) { 29 | bytes memory call = abi.encodePacked(addr, slot0, slot1); 30 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 31 | if (!success) revert("failed to call l1sload"); 32 | return abi.decode(ret, (uint256, uint256)); 33 | } 34 | 35 | function readUint256(address addr, bytes32 slot0, bytes32 slot1, bytes32 slot2) 36 | public 37 | view 38 | returns (uint256, uint256, uint256) 39 | { 40 | bytes memory call = abi.encodePacked(addr, slot0, slot1, slot2); 41 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 42 | if (!success) revert("failed to call l1sload"); 43 | return abi.decode(ret, (uint256, uint256, uint256)); 44 | } 45 | 46 | function readUint256(address addr, bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3) 47 | public 48 | view 49 | returns (uint256, uint256, uint256, uint256) 50 | { 51 | bytes memory call = abi.encodePacked(addr, slot0, slot1, slot2, slot3); 52 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 53 | if (!success) revert("failed to call l1sload"); 54 | return abi.decode(ret, (uint256, uint256, uint256, uint256)); 55 | } 56 | 57 | function readUint256(address addr, bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4) 58 | public 59 | view 60 | returns (uint256, uint256, uint256, uint256, uint256) 61 | { 62 | bytes memory call = abi.encodePacked(addr, slot0, slot1, slot2, slot3, slot4); 63 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 64 | if (!success) revert("failed to call l1sload"); 65 | return abi.decode(ret, (uint256, uint256, uint256, uint256, uint256)); 66 | } 67 | 68 | function readBytes32(address addr, bytes32 slot0) public view returns (bytes32) { 69 | bytes memory call = abi.encodePacked(addr, slot0); 70 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 71 | if (!success) revert("failed to call l1sload"); 72 | return abi.decode(ret, (bytes32)); 73 | } 74 | 75 | function readBytes32(address addr, bytes32 slot0, bytes32 slot1) public view returns (bytes32, bytes32) { 76 | bytes memory call = abi.encodePacked(addr, slot0, slot1); 77 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 78 | if (!success) revert("failed to call l1sload"); 79 | return abi.decode(ret, (bytes32, bytes32)); 80 | } 81 | 82 | function readBytes32(address addr, bytes32 slot0, bytes32 slot1, bytes32 slot2) 83 | public 84 | view 85 | returns (bytes32, bytes32, bytes32) 86 | { 87 | bytes memory call = abi.encodePacked(addr, slot0, slot1, slot2); 88 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 89 | if (!success) revert("failed to call l1sload"); 90 | return abi.decode(ret, (bytes32, bytes32, bytes32)); 91 | } 92 | 93 | function readBytes32(address addr, bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3) 94 | public 95 | view 96 | returns (bytes32, bytes32, bytes32, bytes32) 97 | { 98 | bytes memory call = abi.encodePacked(addr, slot0, slot1, slot2, slot3); 99 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 100 | if (!success) revert("failed to call l1sload"); 101 | return abi.decode(ret, (bytes32, bytes32, bytes32, bytes32)); 102 | } 103 | 104 | function readBytes32(address addr, bytes32 slot0, bytes32 slot1, bytes32 slot2, bytes32 slot3, bytes32 slot4) 105 | public 106 | view 107 | returns (bytes32, bytes32, bytes32, bytes32, bytes32) 108 | { 109 | bytes memory call = abi.encodePacked(addr, slot0, slot1, slot2, slot3, slot4); 110 | (bool success, bytes memory ret) = ADDRESS.staticcall(call); 111 | if (!success) revert("failed to call l1sload"); 112 | return abi.decode(ret, (bytes32, bytes32, bytes32, bytes32, bytes32)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/l1sload/MockL1SLOAD.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | 6 | import {L1SLOAD} from "./L1SLOAD.sol"; 7 | 8 | contract MockL1SLOAD is Test { 9 | fallback(bytes calldata data) external returns (bytes memory res) { 10 | // check data length 11 | if (data.length < 20 + 32) { 12 | revert("invalid payload passed to l1sload"); 13 | } 14 | 15 | if ((data.length - 20) % 32 != 0) { 16 | revert("invalid payload passed to l1sload"); 17 | } 18 | 19 | // decode address 20 | address addr; 21 | 22 | assembly { 23 | addr := shr(96, calldataload(data.offset)) 24 | } 25 | 26 | // decode and read storage slots 27 | for (uint256 index = 0; index < 5; index++) { 28 | if (data.length < 20 + (index + 1) * 32) break; 29 | 30 | bytes32 slot; 31 | uint256 dataindex = 20 + index * 32; 32 | 33 | assembly { 34 | slot := calldataload(add(data.offset, dataindex)) 35 | } 36 | 37 | // execute l1sload 38 | // note: here we are simulating l1sload with a local sload, 39 | // in reality this would be an rpc call executed by the sequencer. 40 | bytes32 val = vm.load(address(addr), slot); 41 | 42 | res = abi.encodePacked(res, val); 43 | } 44 | } 45 | } 46 | 47 | contract TestWithL1SLOAD is Test { 48 | function setUp() public virtual { 49 | MockL1SLOAD l1sload = new MockL1SLOAD(); 50 | bytes memory code = address(l1sload).code; 51 | vm.etch(L1SLOAD.ADDRESS, code); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/Counter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | 6 | import {TestWithL1SLOAD} from "../src/l1sload/MockL1SLOAD.sol"; 7 | import {Counter, CounterReader} from "../src/Counter.sol"; 8 | 9 | contract CounterTest is Test, TestWithL1SLOAD { 10 | Counter public counter; 11 | 12 | function setUp() public virtual override { 13 | super.setUp(); 14 | 15 | // simulate L1 contract deployment 16 | counter = new Counter(); 17 | } 18 | 19 | function testReadCounter() public { 20 | // simulate L2 contract deployment 21 | CounterReader reader = new CounterReader(address(counter)); 22 | 23 | uint256 val0 = reader.readCounter(); 24 | assertEq(val0, 0); 25 | 26 | counter.increment(); 27 | 28 | val0 = reader.readCounter(); 29 | assertEq(val0, 1); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/L1SLOAD.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | 6 | import {TestWithL1SLOAD} from "../src/l1sload/MockL1SLOAD.sol"; 7 | import {L1SLOAD} from "../src/l1sload/L1SLOAD.sol"; 8 | 9 | contract Storage { 10 | function write(bytes32 slot, bytes32 val) public { 11 | assembly { 12 | sstore(slot, val) 13 | } 14 | } 15 | } 16 | 17 | contract L1SLOADTest is Test, TestWithL1SLOAD { 18 | Storage public st; 19 | 20 | function setUp() public virtual override { 21 | super.setUp(); 22 | 23 | st = new Storage(); 24 | } 25 | 26 | function test_readUint256_1(bytes32 slot0, uint256 val0) public { 27 | st.write(slot0, bytes32(val0)); 28 | 29 | uint256 read0 = L1SLOAD.readUint256(address(st), slot0); 30 | assertEq(read0, val0); 31 | } 32 | 33 | function test_readUint256_2(bytes32 slot0, uint256 val0, bytes32 slot1, uint256 val1) public { 34 | vm.assume(slot0 != slot1); 35 | 36 | st.write(slot0, bytes32(val0)); 37 | st.write(slot1, bytes32(val1)); 38 | 39 | // read one-by-one 40 | uint256 read0 = L1SLOAD.readUint256(address(st), slot0); 41 | uint256 read1 = L1SLOAD.readUint256(address(st), slot1); 42 | assertEq(read0, val0); 43 | assertEq(read1, val1); 44 | 45 | // read together 46 | (read0, read1) = L1SLOAD.readUint256(address(st), slot0, slot1); 47 | assertEq(read0, val0); 48 | assertEq(read1, val1); 49 | 50 | // read in different order 51 | (read1, read0) = L1SLOAD.readUint256(address(st), slot1, slot0); 52 | assertEq(read0, val0); 53 | assertEq(read1, val1); 54 | 55 | // read the same twice 56 | (read0, read1) = L1SLOAD.readUint256(address(st), slot0, slot0); 57 | assertEq(read0, val0); 58 | assertEq(read1, val0); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/Part1_Counter.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {TestWithL1SLOAD} from "../src/l1sload/MockL1SLOAD.sol"; 6 | 7 | import {Counter, CounterReader} from "../src/Part1_Counter.sol"; 8 | 9 | contract CounterTest is Test, TestWithL1SLOAD { 10 | Counter public l1Counter; 11 | 12 | function setUp() public virtual override { 13 | super.setUp(); 14 | 15 | l1Counter = new Counter(); 16 | } 17 | 18 | function test_Increment() public { 19 | l1Counter.increment(); 20 | assertEq(l1Counter.number(), 1); 21 | } 22 | 23 | function testReadCounter() public { 24 | CounterReader l2Reader = new CounterReader(address(l1Counter)); 25 | 26 | (uint256 val0, uint256 val1) = l2Reader.readCounter(); 27 | assertEq(val0, 0); 28 | assertEq(val1, 0); 29 | 30 | l1Counter.increment(); 31 | 32 | (val0, val1) = l2Reader.readCounter(); 33 | assertEq(val0, 1); 34 | assertEq(val1, 2); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/Part3_Keystore.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test} from "forge-std/Test.sol"; 5 | import {TestWithL1SLOAD} from "../src/l1sload/MockL1SLOAD.sol"; 6 | 7 | import {L1Wallet, L2Wallet} from "../src/Part3_Keystore.sol"; 8 | 9 | contract WalletTest is Test, TestWithL1SLOAD { 10 | L1Wallet public l1Wallet; 11 | L2Wallet public l2Wallet; 12 | 13 | function setUp() public virtual override { 14 | super.setUp(); 15 | 16 | l1Wallet = new L1Wallet(); 17 | l2Wallet = new L2Wallet(address(l1Wallet)); 18 | } 19 | 20 | function testDeployedAuthorized() public view { 21 | assertTrue(l1Wallet.isAuthorized(address(this))); 22 | assertTrue(l2Wallet.isAuthorized(address(this))); 23 | } 24 | 25 | function testAddSigner(address signer) public { 26 | vm.assume(signer != address(this)); 27 | 28 | assertFalse(l1Wallet.isAuthorized(signer)); 29 | assertFalse(l2Wallet.isAuthorized(signer)); 30 | 31 | l1Wallet.addSigner(signer); 32 | 33 | assertTrue(l1Wallet.isAuthorized(signer)); 34 | assertTrue(l2Wallet.isAuthorized(signer)); 35 | } 36 | } 37 | --------------------------------------------------------------------------------