Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add paymaster support to cast send #612

Merged
merged 8 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Jrigada marked this conversation as resolved.
Show resolved Hide resolved

/// 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