Skip to content
Draft
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
112 changes: 108 additions & 4 deletions crates/anvil-polkadot/src/cmd.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
use crate::config::{
AccountGenerator, AnvilNodeConfig, CHAIN_ID, DEFAULT_MNEMONIC, SubstrateNodeConfig,
AccountGenerator, AnvilNodeConfig, CHAIN_ID, DEFAULT_MNEMONIC, ForkChoice, SubstrateNodeConfig,
};
use alloy_genesis::Genesis;
use alloy_primitives::{U256, utils::Unit};
use alloy_primitives::{B256, U256, utils::Unit};
use alloy_signer_local::coins_bip39::{English, Mnemonic};
use anvil_server::ServerConfig;
use clap::Parser;
use core::fmt;
use foundry_common::shell;
use foundry_config::Chain;
use rand_08::{SeedableRng, rngs::StdRng};
use std::{net::IpAddr, path::PathBuf, time::Duration};
use std::{net::IpAddr, path::PathBuf, str::FromStr, time::Duration};

#[derive(Clone, Debug, Parser)]
pub struct NodeArgs {
Expand Down Expand Up @@ -134,7 +135,20 @@ impl NodeArgs {
.with_code_size_limit(self.evm.code_size_limit)
.disable_code_size_limit(self.evm.disable_code_size_limit)
.with_disable_default_create2_deployer(self.evm.disable_default_create2_deployer)
.with_memory_limit(self.evm.memory_limit);
.with_memory_limit(self.evm.memory_limit)
.with_fork_choice(match (self.evm.fork_block_number, self.evm.fork_transaction_hash) {
(Some(block), None) => Some(ForkChoice::Block(block)),
(None, Some(hash)) => Some(ForkChoice::Transaction(hash)),
_ => self
.evm
.fork_url
.as_ref()
.and_then(|f| f.block)
.map(|num| ForkChoice::Block(num as i128)),
})
.with_eth_rpc_url(self.evm.fork_url.map(|fork| fork.url))
.fork_request_timeout(self.evm.fork_request_timeout.map(Duration::from_millis))
.fork_request_retries(self.evm.fork_request_retries);

let substrate_node_config = SubstrateNodeConfig::new(&anvil_config);

Expand Down Expand Up @@ -170,6 +184,56 @@ impl NodeArgs {
#[derive(Clone, Debug, Parser)]
#[command(next_help_heading = "EVM options")]
pub struct AnvilEvmArgs {
/// Fetch state over a remote endpoint instead of starting from an empty state.
///
/// If you want to fetch state from a specific block number, add a block number like `http://localhost:8545@1400000` or use the `--fork-block-number` argument.
#[arg(
long,
short,
visible_alias = "rpc-url",
value_name = "URL",
help_heading = "Fork config"
)]
pub fork_url: Option<ForkUrl>,

/// Fetch state from a specific block number over a remote endpoint.
///
/// If negative, the given value is subtracted from the `latest` block number.
///
/// See --fork-url.
#[arg(
long,
requires = "fork_url",
value_name = "BLOCK",
help_heading = "Fork config",
allow_hyphen_values = true
)]
pub fork_block_number: Option<i128>,

/// Fetch state from a specific transaction hash over a remote endpoint.
///
/// See --fork-url.
#[arg(
long,
requires = "fork_url",
value_name = "TRANSACTION",
help_heading = "Fork config",
conflicts_with = "fork_block_number"
)]
pub fork_transaction_hash: Option<B256>,

/// Timeout in ms for requests sent to remote JSON-RPC server in forking mode.
///
/// Default value 45000
#[arg(id = "timeout", long = "timeout", help_heading = "Fork config", requires = "fork_url")]
pub fork_request_timeout: Option<u64>,

/// Number of retry requests for spurious networks (timed out requests)
///
/// Default value 5
#[arg(id = "retries", long = "retries", help_heading = "Fork config", requires = "fork_url")]
pub fork_request_retries: Option<u32>,

/// The block gas limit.
#[arg(long, alias = "block-gas-limit", help_heading = "Environment config")]
pub gas_limit: Option<u128>,
Expand Down Expand Up @@ -245,6 +309,46 @@ pub struct AnvilEvmArgs {
pub memory_limit: Option<u64>,
}

/// Represents the input URL for a fork with an optional trailing block number:
/// `http://localhost:8545@1000000`
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ForkUrl {
/// The endpoint url
pub url: String,
/// Optional trailing block
pub block: Option<u64>,
}

impl fmt::Display for ForkUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.url.fmt(f)?;
if let Some(block) = self.block {
write!(f, "@{block}")?;
}
Ok(())
}
}

impl FromStr for ForkUrl {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((url, block)) = s.rsplit_once('@') {
if block == "latest" {
return Ok(Self { url: url.to_string(), block: None });
}
// this will prevent false positives for auths `user:[email protected]`
if !block.is_empty() && !block.contains(':') && !block.contains('.') {
let block: u64 = block
.parse()
.map_err(|_| format!("Failed to parse block number: `{block}`"))?;
return Ok(Self { url: url.to_string(), block: Some(block) });
}
}
Ok(Self { url: s.to_string(), block: None })
}
}

