Skip to content
This repository was archived by the owner on Oct 19, 2024. It is now read-only.

Commit 5ade851

Browse files
committed
fix(core): decode from for EIP2930 transactions
- add test which checks signed decoding for an EIP2930 transaction
1 parent c3b7db9 commit 5ade851

File tree

4 files changed

+166
-20
lines changed

4 files changed

+166
-20
lines changed

Diff for: ethers-core/src/types/transaction/eip1559.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ impl Eip1559TransactionRequest {
160160
self
161161
}
162162

163-
/// Hashes the transaction's data with the provided chain id
163+
/// Hashes the transaction's data for signing
164164
pub fn sighash(&self) -> H256 {
165165
let mut encoded = vec![];
166166
encoded.extend_from_slice(&[Self::TX_TYPE]);

Diff for: ethers-core/src/types/transaction/eip2718.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{
22
eip1559::{Eip1559RequestError, Eip1559TransactionRequest},
3-
eip2930::{AccessList, Eip2930TransactionRequest},
3+
eip2930::{AccessList, Eip2930RequestError, Eip2930TransactionRequest},
44
request::RequestError,
55
};
66
use crate::{
@@ -48,6 +48,9 @@ pub enum TypedTransactionError {
4848
/// When decoding a signed Eip1559 transaction
4949
#[error(transparent)]
5050
Eip1559Error(#[from] Eip1559RequestError),
51+
/// When decoding a signed Eip2930 transaction
52+
#[error(transparent)]
53+
Eip2930Error(#[from] Eip2930RequestError),
5154
/// Error decoding the transaction type from the transaction's RLP encoding
5255
#[error(transparent)]
5356
TypeDecodingError(#[from] rlp::DecoderError),
@@ -596,8 +599,12 @@ mod tests {
596599
let tx_real_rlp: String = tx_real_rlp_vec.encode_hex();
597600
assert_eq!(tx_expected_rlp, tx_real_rlp);
598601

599-
let r = U256::from_str("0x8085850e935fd6af9ace1b0343b9e21d2dcc7e914c36cce61a4e32756c785980").unwrap();
600-
let s = U256::from_str("0x4c57c184d5096263df981cb8a2f2c7f81640792856909dbf3295a2b7a1dc4a55").unwrap();
602+
let r =
603+
U256::from_str("0x8085850e935fd6af9ace1b0343b9e21d2dcc7e914c36cce61a4e32756c785980")
604+
.unwrap();
605+
let s =
606+
U256::from_str("0x4c57c184d5096263df981cb8a2f2c7f81640792856909dbf3295a2b7a1dc4a55")
607+
.unwrap();
601608
let v = 0;
602609
assert_eq!(r, sig.r);
603610
assert_eq!(s, sig.s);

Diff for: ethers-core/src/types/transaction/eip2930.rs

+124-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
use super::{extract_chain_id, normalize_v};
2-
use crate::types::{Address, Bytes, Signature, Transaction, TransactionRequest, H256, U256, U64};
3-
use rlp::{Decodable, DecoderError, RlpStream};
1+
use super::normalize_v;
2+
use crate::{
3+
types::{
4+
Address, Bytes, Signature, SignatureError, Transaction, TransactionRequest, H256, U256, U64,
5+
},
6+
utils::keccak256,
7+
};
8+
use rlp::{Decodable, RlpStream};
49
use rlp_derive::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
510
use serde::{Deserialize, Serialize};
11+
use thiserror::Error;
612

713
const NUM_EIP2930_FIELDS: usize = 8;
814

@@ -58,6 +64,17 @@ pub struct AccessListItem {
5864
pub storage_keys: Vec<H256>,
5965
}
6066

67+
/// An error involving an EIP2930 transaction request.
68+
#[derive(Debug, Error)]
69+
pub enum Eip2930RequestError {
70+
/// When decoding a transaction request from RLP
71+
#[error(transparent)]
72+
DecodingError(#[from] rlp::DecoderError),
73+
/// When recovering the address from a signature
74+
#[error(transparent)]
75+
RecoveryError(#[from] SignatureError),
76+
}
77+
6178
/// An EIP-2930 transaction is a legacy transaction including an [`AccessList`].
6279
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
6380
pub struct Eip2930TransactionRequest {
@@ -67,6 +84,9 @@ pub struct Eip2930TransactionRequest {
6784
}
6885

6986
impl Eip2930TransactionRequest {
87+
/// EIP-2718 transaction type
88+
const TX_TYPE: u8 = 0x01;
89+
7090
pub fn new(tx: TransactionRequest, access_list: AccessList) -> Self {
7191
Self { tx, access_list }
7292
}
@@ -104,30 +124,46 @@ impl Eip2930TransactionRequest {
104124
}
105125

106126
/// Decodes fields based on the RLP offset passed.
107-
fn decode_base_rlp(rlp: &rlp::Rlp, offset: &mut usize) -> Result<Self, DecoderError> {
108-
let request = TransactionRequest::decode_unsigned_rlp_base(rlp, offset)?;
109-
let access_list = rlp.val_at(*offset)?;
127+
fn decode_base_rlp(rlp: &rlp::Rlp, offset: &mut usize) -> Result<Self, rlp::DecoderError> {
128+
let chain_id: u64 = rlp.val_at(*offset)?;
129+
*offset += 1;
130+
131+
let mut request = TransactionRequest::decode_unsigned_rlp_base(rlp, offset)?;
132+
request.chain_id = Some(U64::from(chain_id));
133+
134+
let al = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
135+
let access_list = match al.len() {
136+
0 => AccessList(vec![]),
137+
_ => rlp.val_at(*offset)?,
138+
};
110139
*offset += 1;
111140

112141
Ok(Self { tx: request, access_list })
113142
}
114143

115144
/// Decodes the given RLP into a transaction, attempting to decode its signature as well.
116-
pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), rlp::DecoderError> {
145+
pub fn decode_signed_rlp(rlp: &rlp::Rlp) -> Result<(Self, Signature), Eip2930RequestError> {
117146
let mut offset = 0;
118147
let mut txn = Self::decode_base_rlp(rlp, &mut offset)?;
119148

120149
let v = rlp.val_at(offset)?;
121-
// populate chainid from v
122-
txn.tx.chain_id = extract_chain_id(v);
123150
offset += 1;
124151
let r = rlp.val_at(offset)?;
125152
offset += 1;
126153
let s = rlp.val_at(offset)?;
127154

128155
let sig = Signature { r, s, v };
156+
txn.tx.from = Some(sig.recover(txn.sighash())?);
129157
Ok((txn, sig))
130158
}
159+
160+
/// Hashes the transaction's data for signing
161+
pub fn sighash(&self) -> H256 {
162+
let mut encoded = vec![];
163+
encoded.extend_from_slice(&[Self::TX_TYPE]);
164+
encoded.extend_from_slice(self.rlp().as_ref());
165+
keccak256(encoded).into()
166+
}
131167
}
132168

133169
/// Get a Eip2930TransactionRequest from a rlp encoded byte stream
@@ -151,6 +187,7 @@ mod tests {
151187

152188
use super::*;
153189
use crate::types::{transaction::eip2718::TypedTransaction, U256};
190+
use std::str::FromStr;
154191

155192
#[test]
156193
#[cfg_attr(feature = "celo", ignore)]
@@ -198,4 +235,82 @@ mod tests {
198235
let de: Eip2930TransactionRequest = serde_json::from_str(&serialized).unwrap();
199236
assert_eq!(tx, TypedTransaction::Eip2930(de));
200237
}
238+
239+
#[test]
240+
fn decoding_eip2930_signed() {
241+
let raw_tx = hex::decode("01f901ef018209068508d8f9fc0083124f8094f5b4f13bdbe12709bd3ea280ebf4b936e99b20f280b90184c5d404940000000000000000000000000000000000000000000000000c4d67a76e15d8190000000000000000000000000000000000000000000000000029d9d8fb7440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007b73644935b8e68019ac6356c40661e1bc315860000000000000000000000000761d38e5ddf6ccf6cf7c55759d5210750b5d60f30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000381fe4eb128db1621647ca00965da3f9e09f4fac000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000ac001a0881e7f5298290794bcaa0294986db5c375cbf135dd3c21456b159c470568b687a061fc5f52abab723053fbedf29e1c60b89006416d6c86e1c54ef85a3e84f2dc6e").unwrap();
242+
let expected_tx = TransactionRequest::new()
243+
.chain_id(1u64)
244+
.nonce(2310u64)
245+
.gas_price(38_000_000_000u64)
246+
.gas(1_200_000u64)
247+
.to(Address::from_str("0xf5b4f13bdbe12709bd3ea280ebf4b936e99b20f2").unwrap())
248+
.value(0u64)
249+
.data(hex::decode("c5d404940000000000000000000000000000000000000000000000000c4d67a76e15d8190000000000000000000000000000000000000000000000000029d9d8fb7440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007b73644935b8e68019ac6356c40661e1bc315860000000000000000000000000761d38e5ddf6ccf6cf7c55759d5210750b5d60f30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000381fe4eb128db1621647ca00965da3f9e09f4fac000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000a").unwrap())
250+
.from(Address::from_str("0x82a33964706683db62b85a59128ce2fc07c91658").unwrap())
251+
.with_access_list(AccessList(vec![]));
252+
let r =
253+
U256::from_str("0x881e7f5298290794bcaa0294986db5c375cbf135dd3c21456b159c470568b687")
254+
.unwrap();
255+
let s =
256+
U256::from_str("0x61fc5f52abab723053fbedf29e1c60b89006416d6c86e1c54ef85a3e84f2dc6e")
257+
.unwrap();
258+
let v = 1;
259+
let expected_sig = Signature { r, s, v };
260+
261+
let raw_tx_rlp = rlp::Rlp::new(&raw_tx[..]);
262+
263+
let (real_tx, real_sig) = TypedTransaction::decode_signed(&raw_tx_rlp).unwrap();
264+
let real_tx = match real_tx {
265+
TypedTransaction::Eip2930(tx) => tx,
266+
_ => panic!("The raw bytes should decode to an EIP2930 tranaction"),
267+
};
268+
269+
assert_eq!(expected_tx, real_tx);
270+
assert_eq!(expected_sig, real_sig);
271+
}
272+
273+
#[test]
274+
fn decoding_eip2930_with_access_list() {
275+
let raw_tx = hex::decode("01f90126018223ff850a02ffee00830f4240940000000000a8fb09af944ab3baf7a9b3e1ab29d880b876200200001525000000000b69ffb300000000557b933a7c2c45672b610f8954a3deb39a51a8cae53ec727dbdeb9e2d5456c3be40cff031ab40a55724d5c9c618a2152e99a45649a3b8cf198321f46720b722f4ec38f99ba3bb1303258d2e816e6a95b25647e01bd0967c1b9599fa3521939871d1d0888f845d694724d5c9c618a2152e99a45649a3b8cf198321f46c0d694720b722f4ec38f99ba3bb1303258d2e816e6a95bc0d69425647e01bd0967c1b9599fa3521939871d1d0888c001a08323efae7b9993bd31a58da7924359d24b5504aa2b33194fcc5ae206e65d2e62a054ce201e3b4b5cd38eb17c56ee2f9111b2e164efcd57b3e70fa308a0a51f7014").unwrap();
276+
let expected_tx = TransactionRequest::new()
277+
.chain_id(1u64)
278+
.nonce(9215u64)
279+
.gas_price(43_000_000_000u64)
280+
.gas(1_000_000)
281+
.to(Address::from_str("0x0000000000a8fb09af944ab3baf7a9b3e1ab29d8").unwrap())
282+
.value(0)
283+
.data(Bytes::from_str("0x200200001525000000000b69ffb300000000557b933a7c2c45672b610f8954a3deb39a51a8cae53ec727dbdeb9e2d5456c3be40cff031ab40a55724d5c9c618a2152e99a45649a3b8cf198321f46720b722f4ec38f99ba3bb1303258d2e816e6a95b25647e01bd0967c1b9599fa3521939871d1d0888").unwrap())
284+
.from(Address::from_str("0xe9c790e8fde820ded558a4771b72eec916c04763").unwrap())
285+
.with_access_list(AccessList(vec![
286+
AccessListItem {
287+
address: Address::from_str("0x724d5c9c618a2152e99a45649a3b8cf198321f46").unwrap(),
288+
storage_keys: vec![],
289+
},
290+
AccessListItem {
291+
address: Address::from_str("0x720b722f4ec38f99ba3bb1303258d2e816e6a95b").unwrap(),
292+
storage_keys: vec![],
293+
},
294+
AccessListItem {
295+
address: Address::from_str("0x25647e01bd0967c1b9599fa3521939871d1d0888").unwrap(),
296+
storage_keys: vec![],
297+
},
298+
]));
299+
let expected_sig = Signature {
300+
r: "0x8323efae7b9993bd31a58da7924359d24b5504aa2b33194fcc5ae206e65d2e62".into(),
301+
s: "0x54ce201e3b4b5cd38eb17c56ee2f9111b2e164efcd57b3e70fa308a0a51f7014".into(),
302+
v: 1u64,
303+
};
304+
305+
let raw_tx_rlp = rlp::Rlp::new(&raw_tx[..]);
306+
307+
let (real_tx, real_sig) = TypedTransaction::decode_signed(&raw_tx_rlp).unwrap();
308+
let real_tx = match real_tx {
309+
TypedTransaction::Eip2930(tx) => tx,
310+
_ => panic!("The raw bytes should decode to an EIP2930 tranaction"),
311+
};
312+
313+
assert_eq!(expected_tx, real_tx);
314+
assert_eq!(expected_sig, real_sig);
315+
}
201316
}

Diff for: ethers-core/src/types/transaction/request.rs

+31-7
Original file line numberDiff line numberDiff line change
@@ -250,15 +250,14 @@ impl TransactionRequest {
250250
let mut offset = 0;
251251
let mut txn = Self::decode_unsigned_rlp_base(rlp, &mut offset)?;
252252

253-
// If a signed transaction is passed to this method, the chainid would be set to the v value
254-
// of the signature.
255-
if let Ok(chainid) = rlp.at(offset)?.as_val() {
253+
// If the transaction includes more info, like the chainid, as we serialize in `rlp`, this
254+
// will decode that value.
255+
if let Ok(chainid) = rlp.val_at(offset) {
256+
// If a signed transaction is passed to this method, the chainid would be set to the v
257+
// value of the signature.
256258
txn.chain_id = Some(chainid);
257259
}
258260

259-
// parse the last two elements so we return an error if a signed transaction is passed
260-
let _first_zero: u8 = rlp.at(offset + 1)?.as_val()?;
261-
let _second_zero: u8 = rlp.at(offset + 2)?.as_val()?;
262261
Ok(txn)
263262
}
264263

@@ -351,7 +350,7 @@ impl TransactionRequest {
351350
#[cfg(test)]
352351
#[cfg(not(feature = "celo"))]
353352
mod tests {
354-
use crate::types::{NameOrAddress, Signature};
353+
use crate::types::{Bytes, NameOrAddress, Signature};
355354
use rlp::{Decodable, Rlp};
356355

357356
use super::{Address, TransactionRequest, U256, U64};
@@ -429,6 +428,31 @@ mod tests {
429428
assert_eq!(got_tx.sighash(), tx.sighash());
430429
}
431430

431+
#[test]
432+
fn decode_unsigned_rlp_no_chainid() {
433+
// unlike the corresponding transaction
434+
// 0x02c563d96acaf8c157d08db2228c84836faaf3dd513fc959a54ed4ca6c72573e, this doesn't have a
435+
// `from` field because the `from` field is only obtained via signature recovery
436+
let expected_tx = TransactionRequest::new()
437+
.to(Address::from_str("0xc7696b27830dd8aa4823a1cba8440c27c36adec4").unwrap())
438+
.gas(3_000_000)
439+
.gas_price(20_000_000_000u64)
440+
.value(0)
441+
.nonce(6306u64)
442+
.data(
443+
Bytes::from_str(
444+
"0x91b7f5ed0000000000000000000000000000000000000000000000000000000000000372",
445+
)
446+
.unwrap(),
447+
);
448+
449+
// manually stripped the signature off the end and modified length
450+
let expected_rlp = hex::decode("f8488218a28504a817c800832dc6c094c7696b27830dd8aa4823a1cba8440c27c36adec480a491b7f5ed0000000000000000000000000000000000000000000000000000000000000372").unwrap();
451+
let real_tx = TransactionRequest::decode(&Rlp::new(&expected_rlp)).unwrap();
452+
453+
assert_eq!(real_tx, expected_tx);
454+
}
455+
432456
#[test]
433457
fn test_eip155_encode() {
434458
let tx = TransactionRequest::new()

0 commit comments

Comments
 (0)