ethers_core/types/transaction/
request.rs1use 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#[derive(Debug, Error)]
16pub enum RequestError {
17 #[error(transparent)]
19 DecodingError(#[from] rlp::DecoderError),
20 #[error(transparent)]
22 RecoveryError(#[from] SignatureError),
23}
24
25#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq, Debug)]
27pub struct TransactionRequest {
28 #[serde(skip_serializing_if = "Option::is_none")]
30 pub from: Option<Address>,
31
32 #[serde(skip_serializing_if = "Option::is_none")]
34 pub to: Option<NameOrAddress>,
35
36 #[serde(skip_serializing_if = "Option::is_none")]
38 pub gas: Option<U256>,
39
40 #[serde(rename = "gasPrice")]
42 #[serde(skip_serializing_if = "Option::is_none")]
43 pub gas_price: Option<U256>,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub value: Option<U256>,
48
49 #[serde(skip_serializing_if = "Option::is_none", alias = "input")]
52 pub data: Option<Bytes>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub nonce: Option<U256>,
57
58 #[serde(skip_serializing)]
60 #[serde(default, rename = "chainId")]
61 pub chain_id: Option<U64>,
62
63 #[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 #[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 #[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 pub fn new() -> Self {
86 Self::default()
87 }
88
89 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 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 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 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 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 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 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 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 let Ok(chainid) = rlp.val_at(offset) {
258 txn.chain_id = Some(chainid);
261 }
262
263 Ok(txn)
264 }
265
266 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 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 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#[cfg(feature = "celo")]
319impl TransactionRequest {
320 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 #[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 #[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 #[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 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 assert_eq!(tx.sighash(), txn_request.sighash());
379 }
380
381 #[test]
382 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 assert_eq!(got_tx.sighash(), tx.sighash());
430 }
431
432 #[test]
433 fn decode_unsigned_rlp_no_chainid() {
434 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 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 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 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}