Skip to content

Commit 61000df

Browse files
alexfertelqalisanderggonzalez94
authored
feat: cache contracts before benching (#254)
Use the `cargo stylus cache` to cache contracts in the `CacheManager` before running benches. --------- Co-authored-by: Alisander Qoshqosh <[email protected]> Co-authored-by: Gustavo Gonzalez <[email protected]>
1 parent 9b61526 commit 61000df

15 files changed

+339
-94
lines changed

.github/workflows/gas-bench.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ jobs:
3131
with:
3232
key: "gas-bench"
3333

34+
- name: install cargo-stylus
35+
run: cargo install [email protected]
36+
3437
- name: install solc
3538
run: |
3639
curl -LO https://github.com/ethereum/solidity/releases/download/v0.8.24/solc-static-linux

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ rand = "0.8.5"
8989
regex = "1.10.4"
9090
tiny-keccak = { version = "2.0.2", features = ["keccak"] }
9191
tokio = { version = "1.12.0", features = ["full"] }
92+
futures = "0.3.30"
9293

9394
# procedural macros
9495
syn = { version = "2.0.58", features = ["full"] }

benches/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ openzeppelin-stylus.workspace = true
1111
alloy-primitives = { workspace = true, features = ["tiny-keccak"] }
1212
alloy.workspace = true
1313
tokio.workspace = true
14+
futures.workspace = true
1415
eyre.workspace = true
1516
koba.workspace = true
1617
e2e.workspace = true

benches/src/access_control.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use alloy::{
88
};
99
use e2e::{receipt, Account};
1010

11-
use crate::report::Report;
11+
use crate::{
12+
report::{ContractReport, FunctionReport},
13+
CacheOpt,
14+
};
1215

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

36-
pub async fn bench() -> eyre::Result<Report> {
39+
pub async fn bench() -> eyre::Result<ContractReport> {
40+
let receipts = run_with(CacheOpt::None).await?;
41+
let report = receipts
42+
.into_iter()
43+
.try_fold(ContractReport::new("AccessControl"), ContractReport::add)?;
44+
45+
let cached_receipts = run_with(CacheOpt::Bid(0)).await?;
46+
let report = cached_receipts
47+
.into_iter()
48+
.try_fold(report, ContractReport::add_cached)?;
49+
50+
Ok(report)
51+
}
52+
pub async fn run_with(
53+
cache_opt: CacheOpt,
54+
) -> eyre::Result<Vec<FunctionReport>> {
3755
let alice = Account::new().await?;
3856
let alice_addr = alice.address();
3957
let alice_wallet = ProviderBuilder::new()
@@ -50,7 +68,8 @@ pub async fn bench() -> eyre::Result<Report> {
5068
.wallet(EthereumWallet::from(bob.signer.clone()))
5169
.on_http(bob.url().parse()?);
5270

53-
let contract_addr = deploy(&alice).await;
71+
let contract_addr = deploy(&alice, cache_opt).await?;
72+
5473
let contract = AccessControl::new(contract_addr, &alice_wallet);
5574
let contract_bob = AccessControl::new(contract_addr, &bob_wallet);
5675

@@ -66,14 +85,17 @@ pub async fn bench() -> eyre::Result<Report> {
6685
(setRoleAdminCall::SIGNATURE, receipt!(contract.setRoleAdmin(ROLE.into(), NEW_ADMIN_ROLE.into()))?),
6786
];
6887

69-
let report = receipts
88+
receipts
7089
.into_iter()
71-
.try_fold(Report::new("AccessControl"), Report::add)?;
72-
Ok(report)
90+
.map(FunctionReport::new)
91+
.collect::<eyre::Result<Vec<_>>>()
7392
}
7493

75-
async fn deploy(account: &Account) -> Address {
94+
async fn deploy(
95+
account: &Account,
96+
cache_opt: CacheOpt,
97+
) -> eyre::Result<Address> {
7698
let args = AccessControl::constructorCall {};
7799
let args = alloy::hex::encode(args.abi_encode());
78-
crate::deploy(account, "access-control", Some(args)).await
100+
crate::deploy(account, "access-control", Some(args), cache_opt).await
79101
}

benches/src/erc20.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ use alloy::{
99
use alloy_primitives::U256;
1010
use e2e::{receipt, Account};
1111

12-
use crate::report::Report;
12+
use crate::{
13+
report::{ContractReport, FunctionReport},
14+
CacheOpt,
15+
};
1316

1417
sol!(
1518
#[sol(rpc)]
@@ -39,7 +42,23 @@ const TOKEN_NAME: &str = "Test Token";
3942
const TOKEN_SYMBOL: &str = "TTK";
4043
const CAP: U256 = uint!(1_000_000_U256);
4144

42-
pub async fn bench() -> eyre::Result<Report> {
45+
pub async fn bench() -> eyre::Result<ContractReport> {
46+
let reports = run_with(CacheOpt::None).await?;
47+
let report = reports
48+
.into_iter()
49+
.try_fold(ContractReport::new("Erc20"), ContractReport::add)?;
50+
51+
let cached_reports = run_with(CacheOpt::Bid(0)).await?;
52+
let report = cached_reports
53+
.into_iter()
54+
.try_fold(report, ContractReport::add_cached)?;
55+
56+
Ok(report)
57+
}
58+
59+
pub async fn run_with(
60+
cache_opt: CacheOpt,
61+
) -> eyre::Result<Vec<FunctionReport>> {
4362
let alice = Account::new().await?;
4463
let alice_addr = alice.address();
4564
let alice_wallet = ProviderBuilder::new()
@@ -56,7 +75,8 @@ pub async fn bench() -> eyre::Result<Report> {
5675
.wallet(EthereumWallet::from(bob.signer.clone()))
5776
.on_http(bob.url().parse()?);
5877

59-
let contract_addr = deploy(&alice).await;
78+
let contract_addr = deploy(&alice, cache_opt).await?;
79+
6080
let contract = Erc20::new(contract_addr, &alice_wallet);
6181
let contract_bob = Erc20::new(contract_addr, &bob_wallet);
6282

@@ -79,17 +99,21 @@ pub async fn bench() -> eyre::Result<Report> {
7999
(transferFromCall::SIGNATURE, receipt!(contract_bob.transferFrom(alice_addr, bob_addr, uint!(4_U256)))?),
80100
];
81101

82-
let report =
83-
receipts.into_iter().try_fold(Report::new("Erc20"), Report::add)?;
84-
Ok(report)
102+
receipts
103+
.into_iter()
104+
.map(FunctionReport::new)
105+
.collect::<eyre::Result<Vec<_>>>()
85106
}
86107

87-
async fn deploy(account: &Account) -> Address {
108+
async fn deploy(
109+
account: &Account,
110+
cache_opt: CacheOpt,
111+
) -> eyre::Result<Address> {
88112
let args = Erc20Example::constructorCall {
89113
name_: TOKEN_NAME.to_owned(),
90114
symbol_: TOKEN_SYMBOL.to_owned(),
91115
cap_: CAP,
92116
};
93117
let args = alloy::hex::encode(args.abi_encode());
94-
crate::deploy(account, "erc20", Some(args)).await
118+
crate::deploy(account, "erc20", Some(args), cache_opt).await
95119
}

benches/src/erc721.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use alloy::{
88
};
99
use e2e::{receipt, Account};
1010

11-
use crate::report::Report;
11+
use crate::{
12+
report::{ContractReport, FunctionReport},
13+
CacheOpt,
14+
};
1215

1316
sol!(
1417
#[sol(rpc)]
@@ -29,7 +32,23 @@ sol!(
2932

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

32-
pub async fn bench() -> eyre::Result<Report> {
35+
pub async fn bench() -> eyre::Result<ContractReport> {
36+
let reports = run_with(CacheOpt::None).await?;
37+
let report = reports
38+
.into_iter()
39+
.try_fold(ContractReport::new("Erc721"), ContractReport::add)?;
40+
41+
let cached_reports = run_with(CacheOpt::Bid(0)).await?;
42+
let report = cached_reports
43+
.into_iter()
44+
.try_fold(report, ContractReport::add_cached)?;
45+
46+
Ok(report)
47+
}
48+
49+
pub async fn run_with(
50+
cache_opt: CacheOpt,
51+
) -> eyre::Result<Vec<FunctionReport>> {
3352
let alice = Account::new().await?;
3453
let alice_addr = alice.address();
3554
let alice_wallet = ProviderBuilder::new()
@@ -41,7 +60,8 @@ pub async fn bench() -> eyre::Result<Report> {
4160
let bob = Account::new().await?;
4261
let bob_addr = bob.address();
4362

44-
let contract_addr = deploy(&alice).await;
63+
let contract_addr = deploy(&alice, cache_opt).await?;
64+
4565
let contract = Erc721::new(contract_addr, &alice_wallet);
4666

4767
let token_1 = uint!(1_U256);
@@ -70,13 +90,17 @@ pub async fn bench() -> eyre::Result<Report> {
7090
(burnCall::SIGNATURE, receipt!(contract.burn(token_1))?),
7191
];
7292

73-
let report =
74-
receipts.into_iter().try_fold(Report::new("Erc721"), Report::add)?;
75-
Ok(report)
93+
receipts
94+
.into_iter()
95+
.map(FunctionReport::new)
96+
.collect::<eyre::Result<Vec<_>>>()
7697
}
7798

78-
async fn deploy(account: &Account) -> Address {
99+
async fn deploy(
100+
account: &Account,
101+
cache_opt: CacheOpt,
102+
) -> eyre::Result<Address> {
79103
let args = Erc721Example::constructorCall {};
80104
let args = alloy::hex::encode(args.abi_encode());
81-
crate::deploy(account, "erc721", Some(args)).await
105+
crate::deploy(account, "erc721", Some(args), cache_opt).await
82106
}

benches/src/lib.rs

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::process::Command;
2+
13
use alloy::{
24
primitives::Address,
35
rpc::types::{
@@ -7,6 +9,7 @@ use alloy::{
79
};
810
use alloy_primitives::U128;
911
use e2e::{Account, ReceiptExt};
12+
use eyre::WrapErr;
1013
use koba::config::{Deploy, Generate, PrivateKey};
1114
use serde::Deserialize;
1215

@@ -16,8 +19,6 @@ pub mod erc721;
1619
pub mod merkle_proofs;
1720
pub mod report;
1821

19-
const RPC_URL: &str = "http://localhost:8547";
20-
2122
#[derive(Debug, Deserialize)]
2223
struct ArbOtherFields {
2324
#[serde(rename = "gasUsedForL1")]
@@ -27,23 +28,24 @@ struct ArbOtherFields {
2728
l1_block_number: String,
2829
}
2930

31+
/// Cache options for the contract.
32+
/// `Bid(0)` will likely cache the contract on the nitro test node.
33+
pub enum CacheOpt {
34+
None,
35+
Bid(u32),
36+
}
37+
3038
type ArbTxReceipt =
3139
WithOtherFields<TransactionReceipt<AnyReceiptEnvelope<Log>>>;
3240

33-
fn get_l2_gas_used(receipt: &ArbTxReceipt) -> eyre::Result<u128> {
34-
let l2_gas = receipt.gas_used;
35-
let arb_fields: ArbOtherFields = receipt.other.deserialize_as()?;
36-
let l1_gas = arb_fields.gas_used_for_l1.to::<u128>();
37-
Ok(l2_gas - l1_gas)
38-
}
39-
4041
async fn deploy(
4142
account: &Account,
4243
contract_name: &str,
4344
args: Option<String>,
44-
) -> Address {
45+
cache_opt: CacheOpt,
46+
) -> eyre::Result<Address> {
4547
let manifest_dir =
46-
std::env::current_dir().expect("should get current dir from env");
48+
std::env::current_dir().context("should get current dir from env")?;
4749

4850
let wasm_path = manifest_dir
4951
.join("target")
@@ -53,7 +55,7 @@ async fn deploy(
5355
let sol_path = args.as_ref().map(|_| {
5456
manifest_dir
5557
.join("examples")
56-
.join(format!("{}", contract_name))
58+
.join(contract_name)
5759
.join("src")
5860
.join("constructor.sol")
5961
});
@@ -72,14 +74,46 @@ async fn deploy(
7274
keystore_path: None,
7375
keystore_password_path: None,
7476
},
75-
endpoint: RPC_URL.to_owned(),
77+
endpoint: env("RPC_URL")?,
7678
deploy_only: false,
7779
quiet: true,
7880
};
7981

80-
koba::deploy(&config)
82+
let address = koba::deploy(&config)
8183
.await
8284
.expect("should deploy contract")
83-
.address()
84-
.expect("should return contract address")
85+
.address()?;
86+
87+
if let CacheOpt::Bid(bid) = cache_opt {
88+
cache_contract(account, address, bid)?;
89+
}
90+
91+
Ok(address)
92+
}
93+
94+
/// Try to cache a contract on the stylus network.
95+
/// Already cached contracts won't be cached, and this function will not return
96+
/// an error.
97+
/// Output will be forwarded to the child process.
98+
fn cache_contract(
99+
account: &Account,
100+
contract_addr: Address,
101+
bid: u32,
102+
) -> eyre::Result<()> {
103+
// We don't need a status code.
104+
// Since it is not zero when the contract is already cached.
105+
let _ = Command::new("cargo")
106+
.args(["stylus", "cache", "bid"])
107+
.args(["-e", &env("RPC_URL")?])
108+
.args(["--private-key", &format!("0x{}", account.pk())])
109+
.arg(contract_addr.to_string())
110+
.arg(bid.to_string())
111+
.status()
112+
.context("failed to execute `cargo stylus cache bid` command")?;
113+
Ok(())
114+
}
115+
116+
/// Load the `name` environment variable.
117+
fn env(name: &str) -> eyre::Result<String> {
118+
std::env::var(name).wrap_err(format!("failed to load {name}"))
85119
}

benches/src/main.rs

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
use benches::{access_control, erc20, erc721, merkle_proofs, report::Reports};
1+
use benches::{
2+
access_control, erc20, erc721, merkle_proofs, report::BenchmarkReport,
3+
};
4+
use futures::FutureExt;
25

36
#[tokio::main]
47
async fn main() -> eyre::Result<()> {
5-
let reports = tokio::join!(
6-
access_control::bench(),
7-
erc20::bench(),
8-
erc721::bench(),
9-
merkle_proofs::bench()
10-
);
11-
12-
let reports = [reports.0?, reports.1?, reports.2?, reports.3?];
13-
let report =
14-
reports.into_iter().fold(Reports::default(), Reports::merge_with);
8+
let report = futures::future::try_join_all([
9+
access_control::bench().boxed(),
10+
erc20::bench().boxed(),
11+
erc721::bench().boxed(),
12+
merkle_proofs::bench().boxed(),
13+
])
14+
.await?
15+
.into_iter()
16+
.fold(BenchmarkReport::default(), BenchmarkReport::merge_with);
1517

18+
println!();
1619
println!("{report}");
1720

1821
Ok(())

0 commit comments

Comments
 (0)