Skip to content

Commit

Permalink
feat: cache contracts before benching (#254)
Browse files Browse the repository at this point in the history
Use the `cargo stylus cache` to cache contracts in the `CacheManager`
before running benches.

---------

Co-authored-by: Alisander Qoshqosh <qalisander@gmail.com>
Co-authored-by: Gustavo Gonzalez <gustavo.gonzalez@openzeppelin.com>
  • Loading branch information
3 people authored Sep 27, 2024
1 parent 9b61526 commit 61000df
Show file tree
Hide file tree
Showing 15 changed files with 339 additions and 94 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/gas-bench.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
with:
key: "gas-bench"

- name: install cargo-stylus
run: cargo install cargo-stylus@0.5.1

- name: install solc
run: |
curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
1 change: 1 addition & 0 deletions benches/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ openzeppelin-stylus.workspace = true
alloy-primitives = { workspace = true, features = ["tiny-keccak"] }
alloy.workspace = true
tokio.workspace = true
futures.workspace = true
eyre.workspace = true
koba.workspace = true
e2e.workspace = true
Expand Down
38 changes: 30 additions & 8 deletions benches/src/access_control.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use alloy::{
};
use e2e::{receipt, Account};

use crate::report::Report;
use crate::{
report::{ContractReport, FunctionReport},
CacheOpt,
};

sol!(
#[sol(rpc)]
Expand All @@ -33,7 +36,22 @@ const ROLE: [u8; 32] =
const NEW_ADMIN_ROLE: [u8; 32] =
hex!("879ce0d4bfd332649ca3552efe772a38d64a315eb70ab69689fd309c735946b5");

pub async fn bench() -> eyre::Result<Report> {
pub async fn bench() -> eyre::Result<ContractReport> {
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<Vec<FunctionReport>> {
let alice = Account::new().await?;
let alice_addr = alice.address();
let alice_wallet = ProviderBuilder::new()
Expand All @@ -50,7 +68,8 @@ pub async fn bench() -> eyre::Result<Report> {
.wallet(EthereumWallet::from(bob.signer.clone()))
.on_http(bob.url().parse()?);

let contract_addr = deploy(&alice).await;
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);

Expand All @@ -66,14 +85,17 @@ pub async fn bench() -> eyre::Result<Report> {
(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::<eyre::Result<Vec<_>>>()
}

async fn deploy(account: &Account) -> Address {
async fn deploy(
account: &Account,
cache_opt: CacheOpt,
) -> eyre::Result<Address> {
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
}
40 changes: 32 additions & 8 deletions benches/src/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<Report> {
pub async fn bench() -> eyre::Result<ContractReport> {
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<Vec<FunctionReport>> {
let alice = Account::new().await?;
let alice_addr = alice.address();
let alice_wallet = ProviderBuilder::new()
Expand All @@ -56,7 +75,8 @@ pub async fn bench() -> eyre::Result<Report> {
.wallet(EthereumWallet::from(bob.signer.clone()))
.on_http(bob.url().parse()?);

let contract_addr = deploy(&alice).await;
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);

Expand All @@ -79,17 +99,21 @@ pub async fn bench() -> eyre::Result<Report> {
(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::<eyre::Result<Vec<_>>>()
}

async fn deploy(account: &Account) -> Address {
async fn deploy(
account: &Account,
cache_opt: CacheOpt,
) -> eyre::Result<Address> {
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
}
40 changes: 32 additions & 8 deletions benches/src/erc721.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use alloy::{
};
use e2e::{receipt, Account};

use crate::report::Report;
use crate::{
report::{ContractReport, FunctionReport},
CacheOpt,
};

sol!(
#[sol(rpc)]
Expand All @@ -29,7 +32,23 @@ sol!(

sol!("../examples/erc721/src/constructor.sol");

pub async fn bench() -> eyre::Result<Report> {
pub async fn bench() -> eyre::Result<ContractReport> {
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<Vec<FunctionReport>> {
let alice = Account::new().await?;
let alice_addr = alice.address();
let alice_wallet = ProviderBuilder::new()
Expand All @@ -41,7 +60,8 @@ pub async fn bench() -> eyre::Result<Report> {
let bob = Account::new().await?;
let bob_addr = bob.address();

let contract_addr = deploy(&alice).await;
let contract_addr = deploy(&alice, cache_opt).await?;

let contract = Erc721::new(contract_addr, &alice_wallet);

let token_1 = uint!(1_U256);
Expand Down Expand Up @@ -70,13 +90,17 @@ pub async fn bench() -> eyre::Result<Report> {
(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::<eyre::Result<Vec<_>>>()
}

async fn deploy(account: &Account) -> Address {
async fn deploy(
account: &Account,
cache_opt: CacheOpt,
) -> eyre::Result<Address> {
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
}
66 changes: 50 additions & 16 deletions benches/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::process::Command;

use alloy::{
primitives::Address,
rpc::types::{
Expand All @@ -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;

Expand All @@ -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")]
Expand All @@ -27,23 +28,24 @@ 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<TransactionReceipt<AnyReceiptEnvelope<Log>>>;

fn get_l2_gas_used(receipt: &ArbTxReceipt) -> eyre::Result<u128> {
let l2_gas = receipt.gas_used;
let arb_fields: ArbOtherFields = receipt.other.deserialize_as()?;
let l1_gas = arb_fields.gas_used_for_l1.to::<u128>();
Ok(l2_gas - l1_gas)
}

async fn deploy(
account: &Account,
contract_name: &str,
args: Option<String>,
) -> Address {
cache_opt: CacheOpt,
) -> eyre::Result<Address> {
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")
Expand All @@ -53,7 +55,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")
});
Expand All @@ -72,14 +74,46 @@ 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)
let address = koba::deploy(&config)
.await
.expect("should deploy contract")
.address()
.expect("should return contract address")
.address()?;

if let CacheOpt::Bid(bid) = cache_opt {
cache_contract(account, address, bid)?;
}

Ok(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,
bid: u32,
) -> 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(bid.to_string())
.status()
.context("failed to execute `cargo stylus cache bid` command")?;
Ok(())
}

/// Load the `name` environment variable.
fn env(name: &str) -> eyre::Result<String> {
std::env::var(name).wrap_err(format!("failed to load {name}"))
}
25 changes: 14 additions & 11 deletions benches/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
use benches::{access_control, erc20, erc721, merkle_proofs, report::Reports};
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(Reports::default(), Reports::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}");

Ok(())
Expand Down
Loading

0 comments on commit 61000df

Please sign in to comment.