diff --git a/crates/network/Cargo.toml b/crates/network/Cargo.toml index 0f40c4739..29de36128 100644 --- a/crates/network/Cargo.toml +++ b/crates/network/Cargo.toml @@ -24,3 +24,4 @@ alloy-consensus.workspace = true alloy-network.workspace = true alloy-primitives.workspace = true alloy-rpc-types-eth.workspace = true +alloy-signer.workspace = true diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index 52759c696..0d6c6e0c4 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -8,10 +8,11 @@ pub use alloy_network::*; -use alloy_consensus::TxType; +use alloy_consensus::{TxEnvelope, TxType, TypedTransaction}; use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256}; use alloy_rpc_types_eth::AccessList; -use op_alloy_consensus::OpTxType; +use op_alloy_consensus::{OpTxEnvelope, OpTxType, OpTypedTransaction}; +use op_alloy_rpc_types::OpTransactionRequest; /// Types for an Op-stack network. #[derive(Clone, Copy, Debug)] @@ -22,15 +23,15 @@ pub struct Optimism { impl Network for Optimism { type TxType = OpTxType; - type TxEnvelope = alloy_consensus::TxEnvelope; + type TxEnvelope = op_alloy_consensus::OpTxEnvelope; - type UnsignedTx = alloy_consensus::TypedTransaction; + type UnsignedTx = op_alloy_consensus::OpTypedTransaction; type ReceiptEnvelope = op_alloy_consensus::OpReceiptEnvelope; type Header = alloy_consensus::Header; - type TransactionRequest = alloy_rpc_types_eth::transaction::TransactionRequest; + type TransactionRequest = op_alloy_rpc_types::OpTransactionRequest; type TransactionResponse = op_alloy_rpc_types::Transaction; @@ -42,97 +43,97 @@ impl Network for Optimism { alloy_rpc_types_eth::Block; } -impl TransactionBuilder for alloy_rpc_types_eth::transaction::TransactionRequest { +impl TransactionBuilder for OpTransactionRequest { fn chain_id(&self) -> Option { - self.chain_id + self.as_ref().chain_id() } fn set_chain_id(&mut self, chain_id: ChainId) { - self.chain_id = Some(chain_id); + self.as_mut().set_chain_id(chain_id); } fn nonce(&self) -> Option { - self.nonce + self.as_ref().nonce() } fn set_nonce(&mut self, nonce: u64) { - self.nonce = Some(nonce); + self.as_mut().set_nonce(nonce); } fn input(&self) -> Option<&Bytes> { - self.input.input() + self.as_ref().input() } fn set_input>(&mut self, input: T) { - self.input.input = Some(input.into()); + self.as_mut().set_input(input); } fn from(&self) -> Option
{ - self.from + self.as_ref().from() } fn set_from(&mut self, from: Address) { - self.from = Some(from); + self.as_mut().set_from(from); } fn kind(&self) -> Option { - self.to + self.as_ref().kind() } fn clear_kind(&mut self) { - self.to = None; + self.as_mut().clear_kind(); } fn set_kind(&mut self, kind: TxKind) { - self.to = Some(kind); + self.as_mut().set_kind(kind); } fn value(&self) -> Option { - self.value + self.as_ref().value() } fn set_value(&mut self, value: U256) { - self.value = Some(value) + self.as_mut().set_value(value); } fn gas_price(&self) -> Option { - self.gas_price + self.as_ref().gas_price() } fn set_gas_price(&mut self, gas_price: u128) { - self.gas_price = Some(gas_price); + self.as_mut().set_gas_price(gas_price); } fn max_fee_per_gas(&self) -> Option { - self.max_fee_per_gas + self.as_ref().max_fee_per_gas() } fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) { - self.max_fee_per_gas = Some(max_fee_per_gas); + self.as_mut().set_max_fee_per_gas(max_fee_per_gas); } fn max_priority_fee_per_gas(&self) -> Option { - self.max_priority_fee_per_gas + self.as_ref().max_priority_fee_per_gas() } fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) { - self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + self.as_mut().set_max_priority_fee_per_gas(max_priority_fee_per_gas); } fn gas_limit(&self) -> Option { - self.gas + self.as_ref().gas_limit() } fn set_gas_limit(&mut self, gas_limit: u64) { - self.gas = Some(gas_limit); + self.as_mut().set_gas_limit(gas_limit); } fn access_list(&self) -> Option<&AccessList> { - self.access_list.as_ref() + self.as_ref().access_list() } fn set_access_list(&mut self, access_list: AccessList) { - self.access_list = Some(access_list); + self.as_mut().set_access_list(access_list); } fn complete_type(&self, ty: OpTxType) -> Result<(), Vec<&'static str>> { @@ -140,37 +141,45 @@ impl TransactionBuilder for alloy_rpc_types_eth::transaction::Transact OpTxType::Deposit => Err(vec!["not implemented for deposit tx"]), _ => { let ty = TxType::try_from(ty as u8).unwrap(); - TransactionBuilder::::complete_type(self, ty) + self.as_ref().complete_type(ty) } } } fn can_submit(&self) -> bool { - TransactionBuilder::::can_submit(self) + self.as_ref().can_submit() } fn can_build(&self) -> bool { - TransactionBuilder::::can_build(self) + self.as_ref().can_build() } #[doc(alias = "output_transaction_type")] fn output_tx_type(&self) -> OpTxType { - OpTxType::try_from(self.preferred_type() as u8).unwrap() + match self.as_ref().preferred_type() { + TxType::Eip1559 | TxType::Eip4844 => OpTxType::Eip1559, + TxType::Eip2930 => OpTxType::Eip2930, + TxType::Eip7702 => OpTxType::Eip7702, + TxType::Legacy => OpTxType::Legacy, + } } #[doc(alias = "output_transaction_type_checked")] fn output_tx_type_checked(&self) -> Option { - self.buildable_type().map(|tx_ty| OpTxType::try_from(tx_ty as u8).unwrap()) + self.as_ref().buildable_type().map(|tx_ty| match tx_ty { + TxType::Eip1559 | TxType::Eip4844 => OpTxType::Eip1559, + TxType::Eip2930 => OpTxType::Eip2930, + TxType::Eip7702 => OpTxType::Eip7702, + TxType::Legacy => OpTxType::Legacy, + }) } fn prep_for_submission(&mut self) { - self.transaction_type = Some(self.preferred_type() as u8); - self.trim_conflicting_keys(); - self.populate_blob_hashes(); + self.as_mut().prep_for_submission(); } - fn build_unsigned(self) -> BuildResult { - if let Err((tx_type, missing)) = self.missing_keys() { + fn build_unsigned(self) -> BuildResult { + if let Err((tx_type, missing)) = self.as_ref().missing_keys() { let tx_type = OpTxType::try_from(tx_type as u8).unwrap(); return Err(TransactionBuilderError::InvalidTransactionRequest(tx_type, missing) .into_unbuilt(self)); @@ -185,3 +194,42 @@ impl TransactionBuilder for alloy_rpc_types_eth::transaction::Transact Ok(wallet.sign_request(self).await?) } } + +impl NetworkWallet for EthereumWallet { + fn default_signer_address(&self) -> Address { + NetworkWallet::::default_signer_address(self) + } + + fn has_signer_for(&self, address: &Address) -> bool { + NetworkWallet::::has_signer_for(self, address) + } + + fn signer_addresses(&self) -> impl Iterator { + NetworkWallet::::signer_addresses(self) + } + + async fn sign_transaction_from( + &self, + sender: Address, + tx: OpTypedTransaction, + ) -> alloy_signer::Result { + let tx = match tx { + OpTypedTransaction::Legacy(tx) => TypedTransaction::Legacy(tx), + OpTypedTransaction::Eip2930(tx) => TypedTransaction::Eip2930(tx), + OpTypedTransaction::Eip1559(tx) => TypedTransaction::Eip1559(tx), + OpTypedTransaction::Eip7702(tx) => TypedTransaction::Eip7702(tx), + OpTypedTransaction::Deposit(_) => { + return Err(alloy_signer::Error::other("not implemented for deposit tx")) + } + }; + let tx = NetworkWallet::::sign_transaction_from(self, sender, tx).await?; + + Ok(match tx { + TxEnvelope::Eip1559(tx) => OpTxEnvelope::Eip1559(tx), + TxEnvelope::Eip2930(tx) => OpTxEnvelope::Eip2930(tx), + TxEnvelope::Eip7702(tx) => OpTxEnvelope::Eip7702(tx), + TxEnvelope::Legacy(tx) => OpTxEnvelope::Legacy(tx), + _ => unreachable!(), + }) + } +} diff --git a/crates/rpc-types/Cargo.toml b/crates/rpc-types/Cargo.toml index c449cf01c..c9d96958c 100644 --- a/crates/rpc-types/Cargo.toml +++ b/crates/rpc-types/Cargo.toml @@ -21,6 +21,7 @@ op-alloy-consensus = { workspace = true, features = ["serde"] } # Alloy alloy-serde.workspace = true alloy-consensus.workspace = true +alloy-network.workspace = true alloy-network-primitives.workspace = true alloy-eips = { workspace = true, features = ["serde"] } alloy-rpc-types-eth = { workspace = true, features = ["serde"] } @@ -33,6 +34,8 @@ serde = { workspace = true, features = ["derive"] } # arbitrary arbitrary = { workspace = true, features = ["derive"], optional = true } +derive_more.workspace = true + [dev-dependencies] rand.workspace = true arbitrary = { workspace = true, features = ["derive"] } @@ -54,3 +57,6 @@ arbitrary = [ "alloy-primitives/arbitrary", "alloy-rpc-types-eth/arbitrary", ] +k256 = [ + "alloy-consensus/k256", +] diff --git a/crates/rpc-types/src/lib.rs b/crates/rpc-types/src/lib.rs index 30931976b..4be3c734e 100644 --- a/crates/rpc-types/src/lib.rs +++ b/crates/rpc-types/src/lib.rs @@ -19,4 +19,4 @@ pub mod sync; pub mod transaction; pub use receipt::{OpTransactionReceipt, OpTransactionReceiptFields}; -pub use transaction::{OpTransactionFields, Transaction}; +pub use transaction::{OpTransactionFields, OpTransactionRequest, Transaction}; diff --git a/crates/rpc-types/src/transaction.rs b/crates/rpc-types/src/transaction.rs index 9595e4ce5..0c23d4377 100644 --- a/crates/rpc-types/src/transaction.rs +++ b/crates/rpc-types/src/transaction.rs @@ -11,6 +11,9 @@ use alloy_serde::OtherFields; use op_alloy_consensus::{OpTxEnvelope, OpTxType, TxDeposit}; use serde::{Deserialize, Serialize}; +mod request; +pub use request::OpTransactionRequest; + /// OP Transaction type #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] diff --git a/crates/rpc-types/src/transaction/request.rs b/crates/rpc-types/src/transaction/request.rs new file mode 100644 index 000000000..6f889aa51 --- /dev/null +++ b/crates/rpc-types/src/transaction/request.rs @@ -0,0 +1,194 @@ +use alloc::vec::Vec; +use alloy_consensus::{SignableTransaction, Signed, TxEip1559, TxEip4844, TypedTransaction}; +use alloy_eips::eip7702::SignedAuthorization; +use alloy_network::TransactionBuilder7702; +use alloy_primitives::{Address, Signature, TxKind, U256}; +use alloy_rpc_types_eth::{AccessList, TransactionInput, TransactionRequest}; +use op_alloy_consensus::{OpTxEnvelope, OpTypedTransaction, TxDeposit}; +use serde::{Deserialize, Serialize}; + +/// Builder for [`OpTypedTransaction`]. +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Hash, + derive_more::From, + derive_more::AsRef, + derive_more::AsMut, + Serialize, + Deserialize, +)] +#[serde(transparent)] +pub struct OpTransactionRequest(TransactionRequest); + +impl OpTransactionRequest { + /// Sets the `from` field in the call to the provided address + #[inline] + pub const fn from(mut self, from: Address) -> Self { + self.0.from = Some(from); + self + } + + /// Sets the transactions type for the transactions. + #[doc(alias = "tx_type")] + pub const fn transaction_type(mut self, transaction_type: u8) -> Self { + self.0.transaction_type = Some(transaction_type); + self + } + + /// Sets the gas limit for the transaction. + pub const fn gas_limit(mut self, gas_limit: u64) -> Self { + self.0.gas = Some(gas_limit); + self + } + + /// Sets the nonce for the transaction. + pub const fn nonce(mut self, nonce: u64) -> Self { + self.0.nonce = Some(nonce); + self + } + + /// Sets the maximum fee per gas for the transaction. + pub const fn max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self { + self.0.max_fee_per_gas = Some(max_fee_per_gas); + self + } + + /// Sets the maximum priority fee per gas for the transaction. + pub const fn max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self { + self.0.max_priority_fee_per_gas = Some(max_priority_fee_per_gas); + self + } + + /// Sets the recipient address for the transaction. + #[inline] + pub const fn to(mut self, to: Address) -> Self { + self.0.to = Some(TxKind::Call(to)); + self + } + + /// Sets the value (amount) for the transaction. + pub const fn value(mut self, value: U256) -> Self { + self.0.value = Some(value); + self + } + + /// Sets the access list for the transaction. + pub fn access_list(mut self, access_list: AccessList) -> Self { + self.0.access_list = Some(access_list); + self + } + + /// Sets the input data for the transaction. + pub fn input(mut self, input: TransactionInput) -> Self { + self.0.input = input; + self + } + + /// Builds [`OpTypedTransaction`] from this builder. See [`TransactionRequest::build_typed_tx`] + /// for more info. + /// + /// Note that EIP-4844 transactions are not supported by Optimism and will be converted into + /// EIP-1559 transactions. + pub fn build_typed_tx(self) -> Result { + let tx = self.0.build_typed_tx().map_err(Self)?; + match tx { + TypedTransaction::Legacy(tx) => Ok(OpTypedTransaction::Legacy(tx)), + TypedTransaction::Eip1559(tx) => Ok(OpTypedTransaction::Eip1559(tx)), + TypedTransaction::Eip2930(tx) => Ok(OpTypedTransaction::Eip2930(tx)), + TypedTransaction::Eip4844(tx) => { + let tx: TxEip4844 = tx.into(); + Ok(OpTypedTransaction::Eip1559(TxEip1559 { + chain_id: tx.chain_id, + nonce: tx.nonce, + gas_limit: tx.gas_limit, + max_priority_fee_per_gas: tx.max_priority_fee_per_gas, + max_fee_per_gas: tx.max_fee_per_gas, + to: TxKind::Call(tx.to), + value: tx.value, + access_list: tx.access_list, + input: tx.input, + })) + } + TypedTransaction::Eip7702(tx) => Ok(OpTypedTransaction::Eip7702(tx)), + } + } +} + +impl From for OpTransactionRequest { + fn from(tx: TxDeposit) -> Self { + let TxDeposit { + source_hash: _, + from, + to, + mint: _, + value, + gas_limit, + is_system_transaction: _, + input, + } = tx; + + Self(TransactionRequest { + from: Some(from), + to: Some(to), + value: Some(value), + gas: Some(gas_limit), + input: input.into(), + ..Default::default() + }) + } +} + +impl From> for OpTransactionRequest +where + T: SignableTransaction + Into, +{ + fn from(value: Signed) -> Self { + #[cfg(feature = "k256")] + let from = value.recover_signer().ok(); + #[cfg(not(feature = "k256"))] + let from = None; + + let mut inner: TransactionRequest = value.strip_signature().into(); + inner.from = from; + + Self(inner) + } +} + +impl From for OpTransactionRequest { + fn from(tx: OpTypedTransaction) -> Self { + match tx { + OpTypedTransaction::Legacy(tx) => Self(tx.into()), + OpTypedTransaction::Eip2930(tx) => Self(tx.into()), + OpTypedTransaction::Eip1559(tx) => Self(tx.into()), + OpTypedTransaction::Eip7702(tx) => Self(tx.into()), + OpTypedTransaction::Deposit(tx) => tx.into(), + } + } +} + +impl From for OpTransactionRequest { + fn from(value: OpTxEnvelope) -> Self { + match value { + OpTxEnvelope::Eip2930(tx) => tx.into(), + OpTxEnvelope::Eip1559(tx) => tx.into(), + OpTxEnvelope::Eip7702(tx) => tx.into(), + OpTxEnvelope::Deposit(tx) => tx.into(), + _ => Default::default(), + } + } +} + +impl TransactionBuilder7702 for OpTransactionRequest { + fn authorization_list(&self) -> Option<&Vec> { + self.as_ref().authorization_list() + } + + fn set_authorization_list(&mut self, authorization_list: Vec) { + self.as_mut().set_authorization_list(authorization_list); + } +} diff --git a/scripts/check_no_std.sh b/scripts/check_no_std.sh index 3ea6d71d7..4a5caa974 100755 --- a/scripts/check_no_std.sh +++ b/scripts/check_no_std.sh @@ -5,7 +5,6 @@ no_std_packages=( op-alloy-consensus op-alloy-protocol op-alloy-genesis - op-alloy-rpc-types op-alloy-rpc-types-engine )