ethers_core/types/transaction/
request.rs

1//! Transaction types
2use super::{decode_to, extract_chain_id, rlp_opt, NUM_TX_FIELDS};
3use crate::{
4    types::{
5        Address, Bytes, NameOrAddress, Signature, SignatureError, Transaction, H256, U256, U64,
6    },
7    utils::keccak256,
8};
9
10use rlp::{Decodable, RlpStream};
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14/// An error involving a transaction request.
15#[derive(Debug, Error)]
16pub enum RequestError {
17    /// When decoding a transaction request from RLP
18    #[error(transparent)]
19    DecodingError(#[from] rlp::DecoderError),
20    /// When recovering the address from a signature
21    #[error(transparent)]
22    RecoveryError(#[from] SignatureError),
23}
24
25/// Parameters for sending a transaction
26#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
27pub struct TransactionRequest {
28    /// Sender address or ENS name
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub from: Option<Address>,
31
32    /// Recipient address (None for contract creation)
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub to: Option<NameOrAddress>,
35
36    /// Supplied gas (None for sensible default)
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub gas: Option<U256>,
39
40    /// Gas price (None for sensible default)
41    #[serde(rename = "gasPrice")]
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub gas_price: Option<U256>,
44
45    /// Transferred value (None for no transfer)
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub value: Option<U256>,
48
49    /// The compiled code of a contract OR the first 4 bytes of the hash of the
50    /// invoked method signature and encoded parameters. For details see Ethereum Contract ABI
51    #[serde(skip_serializing_if = "Option::is_none", alias = "input")]
52    pub data: Option<Bytes>,
53
54    /// Transaction nonce (None for next available nonce)
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub nonce: Option<U256>,
57
58    /// Chain ID (None for mainnet)
59    #[serde(skip_serializing)]
60    #[serde(default, rename = "chainId")]
61    pub chain_id: Option<U64>,
62
63    /////////////////  Celo-specific transaction fields /////////////////
64    /// The currency fees are paid in (None for native currency)
65    #[cfg(feature = "celo")]
66    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub fee_currency: Option<Address>,
69
70    /// Gateway fee recipient (None for no gateway fee paid)
71    #[cfg(feature = "celo")]
72    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
73    #[serde(skip_serializing_if = "Option::is_none")]
74    pub gateway_fee_recipient: Option<Address>,
75
76    /// Gateway fee amount (None for no gateway fee paid)
77    #[cfg(feature = "celo")]
78    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub gateway_fee: Option<U256>,
81}
82
83impl TransactionRequest {
84    /// Creates an empty transaction request with all fields left empty
85    pub fn new() -> Self {
86        Self::default()
87    }
88
89    /// Convenience function for sending a new payment transaction to the receiver.
90    pub fn pay<T: Into<NameOrAddress>, V: Into<U256>>(to: T, value: V) -> Self {
91        TransactionRequest { to: Some(to.into()), value: Some(value.into()), ..Default::default() }
92    }
93
94    // Builder pattern helpers
95
96    /// Sets the `from` field in the transaction to the provided value
97    #[must_use]
98    pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
99        self.from = Some(from.into());
100        self
101    }
102
103    /// Sets the `to` field in the transaction to the provided value
104    #[must_use]
105    pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
106        self.to = Some(to.into());
107        self
108    }
109
110    /// Sets the `gas` field in the transaction to the provided value
111    #[must_use]
112    pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
113        self.gas = Some(gas.into());
114        self
115    }
116
117    /// Sets the `gas_price` field in the transaction to the provided value
118    #[must_use]
119    pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
120        self.gas_price = Some(gas_price.into());
121        self
122    }
123
124    /// Sets the `value` field in the transaction to the provided value
125    #[must_use]
126    pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
127        self.value = Some(value.into());
128        self
129    }
130
131    /// Sets the `data` field in the transaction to the provided value
132    #[must_use]
133    pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
134        self.data = Some(data.into());
135        self
136    }
137
138    /// Sets the `nonce` field in the transaction to the provided value
139    #[must_use]
140    pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
141        self.nonce = Some(nonce.into());
142        self
143    }
144
145    /// Sets the `chain_id` field in the transaction to the provided value
146    #[must_use]
147    pub fn chain_id<T: Into<U64>>(mut self, chain_id: T) -> Self {
148        self.chain_id = Some(chain_id.into());
149        self
150    }
151
152    /// Hashes the transaction's data with the provided chain id
153    pub fn sighash(&self) -> H256 {
154        match self.chain_id {
155            Some(_) => keccak256(self.rlp().as_ref()).into(),
156            None => keccak256(self.rlp_unsigned().as_ref()).into(),
157        }
158    }
159
160    /// Gets the transaction's RLP encoding, prepared with the chain_id and extra fields for
161    /// signing. Assumes the chainid exists.
162    pub fn rlp(&self) -> Bytes {
163        let mut rlp = RlpStream::new();
164        if let Some(chain_id) = self.chain_id {
165            rlp.begin_list(NUM_TX_FIELDS);
166            self.rlp_base(&mut rlp);
167            rlp.append(&chain_id);
168            rlp.append(&0u8);
169            rlp.append(&0u8);
170        } else {
171            rlp.begin_list(NUM_TX_FIELDS - 3);
172            self.rlp_base(&mut rlp);
173        }
174        rlp.out().freeze().into()
175    }
176
177    /// Gets the unsigned transaction's RLP encoding
178    pub fn rlp_unsigned(&self) -> Bytes {
179        let mut rlp = RlpStream::new();
180        rlp.begin_list(NUM_TX_FIELDS - 3);
181        self.rlp_base(&mut rlp);
182        rlp.out().freeze().into()
183    }
184
185    /// Produces the RLP encoding of the transaction with the provided signature
186    pub fn rlp_signed(&self, signature: &Signature) -> Bytes {
187        let mut rlp = RlpStream::new();
188        rlp.begin_list(NUM_TX_FIELDS);
189
190        self.rlp_base(&mut rlp);
191
192        // append the signature
193        rlp.append(&signature.v);
194        rlp.append(&signature.r);
195        rlp.append(&signature.s);
196        rlp.out().freeze().into()
197    }
198
199    pub(crate) fn rlp_base(&self, rlp: &mut RlpStream) {
200        rlp_opt(rlp, &self.nonce);
201        rlp_opt(rlp, &self.gas_price);
202        rlp_opt(rlp, &self.gas);
203
204        #[cfg(feature = "celo")]
205        self.inject_celo_metadata(rlp);
206
207        rlp_opt(rlp, &self.to.as_ref());
208        rlp_opt(rlp, &self.value);
209        rlp_opt(rlp, &self.data.as_ref().map(|d| d.as_ref()));
210    }
211
212    /// Decodes the unsigned rlp, returning the transaction request and incrementing the counter
213    /// passed as we are traversing the rlp list.
214    pub(crate) fn decode_unsigned_rlp_base(
215        rlp: &rlp::Rlp,
216        offset: &mut usize,
217    ) -> Result<Self, rlp::DecoderError> {
218        let mut txn = TransactionRequest::new();
219        txn.nonce = Some(rlp.at(*offset)?.as_val()?);
220        *offset += 1;
221        txn.gas_price = Some(rlp.at(*offset)?.as_val()?);
222        *offset += 1;
223        txn.gas = Some(rlp.at(*offset)?.as_val()?);
224        *offset += 1;
225
226        #[cfg(feature = "celo")]
227        {
228            txn.fee_currency = Some(rlp.at(*offset)?.as_val()?);
229            *offset += 1;
230            txn.gateway_fee_recipient = Some(rlp.at(*offset)?.as_val()?);
231            *offset += 1;
232            txn.gateway_fee = Some(rlp.at(*offset)?.as_val()?);
233            *offset += 1;
234        }
235
236        txn.to = decode_to(rlp, offset)?.map(NameOrAddress::Address);
237        txn.value = Some(rlp.at(*offset)?.as_val()?);
238        *offset += 1;
239
240        // finally we need to extract the data which will be encoded as another rlp
241        let txndata = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
242        txn.data = match txndata.len() {
243            0 => None,
244            _ => Some(Bytes::from(txndata.to_vec())),
245        };
246        *offset += 1;
247        Ok(txn)
248    }
249
250    /// Decodes RLP into a transaction.
251    pub fn decode_unsigned_rlp(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
252        let mut offset = 0;
253        let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
254
255        // If the transaction includes more info, like the chainid, as we serialize in `rlp`, this
256        // will decode that value.
257        if let Ok(chainid) = rlp.val_at(offset) {
258            // If a signed transaction is passed to this method, the chainid would be set to the v
259            // value of the signature.
260            txn.chain_id = Some(chainid);
261        }
262
263        Ok(txn)
264    }
265
266    /// Decodes the given RLP into a transaction, attempting to decode its signature as well.
267    pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), RequestError> {
268        let mut offset = 0;
269        let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
270
271        let v = rlp.at(offset)?.as_val()?;
272        // populate chainid from v in case the signature follows EIP155
273        txn.chain_id = extract_chain_id(v);
274        offset += 1;
275        let r = rlp.at(offset)?.as_val()?;
276        offset += 1;
277        let s = rlp.at(offset)?.as_val()?;
278
279        let sig = Signature { r, s, v };
280        txn.from = Some(sig.recover(txn.sighash())?);
281
282        Ok((txn, sig))
283    }
284}
285
286impl Decodable for TransactionRequest {
287    /// Decodes the given RLP into a transaction request, ignoring the signature if populated
288    fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
289        Self::decode_unsigned_rlp(rlp)
290    }
291}
292
293impl From<&Transaction> for TransactionRequest {
294    fn from(tx: &Transaction) -> TransactionRequest {
295        TransactionRequest {
296            from: Some(tx.from),
297            to: tx.to.map(NameOrAddress::Address),
298            gas: Some(tx.gas),
299            gas_price: tx.gas_price,
300            value: Some(tx.value),
301            data: Some(Bytes(tx.input.0.clone())),
302            nonce: Some(tx.nonce),
303            chain_id: tx.chain_id.map(|x| U64::from(x.as_u64())),
304
305            #[cfg(feature = "celo")]
306            fee_currency: tx.fee_currency,
307
308            #[cfg(feature = "celo")]
309            gateway_fee_recipient: tx.gateway_fee_recipient,
310
311            #[cfg(feature = "celo")]
312            gateway_fee: tx.gateway_fee,
313        }
314    }
315}
316
317// Separate impl block for the celo-specific fields
318#[cfg(feature = "celo")]
319impl TransactionRequest {
320    // modifies the RLP stream with the Celo-specific information
321    fn inject_celo_metadata(&self, rlp: &mut RlpStream) {
322        rlp_opt(rlp, &self.fee_currency);
323        rlp_opt(rlp, &self.gateway_fee_recipient);
324        rlp_opt(rlp, &self.gateway_fee);
325    }
326
327    /// Sets the `fee_currency` field in the transaction to the provided value
328    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
329    #[must_use]
330    pub fn fee_currency<T: Into<Address>>(mut self, fee_currency: T) -> Self {
331        self.fee_currency = Some(fee_currency.into());
332        self
333    }
334
335    /// Sets the `gateway_fee` field in the transaction to the provided value
336    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
337    #[must_use]
338    pub fn gateway_fee<T: Into<U256>>(mut self, gateway_fee: T) -> Self {
339        self.gateway_fee = Some(gateway_fee.into());
340        self
341    }
342
343    /// Sets the `gateway_fee_recipient` field in the transaction to the provided value
344    #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
345    #[must_use]
346    pub fn gateway_fee_recipient<T: Into<Address>>(mut self, gateway_fee_recipient: T) -> Self {
347        self.gateway_fee_recipient = Some(gateway_fee_recipient.into());
348        self
349    }
350}
351
352#[cfg(test)]
353#[cfg(not(any(feature = "celo", feature = "optimism")))]
354mod tests {
355    use super::*;
356    use crate::types::{transaction::eip2718::TypedTransaction, Bytes, NameOrAddress, Signature};
357    use rlp::{Decodable, Rlp};
358    use std::str::FromStr;
359
360    #[test]
361    fn encode_decode_rlp() {
362        let tx = TransactionRequest::new()
363            .nonce(3)
364            .gas_price(1)
365            .gas(25000)
366            .to("b94f5374fce5edbc8e2a8697c15331677e6ebf0b".parse::<Address>().unwrap())
367            .value(10)
368            .data(vec![0x55, 0x44])
369            .chain_id(1);
370
371        // turn the rlp bytes encoding into a rlp stream and check that the decoding returns the
372        // same struct
373        let rlp_bytes = &tx.rlp().to_vec()[..];
374        let got_rlp = Rlp::new(rlp_bytes);
375        let txn_request = TransactionRequest::decode(&got_rlp).unwrap();
376
377        // We compare the sighash rather than the specific struct
378        assert_eq!(tx.sighash(), txn_request.sighash());
379    }
380
381    #[test]
382    // test data from https://github.com/ethereum/go-ethereum/blob/b1e72f7ea998ad662166bcf23705ca59cf81e925/core/types/transaction_test.go#L40
383    fn empty_sighash_check() {
384        let tx = TransactionRequest::new()
385            .nonce(0)
386            .to("095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::<Address>().unwrap())
387            .value(0)
388            .gas(0)
389            .gas_price(0);
390
391        let expected_sighash = "c775b99e7ad12f50d819fcd602390467e28141316969f4b57f0626f74fe3b386";
392        let got_sighash = hex::encode(tx.sighash().as_bytes());
393        assert_eq!(expected_sighash, got_sighash);
394    }
395    #[test]
396    fn decode_unsigned_transaction() {
397        let _res: TransactionRequest = serde_json::from_str(
398            r#"{
399    "gas":"0xc350",
400    "gasPrice":"0x4a817c800",
401    "hash":"0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b",
402    "input":"0x68656c6c6f21",
403    "nonce":"0x15",
404    "to":"0xf02c1c8e6114b1dbe8937a39260b5b0a374432bb",
405    "transactionIndex":"0x41",
406    "value":"0xf3dbb76162000",
407    "chain_id": "0x1"
408  }"#,
409        )
410        .unwrap();
411    }
412
413    #[test]
414    fn decode_known_rlp_goerli() {
415        let tx = TransactionRequest::new()
416            .nonce(70272)
417            .from("fab2b4b677a4e104759d378ea25504862150256e".parse::<Address>().unwrap())
418            .to("d1f23226fb4d2b7d2f3bcdd99381b038de705a64".parse::<Address>().unwrap())
419            .value(0)
420            .gas_price(1940000007)
421            .gas(21000);
422
423        let expected_rlp = hex::decode("f866830112808473a20d0782520894d1f23226fb4d2b7d2f3bcdd99381b038de705a6480801ca04bc89d41c954168afb4cbd01fe2e0f9fe12e3aa4665eefcee8c4a208df044b5da05d410fd85a2e31870ea6d6af53fafc8e3c1ae1859717c863cac5cff40fee8da4").unwrap();
424        let (got_tx, _signature) =
425            TransactionRequest::decode_signed_rlp(&Rlp::new(&expected_rlp)).unwrap();
426
427        // intialization of TransactionRequests using new() uses the Default trait, so we just
428        // compare the sighash and signed encoding instead.
429        assert_eq!(got_tx.sighash(), tx.sighash());
430    }
431
432    #[test]
433    fn decode_unsigned_rlp_no_chainid() {
434        // unlike the corresponding transaction
435        // 0x02c563d96acaf8c157d08db2228c84836faaf3dd513fc959a54ed4ca6c72573e, this doesn't have a
436        // `from` field because the `from` field is only obtained via signature recovery
437        let expected_tx = TransactionRequest::new()
438            .to(Address::from_str("0xc7696b27830dd8aa4823a1cba8440c27c36adec4").unwrap())
439            .gas(3_000_000)
440            .gas_price(20_000_000_000u64)
441            .value(0)
442            .nonce(6306u64)
443            .data(
444                Bytes::from_str(
445                    "0x91b7f5ed0000000000000000000000000000000000000000000000000000000000000372",
446                )
447                .unwrap(),
448            );
449
450        // manually stripped the signature off the end and modified length
451        let expected_rlp = hex::decode("f8488218a28504a817c800832dc6c094c7696b27830dd8aa4823a1cba8440c27c36adec480a491b7f5ed0000000000000000000000000000000000000000000000000000000000000372").unwrap();
452        let real_tx = TransactionRequest::decode(&Rlp::new(&expected_rlp)).unwrap();
453
454        assert_eq!(real_tx, expected_tx);
455    }
456
457    #[test]
458    fn test_eip155_encode() {
459        let tx = TransactionRequest::new()
460            .nonce(9)
461            .to("3535353535353535353535353535353535353535".parse::<Address>().unwrap())
462            .value(1000000000000000000u64)
463            .gas_price(20000000000u64)
464            .gas(21000)
465            .chain_id(1);
466
467        let expected_rlp = hex::decode("ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080").unwrap();
468        assert_eq!(expected_rlp, tx.rlp().to_vec());
469
470        let expected_sighash =
471            hex::decode("daf5a779ae972f972197303d7b574746c7ef83eadac0f2791ad23db92e4c8e53")
472                .unwrap();
473
474        assert_eq!(expected_sighash, tx.sighash().as_bytes().to_vec());
475    }
476
477    #[test]
478    fn test_eip155_decode() {
479        let tx = TransactionRequest::new()
480            .nonce(9)
481            .to("3535353535353535353535353535353535353535".parse::<Address>().unwrap())
482            .value(1000000000000000000u64)
483            .gas_price(20000000000u64)
484            .gas(21000)
485            .chain_id(1);
486
487        let expected_hex = hex::decode("ec098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a764000080018080").unwrap();
488        let expected_rlp = rlp::Rlp::new(expected_hex.as_slice());
489        let decoded_transaction = TransactionRequest::decode(&expected_rlp).unwrap();
490        assert_eq!(tx, decoded_transaction);
491    }
492
493    #[test]
494    fn test_eip155_decode_signed() {
495        let expected_signed_bytes = hex::decode("f86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83").unwrap();
496        let expected_signed_rlp = rlp::Rlp::new(expected_signed_bytes.as_slice());
497        let (decoded_tx, decoded_sig) =
498            TransactionRequest::decode_signed_rlp(&expected_signed_rlp).unwrap();
499
500        let expected_sig = Signature {
501            v: 37,
502            r: U256::from_dec_str(
503                "18515461264373351373200002665853028612451056578545711640558177340181847433846",
504            )
505            .unwrap(),
506            s: U256::from_dec_str(
507                "46948507304638947509940763649030358759909902576025900602547168820602576006531",
508            )
509            .unwrap(),
510        };
511        assert_eq!(expected_sig, decoded_sig);
512        assert_eq!(decoded_tx.chain_id, Some(U64::from(1)));
513    }
514
515    #[test]
516    fn test_eip155_signing_decode_vitalik() {
517        // Test vectors come from http://vitalik.ca/files/eip155_testvec.txt and
518        // https://github.com/ethereum/go-ethereum/blob/master/core/types/transaction_signing_test.go
519        // Tests that the rlp decoding properly extracts the from address
520        let rlp_transactions =
521            ["f864808504a817c800825208943535353535353535353535353535353535353535808025a0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116da0044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d",
522                 "f864018504a817c80182a410943535353535353535353535353535353535353535018025a0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bcaa0489efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6",
523                 "f864028504a817c80282f618943535353535353535353535353535353535353535088025a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5a02d7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a5",
524                 "f865038504a817c803830148209435353535353535353535353535353535353535351b8025a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4e0a02a80e1ef1d7842f27f2e6be0972bb708b9a135c38860dbe73c27c3486c34f4de",
525                 "f865048504a817c80483019a28943535353535353535353535353535353535353535408025a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c063a013600b294191fc92924bb3ce4b969c1e7e2bab8f4c93c3fc6d0a51733df3c060",
526                 "f865058504a817c8058301ec309435353535353535353535353535353535353535357d8025a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1a04eebf77a833b30520287ddd9478ff51abbdffa30aa90a8d655dba0e8a79ce0c1",
527                 "f866068504a817c80683023e3894353535353535353535353535353535353535353581d88025a06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2fa06455bf8ea6e7463a1046a0b52804526e119b4bf5136279614e0b1e8e296a4e2d",
528                 "f867078504a817c807830290409435353535353535353535353535353535353535358201578025a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021a052f1a9b320cab38e5da8a8f97989383aab0a49165fc91c737310e4f7e9821021",
529                 "f867088504a817c8088302e2489435353535353535353535353535353535353535358202008025a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c12a064b1702d9298fee62dfeccc57d322a463ad55ca201256d01f62b45b2e1c21c10",
530                 "f867098504a817c809830334509435353535353535353535353535353535353535358202d98025a052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afba052f8f61201b2b11a78d6e866abc9c3db2ae8631fa656bfe5cb53668255367afb"];
531        let rlp_transactions_bytes = rlp_transactions
532            .iter()
533            .map(|rlp_str| hex::decode(rlp_str).unwrap())
534            .collect::<Vec<Vec<u8>>>();
535
536        let raw_addresses = [
537            "0xf0f6f18bca1b28cd68e4357452947e021241e9ce",
538            "0x23ef145a395ea3fa3deb533b8a9e1b4c6c25d112",
539            "0x2e485e0c23b4c3c542628a5f672eeab0ad4888be",
540            "0x82a88539669a3fd524d669e858935de5e5410cf0",
541            "0xf9358f2538fd5ccfeb848b64a96b743fcc930554",
542            "0xa8f7aba377317440bc5b26198a363ad22af1f3a4",
543            "0xf1f571dc362a0e5b2696b8e775f8491d3e50de35",
544            "0xd37922162ab7cea97c97a87551ed02c9a38b7332",
545            "0x9bddad43f934d313c2b79ca28a432dd2b7281029",
546            "0x3c24d7329e92f84f08556ceb6df1cdb0104ca49f",
547        ];
548
549        let addresses = raw_addresses.iter().map(|addr| addr.parse::<Address>().unwrap().into());
550
551        // decoding will do sender recovery and we don't expect any of these to error, so we should
552        // check that the address matches for each decoded transaction
553        let decoded_transactions = rlp_transactions_bytes.iter().map(|raw_tx| {
554            TransactionRequest::decode_signed_rlp(&Rlp::new(raw_tx.as_slice())).unwrap().0
555        });
556
557        for (tx, from_addr) in decoded_transactions.zip(addresses) {
558            let from_tx: NameOrAddress = tx.from.unwrap().into();
559            assert_eq!(from_tx, from_addr);
560        }
561    }
562
563    #[test]
564    fn test_recover_legacy_tx() {
565        let raw_tx = "f9015482078b8505d21dba0083022ef1947a250d5630b4cf539739df2c5dacb4c659f2488d880c46549a521b13d8b8e47ff36ab50000000000000000000000000000000000000000000066ab5a608bd00a23f2fe000000000000000000000000000000000000000000000000000000000000008000000000000000000000000048c04ed5691981c42154c6167398f95e8f38a7ff00000000000000000000000000000000000000000000000000000000632ceac70000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006c6ee5e31d828de241282b9606c8e98ea48526e225a0c9077369501641a92ef7399ff81c21639ed4fd8fc69cb793cfa1dbfab342e10aa0615facb2f1bcf3274a354cfe384a38d0cc008a11c2dd23a69111bc6930ba27a8";
566
567        let data = hex::decode(raw_tx).unwrap();
568        let rlp = Rlp::new(&data);
569        let (tx, sig) = TypedTransaction::decode_signed(&rlp).unwrap();
570        let recovered = sig.recover(tx.sighash()).unwrap();
571
572        let expected: Address = "0xa12e1462d0ced572f396f58b6e2d03894cd7c8a4".parse().unwrap();
573        assert_eq!(expected, recovered);
574    }
575}