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;