1use crate::{OpTxEnvelope, OpTxType};
5use alloy_consensus::{
6 SignableTransaction, Signed, Transaction, TxEip7702, TxEnvelope, Typed2718,
7 error::ValueError,
8 transaction::{RlpEcdsaDecodableTx, TxEip1559, TxEip2930, TxLegacy},
9};
10use alloy_eips::{
11 eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
12 eip2930::AccessList,
13 eip7702::SignedAuthorization,
14};
15use alloy_primitives::{B256, Bytes, ChainId, Signature, TxHash, TxKind, U256, bytes};
16use alloy_rlp::{Decodable, Encodable, Header};
17use core::hash::{Hash, Hasher};
18
19#[derive(Clone, Debug, PartialEq, Eq)]
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
27#[cfg_attr(all(any(test, feature = "arbitrary"), feature = "k256"), derive(arbitrary::Arbitrary))]
28pub enum OpPooledTransaction {
29 Legacy(Signed<TxLegacy>),
31 Eip2930(Signed<TxEip2930>),
33 Eip1559(Signed<TxEip1559>),
35 Eip7702(Signed<TxEip7702>),
37}
38
39impl OpPooledTransaction {
40 pub fn signature_hash(&self) -> B256 {
43 match self {
44 Self::Legacy(tx) => tx.signature_hash(),
45 Self::Eip2930(tx) => tx.signature_hash(),
46 Self::Eip1559(tx) => tx.signature_hash(),
47 Self::Eip7702(tx) => tx.signature_hash(),
48 }
49 }
50
51 pub fn hash(&self) -> &TxHash {
53 match self {
54 Self::Legacy(tx) => tx.hash(),
55 Self::Eip2930(tx) => tx.hash(),
56 Self::Eip1559(tx) => tx.hash(),
57 Self::Eip7702(tx) => tx.hash(),
58 }
59 }
60
61 pub const fn signature(&self) -> &Signature {
63 match self {
64 Self::Legacy(tx) => tx.signature(),
65 Self::Eip2930(tx) => tx.signature(),
66 Self::Eip1559(tx) => tx.signature(),
67 Self::Eip7702(tx) => tx.signature(),
68 }
69 }
70
71 fn network_len(&self) -> usize {
74 let mut payload_length = self.encode_2718_len();
75 if !self.is_legacy() {
76 payload_length += Header { list: false, payload_length }.length();
77 }
78
79 payload_length
80 }
81
82 #[cfg(feature = "k256")]
84 pub fn recover_signer(
85 &self,
86 ) -> Result<alloy_primitives::Address, alloy_primitives::SignatureError> {
87 match self {
88 Self::Legacy(tx) => tx.recover_signer(),
89 Self::Eip2930(tx) => tx.recover_signer(),
90 Self::Eip1559(tx) => tx.recover_signer(),
91 Self::Eip7702(tx) => tx.recover_signer(),
92 }
93 }
94
95 pub fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
98 match self {
99 Self::Legacy(tx) => tx.tx().encode_for_signing(out),
100 Self::Eip2930(tx) => tx.tx().encode_for_signing(out),
101 Self::Eip1559(tx) => tx.tx().encode_for_signing(out),
102 Self::Eip7702(tx) => tx.tx().encode_for_signing(out),
103 }
104 }
105
106 pub fn into_envelope(self) -> TxEnvelope {
108 match self {
109 Self::Legacy(tx) => tx.into(),
110 Self::Eip2930(tx) => tx.into(),
111 Self::Eip1559(tx) => tx.into(),
112 Self::Eip7702(tx) => tx.into(),
113 }
114 }
115
116 pub fn into_op_envelope(self) -> OpTxEnvelope {
118 match self {
119 Self::Legacy(tx) => tx.into(),
120 Self::Eip2930(tx) => tx.into(),
121 Self::Eip1559(tx) => tx.into(),
122 Self::Eip7702(tx) => tx.into(),
123 }
124 }
125
126 pub const fn as_legacy(&self) -> Option<&TxLegacy> {
128 match self {
129 Self::Legacy(tx) => Some(tx.tx()),
130 _ => None,
131 }
132 }
133
134 pub const fn as_eip2930(&self) -> Option<&TxEip2930> {
136 match self {
137 Self::Eip2930(tx) => Some(tx.tx()),
138 _ => None,
139 }
140 }
141
142 pub const fn as_eip1559(&self) -> Option<&TxEip1559> {
144 match self {
145 Self::Eip1559(tx) => Some(tx.tx()),
146 _ => None,
147 }
148 }
149
150 pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
152 match self {
153 Self::Eip7702(tx) => Some(tx.tx()),
154 _ => None,
155 }
156 }
157}
158
159impl From<Signed<TxLegacy>> for OpPooledTransaction {
160 fn from(v: Signed<TxLegacy>) -> Self {
161 Self::Legacy(v)
162 }
163}
164
165impl From<Signed<TxEip2930>> for OpPooledTransaction {
166 fn from(v: Signed<TxEip2930>) -> Self {
167 Self::Eip2930(v)
168 }
169}
170
171impl From<Signed<TxEip1559>> for OpPooledTransaction {
172 fn from(v: Signed<TxEip1559>) -> Self {
173 Self::Eip1559(v)
174 }
175}
176
177impl From<Signed<TxEip7702>> for OpPooledTransaction {
178 fn from(v: Signed<TxEip7702>) -> Self {
179 Self::Eip7702(v)
180 }
181}
182
183impl From<OpPooledTransaction> for alloy_consensus::transaction::PooledTransaction {
184 fn from(value: OpPooledTransaction) -> Self {
185 match value {
186 OpPooledTransaction::Legacy(tx) => tx.into(),
187 OpPooledTransaction::Eip2930(tx) => tx.into(),
188 OpPooledTransaction::Eip1559(tx) => tx.into(),
189 OpPooledTransaction::Eip7702(tx) => tx.into(),
190 }
191 }
192}
193
194impl Hash for OpPooledTransaction {
195 fn hash<H: Hasher>(&self, state: &mut H) {
196 self.trie_hash().hash(state);
197 }
198}
199
200impl Encodable for OpPooledTransaction {
201 fn encode(&self, out: &mut dyn bytes::BufMut) {
210 self.network_encode(out);
211 }
212
213 fn length(&self) -> usize {
214 self.network_len()
215 }
216}
217
218impl Decodable for OpPooledTransaction {
219 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
223 Ok(Self::network_decode(buf)?)
224 }
225}
226
227impl Encodable2718 for OpPooledTransaction {
228 fn type_flag(&self) -> Option<u8> {
229 match self {
230 Self::Legacy(_) => None,
231 Self::Eip2930(_) => Some(0x01),
232 Self::Eip1559(_) => Some(0x02),
233 Self::Eip7702(_) => Some(0x04),
234 }
235 }
236
237 fn encode_2718_len(&self) -> usize {
238 match self {
239 Self::Legacy(tx) => tx.eip2718_encoded_length(),
240 Self::Eip2930(tx) => tx.eip2718_encoded_length(),
241 Self::Eip1559(tx) => tx.eip2718_encoded_length(),
242 Self::Eip7702(tx) => tx.eip2718_encoded_length(),
243 }
244 }
245
246 fn encode_2718(&self, out: &mut dyn alloy_rlp::BufMut) {
247 match self {
248 Self::Legacy(tx) => tx.eip2718_encode(out),
249 Self::Eip2930(tx) => tx.eip2718_encode(out),
250 Self::Eip1559(tx) => tx.eip2718_encode(out),
251 Self::Eip7702(tx) => tx.eip2718_encode(out),
252 }
253 }
254
255 fn trie_hash(&self) -> B256 {
256 *self.hash()
257 }
258}
259
260impl Decodable2718 for OpPooledTransaction {
261 fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
262 match ty.try_into().map_err(|_| alloy_rlp::Error::Custom("unexpected tx type"))? {
263 OpTxType::Eip2930 => Ok(TxEip2930::rlp_decode_signed(buf)?.into()),
264 OpTxType::Eip1559 => Ok(TxEip1559::rlp_decode_signed(buf)?.into()),
265 OpTxType::Eip7702 => Ok(TxEip7702::rlp_decode_signed(buf)?.into()),
266 OpTxType::Legacy => Err(Eip2718Error::UnexpectedType(OpTxType::Legacy.into())),
267 OpTxType::Deposit => Err(Eip2718Error::UnexpectedType(OpTxType::Deposit.into())),
268 }
269 }
270
271 fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
272 TxLegacy::rlp_decode_signed(buf).map(Into::into).map_err(Into::into)
273 }
274}
275
276impl Transaction for OpPooledTransaction {
277 fn chain_id(&self) -> Option<ChainId> {
278 match self {
279 Self::Legacy(tx) => tx.tx().chain_id(),
280 Self::Eip2930(tx) => tx.tx().chain_id(),
281 Self::Eip1559(tx) => tx.tx().chain_id(),
282 Self::Eip7702(tx) => tx.tx().chain_id(),
283 }
284 }
285
286 fn nonce(&self) -> u64 {
287 match self {
288 Self::Legacy(tx) => tx.tx().nonce(),
289 Self::Eip2930(tx) => tx.tx().nonce(),
290 Self::Eip1559(tx) => tx.tx().nonce(),
291 Self::Eip7702(tx) => tx.tx().nonce(),
292 }
293 }
294
295 fn gas_limit(&self) -> u64 {
296 match self {
297 Self::Legacy(tx) => tx.tx().gas_limit(),
298 Self::Eip2930(tx) => tx.tx().gas_limit(),
299 Self::Eip1559(tx) => tx.tx().gas_limit(),
300 Self::Eip7702(tx) => tx.tx().gas_limit(),
301 }
302 }
303
304 fn gas_price(&self) -> Option<u128> {
305 match self {
306 Self::Legacy(tx) => tx.tx().gas_price(),
307 Self::Eip2930(tx) => tx.tx().gas_price(),
308 Self::Eip1559(tx) => tx.tx().gas_price(),
309 Self::Eip7702(tx) => tx.tx().gas_price(),
310 }
311 }
312
313 fn max_fee_per_gas(&self) -> u128 {
314 match self {
315 Self::Legacy(tx) => tx.tx().max_fee_per_gas(),
316 Self::Eip2930(tx) => tx.tx().max_fee_per_gas(),
317 Self::Eip1559(tx) => tx.tx().max_fee_per_gas(),
318 Self::Eip7702(tx) => tx.tx().max_fee_per_gas(),
319 }
320 }
321
322 fn max_priority_fee_per_gas(&self) -> Option<u128> {
323 match self {
324 Self::Legacy(tx) => tx.tx().max_priority_fee_per_gas(),
325 Self::Eip2930(tx) => tx.tx().max_priority_fee_per_gas(),
326 Self::Eip1559(tx) => tx.tx().max_priority_fee_per_gas(),
327 Self::Eip7702(tx) => tx.tx().max_priority_fee_per_gas(),
328 }
329 }
330
331 fn max_fee_per_blob_gas(&self) -> Option<u128> {
332 match self {
333 Self::Legacy(tx) => tx.tx().max_fee_per_blob_gas(),
334 Self::Eip2930(tx) => tx.tx().max_fee_per_blob_gas(),
335 Self::Eip1559(tx) => tx.tx().max_fee_per_blob_gas(),
336 Self::Eip7702(tx) => tx.tx().max_fee_per_blob_gas(),
337 }
338 }
339
340 fn priority_fee_or_price(&self) -> u128 {
341 match self {
342 Self::Legacy(tx) => tx.tx().priority_fee_or_price(),
343 Self::Eip2930(tx) => tx.tx().priority_fee_or_price(),
344 Self::Eip1559(tx) => tx.tx().priority_fee_or_price(),
345 Self::Eip7702(tx) => tx.tx().priority_fee_or_price(),
346 }
347 }
348
349 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
350 match self {
351 Self::Legacy(tx) => tx.tx().effective_gas_price(base_fee),
352 Self::Eip2930(tx) => tx.tx().effective_gas_price(base_fee),
353 Self::Eip1559(tx) => tx.tx().effective_gas_price(base_fee),
354 Self::Eip7702(tx) => tx.tx().effective_gas_price(base_fee),
355 }
356 }
357
358 fn is_dynamic_fee(&self) -> bool {
359 match self {
360 Self::Legacy(tx) => tx.tx().is_dynamic_fee(),
361 Self::Eip2930(tx) => tx.tx().is_dynamic_fee(),
362 Self::Eip1559(tx) => tx.tx().is_dynamic_fee(),
363 Self::Eip7702(tx) => tx.tx().is_dynamic_fee(),
364 }
365 }
366
367 fn kind(&self) -> TxKind {
368 match self {
369 Self::Legacy(tx) => tx.tx().kind(),
370 Self::Eip2930(tx) => tx.tx().kind(),
371 Self::Eip1559(tx) => tx.tx().kind(),
372 Self::Eip7702(tx) => tx.tx().kind(),
373 }
374 }
375
376 fn is_create(&self) -> bool {
377 match self {
378 Self::Legacy(tx) => tx.tx().is_create(),
379 Self::Eip2930(tx) => tx.tx().is_create(),
380 Self::Eip1559(tx) => tx.tx().is_create(),
381 Self::Eip7702(tx) => tx.tx().is_create(),
382 }
383 }
384
385 fn value(&self) -> U256 {
386 match self {
387 Self::Legacy(tx) => tx.tx().value(),
388 Self::Eip2930(tx) => tx.tx().value(),
389 Self::Eip1559(tx) => tx.tx().value(),
390 Self::Eip7702(tx) => tx.tx().value(),
391 }
392 }
393
394 fn input(&self) -> &Bytes {
395 match self {
396 Self::Legacy(tx) => tx.tx().input(),
397 Self::Eip2930(tx) => tx.tx().input(),
398 Self::Eip1559(tx) => tx.tx().input(),
399 Self::Eip7702(tx) => tx.tx().input(),
400 }
401 }
402
403 fn access_list(&self) -> Option<&AccessList> {
404 match self {
405 Self::Legacy(tx) => tx.tx().access_list(),
406 Self::Eip2930(tx) => tx.tx().access_list(),
407 Self::Eip1559(tx) => tx.tx().access_list(),
408 Self::Eip7702(tx) => tx.tx().access_list(),
409 }
410 }
411
412 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
413 match self {
414 Self::Legacy(tx) => tx.tx().blob_versioned_hashes(),
415 Self::Eip2930(tx) => tx.tx().blob_versioned_hashes(),
416 Self::Eip1559(tx) => tx.tx().blob_versioned_hashes(),
417 Self::Eip7702(tx) => tx.tx().blob_versioned_hashes(),
418 }
419 }
420
421 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
422 match self {
423 Self::Legacy(tx) => tx.tx().authorization_list(),
424 Self::Eip2930(tx) => tx.tx().authorization_list(),
425 Self::Eip1559(tx) => tx.tx().authorization_list(),
426 Self::Eip7702(tx) => tx.tx().authorization_list(),
427 }
428 }
429}
430
431impl Typed2718 for OpPooledTransaction {
432 fn ty(&self) -> u8 {
433 match self {
434 Self::Legacy(tx) => tx.tx().ty(),
435 Self::Eip2930(tx) => tx.tx().ty(),
436 Self::Eip1559(tx) => tx.tx().ty(),
437 Self::Eip7702(tx) => tx.tx().ty(),
438 }
439 }
440}
441
442impl From<OpPooledTransaction> for TxEnvelope {
443 fn from(tx: OpPooledTransaction) -> Self {
444 tx.into_envelope()
445 }
446}
447
448impl From<OpPooledTransaction> for OpTxEnvelope {
449 fn from(tx: OpPooledTransaction) -> Self {
450 tx.into_op_envelope()
451 }
452}
453
454impl TryFrom<OpTxEnvelope> for OpPooledTransaction {
455 type Error = ValueError<OpTxEnvelope>;
456
457 fn try_from(value: OpTxEnvelope) -> Result<Self, Self::Error> {
458 value.try_into_pooled()
459 }
460}
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use alloy_primitives::{address, hex};
466 use bytes::Bytes;
467
468 #[test]
469 fn invalid_legacy_pooled_decoding_input_too_short() {
470 let input_too_short = [
471 &hex!("d90b0280808bc5cd028083c5cdfd9e407c56565656")[..],
473 &hex!("c10b02808083c5cd028883c5cdfd9e407c56565656"),
479 &hex!("c10b0280808bc5cd028083c5cdfd9e407c56565656"),
480 &hex!("d40b02808083c5cdeb8783c5acfd9e407c5656565656"),
483 &hex!("d30102808083c5cd02887dc5cdfd9e64fd9e407c56"),
484 ];
485
486 for hex_data in &input_too_short {
487 let input_rlp = &mut &hex_data[..];
488 let res = OpPooledTransaction::decode(input_rlp);
489
490 assert!(
491 res.is_err(),
492 "expected err after decoding rlp input: {:x?}",
493 Bytes::copy_from_slice(hex_data)
494 );
495
496 let input_rlp = &mut &hex_data[..];
498 let res = OpPooledTransaction::decode_2718(input_rlp);
499
500 assert!(
501 res.is_err(),
502 "expected err after decoding enveloped rlp input: {:x?}",
503 Bytes::copy_from_slice(hex_data)
504 );
505 }
506 }
507
508 #[test]
510 fn decode_eip1559_enveloped() {
511 let data = hex!(
512 "02f903d382426882ba09832dc6c0848674742682ed9694714b6a4ea9b94a8a7d9fd362ed72630688c8898c80b90364492d24749189822d8512430d3f3ff7a2ede675ac08265c08e2c56ff6fdaa66dae1cdbe4a5d1d7809f3e99272d067364e597542ac0c369d69e22a6399c3e9bee5da4b07e3f3fdc34c32c3d88aa2268785f3e3f8086df0934b10ef92cfffc2e7f3d90f5e83302e31382e302d64657600000000000000000000000000000000000000000000569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000e1e210594771824dad216568b91c9cb4ceed361c00000000000000000000000000000000000000000000000000000000000546e00000000000000000000000000000000000000000000000000000000000e4e1c00000000000000000000000000000000000000000000000000000000065d6750c00000000000000000000000000000000000000000000000000000000000f288000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002cf600000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000000f1628e56fa6d8c50e5b984a58c0df14de31c7b857ce7ba499945b99252976a93d06dcda6776fc42167fbe71cb59f978f5ef5b12577a90b132d14d9c6efa528076f0161d7bf03643cfc5490ec5084f4a041db7f06c50bd97efa08907ba79ddcac8b890f24d12d8db31abbaaf18985d54f400449ee0559a4452afe53de5853ce090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000064ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000c080a01428023fc54a27544abc421d5d017b9a7c5936ad501cbdecd0d9d12d04c1a033a0753104bbf1c87634d6ff3f0ffa0982710612306003eb022363b57994bdef445a"
513 );
514
515 let res = OpPooledTransaction::decode_2718(&mut &data[..]).unwrap();
516 assert_eq!(res.to(), Some(address!("714b6a4ea9b94a8a7d9fd362ed72630688c8898c")));
517 }
518
519 #[test]
520 fn legacy_valid_pooled_decoding() {
521 let data = &hex!("d30b02808083c5cdeb8783c5acfd9e407c565656")[..];
532
533 let input_rlp = &mut &data[..];
534 let res = OpPooledTransaction::decode(input_rlp);
535 assert!(res.is_ok());
536 assert!(input_rlp.is_empty());
537
538 let res = OpPooledTransaction::decode_2718(&mut &data[..]);
540 assert!(res.is_ok());
541 }
542}