/// Clap's value parser for genesis. Loads a genesis.json file.
fn read_genesis_file(path: &str) -> Result<Genesis, String> {
foundry_common::fs::read_json_file(path.as_ref()).map_err(|err| err.to_string())
Expand Down
106 changes: 104 additions & 2 deletions crates/anvil-polkadot/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ use crate::{
substrate_node::chain_spec::keypairs_from_private_keys,
};
use alloy_genesis::Genesis;
use alloy_primitives::{Address, U256, hex, map::HashMap, utils::Unit};
use alloy_primitives::{Address, TxHash, U256, hex, map::HashMap, utils::Unit};
use alloy_signer::Signer;
use alloy_signer_local::{
MnemonicBuilder, PrivateKeySigner,
coins_bip39::{English, Mnemonic},
};
use anvil_server::ServerConfig;
use eyre::{Context, Result};
use foundry_common::{duration_since_unix_epoch, sh_println};
use foundry_common::{REQUEST_TIMEOUT, duration_since_unix_epoch, sh_println};
use polkadot_sdk::{
pallet_revive::evm::Account,
sc_cli::{
Expand Down Expand Up @@ -331,6 +331,14 @@ pub struct AnvilNodeConfig {
pub memory_limit: Option<u64>,
/// Do not print log messages.
pub silent: bool,
/// url of the rpc server that should be used for any rpc calls
pub eth_rpc_url: Option<String>,
/// pins the block number or transaction hash for the state fork
pub fork_choice: Option<ForkChoice>,
/// Timeout in for requests sent to remote JSON-RPC server in forking mode
pub fork_request_timeout: Duration,
/// Number of request retries for spurious networks
pub fork_request_retries: u32,
}

impl AnvilNodeConfig {
Expand Down Expand Up @@ -554,6 +562,10 @@ impl Default for AnvilNodeConfig {
disable_default_create2_deployer: false,
memory_limit: None,
silent: false,
eth_rpc_url: None,
fork_choice: None,
fork_request_timeout: REQUEST_TIMEOUT,
fork_request_retries: 5,
}
}
}
Expand Down Expand Up @@ -857,6 +869,96 @@ impl AnvilNodeConfig {
self.silent = silent;
self
}

/// Sets the `eth_rpc_url` to use when forking
#[must_use]
pub fn with_eth_rpc_url<U: Into<String>>(mut self, eth_rpc_url: Option<U>) -> Self {
self.eth_rpc_url = eth_rpc_url.map(Into::into);
self
}

/// Sets the `fork_choice` to use to fork off from based on a block number
#[must_use]
pub fn with_fork_block_number<U: Into<u64>>(self, fork_block_number: Option<U>) -> Self {
self.with_fork_choice(fork_block_number.map(Into::into))
}

/// Sets the `fork_choice` to use to fork off from based on a transaction hash
#[must_use]
pub fn with_fork_transaction_hash<U: Into<TxHash>>(
self,
fork_transaction_hash: Option<U>,
) -> Self {
self.with_fork_choice(fork_transaction_hash.map(Into::into))
}

/// Sets the `fork_choice` to use to fork off from
#[must_use]
pub fn with_fork_choice<U: Into<ForkChoice>>(mut self, fork_choice: Option<U>) -> Self {
self.fork_choice = fork_choice.map(Into::into);
self
}

/// Sets the `fork_request_timeout` to use for requests
#[must_use]
pub fn fork_request_timeout(mut self, fork_request_timeout: Option<Duration>) -> Self {
if let Some(fork_request_timeout) = fork_request_timeout {
self.fork_request_timeout = fork_request_timeout;
}
self
}

/// Sets the `fork_request_retries` to use for spurious networks
#[must_use]
pub fn fork_request_retries(mut self, fork_request_retries: Option<u32>) -> Self {
if let Some(fork_request_retries) = fork_request_retries {
self.fork_request_retries = fork_request_retries;
}
self
}
}

/// Fork delimiter used to specify which block or transaction to fork from.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ForkChoice {
/// Block number to fork from.
///
/// If negative, the given value is subtracted from the `latest` block number.
Block(i128),
/// Transaction hash to fork from.
Transaction(TxHash),
}

impl ForkChoice {
/// Returns the block number to fork from
pub fn block_number(&self) -> Option<i128> {
match self {
Self::Block(block_number) => Some(*block_number),
Self::Transaction(_) => None,
}
}

/// Returns the transaction hash to fork from
pub fn transaction_hash(&self) -> Option<TxHash> {
match self {
Self::Block(_) => None,
Self::Transaction(transaction_hash) => Some(*transaction_hash),
}
}
}

/// Convert a transaction hash into a ForkChoice
impl From<TxHash> for ForkChoice {
fn from(tx_hash: TxHash) -> Self {
Self::Transaction(tx_hash)
}
}

/// Convert a decimal block number into a ForkChoice
impl From<u64> for ForkChoice {
fn from(block: u64) -> Self {
Self::Block(block as i128)
}
}

/// Can create dev accounts
Expand Down
Loading