-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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: cast mktx
#7056
Merged
Merged
feat: cast mktx
#7056
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
use crate::tx; | ||
use clap::Parser; | ||
use ethers_core::types::NameOrAddress; | ||
use ethers_middleware::MiddlewareBuilder; | ||
use ethers_providers::Middleware; | ||
use ethers_signers::Signer; | ||
use eyre::Result; | ||
use foundry_cli::{ | ||
opts::{EthereumOpts, TransactionOpts}, | ||
utils, | ||
}; | ||
use foundry_common::types::ToAlloy; | ||
use foundry_config::Config; | ||
use std::str::FromStr; | ||
|
||
/// CLI arguments for `cast mktx`. | ||
#[derive(Debug, Parser)] | ||
pub struct MakeTxArgs { | ||
/// The destination of the transaction. | ||
/// | ||
/// If not provided, you must use `cast mktx --create`. | ||
#[arg(value_parser = NameOrAddress::from_str)] | ||
to: Option<NameOrAddress>, | ||
|
||
/// The signature of the function to call. | ||
sig: Option<String>, | ||
|
||
/// The arguments of the function to call. | ||
args: Vec<String>, | ||
|
||
/// Reuse the latest nonce for the sender account. | ||
#[arg(long, conflicts_with = "nonce")] | ||
resend: bool, | ||
|
||
#[command(subcommand)] | ||
command: Option<MakeTxSubcommands>, | ||
|
||
#[command(flatten)] | ||
tx: TransactionOpts, | ||
|
||
#[command(flatten)] | ||
eth: EthereumOpts, | ||
} | ||
|
||
#[derive(Debug, Parser)] | ||
pub enum MakeTxSubcommands { | ||
/// Use to deploy raw contract bytecode. | ||
#[clap(name = "--create")] | ||
Create { | ||
/// The initialization bytecode of the contract to deploy. | ||
code: String, | ||
|
||
/// The signature of the constructor. | ||
sig: Option<String>, | ||
|
||
/// The constructor arguments. | ||
args: Vec<String>, | ||
}, | ||
} | ||
|
||
impl MakeTxArgs { | ||
pub async fn run(self) -> Result<()> { | ||
let MakeTxArgs { to, mut sig, mut args, resend, command, mut tx, eth } = self; | ||
|
||
let code = if let Some(MakeTxSubcommands::Create { | ||
code, | ||
sig: constructor_sig, | ||
args: constructor_args, | ||
}) = command | ||
{ | ||
sig = constructor_sig; | ||
args = constructor_args; | ||
Some(code) | ||
} else { | ||
None | ||
}; | ||
|
||
tx::validate_to_address(&code, &to)?; | ||
|
||
let config = Config::from(ð); | ||
let provider = utils::get_provider(&config)?; | ||
let chain = utils::get_chain(config.chain, &provider).await?; | ||
let api_key = config.get_etherscan_api_key(Some(chain)); | ||
|
||
// Retrieve the signer, and bail if it can't be constructed. | ||
let signer = eth.wallet.signer().await?; | ||
let from = signer.address(); | ||
|
||
tx::validate_from_address(eth.wallet.from, from.to_alloy())?; | ||
|
||
if resend { | ||
tx.nonce = Some(provider.get_transaction_count(from, None).await?.to_alloy()); | ||
} | ||
|
||
let provider = provider.with_signer(signer); | ||
|
||
let (mut tx, _) = | ||
tx::build_tx(&provider, from, to, code, sig, args, tx, chain, api_key).await?; | ||
|
||
// Fill nonce, gas limit, gas price, and max priority fee per gas if needed | ||
provider.fill_transaction(&mut tx, None).await?; | ||
|
||
let signature = provider.sign_transaction(&tx, from).await?; | ||
let signed_tx = tx.rlp_signed(&signature); | ||
println!("{signed_tx}"); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use alloy_primitives::Address; | ||
use cast::{TxBuilder, TxBuilderOutput}; | ||
use ethers_core::types::NameOrAddress; | ||
use ethers_providers::Middleware; | ||
use eyre::Result; | ||
use foundry_cli::opts::TransactionOpts; | ||
use foundry_config::Chain; | ||
|
||
/// Prevents a misconfigured hwlib from sending a transaction that defies user-specified --from | ||
pub fn validate_from_address( | ||
specified_from: Option<Address>, | ||
signer_address: Address, | ||
) -> Result<()> { | ||
if let Some(specified_from) = specified_from { | ||
if specified_from != signer_address { | ||
eyre::bail!( | ||
"\ | ||
The specified sender via CLI/env vars does not match the sender configured via | ||
the hardware wallet's HD Path. | ||
Please use the `--hd-path <PATH>` parameter to specify the BIP32 Path which | ||
corresponds to the sender, or let foundry automatically detect it by not specifying any sender address." | ||
) | ||
} | ||
} | ||
Ok(()) | ||
} | ||
|
||
/// Ensures the transaction is either a contract deployment or a recipient address is specified | ||
pub fn validate_to_address(code: &Option<String>, to: &Option<NameOrAddress>) -> Result<()> { | ||
if code.is_none() && to.is_none() { | ||
eyre::bail!("Must specify a recipient address or contract code to deploy"); | ||
} | ||
Ok(()) | ||
} | ||
|
||
#[allow(clippy::too_many_arguments)] | ||
pub async fn build_tx<M: Middleware, F: Into<NameOrAddress>, T: Into<NameOrAddress>>( | ||
provider: &M, | ||
from: F, | ||
to: Option<T>, | ||
code: Option<String>, | ||
sig: Option<String>, | ||
args: Vec<String>, | ||
tx: TransactionOpts, | ||
chain: impl Into<Chain>, | ||
etherscan_api_key: Option<String>, | ||
) -> Result<TxBuilderOutput> { | ||
let mut builder = TxBuilder::new(provider, from, to, chain, tx.legacy).await?; | ||
builder | ||
.etherscan_api_key(etherscan_api_key) | ||
.gas(tx.gas_limit) | ||
.gas_price(tx.gas_price) | ||
.priority_gas_price(tx.priority_gas_price) | ||
.value(tx.value) | ||
.nonce(tx.nonce); | ||
|
||
let params = sig.as_deref().map(|sig| (sig, args)); | ||
if let Some(code) = code { | ||
let mut data = hex::decode(code)?; | ||
|
||
if let Some((sig, args)) = params { | ||
let (mut sigdata, _) = builder.create_args(sig, args).await?; | ||
data.append(&mut sigdata); | ||
} | ||
|
||
builder.set_data(data); | ||
} else { | ||
builder.args(params).await?; | ||
} | ||
|
||
let builder_output = builder.build(); | ||
Ok(builder_output) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're actually signing the transaction, this probably should be behind
cast wallet
similar to the existingcast wallet sign
. What do you think about also renaming this tocast wallet sign-tx
to make it more explicit? I don't think cast needs to be backwards compatible with seth anymore.I could imagine a separate
cast mktx
command that returns a populated (optionally serialized) transaction, without signing itThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back and forth a few times on this and initially implemented this as
cast wallet sign-tx
, but ultimately decidedcast mktx
is better. Thecast wallet
commands don't useTxBuilder
, don't use an RPC connection, and therefore have no way of filling a transaction with gas info, nonce, ENS resolution, etc. Having it ascast mktx
allows it to be as close tocast send
as possible, with the difference being it prints the encoded transaction instead of broadcasting it. You get everything provided byTxBuilder
if you need it, but can still specify all the options manually if you have no connection to an RPC endpoint.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose it can be renamed to something like
cast sign-tx
, but since the behavior is identical toseth mktx
(which builds and signs transactions) I went withcast mktx
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a good general principle is to implement the UX that's best for the user, even if it's more work because
cast wallet
commands don't yet haveTxBuilder
or RPC connections. Most foundry users haven't used (or heard of) dapptools and seth is no longer maintained, so we should use the best naming/UX regardless of what seth did.However, IMO the cast UX is already messy and needs breaking change cleanup, so I won't block this PR on trying to keep cast UX clean or changing command names. Just sharing my thoughts, and will defer to you and other foundry maintainers from here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I agree. I wasn't sure what the best place for this was either. Because of how close its logic is to
cast send
, I put it adjacent to where that is. I can rename/move it wherever (though moving it undercast wallet
will require a few more changes like importingTxBuilder
and the utility functions in common withcast send
).