reth_codecs/alloy/transaction/
seismic.rs

1//! Compact implementation for [`AlloyTxSeismic`]
2
3use crate::{
4    txtype::{
5        COMPACT_EXTENDED_IDENTIFIER_FLAG, COMPACT_IDENTIFIER_EIP1559, COMPACT_IDENTIFIER_EIP2930,
6        COMPACT_IDENTIFIER_LEGACY,
7    },
8    Compact,
9};
10use alloy_consensus::{
11    transaction::{TxEip1559, TxEip2930, TxEip7702, TxLegacy},
12    Signed, TxEip4844Variant,
13};
14use alloy_consensus::TxEip4844;
15use alloy_eips::eip2718::{EIP7702_TX_TYPE_ID, EIP4844_TX_TYPE_ID};
16use alloy_primitives::{aliases::U96, Bytes, ChainId, Signature, TxKind, U256};
17use bytes::{Buf, BufMut, BytesMut};
18use reth_codecs_derive::generate_tests;
19use seismic_alloy_consensus::{
20    transaction::TxSeismicElements, SeismicTxEnvelope, SeismicTxType, SeismicTypedTransaction,
21    TxSeismic as AlloyTxSeismic, SEISMIC_TX_TYPE_ID,
22};
23
24use super::ethereum::{CompactEnvelope, Envelope, FromTxCompact, ToTxCompact};
25
26/// Seismic transaction.
27#[derive(Debug, Clone, PartialEq, Eq, Default, Compact)]
28#[reth_codecs(crate = "crate")]
29#[cfg_attr(
30    any(test, feature = "test-utils"),
31    derive(arbitrary::Arbitrary, serde::Serialize, serde::Deserialize),
32    crate::add_arbitrary_tests(crate, compact)
33)]
34#[cfg_attr(feature = "test-utils", allow(unreachable_pub), visibility::make(pub))]
35pub(crate) struct TxSeismic {
36    /// Added as EIP-155: Simple replay attack protection
37    chain_id: ChainId,
38    /// A scalar value equal to the number of transactions sent by the sender; formally Tn.
39    nonce: u64,
40    /// A scalar value equal to the number of
41    /// Wei to be paid per unit of gas for all computation
42    /// costs incurred as a result of the execution of this transaction; formally Tp.
43    ///
44    /// As ethereum circulation is around 120mil eth as of 2022 that is around
45    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
46    /// 340282366920938463463374607431768211455
47    gas_price: u128,
48    /// A scalar value equal to the maximum
49    /// amount of gas that should be used in executing
50    /// this transaction. This is paid up-front, before any
51    /// computation is done and may not be increased
52    /// later; formally Tg.
53    gas_limit: u64,
54    /// The 160-bit address of the message call’s recipient or, for a contract creation
55    /// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
56    to: TxKind,
57    /// A scalar value equal to the number of Wei to
58    /// be transferred to the message call’s recipient or,
59    /// in the case of contract creation, as an endowment
60    /// to the newly created account; formally Tv.
61    value: U256,
62    /// seismic elements
63    seismic_elements: TxSeismicElements,
64    /// Input has two uses depending if transaction is Create or Call (if `to` field is None or
65    /// Some). pub init: An unlimited size byte array specifying the
66    /// EVM-code for the account initialisation procedure CREATE,
67    /// data: An unlimited size byte array specifying the
68    /// input data of the message call, formally Td.
69    input: Bytes,
70}
71
72impl Compact for TxSeismicElements {
73    fn to_compact<B>(&self, buf: &mut B) -> usize
74    where
75        B: bytes::BufMut + AsMut<[u8]>,
76    {
77        let mut len = 0;
78        len += self.encryption_pubkey.serialize().to_compact(buf);
79
80        buf.put_u8(self.message_version);
81        len += core::mem::size_of::<u8>();
82
83        let mut cache = BytesMut::new();
84        let nonce_len = self.encryption_nonce.to_compact(&mut cache);
85        buf.put_u8(nonce_len as u8);
86        buf.put_slice(&cache);
87        len += nonce_len + 1;
88
89        len
90    }
91
92    fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
93        let encryption_pubkey_compressed_bytes =
94            &buf[..seismic_enclave::constants::PUBLIC_KEY_SIZE];
95        let encryption_pubkey =
96            seismic_enclave::PublicKey::from_slice(encryption_pubkey_compressed_bytes).unwrap();
97        buf.advance(seismic_enclave::constants::PUBLIC_KEY_SIZE);
98
99        let (message_version, buf) = (buf[0], &buf[1..]);
100
101        let (nonce_len, buf) = (buf[0], &buf[1..]);
102        let (encryption_nonce, buf) = U96::from_compact(buf, nonce_len as usize);
103        (Self { encryption_pubkey, encryption_nonce, message_version }, buf)
104    }
105}
106
107impl Compact for AlloyTxSeismic {
108    fn to_compact<B>(&self, buf: &mut B) -> usize
109    where
110        B: bytes::BufMut + AsMut<[u8]>,
111    {
112        let tx = TxSeismic {
113            chain_id: self.chain_id,
114            nonce: self.nonce,
115            gas_price: self.gas_price,
116            gas_limit: self.gas_limit,
117            to: self.to,
118            value: self.value,
119            seismic_elements: self.seismic_elements,
120            input: self.input.clone(),
121        };
122
123        tx.to_compact(buf)
124    }
125
126    fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
127        let (tx, _) = TxSeismic::from_compact(buf, len);
128
129        let alloy_tx = Self {
130            chain_id: tx.chain_id,
131            nonce: tx.nonce,
132            gas_price: tx.gas_price,
133            gas_limit: tx.gas_limit,
134            to: tx.to,
135            value: tx.value,
136            seismic_elements: tx.seismic_elements,
137            input: tx.input,
138        };
139
140        (alloy_tx, buf)
141    }
142}
143
144impl Compact for SeismicTxType {
145    fn to_compact<B>(&self, buf: &mut B) -> usize
146    where
147        B: bytes::BufMut + AsMut<[u8]>,
148    {
149        match self {
150            Self::Legacy => COMPACT_IDENTIFIER_LEGACY,
151            Self::Eip2930 => COMPACT_IDENTIFIER_EIP2930,
152            Self::Eip1559 => COMPACT_IDENTIFIER_EIP1559,
153            Self::Eip4844 => {
154                buf.put_u8(EIP4844_TX_TYPE_ID);
155                COMPACT_EXTENDED_IDENTIFIER_FLAG
156            }
157            Self::Eip7702 => {
158                buf.put_u8(EIP7702_TX_TYPE_ID);
159                COMPACT_EXTENDED_IDENTIFIER_FLAG
160            }
161            Self::Seismic => {
162                buf.put_u8(SEISMIC_TX_TYPE_ID);
163                COMPACT_EXTENDED_IDENTIFIER_FLAG
164            }
165        }
166    }
167
168    fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
169        use bytes::Buf;
170        (
171            match identifier {
172                COMPACT_IDENTIFIER_LEGACY => Self::Legacy,
173                COMPACT_IDENTIFIER_EIP2930 => Self::Eip2930,
174                COMPACT_IDENTIFIER_EIP1559 => Self::Eip1559,
175                COMPACT_EXTENDED_IDENTIFIER_FLAG => {
176                    let extended_identifier = buf.get_u8();
177                    match extended_identifier {
178                        EIP7702_TX_TYPE_ID => Self::Eip7702,
179                        SEISMIC_TX_TYPE_ID => Self::Seismic,
180                        _ => panic!("Unsupported TxType identifier: {extended_identifier}"),
181                    }
182                }
183                _ => panic!("Unknown identifier for TxType: {identifier}"),
184            },
185            buf,
186        )
187    }
188}
189
190impl Compact for SeismicTypedTransaction {
191    fn to_compact<B>(&self, out: &mut B) -> usize
192    where
193        B: bytes::BufMut + AsMut<[u8]>,
194    {
195        let identifier = self.tx_type().to_compact(out);
196        match self {
197            Self::Legacy(tx) => tx.to_compact(out),
198            Self::Eip2930(tx) => tx.to_compact(out),
199            Self::Eip1559(tx) => tx.to_compact(out),
200            Self::Eip4844(tx) => {
201                match tx {
202                    TxEip4844Variant::TxEip4844(tx) => tx.to_compact(out),
203                    TxEip4844Variant::TxEip4844WithSidecar(tx) => {
204                        // we do not have a way to encode the sidecar, so we just encode the inner
205                        let inner: &TxEip4844 = tx.tx();
206                        inner.to_compact(out)
207                    },
208                }
209            },
210            Self::Eip7702(tx) => tx.to_compact(out),
211            Self::Seismic(tx) => tx.to_compact(out),
212        };
213        identifier
214    }
215
216    fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
217        let (tx_type, buf) = SeismicTxType::from_compact(buf, identifier);
218        match tx_type {
219            SeismicTxType::Legacy => {
220                let (tx, buf) = Compact::from_compact(buf, buf.len());
221                (Self::Legacy(tx), buf)
222            }
223            SeismicTxType::Eip2930 => {
224                let (tx, buf) = Compact::from_compact(buf, buf.len());
225                (Self::Eip2930(tx), buf)
226            }
227            SeismicTxType::Eip1559 => {
228                let (tx, buf) = Compact::from_compact(buf, buf.len());
229                (Self::Eip1559(tx), buf)
230            }
231            SeismicTxType::Eip4844 => {
232                let (tx, buf): (TxEip4844, _) = Compact::from_compact(buf, buf.len());
233                let tx = TxEip4844Variant::TxEip4844(tx);
234                (Self::Eip4844(tx), buf)
235            }
236            SeismicTxType::Eip7702 => {
237                let (tx, buf) = Compact::from_compact(buf, buf.len());
238                (Self::Eip7702(tx), buf)
239            }
240            SeismicTxType::Seismic => {
241                let (tx, buf) = Compact::from_compact(buf, buf.len());
242                (Self::Seismic(tx), buf)
243            }
244        }
245    }
246}
247
248impl ToTxCompact for SeismicTxEnvelope {
249    fn to_tx_compact(&self, buf: &mut (impl BufMut + AsMut<[u8]>)) {
250        match self {
251            Self::Legacy(tx) => tx.tx().to_compact(buf),
252            Self::Eip2930(tx) => tx.tx().to_compact(buf),
253            Self::Eip1559(tx) => tx.tx().to_compact(buf),
254            Self::Eip4844(tx) => {
255                match tx.tx() {
256                    TxEip4844Variant::TxEip4844(tx) => tx.to_compact(buf),
257                    TxEip4844Variant::TxEip4844WithSidecar(tx) => {
258                        Compact::to_compact(&tx.tx(), buf)
259                    },
260                }
261            },
262            Self::Eip7702(tx) => tx.tx().to_compact(buf),
263            Self::Seismic(tx) => tx.tx().to_compact(buf),
264        };
265    }
266}
267
268impl FromTxCompact for SeismicTxEnvelope {
269    type TxType = SeismicTxType;
270
271    fn from_tx_compact(
272        buf: &[u8],
273        tx_type: SeismicTxType,
274        signature: Signature,
275    ) -> (Self, &[u8]) {
276        match tx_type {
277            SeismicTxType::Legacy => {
278                let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
279                let tx = Signed::new_unhashed(tx, signature);
280                (Self::Legacy(tx), buf)
281            }
282            SeismicTxType::Eip2930 => {
283                let (tx, buf) = TxEip2930::from_compact(buf, buf.len());
284                let tx = Signed::new_unhashed(tx, signature);
285                (Self::Eip2930(tx), buf)
286            }
287            SeismicTxType::Eip1559 => {
288                let (tx, buf) = TxEip1559::from_compact(buf, buf.len());
289                let tx = Signed::new_unhashed(tx, signature);
290                (Self::Eip1559(tx), buf)
291            }
292            SeismicTxType::Eip4844 => {
293                let (variant_tag, rest) = buf.split_first().expect("buffer should not be empty");
294            
295                match variant_tag {
296                    0 => {
297                        let (tx, buf) = TxEip4844::from_compact(rest, rest.len());
298                        let tx = Signed::new_unhashed(TxEip4844Variant::TxEip4844(tx), signature);
299                        (Self::Eip4844(tx), buf)
300                    }
301                    1 => unreachable!("seismic does not serialize sidecars yet"),
302                    _ => panic!("Unknown EIP-4844 variant tag: {}", variant_tag),
303                }
304            }
305            SeismicTxType::Eip7702 => {
306                let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
307                let tx = Signed::new_unhashed(tx, signature);
308                (Self::Eip7702(tx), buf)
309            }
310            SeismicTxType::Seismic => {
311                let (tx, buf) = AlloyTxSeismic::from_compact(buf, buf.len());
312                let tx = Signed::new_unhashed(tx, signature);
313                (Self::Seismic(tx), buf)
314            }
315        }
316    }
317}
318
319impl Envelope for SeismicTxEnvelope {
320    fn signature(&self) -> &Signature {
321        match self {
322            Self::Legacy(tx) => tx.signature(),
323            Self::Eip2930(tx) => tx.signature(),
324            Self::Eip1559(tx) => tx.signature(),
325            Self::Eip4844(tx) => tx.signature(),
326            Self::Eip7702(tx) => tx.signature(),
327            Self::Seismic(tx) => tx.signature(),
328        }
329    }
330
331    fn tx_type(&self) -> Self::TxType {
332        Self::tx_type(self)
333    }
334}
335
336impl Compact for SeismicTxEnvelope {
337    fn to_compact<B>(&self, buf: &mut B) -> usize
338    where
339        B: BufMut + AsMut<[u8]>,
340    {
341        CompactEnvelope::to_compact(self, buf)
342    }
343
344    fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
345        CompactEnvelope::from_compact(buf, len)
346    }
347}
348
349generate_tests!(#[crate, compact] SeismicTypedTransaction, SeismicTypedTransactionTests);
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354    use alloy_primitives::{hex, Bytes, TxKind};
355    use bytes::BytesMut;
356    use seismic_enclave::PublicKey;
357
358    #[test]
359    fn test_seismic_tx_compact_roundtrip() {
360        // Create a test transaction based on the example in file_context_0
361        let tx = AlloyTxSeismic {
362            chain_id: 1166721750861005481,
363            nonce: 13985005159674441909,
364            gas_price: 296133358425745351516777806240018869443,
365            gas_limit: 6091425913586946366,
366            to: TxKind::Create,
367            value: U256::from_str_radix(
368                "30997721070913355446596643088712595347117842472993214294164452566768407578853",
369                10,
370            )
371            .unwrap(),
372            seismic_elements: TxSeismicElements {
373                encryption_pubkey: PublicKey::from_slice(
374                    &hex::decode(
375                        "02d211b6b0a191b9469bb3674e9c609f453d3801c3e3fd7e0bb00c6cc1e1d941df",
376                    )
377                    .unwrap(),
378                )
379                .unwrap(),
380                encryption_nonce: U96::from_str_radix("11856476099097235301", 10).unwrap(),
381                message_version: 85,
382            },
383            input: Bytes::from_static(&[0x24]),
384        };
385
386        // Encode to compact format
387        let mut buf = BytesMut::new();
388        let encoded_size = tx.to_compact(&mut buf);
389
390        // Decode from compact format
391        let (decoded_tx, _) = AlloyTxSeismic::from_compact(&buf, encoded_size);
392
393        // Verify the roundtrip
394        assert_eq!(tx.chain_id, decoded_tx.chain_id);
395        assert_eq!(tx.nonce, decoded_tx.nonce);
396        assert_eq!(tx.gas_price, decoded_tx.gas_price);
397        assert_eq!(tx.gas_limit, decoded_tx.gas_limit);
398        assert_eq!(tx.to, decoded_tx.to);
399        assert_eq!(tx.value, decoded_tx.value);
400        assert_eq!(tx.input, decoded_tx.input);
401
402        // Check seismic elements
403        assert_eq!(
404            tx.seismic_elements.encryption_pubkey.serialize(),
405            decoded_tx.seismic_elements.encryption_pubkey.serialize()
406        );
407        assert_eq!(
408            tx.seismic_elements.encryption_nonce,
409            decoded_tx.seismic_elements.encryption_nonce
410        );
411        assert_eq!(
412            tx.seismic_elements.message_version,
413            decoded_tx.seismic_elements.message_version
414        );
415    }
416}