diff --git a/CHANGELOG.md b/CHANGELOG.md index e36c547649e..8d49f6dd22c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,8 @@ - [#6489](https://github.com/ChainSafe/forest/pull/6489): Implemented `Filecoin.EthGetBlockReceipts` 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`. diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 7aa8ce5a0dc..6e06dee085d 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2396,6 +2396,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; @@ -2409,53 +2412,91 @@ 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, StateLookupPolicy::Enabled) + 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?; - 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), + 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 (state, _) = ctx + .state_manager + .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) { + 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 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 !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 = - 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 452306245d3..85844c20717 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -124,6 +124,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 5b04adcc24b..011fbe90385 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -2147,6 +2147,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()), diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index 1504e99eff4..dc6698eeb15 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -76,6 +76,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