diff --git a/crates/rpc-types/src/receipt.rs b/crates/rpc-types/src/receipt.rs index c41460f49..cbcfbe5bf 100644 --- a/crates/rpc-types/src/receipt.rs +++ b/crates/rpc-types/src/receipt.rs @@ -1,7 +1,8 @@ //! Receipt types for RPC +use alloy_consensus::{Receipt, ReceiptWithBloom}; use alloy_serde::OtherFields; -use op_alloy_consensus::OpReceiptEnvelope; +use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom, OpReceiptEnvelope}; use serde::{Deserialize, Serialize}; /// OP Transaction Receipt type @@ -184,6 +185,55 @@ pub struct L1BlockInfo { impl Eq for L1BlockInfo {} +impl From for OpReceiptEnvelope { + fn from(value: OpTransactionReceipt) -> Self { + let inner_envelope = value.inner.inner; + + /// Helper function to convert the inner logs within a [ReceiptWithBloom] from RPC to + /// consensus types. + #[inline(always)] + fn convert_standard_receipt( + receipt: ReceiptWithBloom, + ) -> ReceiptWithBloom { + let ReceiptWithBloom { logs_bloom, receipt } = receipt; + + let consensus_logs = receipt.logs.into_iter().map(|log| log.inner).collect(); + ReceiptWithBloom { + receipt: Receipt { + status: receipt.status, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: consensus_logs, + }, + logs_bloom, + } + } + + match inner_envelope { + OpReceiptEnvelope::Legacy(receipt) => Self::Legacy(convert_standard_receipt(receipt)), + OpReceiptEnvelope::Eip2930(receipt) => Self::Eip2930(convert_standard_receipt(receipt)), + OpReceiptEnvelope::Eip1559(receipt) => Self::Eip1559(convert_standard_receipt(receipt)), + OpReceiptEnvelope::Eip7702(receipt) => Self::Eip7702(convert_standard_receipt(receipt)), + OpReceiptEnvelope::Deposit(OpDepositReceiptWithBloom { logs_bloom, receipt }) => { + let consensus_logs = receipt.inner.logs.into_iter().map(|log| log.inner).collect(); + let consensus_receipt = OpDepositReceiptWithBloom { + receipt: OpDepositReceipt { + inner: Receipt { + status: receipt.inner.status, + cumulative_gas_used: receipt.inner.cumulative_gas_used, + logs: consensus_logs, + }, + deposit_nonce: receipt.deposit_nonce, + deposit_receipt_version: receipt.deposit_receipt_version, + }, + logs_bloom, + }; + Self::Deposit(consensus_receipt) + } + _ => unreachable!("Unsupported OpReceiptEnvelope variant"), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc-types/src/transaction.rs b/crates/rpc-types/src/transaction.rs index bc33a4ea9..9595e4ce5 100644 --- a/crates/rpc-types/src/transaction.rs +++ b/crates/rpc-types/src/transaction.rs @@ -1,8 +1,14 @@ //! Optimism specific types related to transactions. -use alloy_eips::{eip2930::AccessList, eip7702::SignedAuthorization}; -use alloy_primitives::{Address, BlockHash, Bytes, ChainId, TxKind, B256, U256}; +use alloc::string::{String, ToString}; +use alloy_consensus::{ + SignableTransaction, Transaction as ConsensusTransaction, TxEip1559, TxEip2930, TxEip7702, + TxLegacy, +}; +use alloy_eips::{eip2718::Eip2718Error, eip2930::AccessList, eip7702::SignedAuthorization}; +use alloy_primitives::{Address, BlockHash, Bytes, ChainId, SignatureError, TxKind, B256, U256}; use alloy_serde::OtherFields; +use op_alloy_consensus::{OpTxEnvelope, OpTxType, TxDeposit}; use serde::{Deserialize, Serialize}; /// OP Transaction type @@ -28,7 +34,7 @@ pub struct Transaction { pub deposit_receipt_version: Option, } -impl alloy_consensus::Transaction for Transaction { +impl ConsensusTransaction for Transaction { fn chain_id(&self) -> Option { self.inner.chain_id() } @@ -116,7 +122,7 @@ impl alloy_network_primitives::TransactionResponse for Transaction { } fn to(&self) -> Option { - self.inner.to() + ConsensusTransaction::to(&self.inner) } } @@ -145,3 +151,150 @@ impl From for OtherFields { serde_json::to_value(value).unwrap().try_into().unwrap() } } + +/// Errors that can occur when converting a [Transaction] to an [OpTxEnvelope]. +#[derive(Debug)] +pub enum TransactionConversionError { + /// The transaction type is not supported. + UnsupportedTransactionType(Eip2718Error), + /// The transaction's signature could not be converted to the consensus type. + SignatureConversionError(SignatureError), + /// The transaction is missing a required field. + MissingRequiredField(String), + /// The transaction's signature is missing. + MissingSignature, +} + +impl core::fmt::Display for TransactionConversionError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::UnsupportedTransactionType(e) => { + write!(f, "Unsupported transaction type: {}", e) + } + Self::SignatureConversionError(e) => { + write!(f, "Signature conversion error: {}", e) + } + Self::MissingRequiredField(field) => { + write!(f, "Missing required field for conversion: {}", field) + } + Self::MissingSignature => { + write!(f, "Missing signature") + } + } + } +} + +impl core::error::Error for TransactionConversionError {} + +impl TryFrom for OpTxEnvelope { + type Error = TransactionConversionError; + + fn try_from(value: Transaction) -> Result { + /// Helper function to extract the signature from an RPC [Transaction]. + #[inline(always)] + fn extract_signature( + value: &Transaction, + ) -> Result { + value + .inner + .signature + .ok_or(TransactionConversionError::MissingSignature)? + .try_into() + .map_err(TransactionConversionError::SignatureConversionError) + } + + let ty = OpTxType::try_from(value.ty()) + .map_err(TransactionConversionError::UnsupportedTransactionType)?; + match ty { + OpTxType::Legacy => { + let signature = extract_signature(&value)?; + let legacy = TxLegacy { + chain_id: value.chain_id(), + nonce: value.nonce(), + gas_price: value.gas_price().unwrap_or_default(), + gas_limit: value.gas_limit(), + to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create), + value: value.value(), + input: value.inner.input, + }; + Ok(Self::Legacy(legacy.into_signed(signature))) + } + OpTxType::Eip2930 => { + let signature = extract_signature(&value)?; + let access_list_tx = TxEip2930 { + chain_id: value.chain_id().ok_or_else(|| { + TransactionConversionError::MissingRequiredField("chain_id".to_string()) + })?, + nonce: value.nonce(), + gas_price: value.gas_price().unwrap_or_default(), + gas_limit: value.gas_limit(), + to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create), + value: value.value(), + input: value.inner.input, + access_list: value.inner.access_list.unwrap_or_default(), + }; + Ok(Self::Eip2930(access_list_tx.into_signed(signature))) + } + OpTxType::Eip1559 => { + let signature = extract_signature(&value)?; + let dynamic_fee_tx = TxEip1559 { + chain_id: value.chain_id().ok_or_else(|| { + TransactionConversionError::MissingRequiredField("chain_id".to_string()) + })?, + nonce: value.nonce(), + gas_limit: value.gas_limit(), + to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create), + value: value.value(), + input: value.inner.input, + access_list: value.inner.access_list.unwrap_or_default(), + max_fee_per_gas: value.inner.max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: value + .inner + .max_priority_fee_per_gas + .unwrap_or_default(), + }; + Ok(Self::Eip1559(dynamic_fee_tx.into_signed(signature))) + } + OpTxType::Eip7702 => { + let signature = extract_signature(&value)?; + let set_code_tx = TxEip7702 { + chain_id: value.chain_id().ok_or_else(|| { + TransactionConversionError::MissingRequiredField("chain_id".to_string()) + })?, + nonce: value.nonce(), + gas_limit: value.gas_limit(), + to: value.inner.to.ok_or_else(|| { + TransactionConversionError::MissingRequiredField("to".to_string()) + })?, + value: value.value(), + input: value.inner.input, + access_list: value.inner.access_list.unwrap_or_default(), + max_fee_per_gas: value.inner.max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: value + .inner + .max_priority_fee_per_gas + .unwrap_or_default(), + authorization_list: value.inner.authorization_list.unwrap_or_default(), + }; + Ok(Self::Eip7702(set_code_tx.into_signed(signature))) + } + OpTxType::Deposit => { + let deposit_tx = TxDeposit { + source_hash: value.source_hash.ok_or_else(|| { + TransactionConversionError::MissingRequiredField("source_hash".to_string()) + })?, + from: value.inner.from, + to: value.inner.to.map(TxKind::Call).unwrap_or(TxKind::Create), + mint: value.mint, + value: value.inner.value, + gas_limit: value.gas_limit(), + is_system_transaction: value.is_system_tx.ok_or_else(|| { + TransactionConversionError::MissingRequiredField("is_system_tx".to_string()) + })?, + input: value.inner.input, + }; + Ok(Self::Deposit(deposit_tx)) + } + } + } +}