diff --git a/crates/cast/src/cmd/mktx.rs b/crates/cast/src/cmd/mktx.rs index a17f52be8071b..148b1f0c5cfc8 100644 --- a/crates/cast/src/cmd/mktx.rs +++ b/crates/cast/src/cmd/mktx.rs @@ -1,6 +1,7 @@ use crate::tx::{self, CastTxBuilder}; use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; -use alloy_primitives::hex; +use alloy_primitives::{hex, Address, Bytes, U256, U64}; +use alloy_rlp::{Decodable, RlpDecodable, RlpEncodable}; use alloy_signer::Signer; use clap::Parser; use eyre::{OptionExt, Result}; @@ -50,6 +51,25 @@ pub struct MakeTxArgs { /// Relaxes the wallet requirement. #[arg(long, requires = "from")] raw_unsigned: bool, + + #[arg(long, value_name = "V")] + v: Option, + + #[arg(long, value_name = "R")] + r: Option, + + #[arg(long, value_name = "S")] + s: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, RlpDecodable, RlpEncodable)] +pub struct UnsignedTransaction { + pub nonce: U256, + pub gas_price: U256, + pub gas_limit: U256, + pub to: Address, + pub value: U256, + pub data: Bytes, } #[derive(Debug, Parser)] @@ -70,7 +90,13 @@ pub enum MakeTxSubcommands { impl MakeTxArgs { pub async fn run(self) -> Result<()> { - let Self { to, mut sig, mut args, command, tx, path, eth, raw_unsigned } = self; + let Self { mut to, mut sig, mut args, command, tx, path, eth, raw_unsigned, v, r, s } = + self; + + if !raw_unsigned && to.is_none() && !args.is_empty() { + let to_str = args.remove(0); + to = Some(NameOrAddress::from_str(&to_str)?); + } let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; @@ -100,15 +126,50 @@ impl MakeTxArgs { .with_blob_data(blob_data)?; if raw_unsigned { - // Build unsigned raw tx let from = eth.wallet.from.ok_or_eyre("missing `--from` address")?; - let raw_tx = tx_builder.build_unsigned_raw(from).await?; + let raw_unsigned_hex = tx_builder.build_unsigned_raw(from).await?; + + if let (Some(v), Some(r_hex), Some(s_hex)) = (v, r.as_ref(), s.as_ref()) { + let raw_unsigned_bytes = hex::decode(raw_unsigned_hex.trim_start_matches("0x"))?; + let mut buf = raw_unsigned_bytes.as_slice(); + let unsigned_tx: UnsignedTransaction = UnsignedTransaction::decode(&mut buf)?; + + let r_bytes = hex::decode(r_hex.trim_start_matches("0x"))?; + let s_bytes = hex::decode(s_hex.trim_start_matches("0x"))?; + + if r_bytes.len() != 32 || s_bytes.len() != 32 { + eyre::bail!("r and s must be 32 bytes each"); + } + + let r = U256::from_be_slice(&r_bytes); + let s = U256::from_be_slice(&s_bytes); + let v = U64::from(v); + + let mut out = Vec::new(); + let fields: &[&dyn alloy_rlp::Encodable] = &[ + &unsigned_tx.nonce, + &unsigned_tx.gas_price, + &unsigned_tx.gas_limit, + &unsigned_tx.to, + &unsigned_tx.value, + &unsigned_tx.data, + &v, + &r, + &s, + ]; + + alloy_rlp::encode_list::<&dyn alloy_rlp::Encodable, dyn alloy_rlp::Encodable>( + fields, &mut out, + ); + + sh_println!("0x{}", hex::encode(out))?; + } else { + sh_println!("{}", raw_unsigned_hex)?; + } - sh_println!("{raw_tx}")?; return Ok(()); } - // Retrieve the signer, and bail if it can't be constructed. let signer = eth.wallet.signer().await?; let from = signer.address();