Skip to content

Commit

Permalink
feat(rpc): implement Filecoin.EthGetStorageAt
Browse files Browse the repository at this point in the history
  • Loading branch information
hanabi1224 committed May 17, 2024
1 parent de1f84c commit 0cb182c
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 22 deletions.
103 changes: 83 additions & 20 deletions src/rpc/methods/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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);
Expand Down Expand Up @@ -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<u8>,
);

lotus_json_with_self!(Bytes);

#[derive(PartialEq, Debug, Deserialize, Serialize, Default, Clone, JsonSchema)]
pub struct Hash(#[schemars(with = "String")] pub ethereum_types::H256);

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -363,7 +355,7 @@ pub struct Tx {
pub to: Option<EthAddress>,
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,
Expand Down Expand Up @@ -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()
})
}
Expand Down Expand Up @@ -949,10 +941,10 @@ fn encode_filecoin_params_as_abi(
method: MethodNum,
codec: u64,
params: &fvm_ipld_encoding::RawBytes,
) -> Result<Bytes> {
) -> Result<EthBytes> {
let mut buffer: Vec<u8> = 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(
Expand Down Expand Up @@ -1000,16 +992,16 @@ fn encode_as_abi_helper(param1: u64, param2: u64, data: &[u8]) -> Vec<u8> {
}

/// Decodes the payload using the given codec.
fn decode_payload(payload: &fvm_ipld_encoding::RawBytes, codec: u64) -> Result<Bytes> {
fn decode_payload(payload: &fvm_ipld_encoding::RawBytes, codec: u64) -> Result<EthBytes> {
match codec {
DAG_CBOR | CBOR => {
let result: Result<Vec<u8>, _> = 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}"),
}
}
Expand Down Expand Up @@ -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<impl Blockstore + Send + Sync + 'static>,
Expand Down Expand Up @@ -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<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; 32]);

let ts = tipset_by_block_number_or_hash(&ctx.chain_store, block_number_or_hash)?;
let to_address = FilecoinAddress::try_from(&eth_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::<RawBytes>(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::*;
Expand Down
45 changes: 44 additions & 1 deletion src/rpc/methods/eth/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
);
lotus_json_with_self!(EthBytes);

#[derive(Debug, Deserialize, Serialize)]
pub struct GetBytecodeReturn(pub Option<Cid>);

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<u8>) -> anyhow::Result<Self> {
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<Vec<u8>> {
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 {
Expand Down Expand Up @@ -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"
);
}
}
11 changes: 10 additions & 1 deletion src/tool/subcommands/api_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -1038,6 +1038,15 @@ fn eth_tests_with_tipset(shared_tipset: &Tipset) -> Vec<RpcTest> {
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
Expand Down

0 comments on commit 0cb182c

Please sign in to comment.