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::{
512 networks::{calibnet, mainnet},
513 shim::{crypto::Signature, econ::TokenAmount},
514 };
515 use num::{BigInt, Num as _, Zero as _, traits::FromBytes as _};
516 use num_bigint::ToBigUint as _;
517 use quickcheck_macros::quickcheck;
518 use std::str::FromStr as _;
519
520 const ETH_ADDR_LEN: usize = 20;
521
522 pub fn create_message() -> Message {
523 let from = EthAddress::from_str("0xff38c072f286e3b20b3954ca9f99c05fbecc64aa")
524 .unwrap()
525 .to_filecoin_address()
526 .unwrap();
527
528 let to = Address::new_id(1);
529 Message {
530 version: 0,
531 to,
532 from,
533 value: TokenAmount::from_atto(10),
534 gas_fee_cap: TokenAmount::from_atto(11),
535 gas_premium: TokenAmount::from_atto(12),
536 gas_limit: 13,
537 sequence: 14,
538 method_num: EVMMethod::InvokeContract as u64,
539 params: Default::default(),
540 }
541 }
542
543 pub fn create_eip_1559_signed_message() -> SignedMessage {
544 let mut eip_1559_sig = vec![0u8; EIP_1559_SIG_LEN];
545 eip_1559_sig[0] = EIP_155_SIG_PREFIX;
546
547 SignedMessage {
548 message: create_message(),
549 signature: Signature::new(SignatureType::Delegated, eip_1559_sig),
550 }
551 }
552
553 pub fn create_homestead_signed_message() -> SignedMessage {
554 let mut homestead_sig = vec![0u8; HOMESTEAD_SIG_LEN];
555 homestead_sig[0] = HOMESTEAD_SIG_PREFIX;
556 homestead_sig[HOMESTEAD_SIG_LEN - 1] = 27;
557
558 SignedMessage {
559 message: create_message(),
560 signature: Signature::new(SignatureType::Delegated, homestead_sig),
561 }
562 }
563
564 #[test]
565 fn test_ensure_signed_message_valid() {
566 let create_empty_delegated_message = || SignedMessage {
567 message: create_message(),
568 signature: Signature::new(SignatureType::Delegated, vec![]),
569 };
570 let msg = create_empty_delegated_message();
572 EthTx::ensure_signed_message_valid(&msg).unwrap();
573
574 let mut msg = create_empty_delegated_message();
576 msg.signature = Signature::new(SignatureType::Bls, vec![]);
577 assert!(EthTx::ensure_signed_message_valid(&msg).is_err());
578
579 let mut msg = create_empty_delegated_message();
581 msg.message.version = 1;
582 assert!(EthTx::ensure_signed_message_valid(&msg).is_err());
583
584 let mut msg = create_empty_delegated_message();
586 msg.message.from = Address::new_delegated(0x42, &[0xff; ETH_ADDR_LEN]).unwrap();
587 assert!(EthTx::ensure_signed_message_valid(&msg).is_err());
588 }
589
590 #[test]
591 fn test_eth_transaction_from_signed_filecoin_message_valid_eip1559() {
592 let msg = create_eip_1559_signed_message();
593
594 let tx = EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).unwrap();
595 if let EthTx::Eip1559(tx) = tx {
596 assert_eq!(tx.chain_id, mainnet::ETH_CHAIN_ID);
597 assert_eq!(tx.value, msg.message.value.into());
598 assert_eq!(tx.max_fee_per_gas, msg.message.gas_fee_cap.into());
599 assert_eq!(tx.max_priority_fee_per_gas, msg.message.gas_premium.into());
600 assert_eq!(tx.gas_limit, msg.message.gas_limit);
601 assert_eq!(tx.nonce, msg.message.sequence);
602 assert_eq!(
603 tx.to.unwrap(),
604 EthAddress::from_filecoin_address(&msg.message.to).unwrap()
605 );
606 assert!(tx.input.is_empty());
607 } else {
608 panic!("invalid transaction type");
609 }
610 }
611
612 #[test]
613 fn test_eth_transaction_from_signed_filecoin_message_valid_homestead() {
614 let msg = create_homestead_signed_message();
615 let tx = EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).unwrap();
616 if let EthTx::Homestead(tx) = tx {
617 assert_eq!(tx.value, msg.message.value.into());
618 assert_eq!(tx.gas_limit, msg.message.gas_limit);
619 assert_eq!(tx.nonce, msg.message.sequence);
620 assert_eq!(
621 tx.to.unwrap(),
622 EthAddress::from_filecoin_address(&msg.message.to).unwrap()
623 );
624 assert_eq!(tx.gas_price, msg.message.gas_fee_cap.into());
625 assert!(tx.input.is_empty());
626 } else {
627 panic!("invalid transaction type");
628 }
629 }
630
631 #[test]
632 fn test_eth_transaction_from_signed_filecoin_message_valid_eip_155() {
633 let eth_chain_id = mainnet::ETH_CHAIN_ID;
634
635 let v = (2 * eth_chain_id + 35).to_biguint().unwrap().to_bytes_be();
638 let eip_155_sig_len = calc_valid_eip155_sig_len(eth_chain_id).0 as usize;
639 let mut eip_155_sig = vec![0u8; eip_155_sig_len - v.len()];
640 eip_155_sig[0] = EIP_155_SIG_PREFIX;
641 eip_155_sig.extend(v);
642
643 let msg = SignedMessage {
644 message: create_message(),
645 signature: Signature::new(SignatureType::Delegated, eip_155_sig),
646 };
647
648 let tx = EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).unwrap();
649 if let EthTx::Eip155(tx) = tx {
650 assert_eq!(tx.value, msg.message.value.into());
651 assert_eq!(tx.gas_limit, msg.message.gas_limit);
652 assert_eq!(tx.nonce, msg.message.sequence);
653 assert_eq!(
654 tx.to.unwrap(),
655 EthAddress::from_filecoin_address(&msg.message.to).unwrap()
656 );
657 assert_eq!(tx.gas_price, msg.message.gas_fee_cap.into());
658 assert!(tx.input.is_empty());
659 } else {
660 panic!("invalid transaction type");
661 }
662 }
663
664 #[test]
665 fn test_eth_transaction_from_signed_filecoin_message_empty_signature() {
666 let msg = SignedMessage {
667 message: create_message(),
668 signature: Signature::new(SignatureType::Delegated, vec![]),
669 };
670
671 assert!(EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).is_err());
672 }
673
674 #[test]
675 fn test_eth_transaction_from_signed_filecoin_message_invalid_signature() {
676 let msg = SignedMessage {
677 message: create_message(),
678 signature: Signature::new(
679 SignatureType::Delegated,
680 b"Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn".to_vec(),
681 ),
682 };
683
684 assert!(EthTx::from_signed_message(mainnet::ETH_CHAIN_ID, &msg).is_err());
685 }
686
687 #[test]
688 fn test_is_valid_eth_tx_for_sending_eip1559_always_valid() {
689 let msg = create_eip_1559_signed_message();
690 assert!(is_valid_eth_tx_for_sending(
691 mainnet::ETH_CHAIN_ID,
692 NetworkVersion::V22,
693 &msg
694 ));
695
696 assert!(is_valid_eth_tx_for_sending(
697 mainnet::ETH_CHAIN_ID,
698 NetworkVersion::V23,
699 &msg
700 ));
701 }
702
703 #[test]
704 fn test_is_valid_eth_tx_for_sending_legacy_valid_post_nv23() {
705 let msg = create_homestead_signed_message();
706 assert!(is_valid_eth_tx_for_sending(
707 mainnet::ETH_CHAIN_ID,
708 NetworkVersion::V23,
709 &msg
710 ));
711 }
712
713 #[test]
714 fn test_is_valid_eth_tx_for_sending_legacy_invalid_pre_nv23() {
715 let msg = create_homestead_signed_message();
716 assert!(!is_valid_eth_tx_for_sending(
717 mainnet::ETH_CHAIN_ID,
718 NetworkVersion::V22,
719 &msg
720 ));
721 }
722
723 #[test]
724 fn test_is_valid_eth_tx_for_sending_invalid_non_delegated() {
725 let msg = create_message();
726 let msg = SignedMessage {
727 message: msg,
728 signature: Signature::new_secp256k1(vec![]),
729 };
730 assert!(!is_valid_eth_tx_for_sending(
731 mainnet::ETH_CHAIN_ID,
732 NetworkVersion::V22,
733 &msg
734 ));
735 assert!(!is_valid_eth_tx_for_sending(
736 mainnet::ETH_CHAIN_ID,
737 NetworkVersion::V23,
738 &msg
739 ));
740 }
741
742 #[test]
743 fn test_eip_1559() {
744 let mut tx_args=EthEip1559TxArgsBuilder::default()
745 .chain_id(314159_u64)
746 .nonce(486_u64)
747 .to(Some(
748 ethereum_types::H160::from_str("0xeb4a9cdb9f42d3a503d580a39b6e3736eb21fffd")
749 .unwrap()
750 .into(),
751 ))
752 .value(BigInt::from(0))
753 .max_fee_per_gas(BigInt::from(1500000120))
754 .max_priority_fee_per_gas(BigInt::from(1500000000))
755 .gas_limit(37442471_u64)
756 .input(hex::decode("383487be000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000660d4d120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000003b6261666b726569656f6f75326d36356276376561786e7767656d7562723675787269696867366474646e6c7a663469616f37686c6e6a6d647372750000000000").unwrap())
757 .build()
758 .unwrap();
759 tx_args.v = BigInt::from_str("1").unwrap();
760 tx_args.r = BigInt::from_str(
761 "84103132941276310528712440865285269631208564772362393569572880532520338257200",
762 )
763 .unwrap();
764 tx_args.s = BigInt::from_str(
765 "7820796778417228639067439047870612492553874254089570360061550763595363987236",
766 )
767 .unwrap();
768 let tx = EthTx::from(tx_args);
769 let sig = tx.signature(calibnet::ETH_CHAIN_ID);
770 assert!(sig.is_ok());
771 assert!(
772 tx.to_verifiable_signature(sig.unwrap().bytes().to_vec(), calibnet::ETH_CHAIN_ID)
773 .is_ok()
774 );
775 assert!(tx.rlp_unsigned_message(calibnet::ETH_CHAIN_ID).is_ok());
776 assert!(tx.get_signed_message(calibnet::ETH_CHAIN_ID).is_ok());
777 let expected_hash = ethereum_types::H256::from_str(
778 "0x9f2e70d5737c6b798eccea14895893fb48091ab3c59d0fe95508dc7efdae2e5f",
779 )
780 .unwrap();
781 assert_eq!(expected_hash, tx.eth_hash().unwrap());
782 }
783
784 #[test]
785 fn test_legacy_eip_155() {
786 let mut tx_args = EthLegacyEip155TxArgsBuilder::default()
788 .chain_id(314159_u64)
789 .nonce(0x4_u64)
790 .to(Some(
791 ethereum_types::H160::from_str("0xd0fb381fc644cdd5d694d35e1afb445527b9244b")
792 .unwrap()
793 .into(),
794 ))
795 .value(BigInt::from(0))
796 .gas_limit(0x19ca81cc_u64)
797 .gas_price(BigInt::from(0x40696))
798 .input(hex::decode("d5b3d76d00000000000000000000000000000000000000000000000045466fa6fdcb80000000000000000000000000000000000000000000000000000000002e90edd0000000000000000000000000000000000000000000000000000000000000015180").unwrap())
799 .build()
800 .unwrap();
801 tx_args.v = BigInt::from_str_radix("99681", 16).unwrap();
802 tx_args.r = BigInt::from_str_radix(
803 "580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796",
804 16,
805 )
806 .unwrap();
807 tx_args.s = BigInt::from_str_radix(
808 "55e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
809 16,
810 )
811 .unwrap();
812 let tx = EthTx::from(tx_args);
813 let sig = tx.signature(calibnet::ETH_CHAIN_ID);
814 assert!(sig.is_ok());
815 assert!(
816 tx.to_verifiable_signature(sig.unwrap().bytes().to_vec(), calibnet::ETH_CHAIN_ID)
817 .is_ok()
818 );
819 assert!(tx.rlp_unsigned_message(calibnet::ETH_CHAIN_ID).is_ok());
820 assert!(tx.get_signed_message(calibnet::ETH_CHAIN_ID).is_ok());
821 let expected_hash = ethereum_types::H256::from_str(
822 "0x3ebc897150feeff6caa1b2e5992e347e8409e9e35fa30f7f5f8fcda3f7c965c7",
823 )
824 .unwrap();
825 assert_eq!(expected_hash, tx.eth_hash().unwrap());
826 }
827
828 #[test]
829 fn test_legacy_homestead() {
830 let mut tx_args = EthLegacyHomesteadTxArgsBuilder::default()
832 .nonce(0x4_u64)
833 .to(Some(
834 ethereum_types::H160::from_str("0xd0fb381fc644cdd5d694d35e1afb445527b9244b")
835 .unwrap()
836 .into(),
837 ))
838 .value(BigInt::from(0))
839 .gas_limit(0x19ca81cc_u64)
840 .gas_price(BigInt::from(0x40696))
841 .input(hex::decode("d5b3d76d00000000000000000000000000000000000000000000000045466fa6fdcb80000000000000000000000000000000000000000000000000000000002e90edd0000000000000000000000000000000000000000000000000000000000000015180").unwrap())
842 .build()
843 .unwrap();
844 tx_args.v = BigInt::from_str_radix("99681", 16).unwrap();
847 tx_args.r = BigInt::from_str_radix(
848 "580b1d36c5a8c8c1c550fb45b0a6ff21aaa517be036385541621961b5d873796",
849 16,
850 )
851 .unwrap();
852 tx_args.s = BigInt::from_str_radix(
853 "55e8447d58d64ebc3038d9882886bbc3b0228c7ac77c71f4e811b97ed3f14b5a",
854 16,
855 )
856 .unwrap();
857 let tx = EthTx::from(tx_args.clone());
858 let expected_hash = ethereum_types::H256::from_str(
859 "0x3ebc897150feeff6caa1b2e5992e347e8409e9e35fa30f7f5f8fcda3f7c965c7",
860 )
861 .unwrap();
862 assert_eq!(expected_hash, tx.eth_hash().unwrap());
863 tx_args.v = BigInt::from_str_radix("1b", 16).unwrap();
865 let tx = EthTx::from(tx_args.clone());
866 let sig = tx.signature(calibnet::ETH_CHAIN_ID);
867 assert!(sig.is_ok());
868 assert!(
869 tx.to_verifiable_signature(sig.unwrap().bytes().to_vec(), calibnet::ETH_CHAIN_ID)
870 .is_ok()
871 );
872 assert!(tx.rlp_unsigned_message(calibnet::ETH_CHAIN_ID).is_ok());
873 assert!(tx.get_signed_message(calibnet::ETH_CHAIN_ID).is_ok());
874 }
875
876 #[quickcheck]
877 fn u64_roundtrip(i: u64) {
878 let bm = format_u64(i);
879 if i == 0 {
880 assert!(bm.is_empty());
881 } else {
882 assert!(!bm.starts_with(&[0]));
884
885 let mut padded = [0u8; 8];
887 let bytes: &[u8] = &bm.slice(..);
888 padded[8 - bytes.len()..].copy_from_slice(bytes);
889 assert_eq!(i, u64::from_be_bytes(padded));
890 }
891 }
892
893 #[quickcheck]
894 fn bigint_roundtrip(bi: num_bigint::BigInt) {
895 match format_bigint(&bi) {
896 Ok(bm) => {
897 if bi.is_zero() {
898 assert!(bm.is_empty());
899 } else {
900 assert!(!bm.starts_with(&[0]));
902
903 let unsigned = num_bigint::BigUint::from_be_bytes(&bm.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}