From ad38c92eb184ee0fb793f5688b46e06d1ed0a500 Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Thu, 22 Aug 2024 10:26:21 +0200 Subject: [PATCH 01/10] feat: cache ERC-20 contract before benching --- .github/workflows/gas-bench.yml | 3 +++ benches/src/erc20.rs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/.github/workflows/gas-bench.yml b/.github/workflows/gas-bench.yml index 7d572fc4..4a52c50b 100644 --- a/.github/workflows/gas-bench.yml +++ b/.github/workflows/gas-bench.yml @@ -31,6 +31,9 @@ jobs: with: key: "gas-bench" + - name: install cargo-stylus + run: cargo install cargo-stylus@0.4.2 cargo-stylus-check@0.4.2 + - name: install solc run: | curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.21/solc-static-linux diff --git a/benches/src/erc20.rs b/benches/src/erc20.rs index 22f2aff0..15ed5447 100644 --- a/benches/src/erc20.rs +++ b/benches/src/erc20.rs @@ -1,3 +1,5 @@ +use std::process::Command; + use alloy::{ network::{AnyNetwork, EthereumWallet}, primitives::Address, @@ -8,6 +10,7 @@ use alloy::{ }; use alloy_primitives::U256; use e2e::{receipt, Account}; +use eyre::{bail, Context}; use crate::report::Report; @@ -60,6 +63,19 @@ pub async fn bench() -> eyre::Result { let contract = Erc20::new(contract_addr, &alice_wallet); let contract_bob = Erc20::new(contract_addr, &bob_wallet); + // Add contract to cache manager. We leave the bid unspecified, since we + // should have plenty of room to fill the cache (can hold ~4k contracts). + let status = Command::new("cargo-stylus") + .arg("cache") + .args(&["--e", &std::env::var("RPC_URL")?]) + .args(&["--private-key", &format!("0x{}", alice.pk())]) + .args(&["--address", &format!("{}", contract_addr)]) + .status() + .with_context(|| "`cargo stylus cache` failed:")?; + if !status.success() { + bail!("Failed to cache Erc20 contract"); + } + // IMPORTANT: Order matters! use Erc20::*; #[rustfmt::skip] From 563bb26247472ebba857fab29b886cdfc582016a Mon Sep 17 00:00:00 2001 From: Alexander Gonzalez Date: Thu, 22 Aug 2024 10:56:44 +0200 Subject: [PATCH 02/10] fix: --e -> -e --- benches/src/erc20.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/src/erc20.rs b/benches/src/erc20.rs index 15ed5447..6ea1fba5 100644 --- a/benches/src/erc20.rs +++ b/benches/src/erc20.rs @@ -67,7 +67,7 @@ pub async fn bench() -> eyre::Result { // should have plenty of room to fill the cache (can hold ~4k contracts). let status = Command::new("cargo-stylus") .arg("cache") - .args(&["--e", &std::env::var("RPC_URL")?]) + .args(&["-e", &std::env::var("RPC_URL")?]) .args(&["--private-key", &format!("0x{}", alice.pk())]) .args(&["--address", &format!("{}", contract_addr)]) .status() From c8fe7f07e133febc6e539865371c2086bbb3659f Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 20 Sep 2024 20:18:51 +0400 Subject: [PATCH 03/10] add caching to benchmarks --- .github/workflows/gas-bench.yml | 2 +- benches/src/access_control.rs | 6 +++-- benches/src/erc20.rs | 22 +++------------- benches/src/erc721.rs | 6 +++-- benches/src/lib.rs | 45 +++++++++++++++++++++++++-------- benches/src/main.rs | 1 + benches/src/merkle_proofs.rs | 6 +++-- lib/e2e/src/account.rs | 6 +++-- lib/e2e/src/system.rs | 4 +-- scripts/bench.sh | 9 +++++-- scripts/nitro-testnode.sh | 15 ++++++++--- 11 files changed, 76 insertions(+), 46 deletions(-) diff --git a/.github/workflows/gas-bench.yml b/.github/workflows/gas-bench.yml index cf002e81..a14bf25c 100644 --- a/.github/workflows/gas-bench.yml +++ b/.github/workflows/gas-bench.yml @@ -32,7 +32,7 @@ jobs: key: "gas-bench" - name: install cargo-stylus - run: cargo install cargo-stylus@0.4.2 cargo-stylus-check@0.4.2 + run: cargo install cargo-stylus@0.5.1 - name: install solc run: | diff --git a/benches/src/access_control.rs b/benches/src/access_control.rs index 7c9b7978..da754780 100644 --- a/benches/src/access_control.rs +++ b/benches/src/access_control.rs @@ -50,7 +50,9 @@ pub async fn bench() -> eyre::Result { .wallet(EthereumWallet::from(bob.signer.clone())) .on_http(bob.url().parse()?); - let contract_addr = deploy(&alice).await; + let contract_addr = deploy(&alice).await?; + crate::cache_contract(&alice, contract_addr)?; + let contract = AccessControl::new(contract_addr, &alice_wallet); let contract_bob = AccessControl::new(contract_addr, &bob_wallet); @@ -72,7 +74,7 @@ pub async fn bench() -> eyre::Result { Ok(report) } -async fn deploy(account: &Account) -> Address { +async fn deploy(account: &Account) -> eyre::Result
{ let args = AccessControl::constructorCall {}; let args = alloy::hex::encode(args.abi_encode()); crate::deploy(account, "access-control", Some(args)).await diff --git a/benches/src/erc20.rs b/benches/src/erc20.rs index 6ea1fba5..dddb7807 100644 --- a/benches/src/erc20.rs +++ b/benches/src/erc20.rs @@ -1,5 +1,3 @@ -use std::process::Command; - use alloy::{ network::{AnyNetwork, EthereumWallet}, primitives::Address, @@ -10,7 +8,6 @@ use alloy::{ }; use alloy_primitives::U256; use e2e::{receipt, Account}; -use eyre::{bail, Context}; use crate::report::Report; @@ -59,23 +56,12 @@ pub async fn bench() -> eyre::Result { .wallet(EthereumWallet::from(bob.signer.clone())) .on_http(bob.url().parse()?); - let contract_addr = deploy(&alice).await; + let contract_addr = deploy(&alice).await?; + crate::cache_contract(&alice, contract_addr)?; + let contract = Erc20::new(contract_addr, &alice_wallet); let contract_bob = Erc20::new(contract_addr, &bob_wallet); - // Add contract to cache manager. We leave the bid unspecified, since we - // should have plenty of room to fill the cache (can hold ~4k contracts). - let status = Command::new("cargo-stylus") - .arg("cache") - .args(&["-e", &std::env::var("RPC_URL")?]) - .args(&["--private-key", &format!("0x{}", alice.pk())]) - .args(&["--address", &format!("{}", contract_addr)]) - .status() - .with_context(|| "`cargo stylus cache` failed:")?; - if !status.success() { - bail!("Failed to cache Erc20 contract"); - } - // IMPORTANT: Order matters! use Erc20::*; #[rustfmt::skip] @@ -100,7 +86,7 @@ pub async fn bench() -> eyre::Result { Ok(report) } -async fn deploy(account: &Account) -> Address { +async fn deploy(account: &Account) -> eyre::Result
{ let args = Erc20Example::constructorCall { name_: TOKEN_NAME.to_owned(), symbol_: TOKEN_SYMBOL.to_owned(), diff --git a/benches/src/erc721.rs b/benches/src/erc721.rs index b5dc4bb2..86a488aa 100644 --- a/benches/src/erc721.rs +++ b/benches/src/erc721.rs @@ -41,7 +41,9 @@ pub async fn bench() -> eyre::Result { let bob = Account::new().await?; let bob_addr = bob.address(); - let contract_addr = deploy(&alice).await; + let contract_addr = deploy(&alice).await?; + crate::cache_contract(&alice, contract_addr)?; + let contract = Erc721::new(contract_addr, &alice_wallet); let token_1 = uint!(1_U256); @@ -75,7 +77,7 @@ pub async fn bench() -> eyre::Result { Ok(report) } -async fn deploy(account: &Account) -> Address { +async fn deploy(account: &Account) -> eyre::Result
{ let args = Erc721Example::constructorCall {}; let args = alloy::hex::encode(args.abi_encode()); crate::deploy(account, "erc721", Some(args)).await diff --git a/benches/src/lib.rs b/benches/src/lib.rs index 85a37ca0..ee092aba 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -1,3 +1,5 @@ +use std::process::Command; + use alloy::{ primitives::Address, rpc::types::{ @@ -7,6 +9,7 @@ use alloy::{ }; use alloy_primitives::U128; use e2e::{Account, ReceiptExt}; +use eyre::WrapErr; use koba::config::{Deploy, Generate, PrivateKey}; use serde::Deserialize; @@ -16,8 +19,6 @@ pub mod erc721; pub mod merkle_proofs; pub mod report; -const RPC_URL: &str = "http://localhost:8547"; - #[derive(Debug, Deserialize)] struct ArbOtherFields { #[serde(rename = "gasUsedForL1")] @@ -41,9 +42,9 @@ async fn deploy( account: &Account, contract_name: &str, args: Option, -) -> Address { +) -> eyre::Result
{ let manifest_dir = - std::env::current_dir().expect("should get current dir from env"); + std::env::current_dir().context("should get current dir from env")?; let wasm_path = manifest_dir .join("target") @@ -53,7 +54,7 @@ async fn deploy( let sol_path = args.as_ref().map(|_| { manifest_dir .join("examples") - .join(format!("{}", contract_name)) + .join(contract_name) .join("src") .join("constructor.sol") }); @@ -72,14 +73,36 @@ async fn deploy( keystore_path: None, keystore_password_path: None, }, - endpoint: RPC_URL.to_owned(), + endpoint: env("RPC_URL")?, deploy_only: false, quiet: true, }; - koba::deploy(&config) - .await - .expect("should deploy contract") - .address() - .expect("should return contract address") + koba::deploy(&config).await.expect("should deploy contract").address() +} + +/// Try to cache a contract on the stylus network. +/// Already cached contracts won't be cached, and this function will not return +/// an error. +/// Output will be forwarded to the child process. +fn cache_contract( + account: &Account, + contract_addr: Address, +) -> eyre::Result<()> { + // We don't need a status code. + // Since it is not zero when the contract is already cached. + let _ = Command::new("cargo") + .args(["stylus", "cache", "bid"]) + .args(["-e", &env("RPC_URL")?]) + .args(["--private-key", &format!("0x{}", account.pk())]) + .arg(contract_addr.to_string()) + .arg("0") + .status() + .context("failed to execute `cargo stylus cache bid` command")?; + Ok(()) +} + +/// Load the `name` environment variable. +fn env(name: &str) -> eyre::Result { + std::env::var(name).wrap_err(format!("failed to load {name}")) } diff --git a/benches/src/main.rs b/benches/src/main.rs index cd4e0e38..c5970f24 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -13,6 +13,7 @@ async fn main() -> eyre::Result<()> { let report = reports.into_iter().fold(Reports::default(), Reports::merge_with); + println!(); println!("{report}"); Ok(()) diff --git a/benches/src/merkle_proofs.rs b/benches/src/merkle_proofs.rs index 92a7d55e..7a24630e 100644 --- a/benches/src/merkle_proofs.rs +++ b/benches/src/merkle_proofs.rs @@ -65,7 +65,9 @@ pub async fn bench() -> eyre::Result { .wallet(EthereumWallet::from(alice.signer.clone())) .on_http(alice.url().parse()?); - let contract_addr = deploy(&alice).await; + let contract_addr = deploy(&alice).await?; + crate::cache_contract(&alice, contract_addr)?; + let contract = Verifier::new(contract_addr, &alice_wallet); let proof = PROOF.map(|h| h.into()).to_vec(); @@ -81,6 +83,6 @@ pub async fn bench() -> eyre::Result { Ok(report) } -async fn deploy(account: &Account) -> Address { +async fn deploy(account: &Account) -> eyre::Result
{ crate::deploy(account, "merkle-proofs", None).await } diff --git a/lib/e2e/src/account.rs b/lib/e2e/src/account.rs index 5e0e9559..4d09da9d 100644 --- a/lib/e2e/src/account.rs +++ b/lib/e2e/src/account.rs @@ -13,6 +13,8 @@ use crate::{ system::{fund_account, Wallet, RPC_URL_ENV_VAR_NAME}, }; +const DEFAULT_FUNDING_ETH: u32 = 100; + /// Type that corresponds to a test account. #[derive(Clone, Debug)] pub struct Account { @@ -23,7 +25,7 @@ pub struct Account { } impl Account { - /// Create a new account. + /// Create a new account with default funding of 100 ETH. /// /// # Errors /// @@ -103,7 +105,7 @@ impl AccountFactory { let signer = PrivateKeySigner::random(); let addr = signer.address(); - fund_account(addr, "100")?; + fund_account(addr, DEFAULT_FUNDING_ETH)?; let rpc_url = std::env::var(RPC_URL_ENV_VAR_NAME) .expect("failed to load RPC_URL var from env") diff --git a/lib/e2e/src/system.rs b/lib/e2e/src/system.rs index aef9ac3e..5d90cd8b 100644 --- a/lib/e2e/src/system.rs +++ b/lib/e2e/src/system.rs @@ -61,7 +61,7 @@ pub fn provider() -> Provider { } /// Send `amount` eth to `address` in the nitro-tesnode. -pub fn fund_account(address: Address, amount: &str) -> eyre::Result<()> { +pub fn fund_account(address: Address, amount: u32) -> eyre::Result<()> { let node_script = get_node_path()?.join("test-node.bash"); if !node_script.exists() { bail!("Test nitro node wasn't setup properly. Try to setup it first with `./scripts/nitro-testnode.sh -i -d`") @@ -73,7 +73,7 @@ pub fn fund_account(address: Address, amount: &str) -> eyre::Result<()> { .arg("--to") .arg(format!("address_{address}")) .arg("--ethamount") - .arg(amount) + .arg(amount.to_string()) .output()?; if !output.status.success() { diff --git a/scripts/bench.sh b/scripts/bench.sh index 8878daec..235eb6c6 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -5,9 +5,14 @@ MYDIR=$(realpath "$(dirname "$0")") cd "$MYDIR" cd .. -NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly} +NIGHTLY_TOOLCHAIN=${NIGHTLY_TOOLCHAIN:-nightly-2024-01-01} cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort export RPC_URL=http://localhost:8547 -cargo run --release -p benches + +# No need to run benchmark infrastructure with release. +# Just compilation will take longer. +# Contracts already built with release. +cargo run -p benches + echo "Finished running benches!" diff --git a/scripts/nitro-testnode.sh b/scripts/nitro-testnode.sh index 815323c2..2bf1fdd4 100755 --- a/scripts/nitro-testnode.sh +++ b/scripts/nitro-testnode.sh @@ -18,7 +18,14 @@ do shift ;; -q|--quit) - docker container stop $(docker container ls -q --filter name=nitro-testnode) + NITRO_CONTAINERS=$(docker container ls -q --filter name=nitro-testnode) + + if [ -z "$NITRO_CONTAINERS" ]; then + echo "No running nitro test node containers" + else + docker container stop $NITRO_CONTAINERS || exit + fi + exit 0 ;; *) @@ -41,10 +48,10 @@ then cd "$MYDIR" || exit cd .. - git clone --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git + git clone -b release --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git cd ./nitro-testnode || exit - # `release` branch. - git checkout 8cb6b84e31909157d431e7e4af9fb83799443e00 || exit + git pull origin release --recurse-submodules + git checkout 43861b001713db3526b74e905e383d41e9272760 || exit ./test-node.bash --no-run --init || exit fi From e2015ab081e6b5f0b84988f91a0a03a543d9fc58 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:31:56 +0400 Subject: [PATCH 04/10] Update scripts/nitro-testnode.sh Co-authored-by: alexfertel --- scripts/nitro-testnode.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nitro-testnode.sh b/scripts/nitro-testnode.sh index 2bf1fdd4..43eb6788 100755 --- a/scripts/nitro-testnode.sh +++ b/scripts/nitro-testnode.sh @@ -21,7 +21,7 @@ do NITRO_CONTAINERS=$(docker container ls -q --filter name=nitro-testnode) if [ -z "$NITRO_CONTAINERS" ]; then - echo "No running nitro test node containers" + echo "No nitro-testnode containers running" else docker container stop $NITRO_CONTAINERS || exit fi From 073f654c395e8b9e7115e2ae5a8c2edfb715a959 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 23 Sep 2024 15:37:46 +0400 Subject: [PATCH 05/10] ++ --- lib/e2e/src/account.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/e2e/src/account.rs b/lib/e2e/src/account.rs index 4d09da9d..83d4cd26 100644 --- a/lib/e2e/src/account.rs +++ b/lib/e2e/src/account.rs @@ -25,7 +25,7 @@ pub struct Account { } impl Account { - /// Create a new account with default funding of 100 ETH. + /// Create a new account with a default funding of [`DEFAULT_FUNDING_ETH`]. /// /// # Errors /// From 93f33ebeb36aaeef2613c3faa4a2a4ec7ca78495 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 23 Sep 2024 15:38:37 +0400 Subject: [PATCH 06/10] ++ --- scripts/bench.sh | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/bench.sh b/scripts/bench.sh index 235eb6c6..e6a1a24a 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -10,9 +10,8 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 -# No need to run benchmark infrastructure with release. -# Just compilation will take longer. -# Contracts already built with release. +# No need to compile benchmarks with `--release` +# since gas measurement happens on the contract side cargo run -p benches echo "Finished running benches!" From 886b26beee8a8af8533c02f23abf9b79a987a928 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 25 Sep 2024 20:01:32 +0400 Subject: [PATCH 07/10] add non cached contract gas usage as a separate column --- benches/src/access_control.rs | 38 ++++++--- benches/src/erc20.rs | 40 +++++++--- benches/src/erc721.rs | 40 +++++++--- benches/src/lib.rs | 29 ++++--- benches/src/main.rs | 9 ++- benches/src/merkle_proofs.rs | 39 ++++++--- benches/src/report.rs | 145 ++++++++++++++++++++++++++++------ scripts/bench.sh | 3 + 8 files changed, 269 insertions(+), 74 deletions(-) diff --git a/benches/src/access_control.rs b/benches/src/access_control.rs index da754780..12ccfaa8 100644 --- a/benches/src/access_control.rs +++ b/benches/src/access_control.rs @@ -8,7 +8,10 @@ use alloy::{ }; use e2e::{receipt, Account}; -use crate::report::Report; +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; sol!( #[sol(rpc)] @@ -33,7 +36,22 @@ const ROLE: [u8; 32] = const NEW_ADMIN_ROLE: [u8; 32] = hex!("879ce0d4bfd332649ca3552efe772a38d64a315eb70ab69689fd309c735946b5"); -pub async fn bench() -> eyre::Result { +pub async fn bench() -> eyre::Result { + let receipts = run_with(CacheOpt::None).await?; + let report = receipts + .into_iter() + .try_fold(ContractReport::new("AccessControl"), ContractReport::add)?; + + let cached_receipts = run_with(CacheOpt::Bid(0)).await?; + let report = cached_receipts + .into_iter() + .try_fold(report, ContractReport::add_cached)?; + + Ok(report) +} +pub async fn run_with( + cache_opt: CacheOpt, +) -> eyre::Result> { let alice = Account::new().await?; let alice_addr = alice.address(); let alice_wallet = ProviderBuilder::new() @@ -50,8 +68,7 @@ pub async fn bench() -> eyre::Result { .wallet(EthereumWallet::from(bob.signer.clone())) .on_http(bob.url().parse()?); - let contract_addr = deploy(&alice).await?; - crate::cache_contract(&alice, contract_addr)?; + let contract_addr = deploy(&alice, cache_opt).await?; let contract = AccessControl::new(contract_addr, &alice_wallet); let contract_bob = AccessControl::new(contract_addr, &bob_wallet); @@ -68,14 +85,17 @@ pub async fn bench() -> eyre::Result { (setRoleAdminCall::SIGNATURE, receipt!(contract.setRoleAdmin(ROLE.into(), NEW_ADMIN_ROLE.into()))?), ]; - let report = receipts + receipts .into_iter() - .try_fold(Report::new("AccessControl"), Report::add)?; - Ok(report) + .map(FunctionReport::new) + .collect::>>() } -async fn deploy(account: &Account) -> eyre::Result
{ +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ let args = AccessControl::constructorCall {}; let args = alloy::hex::encode(args.abi_encode()); - crate::deploy(account, "access-control", Some(args)).await + crate::deploy(account, "access-control", Some(args), cache_opt).await } diff --git a/benches/src/erc20.rs b/benches/src/erc20.rs index dddb7807..bcf63435 100644 --- a/benches/src/erc20.rs +++ b/benches/src/erc20.rs @@ -9,7 +9,10 @@ use alloy::{ use alloy_primitives::U256; use e2e::{receipt, Account}; -use crate::report::Report; +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; sol!( #[sol(rpc)] @@ -39,7 +42,23 @@ const TOKEN_NAME: &str = "Test Token"; const TOKEN_SYMBOL: &str = "TTK"; const CAP: U256 = uint!(1_000_000_U256); -pub async fn bench() -> eyre::Result { +pub async fn bench() -> eyre::Result { + let reports = run_with(CacheOpt::None).await?; + let report = reports + .into_iter() + .try_fold(ContractReport::new("Erc20"), ContractReport::add)?; + + let cached_reports = run_with(CacheOpt::Bid(0)).await?; + let report = cached_reports + .into_iter() + .try_fold(report, ContractReport::add_cached)?; + + Ok(report) +} + +pub async fn run_with( + cache_opt: CacheOpt, +) -> eyre::Result> { let alice = Account::new().await?; let alice_addr = alice.address(); let alice_wallet = ProviderBuilder::new() @@ -56,8 +75,7 @@ pub async fn bench() -> eyre::Result { .wallet(EthereumWallet::from(bob.signer.clone())) .on_http(bob.url().parse()?); - let contract_addr = deploy(&alice).await?; - crate::cache_contract(&alice, contract_addr)?; + let contract_addr = deploy(&alice, cache_opt).await?; let contract = Erc20::new(contract_addr, &alice_wallet); let contract_bob = Erc20::new(contract_addr, &bob_wallet); @@ -81,17 +99,21 @@ pub async fn bench() -> eyre::Result { (transferFromCall::SIGNATURE, receipt!(contract_bob.transferFrom(alice_addr, bob_addr, uint!(4_U256)))?), ]; - let report = - receipts.into_iter().try_fold(Report::new("Erc20"), Report::add)?; - Ok(report) + receipts + .into_iter() + .map(FunctionReport::new) + .collect::>>() } -async fn deploy(account: &Account) -> eyre::Result
{ +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ let args = Erc20Example::constructorCall { name_: TOKEN_NAME.to_owned(), symbol_: TOKEN_SYMBOL.to_owned(), cap_: CAP, }; let args = alloy::hex::encode(args.abi_encode()); - crate::deploy(account, "erc20", Some(args)).await + crate::deploy(account, "erc20", Some(args), cache_opt).await } diff --git a/benches/src/erc721.rs b/benches/src/erc721.rs index 86a488aa..b00729a1 100644 --- a/benches/src/erc721.rs +++ b/benches/src/erc721.rs @@ -8,7 +8,10 @@ use alloy::{ }; use e2e::{receipt, Account}; -use crate::report::Report; +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; sol!( #[sol(rpc)] @@ -29,7 +32,23 @@ sol!( sol!("../examples/erc721/src/constructor.sol"); -pub async fn bench() -> eyre::Result { +pub async fn bench() -> eyre::Result { + let reports = run_with(CacheOpt::None).await?; + let report = reports + .into_iter() + .try_fold(ContractReport::new("Erc721"), ContractReport::add)?; + + let cached_reports = run_with(CacheOpt::Bid(0)).await?; + let report = cached_reports + .into_iter() + .try_fold(report, ContractReport::add_cached)?; + + Ok(report) +} + +pub async fn run_with( + cache_opt: CacheOpt, +) -> eyre::Result> { let alice = Account::new().await?; let alice_addr = alice.address(); let alice_wallet = ProviderBuilder::new() @@ -41,8 +60,7 @@ pub async fn bench() -> eyre::Result { let bob = Account::new().await?; let bob_addr = bob.address(); - let contract_addr = deploy(&alice).await?; - crate::cache_contract(&alice, contract_addr)?; + let contract_addr = deploy(&alice, cache_opt).await?; let contract = Erc721::new(contract_addr, &alice_wallet); @@ -72,13 +90,17 @@ pub async fn bench() -> eyre::Result { (burnCall::SIGNATURE, receipt!(contract.burn(token_1))?), ]; - let report = - receipts.into_iter().try_fold(Report::new("Erc721"), Report::add)?; - Ok(report) + receipts + .into_iter() + .map(FunctionReport::new) + .collect::>>() } -async fn deploy(account: &Account) -> eyre::Result
{ +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ let args = Erc721Example::constructorCall {}; let args = alloy::hex::encode(args.abi_encode()); - crate::deploy(account, "erc721", Some(args)).await + crate::deploy(account, "erc721", Some(args), cache_opt).await } diff --git a/benches/src/lib.rs b/benches/src/lib.rs index ee092aba..c884bcdb 100644 --- a/benches/src/lib.rs +++ b/benches/src/lib.rs @@ -28,20 +28,21 @@ struct ArbOtherFields { l1_block_number: String, } +/// Cache options for the contract. +/// `Bid(0)` will likely cache the contract on the nitro test node. +pub enum CacheOpt { + None, + Bid(u32), +} + type ArbTxReceipt = WithOtherFields>>; -fn get_l2_gas_used(receipt: &ArbTxReceipt) -> eyre::Result { - let l2_gas = receipt.gas_used; - let arb_fields: ArbOtherFields = receipt.other.deserialize_as()?; - let l1_gas = arb_fields.gas_used_for_l1.to::(); - Ok(l2_gas - l1_gas) -} - async fn deploy( account: &Account, contract_name: &str, args: Option, + cache_opt: CacheOpt, ) -> eyre::Result
{ let manifest_dir = std::env::current_dir().context("should get current dir from env")?; @@ -78,7 +79,16 @@ async fn deploy( quiet: true, }; - koba::deploy(&config).await.expect("should deploy contract").address() + let address = koba::deploy(&config) + .await + .expect("should deploy contract") + .address()?; + + if let CacheOpt::Bid(bid) = cache_opt { + cache_contract(account, address, bid)?; + } + + Ok(address) } /// Try to cache a contract on the stylus network. @@ -88,6 +98,7 @@ async fn deploy( fn cache_contract( account: &Account, contract_addr: Address, + bid: u32, ) -> eyre::Result<()> { // We don't need a status code. // Since it is not zero when the contract is already cached. @@ -96,7 +107,7 @@ fn cache_contract( .args(["-e", &env("RPC_URL")?]) .args(["--private-key", &format!("0x{}", account.pk())]) .arg(contract_addr.to_string()) - .arg("0") + .arg(bid.to_string()) .status() .context("failed to execute `cargo stylus cache bid` command")?; Ok(()) diff --git a/benches/src/main.rs b/benches/src/main.rs index c5970f24..d940ff1e 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -1,4 +1,6 @@ -use benches::{access_control, erc20, erc721, merkle_proofs, report::Reports}; +use benches::{ + access_control, erc20, erc721, merkle_proofs, report::BenchmarkReport, +}; #[tokio::main] async fn main() -> eyre::Result<()> { @@ -10,8 +12,9 @@ async fn main() -> eyre::Result<()> { ); let reports = [reports.0?, reports.1?, reports.2?, reports.3?]; - let report = - reports.into_iter().fold(Reports::default(), Reports::merge_with); + let report = reports + .into_iter() + .fold(BenchmarkReport::default(), BenchmarkReport::merge_with); println!(); println!("{report}"); diff --git a/benches/src/merkle_proofs.rs b/benches/src/merkle_proofs.rs index 7a24630e..fd986520 100644 --- a/benches/src/merkle_proofs.rs +++ b/benches/src/merkle_proofs.rs @@ -8,7 +8,10 @@ use alloy::{ }; use e2e::{receipt, Account}; -use crate::report::Report; +use crate::{ + report::{ContractReport, FunctionReport}, + CacheOpt, +}; sol!( #[sol(rpc)] @@ -57,7 +60,23 @@ const PROOF: [[u8; 32]; 16] = bytes_array! { "fd47b6c292f51911e8dfdc3e4f8bd127773b17f25b7a554beaa8741e99c41208", }; -pub async fn bench() -> eyre::Result { +pub async fn bench() -> eyre::Result { + let reports = run_with(CacheOpt::None).await?; + let report = reports + .into_iter() + .try_fold(ContractReport::new("MerkleProofs"), ContractReport::add)?; + + let cached_reports = run_with(CacheOpt::Bid(0)).await?; + let report = cached_reports + .into_iter() + .try_fold(report, ContractReport::add_cached)?; + + Ok(report) +} + +pub async fn run_with( + cache_opt: CacheOpt, +) -> eyre::Result> { let alice = Account::new().await?; let alice_wallet = ProviderBuilder::new() .network::() @@ -65,8 +84,7 @@ pub async fn bench() -> eyre::Result { .wallet(EthereumWallet::from(alice.signer.clone())) .on_http(alice.url().parse()?); - let contract_addr = deploy(&alice).await?; - crate::cache_contract(&alice, contract_addr)?; + let contract_addr = deploy(&alice, cache_opt).await?; let contract = Verifier::new(contract_addr, &alice_wallet); @@ -77,12 +95,15 @@ pub async fn bench() -> eyre::Result { receipt!(contract.verify(proof, ROOT.into(), LEAF.into()))?, )]; - let report = receipts + receipts .into_iter() - .try_fold(Report::new("MerkleProofs"), Report::add)?; - Ok(report) + .map(FunctionReport::new) + .collect::>>() } -async fn deploy(account: &Account) -> eyre::Result
{ - crate::deploy(account, "merkle-proofs", None).await +async fn deploy( + account: &Account, + cache_opt: CacheOpt, +) -> eyre::Result
{ + crate::deploy(account, "merkle-proofs", None, cache_opt).await } diff --git a/benches/src/report.rs b/benches/src/report.rs index 3a1184b2..49b5fe70 100644 --- a/benches/src/report.rs +++ b/benches/src/report.rs @@ -1,64 +1,157 @@ -use std::fmt::Display; +use std::{collections::HashMap, fmt::Display}; -use crate::{get_l2_gas_used, ArbTxReceipt}; +use crate::{ArbOtherFields, ArbTxReceipt}; const SEPARATOR: &str = "::"; #[derive(Debug)] -pub struct Report { +pub struct FunctionReport { + sig: String, + gas: u128, +} + +impl FunctionReport { + pub(crate) fn new(receipt: (&str, ArbTxReceipt)) -> eyre::Result { + Ok(FunctionReport { + sig: receipt.0.to_owned(), + gas: get_l2_gas_used(&receipt.1)?, + }) + } +} + +#[derive(Debug)] +pub struct ContractReport { contract: String, - fns: Vec<(String, u128)>, + functions: Vec, + functions_cached: Vec, } -impl Report { +impl ContractReport { pub fn new(contract: &str) -> Self { - Report { contract: contract.to_owned(), fns: vec![] } + ContractReport { + contract: contract.to_owned(), + functions: vec![], + functions_cached: vec![], + } + } + + pub fn add(mut self, fn_report: FunctionReport) -> eyre::Result { + self.functions.push(fn_report); + Ok(self) } - pub fn add(mut self, receipt: (&str, ArbTxReceipt)) -> eyre::Result { - let gas = get_l2_gas_used(&receipt.1)?; - self.fns.push((receipt.0.to_owned(), gas)); + pub fn add_cached( + mut self, + fn_report: FunctionReport, + ) -> eyre::Result { + self.functions_cached.push(fn_report); Ok(self) } - fn get_longest_signature(&self) -> usize { + fn signature_max_len(&self) -> usize { let prefix_len = self.contract.len() + SEPARATOR.len(); - self.fns + self.functions + .iter() + .map(|FunctionReport { sig: name, .. }| prefix_len + name.len()) + .max() + .unwrap_or_default() + } + + fn gas_max_len(&self) -> usize { + self.functions + .iter() + .map(|FunctionReport { gas, .. }| gas.to_string().len()) + .max() + .unwrap_or_default() + } + + fn gas_cached_max_len(&self) -> usize { + self.functions_cached .iter() - .map(|(sig, _)| prefix_len + sig.len()) + .map(|FunctionReport { gas, .. }| gas.to_string().len()) .max() .unwrap_or_default() } } #[derive(Debug, Default)] -pub struct Reports(Vec); +pub struct BenchmarkReport(Vec); -impl Reports { - pub fn merge_with(mut self, report: Report) -> Self { +impl BenchmarkReport { + pub fn merge_with(mut self, report: ContractReport) -> Self { self.0.push(report); self } -} -impl Display for Reports { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let reports = &self.0; - let width = reports + pub fn column_width( + &self, + column_value: impl FnMut(&ContractReport) -> usize, + header: &str, + ) -> usize { + self.0 .iter() - .map(Report::get_longest_signature) + .map(column_value) + .chain(std::iter::once(header.len())) .max() - .unwrap_or_default(); + .unwrap_or_default() + } +} + +impl Display for BenchmarkReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + const HEADER_SIG: &str = "Contract::function"; + const HEADER_GAS_CACHED: &str = "Cached"; + const HEADER_GAS: &str = "Not Cached"; + + // Calculating the width of table columns. + let width1 = + self.column_width(ContractReport::signature_max_len, HEADER_SIG); + let width2 = self.column_width( + ContractReport::gas_cached_max_len, + HEADER_GAS_CACHED, + ); + let width3 = self.column_width(ContractReport::gas_max_len, HEADER_GAS); - for report in reports { + // Print headers for the table columns. + writeln!( + f, + "| {HEADER_SIG:width2$} | {HEADER_GAS:>width3$} |" + )?; + writeln!( + f, + "| {:->width1$} | {:->width2$} | {:->width3$} |", + "", "", "" + )?; + + // Merging a non-cached gas report with a cached one. + for report in &self.0 { let prefix = format!("{}{SEPARATOR}", report.contract); + let gas: HashMap<_, _> = report + .functions + .iter() + .map(|func| (&*func.sig, func.gas)) + .collect(); + + for report_cached in &report.functions_cached { + let sig = &*report_cached.sig; + let gas_cached = &report_cached.gas; + let gas = gas[sig]; - for (sig, gas) in &report.fns { - let signature = format!("{prefix}{sig}"); - writeln!(f, "{signature:10}")?; + let full_sig = format!("{prefix}{sig}"); + writeln!( + f, + "| {full_sig:width2$} | {gas:>width3$} |" + )?; } } Ok(()) } } + +fn get_l2_gas_used(receipt: &ArbTxReceipt) -> eyre::Result { + let l2_gas = receipt.gas_used; + let arb_fields: ArbOtherFields = receipt.other.deserialize_as()?; + let l1_gas = arb_fields.gas_used_for_l1.to::(); + Ok(l2_gas - l1_gas) +} diff --git a/scripts/bench.sh b/scripts/bench.sh index e6a1a24a..7932e506 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -14,4 +14,7 @@ export RPC_URL=http://localhost:8547 # since gas measurement happens on the contract side cargo run -p benches +echo "NOTE: To measure non cached contract's gas usage correctly, + benchmarks should run on a clean instance of the nitro test node." +echo echo "Finished running benches!" From a92c99bffc6fe3b4417ca9df20589855703000bf Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 26 Sep 2024 16:44:19 +0400 Subject: [PATCH 08/10] ++ --- Cargo.lock | 1 + Cargo.toml | 1 + benches/Cargo.toml | 1 + benches/src/main.rs | 21 ++++++++++----------- scripts/nitro-testnode.sh | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ebb25bb5..701c7fb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -822,6 +822,7 @@ dependencies = [ "alloy-primitives", "e2e", "eyre", + "futures", "keccak-const", "koba", "openzeppelin-stylus", diff --git a/Cargo.toml b/Cargo.toml index f196fd18..8074c36c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ rand = "0.8.5" regex = "1.10.4" tiny-keccak = { version = "2.0.2", features = ["keccak"] } tokio = { version = "1.12.0", features = ["full"] } +futures = "0.3.30" # procedural macros syn = { version = "2.0.58", features = ["full"] } diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 921de2f6..0aa975de 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -11,6 +11,7 @@ openzeppelin-stylus = { path = "../contracts" } alloy-primitives = { workspace = true, features = ["tiny-keccak"] } alloy.workspace = true tokio.workspace = true +futures.workspace = true eyre.workspace = true koba.workspace = true e2e = { path = "../lib/e2e" } diff --git a/benches/src/main.rs b/benches/src/main.rs index d940ff1e..52278d77 100644 --- a/benches/src/main.rs +++ b/benches/src/main.rs @@ -1,20 +1,19 @@ use benches::{ access_control, erc20, erc721, merkle_proofs, report::BenchmarkReport, }; +use futures::FutureExt; #[tokio::main] async fn main() -> eyre::Result<()> { - let reports = tokio::join!( - access_control::bench(), - erc20::bench(), - erc721::bench(), - merkle_proofs::bench() - ); - - let reports = [reports.0?, reports.1?, reports.2?, reports.3?]; - let report = reports - .into_iter() - .fold(BenchmarkReport::default(), BenchmarkReport::merge_with); + let report = futures::future::try_join_all([ + access_control::bench().boxed(), + erc20::bench().boxed(), + erc721::bench().boxed(), + merkle_proofs::bench().boxed(), + ]) + .await? + .into_iter() + .fold(BenchmarkReport::default(), BenchmarkReport::merge_with); println!(); println!("{report}"); diff --git a/scripts/nitro-testnode.sh b/scripts/nitro-testnode.sh index 43eb6788..1df58971 100755 --- a/scripts/nitro-testnode.sh +++ b/scripts/nitro-testnode.sh @@ -48,7 +48,7 @@ then cd "$MYDIR" || exit cd .. - git clone -b release --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git + git clone --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git cd ./nitro-testnode || exit git pull origin release --recurse-submodules git checkout 43861b001713db3526b74e905e383d41e9272760 || exit From e84a57f346db3eff51f005f4dc9992e932dd652b Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 26 Sep 2024 19:57:29 +0400 Subject: [PATCH 09/10] ++ --- scripts/nitro-testnode.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/nitro-testnode.sh b/scripts/nitro-testnode.sh index 1df58971..4e807379 100755 --- a/scripts/nitro-testnode.sh +++ b/scripts/nitro-testnode.sh @@ -51,7 +51,7 @@ then git clone --recurse-submodules https://github.com/OffchainLabs/nitro-testnode.git cd ./nitro-testnode || exit git pull origin release --recurse-submodules - git checkout 43861b001713db3526b74e905e383d41e9272760 || exit + git checkout d4244cd5c2cb56ca3d11c23478ef9642f8ebf472 || exit ./test-node.bash --no-run --init || exit fi From 9256c4a5b718c592ceec4cd2886183b3bab99a7c Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh <37006439+qalisander@users.noreply.github.com> Date: Thu, 26 Sep 2024 23:28:25 +0400 Subject: [PATCH 10/10] Update scripts/bench.sh Co-authored-by: Gustavo Gonzalez --- scripts/bench.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/bench.sh b/scripts/bench.sh index 7932e506..1801d6c3 100755 --- a/scripts/bench.sh +++ b/scripts/bench.sh @@ -11,7 +11,7 @@ cargo +"$NIGHTLY_TOOLCHAIN" build --release --target wasm32-unknown-unknown -Z b export RPC_URL=http://localhost:8547 # No need to compile benchmarks with `--release` -# since gas measurement happens on the contract side +# since this only runs the benchmarking code and the contracts have already been compiled with `--release` cargo run -p benches echo "NOTE: To measure non cached contract's gas usage correctly,