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.

- [#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`.
Expand Down
121 changes: 81 additions & 40 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2396,6 +2396,9 @@ impl RpcMethod<2> for EthGetCode {
const PARAM_NAMES: [&'static str; 2] = ["ethAddress", "blockNumberOrHash"];
const API_PATHS: BitFlags<ApiPaths> = 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;
Expand All @@ -2409,53 +2412,91 @@ impl RpcMethod<2> for EthGetCode {
block_param,
ResolveNullTipset::TakeOlder,
)?;
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());
}
eth_get_code(&ctx, &ts, &eth_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<ApiPaths> = 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<impl Blockstore + Send + Sync + 'static>,
(eth_address, block_param): Self::Params,
) -> Result<Self::Ok, ServerError> {
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, &eth_address).await
}
}

async fn eth_get_code<DB>(
ctx: &Ctx<DB>,
ts: &Tipset,
eth_address: &EthAddress,
) -> Result<EthBytes, ServerError>
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())
}
}

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