From de284a06bea76c9aaccbd0010df87d16c6a78e83 Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 28 Jan 2026 18:33:45 +0530 Subject: [PATCH 1/6] Impl EthGetCode V2 --- src/rpc/methods/eth.rs | 114 ++++++++++++------ src/rpc/mod.rs | 1 + .../subcommands/api_cmd/api_compare_tests.rs | 22 ++++ 3 files changed, 99 insertions(+), 38 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 01ab8ba1909..4e940ff24c7 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2356,6 +2356,9 @@ impl RpcMethod<2> for EthGetCode { const PARAM_NAMES: [&'static str; 2] = ["ethAddress", "blockNumberOrHash"]; const API_PATHS: BitFlags = ApiPaths::all(); const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = Some( + "Retrieves the contract code at a specific address and block state, identified by its number, hash, or a special tag.", + ); type Params = (EthAddress, BlockNumberOrHash); type Ok = EthBytes; @@ -2369,50 +2372,85 @@ impl RpcMethod<2> for EthGetCode { block_param, ResolveNullTipset::TakeOlder, )?; - let to_address = FilecoinAddress::try_from(ð_address)?; - let actor = ctx - .state_manager - .get_required_actor(&to_address, *ts.parent_state())?; - // Not a contract. We could try to distinguish between accounts and "native" contracts here, - // but it's not worth it. - if !is_evm_actor(&actor.code) { - return Ok(Default::default()); - } + eth_get_code(&ctx, &ts, ð_address).await + } +} - let message = Message { - from: FilecoinAddress::SYSTEM_ACTOR, - to: to_address, - method_num: METHOD_GET_BYTE_CODE, - gas_limit: BLOCK_GAS_LIMIT, - ..Default::default() - }; +pub enum EthGetCodeV2 {} +impl RpcMethod<2> for EthGetCodeV2 { + const NAME: &'static str = "Filecoin.EthGetCode"; + const NAME_ALIAS: Option<&'static str> = Some("eth_getCode"); + const PARAM_NAMES: [&'static str; 2] = ["ethAddress", "blockNumberOrHash"]; + const API_PATHS: BitFlags = make_bitflags!(ApiPaths::V2); + const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = Some( + "Retrieves the contract code at a specific address and block state, identified by its number, hash, or a special tag.", + ); - let (state, _) = ctx.state_manager.tipset_state(&ts).await?; - let api_invoc_result = 'invoc: { - for ts in ts.chain(ctx.store()) { - match ctx.state_manager.call_on_state(state, &message, Some(ts)) { - Ok(res) => { - break 'invoc res; - } - Err(e) => tracing::warn!(%e), + type Params = (EthAddress, ExtBlockNumberOrHash); + type Ok = EthBytes; + + async fn handle( + ctx: Ctx, + (eth_address, block_param): Self::Params, + ) -> Result { + let ts = tipset_by_block_number_or_hash_v2(&ctx, block_param, ResolveNullTipset::TakeOlder) + .await?; + eth_get_code(&ctx, &ts, ð_address).await + } +} + +async fn eth_get_code( + ctx: &Ctx, + ts: &Tipset, + eth_address: &EthAddress, +) -> Result +where + DB: Blockstore + Send + Sync + 'static, +{ + let to_address = FilecoinAddress::try_from(eth_address)?; + let actor = ctx + .state_manager + .get_required_actor(&to_address, *ts.parent_state())?; + // Not a contract. We could try to distinguish between accounts and "native" contracts here, + // but it's not worth it. + if !is_evm_actor(&actor.code) { + return Ok(Default::default()); + } + + let message = Message { + from: FilecoinAddress::SYSTEM_ACTOR, + to: to_address, + method_num: METHOD_GET_BYTE_CODE, + gas_limit: BLOCK_GAS_LIMIT, + ..Default::default() + }; + + let (state, _) = ctx.state_manager.tipset_state(ts).await?; + let api_invoc_result = 'invoc: { + for ts in ts.clone().chain(ctx.store()) { + match ctx.state_manager.call_on_state(state, &message, Some(ts)) { + Ok(res) => { + break 'invoc res; } + Err(e) => tracing::warn!(%e), } - return Err(anyhow::anyhow!("Call failed").into()); - }; - let Some(msg_rct) = api_invoc_result.msg_rct else { - return Err(anyhow::anyhow!("no message receipt").into()); - }; - if !api_invoc_result.error.is_empty() { - return Err(anyhow::anyhow!("GetBytecode failed: {}", api_invoc_result.error).into()); } + return Err(anyhow::anyhow!("Call failed").into()); + }; + let Some(msg_rct) = api_invoc_result.msg_rct else { + return Err(anyhow::anyhow!("no message receipt").into()); + }; + if !api_invoc_result.error.is_empty() { + return Err(anyhow::anyhow!("GetBytecode failed: {}", api_invoc_result.error).into()); + } - let get_bytecode_return: GetBytecodeReturn = - fvm_ipld_encoding::from_slice(msg_rct.return_data().as_slice())?; - if let Some(cid) = get_bytecode_return.0 { - Ok(EthBytes(ctx.store().get_required(&cid)?)) - } else { - Ok(Default::default()) - } + let get_bytecode_return: GetBytecodeReturn = + fvm_ipld_encoding::from_slice(msg_rct.return_data().as_slice())?; + if let Some(cid) = get_bytecode_return.0 { + Ok(EthBytes(ctx.store().get_required(&cid)?)) + } else { + Ok(Default::default()) } } diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index e4348967a92..15d464c368c 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -121,6 +121,7 @@ macro_rules! for_each_rpc_method { $callback!($crate::rpc::eth::EthGetBlockTransactionCountByNumber); $callback!($crate::rpc::eth::EthGetBlockTransactionCountByNumberV2); $callback!($crate::rpc::eth::EthGetCode); + $callback!($crate::rpc::eth::EthGetCodeV2); $callback!($crate::rpc::eth::EthGetLogs); $callback!($crate::rpc::eth::EthGetFilterLogs); $callback!($crate::rpc::eth::EthGetFilterChanges); diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index 489d8f08534..b751df999ce 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -2128,6 +2128,28 @@ fn eth_tests_with_tipset(store: &Arc, shared_tipset: &Tipset )) .unwrap(), ), + RpcTest::identity( + EthGetCodeV2::request(( + // https://filfox.info/en/address/f410fpoidg73f7krlfohnla52dotowde5p2sejxnd4mq + EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(), + ExtBlockNumberOrHash::from_block_number(shared_tipset.epoch()), + )) + .unwrap(), + ), + RpcTest::basic( + EthGetCodeV2::request(( + EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Safe), + )) + .unwrap(), + ), + RpcTest::basic( + EthGetCodeV2::request(( + EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Finalized), + )) + .unwrap(), + ), RpcTest::identity( EthGetTransactionByBlockNumberAndIndex::request(( BlockNumberOrPredefined::BlockNumber(shared_tipset.epoch().into()), From 6025bab1ee84268f8c7107a91ddbd9044fa8e54c Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 28 Jan 2026 18:47:37 +0530 Subject: [PATCH 2/6] add test snapshots --- src/tool/subcommands/api_cmd/test_snapshots.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index c20a4cbd303..3f36be131c1 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -73,6 +73,9 @@ filecoin_ethgetblocktransactioncountbynumber_v2_1769099953141937.rpcsnap.json.zs filecoin_ethgetcode_1765803672602510.rpcsnap.json.zst # latest filecoin_ethgetcode_1765803672604518.rpcsnap.json.zst # concrete filecoin_ethgetcode_1765803672655291.rpcsnap.json.zst # pending +filecoin_ethgetcode_v2_1769605928230413.rpcsnap.json.zst +filecoin_ethgetcode_v2_finalized_1769605928335700.rpcsnap.json.zst +filecoin_ethgetcode_v2_safe_1769605928230488.rpcsnap.json.zst filecoin_ethgetlogs_1759922913569082.rpcsnap.json.zst filecoin_ethgetmessagecidbytransactionhash_1737446676697418.rpcsnap.json.zst filecoin_ethgetstorageat_1765803742043605.rpcsnap.json.zst # latest From 2dcb083186397e6238ac78323328cd574beee814 Mon Sep 17 00:00:00 2001 From: Shashank Date: Wed, 28 Jan 2026 18:59:41 +0530 Subject: [PATCH 3/6] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 449f5e38f1f..71ba484dc07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ - [#6469](https://github.com/ChainSafe/forest/pull/6469): Implemented `Filecoin.EthGetTransactionByBlockNumberAndIndex` for API v2. +- [#6490](https://github.com/ChainSafe/forest/pull/6490): Implemented `Filecoin.EthGetCode` for API v2. + ### Changed - [#6471](https://github.com/ChainSafe/forest/pull/6471): Moved `forest-tool state` subcommand to `forest-dev`. From 0c6ce3b53c91dea6e64b265b25b5004571945e24 Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 29 Jan 2026 00:18:32 +0530 Subject: [PATCH 4/6] lint fix --- src/rpc/methods/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index bca36203073..d1aeb19039e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2433,7 +2433,7 @@ where let (state, _) = ctx .state_manager - .tipset_state(&ts, StateLookupPolicy::Enabled) + .tipset_state(ts, StateLookupPolicy::Enabled) .await?; let api_invoc_result = 'invoc: { for ts in ts.clone().chain(ctx.store()) { From 1875a01327c65332b067d8dac861e7b5d1b058db Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 29 Jan 2026 03:15:48 +0530 Subject: [PATCH 5/6] fix --- src/rpc/methods/eth.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index ca8f88a5778..05193efd185 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2484,8 +2484,13 @@ where let Some(msg_rct) = api_invoc_result.msg_rct else { return Err(anyhow::anyhow!("no message receipt").into()); }; - if !api_invoc_result.error.is_empty() { - return Err(anyhow::anyhow!("GetBytecode failed: {}", api_invoc_result.error).into()); + if !msg_rct.exit_code().is_success() || !api_invoc_result.error.is_empty() { + return Err(anyhow::anyhow!( + "GetBytecode failed: exit={} error={}", + msg_rct.exit_code(), + api_invoc_result.error + ) + .into()); } let get_bytecode_return: GetBytecodeReturn = From 6c37967eb06f50ed03caa5fb9e8b147f4659b64e Mon Sep 17 00:00:00 2001 From: Shashank Date: Thu, 29 Jan 2026 03:33:14 +0530 Subject: [PATCH 6/6] more fix --- src/rpc/methods/eth.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 05193efd185..6e06dee085d 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2449,9 +2449,11 @@ where DB: Blockstore + Send + Sync + 'static, { let to_address = FilecoinAddress::try_from(eth_address)?; - let actor = ctx + let (state, _) = ctx .state_manager - .get_required_actor(&to_address, *ts.parent_state())?; + .tipset_state(ts, StateLookupPolicy::Enabled) + .await?; + let actor = ctx.state_manager.get_required_actor(&to_address, state)?; // Not a contract. We could try to distinguish between accounts and "native" contracts here, // but it's not worth it. if !is_evm_actor(&actor.code) { @@ -2466,10 +2468,6 @@ where ..Default::default() }; - let (state, _) = ctx - .state_manager - .tipset_state(ts, StateLookupPolicy::Enabled) - .await?; let api_invoc_result = 'invoc: { for ts in ts.clone().chain(ctx.store()) { match ctx.state_manager.call_on_state(state, &message, Some(ts)) {