diff --git a/crates/cast/bin/cmd/estimate.rs b/crates/cast/bin/cmd/estimate.rs
index 78e474050..914a47ef1 100644
--- a/crates/cast/bin/cmd/estimate.rs
+++ b/crates/cast/bin/cmd/estimate.rs
@@ -2,8 +2,6 @@ use crate::tx::{CastTxBuilder, SenderKind};
use alloy_primitives::U256;
use alloy_provider::Provider;
use alloy_rpc_types::BlockId;
-use alloy_zksync::network::transaction_request::TransactionRequest as ZkTransactionRequest;
-use cast::ZkTransactionOpts;
use clap::Parser;
use eyre::Result;
use foundry_cli::{
@@ -14,6 +12,8 @@ use foundry_common::ens::NameOrAddress;
use foundry_config::Config;
use std::str::FromStr;
+mod zksync;
+
/// CLI arguments for `cast estimate`.
#[derive(Debug, Parser)]
pub struct EstimateArgs {
@@ -44,7 +44,11 @@ pub struct EstimateArgs {
/// Zksync Transaction
#[command(flatten)]
- zksync: ZkTransactionOpts,
+ zk_tx: zksync::ZkTransactionOpts,
+
+ /// Force a zksync eip-712 transaction and apply CREATE overrides
+ #[arg(long = "zksync")]
+ zk_force: bool,
}
#[derive(Debug, Parser)]
@@ -73,7 +77,9 @@ pub enum EstimateSubcommands {
impl EstimateArgs {
pub async fn run(self) -> Result<()> {
- let Self { to, mut sig, mut args, mut tx, block, eth, command, zksync } = self;
+ let Self { to, mut sig, mut args, mut tx, block, eth, command, zk_tx, zk_force } = self;
+
+ let mut zk_code = Default::default();
let config = Config::from(ð);
let provider = utils::get_provider(&config)?;
@@ -87,6 +93,12 @@ impl EstimateArgs {
value,
}) = command
{
+ if zk_tx.has_zksync_args() || zk_force {
+ // NOTE(zk): `with_code_sig_and_args` decodes the code and appends it to the input
+ // we want the raw decoded constructor input from that function so we keep the code
+ // to encode the CONTRACT_CREATOR call later
+ zk_code = code.clone();
+ }
sig = create_sig;
args = create_args;
if let Some(value) = value {
@@ -106,11 +118,8 @@ impl EstimateArgs {
.build_raw(sender)
.await?;
- let gas = if zksync.has_zksync_args() {
- let zk_provider = utils::get_provider_zksync(&config)?;
- let mut zk_tx: ZkTransactionRequest = tx.inner.clone().into();
- zksync.apply_to_tx(&mut zk_tx);
- zk_provider.estimate_gas(&zk_tx).await?
+ let gas = if zk_tx.has_zksync_args() || zk_force {
+ zksync::estimate_gas(zk_tx, &config, tx, zk_code).await?
} else {
provider.estimate_gas(&tx).block(block.unwrap_or_default()).await?
};
diff --git a/crates/cast/bin/cmd/estimate/zksync.rs b/crates/cast/bin/cmd/estimate/zksync.rs
new file mode 100644
index 000000000..3fdc0f981
--- /dev/null
+++ b/crates/cast/bin/cmd/estimate/zksync.rs
@@ -0,0 +1,92 @@
+use alloy_network::TransactionBuilder;
+use alloy_primitives::{hex, Address, Bytes, TxKind, U256};
+use alloy_provider::Provider;
+use alloy_rpc_types::TransactionRequest;
+use alloy_serde::WithOtherFields;
+use alloy_zksync::network::{
+ transaction_request::TransactionRequest as ZkTransactionRequest,
+ unsigned_tx::eip712::PaymasterParams,
+};
+use clap::{command, Parser};
+use eyre::Result;
+use foundry_cli::utils;
+use foundry_config::Config;
+
+#[derive(Clone, Debug, Parser)]
+#[command(next_help_heading = "Transaction options")]
+pub struct ZkTransactionOpts {
+ /// Paymaster address for the ZKSync transaction
+ #[arg(long = "zk-paymaster-address", requires = "paymaster_input")]
+ pub paymaster_address: Option
,
+
+ /// Paymaster input for the ZKSync transaction
+ #[arg(long = "zk-paymaster-input", requires = "paymaster_address", value_parser = parse_hex_bytes)]
+ pub paymaster_input: Option,
+
+ /// Factory dependencies for the ZKSync transaction
+ #[arg(long = "zk-factory-deps", value_parser = parse_hex_bytes, value_delimiter = ',')]
+ pub factory_deps: Vec,
+
+ /// Custom signature for the ZKSync transaction
+ #[arg(long = "zk-custom-signature", value_parser = parse_hex_bytes)]
+ pub custom_signature: Option,
+
+ /// Gas per pubdata for the ZKSync transaction
+ #[arg(long = "zk-gas-per-pubdata")]
+ pub gas_per_pubdata: Option,
+}
+
+fn parse_hex_bytes(s: &str) -> Result {
+ hex::decode(s).map(Bytes::from).map_err(|e| format!("Invalid hex string: {e}"))
+}
+
+impl ZkTransactionOpts {
+ pub fn has_zksync_args(&self) -> bool {
+ self.paymaster_address.is_some() ||
+ !self.factory_deps.is_empty() ||
+ self.custom_signature.is_some() ||
+ self.gas_per_pubdata.is_some()
+ }
+}
+
+pub async fn estimate_gas(
+ zk_tx: ZkTransactionOpts,
+ config: &Config,
+ evm_tx: WithOtherFields,
+ zk_code: String,
+) -> Result {
+ let zk_provider = utils::get_provider_zksync(config)?;
+ let is_create = evm_tx.to == Some(TxKind::Create);
+ let mut tx: ZkTransactionRequest = evm_tx.inner.clone().into();
+ if let Some(gas_per_pubdata) = zk_tx.gas_per_pubdata {
+ tx.set_gas_per_pubdata(gas_per_pubdata)
+ }
+
+ if let Some(custom_signature) = &zk_tx.custom_signature {
+ tx.set_custom_signature(custom_signature.clone());
+ }
+
+ if let (Some(paymaster), Some(paymaster_input)) =
+ (zk_tx.paymaster_address, zk_tx.paymaster_input.clone())
+ {
+ tx.set_paymaster_params(PaymasterParams { paymaster, paymaster_input });
+ }
+
+ if is_create {
+ let evm_input: Vec = tx.input().cloned().map(|bytes| bytes.into()).unwrap_or_default();
+ let zk_code_decoded = hex::decode(zk_code)?;
+ // constructor input gets appended to the bytecode
+ let zk_input = &evm_input[zk_code_decoded.len()..];
+ tx = tx.with_create_params(
+ zk_code_decoded,
+ zk_input.to_vec(),
+ zk_tx.factory_deps.into_iter().map(|v| v.into()).collect(),
+ )?;
+ } else {
+ tx.set_factory_deps(zk_tx.factory_deps.clone());
+ }
+
+ // TODO: Check if alloy calls this for estimate_gas. If so, we do not need this.
+ tx.prep_for_submission();
+ Ok(zk_provider.estimate_gas(&tx).await?)
+}
diff --git a/crates/cast/src/zksync.rs b/crates/cast/src/zksync.rs
index 25afcc87e..14234adf6 100644
--- a/crates/cast/src/zksync.rs
+++ b/crates/cast/src/zksync.rs
@@ -1,14 +1,11 @@
//! Contains zksync specific logic for foundry's `cast` functionality
use alloy_network::AnyNetwork;
-use alloy_primitives::{Address, Bytes, U256};
use alloy_provider::{PendingTransactionBuilder, Provider};
use alloy_transport::Transport;
use alloy_zksync::network::{
- transaction_request::TransactionRequest as ZkTransactionRequest,
- unsigned_tx::eip712::PaymasterParams, Zksync,
+ transaction_request::TransactionRequest as ZkTransactionRequest, Zksync,
};
-use clap::Parser;
use eyre::Result;
use crate::Cast;
@@ -66,59 +63,3 @@ where
Ok(res)
}
}
-
-#[derive(Clone, Debug, Parser)]
-#[command(next_help_heading = "Transaction options")]
-pub struct ZkTransactionOpts {
- // /// Use ZKSync
- // #[arg(long, default_value_ifs([("paymaster_address", ArgPredicate::IsPresent,
- // "true"),("paymaster_input", ArgPredicate::IsPresent, "true")]))] pub zksync: bool,
- /// Paymaster address for the ZKSync transaction
- #[arg(long = "zk-paymaster-address", requires = "paymaster_input")]
- pub paymaster_address: Option,
-
- /// Paymaster input for the ZKSync transaction
- #[arg(long = "zk-paymaster-input", requires = "paymaster_address")]
- pub paymaster_input: Option,
-
- /// Factory dependencies for the ZKSync transaction
- #[arg(long = "zk-factory-deps")]
- pub factory_deps: Vec,
-
- /// Custom signature for the ZKSync transaction
- #[arg(long = "zk-custom-signature")]
- pub custom_signature: Option,
-
- /// Gas per pubdata for the ZKSync transaction
- #[arg(long = "zk-gas-per-pubdata")]
- pub gas_per_pubdata: Option,
-}
-
-impl ZkTransactionOpts {
- pub fn has_zksync_args(&self) -> bool {
- self.paymaster_address.is_some() ||
- !self.factory_deps.is_empty() ||
- self.custom_signature.is_some() ||
- self.gas_per_pubdata.is_some()
- }
-
- pub fn apply_to_tx(&self, tx: &mut ZkTransactionRequest) {
- if let Some(gas_per_pubdata) = self.gas_per_pubdata {
- tx.set_gas_per_pubdata(gas_per_pubdata)
- }
-
- if !self.factory_deps.is_empty() {
- tx.set_factory_deps(self.factory_deps.clone());
- }
-
- if let Some(custom_signature) = &self.custom_signature {
- tx.set_custom_signature(custom_signature.clone());
- }
-
- if let (Some(paymaster), Some(paymaster_input)) =
- (self.paymaster_address, self.paymaster_input.clone())
- {
- tx.set_paymaster_params(PaymasterParams { paymaster, paymaster_input });
- }
- }
-}