reth_codecs/alloy/transaction/
seismic.rs

1//! Compact implementation for [`AlloyTxSeismic`]
2
3use crate::Compact;
4use alloy_consensus::{transaction::TxSeismicElements, TxSeismic as AlloyTxSeismic};
5use alloy_primitives::{aliases::U96, Bytes, ChainId, TxKind, U256};
6use bytes::{Buf, BytesMut};
7
8/// Seismic transaction.
9#[derive(Debug, Clone, PartialEq, Eq, Default, Compact)]
10#[reth_codecs(crate = "crate")]
11#[cfg_attr(
12    any(test, feature = "test-utils"),
13    derive(arbitrary::Arbitrary, serde::Serialize, serde::Deserialize),
14    crate::add_arbitrary_tests(crate, compact)
15)]
16#[cfg_attr(feature = "test-utils", allow(unreachable_pub), visibility::make(pub))]
17pub(crate) struct TxSeismic {
18    /// Added as EIP-155: Simple replay attack protection
19    chain_id: ChainId,
20    /// A scalar value equal to the number of transactions sent by the sender; formally Tn.
21    nonce: u64,
22    /// A scalar value equal to the number of
23    /// Wei to be paid per unit of gas for all computation
24    /// costs incurred as a result of the execution of this transaction; formally Tp.
25    ///
26    /// As ethereum circulation is around 120mil eth as of 2022 that is around
27    /// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
28    /// 340282366920938463463374607431768211455
29    gas_price: u128,
30    /// A scalar value equal to the maximum
31    /// amount of gas that should be used in executing
32    /// this transaction. This is paid up-front, before any
33    /// computation is done and may not be increased
34    /// later; formally Tg.
35    gas_limit: u64,
36    /// The 160-bit address of the message call’s recipient or, for a contract creation
37    /// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
38    to: TxKind,
39    /// A scalar value equal to the number of Wei to
40    /// be transferred to the message call’s recipient or,
41    /// in the case of contract creation, as an endowment
42    /// to the newly created account; formally Tv.
43    value: U256,
44    /// seismic elements
45    seismic_elements: TxSeismicElements,
46    /// Input has two uses depending if transaction is Create or Call (if `to` field is None or
47    /// Some). pub init: An unlimited size byte array specifying the
48    /// EVM-code for the account initialisation procedure CREATE,
49    /// data: An unlimited size byte array specifying the
50    /// input data of the message call, formally Td.
51    input: Bytes,
52}
53
54impl Compact for TxSeismicElements {
55    fn to_compact<B>(&self, buf: &mut B) -> usize
56    where
57        B: bytes::BufMut + AsMut<[u8]>,
58    {
59        let mut len = 0;
60        len += self.encryption_pubkey.serialize().to_compact(buf);
61
62        buf.put_u8(self.message_version);
63        len += core::mem::size_of::<u8>();
64
65        let mut cache = BytesMut::new();
66        let nonce_len = self.encryption_nonce.to_compact(&mut cache);
67        buf.put_u8(nonce_len as u8);
68        buf.put_slice(&cache);
69        len += nonce_len + 1;
70
71        len
72    }
73
74    fn from_compact(mut buf: &[u8], _len: usize) -> (Self, &[u8]) {
75        let encryption_pubkey_compressed_bytes =
76            &buf[..seismic_enclave::constants::PUBLIC_KEY_SIZE];
77        let encryption_pubkey =
78            seismic_enclave::PublicKey::from_slice(encryption_pubkey_compressed_bytes).unwrap();
79        buf.advance(seismic_enclave::constants::PUBLIC_KEY_SIZE);
80
81        let (message_version, buf) = (buf[0], &buf[1..]);
82
83        let (nonce_len, buf) = (buf[0], &buf[1..]);
84        let (encryption_nonce, buf) = U96::from_compact(buf, nonce_len as usize);
85        (Self { encryption_pubkey, encryption_nonce, message_version }, buf)
86    }
87}
88
89impl Compact for AlloyTxSeismic {
90    fn to_compact<B>(&self, buf: &mut B) -> usize
91    where
92        B: bytes::BufMut + AsMut<[u8]>,
93    {
94        let tx = TxSeismic {
95            chain_id: self.chain_id,
96            nonce: self.nonce,
97            gas_price: self.gas_price,
98            gas_limit: self.gas_limit,
99            to: self.to,
100            value: self.value,
101            seismic_elements: self.seismic_elements,
102            input: self.input.clone(),
103        };
104
105        tx.to_compact(buf)
106    }
107
108    fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
109        let (tx, _) = TxSeismic::from_compact(buf, len);
110
111        let alloy_tx = Self {
112            chain_id: tx.chain_id,
113            nonce: tx.nonce,
114            gas_price: tx.gas_price,
115            gas_limit: tx.gas_limit,
116            to: tx.to,
117            value: tx.value,
118            seismic_elements: tx.seismic_elements,
119            input: tx.input,
120        };
121
122        (alloy_tx, buf)
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use alloy_primitives::{hex, Bytes, TxKind};
130    use bytes::BytesMut;
131    use seismic_enclave::PublicKey;
132
133    #[test]
134    fn test_seismic_tx_compact_roundtrip() {
135        // Create a test transaction based on the example in file_context_0
136        let tx = AlloyTxSeismic {
137            chain_id: 1166721750861005481,
138            nonce: 13985005159674441909,
139            gas_price: 296133358425745351516777806240018869443,
140            gas_limit: 6091425913586946366,
141            to: TxKind::Create,
142            value: U256::from_str_radix(
143                "30997721070913355446596643088712595347117842472993214294164452566768407578853",
144                10,
145            )
146            .unwrap(),
147            seismic_elements: TxSeismicElements {
148                encryption_pubkey: PublicKey::from_slice(
149                    &hex::decode(
150                        "02d211b6b0a191b9469bb3674e9c609f453d3801c3e3fd7e0bb00c6cc1e1d941df",
151                    )
152                    .unwrap(),
153                )
154                .unwrap(),
155                encryption_nonce: U96::from_str_radix("11856476099097235301", 10).unwrap(),
156                message_version: 85,
157            },
158            input: Bytes::from_static(&[0x24]),
159        };
160
161        // Encode to compact format
162        let mut buf = BytesMut::new();
163        let encoded_size = tx.to_compact(&mut buf);
164
165        // Decode from compact format
166        let (decoded_tx, _) = AlloyTxSeismic::from_compact(&buf, encoded_size);
167
168        // Verify the roundtrip
169        assert_eq!(tx.chain_id, decoded_tx.chain_id);
170        assert_eq!(tx.nonce, decoded_tx.nonce);
171        assert_eq!(tx.gas_price, decoded_tx.gas_price);
172        assert_eq!(tx.gas_limit, decoded_tx.gas_limit);
173        assert_eq!(tx.to, decoded_tx.to);
174        assert_eq!(tx.value, decoded_tx.value);
175        assert_eq!(tx.input, decoded_tx.input);
176
177        // Check seismic elements
178        assert_eq!(
179            tx.seismic_elements.encryption_pubkey.serialize(),
180            decoded_tx.seismic_elements.encryption_pubkey.serialize()
181        );
182        assert_eq!(
183            tx.seismic_elements.encryption_nonce,
184            decoded_tx.seismic_elements.encryption_nonce
185        );
186        assert_eq!(
187            tx.seismic_elements.message_version,
188            decoded_tx.seismic_elements.message_version
189        );
190    }
191}