Skip to content
Merged
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 @@ -33,6 +33,8 @@

- [#7595](https://github.com/ChainSafe/forest/pull/7595): Implement `forest-cli evm invoke`.

- [#7472](https://github.com/ChainSafe/forest/issues/7472): `forest-wallet send` now accepts `--method` and `--params-hex`, matching `lotus send`.

### Changed

- [#7594](https://github.com/ChainSafe/forest/pull/7594): `forest-cli index backfill` now defaults `--recompute` to true and exits with an error if any tipsets were skipped.
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/users/reference/cli.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 3 additions & 6 deletions src/cli/subcommands/evm_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0, MIT

use crate::cli::humantoken;
use crate::eth::{EAMMethod, EVMMethod};
use crate::eth::{EAMMethod, EVMMethod, encode_evm_params};
use crate::rpc::eth::{
BlockNumberOrHash, Predefined,
types::{EthAddress, EthBytes, EthCallMessage},
Expand All @@ -20,7 +20,7 @@ use base64::prelude::BASE64_STANDARD;
use cid::Cid;
use clap::Subcommand;
use fil_actor_eam_state::v16::CreateExternalParams;
use fil_actor_evm_state::v16::{InvokeContractParams, InvokeContractReturn};
use fil_actor_evm_state::v16::InvokeContractReturn;
use fvm_ipld_encoding::RawBytes;
use std::path::PathBuf;
use std::str::FromStr as _;
Expand Down Expand Up @@ -190,10 +190,7 @@ async fn invoke(
calldata: EthBytes,
) -> anyhow::Result<()> {
let from = resolve_from(&client, from).await?;
let params = RawBytes::serialize(InvokeContractParams {
input_data: calldata.0,
})
.context("failed to encode evm params as cbor")?;
let params = RawBytes::new(encode_evm_params(&calldata.0)?);

let msg = Message {
to: address,
Expand Down
99 changes: 37 additions & 62 deletions src/dev/subcommands/devnet_cmd/eth_gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ use crate::rpc::eth::{
};
use crate::rpc::prelude::*;
use crate::shim::address::Address;
use crate::utils::encoding::{hex, keccak_256};
use crate::utils::encoding::keccak_256;
use anyhow::{Context as _, ensure};
use cid::Cid;
use jsonrpsee::core::ClientError;
use libtest_mimic::{Arguments, Failed, Trial};
use std::str::FromStr as _;
Expand Down Expand Up @@ -100,37 +99,27 @@ fn recurse_calldata(depth: u64) -> Vec<u8> {
out
}

/// Deployed `NestedGas` addresses: `eth` for the JSON-RPC calls, `f4` as the `lotus send` target.
struct Deployed {
eth: EthAddress,
f4: Address,
}

/// Deploys `NestedGas` once per process.
async fn contract() -> anyhow::Result<&'static Deployed> {
static CONTRACT: OnceCell<Deployed> = OnceCell::const_new();
async fn contract() -> anyhow::Result<&'static EthAddress> {
static CONTRACT: OnceCell<EthAddress> = OnceCell::const_new();
CONTRACT
.get_or_try_init(|| async {
let from = sender_addr().await?.to_string();
let deploy = forest_evm_deploy_hex(&from, NESTED_GAS_HEX)?;
let from = sender().await?;
let deploy = forest_evm_deploy_hex(from, NESTED_GAS_HEX)?;
let f4 = parse_f4_from_evm_deploy(&deploy)?;
eprintln!("deployed NestedGas at {f4}");
poll_until_actor_on("forest", f4, forest_client).await?;
poll_until_actor_on("lotus", f4, lotus_client).await?;
poll_until_next_epoch().await?;
anyhow::Ok(Deployed {
eth: EthAddress::from_filecoin_address(&f4)?,
f4,
})
EthAddress::from_filecoin_address(&f4)
})
.await
}

/// An `f4` sender funded well enough to afford the gas limits under test. Lotus rejects
/// estimation from an unfunded or non-`f4` sender, so both properties are required.
async fn sender_addr() -> anyhow::Result<&'static Address> {
static SENDER: OnceCell<Address> = OnceCell::const_new();
SENDER
/// Funded delegated sender. Lotus rejects estimates from an unfunded or non-`f4` address.
async fn sender() -> anyhow::Result<&'static str> {
static SENDER: OnceCell<String> = OnceCell::const_new();
Ok(SENDER
.get_or_try_init(|| async {
let addr = lotus_exec(&["wallet", "new", "delegated"])?;
let msg = send_from(
Expand All @@ -142,23 +131,25 @@ async fn sender_addr() -> anyhow::Result<&'static Address> {
eprintln!("funding sender {addr} with {SENDER_FUND_AMT}, msg: {msg}");
let balance = poll_until_funded(&addr, Backend::Local).await?;
eprintln!("sender {addr} funded balance: {balance}");
let sender = Address::from_str(&addr).context("parsing the sender address")?;
poll_until_actor_on("lotus", sender, lotus_client).await?;
let parsed = Address::from_str(&addr).context("parsing the sender address")?;
poll_until_actor_on("lotus", parsed, lotus_client).await?;
import_lotus_wallet_into_forest(&addr)?;
Ok(sender)
anyhow::Ok(addr)
})
.await
.await?
.as_str())
}

async fn estimate(
client: &Client,
calldata: Vec<u8>,
block: BlockNumberOrHash,
) -> anyhow::Result<u64> {
let (sender, deployed) = tokio::try_join!(sender_addr(), contract())?;
let (from, to) = tokio::try_join!(sender(), contract())?;
let from = Address::from_str(from).context("parsing the sender address")?;
let msg = EthCallMessage {
from: Some(EthAddress::from_filecoin_address(sender)?),
to: Some(deployed.eth),
from: Some(EthAddress::from_filecoin_address(&from)?),
to: Some(*to),
data: Some(EthBytes(calldata)),
..Default::default()
};
Expand Down Expand Up @@ -194,7 +185,7 @@ async fn poll_until_next_epoch() -> anyhow::Result<()> {
/// height only after the deploy/fund guarantees the pinned tipset already contains the contract and
/// sender on both nodes (the funding poll also lets both catch up to the deploy).
async fn pinned_common_block() -> anyhow::Result<(Client, Client, i64)> {
tokio::try_join!(contract(), sender_addr())?;
contract().await?;
let (forest_c, lotus_c) = (forest_client()?, lotus_client()?);
let block = common_block_number(&forest_c, &lotus_c).await?;
Ok((forest_c, lotus_c, block))
Expand Down Expand Up @@ -235,46 +226,30 @@ async fn estimate_agrees(depth: u64) -> anyhow::Result<()> {
async fn estimate_is_sufficient_on_chain() -> anyhow::Result<()> {
let forest = forest_client()?;
// No cross-node comparison here, so `Latest` is fine: the estimate must reflect the same
// fresh state the following `lotus send` executes against.
// fresh state the following `forest-wallet send` executes against.
let estimate = estimate(
&forest,
recurse_calldata(NESTED_DEPTH),
BlockNumberOrHash::PredefinedBlock(Predefined::Latest),
)
.await?;
let sender = sender_addr().await?.to_string();
let target = contract().await?.f4.to_string();
let params = hex::encode(recurse_calldata(NESTED_DEPTH));
let gas_limit = estimate.to_string();
// `lotus send` infers `InvokeContract` and CBOR-wraps the params when the sender is an
// eth account, and rejects an explicit `--method`, so pass the bare calldata. Retry the
// submit while Lotus's mpool briefly lags the freshly funded sender.
let out = lotus_exec_retrying_transient(&[
"send",
"--from",
&sender,
"--params-hex",
&params,
"--gas-limit",
&gas_limit,
&target,
"0",
])
.await?;
let cid = out
.lines()
.last()
.context("no cid from `lotus send`")?
.trim();
let from = sender().await?;
// `forest-wallet send` infers `InvokeContract` and CBOR-wraps the params when the sender is an
// eth account, and rejects an explicit `--method`, so pass the bare calldata.
let cid = wallet_send_calldata(
from,
contract().await?,
&recurse_calldata(NESTED_DEPTH),
estimate,
)
.await
.with_context(|| {
format!(
"a transaction submitted at forest's own eth_estimateGas value ({estimate}) failed \
on chain; the estimate is not a usable gas limit"
)
})?;
eprintln!("submitted at forest's estimate {estimate}: {cid}");

let lookup = poll_until_message_executed(&forest, Cid::from_str(cid)?).await?;
let exit = lookup.receipt.exit_code();
ensure!(
exit.is_success(),
"a transaction submitted at forest's own eth_estimateGas value ({estimate}) failed \
on chain with exit code {exit}; the estimate is not a usable gas limit"
);
Ok(())
}

Expand Down
Loading
Loading