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

feat: solidity verifier usage example #16

Merged
merged 6 commits into from
Sep 17, 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "examples/eth/lib/forge-std"]
path = examples/eth/lib/forge-std
url = https://github.com/foundry-rs/forge-std
6 changes: 2 additions & 4 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ async fn generate_tx_proof(tx_hash: &str, rpc_url: &str) -> Result<(), EthTrieEr
let root = txs_mpt_handler.get_root()?;

let mpt_proof = MptProof { root, proof, index };
println!("Generated TX Proof: ");
println!("{}", serde_json::to_string(&mpt_proof).unwrap());
print!("{}", serde_json::to_string(&mpt_proof).unwrap());
Ok(())
}

Expand All @@ -98,7 +97,6 @@ async fn generate_receipt_proof(tx_hash: &str, rpc_url: &str) -> Result<(), EthT
let root = tx_receipts_mpt_handler.get_root()?;

let mpt_proof = MptProof { root, proof, index };
println!("Generated Receipt Proof: ");
println!("{}", serde_json::to_string(&mpt_proof).unwrap());
print!("{}", serde_json::to_string(&mpt_proof).unwrap());
Ok(())
}
5 changes: 5 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Trie Proof examples

Available examples:

- [Ethereum transaction inclusion proof (Solidity)](./eth/)
14 changes: 14 additions & 0 deletions examples/eth/.gitignore
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
30 changes: 30 additions & 0 deletions examples/eth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Ethereum Trie Proof: Solidity example

This example demonstrates how to verify an Ethereum transaction inclusion proof in Solidity.
The proof will be generated with the [`cli`](../../cli/) binary directly from the Foundry test.

## Overview of the example

The example consists of a single end2end test in [Prover.t.sol](./test/Prover.t.sol) that generates a Merkle
inclusion proof and verifies it in Solidity against a given transaction hash.

## Usage

Run the Foundry tests:

```shell
# make sure you are in the right directory
cd examples/eth

forge test --ffi
```

## Troubleshooting

If the test fails with an RPC error, you can customize the RPC endpoint by setting the `RPC_URL`
environment variable to the desired endpoing in your shell. For example:

```shell
export RPC_URL=https://mainnet.infura.io/v3/your_project_id
forge test --ffi
```
6 changes: 6 additions & 0 deletions examples/eth/foundry.toml
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
1 change: 1 addition & 0 deletions examples/eth/lib/forge-std
Submodule forge-std added at 1714be
12 changes: 12 additions & 0 deletions examples/eth/src/Prover.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

import {MerkleTrie} from "./lib/MerkleTrie.sol";

contract Prover {
constructor() {}

function get(bytes memory _key, bytes memory _proof, bytes32 _root) public pure returns (bool, bytes memory) {
return MerkleTrie.get(_key, _proof, _root);
}
}
179 changes: 179 additions & 0 deletions examples/eth/src/lib/BytesUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

/**
* @title BytesUtils
*/
library BytesUtils {
/**
*
* Internal Functions *
*
*/
function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) {
unchecked {
require(_length + 31 >= _length, "slice_overflow");
require(_start + _length >= _start, "slice_overflow");
require(_bytes.length >= _start + _length, "slice_outOfBounds");

bytes memory tempBytes;

assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)

// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)

// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)

for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} { mstore(mc, mload(cc)) }

mstore(tempBytes, _length)

//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)

//zero out the 32 bytes slice we are about to return
//we need to do it because Solidity does not garbage collect
mstore(tempBytes, 0)

mstore(0x40, add(tempBytes, 0x20))
}
}

return tempBytes;
}
}

function slice(bytes memory _bytes, uint256 _start) internal pure returns (bytes memory) {
unchecked {
if (_bytes.length - _start == 0) {
return bytes("");
}

return slice(_bytes, _start, _bytes.length - _start);
}
}

function toBytes32PadLeft(bytes memory _bytes) internal pure returns (bytes32) {
unchecked {
bytes32 ret;
uint256 len = _bytes.length <= 32 ? _bytes.length : 32;
assembly {
ret := shr(mul(sub(32, len), 8), mload(add(_bytes, 32)))
}
return ret;
}
}

function toBytes32(bytes memory _bytes) internal pure returns (bytes32) {
unchecked {
if (_bytes.length < 32) {
bytes32 ret;
assembly {
ret := mload(add(_bytes, 32))
}
return ret;
}

return abi.decode(_bytes, (bytes32)); // will truncate if input length > 32 bytes
}
}

function toUint256(bytes memory _bytes) internal pure returns (uint256) {
return uint256(toBytes32(_bytes));
}

function toUint24(bytes memory _bytes, uint256 _start) internal pure returns (uint24) {
require(_start + 3 >= _start, "toUint24_overflow");
require(_bytes.length >= _start + 3, "toUint24_outOfBounds");
uint24 tempUint;

assembly {
tempUint := mload(add(add(_bytes, 0x3), _start))
}

return tempUint;
}

function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) {
require(_start + 1 >= _start, "toUint8_overflow");
require(_bytes.length >= _start + 1, "toUint8_outOfBounds");
uint8 tempUint;

assembly {
tempUint := mload(add(add(_bytes, 0x1), _start))
}

return tempUint;
}

function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) {
require(_start + 20 >= _start, "toAddress_overflow");
require(_bytes.length >= _start + 20, "toAddress_outOfBounds");
address tempAddress;

assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}

return tempAddress;
}

function toNibbles(bytes memory _bytes) internal pure returns (bytes memory) {
unchecked {
bytes memory nibbles = new bytes(_bytes.length * 2);

for (uint256 i = 0; i < _bytes.length; i++) {
nibbles[i * 2] = _bytes[i] >> 4;
nibbles[i * 2 + 1] = bytes1(uint8(_bytes[i]) % 16);
}

return nibbles;
}
}

function fromNibbles(bytes memory _bytes) internal pure returns (bytes memory) {
unchecked {
bytes memory ret = new bytes(_bytes.length / 2);

for (uint256 i = 0; i < ret.length; i++) {
ret[i] = (_bytes[i * 2] << 4) | (_bytes[i * 2 + 1]);
}

return ret;
}
}

function equal(bytes memory _bytes, bytes memory _other) internal pure returns (bool) {
return keccak256(_bytes) == keccak256(_other);
}
}
Loading