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