Skip to content

Commit

Permalink
Merge pull request #16 from merklefruit/nico/feat/sol-example
Browse files Browse the repository at this point in the history
feat: solidity verifier usage example
  • Loading branch information
rkdud007 authored Sep 17, 2024
2 parents 1412def + efb3ea8 commit 39c8d41
Show file tree
Hide file tree
Showing 13 changed files with 1,630 additions and 4 deletions.
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

0 comments on commit 39c8d41

Please sign in to comment.