diff --git a/README.md b/README.md index 685a5110..14eefde7 100644 --- a/README.md +++ b/README.md @@ -676,14 +676,32 @@ env MY_FAUCET_MANAGER_MNEMONIC="$TEST_MNEMONIC" smoke-test-goerli --reset To run the tests against Arbitrum Goerli follow these steps: -- Install [Metamask](https://metamask.io/) in your browser and copy the mnemonic. +- Install [Metamask](https://metamask.io/) in your browser and restore with + your GOERLI_MNEMONIC. - Switch metamask to the Goerli network. - Get some Goerli ethers at some faucet (see Goerli section above) - Go to the [Arbitrum bridge](https://bridge.arbitrum.io/) and deposit your Goerli eth. Leave a bit for the ethereum gas fees. Wait a few minutes until your account is funded. +- Deploy the contracts + +``` +hardhat deploy --tags CAPE --network arbitrum_goerli --reset +``` + - Run the tests ``` -> env ETH_MNEMONIC="$YOUR_ETH_MNEMONIC" CAPE_WEB3_PROVIDER_URL=https://goerli-rollup.arbitrum.io/rpc cargo test --release test_2user_and_submit -- --nocapture +run-tests-arbitrum +``` + +This script will re-use the stateless libraries in order to save gas and speed +up test execution. To run a single test, pass the test name to the script as +argument, e.g. `run-tests-arbitrum backend::test::test_transfer`. + +If you are getting `Too Many Requests` errors, you may want to set up an infura +project and use their arbitrum goerli RPC and use it via + +``` +CAPE_WEB3_PROVIDER_URL=https://arbitrum-goerli.infura.io/v3/... run-tests-arbitrum ``` diff --git a/bin/run-tests-arbitrum b/bin/run-tests-arbitrum new file mode 100755 index 00000000..b4677c1c --- /dev/null +++ b/bin/run-tests-arbitrum @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +# Copyright (c) 2022 Espresso Systems (espressosys.com) +# This file is part of the Configurable Asset Privacy for Ethereum (CAPE) library. +# +# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. +# You should have received a copy of the GNU General Public License along with this program. If not, see . + +set -euo pipefail + +function get_addr() { + cat contracts/deployments/arbitrum/$1.json | jq -r .address +} + +export VERIFYING_KEYS_LIB_ADDRESS=$(get_addr VerifyingKeys) +export RESCUE_LIB_ADDRESS=$(get_addr RescueLib) +export VERIFIER_ADDRESS=$(get_addr PlonkVerifier) + +export CAPE_WEB3_PROVIDER_URL=${CAPE_WEB3_PROVIDER_URL:-https://goerli-rollup.arbitrum.io/rpc} +export ETH_MNEMONIC="${ETH_MNEMONIC:-$GOERLI_MNEMONIC}" + +echo "Running tests..." +cargo test --release -- --test-threads 1 "$@" diff --git a/contracts/deploy/00_cape.ts b/contracts/deploy/00_cape.ts index 6c0c5041..97d4d5d7 100644 --- a/contracts/deploy/00_cape.ts +++ b/contracts/deploy/00_cape.ts @@ -28,7 +28,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Wait for 2 confirmations on public networks. waitConfirmations: hre.network.tags.public ? 2 : 1, // Avoid deployment failures due to potentially failing `estimateGas` calls. - gasLimit: 10_000_000, + // gasLimit: 10_000_000, // This is better set in hardhat config.networks..gas }; log("Deploy options:", opts); diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index fe6f983c..0e77d173 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -83,6 +83,11 @@ const config: HardhatUserConfig = { accounts: { mnemonic: process.env.GOERLI_MNEMONIC }, tags: ["public"], }, + arbitrum_goerli: { + url: "https://goerli-rollup.arbitrum.io/rpc", + gasPrice: 100_000_000, + accounts: { mnemonic: process.env.GOERLI_MNEMONIC }, + }, localhost: { url: `http://localhost:${process.env.RPC_PORT || 8545}`, timeout: 120000, // when running against hardhat, some tests are very slow diff --git a/contracts/rust/src/cape/events.rs b/contracts/rust/src/cape/events.rs index 43b8a922..4e02c297 100644 --- a/contracts/rust/src/cape/events.rs +++ b/contracts/rust/src/cape/events.rs @@ -23,12 +23,13 @@ pub fn decode_cape_block_from_event(block: BlockCommittedFilter) -> Result().0) .send() .await? - .await?; + .await? + .ensure_mined(); // Adapted from seahorse // https://github.com/EspressoSystems/seahorse/blob/ace20bc5f1bcf5b88ca0562799b8e80e6c52e933/src/persistence.rs#L574 @@ -96,15 +98,19 @@ mod tests { &connection.contract, block_with_memos.clone(), BlockNumber::Latest, - GAS_LIMIT_OVERRIDE, // gas limit + 1_000_000, // extra gas. This transaction sometimes runs out of gas, reason unclear. ) .await? - .await?; + .await? + .ensure_mined(); // A connection with a random wallet (for queries only) let query_connection = EthConnection::from_config_for_query( &format!("{:?}", connection.contract.address()), // 0x123...cdf - "http://localhost:8545", + &match std::env::var("CAPE_WEB3_PROVIDER_URL") { + Ok(url) => url, + Err(_) => "http://localhost:8545".to_string(), + }, ); let events = query_connection diff --git a/contracts/rust/src/cape/faucet.rs b/contracts/rust/src/cape/faucet.rs index 6a07f75e..fc8a929c 100644 --- a/contracts/rust/src/cape/faucet.rs +++ b/contracts/rust/src/cape/faucet.rs @@ -12,23 +12,27 @@ mod test { use crate::{ assertion::Matcher, deploy::deploy_test_cape_with_deployer, - ethereum::get_funded_client, + ethereum::{get_funded_client, get_provider}, model::CAPE_MERKLE_HEIGHT, types::{self as sol, field_to_u256, GenericInto, TestCAPE}, }; use anyhow::Result; - use ethers::abi::AbiDecode; + use ethers::{abi::AbiDecode, prelude::SignerMiddleware, signers::LocalWallet}; use jf_cap::{ keys::UserKeyPair, structs::{AssetDefinition, BlindFactor, FreezeFlag, RecordCommitment, RecordOpening}, BaseField, MerkleTree, }; + use std::sync::Arc; #[tokio::test] async fn test_faucet() -> Result<()> { let rng = &mut ark_std::test_rng(); let deployer = get_funded_client().await?; - let non_deployer = get_funded_client().await?; + let non_deployer = Arc::new(SignerMiddleware::new( + get_provider(), + LocalWallet::new(&mut rand::thread_rng()), + )); let contract = deploy_test_cape_with_deployer(deployer.clone()).await; let faucet_manager = UserKeyPair::generate(rng); @@ -43,7 +47,7 @@ mod test { faucet_manager.address().into(), faucet_manager.pub_key().enc_key().into(), ) - .send() + .call() .await .should_revert_with_message("Only invocable by deployer"); diff --git a/contracts/rust/src/cape/submit_block.rs b/contracts/rust/src/cape/submit_block.rs index 82abd70a..be281f5c 100644 --- a/contracts/rust/src/cape/submit_block.rs +++ b/contracts/rust/src/cape/submit_block.rs @@ -14,6 +14,7 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use ethers::prelude::signer::SignerMiddlewareError; use ethers::prelude::{BlockNumber, Provider, Wallet}; use ethers::prelude::{Bytes, Http, Middleware, PendingTransaction, TxHash}; +use ethers::providers::ProviderError; use ethers_core::k256::ecdsa::SigningKey; use super::{BlockMemos, BlockWithMemos}; @@ -46,7 +47,7 @@ pub async fn submit_cape_block_with_memos( contract: &CAPE, block: BlockWithMemos, block_number: BlockNumber, - gas_limit: u64, + extra_gas: u64, ) -> Result, SignerMiddlewareError, Wallet>> { let mut memos_bytes: Vec = vec![]; @@ -95,7 +96,23 @@ pub async fn submit_cape_block_with_memos( // gas usage of processing a burn note is potentially unbounded. Using // tokens whose transfer function far exceeds normal gas consumption is // currently not supported. - tx.set_gas(gas_limit); + // + // TODO: mathis: it's a bit wasteful to download the entire block for this + // but I don't know of another way to obtain the current block gas limit. + let block = contract + .client() + .get_block(BlockNumber::Latest) + .await? + .ok_or_else(|| { + SignerMiddlewareError::MiddlewareError(ProviderError::CustomError( + "Did not receive a block".to_string(), + )) + })?; + + tx.set_gas(std::cmp::min( + tx.gas().ok_or(SignerMiddlewareError::GasMissing)? + extra_gas, + block.gas_limit, + )); contract.client().send_transaction(tx, None).await } @@ -236,7 +253,6 @@ mod tests { .await? .ensure_mined(); - // Check that now the nullifier has been inserted assert!( contract .nullifiers(nf.generic_into::().0) diff --git a/contracts/rust/src/deploy.rs b/contracts/rust/src/deploy.rs index f37852f3..bbb45a36 100644 --- a/contracts/rust/src/deploy.rs +++ b/contracts/rust/src/deploy.rs @@ -16,6 +16,8 @@ use crate::types::{ TestTranscript, TestVerifyingKeys, CAPE, }; use ethers::prelude::{k256::ecdsa::SigningKey, Http, Provider, SignerMiddleware, Wallet}; +use ethers::types::Address; +use std::env; use std::sync::Arc; // Middleware used for locally signing transactions @@ -35,13 +37,23 @@ pub async fn deploy_test_cape_with_deployer( deployer: Arc, ) -> TestCAPE { // deploy the PlonkVerifier - let verifier = deploy( - deployer.clone(), - &contract_abi_path("verifier/PlonkVerifier.sol/PlonkVerifier"), - (), - ) - .await - .unwrap(); + + // If VERIFIER_ADDRESS is set, use that address instead of deploying a new + // contract. + let verifier_address = match env::var("VERIFIER_ADDRESS") { + Ok(val) => { + println!("Using Verifier at {val}"); + val.parse::
().unwrap() + } + Err(_) => deploy( + deployer.clone(), + &contract_abi_path("verifier/PlonkVerifier.sol/PlonkVerifier"), + (), + ) + .await + .unwrap() + .address(), + }; let records_merkle_tree = deploy( deployer.clone(), @@ -57,7 +69,7 @@ pub async fn deploy_test_cape_with_deployer( &contract_abi_path("mocks/TestCAPE.sol/TestCAPE"), CAPEConstructorArgs::new( CAPE_NUM_ROOTS as u64, - verifier.address(), + verifier_address, records_merkle_tree.address(), ) .to_tuple(), diff --git a/contracts/rust/src/ethereum.rs b/contracts/rust/src/ethereum.rs index 93544053..71dca3c7 100644 --- a/contracts/rust/src/ethereum.rs +++ b/contracts/rust/src/ethereum.rs @@ -26,8 +26,9 @@ use ethers::{ use std::{convert::TryFrom, env, fs, path::Path, sync::Arc, time::Duration}; /// Supply this gas limit when the automatically filled estimated gas value is -/// too low and the transaction runs out of gas. -pub const GAS_LIMIT_OVERRIDE: u64 = 10_000_000; +/// too low and the transaction runs out of gas. This limit is large enough for +/// arbitrum and small enough for L1 (block gas limit 30_000_000 @ 2022-11-01). +pub const GAS_LIMIT_OVERRIDE: u64 = 25_000_000; /// Utility to interact with the CAPE contract on some Ethereum blockchain #[derive(Clone, Debug)] @@ -157,7 +158,10 @@ async fn link_unlinked_libraries( // Connect to linked library if env var with address is set // otherwise, deploy the library. let rescue_lib_address = match env::var("RESCUE_LIB_ADDRESS") { - Ok(val) => val.parse::
()?, + Ok(val) => { + println!("Using RescueLib library at {val}"); + val.parse::
()? + } Err(_) => deploy( client.clone(), &contract_abi_path("libraries/RescueLib.sol/RescueLib"), @@ -181,7 +185,10 @@ async fn link_unlinked_libraries( // Connect to linked library if env var with address is set // otherwise, deploy the library. let verifying_keys_lib_address = match env::var("VERIFYING_KEYS_LIB_ADDRESS") { - Ok(val) => val.parse::
()?, + Ok(val) => { + println!("Using VerifyingKeys library at {val}"); + val.parse::
()? + } Err(_) => deploy( client.clone(), &contract_abi_path("libraries/VerifyingKeys.sol/VerifyingKeys"), diff --git a/relayer/src/bin/minimal-relayer.rs b/relayer/src/bin/minimal-relayer.rs index d076c1ab..aa8a06b8 100644 --- a/relayer/src/bin/minimal-relayer.rs +++ b/relayer/src/bin/minimal-relayer.rs @@ -14,7 +14,7 @@ use ethers::prelude::{ coins_bip39::English, Address, Middleware, MnemonicBuilder, Signer, SignerMiddleware, }; use relayer::{ - init_web_server, submit_empty_block_loop, NonceCountRule, WebState, DEFAULT_RELAYER_GAS_LIMIT, + init_web_server, submit_empty_block_loop, NonceCountRule, WebState, DEFAULT_RELAYER_EXTRA_GAS, DEFAULT_RELAYER_MAX_RETRIES, DEFAULT_RELAYER_PORT, DEFAULT_RELAYER_RETRY_INTERVAL_MS, }; use std::{num::NonZeroU64, sync::Arc, time::Duration}; @@ -73,8 +73,8 @@ struct MinimalRelayerOptions { /// /// The default of 10M is enough to cover the gas cost of submitting one note /// and crediting up to 10 pending deposits in the smart contract. - #[structopt(long, env = "CAPE_RELAYER_GAS_LIMIT", default_value = DEFAULT_RELAYER_GAS_LIMIT)] - gas_limit: NonZeroU64, + #[structopt(long, env = "CAPE_RELAYER_EXTRA_GAS", default_value = DEFAULT_RELAYER_EXTRA_GAS)] + extra_gas: NonZeroU64, /// Maximum number of times to retry transaction submission. /// @@ -121,7 +121,7 @@ async fn main() -> std::io::Result<()> { let web_state = WebState::new( contract, opt.nonce_count_rule, - opt.gas_limit.into(), + opt.extra_gas.into(), opt.max_retries, Duration::from_millis(opt.retry_interval), ); diff --git a/relayer/src/lib.rs b/relayer/src/lib.rs index b7903acb..1a4d656a 100644 --- a/relayer/src/lib.rs +++ b/relayer/src/lib.rs @@ -30,7 +30,11 @@ use tide::{ use tracing::{event, Level}; pub const DEFAULT_RELAYER_PORT: &str = "50077"; -pub const DEFAULT_RELAYER_GAS_LIMIT: &str = "10000000"; // 10M + +/// The extra gas added to the gas estimate to account for the gas cost of +/// crediting deposits. +pub const DEFAULT_RELAYER_EXTRA_GAS: &str = "10000000"; // 10M + pub const DEFAULT_RELAYER_RETRY_INTERVAL_MS: &str = "500"; pub const DEFAULT_RELAYER_MAX_RETRIES: &str = "2"; @@ -88,7 +92,7 @@ fn server_error>(err: E) -> tide::Error { pub struct WebState { contract: CAPE, nonce_count_rule: NonceCountRule, - gas_limit: u64, + extra_gas: u64, max_retries: u64, retry_interval: Duration, block_submission_mutex: Arc>, @@ -98,14 +102,14 @@ impl WebState { pub fn new( contract: CAPE, nonce_count_rule: NonceCountRule, - gas_limit: u64, + extra_gas: u64, max_retries: u64, retry_interval: Duration, ) -> Self { Self { contract, nonce_count_rule, - gas_limit, + extra_gas, max_retries, retry_interval, block_submission_mutex: Arc::new(Mutex::new(())), @@ -237,7 +241,7 @@ async fn submit_block(web_state: &WebState, block: BlockWithMemos) -> Result surf::Client { let client: surf::Client = surf::Config::new() .set_base_url(Url::parse(&format!("http://localhost:{}", port)).unwrap()) @@ -707,7 +668,7 @@ mod test { let web_state = WebState::new( contract, NonceCountRule::Pending, - DEFAULT_RELAYER_GAS_LIMIT.parse().unwrap(), + DEFAULT_RELAYER_EXTRA_GAS.parse().unwrap(), DEFAULT_RELAYER_MAX_RETRIES.parse().unwrap(), Duration::from_millis(DEFAULT_RELAYER_RETRY_INTERVAL_MS.parse().unwrap()), ); diff --git a/wallet/src/backend.rs b/wallet/src/backend.rs index 048636e6..830cf8e3 100644 --- a/wallet/src/backend.rs +++ b/wallet/src/backend.rs @@ -713,7 +713,9 @@ mod test { ui::AssetInfo, }; use crate::{CapeWallet, CapeWalletExt}; - use cap_rust_sandbox::{deploy::deploy_erc20_token, universal_param::UNIVERSAL_PARAM}; + use cap_rust_sandbox::{ + deploy::deploy_erc20_token, ethereum::get_funded_client, universal_param::UNIVERSAL_PARAM, + }; use ethers::types::{TransactionRequest, U256}; use jf_cap::structs::AssetCode; use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; @@ -932,19 +934,35 @@ mod test { // Fund the Ethereum wallets for contract calls. let provider = EthRpc::provider(&rpc_url_for_test()).interval(Duration::from_millis(100u64)); + + let deployer = get_funded_client().await.unwrap(); let accounts = provider.get_accounts().await.unwrap(); - assert!(!accounts.is_empty()); + for wallet in [&sponsor, &wrapper] { - let tx = TransactionRequest::new() - .to(Address::from(wallet.eth_address().await.unwrap())) - .value(ethers::utils::parse_ether(U256::from(1u64)).unwrap()) - .from(accounts[0]); - provider - .send_transaction(tx, None) - .await - .unwrap() - .await - .unwrap(); + if accounts.is_empty() { + // On public testnets we don't have unlocked accounts + let tx = TransactionRequest::new() + .to(Address::from(wallet.eth_address().await.unwrap())) + .value(ethers::utils::parse_ether("0.01").unwrap()); + deployer + .send_transaction(tx, None) + .await + .unwrap() + .await + .unwrap(); + } else { + // On private testnets use the unlocked account + let tx = TransactionRequest::new() + .to(Address::from(wallet.eth_address().await.unwrap())) + .value(ethers::utils::parse_ether("1").unwrap()) + .from(accounts[0]); + provider + .send_transaction(tx, None) + .await + .unwrap() + .await + .unwrap(); + } } let erc20_contract = deploy_erc20_token().await;