1use super::{derive_eip_155_chain_id, validate_eip155_chain_id};
5use crate::eth::{LEGACY_V_VALUE_27, LEGACY_V_VALUE_28};
6use crate::shim::crypto::Signature;
7use crate::shim::fvm_shared_latest;
8use anyhow::{Context, bail, ensure};
9use bytes::BytesMut;
10use cbor4ii::core::{Value, dec::Decode as _, utils::SliceReader};
11use fvm_shared4::METHOD_CONSTRUCTOR;
12use num::{BigInt, Signed as _, bigint::Sign};
13use num_derive::FromPrimitive;
14use num_traits::cast::ToPrimitive;
15use rlp::Rlp;
16
17use crate::{
18 message::{Message as _, SignedMessage},
19 rpc::eth::types::EthAddress,
20 shim::{address::Address, crypto::SignatureType, message::Message, version::NetworkVersion},
21};
22
23use super::{
24 EIP_1559_TX_TYPE, EIP_2930_TX_TYPE, EthChainId,
25 eip_155_transaction::{
26 EIP_155_SIG_PREFIX, EthLegacyEip155TxArgs, EthLegacyEip155TxArgsBuilder,
27 calc_valid_eip155_sig_len,
28 },
29 eip_1559_transaction::{EIP_1559_SIG_LEN, EthEip1559TxArgs, EthEip1559TxArgsBuilder},
30 homestead_transaction::{
31 EthLegacyHomesteadTxArgs, EthLegacyHomesteadTxArgsBuilder, HOMESTEAD_SIG_LEN,
32 HOMESTEAD_SIG_PREFIX,
33 },
34};
35#[derive(FromPrimitive)]
37#[repr(u64)]
38pub enum EAMMethod {
39 Constructor = METHOD_CONSTRUCTOR,
40 Create = 2,
41 Create2 = 3,
42 CreateExternal = 4,
43}
44
45#[derive(FromPrimitive)]
46#[repr(u64)]
47pub enum EVMMethod {
48 Constructor = METHOD_CONSTRUCTOR,
49 Resurrect = 2,
50 GetBytecode = 3,
51 GetBytecodeHash = 4,
52 GetStorageAt = 5,
53 InvokeContractDelegate = 6,
54 InvokeContract = 3844450837,
59}
60
61#[derive(Debug)]
64pub enum EthTx {
65 Homestead(Box<EthLegacyHomesteadTxArgs>),
66 Eip1559(Box<EthEip1559TxArgs>),
67 Eip155(Box<EthLegacyEip155TxArgs>),
68}
69
70impl EthTx {
71 pub fn from_signed_message(
74 eth_chain_id: EthChainId,
75 msg: &SignedMessage,
76 ) -> anyhow::Result<Self> {
77 Self::ensure_signed_message_valid(msg)?;
78
79 let sig_len = msg.signature().bytes().len();
81
82 let valid_eip_155_signature_lengths = calc_valid_eip155_sig_len(eth_chain_id);
86
87 let tx: Self = if sig_len == EIP_1559_SIG_LEN {
88 let args = EthEip1559TxArgsBuilder::default()
89 .chain_id(eth_chain_id)
90 .unsigned_message(msg.message())?
91 .build()?
92 .with_signature(msg.signature())?;
93 EthTx::Eip1559(Box::new(args))
94 } else if sig_len == HOMESTEAD_SIG_LEN
95 || sig_len == valid_eip_155_signature_lengths.0 as usize
96 || sig_len == valid_eip_155_signature_lengths.1 as usize
97 {
98 match *msg.signature().bytes().first().expect("infallible") {
100 HOMESTEAD_SIG_PREFIX => {
101 let args = EthLegacyHomesteadTxArgsBuilder::default()
102 .unsigned_message(msg.message())?
103 .build()?
104 .with_signature(msg.signature())?;
105 EthTx::Homestead(Box::new(args))
106 }
107 EIP_155_SIG_PREFIX => {
108 let args = EthLegacyEip155TxArgsBuilder::default()
109 .chain_id(eth_chain_id)
110 .unsigned_message(msg.message())?
111 .build()?
112 .with_signature(msg.signature())?;
113 EthTx::Eip155(Box::new(args))
114 }
115 _ => bail!("unsupported signature prefix"),
116 }
117 } else {
118 bail!("unsupported signature length: {sig_len}");
119 };
120
121 Ok(tx)
122 }
123
124 pub fn eth_hash(&self) -> anyhow::Result<keccak_hash::H256> {
125 Ok(keccak_hash::keccak(self.rlp_signed_message()?))
126 }
127
128 pub fn get_signed_message(&self, eth_chain_id: EthChainId) -> anyhow::Result<SignedMessage> {
129 let from = self.sender(eth_chain_id)?;
130 let msg = match self {
131 Self::Homestead(tx) => (*tx).get_signed_message(from)?,
132 Self::Eip1559(tx) => (*tx).get_signed_message(from, eth_chain_id)?,
133 Self::Eip155(tx) => (*tx).get_signed_message(from, eth_chain_id)?,
134 };
135 Ok(msg)
136 }
137
138 pub fn get_unsigned_message(
139 &self,
140 from: Address,
141 eth_chain_id: EthChainId,
142 ) -> anyhow::Result<Message> {
143 let msg = match self {
144 Self::Homestead(tx) => (*tx).get_unsigned_message(from)?,
145 Self::Eip1559(tx) => (*tx).get_unsigned_message(from, eth_chain_id)?,
146 Self::Eip155(tx) => (*tx).get_unsigned_message(from, eth_chain_id)?,
147 };
148 Ok(msg)
149 }
150
151 pub fn rlp_unsigned_message(&self, eth_chain_id: EthChainId) -> anyhow::Result<Vec<u8>> {
152 match self {
153 Self::Homestead(tx) => (*tx).rlp_unsigned_message(),
154 Self::Eip1559(tx) => (*tx).rlp_unsigned_message(),
155 Self::Eip155(tx) => (*tx).rlp_unsigned_message(eth_chain_id),
156 }
157 }
158
159 pub fn rlp_signed_message(&self) -> anyhow::Result<Vec<u8>> {
160 match self {
161 Self::Homestead(tx) => (*tx).rlp_signed_message(),
162 Self::Eip1559(tx) => (*tx).rlp_signed_message(),
163 Self::Eip155(tx) => (*tx).rlp_signed_message(),
164 }
165 }
166
167 fn signature(&self, eth_chain_id: EthChainId) -> anyhow::Result<Signature> {
168 match self {
169 Self::Homestead(tx) => (*tx).signature(),
170 Self::Eip1559(tx) => (*tx).signature(),
171 Self::Eip155(tx) => (*tx).signature(eth_chain_id),
172 }
173 }
174
175 pub fn to_verifiable_signature(
176 &self,
177 sig: Vec<u8>,
178 eth_chain_id: EthChainId,
179 ) -> anyhow::Result<Vec<u8>> {
180 match self {
181 Self::Homestead(tx) => (*tx).to_verifiable_signature(sig),
182 Self::Eip1559(tx) => (*tx).to_verifiable_signature(sig),
183 Self::Eip155(tx) => (*tx).to_verifiable_signature(sig, eth_chain_id),
184 }
185 }
186
187 pub fn is_eip1559(&self) -> bool {
189 matches!(self, EthTx::Eip1559(_))
190 }
191
192 fn ensure_signed_message_valid(msg: &SignedMessage) -> anyhow::Result<()> {
195 ensure!(
196 msg.signature().signature_type() == SignatureType::Delegated,
197 "Signature is not delegated type"
198 );
199
200 ensure!(
201 msg.message().version == 0,
202 "unsupported msg version: {}",
203 msg.message().version
204 );
205
206 EthAddress::from_filecoin_address(&msg.from())?;
207
208 Ok(())
209 }
210
211 fn sender(&self, eth_chain_id: EthChainId) -> anyhow::Result<Address> {
212 let hash = keccak_hash::keccak(self.rlp_unsigned_message(eth_chain_id)?);
213 let sig = self.signature(eth_chain_id)?;
214 let sig_data = self.to_verifiable_signature(sig.bytes().to_vec(), eth_chain_id)?[..]
215 .try_into()
216 .expect("Incorrect signature length");
217 let pubkey =
218 fvm_shared_latest::crypto::signature::ops::recover_secp_public_key(&hash.0, &sig_data)?;
219 let eth_addr = EthAddress::eth_address_from_pub_key(&pubkey)?;
220 eth_addr.to_filecoin_address()
221 }
222}
223
224pub fn is_valid_eth_tx_for_sending(
226 eth_chain_id: EthChainId,
227 network_version: NetworkVersion,
228 message: &SignedMessage,
229) -> bool {
230 let eth_tx = EthTx::from_signed_message(eth_chain_id, message);
231
232 if let Ok(eth_tx) = eth_tx {
233 network_version >= NetworkVersion::V23 || eth_tx.is_eip1559()
236 } else {
237 false
238 }
239}
240
241pub fn get_eth_params_and_recipient(
243 msg: &Message,
244) -> anyhow::Result<(Vec<u8>, Option<EthAddress>)> {
245 let mut to = None;
246 let mut params = vec![];
247
248 ensure!(msg.version == 0, "unsupported msg version: {}", msg.version);
249
250 if !msg.params().bytes().is_empty() {
251 let mut reader = SliceReader::new(msg.params().bytes());
252 match Value::decode(&mut reader) {
253 Ok(Value::Bytes(bytes)) => params = bytes,
254 _ => bail!("failed to read params byte array"),
255 }
256 }
257
258 if msg.to == Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR {
259 if msg.method_num() != EAMMethod::CreateExternal as u64 {
260 bail!("unsupported EAM method");
261 }
262 } else if msg.method_num() == EVMMethod::InvokeContract as u64 {
263 let addr = EthAddress::from_filecoin_address(&msg.to)?;
264 to = Some(addr);
265 } else {
266 bail!(
267 "invalid methodnum {}: only allowed method is InvokeContract({})",
268 msg.method_num(),
269 EVMMethod::InvokeContract as u64
270 );
271 }
272
273 Ok((params, to))
274}
275
276pub fn format_u64(value: u64) -> BytesMut {
277 if value != 0 {
278 let i = (value.leading_zeros() / 8) as usize;
279 let bytes = value.to_be_bytes();
280 bytes.get(i..).expect("failed to get slice").into()
284 } else {
285 BytesMut::new()
287 }
288}
289
290pub fn format_bigint(value: &BigInt) -> anyhow::Result<BytesMut> {
291 Ok(if value.is_positive() {
292 BytesMut::from_iter(value.to_bytes_be().1.iter())
293 } else {
294 if value.is_negative() {
295 bail!("can't format a negative number");
296 }
297 BytesMut::new()
299 })
300}
301
302pub fn format_address(value: &Option<EthAddress>) -> BytesMut {
303 if let Some(addr) = value {
304 addr.0.as_bytes().into()
305 } else {
306 BytesMut::new()
307 }
308}
309
310pub fn pad_leading_zeros(data: Vec<u8>, length: usize) -> Vec<u8> {
312 if data.len() >= length {
313 return data;
314 }
315 let mut zeros = vec![0; length - data.len()];
316 zeros.extend(data);
317 zeros
318}
319
320pub fn parse_eth_transaction(data: &[u8]) -> anyhow::Result<EthTx> {
322 ensure!(!data.is_empty(), "eth transaction data is empty");
323
324 match data.first() {
325 Some(&EIP_2930_TX_TYPE) => {
326 Err(anyhow::anyhow!("EIP-2930 transaction is not supported"))
328 }
329 Some(&EIP_1559_TX_TYPE) => {
330 parse_eip1559_tx(data).context("Failed to parse EIP-1559 transaction")
331 }
332 Some(tx_type) if *tx_type > 0x7f => parse_legacy_tx(data)
333 .map_err(|err| anyhow::anyhow!("failed to parse legacy transaction: {}", err)),
334 _ => Err(anyhow::anyhow!("unsupported transaction type")),
335 }
336}
337
338fn parse_eip1559_tx(data: &[u8]) -> anyhow::Result<EthTx> {
339 let decoded = Rlp::new(data.get(1..).context("failed to get range of values")?);
341 ensure!(
342 decoded.item_count()? == 12,
343 "not an EIP-1559 transaction: should have 12 elements in the Rlp list"
344 );
345
346 let chain_id = decoded.at(0)?.as_val::<u64>()?;
347 let nonce = decoded.at(1)?.as_val::<u64>()?;
348 let max_priority_fee_per_gas = BigInt::from_bytes_be(Sign::Plus, decoded.at(2)?.data()?);
349 let max_fee_per_gas = BigInt::from_bytes_be(Sign::Plus, decoded.at(3)?.data()?);
350 let gas_limit = decoded.at(4)?.as_val::<u64>()?;
351
352 let addr_data = decoded.at(5)?.data()?;
353 let to = (!addr_data.is_empty())
354 .then(|| EthAddress::try_from(addr_data))
355 .transpose()?;
356
357 let value = BigInt::from_bytes_be(Sign::Plus, decoded.at(6)?.data()?);
358 let input = decoded.at(7)?.data()?.to_vec();
359
360 ensure!(
362 decoded.at(8)?.item_count()? == 0,
363 "access list should be an empty list"
364 );
365
366 let v = BigInt::from_bytes_be(Sign::Plus, decoded.at(9)?.data()?);
367 let r = BigInt::from_bytes_be(Sign::Plus, decoded.at(10)?.data()?);
368 let s = BigInt::from_bytes_be(Sign::Plus, decoded.at(11)?.data()?);
369
370 ensure!(
372 v == BigInt::from(0) || v == BigInt::from(1),
373 "EIP-1559 transactions only support 0 or 1 for v"
374 );
375
376 let tx_args = EthEip1559TxArgs {
378 chain_id,
379 nonce,
380 to,
381 max_priority_fee_per_gas,
382 max_fee_per_gas,
383 gas_limit,
384 value,
385 input,
386 v,
387 r,
388 s,
389 };
390
391 Ok(EthTx::Eip1559(Box::new(tx_args)))
392}
393
394fn parse_legacy_tx(data: &[u8]) -> anyhow::Result<EthTx> {
395 let decoded = Rlp::new(data);
397 if decoded.item_count()? != 9 {
398 bail!("not a Legacy transaction: should have 9 elements in the rlp list");
399 }
400
401 let nonce = decoded.at(0)?.as_val::<u64>()?;
403 let gas_price = BigInt::from_bytes_be(Sign::Plus, decoded.at(1)?.data()?);
404 let gas_limit = decoded.at(2)?.as_val::<u64>()?;
405
406 let addr_data = decoded.at(3)?.data()?;
407 let to = (!addr_data.is_empty())
408 .then(|| EthAddress::try_from(addr_data))
409 .transpose()?;
410
411 let value = BigInt::from_bytes_be(Sign::Plus, decoded.at(4)?.data()?);
412 let input = decoded.at(5)?.data()?.to_vec();
413
414 let v = BigInt::from_bytes_be(Sign::Plus, decoded.at(6)?.data()?);
416 let r = BigInt::from_bytes_be(Sign::Plus, decoded.at(7)?.data()?);
417 let s = BigInt::from_bytes_be(Sign::Plus, decoded.at(8)?.data()?);
418
419 let chain_id = derive_eip_155_chain_id(&v)?
421 .to_u64()
422 .context("unable to convert derived chain to `u64`")?;
423
424 if chain_id == 0 {
426 ensure!(
428 v == BigInt::from(LEGACY_V_VALUE_27) || v == BigInt::from(LEGACY_V_VALUE_28),
429 "legacy homestead transactions only support 27 or 28 for v, got {}",
430 v
431 );
432
433 let tx_args = EthLegacyHomesteadTxArgs {
434 nonce,
435 gas_price,
436 gas_limit,
437 to,
438 value,
439 input,
440 v,
441 r,
442 s,
443 };
444 return Ok(EthTx::Homestead(Box::new(tx_args)));
445 }
446
447 validate_eip155_chain_id(chain_id, &v)?;
449
450 Ok(EthTx::Eip155(Box::new(EthLegacyEip155TxArgs {
451 chain_id,
452 nonce,
453 gas_price,
454 gas_limit,
455 to,
456 value,
457 input,
458 v,
459 r,
460 s,
461 })))
462}
463
464#[derive(Debug)]
465pub struct MethodInfo {
466 pub to: Address,
467 pub method: u64,
468 pub params: Vec<u8>,
469}
470
471pub fn get_filecoin_method_info(
473 recipient: &Option<EthAddress>,
474 input: &[u8],
475) -> anyhow::Result<MethodInfo> {
476 let params = if !input.is_empty() {
477 cbor4ii::serde::to_vec(
478 Vec::with_capacity(input.len()),
479 &Value::Bytes(input.to_vec()),
480 )
481 .context("failed to encode params")?
482 } else {
483 Vec::new()
484 };
485
486 let (to, method) = match recipient {
487 None => {
488 (
490 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR,
491 EAMMethod::CreateExternal as u64,
492 )
493 }
494 Some(recipient) => {
495 let to = recipient
497 .to_filecoin_address()
498 .context("failed to convert EthAddress to Filecoin address")?;
499 (to, EVMMethod::InvokeContract as u64)
500 }
501 };
502
503 Ok(MethodInfo { to, method, params })
504}
505
506#[cfg(test)]
507pub(crate) mod tests {
508 use super::*;
509 use crate::{
510 networks::{calibnet, mainnet},
511 shim::{crypto::Signature, econ::TokenAmount},
512 };
513 use num::{BigInt, Num as _, Zero as _, traits::FromBytes as _};
514 use num_bigint::ToBigUint as _;
515 use quickcheck_macros::quickcheck;
516 use std::str::FromStr as _;
517
518 const ETH_ADDR_LEN: usize = 20;
519
520 pub fn create_message() -> Message {
521 let from = EthAddress::from_str("0xff38c072f286e3b20b3954ca9f99c05fbecc64aa")
522 .unwrap()
523 .to_filecoin_address()
524 .unwrap();
525
526 let to = Address::new_id(1);
527 Message {
528 version: 0,
529 to,
530 from,
531 value: TokenAmount::from_atto(10),
532 gas_fee_cap: TokenAmount::from_atto(11),
533 gas_premium: TokenAmount::from_atto(12),
534 gas_limit: 13,
535 sequence: 14,
536 method_num: EVMMethod::InvokeContract as u64,
537 params: Default::default(),
538 }
539 }
540
541 pub fn create_eip_1559_signed_message() -> SignedMessage {
542 let mut eip_1559_sig = vec![0u8; EIP_1559_SIG_LEN];
543 eip_1559_sig[0] = EIP_155_SIG_PREFIX;
544
545 SignedMessage {
546 message: create_message(),
547 signature: Signature::new(SignatureType::Delegated, eip_1559_sig),
548 }
549 }
550
551 pub fn create_homestead_signed_message() -> SignedMessage {
552 let mut homestead_sig = vec![0u8; HOMESTEAD_SIG_LEN];
553 homestead_sig[0] = HOMESTEAD_SIG_PREFIX;
554 homestead_sig[HOMESTEAD_SIG_LEN - 1] = 27;
555
556 SignedMessage {
557 message: create_message(),
558 signature: Signature::new(SignatureType::Delegated, homestead_sig),
559 }
560 }
561
562 #[test]
563 fn test_ensure_signed_message_valid() {
564 let create_empty_delegated_message = || SignedMessage {
565 message: create_message(),
566 signature: Signature::new(SignatureType::Delegated, vec![]),
567 };
568 let msg = create_empty_delegated_message();
570 EthTx::ensure_signed_message_valid(&msg).unwrap();
571
572 let mut msg = create_empty_delegated_message();
574 msg.signature = Signature::new(SignatureType::Bls, vec![]);
575 assert!(EthTx::ensure_signed_message_valid(&msg).is_err());
576
577 let mut msg = create_empty_delegated_message();
579 msg.message.version = 1;
580 assert!(EthTx::ensure_signed_message_valid(&msg).is_err());
581
582 let mut msg = create_empty_delegated_message();
584 msg.message.from = Address::new_delegated(0x42, &[0xff; ETH_ADDR_LEN]).unwrap();
585 assert!(EthTx::ensure_signed_message_valid(&msg).is_err());
586 }
587
588 #[test]
589 fn test_eth_transaction_from_signed_filecoin_message_valid_eip1559() {
590 let msg = create_eip_1559_signed_message();
591
592 let tx = EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).unwrap();
593 if let EthTx::Eip1559(tx) = tx {
594 assert_eq!(tx.chain_id, mainnet::ETH_CHAIN_ID);
595 assert_eq!(tx.value, msg.message.value.into());
596 assert_eq!(tx.max_fee_per_gas, msg.message.gas_fee_cap.into());
597 assert_eq!(tx.max_priority_fee_per_gas, msg.message.gas_premium.into());
598 assert_eq!(tx.gas_limit, msg.message.gas_limit);
599 assert_eq!(tx.nonce, msg.message.sequence);
600 assert_eq!(
601 tx.to.unwrap(),
602 EthAddress::from_filecoin_address(&msg.message.to).unwrap()
603 );
604 assert!(tx.input.is_empty());
605 } else {
606 panic!("invalid transaction type");
607 }
608 }
609
610 #[test]
611 fn test_eth_transaction_from_signed_filecoin_message_valid_homestead() {
612 let msg = create_homestead_signed_message();
613 let tx = EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).unwrap();
614 if let EthTx::Homestead(tx) = tx {
615 assert_eq!(tx.value, msg.message.value.into());
616 assert_eq!(tx.gas_limit, msg.message.gas_limit);
617 assert_eq!(tx.nonce, msg.message.sequence);
618 assert_eq!(
619 tx.to.unwrap(),
620 EthAddress::from_filecoin_address(&msg.message.to).unwrap()
621 );
622 assert_eq!(tx.gas_price, msg.message.gas_fee_cap.into());
623 assert!(tx.input.is_empty());
624 } else {
625 panic!("invalid transaction type");
626 }
627 }
628
629 #[test]
630 fn test_eth_transaction_from_signed_filecoin_message_valid_eip_155() {
631 let eth_chain_id = mainnet::ETH_CHAIN_ID;
632
633 let v = (2 * eth_chain_id + 35).to_biguint().unwrap().to_bytes_be();
636 let eip_155_sig_len = calc_valid_eip155_sig_len(eth_chain_id).0 as usize;
637 let mut eip_155_sig = vec![0u8; eip_155_sig_len as usize - v.len()];
638 eip_155_sig[0] = EIP_155_SIG_PREFIX;
639 eip_155_sig.extend(v);
640
641 let msg = SignedMessage {
642 message: create_message(),
643 signature: Signature::new(SignatureType::Delegated, eip_155_sig),
644 };
645
646 let tx = EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).unwrap();
647 if let EthTx::Eip155(tx) = tx {
648 assert_eq!(tx.value, msg.message.value.into());
649 assert_eq!(tx.gas_limit, msg.message.gas_limit);
650 assert_eq!(tx.nonce, msg.message.sequence);
651 assert_eq!(
652 tx.to.unwrap(),
653 EthAddress::from_filecoin_address(&msg.message.to).unwrap()
654 );
655 assert_eq!(tx.gas_price, msg.message.gas_fee_cap.into());
656 assert!(tx.input.is_empty());
657 } else {
658 panic!("invalid transaction type");
659 }
660 }
661
662 #[test]
663 fn test_eth_transaction_from_signed_filecoin_message_empty_signature() {
664 let msg = SignedMessage {
665 message: create_message(),
666 signature: Signature::new(SignatureType::Delegated, vec![]),
667 };
668
669 assert!(EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).is_err());
670 }
671
672 #[test]
673 fn test_eth_transaction_from_signed_filecoin_message_invalid_signature() {
674 let msg = SignedMessage {
675 message: create_message(),
676 signature: Signature::new(
677 SignatureType::Delegated,
678 b"Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn".to_vec(),
679 ),
680 };
681
682 assert!(EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).is_err());
683 }
684
685 #[test]
686 fn test_is_valid_eth_tx_for_sending_eip1559_always_valid() {
687 let msg = create_eip_1559_signed_message();
688 assert!(is_valid_eth_tx_for_sending(
689 mainnet::ETH_CHAIN_ID,
690 NetworkVersion::V22,
691 &msg
692 ));
693
694 assert!(is_valid_eth_tx_for_sending(
695 mainnet::ETH_CHAIN_ID,
696 NetworkVersion::V23,
697 &msg
698 ));
699 }
700
701 #[test]
702 fn test_is_valid_eth_tx_for_sending_legacy_valid_post_nv23() {
703 let msg = create_homestead_signed_message();
704 assert!(is_valid_eth_tx_for_sending(
705 mainnet::ETH_CHAIN_ID,
706 NetworkVersion::V23,
707 &msg
708 ));
709 }
710
711 #[test]
712 fn test_is_valid_eth_tx_for_sending_legacy_invalid_pre_nv23() {
713 let msg = create_homestead_signed_message();
714 assert!(!is_valid_eth_tx_for_sending(
715 mainnet::ETH_CHAIN_ID,
716 NetworkVersion::V22,
717 &msg
718 ));
719 }
720
721 #[test]
722 fn test_is_valid_eth_tx_for_sending_invalid_non_delegated() {
723 let msg = create_message();
724 let msg = SignedMessage {
725 message: msg,
726 signature: Signature::new_secp256k1(vec![]),
727 };
728 assert!(!is_valid_eth_tx_for_sending(
729 mainnet::ETH_CHAIN_ID,
730 NetworkVersion::V22,
731 &msg
732 ));
733 assert!(!is_valid_eth_tx_for_sending(
734 mainnet::ETH_CHAIN_ID,
735 NetworkVersion::V23,
736 &msg
737 ));
738 }
739
740 #[test]
741 fn test_eip_1559() {
742 let mut tx_args=EthEip1559TxArgsBuilder::default()
743 .chain_id(314159_u64)
744 .nonce(486_u64)
745 .to(Some(
746 ethereum_types::H160::from_str("0xeb4a9cdb9f42d3a503d580a39b6e3736eb21fffd")
747 .unwrap()
748 .into(),
749 ))
750 .value(BigInt::from(0))
751 .max_fee_per_gas(BigInt::from(1500000120))
752 .max_priority_fee_per_gas(BigInt::from(1500000000))
753 .gas_limit(37442471_u64)
754 .input(hex::decode("383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000").unwrap())
755 .build()
756 .unwrap();
757 tx_args.v = BigInt::from_str("1").unwrap();
758 tx_args.r = BigInt::from_str(
759 "84103132941276310528712440865285269631208564772362393569572880532520338257200",
760 )
761 .unwrap();
762 tx_args.s = BigInt::from_str(
763 "7820796778417228639067439047870612492553874254089570360061550763595363987236",
764 )
765 .unwrap();
766 let tx = EthTx::Eip1559(Box::new(tx_args));
767 let sig = tx.signature(calibnet::ETH_CHAIN_ID);
768 assert!(sig.is_ok());
769 assert!(
770 tx.to_verifiable_signature(sig.unwrap().bytes().to_vec(), calibnet::ETH_CHAIN_ID)
771 .is_ok()
772 );
773 assert!(tx.rlp_unsigned_message(calibnet::ETH_CHAIN_ID).is_ok());
774 assert!(tx.get_signed_message(calibnet::ETH_CHAIN_ID).is_ok());
775 let expected_hash = ethereum_types::H256::from_str(
776 "0x9f2e70d5737c6b798eccea14895893fb48091ab3c59d0fe95508dc7efdae2e5f",
777 )
778 .unwrap();
779 assert_eq!(expected_hash, tx.eth_hash().unwrap());
780 }
781
782 #[test]
783 fn test_legacy_eip_155() {
784 let mut tx_args = EthLegacyEip155TxArgsBuilder::default()
786 .chain_id(314159_u64)
787 .nonce(0x4_u64)
788 .to(Some(
789 ethereum_types::H160::from_str("0xd0fb381fc644cdd5d694d35e1afb445527b9244b")
790 .unwrap()
791 .into(),
792 ))
793 .value(BigInt::from(0))
794 .gas_limit(0x19ca81cc_u64)
795 .gas_price(BigInt::from(0x40696))
796 .input(hex::decode("d5b3d76d00000000000000000000000000000000000000000000000045466fa6fdcb80000000000000000000000000000000000000000000000000000000002e90edd0000000000000000000000000000000000000000000000000000000000000015180").unwrap())
797 .build()
798 .unwrap();
799 tx_args.v = BigInt::from_str_radix("99681", 16).unwrap();
800 tx_args.r = BigInt::from_str_radix(
801 "580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796",
802 16,
803 )
804 .unwrap();
805 tx_args.s = BigInt::from_str_radix(
806 "55e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
807 16,
808 )
809 .unwrap();
810 let tx = EthTx::Eip155(Box::new(tx_args));
811 let sig = tx.signature(calibnet::ETH_CHAIN_ID);
812 assert!(sig.is_ok());
813 assert!(
814 tx.to_verifiable_signature(sig.unwrap().bytes().to_vec(), calibnet::ETH_CHAIN_ID)
815 .is_ok()
816 );
817 assert!(tx.rlp_unsigned_message(calibnet::ETH_CHAIN_ID).is_ok());
818 assert!(tx.get_signed_message(calibnet::ETH_CHAIN_ID).is_ok());
819 let expected_hash = ethereum_types::H256::from_str(
820 "0x3ebc897150feeff6caa1b2e5992e347e8409e9e35fa30f7f5f8fcda3f7c965c7",
821 )
822 .unwrap();
823 assert_eq!(expected_hash, tx.eth_hash().unwrap());
824 }
825
826 #[test]
827 fn test_legacy_homestead() {
828 let mut tx_args = EthLegacyHomesteadTxArgsBuilder::default()
830 .nonce(0x4_u64)
831 .to(Some(
832 ethereum_types::H160::from_str("0xd0fb381fc644cdd5d694d35e1afb445527b9244b")
833 .unwrap()
834 .into(),
835 ))
836 .value(BigInt::from(0))
837 .gas_limit(0x19ca81cc_u64)
838 .gas_price(BigInt::from(0x40696))
839 .input(hex::decode("d5b3d76d00000000000000000000000000000000000000000000000045466fa6fdcb80000000000000000000000000000000000000000000000000000000002e90edd0000000000000000000000000000000000000000000000000000000000000015180").unwrap())
840 .build()
841 .unwrap();
842 tx_args.v = BigInt::from_str_radix("99681", 16).unwrap();
845 tx_args.r = BigInt::from_str_radix(
846 "580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796",
847 16,
848 )
849 .unwrap();
850 tx_args.s = BigInt::from_str_radix(
851 "55e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
852 16,
853 )
854 .unwrap();
855 let tx = EthTx::Homestead(Box::new(tx_args.clone()));
856 let expected_hash = ethereum_types::H256::from_str(
857 "0x3ebc897150feeff6caa1b2e5992e347e8409e9e35fa30f7f5f8fcda3f7c965c7",
858 )
859 .unwrap();
860 assert_eq!(expected_hash, tx.eth_hash().unwrap());
861 tx_args.v = BigInt::from_str_radix("1b", 16).unwrap();
863 let tx = EthTx::Homestead(Box::new(tx_args.clone()));
864 let sig = tx.signature(calibnet::ETH_CHAIN_ID);
865 assert!(sig.is_ok());
866 assert!(
867 tx.to_verifiable_signature(sig.unwrap().bytes().to_vec(), calibnet::ETH_CHAIN_ID)
868 .is_ok()
869 );
870 assert!(tx.rlp_unsigned_message(calibnet::ETH_CHAIN_ID).is_ok());
871 assert!(tx.get_signed_message(calibnet::ETH_CHAIN_ID).is_ok());
872 }
873
874 #[quickcheck]
875 fn u64_roundtrip(i: u64) {
876 let bm = format_u64(i);
877 if i == 0 {
878 assert!(bm.is_empty());
879 } else {
880 let freezed = bm.freeze();
882 assert!(!freezed.starts_with(&[0]));
883
884 let mut padded = [0u8; 8];
886 let bytes: &[u8] = &freezed.slice(..);
887 padded[8 - bytes.len()..].copy_from_slice(bytes);
888 assert_eq!(i, u64::from_be_bytes(padded));
889 }
890 }
891
892 #[quickcheck]
893 fn bigint_roundtrip(bi: num_bigint::BigInt) {
894 match format_bigint(&bi) {
895 Ok(bm) => {
896 if bi.is_zero() {
897 assert!(bm.is_empty());
898 } else {
899 let freezed = bm.freeze();
901 assert!(!freezed.starts_with(&[0]));
902
903 let unsigned = num_bigint::BigUint::from_be_bytes(&freezed.slice(..));
905 assert_eq!(bi, unsigned.into());
906 }
907 }
908 Err(_) => {
909 assert!(bi.is_negative());
911 }
912 }
913 }
914
915 #[test]
916 fn test_pad_leading_zeros() {
917 let data = vec![1, 2, 3];
919 let padded = pad_leading_zeros(data, 5);
920 assert_eq!(padded, vec![0, 0, 1, 2, 3]);
921
922 let data = vec![4, 5, 6];
924 let padded = pad_leading_zeros(data, 3);
925 assert_eq!(padded, vec![4, 5, 6]);
926
927 let data = vec![7, 8, 9, 10];
929 let padded = pad_leading_zeros(data, 3); assert_eq!(padded, vec![7, 8, 9, 10]); let data = vec![];
934 let padded = pad_leading_zeros(data, 4);
935 assert_eq!(padded, vec![0, 0, 0, 0]);
936 }
937
938 #[test]
939 fn test_parse_eth_transaction() {
940 let raw_tx = hex::decode(
942 "f8cc04830406968419ca81cc94d0fb381fc644cdd5d694d35e1afb445527b9244b80b864d5b3d76d00000000000000000000000000000000000000000000000045466fa6fdcb80000000000000000000000000000000000000000000000000000000002e90edd000000000000000000000000000000000000000000000000000000000000001518083099681a0580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796a055e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
943 )
944 .expect("Invalid hex");
945 let eth_tx = parse_eth_transaction(&raw_tx);
946 assert!(eth_tx.is_ok());
947
948 let raw_tx = hex::decode(
950 "02f901368304cb2f8201e68459682f008459682f7884023b53a794eb4a9cdb9f42d3a503d580a39b6e3736eb21fffd80b8c4383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000c001a0b9f0afb3fa8821fa414bac6056e613c61a8263ca341b59539096dbbc8600f530a0114a6a032347e132f115accc7664ccc61549be28f5b844c3fc170006feb72f24",
951 )
952 .expect("Invalid hex");
953 let eth_tx = parse_eth_transaction(&raw_tx);
954 assert!(eth_tx.is_ok());
955 }
956
957 #[test]
958 fn test_derive_sender() {
959 let raw_tx = hex::decode(
961 "f8cc04830406968419ca81cc94d0fb381fc644cdd5d694d35e1afb445527b9244b80b864d5b3d76d00000000000000000000000000000000000000000000000045466fa6fdcb80000000000000000000000000000000000000000000000000000000002e90edd000000000000000000000000000000000000000000000000000000000000001518083099681a0580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796a055e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
962 )
963 .expect("Invalid hex");
964 let eth_tx = parse_eth_transaction(&raw_tx).unwrap();
965 let from = eth_tx.sender(calibnet::ETH_CHAIN_ID).unwrap();
966 assert_eq!(
967 EthAddress::from_filecoin_address(&from).unwrap(),
968 EthAddress::from_str("0xEb1D0C87B7e33D0Ab44a397b675F0897295491C2").unwrap()
969 );
970
971 let raw_tx = hex::decode(
973 "02f901368304cb2f8201e68459682f008459682f7884023b53a794eb4a9cdb9f42d3a503d580a39b6e3736eb21fffd80b8c4383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000c001a0b9f0afb3fa8821fa414bac6056e613c61a8263ca341b59539096dbbc8600f530a0114a6a032347e132f115accc7664ccc61549be28f5b844c3fc170006feb72f24",
974 )
975 .expect("Invalid hex");
976 let eth_tx = parse_eth_transaction(&raw_tx).unwrap();
977 let from = eth_tx.sender(calibnet::ETH_CHAIN_ID).unwrap();
978 assert_eq!(
979 EthAddress::from_filecoin_address(&from).unwrap(),
980 EthAddress::from_str("0x4fda4174D5D07C906395bfB77806287cc65Fd129").unwrap()
981 );
982 }
983
984 #[test]
985 fn test_parse_legacy_tx() {
986 let raw_tx = hex::decode(
988 "f8cc04830406968419ca81cc94d0fb381fc644cdd5d694d35e1afb445527b9244b80b864d5b3d76d00000000000000000000000000000000000000000000000045466fa6fdcb80000000000000000000000000000000000000000000000000000000002e90edd000000000000000000000000000000000000000000000000000000000000001518083099681a0580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796a055e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
989 )
990 .expect("Invalid hex");
991
992 let result = parse_legacy_tx(&raw_tx);
993 assert!(result.is_ok());
994 let txn = result.unwrap();
995
996 if let EthTx::Eip155(tx) = txn {
997 assert_eq!(tx.chain_id, 314159);
998 assert_eq!(tx.nonce, 4);
999 assert_eq!(tx.gas_price, BigInt::from(263830u64));
1000 assert_eq!(tx.gas_limit, 432701900);
1001 assert_eq!(
1002 tx.to.unwrap(),
1003 EthAddress::from_str("0xd0fb381fc644cdd5d694d35e1afb445527b9244b").unwrap()
1004 );
1005 assert_eq!(tx.value, BigInt::from(0));
1006 assert_eq!(
1007 tx.v,
1008 BigInt::from_str_radix("099681", 16).expect("Invalid hex string")
1009 );
1010 assert_eq!(
1011 tx.r,
1012 BigInt::from_str_radix(
1013 "580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796",
1014 16
1015 )
1016 .expect("Invalid hex string")
1017 );
1018 assert_eq!(
1019 tx.s,
1020 BigInt::from_str_radix(
1021 "55e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
1022 16
1023 )
1024 .expect("Invalid hex string")
1025 );
1026 } else {
1027 panic!("Expected EIP-1559 transaction");
1028 }
1029 }
1030
1031 #[test]
1032 fn test_parse_eip1559_tx() {
1033 let raw_tx = hex::decode(
1035 "02f901368304cb2f8201e68459682f008459682f7884023b53a794eb4a9cdb9f42d3a503d580a39b6e3736eb21fffd80b8c4383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000c001a0b9f0afb3fa8821fa414bac6056e613c61a8263ca341b59539096dbbc8600f530a0114a6a032347e132f115accc7664ccc61549be28f5b844c3fc170006feb72f24"
1036 ).expect("Invalid hex");
1037
1038 let result = parse_eip1559_tx(&raw_tx);
1039 assert!(result.is_ok());
1040
1041 let txn = result.unwrap();
1042
1043 if let EthTx::Eip1559(tx) = txn {
1044 assert_eq!(tx.chain_id, 314159);
1045 assert_eq!(tx.nonce, 486);
1046 assert_eq!(tx.max_fee_per_gas, BigInt::from(1500000120u64));
1047 assert_eq!(tx.max_priority_fee_per_gas, BigInt::from(1500000000u64));
1048 assert_eq!(tx.gas_limit, 37442471);
1049 assert_eq!(
1050 tx.to.unwrap(),
1051 EthAddress::from_str("0xeb4a9cdb9f42d3a503d580a39b6e3736eb21fffd").unwrap()
1052 );
1053 assert_eq!(tx.value, BigInt::from(0));
1054 assert_eq!(
1055 tx.v,
1056 BigInt::from_str_radix("01", 16).expect("Invalid hex string")
1057 );
1058 assert_eq!(
1059 tx.r,
1060 BigInt::from_str_radix(
1061 "b9f0afb3fa8821fa414bac6056e613c61a8263ca341b59539096dbbc8600f530",
1062 16
1063 )
1064 .expect("Invalid hex string")
1065 );
1066 assert_eq!(
1067 tx.s,
1068 BigInt::from_str_radix(
1069 "114a6a032347e132f115accc7664ccc61549be28f5b844c3fc170006feb72f24",
1070 16
1071 )
1072 .expect("Invalid hex string")
1073 );
1074 } else {
1075 panic!("Expected EIP-1559 transaction");
1076 }
1077 }
1078}