Skip to content

Commit 4a216e9

Browse files
Jrigadaelfedynbaztec
authored
fix: Respect priority fee and max fee per gas for broadcasting txs with scripts (#841)
* Pass down priority and max fee per gas for broadcasting script txs * Use EIP1559 parameters only in zksync execution and fix multiplier * Add test for gas estimate multiplier * feat: Add zk gas per pubdata flag (#845) * Add zk gas per pubdata flag * Update crates/cli/src/opts/build/zksync.rs Co-authored-by: Federico Rodríguez <[email protected]> * simplify approach * Cargo fmt * Put back new line * Update crates/script/src/lib.rs Co-authored-by: Federico Rodríguez <[email protected]> --------- Co-authored-by: Federico Rodríguez <[email protected]> * Fix variable name --------- Co-authored-by: Federico Rodríguez <[email protected]> Co-authored-by: Nisheeth Barthwal <[email protected]>
1 parent 7630fbd commit 4a216e9

File tree

8 files changed

+199
-9
lines changed

8 files changed

+199
-9
lines changed

crates/cast/bin/cmd/send.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ async fn cast_send_zk<P: Provider<T, AnyNetwork>, Z: ZksyncProvider<T>, T: Trans
286286
zk_tx.set_paymaster_params(paymaster_params);
287287
}
288288

289-
foundry_zksync_core::estimate_gas(&mut zk_tx, &zk_provider).await?;
289+
foundry_zksync_core::estimate_fee(&mut zk_tx, &zk_provider, 130, None).await?;
290290

291291
let cast = ZkCast::new(zk_provider, Cast::new(provider));
292292
let pending_tx = cast.send_zk(zk_tx).await?;

crates/forge/bin/cmd/create.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,7 @@ impl CreateArgs {
692692
deployer.tx.set_gas_price(gas_price);
693693

694694
// estimate fee
695-
foundry_zksync_core::estimate_gas(&mut deployer.tx, &provider).await?;
695+
foundry_zksync_core::estimate_fee(&mut deployer.tx, &provider, 130, None).await?;
696696

697697
if !is_legacy {
698698
let estimate = provider.estimate_eip1559_fees(None).await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import {Script} from "forge-std/Script.sol";
5+
import {Greeter} from "src/Greeter.sol";
6+
7+
contract GasScript is Script {
8+
function run() public {
9+
vm.startBroadcast();
10+
Greeter greeter = new Greeter();
11+
vm.stopBroadcast();
12+
}
13+
}

crates/forge/tests/it/zk/gas.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
use foundry_test_utils::{forgetest_async, util, TestProject};
2+
3+
use foundry_test_utils::{util::OutputExt, ZkSyncNode};
4+
5+
forgetest_async!(zk_script_execution_with_gas_price_specified_by_user, |prj, cmd| {
6+
// Setup
7+
setup_gas_prj(&mut prj);
8+
let node = ZkSyncNode::start().await;
9+
let url = node.url();
10+
cmd.forge_fuse();
11+
let private_key = get_rich_wallet_key();
12+
13+
// Create script args with gas price parameters
14+
let script_args =
15+
create_script_args(&private_key, url.as_str(), "--with-gas-price", "370000037");
16+
let mut script_args = script_args.into_iter().collect::<Vec<_>>();
17+
script_args.extend_from_slice(&["--priority-gas-price", "123123"]);
18+
19+
// Execute script and verify success
20+
cmd.arg("script").args(&script_args);
21+
let stdout = cmd.assert_success().get_output().stdout_lossy();
22+
assert!(stdout.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL"));
23+
24+
// Verify transaction details from broadcast artifacts
25+
let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast").as_path())
26+
.find(|file| file.ends_with("run-latest.json"))
27+
.expect("No broadcast artifacts");
28+
29+
let json: serde_json::Value =
30+
serde_json::from_str(&foundry_common::fs::read_to_string(run_latest).unwrap()).unwrap();
31+
32+
assert_eq!(json["transactions"].as_array().expect("broadcastable txs").len(), 1);
33+
34+
// Verify gas prices in transaction
35+
let transaction_hash = json["receipts"][0]["transactionHash"].as_str().unwrap();
36+
let stdout = cmd
37+
.cast_fuse()
38+
.arg("tx")
39+
.arg(transaction_hash)
40+
.arg("--rpc-url")
41+
.arg(url.as_str())
42+
.assert_success()
43+
.get_output()
44+
.stdout_lossy();
45+
46+
assert!(stdout.contains("maxFeePerGas 370000037"));
47+
assert!(stdout.contains("maxPriorityFeePerGas 123123"));
48+
});
49+
50+
forgetest_async!(zk_script_execution_with_gas_multiplier, |prj, cmd| {
51+
// Setup
52+
setup_gas_prj(&mut prj);
53+
let node = ZkSyncNode::start().await;
54+
let url = node.url();
55+
cmd.forge_fuse();
56+
let private_key = get_rich_wallet_key();
57+
58+
// Test with insufficient gas multiplier (should fail)
59+
let insufficient_multiplier_args =
60+
create_script_args(&private_key, &url, "--gas-estimate-multiplier", "1");
61+
cmd.arg("script").args(&insufficient_multiplier_args);
62+
cmd.assert_failure();
63+
cmd.forge_fuse();
64+
65+
// Test with sufficient gas multiplier (should succeed)
66+
let sufficient_multiplier_args =
67+
create_script_args(&private_key, &url, "--gas-estimate-multiplier", "100");
68+
cmd.arg("script").args(&sufficient_multiplier_args);
69+
let stdout = cmd.assert_success().get_output().stdout_lossy();
70+
assert!(stdout.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL"));
71+
});
72+
73+
forgetest_async!(zk_script_execution_with_gas_per_pubdata, |prj, cmd| {
74+
// Setup
75+
setup_gas_prj(&mut prj);
76+
let node = ZkSyncNode::start().await;
77+
let url = node.url();
78+
cmd.forge_fuse();
79+
let private_key = get_rich_wallet_key();
80+
81+
// Test with unacceptable gas per pubdata (should fail)
82+
let zero_pubdata_args = create_script_args(&private_key, &url, "--zk-gas-per-pubdata", "1");
83+
cmd.arg("script").args(&zero_pubdata_args);
84+
cmd.assert_failure();
85+
cmd.forge_fuse();
86+
87+
// Test with sufficient gas per pubdata (should succeed)
88+
let sufficient_pubdata_args =
89+
create_script_args(&private_key, &url, "--zk-gas-per-pubdata", "3000");
90+
cmd.arg("script").args(&sufficient_pubdata_args);
91+
let stdout = cmd.assert_success().get_output().stdout_lossy();
92+
assert!(stdout.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL"));
93+
});
94+
95+
fn get_rich_wallet_key() -> String {
96+
ZkSyncNode::rich_wallets()
97+
.next()
98+
.map(|(_, pk, _)| pk)
99+
.expect("No rich wallets available")
100+
.to_owned()
101+
}
102+
103+
fn create_script_args<'a>(
104+
private_key: &'a str,
105+
url: &'a str,
106+
gas_param: &'a str,
107+
gas_value: &'a str,
108+
) -> Vec<&'a str> {
109+
vec![
110+
"--zk-startup",
111+
"./script/Gas.s.sol",
112+
"--private-key",
113+
private_key,
114+
"--chain",
115+
"260",
116+
"--rpc-url",
117+
url,
118+
"--slow",
119+
"-vvvvv",
120+
"--broadcast",
121+
"--timeout",
122+
"3",
123+
gas_param,
124+
gas_value,
125+
]
126+
}
127+
128+
fn setup_gas_prj(prj: &mut TestProject) {
129+
util::initialize(prj.root());
130+
prj.add_script("Gas.s.sol", include_str!("../../fixtures/zk/Gas.s.sol")).unwrap();
131+
prj.add_source("Greeter.sol", include_str!("../../../../../testdata/zk/Greeter.sol")).unwrap();
132+
}

crates/forge/tests/it/zk/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod factory;
1010
mod factory_deps;
1111
mod fork;
1212
mod fuzz;
13+
mod gas;
1314
mod invariant;
1415
mod linking;
1516
mod logs;

crates/script/src/broadcast.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ pub async fn send_transaction(
7070
is_fixed_gas_limit: bool,
7171
estimate_via_rpc: bool,
7272
estimate_multiplier: u64,
73+
gas_per_pubdata: Option<u64>,
7374
) -> Result<TxHash> {
7475
let zk_tx_meta =
7576
if let SendTransactionKind::Raw(tx, _) | SendTransactionKind::Unlocked(tx) = &mut kind {
@@ -141,7 +142,14 @@ pub async fn send_transaction(
141142
},
142143
);
143144
}
144-
foundry_zksync_core::estimate_gas(&mut zk_tx, &zk_provider).await?;
145+
146+
foundry_zksync_core::estimate_fee(
147+
&mut zk_tx,
148+
&zk_provider,
149+
estimate_multiplier,
150+
gas_per_pubdata,
151+
)
152+
.await?;
145153

146154
let zk_signer = alloy_zksync::wallet::ZksyncWallet::new(signer.default_signer());
147155
let signed = zk_tx.build(&zk_signer).await?.encoded_2718();
@@ -317,7 +325,20 @@ impl BundledState {
317325
self.args.with_gas_price,
318326
self.args.priority_gas_price,
319327
) {
320-
(true, Some(gas_price), _) => (Some(gas_price.to()), None),
328+
(true, Some(gas_price), priority_fee) => (
329+
Some(gas_price.to()),
330+
if self.script_config.config.zksync.run_in_zk_mode() {
331+
// NOTE(zk): Zksync is marked as legacy in alloy chains but it is
332+
// compliant with EIP-1559 so we need to
333+
// pass down the user provided values
334+
priority_fee.map(|fee| Eip1559Estimation {
335+
max_fee_per_gas: gas_price.to(),
336+
max_priority_fee_per_gas: fee.to(),
337+
})
338+
} else {
339+
None
340+
},
341+
),
321342
(true, None, _) => (Some(provider.get_gas_price().await?), None),
322343
(false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => (
323344
None,
@@ -367,6 +388,15 @@ impl BundledState {
367388

368389
if let Some(gas_price) = gas_price {
369390
tx.set_gas_price(gas_price);
391+
if self.script_config.config.zksync.run_in_zk_mode() {
392+
// NOTE(zk): Also set EIP-1559 fees for zk transactions
393+
if let Some(eip1559_fees) = eip1559_fees {
394+
tx.set_max_priority_fee_per_gas(
395+
eip1559_fees.max_priority_fee_per_gas,
396+
);
397+
tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas);
398+
}
399+
}
370400
} else {
371401
let eip1559_fees = eip1559_fees.expect("was set above");
372402
tx.set_max_priority_fee_per_gas(
@@ -417,6 +447,7 @@ impl BundledState {
417447
*is_fixed_gas_limit,
418448
estimate_via_rpc,
419449
self.args.gas_estimate_multiplier,
450+
self.args.zk_gas_per_pubdata,
420451
);
421452
pending_transactions.push(fut);
422453
}

crates/script/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ pub struct ScriptArgs {
217217

218218
#[command(flatten)]
219219
pub retry: RetryArgs,
220+
221+
/// Gas per pubdata
222+
#[clap(long = "zk-gas-per-pubdata", value_name = "GAS_PER_PUBDATA")]
223+
pub zk_gas_per_pubdata: Option<u64>,
220224
}
221225

222226
impl ScriptArgs {

crates/zksync/core/src/lib.rs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,24 @@ pub struct EstimatedGas {
136136

137137
/// Estimates the gas parameters for the provided transaction.
138138
/// This will call `estimateFee` method on the rpc and set the gas parameters on the transaction.
139-
pub async fn estimate_gas<P: ZksyncProvider<T>, T: Transport + Clone>(
139+
pub async fn estimate_fee<P: ZksyncProvider<T>, T: Transport + Clone>(
140140
tx: &mut ZkTransactionRequest,
141141
provider: P,
142+
estimate_multiplier: u64,
143+
gas_per_pubdata: Option<u64>,
142144
) -> Result<()> {
143145
let fee = provider.estimate_fee(tx.clone()).await?;
144-
tx.set_gas_limit(fee.gas_limit);
145-
tx.set_max_fee_per_gas(fee.max_fee_per_gas);
146-
tx.set_max_priority_fee_per_gas(fee.max_priority_fee_per_gas);
147-
tx.set_gas_per_pubdata(fee.gas_per_pubdata_limit);
146+
tx.set_gas_limit(fee.gas_limit * estimate_multiplier / 100);
147+
// If user provided a gas price, use it for both maxFeePerGas
148+
let max_fee = tx.max_fee_per_gas().unwrap_or(fee.max_fee_per_gas);
149+
let max_priority_fee = tx.max_priority_fee_per_gas().unwrap_or(fee.max_priority_fee_per_gas);
150+
let gas_per_pubdata = match gas_per_pubdata {
151+
Some(value) => rU256::from(value),
152+
None => fee.gas_per_pubdata_limit,
153+
};
154+
tx.set_max_fee_per_gas(max_fee);
155+
tx.set_max_priority_fee_per_gas(max_priority_fee);
156+
tx.set_gas_per_pubdata(gas_per_pubdata);
148157

149158
Ok(())
150159
}

0 commit comments

Comments
 (0)