diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 945bc2a5b1..8b7d1d360b 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -79,13 +79,9 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: | export FOUNDRY_PROFILE=quick - export AZTEC_SRS_PATH="$PWD/data/aztec20/kzg10-aztec20-srs-65544.bin" - ./scripts/download_srs_aztec.sh nix develop --accept-flake-config -c forge test -vvv - name: Run tests (full version for main) if: ${{ github.event_name != 'pull_request' }} run: | - export AZTEC_SRS_PATH="$PWD/data/aztec20/kzg10-aztec20-srs-65544.bin" - ./scripts/download_srs_aztec.sh nix develop --accept-flake-config -c forge test -vvv diff --git a/.github/workflows/test-demo-native.yml b/.github/workflows/test-demo-native.yml index 2a6cc1e9eb..90666c366a 100644 --- a/.github/workflows/test-demo-native.yml +++ b/.github/workflows/test-demo-native.yml @@ -49,7 +49,5 @@ jobs: - name: Test Demo run: | export PATH="$PWD/target/release:$PATH" - export AZTEC_SRS_PATH="$PWD/data/aztec20/kzg10-aztec20-srs-65544.bin" - ./scripts/download_srs_aztec.sh scripts/demo-native --tui=false & timeout -v 600 scripts/smoke-test-demo diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f390aabf1d..3cb1482c97 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,8 +52,6 @@ jobs: run: | export RUSTFLAGS="$RUSTFLAGS --cfg hotshot_example" export PATH="$PWD/target/release:$PATH" - export AZTEC_SRS_PATH="$PWD/data/aztec20/kzg10-aztec20-srs-65544.bin" - ./scripts/download_srs_aztec.sh cargo build --locked --bin diff-test --release cargo test --locked --release --workspace --all-features --no-run cargo test --locked --release --workspace --all-features --verbose -- --test-threads 1 --nocapture diff --git a/Cargo.lock b/Cargo.lock index 95a6ab2ef7..0f7311b04d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,9 +493,9 @@ dependencies = [ [[package]] name = "ark-srs" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9d2dbff870743404933d3ef8a3be3adc3d1926f205e5aec2e43d3ee6885431" +checksum = "984122d5ed5520ed6175f2bf3f9250eb05be14f688b83329262d144e5d19b700" dependencies = [ "anyhow", "ark-bn254", @@ -504,8 +504,13 @@ dependencies = [ "ark-poly-commit", "ark-serialize", "ark-std", + "directories", "hex-literal", + "rand 0.8.5", "sha2 0.10.8", + "tracing", + "tracing-subscriber 0.3.18", + "ureq", ] [[package]] @@ -2660,6 +2665,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs" version = "5.0.1" @@ -5821,7 +5835,7 @@ dependencies = [ "rcgen 0.11.3", "ring 0.16.20", "rustls 0.21.10", - "rustls-webpki", + "rustls-webpki 0.101.7", "thiserror", "x509-parser", "yasna", @@ -8144,10 +8158,24 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring 0.17.7", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct 0.7.1", ] +[[package]] +name = "rustls" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +dependencies = [ + "log", + "ring 0.17.7", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -8169,6 +8197,12 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -8179,6 +8213,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring 0.17.7", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.14" @@ -8409,7 +8454,6 @@ dependencies = [ "ethers-contract-derive", "futures", "hotshot", - "hotshot-contract-adapter", "hotshot-events-service", "hotshot-orchestrator", "hotshot-query-service", @@ -8459,10 +8503,15 @@ dependencies = [ "anyhow", "ark-serialize", "async-std", + "clap", "commit", "contract-bindings", + "derive_more", "ethers", + "futures", + "hotshot-contract-adapter", "portpicker", + "serde_json", "surf", "tempfile", "tracing", @@ -10413,6 +10462,23 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +dependencies = [ + "base64 0.21.7", + "flate2", + "log", + "once_cell", + "rustls 0.22.3", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "url", + "webpki-roots 0.26.1", +] + [[package]] name = "url" version = "2.5.0" @@ -10708,6 +10774,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index a48d1bd495..80fcebe9ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ ark-ed-on-bn254 = "0.4" ark-ff = "0.4" ark-poly = "0.4" ark-serialize = "0.4" -ark-srs = "0.2.0" +ark-srs = "0.3.1" async-compatibility-layer = { git = "https://github.com/EspressoSystems/async-compatibility-layer", tag = "1.4.1", features = [ "logging-utils", ] } diff --git a/contracts/test/LightClientTest.s.sol b/contracts/test/LightClientTest.s.sol deleted file mode 100644 index 90c4478c11..0000000000 --- a/contracts/test/LightClientTest.s.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import "forge-std/Script.sol"; -import { BN254 } from "bn254/BN254.sol"; -import { LightClient as LC } from "../src/LightClient.sol"; -import { LightClientMock as LCTest } from "../test/mocks/LightClientMock.sol"; - -/// @notice deployment script for LightClientTest which is designed for testing purposes with -/// verification key corresponding to smaller circuit, thus faster proof generation during tests. -/// -/// @dev for production deployment script, please use `gen-light-client-deploy/main.rs` to -/// generate `LightClient.s.sol` with proper genesis block values. -contract DeployLightClientTestScript is Script { - function run(uint32 numBlocksPerEpoch, uint32 numInitValidators) external { - string memory seedPhrase = vm.envString("MNEMONIC"); - uint256 privateKey = vm.deriveKey(seedPhrase, 0); - vm.startBroadcast(privateKey); - - string[] memory cmds = new string[](4); - cmds[0] = "diff-test"; - cmds[1] = "mock-genesis"; - cmds[2] = vm.toString(numBlocksPerEpoch); - cmds[3] = vm.toString(uint256(numInitValidators)); - - bytes memory result = vm.ffi(cmds); - (LC.LightClientState memory state,,) = - abi.decode(result, (LC.LightClientState, bytes32, bytes32)); - - new LCTest(state, numBlocksPerEpoch); - vm.stopBroadcast(); - } -} diff --git a/flake.nix b/flake.nix index b7abdd7d98..59c458a309 100644 --- a/flake.nix +++ b/flake.nix @@ -228,8 +228,6 @@ # with rustup installations. export CARGO_HOME=$HOME/.cargo-nix export PATH="$PWD/$CARGO_TARGET_DIR/release:$PATH" - export AZTEC_SRS_PATH="$PWD/data/aztec20/kzg10-aztec20-srs-1048584.bin" - ./scripts/download_srs_aztec.sh '' + self.checks.${system}.pre-commit-check.shellHook; RUST_SRC_PATH = "${stableToolchain}/lib/rustlib/src/rust/library"; FOUNDRY_SOLC = "${solc}/bin/solc"; diff --git a/hotshot-state-prover/src/bin/state-prover.rs b/hotshot-state-prover/src/bin/state-prover.rs index 7dfc08a4ce..1d9f94fb2b 100644 --- a/hotshot-state-prover/src/bin/state-prover.rs +++ b/hotshot-state-prover/src/bin/state-prover.rs @@ -73,8 +73,6 @@ struct Args { pub stake_table_capacity: usize, } -// - #[derive(Clone, Debug, Snafu)] pub struct ParseDurationError { reason: String, diff --git a/hotshot-state-prover/src/circuit.rs b/hotshot-state-prover/src/circuit.rs index 032b3c519b..cdd262d8bd 100644 --- a/hotshot-state-prover/src/circuit.rs +++ b/hotshot-state-prover/src/circuit.rs @@ -448,7 +448,7 @@ mod tests { .map(|b| if b { F::from(1u64) } else { F::from(0u64) }) .collect::>(); // good path - let (circuit, public_inputs) = build::<_, _, _, _, _>( + let (circuit, public_inputs) = build( &entries, &bit_vec, &bit_masked_sigs, @@ -461,7 +461,7 @@ mod tests { .check_circuit_satisfiability(public_inputs.as_ref()) .is_ok()); - let (circuit, public_inputs) = build::<_, _, _, _, _>( + let (circuit, public_inputs) = build( &entries, &bit_vec, &bit_masked_sigs, @@ -476,7 +476,7 @@ mod tests { // bad path: feeding non-bit vector let bit_vec = [F::from(2u64); 10]; - let (circuit, public_inputs) = build::<_, _, _, _, _>( + let (circuit, public_inputs) = build( &entries, &bit_vec, &bit_masked_sigs, @@ -510,7 +510,7 @@ mod tests { .into_iter() .map(|b| if b { F::from(1u64) } else { F::from(0u64) }) .collect::>(); - let (bad_circuit, public_inputs) = build::<_, _, _, _, _>( + let (bad_circuit, public_inputs) = build( &entries, &bad_bit_vec, &bad_bit_masked_sigs, @@ -534,7 +534,7 @@ mod tests { }) .collect::, PrimitivesError>>() .unwrap(); - let (bad_circuit, public_inputs) = build::<_, _, _, _, _>( + let (bad_circuit, public_inputs) = build( &entries, &bit_vec, &sig_for_bad_state, @@ -559,7 +559,7 @@ mod tests { }) .collect::, PrimitivesError>>() .unwrap(); - let (bad_circuit, public_inputs) = build::<_, _, _, _, _>( + let (bad_circuit, public_inputs) = build( &entries, &bit_vec, &wrong_sigs, @@ -573,7 +573,7 @@ mod tests { .is_err()); // bad path: overflowing stake table size - assert!(build::<_, _, _, _, _>( + assert!(build( &entries, &bit_vec, &bit_masked_sigs, diff --git a/hotshot-state-prover/src/service.rs b/hotshot-state-prover/src/service.rs index f76fbaabb4..0dbbe68295 100644 --- a/hotshot-state-prover/src/service.rs +++ b/hotshot-state-prover/src/service.rs @@ -477,19 +477,17 @@ mod test { use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; use ethers::{ abi::AbiEncode, - providers::Middleware, utils::{Anvil, AnvilInstance}, }; use hotshot_stake_table::vec_based::StakeTable; use hotshot_types::light_client::StateSignKey; use jf_primitives::signatures::{SchnorrSignatureScheme, SignatureScheme}; use jf_utils::test_rng; - use std::process::Command; + use sequencer_utils::deployer; const STAKE_TABLE_CAPACITY_FOR_TEST: usize = 10; const BLOCKS_PER_EPOCH: u32 = 10; const NUM_INIT_VALIDATORS: u32 = (STAKE_TABLE_CAPACITY_FOR_TEST / 2) as u32; - const TEST_MNEMONIC: &str = "test test test test test test test test test test test junk"; /// Init a meaningful ledger state that prover can generate future valid proof. /// this is used for testing purposes, contract deployed to test proof verification should also be initialized with this genesis @@ -585,38 +583,24 @@ mod test { (pi, proof) } - /// deploy LightClient.sol on local blockchain (via `anvil`) for testing + /// deploy LightClientMock.sol on local blockchain (via `anvil`) for testing /// return (signer-loaded wallet, contract instance) async fn deploy_contract_for_test( anvil: &AnvilInstance, + genesis: ParsedLightClientState, ) -> Result<(Arc, LightClient)> { let provider = Provider::::try_from(anvil.endpoint())?; - let signer = Wallet::from(anvil.keys()[0].clone()); + let signer = Wallet::from(anvil.keys()[0].clone()) + .with_chain_id(provider.get_chainid().await?.as_u64()); let l1_wallet = Arc::new(L1Wallet::new(provider.clone(), signer)); - let output = Command::new("just") - .arg("dev-deploy") - .arg(anvil.endpoint()) - .arg(TEST_MNEMONIC) - .arg(BLOCKS_PER_EPOCH.to_string()) - .arg(NUM_INIT_VALIDATORS.to_string()) - .output() - .expect("fail to deploy"); - - if !output.status.success() { - tracing::error!("{}", String::from_utf8(output.stderr).unwrap()); - return Err(anyhow!("failed to deploy contract")); - } - - let last_blk_num = provider.get_block_number().await?; - // the first tx deploys PlonkVerifier.sol library, the second deploys LightClient.sol - let address = provider - .get_block_receipts(last_blk_num) - .await? - .last() - .unwrap() - .contract_address - .expect("fail to get LightClient address from receipt"); + let mut contracts = deployer::Contracts::default(); + let address = deployer::deploy_mock_light_client_contract( + l1_wallet.clone(), + &mut contracts, + Some((genesis.into(), BLOCKS_PER_EPOCH)), + ) + .await?; let contract = LightClient::new(address, l1_wallet.clone()); Ok((l1_wallet, contract)) @@ -652,15 +636,13 @@ mod test { setup_backtrace(); let anvil = Anvil::new().spawn(); - let (_wallet, contract) = deploy_contract_for_test(&anvil).await?; + let dummy_genesis = ParsedLightClientState::dummy_genesis(); + let (_wallet, contract) = deploy_contract_for_test(&anvil, dummy_genesis.clone()).await?; // now test if we can read from the contract assert_eq!(contract.blocks_per_epoch().call().await?, BLOCKS_PER_EPOCH); let genesis: ParsedLightClientState = contract.get_genesis_state().await?.into(); - // NOTE: these values changes with `contracts/scripts/LightClient.s.sol` - assert_eq!(genesis.view_num, 0); - assert_eq!(genesis.block_height, 0); - assert_eq!(genesis.threshold, U256::from(40)); + assert_eq!(genesis, dummy_genesis); let mut config = StateProverConfig::default(); config.update_l1_info(&anvil, contract.address()); @@ -678,12 +660,10 @@ mod test { let (genesis, _qc_keys, state_keys, st) = init_ledger_for_test(); let anvil = Anvil::new().spawn(); - let (_wallet, contract) = deploy_contract_for_test(&anvil).await?; + let (_wallet, contract) = deploy_contract_for_test(&anvil, genesis.clone()).await?; let mut config = StateProverConfig::default(); config.update_l1_info(&anvil, contract.address()); - // sanity check on `config` - // sanity check to ensure the same genesis state for LightClientTest and for our tests let genesis_l1: ParsedLightClientState = contract.get_genesis_state().await?.into(); assert_eq!(genesis_l1, genesis, "mismatched genesis, aborting tests"); diff --git a/hotshot-state-prover/src/snark.rs b/hotshot-state-prover/src/snark.rs index 5db1d2d505..2e166003f8 100644 --- a/hotshot-state-prover/src/snark.rs +++ b/hotshot-state-prover/src/snark.rs @@ -82,7 +82,7 @@ where CircuitField::from(0u64) } }); - let (circuit, public_inputs) = crate::circuit::build::<_, _, _, _, _>( + let (circuit, public_inputs) = crate::circuit::build( stake_table_entries, signer_bit_vec, signatures, diff --git a/justfile b/justfile index e9307a17ce..d25716661f 100644 --- a/justfile +++ b/justfile @@ -83,12 +83,6 @@ sol-test: cargo build --bin diff-test --release forge test -# Deploy contracts to local blockchain for development and testing -dev-deploy url="http://localhost:8545" mnemonics="test test test test test test test test test test test junk" num_blocks_per_epoch="10" num_init_validators="5": - MNEMONICS="{{mnemonics}}" forge script contracts/test/LightClientTest.s.sol:DeployLightClientTestScript \ - --sig "run(uint32 numBlocksPerEpoch, uint32 numInitValidators)" {{num_blocks_per_epoch}} {{num_init_validators}} \ - --fork-url {{url}} --broadcast - # This is meant for local development and produces HTML output. In CI # the lcov output is pushed to coveralls. code-coverage: @@ -98,10 +92,12 @@ code-coverage: --ignore 'contract-bindings/*' --ignore 'contracts/*' @echo "HTML report available at: $CARGO_TARGET_DIR/coverage/index.html" +# Download Aztec's SRS for production download-srs: @echo "Check existence or download SRS for production" - @AZTEC_SRS_PATH="$PWD/data/aztec20/kzg10-aztec20-srs-1048584.bin" ./scripts/download_srs_aztec.sh + @./scripts/download_srs_aztec.sh +# Download Aztec's SRS for test (smaller degree usually) dev-download-srs: @echo "Check existence or download SRS for dev/test" @AZTEC_SRS_PATH="$PWD/data/aztec20/kzg10-aztec20-srs-65544.bin" ./scripts/download_srs_aztec.sh diff --git a/scripts/download_srs_aztec.sh b/scripts/download_srs_aztec.sh deleted file mode 100755 index 7ec16c68f7..0000000000 --- a/scripts/download_srs_aztec.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [ -f "$AZTEC_SRS_PATH" ]; then - echo "SRS file $AZTEC_SRS_PATH exists" -else - echo "SRS file $AZTEC_SRS_PATH does not exist, downloading ..." - wget -q -P "$(dirname $AZTEC_SRS_PATH)" "https://github.com/EspressoSystems/ark-srs/releases/download/v0.2.0/$(basename $AZTEC_SRS_PATH)" -fi diff --git a/sequencer/Cargo.toml b/sequencer/Cargo.toml index be8222bae0..425a84604a 100644 --- a/sequencer/Cargo.toml +++ b/sequencer/Cargo.toml @@ -45,7 +45,6 @@ tagged-base64 = { workspace = true } zeroize = { workspace = true } hotshot = { workspace = true } -hotshot-contract-adapter = { path = "../contracts/rust/adapter" } hotshot-events-service = { workspace = true } hotshot-orchestrator = { workspace = true } hotshot-query-service = { workspace = true } diff --git a/sequencer/src/bin/deploy.rs b/sequencer/src/bin/deploy.rs index b787892792..f3dde02922 100644 --- a/sequencer/src/bin/deploy.rs +++ b/sequencer/src/bin/deploy.rs @@ -1,33 +1,19 @@ -use anyhow::{ensure, Context}; +use anyhow::Context; use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; use async_std::sync::Arc; -use clap::{builder::OsStr, Parser}; +use clap::Parser; use contract_bindings::{ - erc1967_proxy::ERC1967Proxy, - hot_shot::HotShot, - light_client::LIGHTCLIENT_ABI, - light_client_mock::{LightClientMock, LIGHTCLIENTMOCK_ABI}, - light_client_state_update_vk_mock::LightClientStateUpdateVKMock, - plonk_verifier::PlonkVerifier, - shared_types::LightClientState, + erc1967_proxy::ERC1967Proxy, hot_shot::HotShot, light_client::LightClient, }; -use derive_more::Display; -use ethers::{ - contract::Contract as ContractBindings, - prelude::{coins_bip39::English, *}, - solc::artifacts::BytecodeObject, -}; -use futures::future::{BoxFuture, FutureExt}; -use hotshot_contract_adapter::light_client::ParsedLightClientState; +use ethers::prelude::{coins_bip39::English, *}; +use futures::future::FutureExt; use hotshot_stake_table::config::STAKE_TABLE_CAPACITY; use hotshot_state_prover::service::light_client_genesis; -use std::{ - collections::HashMap, - fs::File, - io::{stdout, Write}, - ops::Deref, - path::PathBuf, +use sequencer_utils::deployer::{ + deploy_light_client_contract, deploy_mock_light_client_contract, Contract, Contracts, + DeployedContracts, }; +use std::{fs::File, io::stdout, path::PathBuf}; use url::Url; /// Deploy contracts needed to run the sequencer. @@ -105,131 +91,6 @@ struct Options { pub stake_table_capacity: usize, } -/// Set of predeployed contracts. -#[derive(Clone, Debug, Parser)] -struct DeployedContracts { - /// Use an already-deployed HotShot.sol instead of deploying a new one. - #[clap(long, env = Contract::HotShot)] - hotshot: Option
, - - /// Use an already-deployed PlonkVerifier.sol instead of deploying a new one. - #[clap(long, env = Contract::PlonkVerifier)] - plonk_verifier: Option
, - - /// Use an already-deployed LightClientStateUpdateVK.sol instead of deploying a new one. - #[clap(long, env = Contract::StateUpdateVK)] - light_client_state_update_vk: Option
, - - /// Use an already-deployed LightClient.sol instead of deploying a new one. - #[clap(long, env = Contract::LightClient)] - light_client: Option
, - - /// Use an already-deployed LightClient.sol proxy instead of deploying a new one. - #[clap(long, env = Contract::LightClientProxy)] - light_client_proxy: Option
, -} - -/// An identifier for a particular contract. -#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Hash)] -enum Contract { - #[display(fmt = "ESPRESSO_SEQUENCER_HOTSHOT_ADDRESS")] - HotShot, - #[display(fmt = "ESPRESSO_SEQUENCER_PLONK_VERIFIER_ADDRESS")] - PlonkVerifier, - #[display(fmt = "ESPRESSO_SEQUENCER_LIGHT_CLIENT_STATE_UPDATE_VK_ADDRESS")] - StateUpdateVK, - #[display(fmt = "ESPRESSO_SEQUENCER_LIGHT_CLIENT_ADDRESS")] - LightClient, - #[display(fmt = "ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS")] - LightClientProxy, -} - -impl From for OsStr { - fn from(c: Contract) -> OsStr { - c.to_string().into() - } -} - -/// Cache of contracts predeployed or deployed during this current run. -struct Contracts(HashMap); - -impl From for Contracts { - fn from(deployed: DeployedContracts) -> Self { - let mut m = HashMap::new(); - if let Some(addr) = deployed.hotshot { - m.insert(Contract::HotShot, addr); - } - if let Some(addr) = deployed.plonk_verifier { - m.insert(Contract::PlonkVerifier, addr); - } - if let Some(addr) = deployed.light_client_state_update_vk { - m.insert(Contract::StateUpdateVK, addr); - } - if let Some(addr) = deployed.light_client { - m.insert(Contract::LightClient, addr); - } - if let Some(addr) = deployed.light_client_proxy { - m.insert(Contract::LightClientProxy, addr); - } - Self(m) - } -} - -impl Contracts { - /// Deploy a contract by calling a function. - /// - /// The `deploy` function will be called only if contract `name` is not already deployed; - /// otherwise this function will just return the predeployed address. The `deploy` function may - /// access this [`Contracts`] object, so this can be used to deploy contracts recursively in - /// dependency order. - async fn deploy_fn( - &mut self, - name: Contract, - deploy: impl FnOnce(&mut Self) -> BoxFuture<'_, anyhow::Result
>, - ) -> anyhow::Result
{ - if let Some(addr) = self.0.get(&name) { - tracing::info!("skipping deployment of {name}, already deployed at {addr:#x}"); - return Ok(*addr); - } - tracing::info!("deploying {name}"); - let addr = deploy(self).await?; - tracing::info!("deployed {name} at {addr:#x}"); - - self.0.insert(name, addr); - Ok(addr) - } - - /// Deploy a contract by executing its deploy transaction. - /// - /// The transaction will only be broadcast if contract `name` is not already deployed. - async fn deploy_tx( - &mut self, - name: Contract, - tx: ContractDeployer, - ) -> anyhow::Result
- where - M: Middleware + 'static, - C: Deref> + From, M>> + Send + 'static, - { - self.deploy_fn(name, |_| { - async { - let contract = tx.send().await?; - Ok(contract.address()) - } - .boxed() - }) - .await - } - - /// Write a .env file. - fn write(&self, mut w: impl Write) -> anyhow::Result<()> { - for (contract, address) in &self.0 { - writeln!(w, "{contract}={address:#x}")?; - } - Ok(()) - } -} - #[async_std::main] async fn main() -> anyhow::Result<()> { setup_logging(); @@ -251,38 +112,37 @@ async fn main() -> anyhow::Result<()> { contracts .deploy_tx(Contract::HotShot, HotShot::deploy(l1.clone(), ())?) .await?; - contracts - .deploy_fn(Contract::LightClientProxy, |contracts| { - let l1 = l1.clone(); - let orchestrator_url = opt.orchestrator_url.clone(); - async move { - let light_client = LightClientMock::new( - contracts - .deploy_fn(Contract::LightClient, |contracts| { - deploy_light_client_contract( - l1.clone(), - contracts, - opt.use_mock_contract, - ) - .boxed() - }) - .await?, - l1.clone(), - ); - let genesis = - light_client_genesis(&orchestrator_url, opt.stake_table_capacity).await?; - let data = light_client - .initialize(genesis.into(), u32::MAX, owner) - .calldata() - .context("calldata for initialize transaction not available")?; - let proxy = ERC1967Proxy::deploy(l1, (light_client.address(), data))? - .send() - .await?; - Ok(proxy.address()) - } - .boxed() - }) - .await?; + + if opt.use_mock_contract { + // LightClientMock is a non-upgradable contract, thus directly initialize + // it via its constructor + contracts + .deploy_fn(Contract::LightClient, |contracts| { + deploy_mock_light_client_contract(l1.clone(), contracts, None).boxed() + }) + .await?; + } else { + // LightClient is a upgradable contract, thus deploy first, + // then initialize it through a proxy contract + let lc_address = contracts + .deploy_fn(Contract::LightClient, |contracts| { + deploy_light_client_contract(l1.clone(), contracts).boxed() + }) + .await?; + let light_client = LightClient::new(lc_address, l1.clone()); + + let genesis = light_client_genesis(&opt.orchestrator_url, opt.stake_table_capacity).await?; + let data = light_client + .initialize(genesis.into(), u32::MAX, owner) + .calldata() + .context("calldata for initialize transaction not available")?; + contracts + .deploy_tx( + Contract::LightClientProxy, + ERC1967Proxy::deploy(l1.clone(), (lc_address, data))?, + ) + .await?; + } if let Some(out) = &opt.out { let file = File::options().create(true).write(true).open(out)?; @@ -293,118 +153,3 @@ async fn main() -> anyhow::Result<()> { Ok(()) } - -async fn deploy_light_client_contract( - l1: Arc, - contracts: &mut Contracts, - use_mock_contract: bool, -) -> anyhow::Result
{ - // Deploy library contracts. - let plonk_verifier = contracts - .deploy_tx( - Contract::PlonkVerifier, - PlonkVerifier::deploy(l1.clone(), ())?, - ) - .await?; - let vk = contracts - .deploy_tx( - Contract::StateUpdateVK, - LightClientStateUpdateVKMock::deploy(l1.clone(), ())?, - ) - .await?; - if use_mock_contract { - deploy_mock_light_client_contract(l1, plonk_verifier, vk).await - } else { - deploy_production_light_client_contract(l1, plonk_verifier, vk).await - } -} - -async fn deploy_production_light_client_contract( - l1: Arc, - plonk_verifier: Address, - vk: Address, -) -> anyhow::Result
{ - // Link with LightClient's bytecode artifacts. We include the unlinked bytecode for the contract - // in this binary so that the contract artifacts do not have to be distributed with the binary. - // This should be fine because if the bindings we are importing are up to date, so should be the - // contract artifacts: this is no different than foundry inlining bytecode objects in generated - // bindings, except that foundry doesn't provide the bytecode for contracts that link with - // libraries, so we have to do it ourselves. - let mut bytecode: BytecodeObject = serde_json::from_str(include_str!( - "../../../contract-bindings/artifacts/LightClient_bytecode.json", - ))?; - bytecode - .link_fully_qualified( - "contracts/src/libraries/PlonkVerifier.sol:PlonkVerifier", - plonk_verifier, - ) - .resolve() - .context("error linking PlonkVerifier lib")?; - bytecode - .link_fully_qualified( - "contracts/tests/mocks/LightClientStateUpdateVK.sol:LightClientStateUpdateVK", - vk, - ) - .resolve() - .context("error linking LightClientStateUpdateVK lib")?; - ensure!(!bytecode.is_unlinked(), "failed to link LightClient.sol"); - - // Deploy light client. - let light_client_factory = ContractFactory::new( - LIGHTCLIENT_ABI.clone(), - bytecode - .as_bytes() - .context("error parsing bytecode for linked LightClient contract")? - .clone(), - l1, - ); - let contract = light_client_factory.deploy(())?.send().await?; - Ok(contract.address()) -} - -async fn deploy_mock_light_client_contract( - l1: Arc, - plonk_verifier: Address, - vk: Address, -) -> anyhow::Result
{ - // Link with LightClient's bytecode artifacts. We include the unlinked bytecode for the contract - // in this binary so that the contract artifacts do not have to be distributed with the binary. - // This should be fine because if the bindings we are importing are up to date, so should be the - // contract artifacts: this is no different than foundry inlining bytecode objects in generated - // bindings, except that foundry doesn't provide the bytecode for contracts that link with - // libraries, so we have to do it ourselves. - let mut bytecode: BytecodeObject = serde_json::from_str(include_str!( - "../../../contract-bindings/artifacts/LightClientMock_bytecode.json", - ))?; - bytecode - .link_fully_qualified( - "contracts/src/libraries/PlonkVerifier.sol:PlonkVerifier", - plonk_verifier, - ) - .resolve() - .context("error linking PlonkVerifier lib")?; - bytecode - .link_fully_qualified( - "contracts/tests/mocks/LightClientStateUpdateVKMock.sol:LightClientStateUpdateVKMock", - vk, - ) - .resolve() - .context("error linking LightClientStateUpdateVK lib")?; - ensure!(!bytecode.is_unlinked(), "failed to link LightClient.sol"); - - // Deploy light client. - let light_client_factory = ContractFactory::new( - LIGHTCLIENTMOCK_ABI.clone(), - bytecode - .as_bytes() - .context("error parsing bytecode for linked LightClient contract")? - .clone(), - l1, - ); - let dummy_genesis: LightClientState = ParsedLightClientState::dummy_genesis().into(); - let contract = light_client_factory - .deploy((dummy_genesis, u32::MAX))? - .send() - .await?; - Ok(contract.address()) -} diff --git a/utils/Cargo.toml b/utils/Cargo.toml index b3c844915e..99ec97aa6e 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -6,13 +6,18 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = "1.0" -ark-serialize = { version = "0.4", features = ["derive"] } +anyhow = { workspace = true } +ark-serialize = { workspace = true, features = ["derive"] } async-std = { workspace = true } +clap = { workspace = true } commit = { git = "https://github.com/EspressoSystems/commit" } contract-bindings = { path = "../contract-bindings" } +derive_more = { workspace = true } ethers = "2.0.4" +futures = { workspace = true } +hotshot-contract-adapter ={ path = "../contracts/rust/adapter" } portpicker = "0.1.1" +serde_json = "^1.0.113" surf = "2.3.2" tempfile = "3.9.0" tracing = "0.1.37" diff --git a/utils/src/deployer.rs b/utils/src/deployer.rs new file mode 100644 index 0000000000..204d851968 --- /dev/null +++ b/utils/src/deployer.rs @@ -0,0 +1,272 @@ +use anyhow::{ensure, Context}; +use async_std::sync::Arc; +use clap::{builder::OsStr, Parser}; +use contract_bindings::{ + light_client::LIGHTCLIENT_ABI, light_client_mock::LIGHTCLIENTMOCK_ABI, + light_client_state_update_vk::LightClientStateUpdateVK, + light_client_state_update_vk_mock::LightClientStateUpdateVKMock, plonk_verifier::PlonkVerifier, + shared_types::LightClientState, +}; +use derive_more::Display; +use ethers::{prelude::*, solc::artifacts::BytecodeObject}; +use futures::future::{BoxFuture, FutureExt}; +use hotshot_contract_adapter::light_client::ParsedLightClientState; +use std::{collections::HashMap, io::Write, ops::Deref}; + +/// Set of predeployed contracts. +#[derive(Clone, Debug, Parser)] +pub struct DeployedContracts { + /// Use an already-deployed HotShot.sol instead of deploying a new one. + #[clap(long, env = Contract::HotShot)] + hotshot: Option
, + + /// Use an already-deployed PlonkVerifier.sol instead of deploying a new one. + #[clap(long, env = Contract::PlonkVerifier)] + plonk_verifier: Option
, + + /// Use an already-deployed LightClientStateUpdateVK.sol instead of deploying a new one. + #[clap(long, env = Contract::StateUpdateVK)] + light_client_state_update_vk: Option
, + + /// Use an already-deployed LightClient.sol instead of deploying a new one. + #[clap(long, env = Contract::LightClient)] + light_client: Option
, + + /// Use an already-deployed LightClient.sol proxy instead of deploying a new one. + #[clap(long, env = Contract::LightClientProxy)] + light_client_proxy: Option
, +} + +/// An identifier for a particular contract. +#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, Hash)] +pub enum Contract { + #[display(fmt = "ESPRESSO_SEQUENCER_HOTSHOT_ADDRESS")] + HotShot, + #[display(fmt = "ESPRESSO_SEQUENCER_PLONK_VERIFIER_ADDRESS")] + PlonkVerifier, + #[display(fmt = "ESPRESSO_SEQUENCER_LIGHT_CLIENT_STATE_UPDATE_VK_ADDRESS")] + StateUpdateVK, + #[display(fmt = "ESPRESSO_SEQUENCER_LIGHT_CLIENT_ADDRESS")] + LightClient, + #[display(fmt = "ESPRESSO_SEQUENCER_LIGHT_CLIENT_PROXY_ADDRESS")] + LightClientProxy, +} + +impl From for OsStr { + fn from(c: Contract) -> OsStr { + c.to_string().into() + } +} + +/// Cache of contracts predeployed or deployed during this current run. +#[derive(Debug, Clone, Default)] +pub struct Contracts(HashMap); + +impl From for Contracts { + fn from(deployed: DeployedContracts) -> Self { + let mut m = HashMap::new(); + if let Some(addr) = deployed.hotshot { + m.insert(Contract::HotShot, addr); + } + if let Some(addr) = deployed.plonk_verifier { + m.insert(Contract::PlonkVerifier, addr); + } + if let Some(addr) = deployed.light_client_state_update_vk { + m.insert(Contract::StateUpdateVK, addr); + } + if let Some(addr) = deployed.light_client { + m.insert(Contract::LightClient, addr); + } + if let Some(addr) = deployed.light_client_proxy { + m.insert(Contract::LightClientProxy, addr); + } + Self(m) + } +} + +impl Contracts { + /// Deploy a contract by calling a function. + /// + /// The `deploy` function will be called only if contract `name` is not already deployed; + /// otherwise this function will just return the predeployed address. The `deploy` function may + /// access this [`Contracts`] object, so this can be used to deploy contracts recursively in + /// dependency order. + pub async fn deploy_fn( + &mut self, + name: Contract, + deploy: impl FnOnce(&mut Self) -> BoxFuture<'_, anyhow::Result
>, + ) -> anyhow::Result
{ + if let Some(addr) = self.0.get(&name) { + tracing::info!("skipping deployment of {name}, already deployed at {addr:#x}"); + return Ok(*addr); + } + tracing::info!("deploying {name}"); + let addr = deploy(self).await?; + tracing::info!("deployed {name} at {addr:#x}"); + + self.0.insert(name, addr); + Ok(addr) + } + + /// Deploy a contract by executing its deploy transaction. + /// + /// The transaction will only be broadcast if contract `name` is not already deployed. + pub async fn deploy_tx( + &mut self, + name: Contract, + tx: ContractDeployer, + ) -> anyhow::Result
+ where + M: Middleware + 'static, + C: Deref> + + From, M>> + + Send + + 'static, + { + self.deploy_fn(name, |_| { + async { + let contract = tx.send().await?; + Ok(contract.address()) + } + .boxed() + }) + .await + } + + /// Write a .env file. + pub fn write(&self, mut w: impl Write) -> anyhow::Result<()> { + for (contract, address) in &self.0 { + writeln!(w, "{contract}={address:#x}")?; + } + Ok(()) + } +} + +/// Default deployment function `LightClient.sol` in production +/// +/// # NOTE: +/// currently, `LightClient.sol` follows upgradable contract, thus a follow-up +/// call to `.initialize()` with proper genesis block (and other constructor args) +/// are expected to be *delegatecall-ed through the proxy contract*. +pub async fn deploy_light_client_contract( + l1: Arc, + contracts: &mut Contracts, +) -> anyhow::Result
{ + // Deploy library contracts. + let plonk_verifier = contracts + .deploy_tx( + Contract::PlonkVerifier, + PlonkVerifier::deploy(l1.clone(), ())?, + ) + .await?; + let vk = contracts + .deploy_tx( + Contract::StateUpdateVK, + LightClientStateUpdateVK::deploy(l1.clone(), ())?, + ) + .await?; + + // Link with LightClient's bytecode artifacts. We include the unlinked bytecode for the contract + // in this binary so that the contract artifacts do not have to be distributed with the binary. + // This should be fine because if the bindings we are importing are up to date, so should be the + // contract artifacts: this is no different than foundry inlining bytecode objects in generated + // bindings, except that foundry doesn't provide the bytecode for contracts that link with + // libraries, so we have to do it ourselves. + let mut bytecode: BytecodeObject = serde_json::from_str(include_str!( + "../../contract-bindings/artifacts/LightClient_bytecode.json", + ))?; + bytecode + .link_fully_qualified( + "contracts/src/libraries/PlonkVerifier.sol:PlonkVerifier", + plonk_verifier, + ) + .resolve() + .context("error linking PlonkVerifier lib")?; + bytecode + .link_fully_qualified( + "contracts/src/libraries/LightClientStateUpdateVK.sol:LightClientStateUpdateVK", + vk, + ) + .resolve() + .context("error linking LightClientStateUpdateVK lib")?; + ensure!(!bytecode.is_unlinked(), "failed to link LightClient.sol"); + + // Deploy light client. + let light_client_factory = ContractFactory::new( + LIGHTCLIENT_ABI.clone(), + bytecode + .as_bytes() + .context("error parsing bytecode for linked LightClient contract")? + .clone(), + l1, + ); + let contract = light_client_factory.deploy(())?.send().await?; + Ok(contract.address()) +} + +/// Default deployment function `LightClientMock.sol` for testing +/// +/// # NOTE +/// unlike [`deploy_light_client_contract()`], the `LightClientMock` doesn't +/// use upgradable contract for simplicity, thus there's no follow-up `.initialize()` +/// necessary, as we have already call its un-disabled constructor. +pub async fn deploy_mock_light_client_contract( + l1: Arc, + contracts: &mut Contracts, + constructor_args: Option<(LightClientState, u32)>, +) -> anyhow::Result
{ + // Deploy library contracts. + let plonk_verifier = contracts + .deploy_tx( + Contract::PlonkVerifier, + PlonkVerifier::deploy(l1.clone(), ())?, + ) + .await?; + let vk = contracts + .deploy_tx( + Contract::StateUpdateVK, + LightClientStateUpdateVKMock::deploy(l1.clone(), ())?, + ) + .await?; + + let mut bytecode: BytecodeObject = serde_json::from_str(include_str!( + "../../contract-bindings/artifacts/LightClientMock_bytecode.json", + ))?; + bytecode + .link_fully_qualified( + "contracts/src/libraries/PlonkVerifier.sol:PlonkVerifier", + plonk_verifier, + ) + .resolve() + .context("error linking PlonkVerifier lib")?; + bytecode + .link_fully_qualified( + "contracts/tests/mocks/LightClientStateUpdateVKMock.sol:LightClientStateUpdateVKMock", + vk, + ) + .resolve() + .context("error linking LightClientStateUpdateVKMock lib")?; + ensure!( + !bytecode.is_unlinked(), + "failed to link LightClientMock.sol" + ); + + // Deploy light client. + let light_client_factory = ContractFactory::new( + LIGHTCLIENTMOCK_ABI.clone(), + bytecode + .as_bytes() + .context("error parsing bytecode for linked LightClientMock contract")? + .clone(), + l1, + ); + let constructor_args = match constructor_args { + Some(args) => args, + None => (ParsedLightClientState::dummy_genesis().into(), u32::MAX), + }; + let contract = light_client_factory + .deploy(constructor_args)? + .send() + .await?; + Ok(contract.address()) +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 94dad1ca5b..64c6243b12 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -20,6 +20,7 @@ use std::{ use tempfile::TempDir; use url::Url; +pub mod deployer; pub mod test_utils; pub type Signer = SignerMiddleware, LocalWallet>;