Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@

- [#6489](https://github.com/ChainSafe/forest/pull/6489): Implemented `Filecoin.EthGetBlockReceipts` for API v2.

- [#6492](https://github.com/ChainSafe/forest/pull/6492): Implemented `Filecoin.EthGetStorageAt` for API v2.

### Changed

- [#6471](https://github.com/ChainSafe/forest/pull/6471): Moved `forest-tool state` subcommand to `forest-dev`.
Expand Down
139 changes: 89 additions & 50 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2466,6 +2466,10 @@ impl RpcMethod<3> for EthGetStorageAt {
const PARAM_NAMES: [&'static str; 3] = ["ethAddress", "position", "blockNumberOrHash"];
const API_PATHS: BitFlags<ApiPaths> = 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;
Expand All @@ -2474,67 +2478,102 @@ impl RpcMethod<3> for EthGetStorageAt {
ctx: Ctx<impl Blockstore + Send + Sync + 'static>,
(eth_address, position, block_number_or_hash): Self::Params,
) -> Result<Self::Ok, ServerError> {
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(&eth_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<ApiPaths> = 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<impl Blockstore + Send + Sync + 'static>,
(eth_address, position, block_number_or_hash): Self::Params,
) -> Result<Self::Ok, ServerError> {
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<DB: Blockstore + Send + Sync + 'static>(
ctx: &Ctx<DB>,
ts: Tipset,
eth_address: EthAddress,
position: EthBytes,
) -> Result<EthBytes, ServerError> {
let to_address = FilecoinAddress::try_from(&eth_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::<RawBytes>(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::<RawBytes>(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))
}
}

Expand Down
1 change: 1 addition & 0 deletions src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,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);
Expand Down
24 changes: 24 additions & 0 deletions src/tool/subcommands/api_cmd/api_compare_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,30 @@ fn eth_tests_with_tipset<DB: Blockstore>(store: &Arc<DB>, 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(),
Expand Down
3 changes: 3 additions & 0 deletions src/tool/subcommands/api_cmd/test_snapshots.txt
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,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
Expand Down
Loading