From 0cb182c2369c34fdd767759487298aac6c7e8069 Mon Sep 17 00:00:00 2001 From: hanabi1224 Date: Fri, 17 May 2024 17:25:04 +0800 Subject: [PATCH] feat(rpc): implement Filecoin.EthGetStorageAt --- src/rpc/methods/eth.rs | 103 +++++++++++++++++++++++++------- src/rpc/methods/eth/types.rs | 45 +++++++++++++- src/tool/subcommands/api_cmd.rs | 11 +++- 3 files changed, 137 insertions(+), 22 deletions(-) diff --git a/src/rpc/methods/eth.rs b/src/rpc/methods/eth.rs index 8120407fd2cf..84ed80bd7a4e 100644 --- a/src/rpc/methods/eth.rs +++ b/src/rpc/methods/eth.rs @@ -31,7 +31,7 @@ use cid::{ Cid, }; use fvm_ipld_blockstore::Blockstore; -use fvm_ipld_encoding::{CBOR, DAG_CBOR, IPLD_RAW}; +use fvm_ipld_encoding::{RawBytes, CBOR, DAG_CBOR, IPLD_RAW}; use itertools::Itertools; use keccak_hash::keccak; use num_bigint::{self, Sign}; @@ -51,6 +51,7 @@ macro_rules! for_each_method { $callback!(crate::rpc::eth::EthBlockNumber); $callback!(crate::rpc::eth::EthChainId); $callback!(crate::rpc::eth::EthGetCode); + $callback!(crate::rpc::eth::EthGetStorageAt); $callback!(crate::rpc::eth::EthGasPrice); $callback!(crate::rpc::eth::EthGetBalance); $callback!(crate::rpc::eth::EthGetBlockByNumber); @@ -160,15 +161,6 @@ pub struct Int64( lotus_json_with_self!(Int64); -#[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema)] -pub struct Bytes( - #[schemars(with = "String")] - #[serde(with = "crate::lotus_json::hexify_vec_bytes")] - pub Vec, -); - -lotus_json_with_self!(Bytes); - #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema)] pub struct Hash(#[schemars(with = "String")] pub ethereum_types::H256); @@ -316,7 +308,7 @@ pub struct Block { pub gas_limit: Uint64, pub gas_used: Uint64, pub timestamp: Uint64, - pub extra_data: Bytes, + pub extra_data: EthBytes, pub mix_hash: Hash, pub nonce: Nonce, pub base_fee_per_gas: BigInt, @@ -363,7 +355,7 @@ pub struct Tx { pub to: Option, pub value: BigInt, pub r#type: Uint64, - pub input: Bytes, + pub input: EthBytes, pub gas: Uint64, pub max_fee_per_gas: BigInt, pub max_priority_fee_per_gas: BigInt, @@ -897,7 +889,7 @@ fn eth_tx_from_signed_eth_message(smsg: &SignedMessage, chain_id: u32) -> Result v, r, s, - input: Bytes(tx_args.input), + input: EthBytes(tx_args.input), ..Tx::default() }) } @@ -949,10 +941,10 @@ fn encode_filecoin_params_as_abi( method: MethodNum, codec: u64, params: &fvm_ipld_encoding::RawBytes, -) -> Result { +) -> Result { let mut buffer: Vec = vec![0x86, 0x8e, 0x10, 0xc4]; buffer.append(&mut encode_filecoin_returns_as_abi(method, codec, params)); - Ok(Bytes(buffer)) + Ok(EthBytes(buffer)) } fn encode_filecoin_returns_as_abi( @@ -1000,16 +992,16 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec { } /// Decodes the payload using the given codec. -fn decode_payload(payload: &fvm_ipld_encoding::RawBytes, codec: u64) -> Result { +fn decode_payload(payload: &fvm_ipld_encoding::RawBytes, codec: u64) -> Result { match codec { DAG_CBOR | CBOR => { let result: Result, _> = serde_ipld_dagcbor::de::from_reader(payload.reader()); match result { - Ok(buffer) => Ok(Bytes(buffer)), + Ok(buffer) => Ok(EthBytes(buffer)), Err(err) => bail!("decode_payload: failed to decode cbor payload: {err}"), } } - IPLD_RAW => Ok(Bytes(payload.to_vec())), + IPLD_RAW => Ok(EthBytes(payload.to_vec())), _ => bail!("decode_payload: unsupported codec {codec}"), } } @@ -1291,7 +1283,7 @@ impl RpcMethod<2> for EthGetCode { const PERMISSION: Permission = Permission::Read; type Params = (EthAddress, BlockNumberOrHash); - type Ok = Bytes; + type Ok = EthBytes; async fn handle( ctx: Ctx, @@ -1338,13 +1330,84 @@ impl RpcMethod<2> for EthGetCode { 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(Bytes(ctx.store().get_required(&cid)?)) + Ok(EthBytes(ctx.store().get_required(&cid)?)) } else { Ok(Default::default()) } } } +pub enum EthGetStorageAt {} +impl RpcMethod<3> for EthGetStorageAt { + const NAME: &'static str = "Filecoin.EthGetStorageAt"; + const PARAM_NAMES: [&'static str; 3] = ["eth_address", "position", "block_number_or_hash"]; + const API_VERSION: ApiVersion = ApiVersion::V1; + const PERMISSION: Permission = Permission::Read; + + type Params = (EthAddress, EthBytes, BlockNumberOrHash); + type Ok = EthBytes; + + async fn handle( + ctx: Ctx, + (eth_address, position, block_number_or_hash): Self::Params, + ) -> Result { + let make_empty_result = || EthBytes(vec![0; 32]); + + let ts = tipset_by_block_number_or_hash(&ctx.chain_store, block_number_or_hash)?; + let to_address = FilecoinAddress::try_from(ð_address)?; + let Some(actor) = ctx + .state_manager + .get_actor(&to_address, *ts.parent_state())? + else { + return Ok(make_empty_result()); + }; + + if !fil_actor_interface::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 mut api_invoc_result = None; + for ts in ts.chain_arc(ctx.store()) { + match ctx.state_manager.call(&message, Some(ts)) { + Ok(res) => { + api_invoc_result = Some(res); + break; + } + Err(e) => tracing::warn!(%e), + } + } + let Some(api_invoc_result) = api_invoc_result else { + return Err(anyhow::anyhow!("no message receipt").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 mut ret = fvm_ipld_encoding::from_slice::(msg_rct.return_data().as_slice())? + .bytes() + .to_vec(); + if ret.len() < 32 { + let mut with_padding = vec![0; 32_usize.saturating_sub(ret.len())]; + with_padding.append(&mut ret); + Ok(EthBytes(with_padding)) + } else { + Ok(EthBytes(ret)) + } + } +} + #[cfg(test)] mod test { use super::*; diff --git a/src/rpc/methods/eth/types.rs b/src/rpc/methods/eth/types.rs index 757a9ef76d2b..198e3137af3c 100644 --- a/src/rpc/methods/eth/types.rs +++ b/src/rpc/methods/eth/types.rs @@ -4,17 +4,51 @@ use super::*; pub const METHOD_GET_BYTE_CODE: u64 = 3; +pub const METHOD_GET_STORAGE_AT: u64 = 5; + +#[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema)] +pub struct EthBytes( + #[schemars(with = "String")] + #[serde(with = "crate::lotus_json::hexify_vec_bytes")] + pub Vec, +); +lotus_json_with_self!(EthBytes); #[derive(Debug, Deserialize, Serialize)] pub struct GetBytecodeReturn(pub Option); +const GET_STORAGE_AT_PARAMS_ARRAY_LENGTH: usize = 32; + +#[derive(Debug, Clone)] +pub struct GetStorageAtParams(pub [u8; GET_STORAGE_AT_PARAMS_ARRAY_LENGTH]); + +impl GetStorageAtParams { + pub fn new(position: Vec) -> anyhow::Result { + if position.len() > GET_STORAGE_AT_PARAMS_ARRAY_LENGTH { + anyhow::bail!("supplied storage key is too long"); + } + let mut bytes = [0; GET_STORAGE_AT_PARAMS_ARRAY_LENGTH]; + bytes + .get_mut(GET_STORAGE_AT_PARAMS_ARRAY_LENGTH.saturating_sub(position.len())..) + .expect("Infallible") + .copy_from_slice(&position); + Ok(Self(bytes)) + } + + pub fn serialize_params(&self) -> anyhow::Result> { + const LENGTH_BUF_GET_STORAGE_AT_PARAMS: u8 = 129; + let mut encoded = fvm_ipld_encoding::to_vec(&RawBytes::new(self.0.to_vec()))?; + encoded.insert(0, LENGTH_BUF_GET_STORAGE_AT_PARAMS); + Ok(encoded) + } +} + #[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema)] pub struct EthAddress( #[schemars(with = "String")] #[serde(with = "crate::lotus_json::hexify_bytes")] pub ethereum_types::Address, ); - lotus_json_with_self!(EthAddress); impl EthAddress { @@ -152,4 +186,13 @@ mod tests { let ser = fvm_ipld_encoding::to_vec(&des).unwrap(); assert_eq!(ser, bytes); } + + #[test] + fn get_storage_at_params() { + let param = GetStorageAtParams::new(vec![0xa]).unwrap(); + assert_eq!( + &hex::encode(param.serialize_params().unwrap()), + "815820000000000000000000000000000000000000000000000000000000000000000a" + ); + } } diff --git a/src/tool/subcommands/api_cmd.rs b/src/tool/subcommands/api_cmd.rs index f18ae35a75c1..032d343a9d6c 100644 --- a/src/tool/subcommands/api_cmd.rs +++ b/src/tool/subcommands/api_cmd.rs @@ -14,7 +14,7 @@ use crate::message::{Message as _, SignedMessage}; use crate::message_pool::{MessagePool, MpoolRpcProvider}; use crate::networks::{parse_bootstrap_peers, ChainConfig, NetworkChain}; use crate::rpc::beacon::BeaconGetEntry; -use crate::rpc::eth::types::EthAddress; +use crate::rpc::eth::types::{EthAddress, EthBytes}; use crate::rpc::gas::GasEstimateGasLimit; use crate::rpc::miner::BlockTemplate; use crate::rpc::types::{ApiTipsetKey, MessageFilter, MessageLookup, SectorOnChainInfo}; @@ -1038,6 +1038,15 @@ fn eth_tests_with_tipset(shared_tipset: &Tipset) -> Vec { RpcTest::identity( EthGetBlockTransactionCountByNumber::request((Int64(shared_tipset.epoch()),)).unwrap(), ), + RpcTest::identity( + EthGetStorageAt::request(( + // https://filfox.info/en/address/f410fpoidg73f7krlfohnla52dotowde5p2sejxnd4mq + EthAddress::from_str("0x7B90337f65fAA2B2B8ed583ba1Ba6EB0C9D7eA44").unwrap(), + EthBytes(vec![0xa]), + BlockNumberOrHash::BlockNumber(shared_tipset.epoch()), + )) + .unwrap(), + ), RpcTest::identity( EthGetCode::request(( // https://filfox.info/en/address/f410fpoidg73f7krlfohnla52dotowde5p2sejxnd4mq