From 1ffb8455aaf7ef4e6cf9b9beebd461405f89a1f0 Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Mon, 11 Mar 2024 12:42:34 +0300 Subject: [PATCH 01/10] chore: forge init --- .../.github/workflows/test.yml | 34 ++++++++++ .../src/evm/system_contracts/.gitignore | 14 ++++ .../src/evm/system_contracts/README.md | 66 +++++++++++++++++++ .../src/evm/system_contracts/foundry.toml | 6 ++ .../evm/system_contracts/script/Counter.s.sol | 12 ++++ .../src/evm/system_contracts/src/Counter.sol | 14 ++++ .../evm/system_contracts/test/Counter.t.sol | 24 +++++++ 7 files changed, 170 insertions(+) create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/.gitignore create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/README.md create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/foundry.toml create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml b/module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml new file mode 100644 index 000000000..9282e8294 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: test + +on: workflow_dispatch + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Forge build + run: | + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/.gitignore b/module-system/module-implementations/sov-evm/src/evm/system_contracts/.gitignore new file mode 100644 index 000000000..85198aaa5 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/README.md b/module-system/module-implementations/sov-evm/src/evm/system_contracts/README.md new file mode 100644 index 000000000..9265b4558 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/foundry.toml b/module-system/module-implementations/sov-evm/src/evm/system_contracts/foundry.toml new file mode 100644 index 000000000..25b918f9c --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol new file mode 100644 index 000000000..df9ee8b02 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; + +contract CounterScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + } +} diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol new file mode 100644 index 000000000..aded7997b --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol new file mode 100644 index 000000000..54b724f7a --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test, console} from "forge-std/Test.sol"; +import {Counter} from "../src/Counter.sol"; + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} From 5ffa66baca9c970016afd37fcd8ec7ac18615d5c Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Mon, 11 Mar 2024 12:42:37 +0300 Subject: [PATCH 02/10] forge install: forge-std v1.7.6 --- .gitmodules | 3 +++ .../sov-evm/src/evm/system_contracts/lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..16945c69b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std"] + path = module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std new file mode 160000 index 000000000..ae570fec0 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce From 8083067eaaa88ae9c9a1500b47b4fb9da882d39b Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Mon, 11 Mar 2024 14:58:39 +0300 Subject: [PATCH 03/10] Add blockhash contract --- .../src/evm/system_contracts/lib/Ownable.sol | 41 +++++++++++++++++ .../evm/system_contracts/script/Counter.s.sol | 12 ----- .../src/evm/system_contracts/src/Counter.sol | 14 ------ .../system_contracts/src/L1BlockHashList.sol | 22 +++++++++ .../src/interfaces/IL1BlockHashList.sol | 7 +++ .../evm/system_contracts/test/Counter.t.sol | 24 ---------- .../test/L1BlockHashList.t.sol | 36 +++++++++++++++ .../evm/system_contracts/test/Ownable.t.sol | 46 +++++++++++++++++++ 8 files changed, 152 insertions(+), 50 deletions(-) create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol delete mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol delete mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol delete mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol create mode 100644 module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Ownable.t.sol diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol new file mode 100644 index 000000000..4e41de368 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +abstract contract Ownable { + address public owner; + address public pendingOwner; + + event OwnershipTransferred(address previousOwner, address newOwner); + event OwnershipTransferRequested(address previousOwner, address newOwner); + + modifier onlyOwner() { + require(msg.sender == owner, "Caller is not owner"); + _; + } + + modifier onlyPendingOwner() { + require(msg.sender == pendingOwner, "Caller is not pending owner"); + _; + } + + constructor() { + owner = msg.sender; + } + + function renounceOwnership() public onlyOwner { + owner = address(0); + emit OwnershipTransferred(owner, address(0)); + } + + function transferOwnership(address newOwner) public onlyOwner { + pendingOwner = newOwner; + emit OwnershipTransferRequested(owner, newOwner); + } + + function acceptOwnership() public onlyPendingOwner { + address old_owner = owner; + owner = pendingOwner; + pendingOwner = address(0); + emit OwnershipTransferred(old_owner, pendingOwner); + } +} \ No newline at end of file diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol deleted file mode 100644 index df9ee8b02..000000000 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol deleted file mode 100644 index aded7997b..000000000 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol new file mode 100644 index 000000000..8f75ac9ee --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "../lib/Ownable.sol"; +import "./interfaces/IL1BlockHashList.sol"; + +contract L1BlockHashList is Ownable, IL1BlockHashList { + mapping(uint256 => bytes32) public blockHashes; + uint256 public blockNumber; + + event BlockHashAdded(uint256 blockNumber, bytes32 blockHash); + constructor() Ownable(){ } + + function setBlockHash(bytes32 blockHash) public onlyOwner { + blockHashes[blockNumber++] = blockHash; + emit BlockHashAdded(blockNumber, blockHash); + } + + function getBlockHash(uint256 _blockNumber) public view returns (bytes32) { + return blockHashes[_blockNumber]; + } +} diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol new file mode 100644 index 000000000..cc1d59296 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +interface IL1BlockHashList { + function setBlockHash(bytes32) external; + function getBlockHash(uint256) external view returns (bytes32); +} \ No newline at end of file diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol deleted file mode 100644 index 54b724f7a..000000000 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -} diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol new file mode 100644 index 000000000..a8a539cca --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; +import "../src/L1BlockHashList.sol"; + +contract L1BlockHashListTest is Test { + L1BlockHashList l1BlockHashList; + bytes32 randomBlockHash = bytes32(keccak256("CITREA_TEST")); + + function setUp() public { + l1BlockHashList = new L1BlockHashList(); + } + + function testSetBlockHash() public { + l1BlockHashList.setBlockHash(randomBlockHash); + assertEq(l1BlockHashList.getBlockHash(0), randomBlockHash); + } + + function testNonOwnerCannotSetBlockHash() public { + vm.startPrank(address(0x1)); + vm.expectRevert("Caller is not owner"); + l1BlockHashList.setBlockHash(randomBlockHash); + } + + function testBlockHashAvailableAfterManyWrites() public { + for (uint256 i = 0; i < 100; i++) { + bytes32 blockHash = keccak256(abi.encodePacked(i)); + l1BlockHashList.setBlockHash(blockHash); + assertEq(l1BlockHashList.getBlockHash(i), blockHash); + } + + bytes32 zeroth_hash = keccak256(abi.encodePacked(uint(0))); + assertEq(l1BlockHashList.getBlockHash(0), zeroth_hash); + } +} \ No newline at end of file diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Ownable.t.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Ownable.t.sol new file mode 100644 index 000000000..e5f7280b1 --- /dev/null +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/Ownable.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import "../lib/Ownable.sol"; +import "forge-std/Test.sol"; + +contract OwnableHarness is Ownable { + constructor () Ownable() { } + + function privilegedFunction() public onlyOwner { + } +} + +contract OwnableTest is Test { + OwnableHarness ownable; + + function setUp() public { + ownable = new OwnableHarness(); + } + + function testOnlyOwner() public { + ownable.privilegedFunction(); + address non_owner = address(0x1); + vm.startPrank(non_owner); + vm.expectRevert("Caller is not owner"); + ownable.privilegedFunction(); + } + + function testTransferOwnership() public { + ownable.transferOwnership(address(0x1)); + assertEq(ownable.pendingOwner(), address(0x1)); + } + + function testAcceptOwnership() public { + address new_owner = address(0x1); + ownable.transferOwnership(new_owner); + vm.startPrank(new_owner); + ownable.acceptOwnership(); + assertEq(ownable.owner(), new_owner); + } + + function testRenounceOwnership() public { + ownable.renounceOwnership(); + assertEq(ownable.owner(), address(0)); + } +} \ No newline at end of file From 1030e442be89c17c1d20165b5670e5acce914192 Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:30:31 +0300 Subject: [PATCH 04/10] Change license --- .../sov-evm/src/evm/system_contracts/lib/Ownable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol index 4e41de368..1edf917d4 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/lib/Ownable.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity ^0.8.13; abstract contract Ownable { From 511e707670fcf94caeefac55b16b26867cd4d857 Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:54:56 +0300 Subject: [PATCH 05/10] Add merkle root info --- .../system_contracts/src/L1BlockHashList.sol | 18 ++++++++++++---- .../src/interfaces/IL1BlockHashList.sol | 4 +++- .../test/L1BlockHashList.t.sol | 21 +++++++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol index 8f75ac9ee..aba17f9d8 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol @@ -6,17 +6,27 @@ import "./interfaces/IL1BlockHashList.sol"; contract L1BlockHashList is Ownable, IL1BlockHashList { mapping(uint256 => bytes32) public blockHashes; + mapping(bytes32 => bytes32) public merkleRoots; uint256 public blockNumber; - event BlockHashAdded(uint256 blockNumber, bytes32 blockHash); + event BlockInfoAdded(uint256 blockNumber, bytes32 blockHash, bytes32 merkleRoot); constructor() Ownable(){ } - function setBlockHash(bytes32 blockHash) public onlyOwner { - blockHashes[blockNumber++] = blockHash; - emit BlockHashAdded(blockNumber, blockHash); + function setBlockInfo(bytes32 _blockHash, bytes32 _merkleRoot) public onlyOwner { + blockHashes[blockNumber++] = _blockHash; + merkleRoots[_blockHash] = _merkleRoot; + emit BlockInfoAdded(blockNumber, _blockHash, _merkleRoot); } function getBlockHash(uint256 _blockNumber) public view returns (bytes32) { return blockHashes[_blockNumber]; } + + function getMerkleRootFromBlockHash(bytes32 _blockHash) public view returns (bytes32) { + return merkleRoots[_blockHash]; + } + + function getMerkleRootFromBlockNumber(uint256 _blockNumber) public view returns (bytes32) { + return merkleRoots[blockHashes[_blockNumber]]; + } } diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol index cc1d59296..ee787c8d3 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol @@ -2,6 +2,8 @@ pragma solidity ^0.8.13; interface IL1BlockHashList { - function setBlockHash(bytes32) external; + function setBlockInfo(bytes32, bytes32) external; function getBlockHash(uint256) external view returns (bytes32); + function getMerkleRootFromBlockHash(bytes32) external view returns (bytes32); + function getMerkleRootFromBlockNumber(uint256) external view returns (bytes32); } \ No newline at end of file diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol index a8a539cca..9a15ede99 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol @@ -7,30 +7,39 @@ import "../src/L1BlockHashList.sol"; contract L1BlockHashListTest is Test { L1BlockHashList l1BlockHashList; bytes32 randomBlockHash = bytes32(keccak256("CITREA_TEST")); + bytes32 randomMerkleRoot = bytes32(keccak256("CITREA")); function setUp() public { l1BlockHashList = new L1BlockHashList(); } - function testSetBlockHash() public { - l1BlockHashList.setBlockHash(randomBlockHash); + function testSetBlockInfo() public { + l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); assertEq(l1BlockHashList.getBlockHash(0), randomBlockHash); + assertEq(l1BlockHashList.getMerkleRootFromBlockHash(randomBlockHash), randomMerkleRoot); + assertEq(l1BlockHashList.getMerkleRootFromBlockNumber(0), randomMerkleRoot); } - function testNonOwnerCannotSetBlockHash() public { + function testNonOwnerCannotSetBlockInfo() public { vm.startPrank(address(0x1)); vm.expectRevert("Caller is not owner"); - l1BlockHashList.setBlockHash(randomBlockHash); + l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); } - function testBlockHashAvailableAfterManyWrites() public { + function testBlockInfoAvailableAfterManyWrites() public { for (uint256 i = 0; i < 100; i++) { bytes32 blockHash = keccak256(abi.encodePacked(i)); - l1BlockHashList.setBlockHash(blockHash); + bytes32 root = keccak256(abi.encodePacked(blockHash)); + l1BlockHashList.setBlockInfo(blockHash, root); assertEq(l1BlockHashList.getBlockHash(i), blockHash); + assertEq(l1BlockHashList.getMerkleRootFromBlockHash(blockHash), root); + assertEq(l1BlockHashList.getMerkleRootFromBlockNumber(i), root); } bytes32 zeroth_hash = keccak256(abi.encodePacked(uint(0))); + bytes32 zeroth_root = keccak256(abi.encodePacked(zeroth_hash)); assertEq(l1BlockHashList.getBlockHash(0), zeroth_hash); + assertEq(l1BlockHashList.getMerkleRootFromBlockHash(zeroth_hash), zeroth_root); + assertEq(l1BlockHashList.getMerkleRootFromBlockNumber(0), zeroth_root); } } \ No newline at end of file From 47dd8cd320946a3528740cb0af165cef9b1cfca5 Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:57:49 +0300 Subject: [PATCH 06/10] Add initialize block number function and natspec --- .../test.yml => .github/workflows/foundry.yml | 4 +- .../system_contracts/src/L1BlockHashList.sol | 30 ++++++++++++-- .../src/interfaces/IL1BlockHashList.sol | 5 ++- .../test/L1BlockHashList.t.sol | 39 ++++++++++++++----- 4 files changed, 63 insertions(+), 15 deletions(-) rename module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml => .github/workflows/foundry.yml (76%) diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml b/.github/workflows/foundry.yml similarity index 76% rename from module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml rename to .github/workflows/foundry.yml index 9282e8294..9a7cc6f7e 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/.github/workflows/test.yml +++ b/.github/workflows/foundry.yml @@ -1,4 +1,4 @@ -name: test +name: foundry on: workflow_dispatch @@ -24,11 +24,13 @@ jobs: - name: Run Forge build run: | + cd module-system/module-implementations/sov-evm/src/evm/system_contracts forge --version forge build --sizes id: build - name: Run Forge tests run: | + cd module-system/module-implementations/sov-evm/src/evm/system_contracts forge test -vvv id: test diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol index aba17f9d8..99eacc163 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/L1BlockHashList.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.13; import "../lib/Ownable.sol"; import "./interfaces/IL1BlockHashList.sol"; +/// @title A system contract that stores block hashes and merkle roots of L1 blocks +/// @author Citrea + contract L1BlockHashList is Ownable, IL1BlockHashList { mapping(uint256 => bytes32) public blockHashes; mapping(bytes32 => bytes32) public merkleRoots; @@ -12,21 +15,42 @@ contract L1BlockHashList is Ownable, IL1BlockHashList { event BlockInfoAdded(uint256 blockNumber, bytes32 blockHash, bytes32 merkleRoot); constructor() Ownable(){ } + /// @notice Sets the initial value for the block number, can only be called once + /// @param _blockNumber The L1 block number that is associated with the genesis block of Citrea + function initializeBlockNumber(uint256 _blockNumber) public onlyOwner { + require(blockNumber == 0, "Already initialized"); + blockNumber = _blockNumber; + } + + /// @notice Sets the block hash and merkle root for a given block + /// @notice Can only be called after the initial block number is set + /// @dev The block number is incremented by the contract as no block info should be overwritten or skipped + /// @param _blockHash The hash of the current L1 block + /// @param _merkleRoot The merkle root of the current L1 block function setBlockInfo(bytes32 _blockHash, bytes32 _merkleRoot) public onlyOwner { - blockHashes[blockNumber++] = _blockHash; + uint256 _blockNumber = blockNumber; + require(_blockNumber != 0, "Not initialized"); + blockHashes[_blockNumber] = _blockHash; + blockNumber = _blockNumber + 1; merkleRoots[_blockHash] = _merkleRoot; emit BlockInfoAdded(blockNumber, _blockHash, _merkleRoot); } + /// @param _blockNumber The number of the block to get the hash for + /// @return The block hash for the given block function getBlockHash(uint256 _blockNumber) public view returns (bytes32) { return blockHashes[_blockNumber]; } - function getMerkleRootFromBlockHash(bytes32 _blockHash) public view returns (bytes32) { + /// @param _blockHash The block hash of the block to get the merkle root for + /// @return The merkle root for the given block + function getMerkleRootByHash(bytes32 _blockHash) public view returns (bytes32) { return merkleRoots[_blockHash]; } - function getMerkleRootFromBlockNumber(uint256 _blockNumber) public view returns (bytes32) { + /// @param _blockNumber The block number of the block to get the merkle root for + /// @return The merkle root for the given block + function getMerkleRootByNumber(uint256 _blockNumber) public view returns (bytes32) { return merkleRoots[blockHashes[_blockNumber]]; } } diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol index ee787c8d3..06da7b7d7 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/src/interfaces/IL1BlockHashList.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.13; interface IL1BlockHashList { + function initializeBlockNumber(uint256) external; function setBlockInfo(bytes32, bytes32) external; function getBlockHash(uint256) external view returns (bytes32); - function getMerkleRootFromBlockHash(bytes32) external view returns (bytes32); - function getMerkleRootFromBlockNumber(uint256) external view returns (bytes32); + function getMerkleRootByHash(bytes32) external view returns (bytes32); + function getMerkleRootByNumber(uint256) external view returns (bytes32); } \ No newline at end of file diff --git a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol index 9a15ede99..fa6204a8a 100644 --- a/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol +++ b/module-system/module-implementations/sov-evm/src/evm/system_contracts/test/L1BlockHashList.t.sol @@ -8,38 +8,59 @@ contract L1BlockHashListTest is Test { L1BlockHashList l1BlockHashList; bytes32 randomBlockHash = bytes32(keccak256("CITREA_TEST")); bytes32 randomMerkleRoot = bytes32(keccak256("CITREA")); + uint256 constant INITIAL_BLOCK_NUMBER = 505050; function setUp() public { l1BlockHashList = new L1BlockHashList(); } function testSetBlockInfo() public { + l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); - assertEq(l1BlockHashList.getBlockHash(0), randomBlockHash); - assertEq(l1BlockHashList.getMerkleRootFromBlockHash(randomBlockHash), randomMerkleRoot); - assertEq(l1BlockHashList.getMerkleRootFromBlockNumber(0), randomMerkleRoot); + assertEq(l1BlockHashList.getBlockHash(INITIAL_BLOCK_NUMBER), randomBlockHash); + assertEq(l1BlockHashList.getMerkleRootByHash(randomBlockHash), randomMerkleRoot); + assertEq(l1BlockHashList.getMerkleRootByNumber(INITIAL_BLOCK_NUMBER), randomMerkleRoot); + } + + function testCannotReinitalize() public { + l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); + vm.expectRevert("Already initialized"); + l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER - 10); } function testNonOwnerCannotSetBlockInfo() public { + l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); vm.startPrank(address(0x1)); vm.expectRevert("Caller is not owner"); l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); } + function testNonOwnerCannotInitializeBlockNumber() public { + vm.startPrank(address(0x1)); + vm.expectRevert("Caller is not owner"); + l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); + } + + function testCannotSetInfoWithoutInitialize() public { + vm.expectRevert("Not initialized"); + l1BlockHashList.setBlockInfo(randomBlockHash, randomMerkleRoot); + } + function testBlockInfoAvailableAfterManyWrites() public { + l1BlockHashList.initializeBlockNumber(INITIAL_BLOCK_NUMBER); for (uint256 i = 0; i < 100; i++) { bytes32 blockHash = keccak256(abi.encodePacked(i)); bytes32 root = keccak256(abi.encodePacked(blockHash)); l1BlockHashList.setBlockInfo(blockHash, root); - assertEq(l1BlockHashList.getBlockHash(i), blockHash); - assertEq(l1BlockHashList.getMerkleRootFromBlockHash(blockHash), root); - assertEq(l1BlockHashList.getMerkleRootFromBlockNumber(i), root); + assertEq(l1BlockHashList.getBlockHash(i + INITIAL_BLOCK_NUMBER), blockHash); + assertEq(l1BlockHashList.getMerkleRootByHash(blockHash), root); + assertEq(l1BlockHashList.getMerkleRootByNumber(i + INITIAL_BLOCK_NUMBER), root); } bytes32 zeroth_hash = keccak256(abi.encodePacked(uint(0))); bytes32 zeroth_root = keccak256(abi.encodePacked(zeroth_hash)); - assertEq(l1BlockHashList.getBlockHash(0), zeroth_hash); - assertEq(l1BlockHashList.getMerkleRootFromBlockHash(zeroth_hash), zeroth_root); - assertEq(l1BlockHashList.getMerkleRootFromBlockNumber(0), zeroth_root); + assertEq(l1BlockHashList.getBlockHash(INITIAL_BLOCK_NUMBER), zeroth_hash); + assertEq(l1BlockHashList.getMerkleRootByHash(zeroth_hash), zeroth_root); + assertEq(l1BlockHashList.getMerkleRootByNumber(INITIAL_BLOCK_NUMBER), zeroth_root); } } \ No newline at end of file From c0510b6b22219a693a987e834b7ec1d5c8514d63 Mon Sep 17 00:00:00 2001 From: okkothejawa <103260942+okkothejawa@users.noreply.github.com> Date: Wed, 13 Mar 2024 16:10:04 +0300 Subject: [PATCH 07/10] Update foundry workflow --- .github/workflows/foundry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index 9a7cc6f7e..d72972f18 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -11,7 +11,7 @@ jobs: fail-fast: true name: Foundry project - runs-on: ubuntu-latest + runs-on: ubicloud-standard-2 steps: - uses: actions/checkout@v4 with: From beb8a7ab975de68b1375762ac5f06309d190f73a Mon Sep 17 00:00:00 2001 From: eyusufatik Date: Wed, 13 Mar 2024 16:13:33 +0300 Subject: [PATCH 08/10] update ci --- .github/workflows/foundry.yml | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index d72972f18..d9655b2f0 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -1,10 +1,28 @@ name: foundry -on: workflow_dispatch +on: + merge_group: + types: ["checks_requested"] + push: + branches: ["nightly", "stable"] + pull_request: + branches: ["nightly", "stable"] + types: [opened, synchronize, reopened, ready_for_review] env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -D warnings FOUNDRY_PROFILE: ci + +# Automatically cancels a job if a new commit if pushed to the same PR, branch, or tag. +# Source: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + # Except in `nightly` and `stable` branches! Any cancelled job will cause the + # CI run to fail, and we want to keep a clean history for major branches. + cancel-in-progress: ${{ (github.ref != 'refs/heads/nightly') && (github.ref != 'refs/heads/stable') }} + jobs: check: strategy: From 0a3a3bef59746a5fa3c796c0cb64921a20981f8d Mon Sep 17 00:00:00 2001 From: eyusufatik Date: Wed, 13 Mar 2024 16:15:50 +0300 Subject: [PATCH 09/10] try running in parallel --- .github/workflows/foundry.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml index d9655b2f0..2d877babc 100644 --- a/.github/workflows/foundry.yml +++ b/.github/workflows/foundry.yml @@ -18,7 +18,7 @@ env: # Automatically cancels a job if a new commit if pushed to the same PR, branch, or tag. # Source: concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-foundry # Except in `nightly` and `stable` branches! Any cancelled job will cause the # CI run to fail, and we want to keep a clean history for major branches. cancel-in-progress: ${{ (github.ref != 'refs/heads/nightly') && (github.ref != 'refs/heads/stable') }} From a571b8c31e9e9ccecd6399ff53418da713067220 Mon Sep 17 00:00:00 2001 From: eyusufatik Date: Wed, 13 Mar 2024 16:18:58 +0300 Subject: [PATCH 10/10] use single workflow --- .github/workflows/{rust.yml => checks.yml} | 33 ++++++++++++- .github/workflows/foundry.yml | 54 ---------------------- 2 files changed, 32 insertions(+), 55 deletions(-) rename .github/workflows/{rust.yml => checks.yml} (90%) delete mode 100644 .github/workflows/foundry.yml diff --git a/.github/workflows/rust.yml b/.github/workflows/checks.yml similarity index 90% rename from .github/workflows/rust.yml rename to .github/workflows/checks.yml index f2cc5b8dd..4c7f8df01 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/checks.yml @@ -1,4 +1,4 @@ -name: Rust +name: Checks # On Rust, GitHub Actions, and caching # =========== @@ -50,6 +50,8 @@ on: env: CARGO_TERM_COLOR: always RUSTFLAGS: -D warnings + FOUNDRY_PROFILE: ci + # Automatically cancels a job if a new commit if pushed to the same PR, branch, or tag. # Source: @@ -170,3 +172,32 @@ jobs: run: rustup show - name: Run nextest run: SKIP_GUEST_BUILD=1 make test + + system-contracts: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubicloud-standard-2 + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Forge build + run: | + cd module-system/module-implementations/sov-evm/src/evm/system_contracts + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + run: | + cd module-system/module-implementations/sov-evm/src/evm/system_contracts + forge test -vvv + id: test \ No newline at end of file diff --git a/.github/workflows/foundry.yml b/.github/workflows/foundry.yml deleted file mode 100644 index 2d877babc..000000000 --- a/.github/workflows/foundry.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: foundry - -on: - merge_group: - types: ["checks_requested"] - push: - branches: ["nightly", "stable"] - pull_request: - branches: ["nightly", "stable"] - types: [opened, synchronize, reopened, ready_for_review] - -env: - CARGO_TERM_COLOR: always - RUSTFLAGS: -D warnings - FOUNDRY_PROFILE: ci - - -# Automatically cancels a job if a new commit if pushed to the same PR, branch, or tag. -# Source: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}-foundry - # Except in `nightly` and `stable` branches! Any cancelled job will cause the - # CI run to fail, and we want to keep a clean history for major branches. - cancel-in-progress: ${{ (github.ref != 'refs/heads/nightly') && (github.ref != 'refs/heads/stable') }} - -jobs: - check: - strategy: - fail-fast: true - - name: Foundry project - runs-on: ubicloud-standard-2 - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - - name: Run Forge build - run: | - cd module-system/module-implementations/sov-evm/src/evm/system_contracts - forge --version - forge build --sizes - id: build - - - name: Run Forge tests - run: | - cd module-system/module-implementations/sov-evm/src/evm/system_contracts - forge test -vvv - id: test