diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a801a05906..c9285560bae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,8 @@ - [#6490](https://github.com/ChainSafe/forest/pull/6490): Implemented `Filecoin.EthGetCode` for API v2. +- [#6492](https://github.com/ChainSafe/forest/pull/6492): Implemented `Filecoin.EthGetStorageAt` for API v2. + - [#6498](https://github.com/ChainSafe/forest/pull/6498): Implemented `Filecoin.EthGetBlockReceiptsLimited` for API v2. ### Changed diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index a1cc3120d05..8b219bf132c 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -2537,6 +2537,10 @@ impl RpcMethod<3> for EthGetStorageAt { const PARAM_NAMES: [&'static str; 3] = ["ethAddress", "position", "blockNumberOrHash"]; const API_PATHS: BitFlags = ApiPaths::all(); const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = Some( + "Retrieves the storage value at a specific position for a contract + at a given block state, identified by its number, hash, or a special tag.", + ); type Params = (EthAddress, EthBytes, BlockNumberOrHash); type Ok = EthBytes; @@ -2545,67 +2549,102 @@ impl RpcMethod<3> for EthGetStorageAt { ctx: Ctx, (eth_address, position, block_number_or_hash): Self::Params, ) -> Result { - let make_empty_result = || EthBytes(vec![0; EVM_WORD_LENGTH]); - let ts = tipset_by_block_number_or_hash( ctx.chain_store(), block_number_or_hash, ResolveNullTipset::TakeOlder, )?; - let to_address = FilecoinAddress::try_from(ð_address)?; - let (state, _) = ctx - .state_manager - .tipset_state(&ts, StateLookupPolicy::Enabled) - .await?; - let Some(actor) = ctx.state_manager.get_actor(&to_address, state)? else { - return Ok(make_empty_result()); - }; + get_storage_at(&ctx, ts, eth_address, position).await + } +} - if !is_evm_actor(&actor.code) { - return Ok(make_empty_result()); - } +pub enum EthGetStorageAtV2 {} +impl RpcMethod<3> for EthGetStorageAtV2 { + const NAME: &'static str = "Filecoin.EthGetStorageAt"; + const NAME_ALIAS: Option<&'static str> = Some("eth_getStorageAt"); + const PARAM_NAMES: [&'static str; 3] = ["ethAddress", "position", "blockNumberOrHash"]; + const API_PATHS: BitFlags = make_bitflags!(ApiPaths::V2); + const PERMISSION: Permission = Permission::Read; + const DESCRIPTION: Option<&'static str> = Some( + "Retrieves the storage value at a specific position for a contract + at a given block state, identified by its number, hash, or a special tag.", + ); - let params = RawBytes::new(GetStorageAtParams::new(position.0)?.serialize_params()?); - let message = Message { - from: FilecoinAddress::SYSTEM_ACTOR, - to: to_address, - method_num: METHOD_GET_STORAGE_AT, - gas_limit: BLOCK_GAS_LIMIT, - params, - ..Default::default() - }; - 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, EthBytes, ExtBlockNumberOrHash); + type Ok = EthBytes; + + async fn handle( + ctx: Ctx, + (eth_address, position, block_number_or_hash): Self::Params, + ) -> Result { + let ts = tipset_by_block_number_or_hash_v2( + &ctx, + block_number_or_hash, + ResolveNullTipset::TakeOlder, + ) + .await?; + get_storage_at(&ctx, ts, eth_address, position).await + } +} + +async fn get_storage_at( + ctx: &Ctx, + ts: Tipset, + eth_address: EthAddress, + position: EthBytes, +) -> Result { + let to_address = FilecoinAddress::try_from(ð_address)?; + let (state, _) = ctx + .state_manager + .tipset_state(&ts, StateLookupPolicy::Enabled) + .await?; + let make_empty_result = || EthBytes(vec![0; EVM_WORD_LENGTH]); + let Some(actor) = ctx.state_manager.get_actor(&to_address, state)? else { + return Ok(make_empty_result()); + }; + + if !is_evm_actor(&actor.code) { + return Ok(make_empty_result()); + } + + let params = RawBytes::new(GetStorageAtParams::new(position.0)?.serialize_params()?); + let message = Message { + from: FilecoinAddress::SYSTEM_ACTOR, + to: to_address, + method_num: METHOD_GET_STORAGE_AT, + gas_limit: BLOCK_GAS_LIMIT, + params, + ..Default::default() + }; + 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), } - 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!( - "failed to lookup storage slot: {}", - 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!("failed to lookup storage slot: {}", api_invoc_result.error).into(), + ); + } - let mut ret = fvm_ipld_encoding::from_slice::(msg_rct.return_data().as_slice())? - .bytes() - .to_vec(); - if ret.len() < EVM_WORD_LENGTH { - let mut with_padding = vec![0; EVM_WORD_LENGTH.saturating_sub(ret.len())]; - with_padding.append(&mut ret); - Ok(EthBytes(with_padding)) - } else { - Ok(EthBytes(ret)) - } + let mut ret = fvm_ipld_encoding::from_slice::(msg_rct.return_data().as_slice())? + .bytes() + .to_vec(); + if ret.len() < EVM_WORD_LENGTH { + let mut with_padding = vec![0; EVM_WORD_LENGTH.saturating_sub(ret.len())]; + with_padding.append(&mut ret); + Ok(EthBytes(with_padding)) + } else { + Ok(EthBytes(ret)) } } diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index e7d55df8e16..270732ad2bb 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -131,6 +131,7 @@ macro_rules! for_each_rpc_method { $callback!($crate::rpc::eth::EthGetFilterChanges); $callback!($crate::rpc::eth::EthGetMessageCidByTransactionHash); $callback!($crate::rpc::eth::EthGetStorageAt); + $callback!($crate::rpc::eth::EthGetStorageAtV2); $callback!($crate::rpc::eth::EthGetTransactionByHash); $callback!($crate::rpc::eth::EthGetTransactionByHashLimited); $callback!($crate::rpc::eth::EthGetTransactionCount); diff --git a/src/tool/subcommands/api_cmd/api_compare_tests.rs b/src/tool/subcommands/api_cmd/api_compare_tests.rs index 690c9df2ff0..65ad68986ba 100644 --- a/src/tool/subcommands/api_cmd/api_compare_tests.rs +++ b/src/tool/subcommands/api_cmd/api_compare_tests.rs @@ -2007,6 +2007,30 @@ fn eth_tests_with_tipset(store: &Arc, shared_tipset: &Tipset )) .unwrap(), ), + RpcTest::identity( + EthGetStorageAtV2::request(( + EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(), + EthBytes(vec![0xa]), + ExtBlockNumberOrHash::from_block_number(shared_tipset.epoch()), + )) + .unwrap(), + ), + RpcTest::basic( + EthGetStorageAtV2::request(( + EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(), + EthBytes(vec![0xa]), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Safe), + )) + .unwrap(), + ), + RpcTest::basic( + EthGetStorageAtV2::request(( + EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(), + EthBytes(vec![0xa]), + ExtBlockNumberOrHash::from_predefined(ExtPredefined::Finalized), + )) + .unwrap(), + ), RpcTest::identity( EthFeeHistory::request(( 10.into(), diff --git a/src/tool/subcommands/api_cmd/test_snapshots.txt b/src/tool/subcommands/api_cmd/test_snapshots.txt index dcb624a8e35..2fb408f1299 100644 --- a/src/tool/subcommands/api_cmd/test_snapshots.txt +++ b/src/tool/subcommands/api_cmd/test_snapshots.txt @@ -85,6 +85,9 @@ filecoin_ethgetmessagecidbytransactionhash_1737446676697418.rpcsnap.json.zst filecoin_ethgetstorageat_1765803742043605.rpcsnap.json.zst # latest filecoin_ethgetstorageat_1765803742046844.rpcsnap.json.zst # concrete filecoin_ethgetstorageat_1765803742145789.rpcsnap.json.zst # pending +filecoin_ethgetstorageat_v2_1769611091439289.rpcsnap.json.zst +filecoin_ethgetstorageat_v2_finalized_1769611091511354.rpcsnap.json.zst +filecoin_ethgetstorageat_v2_safe_1769611091439347.rpcsnap.json.zst filecoin_ethgettransactionbyblockhashandindex_1740132538373654.rpcsnap.json.zst filecoin_ethgettransactionbyblocknumberandindex_1740132538304408.rpcsnap.json.zst filecoin_ethgettransactionbyblocknumberandindex_latest_1769103643171646.rpcsnap.json.zst