diff --git a/CHANGELOG.md b/CHANGELOG.md index 48719cf1305e..280c9b7b187d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ - [#6387](https://github.com/ChainSafe/forest/pull/6387) Implemented `Filecoin.EthGetTransactionCount` for API v2. +- [#6403](https://github.com/ChainSafe/forest/pull/6403) Implemented `Filecoin.EthGetBalance` for API v2. + ### Changed - [#6368](https://github.com/ChainSafe/forest/pull/6368): Migrated build and development tooling from Makefile to `mise`. Contributors should install `mise` and use `mise run` commands instead of `make` commands. diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 84e178994f93..7e75961fd009 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -864,6 +864,8 @@ impl RpcMethod<2> for EthGetBalance { const PARAM_NAMES: [&'static str; 2] = ["address", "blockParam"]; const API_PATHS: BitFlags = ApiPaths::all(); const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = + Some("Returns the balance of an Ethereum address at the specified block state"); type Params = (EthAddress, BlockNumberOrHash); type Ok = EthBigInt; @@ -872,18 +874,51 @@ impl RpcMethod<2> for EthGetBalance { ctx: Ctx, (address, block_param): Self::Params, ) -> Result { - let fil_addr = address.to_filecoin_address()?; let ts = tipset_by_block_number_or_hash( ctx.chain_store(), block_param, ResolveNullTipset::TakeOlder, )?; - let state = StateTree::new_from_root(ctx.store_owned(), ts.parent_state())?; - let actor = state.get_required_actor(&fil_addr)?; - Ok(EthBigInt(actor.balance.atto().clone())) + let balance = eth_get_balance(&ctx, &address, &ts)?; + Ok(balance) } } +pub enum EthGetBalanceV2 {} +impl RpcMethod<2> for EthGetBalanceV2 { + const NAME: &'static str = "Filecoin.EthGetBalance"; + const NAME_ALIAS: Option<&'static str> = Some("eth_getBalance"); + const PARAM_NAMES: [&'static str; 2] = ["address", "blockParam"]; + const API_PATHS: BitFlags = make_bitflags!(ApiPaths::V2); + const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = + Some("Returns the balance of an Ethereum address at the specified block state"); + + type Params = (EthAddress, ExtBlockNumberOrHash); + type Ok = EthBigInt; + + async fn handle( + ctx: Ctx, + (address, block_param): Self::Params, + ) -> Result { + let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder) + .await?; + let balance = eth_get_balance(&ctx, &address, &ts)?; + Ok(balance) + } +} + +fn eth_get_balance( + ctx: &Ctx, + address: &EthAddress, + ts: &Tipset, +) -> Result { + let fil_addr = address.to_filecoin_address()?; + let state = StateTree::new_from_root(ctx.store_owned(), ts.parent_state())?; + let actor = state.get_required_actor(&fil_addr)?; + Ok(EthBigInt(actor.balance.atto().clone())) +} + fn get_tipset_from_hash( chain_store: &ChainStore, block_hash: &EthHash, diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 77aa60d5d753..6234a80df68c 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -110,6 +110,7 @@ macro_rules! for_each_rpc_method { $callback!($crate::rpc::eth::EthFeeHistoryV2); $callback!($crate::rpc::eth::EthGasPrice); $callback!($crate::rpc::eth::EthGetBalance); + $callback!($crate::rpc::eth::EthGetBalanceV2); $callback!($crate::rpc::eth::EthGetBlockByHash); $callback!($crate::rpc::eth::EthGetBlockByNumber); $callback!($crate::rpc::eth::EthGetBlockReceipts); diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index ecbe81cf8526..f5941de0b523 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -1642,6 +1642,77 @@ fn eth_tests_with_tipset(store: &Arc, shared_tipset: &Tipset )) .unwrap(), ), + RpcTest::identity( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff38c072f286e3b20b3954ca9f99c05fbecc64aa").unwrap(), + ExtBlockNumberOrHash::from_block_number(shared_tipset.epoch()), + )) + .unwrap(), + ), + RpcTest::identity( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_block_number(shared_tipset.epoch()), + )) + .unwrap(), + ), + RpcTest::identity( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_block_number_object(shared_tipset.epoch()), + )) + .unwrap(), + ), + RpcTest::identity( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_block_hash_object(block_hash.clone(), false), + )) + .unwrap(), + ), + RpcTest::identity( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_block_hash_object(block_hash.clone(), true), + )) + .unwrap(), + ), + RpcTest::identity( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Earliest), + )) + .unwrap(), + ) + .policy_on_rejected(PolicyOnRejected::PassWithQuasiIdenticalError), + RpcTest::basic( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Pending), + )) + .unwrap(), + ), + RpcTest::basic( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Latest), + )) + .unwrap(), + ), + RpcTest::basic( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Safe), + )) + .unwrap(), + ), + RpcTest::basic( + EthGetBalanceV2::request(( + EthAddress::from_str("0xff000000000000000000000000000000000003ec").unwrap(), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Finalized), + )) + .unwrap(), + ), RpcTest::identity( EthGetBlockByNumber::request(( ExtBlockNumberOrHash::from_block_number(shared_tipset.epoch()), diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 2e645609c48f..a49df7afa940 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -59,6 +59,7 @@ filecoin_ethfeehistory_1737446676883828.rpcsnap.json.zst filecoin_ethfeehistory_v2_1767605175056660.rpcsnap.json.zst filecoin_ethgasprice_1758725940980141.rpcsnap.json.zst filecoin_ethgetbalance_1740048634848277.rpcsnap.json.zst +filecoin_ethgetbalance_v2_1768188109932986.rpcsnap.json.zst filecoin_ethgetblockbyhash_1740132537807408.rpcsnap.json.zst filecoin_ethgetblockbynumber_1737446676696328.rpcsnap.json.zst filecoin_ethgetblockreceipts_1740132537907751.rpcsnap.json.zst