diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index c8cac3138e1..57105d3d98e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -58,7 +58,6 @@ use crate::state_manager::{ExecutedMessage, ExecutedTipset, StateManager, Tipset use crate::utils::cache::SizeTrackingCache; use crate::utils::db::BlockstoreExt as _; use crate::utils::encoding::from_slice_with_fallback; -use crate::utils::get_size::big_int_heap_size_helper; use crate::utils::misc::env::env_or_default; use crate::utils::multihash::prelude::*; use ahash::{HashMap, HashSet}; @@ -69,7 +68,7 @@ use fvm_ipld_encoding::{CBOR, DAG_CBOR, IPLD_RAW, RawBytes}; use get_size2::GetSize; use ipld_core::ipld::Ipld; use nonzero_ext::nonzero; -use num::{BigInt, Zero as _}; +use num::BigInt; use nunny::Vec as NonEmpty; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -114,8 +113,6 @@ const EMPTY_ROOT: &str = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc00162 /// The address used in messages to actors that have since been deleted. const REVERTED_ETH_ADDRESS: &str = "0xff0000000000000000000000ffffffffffffffff"; -// TODO(forest): https://github.com/ChainSafe/forest/issues/4436 -// use ethereum_types::U256 or use lotus_json::big_int #[derive( Eq, Hash, @@ -125,20 +122,39 @@ const REVERTED_ETH_ADDRESS: &str = "0xff0000000000000000000000ffffffffffffffff"; Serialize, Default, Clone, + Copy, JsonSchema, + GetSize, derive_more::From, derive_more::Into, + derive_more::Deref, )] pub struct EthBigInt( - #[serde(with = "crate::lotus_json::hexify")] + // `ethereum_types::U256` serializes as a `0x`-prefixed, leading-zero-trimmed hex string, + // which matches the Ethereum JSON-RPC wire format used by Lotus. #[schemars(with = "String")] - pub BigInt, + #[get_size(ignore)] + ethereum_types::U256, ); lotus_json_with_self!(EthBigInt); -impl GetSize for EthBigInt { - fn get_heap_size_with_tracker(&self, tracker: T) -> (usize, T) { - (big_int_heap_size_helper(&self.0), tracker) +impl From for EthBigInt { + fn from(value: BigInt) -> Self { + (&value).into() + } +} + +impl From<&BigInt> for EthBigInt { + fn from(value: &BigInt) -> Self { + // Eth values are non-negative, so the sign is dropped. + let (_sign, bytes) = value.to_bytes_be(); + Self(ethereum_types::U256::from_big_endian(&bytes)) + } +} + +impl From for EthBigInt { + fn from(value: u64) -> Self { + Self(value.into()) } } @@ -150,7 +166,20 @@ impl From for EthBigInt { impl From<&TokenAmount> for EthBigInt { fn from(amount: &TokenAmount) -> Self { - Self(amount.atto().to_owned()) + amount.atto().into() + } +} + +impl From for BigInt { + fn from(value: EthBigInt) -> Self { + // Eth values are non-negative, so the sign is always positive. + BigInt::from_bytes_be(num_bigint::Sign::Plus, &value.to_big_endian()) + } +} + +impl From for TokenAmount { + fn from(value: EthBigInt) -> Self { + TokenAmount::from_atto(value) } } @@ -860,7 +889,7 @@ impl RpcMethod<0> for EthBaseFee { _: &http::Extensions, ) -> Result { let base_fee = Self::get_base_fee(&ctx, &ctx.chain_store().heaviest_tipset())?; - Ok(EthBigInt(base_fee.atto().clone())) + Ok(base_fee.atto().into()) } } @@ -889,7 +918,7 @@ impl RpcMethod<1> for BaseFeeByHeight { ResolveNullTipset::TakeOlder, )?; let base_fee = EthBaseFee::get_base_fee(&ctx, &ts)?; - Ok(EthBigInt(base_fee.atto().clone())) + Ok(base_fee.atto().into()) } } @@ -979,7 +1008,7 @@ impl RpcMethod<0> for EthGasPrice { .await .map(|gas_premium| gas_premium.atto().to_owned()) .unwrap_or_default(); - Ok(EthBigInt(base_fee + tip)) + Ok((base_fee + tip).into()) } } @@ -1015,7 +1044,7 @@ async fn eth_get_balance(ctx: &Ctx, address: &EthAddress, ts: &Tipset) -> Result let TipsetState { state_root, .. } = ctx.state_manager.load_tipset_state(ts).await?; let state_tree = ctx.state_manager.get_state_tree(&state_root)?; match state_tree.get_actor(&fil_addr)? { - Some(actor) => Ok(EthBigInt(actor.balance.atto().clone())), + Some(actor) => Ok(actor.balance.atto().into()), None => Ok(EthBigInt::default()), // Balance is 0 if the actor doesn't exist } } @@ -1343,8 +1372,8 @@ async fn new_eth_tx_receipt( msg_receipt.gas_used(), tx.gas.into(), &tipset.block_headers().first().parent_base_fee, - &gas_fee_cap.0.into(), - &gas_premium.0.into(), + &gas_fee_cap.into(), + &gas_premium.into(), ); let total_spent: BigInt = gas_outputs.total_spent().into(); @@ -2105,7 +2134,7 @@ fn calculate_rewards_and_gas_used( let gas_used_total = tx_gas_rewards.iter().map(|i| i.gas_used).sum(); let mut rewards = reward_percentiles .iter() - .map(|_| EthBigInt(MIN_GAS_PREMIUM.into())) + .map(|_| EthBigInt::from(MIN_GAS_PREMIUM)) .collect_vec(); if !tx_gas_rewards.is_empty() { tx_gas_rewards.sort_by(|a, b| a.premium.cmp(&b.premium)); @@ -2381,8 +2410,8 @@ impl RpcMethod<0> for EthMaxPriorityFeePerGas { _: &http::Extensions, ) -> Result { match gas::estimate_gas_premium(&ctx, 0, &ApiTipsetKey(None)).await { - Ok(gas_premium) => Ok(EthBigInt(gas_premium.atto().clone())), - Err(_) => Ok(EthBigInt(num_bigint::BigInt::zero())), + Ok(gas_premium) => Ok(gas_premium.atto().into()), + Err(_) => Ok(EthBigInt::default()), } } } @@ -4102,11 +4131,11 @@ mod test { #[quickcheck] fn gas_price_result_serde_roundtrip(i: u128) { - let r = EthBigInt(i.into()); + let r = EthBigInt(ethereum_types::U256::from(i)); let encoded = serde_json::to_string(&r).unwrap(); assert_eq!(encoded, format!("\"{i:#x}\"")); let decoded: EthBigInt = serde_json::from_str(&encoded).unwrap(); - assert_eq!(r.0, decoded.0); + assert_eq!(r, decoded); } /// `transactionPosition` must be 0-indexed and system-actor messages must diff --git a/src/rpc/methods/eth/trace/geth.rs b/src/rpc/methods/eth/trace/geth.rs index 01b0991f65b..ffcfd1187f0 100644 --- a/src/rpc/methods/eth/trace/geth.rs +++ b/src/rpc/methods/eth/trace/geth.rs @@ -138,7 +138,7 @@ fn build_account_snapshot_from_entries( }; Ok(Some(super::types::AccountState { - balance: Some(EthBigInt(actor.balance.atto().clone())), + balance: Some(EthBigInt::from(actor.balance.atto())), code, nonce, storage, @@ -267,7 +267,6 @@ mod tests { use super::super::types::{PreStateConfig, PreStateFrame}; use super::*; use crate::rpc::eth::{EthBigInt, EthUint64}; - use num::BigInt; #[test] fn test_build_prestate_frame_default_mode_empty() { @@ -318,7 +317,7 @@ mod tests { assert_eq!(mode.0.len(), 1); let snap = mode.0.get(&actor_addr).unwrap(); // Default mode returns pre-state - assert_eq!(snap.balance, Some(EthBigInt(BigInt::from(1000)))); + assert_eq!(snap.balance, Some(EthBigInt::from(1000))); assert_eq!(snap.nonce, Some(EthUint64(5))); } _ => panic!("Expected Default mode"), @@ -354,12 +353,12 @@ mod tests { // Pre should contain the original state let pre_snap = diff.pre.get(&addr).unwrap(); - assert_eq!(pre_snap.balance, Some(EthBigInt(BigInt::from(1000)))); + assert_eq!(pre_snap.balance, Some(EthBigInt::from(1000))); assert_eq!(pre_snap.nonce, Some(EthUint64(5))); // Post should only contain changed fields let post_snap = diff.post.get(&addr).unwrap(); - assert_eq!(post_snap.balance, Some(EthBigInt(BigInt::from(2000)))); + assert_eq!(post_snap.balance, Some(EthBigInt::from(2000))); assert_eq!(post_snap.nonce, Some(EthUint64(6))); } _ => panic!("Expected Diff mode"), @@ -427,7 +426,7 @@ mod tests { // Created accounts should only appear in post assert!(!diff.pre.contains_key(&addr)); let post_snap = diff.post.get(&addr).unwrap(); - assert_eq!(post_snap.balance, Some(EthBigInt(BigInt::from(5000)))); + assert_eq!(post_snap.balance, Some(EthBigInt::from(5000))); assert_eq!(post_snap.nonce, Some(EthUint64(0))); } _ => panic!("Expected Diff mode"), @@ -461,7 +460,7 @@ mod tests { let addr = create_masked_id_eth_address(actor_id); // Deleted accounts should only appear in pre let pre_snap = diff.pre.get(&addr).unwrap(); - assert_eq!(pre_snap.balance, Some(EthBigInt(BigInt::from(3000)))); + assert_eq!(pre_snap.balance, Some(EthBigInt::from(3000))); assert_eq!(pre_snap.nonce, Some(EthUint64(10))); assert!(!diff.post.contains_key(&addr)); } @@ -499,10 +498,10 @@ mod tests { let pre_snap = diff.pre.get(&addr).unwrap(); let post_snap = diff.post.get(&addr).unwrap(); // Balance/nonce reflect actual values, storage skipped - assert_eq!(pre_snap.balance, Some(EthBigInt(BigInt::from(1000)))); + assert_eq!(pre_snap.balance, Some(EthBigInt::from(1000))); assert_eq!(pre_snap.nonce, Some(EthUint64(5))); assert!(pre_snap.storage.is_empty()); - assert_eq!(post_snap.balance, Some(EthBigInt(BigInt::from(2000)))); + assert_eq!(post_snap.balance, Some(EthBigInt::from(2000))); assert_eq!(post_snap.nonce, Some(EthUint64(6))); assert!(post_snap.storage.is_empty()); } @@ -538,7 +537,7 @@ mod tests { PreStateFrame::Default(mode) => { assert_eq!(mode.0.len(), 1); let snap = mode.0.get(&actor_addr).unwrap(); - assert_eq!(snap.balance, Some(EthBigInt(BigInt::from(1000)))); + assert_eq!(snap.balance, Some(EthBigInt::from(1000))); assert_eq!(snap.nonce, Some(EthUint64(5))); assert!(snap.storage.is_empty()); } diff --git a/src/rpc/methods/eth/trace/state_diff.rs b/src/rpc/methods/eth/trace/state_diff.rs index 3eefb669e5b..fdaec7f2380 100644 --- a/src/rpc/methods/eth/trace/state_diff.rs +++ b/src/rpc/methods/eth/trace/state_diff.rs @@ -83,8 +83,8 @@ fn build_account_diff( let mut diff = AccountDiff::default(); // Compare balance - let pre_balance = pre_actor.map(|a| EthBigInt(a.balance.atto().clone())); - let post_balance = post_actor.map(|a| EthBigInt(a.balance.atto().clone())); + let pre_balance = pre_actor.map(|a| EthBigInt::from(a.balance.atto())); + let post_balance = post_actor.map(|a| EthBigInt::from(a.balance.atto())); diff.balance = Delta::from_comparison(pre_balance, post_balance); // Compare nonce @@ -277,7 +277,6 @@ mod tests { use crate::rpc::eth::types::EthBytes; use crate::shim::address::Address as FilecoinAddress; use crate::shim::state_tree::StateTreeVersion; - use num::BigInt; #[test] fn test_build_state_diff_empty_touched_addresses() { @@ -319,8 +318,8 @@ mod tests { let diff = state_diff.0.get(ð_addr).unwrap(); match &diff.balance { Delta::Changed(change) => { - assert_eq!(change.from.0, BigInt::from(1000)); - assert_eq!(change.to.0, BigInt::from(2000)); + assert_eq!(change.from, EthBigInt::from(1000)); + assert_eq!(change.to, EthBigInt::from(2000)); } _ => panic!("Expected Delta::Changed for balance"), } @@ -343,8 +342,8 @@ mod tests { let diff = state_diff.0.get(ð_addr).unwrap(); match &diff.balance { Delta::Changed(change) => { - assert_eq!(change.from.0, BigInt::from(5000)); - assert_eq!(change.to.0, BigInt::from(3000)); + assert_eq!(change.from, EthBigInt::from(5000)); + assert_eq!(change.to, EthBigInt::from(3000)); } _ => panic!("Expected Delta::Changed for balance"), } @@ -391,8 +390,8 @@ mod tests { let diff = state_diff.0.get(ð_addr).unwrap(); match &diff.balance { Delta::Changed(change) => { - assert_eq!(change.from.0, BigInt::from(10000)); - assert_eq!(change.to.0, BigInt::from(9000)); + assert_eq!(change.from, EthBigInt::from(10000)); + assert_eq!(change.to, EthBigInt::from(9000)); } _ => panic!("Expected Delta::Changed for balance"), } @@ -420,7 +419,7 @@ mod tests { let diff = state_diff.0.get(ð_addr).unwrap(); match &diff.balance { Delta::Added(balance) => { - assert_eq!(balance.0, BigInt::from(5000)); + assert_eq!(balance, &EthBigInt::from(5000)); } _ => panic!("Expected Delta::Added for balance"), } @@ -447,7 +446,7 @@ mod tests { let diff = state_diff.0.get(ð_addr).unwrap(); match &diff.balance { Delta::Removed(balance) => { - assert_eq!(balance.0, BigInt::from(3000)); + assert_eq!(balance, &EthBigInt::from(3000)); } _ => panic!("Expected Delta::Removed for balance"), } @@ -546,8 +545,8 @@ mod tests { pre: Some((1000, 5, Some(bytecode1))), post: Some((2000, 5, Some(bytecode1))), expected_balance: Delta::Changed(ChangedType { - from: EthBigInt(BigInt::from(1000)), - to: EthBigInt(BigInt::from(2000)), + from: EthBigInt::from(1000), + to: EthBigInt::from(2000), }), expected_nonce: Delta::Unchanged, expected_code: Delta::Unchanged, @@ -579,8 +578,8 @@ mod tests { pre: Some((1000, 5, Some(bytecode1))), post: Some((2000, 6, Some(bytecode1))), expected_balance: Delta::Changed(ChangedType { - from: EthBigInt(BigInt::from(1000)), - to: EthBigInt(BigInt::from(2000)), + from: EthBigInt::from(1000), + to: EthBigInt::from(2000), }), expected_nonce: Delta::Changed(ChangedType { from: EthUint64(5), @@ -592,7 +591,7 @@ mod tests { name: "Creation", pre: None, post: Some((5000, 0, Some(bytecode1))), - expected_balance: Delta::Added(EthBigInt(BigInt::from(5000))), + expected_balance: Delta::Added(EthBigInt::from(5000)), expected_nonce: Delta::Added(EthUint64(0)), expected_code: Delta::Added(EthBytes(bytecode1.to_vec())), }, @@ -600,7 +599,7 @@ mod tests { name: "Deletion", pre: Some((3000, 10, Some(bytecode1))), post: None, - expected_balance: Delta::Removed(EthBigInt(BigInt::from(3000))), + expected_balance: Delta::Removed(EthBigInt::from(3000)), expected_nonce: Delta::Removed(EthUint64(10)), expected_code: Delta::Removed(EthBytes(bytecode1.to_vec())), }, diff --git a/src/rpc/methods/eth/trace/types.rs b/src/rpc/methods/eth/trace/types.rs index 502d0dcf1d5..a268c640ce5 100644 --- a/src/rpc/methods/eth/trace/types.rs +++ b/src/rpc/methods/eth/trace/types.rs @@ -832,7 +832,6 @@ lotus_json_with_self!(StateDiff); #[cfg(test)] mod tests { use super::*; - use num_bigint::BigInt; use rstest::rstest; #[test] @@ -916,7 +915,7 @@ mod tests { assert!( !AccountState { - balance: Some(EthBigInt(BigInt::from(100))), + balance: Some(EthBigInt::from(100)), ..Default::default() } .is_empty() @@ -949,7 +948,7 @@ mod tests { #[test] fn test_account_state_retain_changed_strips_identical_fields() { let pre = AccountState { - balance: Some(EthBigInt(num::BigInt::from(1000))), + balance: Some(EthBigInt::from(1000)), nonce: Some(EthUint64(5)), code: Some(EthBytes(vec![0x60])), storage: BTreeMap::new(), @@ -964,24 +963,21 @@ mod tests { #[test] fn test_account_state_retain_changed_keeps_different_fields() { let pre = AccountState { - balance: Some(EthBigInt(num::BigInt::from(1000))), + balance: Some(EthBigInt::from(1000)), nonce: Some(EthUint64(5)), code: Some(EthBytes(vec![0x60])), storage: BTreeMap::new(), }; let mut post = AccountState { - balance: Some(EthBigInt(BigInt::from(2000))), // changed - nonce: Some(EthUint64(5)), // same - code: Some(EthBytes(vec![0x60, 0x80])), // changed + balance: Some(EthBigInt::from(2000)), // changed + nonce: Some(EthUint64(5)), // same + code: Some(EthBytes(vec![0x60, 0x80])), // changed storage: BTreeMap::new(), }; post.retain_changed(&pre); - assert!( - post.balance - .is_some_and(|b| b.eq(&EthBigInt(BigInt::from(2000)))) - ); + assert!(post.balance.is_some_and(|b| b.eq(&EthBigInt::from(2000)))); assert!(post.nonce.is_none()); // stripped assert!(post.code.is_some_and(|b| b.eq(&EthBytes(vec![0x60, 0x80])))); } @@ -1031,7 +1027,7 @@ mod tests { assert!(AccountDiff::default().is_unchanged()); assert!( !AccountDiff { - balance: Delta::Added(EthBigInt(num::BigInt::from(1))), + balance: Delta::Added(EthBigInt::from(1)), ..Default::default() } .is_unchanged() @@ -1062,7 +1058,7 @@ mod tests { assert!(sd.0.is_empty()); // Changed diff is inserted let changed = AccountDiff { - balance: Delta::Added(EthBigInt(num::BigInt::from(100))), + balance: Delta::Added(EthBigInt::from(100)), ..Default::default() }; sd.insert_if_changed(addr, changed); @@ -1152,7 +1148,7 @@ mod tests { from, to: Some(to), gas: EthUint64(21000), - value: EthBigInt(num::BigInt::from(1000)), + value: EthBigInt::from(1000), input: EthBytes(vec![0x01, 0x02]), }), result: TraceResult::Call(EthCallTraceResult { @@ -1171,7 +1167,7 @@ mod tests { assert_eq!(frame.gas_used.0, 5000); assert!(frame.error.is_none()); assert!(frame.revert_reason.is_none()); - assert_eq!(frame.value, Some(EthBigInt(num::BigInt::from(1000)))); + assert_eq!(frame.value, Some(EthBigInt::from(1000))); } #[test] @@ -1183,7 +1179,7 @@ mod tests { from: EthAddress::default(), to: Some(EthAddress::from_actor_id(100)), gas: EthUint64(21000), - value: EthBigInt(num::BigInt::from(0)), + value: EthBigInt::from(0), input: EthBytes(vec![]), }), result: TraceResult::Call(EthCallTraceResult { @@ -1214,7 +1210,7 @@ mod tests { from: EthAddress::default(), to: Some(EthAddress::from_actor_id(100)), gas: EthUint64(21000), - value: EthBigInt(num::BigInt::from(0)), + value: EthBigInt::from(0), input: EthBytes(vec![]), }), result: TraceResult::Call(EthCallTraceResult { @@ -1244,7 +1240,7 @@ mod tests { action: TraceAction::Create(EthCreateTraceAction { from, gas: EthUint64(100000), - value: EthBigInt(num::BigInt::from(0)), + value: EthBigInt::from(0), init: init_code.clone(), }), result: TraceResult::Create(EthCreateTraceResult { @@ -1262,7 +1258,7 @@ mod tests { assert_eq!(frame.to, Some(created)); assert_eq!(frame.gas.0, 100000); assert_eq!(frame.gas_used.0, 50000); - assert_eq!(frame.value, Some(EthBigInt(num::BigInt::from(0)))); + assert_eq!(frame.value, Some(EthBigInt::from(0))); assert_eq!(frame.input.0, init_code.0); // initcode goes to input assert_eq!(frame.output, Some(EthBytes(vec![0xFE]))); // deployed code assert!(frame.error.is_none()); @@ -1278,7 +1274,7 @@ mod tests { from: EthAddress::default(), to: None, gas: EthUint64(0), - value: EthBigInt(num::BigInt::from(0)), + value: EthBigInt::from(0), input: EthBytes(vec![]), }), result: TraceResult::Create(EthCreateTraceResult { @@ -1302,7 +1298,7 @@ mod tests { from, to, gas: EthUint64(21000), - value: EthBigInt(num::BigInt::from(0)), + value: EthBigInt::from(0), input: EthBytes(vec![]), }), result: TraceResult::Call(EthCallTraceResult { @@ -1321,7 +1317,7 @@ mod tests { action: TraceAction::Create(EthCreateTraceAction { from, gas: EthUint64(100000), - value: EthBigInt(num::BigInt::from(0)), + value: EthBigInt::from(0), init: EthBytes(vec![0x60, 0x80]), }), result: TraceResult::Create(EthCreateTraceResult { @@ -1433,7 +1429,7 @@ mod tests { action: TraceAction::Create(EthCreateTraceAction { from: EthAddress::default(), gas: EthUint64(100000), - value: EthBigInt(num::BigInt::from(0)), + value: EthBigInt::from(0), init: EthBytes(vec![]), }), result: TraceResult::Call(EthCallTraceResult { diff --git a/src/rpc/methods/eth/types.rs b/src/rpc/methods/eth/types.rs index df380c1d9c9..fd771a53a42 100644 --- a/src/rpc/methods/eth/types.rs +++ b/src/rpc/methods/eth/types.rs @@ -406,7 +406,7 @@ impl TryFrom for Message { Ok(Message { from, to, - value: tx.value.unwrap_or_default().0.into(), + value: tx.value.unwrap_or_default().into(), method_num, params, gas_limit: BLOCK_GAS_LIMIT, diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index ae0de69aeb5..c8c05f74301 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -1415,7 +1415,7 @@ fn eth_tests(server_mode: ServerMode) -> anyhow::Result> { // There is randomness in the result of this API, but at least check that the results are non-zero. tests.push(RpcTest::validate( EthGasPrice::request_with_alias((), use_alias)?, - |forest, lotus| forest.0.is_positive() && lotus.0.is_positive(), + |forest, lotus| !forest.is_zero() && !lotus.is_zero(), )); tests.push(RpcTest::basic(EthSyncing::request_with_alias( (),