reth_seismic_rpc/eth/
utils.rs

1//! Utils for testing the seismic rpc api
2
3use alloy_rpc_types::TransactionRequest;
4use reth_primitives::Recovered;
5use reth_primitives_traits::SignedTransaction;
6use reth_rpc_eth_types::{utils::recover_raw_transaction, EthApiError, EthResult};
7use seismic_alloy_consensus::{Decodable712, SeismicTxEnvelope, TypedDataRequest};
8use seismic_alloy_network::{SeismicReth, TransactionBuilder};
9use seismic_alloy_rpc_types::{SeismicCallRequest, SeismicTransactionRequest};
10
11/// Override the request for seismic calls
12pub fn seismic_override_call_request(request: &mut TransactionRequest) {
13    // If user calls with the standard (unsigned) eth_call,
14    // then disregard whatever they put in the from field
15    // They will still be able to read public contract functions,
16    // but they will not be able to spoof msg.sender in these calls
17    request.from = None;
18    request.gas_price = None; // preventing InsufficientFunds error
19    request.max_fee_per_gas = None; // preventing InsufficientFunds error
20    request.max_priority_fee_per_gas = None; // preventing InsufficientFunds error
21    request.max_fee_per_blob_gas = None; // preventing InsufficientFunds error
22    request.value = None; // preventing InsufficientFunds error
23}
24
25/// Recovers a [`SignedTransaction`] from a typed data request.
26///
27/// This is a helper function that returns the appropriate RPC-specific error if the input data is
28/// malformed.
29///
30/// See [`alloy_eips::eip2718::Decodable2718::decode_2718`]
31pub fn recover_typed_data_request<T: SignedTransaction + Decodable712>(
32    mut data: &TypedDataRequest,
33) -> EthResult<Recovered<T>> {
34    let transaction =
35        T::decode_712(&mut data).map_err(|_| EthApiError::FailedToDecodeSignedTransaction)?;
36
37    SignedTransaction::try_into_recovered(transaction)
38        .or(Err(EthApiError::InvalidTransactionSignature))
39}
40
41/// Convert a [`SeismicCallRequest`] to a [`SeismicTransactionRequest`].
42///
43/// If the call requests simulates a transaction without a signature from msg.sender,
44/// we null out the fields that may reveal sensitive information.
45pub fn convert_seismic_call_to_tx_request(
46    request: SeismicCallRequest,
47) -> Result<SeismicTransactionRequest, EthApiError> {
48    match request {
49        SeismicCallRequest::TransactionRequest(mut tx_request) => {
50            seismic_override_call_request(&mut tx_request.inner); // null fields that may reveal sensitive information
51            Ok(tx_request)
52        }
53
54        SeismicCallRequest::TypedData(typed_request) => {
55            SeismicTransactionRequest::decode_712(&typed_request)
56                .map_err(|_e| EthApiError::FailedToDecodeSignedTransaction)
57        }
58
59        SeismicCallRequest::Bytes(bytes) => {
60            let tx = recover_raw_transaction::<SeismicTxEnvelope>(&bytes)?;
61            let mut req: SeismicTransactionRequest = tx.inner().clone().into();
62            TransactionBuilder::<SeismicReth>::set_from(&mut req, tx.signer());
63            Ok(req)
64        }
65    }
66}
67
68#[cfg(test)]
69mod test {
70    use crate::utils::recover_typed_data_request;
71    use alloy_primitives::{
72        aliases::U96,
73        hex::{self, FromHex},
74        Address, Bytes, FixedBytes, Signature, U256,
75    };
76    use reth_primitives_traits::SignedTransaction;
77    use reth_seismic_primitives::SeismicTransactionSigned;
78    use secp256k1::PublicKey;
79    use seismic_alloy_consensus::{
80        SeismicTxEnvelope, TxSeismic, TxSeismicElements, TypedDataRequest,
81    };
82    use std::str::FromStr;
83
84    #[test]
85    fn test_typed_data_tx_hash() {
86        let r_bytes =
87            hex::decode("e93185920818650416b4b0cc953c48f59fd9a29af4b7e1c4b1ac4824392f9220")
88                .unwrap();
89        let s_bytes =
90            hex::decode("79b76b064a83d423997b7234c575588f60da5d3e1e0561eff9804eb04c23789a")
91                .unwrap();
92        let mut r_padded = [0u8; 32];
93        let mut s_padded = [0u8; 32];
94        let r_start = 32 - r_bytes.len();
95        let s_start = 32 - s_bytes.len();
96
97        r_padded[r_start..].copy_from_slice(&r_bytes);
98        s_padded[s_start..].copy_from_slice(&s_bytes);
99
100        let r = U256::from_be_bytes(r_padded);
101        let s = U256::from_be_bytes(s_padded);
102
103        let signature = Signature::new(r, s, false);
104
105        let tx = TxSeismic {
106            chain_id: 5124,
107            nonce: 48,
108            gas_price: 360000,
109            gas_limit: 169477,
110            to: alloy_primitives::TxKind::Call(Address::from_str("0x3aB946eEC2553114040dE82D2e18798a51cf1e14").unwrap()),
111            value: U256::from_str("1000000000000000").unwrap(),
112            input: Bytes::from_str("0x4e69e56c3bb999b8c98772ebb32aebcbd43b33e9e65a46333dfe6636f37f3009e93bad334235aec73bd54d11410e64eb2cab4da8").unwrap(),
113            seismic_elements: TxSeismicElements {
114                encryption_pubkey: PublicKey::from_str("028e76821eb4d77fd30223ca971c49738eb5b5b71eabe93f96b348fdce788ae5a0").unwrap(),
115                encryption_nonce: U96::from_str("0x7da3a99bf0f90d56551d99ea").unwrap(),
116                message_version: 2,
117            }
118        };
119
120        let signed = SeismicTransactionSigned::new_unhashed(
121            seismic_alloy_consensus::SeismicTypedTransaction::Seismic(tx.clone()),
122            signature,
123        );
124        let signed_hash = signed.recalculate_hash();
125        let signed_sighash = signed.signature_hash();
126
127        let td = tx.eip712_to_type_data();
128        let req = TypedDataRequest { signature, data: td };
129
130        let recovered = recover_typed_data_request::<SeismicTxEnvelope>(&req).unwrap();
131        let recovered_hash = recovered.tx_hash();
132        let recovered_sighash = recovered.signature_hash();
133
134        let expected_tx_hash = FixedBytes::<32>::from_hex(
135            "d578c4f5e787b2994749e68e44860692480ace52b219bbc0119919561cbc29ea",
136        )
137        .unwrap();
138        assert_eq!(signed_hash, expected_tx_hash);
139        assert_eq!(recovered_hash, expected_tx_hash);
140
141        let expected_sighash = FixedBytes::<32>::from_hex(
142            "2886e254cbaa8b07a578dec42d3d71a8d4374b607bafe4e4b1c7fd4a8cb50911",
143        )
144        .unwrap();
145        assert_eq!(signed_sighash, expected_sighash);
146        assert_eq!(recovered_sighash, expected_sighash);
147    }
148}