Skip to content

Commit

Permalink
feat: Add paymaster support to cast send (#612)
Browse files Browse the repository at this point in the history
* Add paymaster support to cast send

* remove reference

* Add zksync flag to cast send and handle missing zk paymaster parameters

* Clap zksync flag when paymaster parameters present, better handling paymaster parameters

* put cast send logic for zksync in it's own function

* Add cast paymaster tests

* Remove unnecesary paymaster settings in test
  • Loading branch information
Jrigada authored Oct 22, 2024
1 parent e498b34 commit baee07e
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 11 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/cast/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ foundry-compilers.workspace = true
foundry-config.workspace = true
foundry-evm.workspace = true
foundry-wallets.workspace = true
foundry-zksync-core.workspace = true
zksync-web3-rs.workspace = true

alloy-chains.workspace = true
alloy-consensus = { workspace = true, features = ["serde", "kzg"] }
Expand Down
106 changes: 97 additions & 9 deletions crates/cast/bin/cmd/send.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
use crate::tx::{self, CastTxBuilder};
use alloy_network::{AnyNetwork, EthereumWallet};
use alloy_primitives::{Address, Bytes, TxHash};
use alloy_provider::{Provider, ProviderBuilder};
use alloy_rpc_types::TransactionRequest;
use alloy_serde::WithOtherFields;
use alloy_signer::Signer;
use alloy_transport::Transport;
use cast::Cast;
use clap::Parser;
use clap::{builder::ArgPredicate, Parser};
use eyre::Result;
use foundry_cli::{
opts::{EthereumOpts, TransactionOpts},
utils,
};
use foundry_common::{cli_warn, ens::NameOrAddress};
use foundry_config::Config;
use foundry_wallets::WalletSigner;
use foundry_zksync_core::{self, convert::ConvertAddress};
use std::{path::PathBuf, str::FromStr};
use zksync_web3_rs::eip712::PaymasterParams;

/// ZkSync-specific paymaster parameters for transactions
#[derive(Debug, Parser)]
pub struct ZksyncParams {
/// Use ZKSync
#[arg(long, default_value_ifs([("paymaster_address", ArgPredicate::IsPresent, "true"),("paymaster_input", ArgPredicate::IsPresent, "true")]))]
zksync: bool,

/// The paymaster address for the ZKSync transaction
#[arg(long = "zk-paymaster-address", requires = "paymaster_input")]
paymaster_address: Option<String>,

/// The paymaster input for the ZKSync transaction
#[arg(long = "zk-paymaster-input", requires = "paymaster_address")]
paymaster_input: Option<String>,
}

/// CLI arguments for `cast send`.
#[derive(Debug, Parser)]
Expand Down Expand Up @@ -69,6 +89,9 @@ pub struct SendTxArgs {
help_heading = "Transaction options"
)]
path: Option<PathBuf>,

#[command(flatten)]
zksync_params: ZksyncParams,
}

#[derive(Debug, Parser)]
Expand Down Expand Up @@ -103,6 +126,7 @@ impl SendTxArgs {
unlocked,
path,
timeout,
zksync_params,
} = self;

let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None };
Expand Down Expand Up @@ -171,14 +195,30 @@ impl SendTxArgs {

tx::validate_from_address(eth.wallet.from, from)?;

let (tx, _) = builder.build(&signer).await?;

let wallet = EthereumWallet::from(signer);
let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
.wallet(wallet)
.on_provider(&provider);

cast_send(provider, tx, cast_async, confirmations, timeout, to_json).await
if zksync_params.zksync {
let (tx, _) = builder.build(&signer).await?;
cast_send_zk(
&provider,
zksync_params,
tx,
cast_async,
confirmations,
timeout,
to_json,
signer,
)
.await
} else {
// Standard transaction
let (tx, _) = builder.build(&signer).await?;

let wallet = EthereumWallet::from(signer);
let provider = ProviderBuilder::<_, _, AnyNetwork>::default()
.wallet(wallet)
.on_provider(&provider);

cast_send(provider, tx, cast_async, confirmations, timeout, to_json).await
}
}
}
}
Expand All @@ -196,6 +236,54 @@ async fn cast_send<P: Provider<T, AnyNetwork>, T: Transport + Clone>(

let tx_hash = pending_tx.inner().tx_hash();

handle_transaction_result(&cast, tx_hash, cast_async, confs, timeout, to_json).await
}

#[allow(clippy::too_many_arguments)]
async fn cast_send_zk<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
provider: P,
zksync_params: ZksyncParams,
tx: WithOtherFields<TransactionRequest>,
cast_async: bool,
confs: u64,
timeout: u64,
to_json: bool,
signer: WalletSigner,
) -> Result<()> {
// ZkSync transaction
let paymaster_params = zksync_params
.paymaster_address
.and_then(|addr| zksync_params.paymaster_input.map(|input| (addr, input)))
.map(|(addr, input)| PaymasterParams {
paymaster: Address::from_str(&addr).expect("Invalid paymaster address").to_h160(),
paymaster_input: Bytes::from_str(&input).expect("Invalid paymaster input").to_vec(),
});

// Build EIP712 transaction for ZKSync
let tx = foundry_zksync_core::new_eip712_transaction(
tx,
Vec::new(), // Empty factory_deps
paymaster_params,
&provider,
signer,
)
.await
.map_err(|e| eyre::eyre!("Failed to create EIP712 transaction: {}", e))?;

// Use send_raw_transaction for ZKSync
let tx_hash = provider.send_raw_transaction(&tx).await?.tx_hash().to_owned();
let cast = Cast::new(provider);
handle_transaction_result(&cast, &tx_hash, cast_async, confs, timeout, to_json).await
}

async fn handle_transaction_result<P: Provider<T, AnyNetwork>, T: Transport + Clone>(
cast: &Cast<P, T>,
tx_hash: &TxHash,
cast_async: bool,
confs: u64,
timeout: u64,
to_json: bool,
) -> Result<()> {
if cast_async {
println!("{tx_hash:#x}");
} else {
Expand Down
4 changes: 3 additions & 1 deletion crates/cast/bin/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ where
}

if let Some(gas_price) = tx_opts.gas_price {
if legacy {
// We need to set the gas price to be able to create the EIP-712 transaction in
// zkcontext
if legacy || config.zksync.startup {
tx.set_gas_price(gas_price.to());
} else {
tx.set_max_fee_per_gas(gas_price.to());
Expand Down
Loading

0 comments on commit baee07e

Please sign in to comment.