diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index dae20d2..ac00bea 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -52,9 +52,8 @@ jobs: RUST_BACKTRACE: full RUST_LOG: info - test-e2e: - name: Test + name: Test e2es runs-on: ["runs-on", "runner=32cpu-linux-x64", "run-id=${{ github.run_id }}"] env: CARGO_NET_GIT_FETCH_WITH_CLI: "true" @@ -70,6 +69,32 @@ jobs: run: | SP1_DEV=1 RUST_LOG=info cargo test -p sp1-cc-host-executor --release -- --nocapture + test-uniswap-forge: + name: Test uniswap forge + 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: | + cd examples/uniswap/contracts + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + run: | + cd contracts + forge test -vvv + id: test + diff --git a/Cargo.lock b/Cargo.lock index 7fc7fd3..9091a50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7887,6 +7887,7 @@ dependencies = [ "alloy-sol-macro 0.8.5", "alloy-sol-types 0.8.5", "bincode", + "clap", "eyre", "revm", "rsp-rpc-db", diff --git a/Cargo.toml b/Cargo.toml index 13e9b11..450e498 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,6 @@ rsp-mpt = { git = "https://github.com/succinctlabs/rsp" , rev = "3647076"} # reth reth-primitives = { git = "https://github.com/sp1-patches/reth", tag = "rsp-20240830", default-features = false, features = [ "alloy-compat", - "optimism", "std", ] } reth-codecs = { git = "https://github.com/sp1-patches/reth", tag = "rsp-20240830", default-features = false } @@ -75,10 +74,8 @@ reth-ethereum-consensus = { git = "https://github.com/sp1-patches/reth", tag = " # revm revm = { version = "14.0.0", features = [ - "optimism", "std", "serde", - "kzg-rs", ], default-features = false } revm-primitives = { version = "9.0.0", features = [ "std", @@ -89,7 +86,6 @@ revm-primitives = { version = "9.0.0", features = [ alloy-primitives = "0.8" alloy-provider = { version = "0.3", default-features = false, features = [ "reqwest", - "reqwest-rustls-tls", ] } alloy-rpc-types = { version = "0.3", default-features = false, features = [ "eth", diff --git a/README.md b/README.md index 069c373..3287312 100644 --- a/README.md +++ b/README.md @@ -150,10 +150,12 @@ Then, from the root directory of the repository, run where `[example]` is one of the following * `uniswap` - * Fetches the price of the UNI / WETH pair on Uniswap V3. - * Outputs a file called [plonk-fixture.json](examples/uniswap/contracts/src/fixtures/plonk-fixture.json), which contains everything you need to verify the proof on chain. - * To see an example of on-chain verification, take a look at the [contracts](./examples/uniswap/contracts/) directory. - * On chain verification requires generating a plonk or groth16 proof, which requires significant computational resources. We recommend using the [SP1 Prover network](https://docs.succinct.xyz/generating-proofs/prover-network.html). + * Fetches the price of the UNI / WETH pair on Uniswap V3. By default, this does not generate a proof. + * Running `RUST_LOG=info cargo run --bin [example] --release -- --prove` will generate a plonk proof. This requires + significant computational resources, so we recommend using the [SP1 Prover network] + (https://docs.succinct.xyz/generating-proofs/prover-network.html). + * Outputs a file called [plonk-fixture.json](examples/uniswap/contracts/src/fixtures/plonk-fixture.json), which contains everything you need to verify the proof on chain. + * To see an example of on-chain verification, take a look at the [contracts](./examples/uniswap/contracts/) directory. * `multiplexer` * Calls a contract that fetches the prices of many different collateral assets. * The source code of this contract is found [here](./examples/multiplexer/ZkOracleHelper.sol). diff --git a/crates/client-executor/src/lib.rs b/crates/client-executor/src/lib.rs index 6ac410b..036d4c0 100644 --- a/crates/client-executor/src/lib.rs +++ b/crates/client-executor/src/lib.rs @@ -2,6 +2,7 @@ pub mod io; use alloy_sol_types::{sol, SolCall}; use eyre::OptionExt; use io::EVMStateSketch; +use reth_chainspec::ChainSpec; use reth_evm::ConfigureEvmEnv; use reth_evm_ethereum::EthEvmConfig; use reth_primitives::Header; @@ -10,6 +11,7 @@ use revm_primitives::{Address, BlockEnv, Bytes, CfgEnvWithHandlerCfg, SpecId, Tx use rsp_client_executor::io::WitnessInput; use rsp_witness_db::WitnessDb; +pub use rsp_primitives::chain_spec::mainnet; /// Input to a contract call. #[derive(Debug, Clone)] pub struct ContractInput { @@ -20,6 +22,9 @@ pub struct ContractInput { /// The calldata to pass to the contract. pub calldata: C, } +pub fn sepolia() -> ChainSpec { + (*reth_chainspec::SEPOLIA.clone()).clone() +} sol! { /// Public values of a contract call. @@ -57,13 +62,19 @@ pub struct ClientExecutor { pub witness_db: WitnessDb, /// The block header. pub header: Header, + /// The chain spec. + pub chain_spec: ChainSpec, } impl ClientExecutor { /// Instantiates a new [`ClientExecutor`] - pub fn new(state_sketch: EVMStateSketch) -> eyre::Result { + pub fn new(state_sketch: EVMStateSketch, chain_spec: ChainSpec) -> eyre::Result { // let header = state_sketch.header.clone(); - Ok(Self { witness_db: state_sketch.witness_db().unwrap(), header: state_sketch.header }) + Ok(Self { + witness_db: state_sketch.witness_db().unwrap(), + header: state_sketch.header, + chain_spec, + }) } /// Executes the smart contract call with the given [`ContractInput`] in SP1. @@ -74,20 +85,20 @@ impl ClientExecutor { call: ContractInput, ) -> eyre::Result { let cache_db = CacheDB::new(&self.witness_db); - let mut evm = new_evm(cache_db, &self.header, U256::ZERO, &call); + let mut evm = new_evm(cache_db, &self.header, U256::ZERO, &call, &self.chain_spec); let tx_output = evm.transact()?; let tx_output_bytes = tx_output.result.output().ok_or_eyre("Error decoding result")?; Ok(ContractPublicValues::new::(call, tx_output_bytes.clone(), self.header.hash_slow())) } } -/// TODO Add support for other chains besides Ethereum Mainnet. /// Instantiates a new EVM, which is ready to run `call`. pub fn new_evm<'a, D, C>( db: D, header: &Header, total_difficulty: U256, call: &ContractInput, + chain_spec: &ChainSpec, ) -> Evm<'a, (), D> where D: Database, @@ -99,7 +110,7 @@ where EthEvmConfig::default().fill_cfg_and_block_env( &mut cfg_env, &mut block_env, - &rsp_primitives::chain_spec::mainnet(), + chain_spec, header, total_difficulty, ); diff --git a/crates/host-executor/src/lib.rs b/crates/host-executor/src/lib.rs index bbbdaa9..afe625d 100644 --- a/crates/host-executor/src/lib.rs +++ b/crates/host-executor/src/lib.rs @@ -8,6 +8,7 @@ use alloy_rpc_types::BlockNumberOrTag; use alloy_sol_types::SolCall; use alloy_transport::Transport; use eyre::{eyre, OptionExt}; +use reth_chainspec::ChainSpec; use reth_primitives::{Block, Header}; use revm::db::CacheDB; use revm_primitives::{B256, U256}; @@ -29,11 +30,17 @@ pub struct HostExecutor + Clone pub rpc_db: RpcDb, /// The provider used to fetch data. pub provider: P, + /// The chain spec. + pub chain_spec: ChainSpec, } impl + Clone> HostExecutor { /// Create a new [`HostExecutor`] with a specific [`Provider`] and [`BlockNumberOrTag`]. - pub async fn new(provider: P, block_number: BlockNumberOrTag) -> eyre::Result { + pub async fn new( + provider: P, + block_number: BlockNumberOrTag, + chain_spec: ChainSpec, + ) -> eyre::Result { let block = provider .get_block_by_number(block_number, true) .await? @@ -41,13 +48,13 @@ impl + Clone> HostExecutor(&mut self, call: ContractInput) -> eyre::Result { let cache_db = CacheDB::new(&self.rpc_db); - let mut evm = new_evm(cache_db, &self.header, U256::ZERO, &call); + let mut evm = new_evm(cache_db, &self.header, U256::ZERO, &call, &self.chain_spec); let output = evm.transact()?; let output_bytes = output.result.output().ok_or_eyre("Error getting result")?; diff --git a/crates/host-executor/src/test.rs b/crates/host-executor/src/test.rs index 9c678f9..9603305 100644 --- a/crates/host-executor/src/test.rs +++ b/crates/host-executor/src/test.rs @@ -89,6 +89,8 @@ async fn test_e2e( // Load environment variables. dotenv::dotenv().ok(); + let mainnet = rsp_primitives::chain_spec::mainnet(); + // Which block transactions are executed on. let block_number = BlockNumberOrTag::Latest; @@ -97,14 +99,15 @@ async fn test_e2e( // Use `RPC_URL` to get all of the necessary state for the smart contract call. let rpc_url = std::env::var("ETH_RPC_URL").unwrap_or_else(|_| panic!("Missing RPC_URL")); let provider = ReqwestProvider::new_http(Url::parse(&rpc_url)?); - let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?; + let mut host_executor = + HostExecutor::new(provider.clone(), block_number, mainnet.clone()).await?; let _contract_output = host_executor.execute(contract_input.clone()).await?; // Now that we've executed all of the calls, get the `EVMStateSketch` from the host executor. let state_sketch = host_executor.finalize().await?; - let client_executor = ClientExecutor::new(state_sketch)?; + let client_executor = ClientExecutor::new(state_sketch, mainnet.clone())?; let public_values = client_executor.execute(contract_input)?; diff --git a/examples/multiplexer/client/src/main.rs b/examples/multiplexer/client/src/main.rs index c47ea15..a6bcb0b 100644 --- a/examples/multiplexer/client/src/main.rs +++ b/examples/multiplexer/client/src/main.rs @@ -3,11 +3,9 @@ sp1_zkvm::entrypoint!(main); use alloy_primitives::{address, Address}; use alloy_sol_macro::sol; -use alloy_sol_types::{SolCall, SolValue}; +use alloy_sol_types::SolValue; use bincode; -use sp1_cc_client_executor::{ - io::EVMStateSketch, ClientExecutor, ContractInput, ContractPublicValues, -}; +use sp1_cc_client_executor::{io::EVMStateSketch, mainnet, ClientExecutor, ContractInput}; sol! { /// Interface to the multiplexer contract. It gets the prices of many tokens, including @@ -47,7 +45,7 @@ pub fn main() { // Initialize the client executor with the state sketch. // This step also validates all of the storage against the provided state root. - let executor = ClientExecutor::new(state_sketch).unwrap(); + let executor = ClientExecutor::new(state_sketch, mainnet()).unwrap(); // Execute the getRates call using the client executor. let calldata = IOracleHelper::getRatesCall { collaterals: COLLATERALS.to_vec() }; diff --git a/examples/multiplexer/host/src/main.rs b/examples/multiplexer/host/src/main.rs index 1290c4f..d05ce94 100644 --- a/examples/multiplexer/host/src/main.rs +++ b/examples/multiplexer/host/src/main.rs @@ -3,7 +3,7 @@ use alloy_provider::ReqwestProvider; use alloy_rpc_types::BlockNumberOrTag; use alloy_sol_macro::sol; use alloy_sol_types::{SolCall, SolValue}; -use sp1_cc_client_executor::{ContractInput, ContractPublicValues}; +use sp1_cc_client_executor::{mainnet, ContractInput, ContractPublicValues}; use sp1_cc_host_executor::HostExecutor; use sp1_sdk::{utils, ProverClient, SP1Stdin}; use url::Url; @@ -56,7 +56,7 @@ async fn main() -> eyre::Result<()> { let rpc_url = std::env::var("ETH_RPC_URL").unwrap_or_else(|_| panic!("Missing ETH_RPC_URL in env")); let provider = ReqwestProvider::new_http(Url::parse(&rpc_url)?); - let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?; + let mut host_executor = HostExecutor::new(provider.clone(), block_number, mainnet()).await?; // Keep track of the block hash. Later, the client's execution will be validated against this. let block_hash = host_executor.header.hash_slow(); diff --git a/examples/uniswap/client/src/main.rs b/examples/uniswap/client/src/main.rs index ccddbbb..56537e2 100644 --- a/examples/uniswap/client/src/main.rs +++ b/examples/uniswap/client/src/main.rs @@ -3,9 +3,9 @@ sp1_zkvm::entrypoint!(main); use alloy_primitives::{address, Address}; use alloy_sol_macro::sol; -use alloy_sol_types::{SolCall, SolValue}; +use alloy_sol_types::SolValue; use bincode; -use sp1_cc_client_executor::{io::EVMStateSketch, ClientExecutor, ContractInput}; +use sp1_cc_client_executor::{io::EVMStateSketch, mainnet, ClientExecutor, ContractInput}; sol! { /// Simplified interface of the IUniswapV3PoolState interface. interface IUniswapV3PoolState { @@ -28,7 +28,7 @@ pub fn main() { // Initialize the client executor with the state sketch. // This step also validates all of the storage against the provided state root. - let executor = ClientExecutor::new(state_sketch).unwrap(); + let executor = ClientExecutor::new(state_sketch, mainnet()).unwrap(); // Execute the slot0 call using the client executor. let slot0_call = IUniswapV3PoolState::slot0Call {}; diff --git a/examples/uniswap/host/Cargo.toml b/examples/uniswap/host/Cargo.toml index e2d2cb8..b0d5a18 100644 --- a/examples/uniswap/host/Cargo.toml +++ b/examples/uniswap/host/Cargo.toml @@ -29,6 +29,7 @@ eyre.workspace = true bincode.workspace = true serde.workspace = true serde_json.workspace = true +clap = { version = "4.0", features = ["derive", "env"] } # sp1 sp1-sdk = { git = "https://github.com/succinctlabs/sp1", branch = "yuwen/fs-test"} diff --git a/examples/uniswap/host/src/main.rs b/examples/uniswap/host/src/main.rs index a75dedc..61c8024 100644 --- a/examples/uniswap/host/src/main.rs +++ b/examples/uniswap/host/src/main.rs @@ -6,8 +6,9 @@ use alloy_provider::ReqwestProvider; use alloy_rpc_types::BlockNumberOrTag; use alloy_sol_macro::sol; use alloy_sol_types::{SolCall, SolValue}; +use clap::Parser; use serde::{Deserialize, Serialize}; -use sp1_cc_client_executor::{ContractInput, ContractPublicValues}; +use sp1_cc_client_executor::{mainnet, ContractInput, ContractPublicValues}; use sp1_cc_host_executor::HostExecutor; use sp1_sdk::{utils, HashableKey, ProverClient, SP1ProofWithPublicValues, SP1Stdin}; use url::Url; @@ -38,6 +39,14 @@ struct SP1CCProofFixture { proof: String, } +/// The arguments for the command. +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +struct Args { + #[clap(long, default_value = "false")] + prove: bool, +} + /// Generate a `SP1CCProofFixture`, and save it as a json file. /// /// This is useful for verifying the proof of contract call execution on chain. @@ -62,6 +71,9 @@ async fn main() -> eyre::Result<()> { // Setup logging. utils::setup_logger(); + // Parse the command line arguments. + let args = Args::parse(); + // Which block transactions are executed on. let block_number = BlockNumberOrTag::Number(20600000); @@ -71,7 +83,7 @@ async fn main() -> eyre::Result<()> { let rpc_url = std::env::var("ETH_RPC_URL").unwrap_or_else(|_| panic!("Missing ETH_RPC_URL in env")); let provider = ReqwestProvider::new_http(Url::parse(&rpc_url)?); - let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?; + let mut host_executor = HostExecutor::new(provider.clone(), block_number, mainnet()).await?; // Keep track of the block hash. Later, validate the client's execution against this. let block_hash = host_executor.header.hash_slow(); @@ -102,6 +114,11 @@ async fn main() -> eyre::Result<()> { let (_, report) = client.execute(ELF, stdin.clone()).run().unwrap(); println!("executed program with {} cycles", report.total_instruction_count()); + // If the prove flag is not set, we return here. + if !args.prove { + return Ok(()); + } + // Generate the proof for the given program and input. let (pk, vk) = client.setup(ELF); let proof = client.prove(&pk, stdin).plonk().run().unwrap(); diff --git a/examples/verify-quorum/client/src/main.rs b/examples/verify-quorum/client/src/main.rs index 85ee5e7..c213d78 100644 --- a/examples/verify-quorum/client/src/main.rs +++ b/examples/verify-quorum/client/src/main.rs @@ -5,7 +5,7 @@ use alloy_primitives::{address, Address, Bytes, B256}; use alloy_sol_macro::sol; use alloy_sol_types::SolValue; use bincode; -use sp1_cc_client_executor::{io::EVMStateSketch, ClientExecutor, ContractInput}; +use sp1_cc_client_executor::{io::EVMStateSketch, sepolia, ClientExecutor, ContractInput}; sol! { /// Part of the SimpleStaking interface @@ -34,7 +34,7 @@ pub fn main() { // Initialize the client executor with the state sketch. // This step also validates all of the storage against the provided state root. - let executor = ClientExecutor::new(state_sketch).unwrap(); + let executor = ClientExecutor::new(state_sketch, sepolia()).unwrap(); // Set up the call to `verifySigned`. let verify_signed_call = ContractInput { diff --git a/examples/verify-quorum/host/src/main.rs b/examples/verify-quorum/host/src/main.rs index d76a946..e0144e5 100644 --- a/examples/verify-quorum/host/src/main.rs +++ b/examples/verify-quorum/host/src/main.rs @@ -7,7 +7,7 @@ use rand_chacha::ChaCha20Rng; use rand_core::SeedableRng; use reth_primitives::public_key_to_address; use secp256k1::{generate_keypair, Message, SECP256K1}; -use sp1_cc_client_executor::{ContractInput, ContractPublicValues}; +use sp1_cc_client_executor::{sepolia, ContractInput, ContractPublicValues}; use sp1_cc_host_executor::HostExecutor; use sp1_sdk::{utils, ProverClient, SP1Stdin}; use url::Url; @@ -60,7 +60,7 @@ async fn main() -> eyre::Result<()> { let rpc_url = std::env::var("ETH_SEPOLIA_RPC_URL") .unwrap_or_else(|_| panic!("Missing ETH_SEPOLIA_RPC_URL in env")); let provider = ReqwestProvider::new_http(Url::parse(&rpc_url)?); - let mut host_executor = HostExecutor::new(provider.clone(), block_number).await?; + let mut host_executor = HostExecutor::new(provider.clone(), block_number, sepolia()).await?; // Keep track of the block hash. Later, validate the client's execution against this. let block_hash = host_executor.header.hash_slow();