Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add block hash system contract #229

Merged
merged 10 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion .github/workflows/rust.yml → .github/workflows/checks.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Rust
name: Checks

# On Rust, GitHub Actions, and caching
# ===========
Expand Down Expand Up @@ -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: <https://stackoverflow.com/a/72408109/5148606>
Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
Original file line number Diff line number Diff line change
@@ -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 <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
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;
uint256 public blockNumber;

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 {
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];
}

/// @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];
}

/// @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]];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

interface IL1BlockHashList {
function initializeBlockNumber(uint256) external;
function setBlockInfo(bytes32, bytes32) external;
function getBlockHash(uint256) external view returns (bytes32);
function getMerkleRootByHash(bytes32) external view returns (bytes32);
function getMerkleRootByNumber(uint256) external view returns (bytes32);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// 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"));
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(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 + 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(INITIAL_BLOCK_NUMBER), zeroth_hash);
assertEq(l1BlockHashList.getMerkleRootByHash(zeroth_hash), zeroth_root);
assertEq(l1BlockHashList.getMerkleRootByNumber(INITIAL_BLOCK_NUMBER), zeroth_root);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Loading