1use crate::api_server::revive_conversions::ReviveAddress;
2use alloy_genesis::Genesis;
3use alloy_primitives::{Address, U256, hex, map::HashMap, utils::Unit};
4use alloy_signer_local::{
5 MnemonicBuilder, PrivateKeySigner,
6 coins_bip39::{English, Mnemonic},
7};
8use anvil_server::ServerConfig;
9use eyre::{Context, Result};
10use foundry_common::{duration_since_unix_epoch, sh_println};
11use polkadot_sdk::{
12 pallet_revive::evm::Account,
13 sc_cli::{
14 self, CliConfiguration as SubstrateCliConfiguration, Cors, DEFAULT_WASM_EXECUTION_METHOD,
15 DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, RPC_DEFAULT_MAX_CONNECTIONS,
16 RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
17 RPC_DEFAULT_MAX_SUBS_PER_CONN, RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN,
18 },
19 sc_service,
20};
21use rand_08::thread_rng;
22use serde_json::{Value, json};
23use std::{
24 fmt::Write as FmtWrite,
25 fs::File,
26 io,
27 net::{IpAddr, Ipv4Addr},
28 num::NonZeroU32,
29 path::PathBuf,
30 time::Duration,
31};
32use subxt_signer::eth::Keypair;
33use yansi::Paint;
34
35pub use foundry_common::version::SHORT_VERSION as VERSION_MESSAGE;
36
37pub const NODE_PORT: u16 = 8545;
39pub const CHAIN_ID: u64 = 31337;
41pub const DEFAULT_GAS_LIMIT: u128 = 30_000_000;
43pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk";
45
46pub const DEFAULT_IPC_ENDPOINT: &str =
48 if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" };
49
50pub const INITIAL_BASE_FEE: u128 = 1_000_000;
53
54const BANNER: &str = r"
55 _ _
56 (_) | |
57 __ _ _ __ __ __ _ | |
58 / _` | | '_ \ \ \ / / | | | |
59 | (_| | | | | | \ V / | | | |
60 \__,_| |_| |_| \_/ |_| |_|
61";
62
63#[derive(Clone, Debug)]
64pub struct SubstrateNodeConfig {
65 shared_params: sc_cli::SharedParams,
66 rpc_params: sc_cli::RpcParams,
67 import_params: sc_cli::ImportParams,
68}
69
70impl SubstrateNodeConfig {
71 pub fn new(_anvil_config: &AnvilNodeConfig) -> Self {
72 let shared_params = sc_cli::SharedParams {
73 chain: None,
74 dev: true,
75 base_path: None,
76 log: vec![],
77 detailed_log_output: true,
78 disable_log_color: false,
79 enable_log_reloading: false,
80 tracing_targets: None,
81 tracing_receiver: sc_cli::TracingReceiver::Log,
82 };
83
84 let rpc_params = sc_cli::RpcParams {
85 rpc_external: false,
86 unsafe_rpc_external: false,
87 rpc_methods: sc_cli::RpcMethods::Auto,
88 rpc_rate_limit: None,
89 rpc_rate_limit_whitelisted_ips: vec![],
90 rpc_rate_limit_trust_proxy_headers: false,
91 rpc_max_request_size: RPC_DEFAULT_MAX_REQUEST_SIZE_MB,
92 rpc_max_response_size: RPC_DEFAULT_MAX_RESPONSE_SIZE_MB,
93 rpc_max_subscriptions_per_connection: RPC_DEFAULT_MAX_SUBS_PER_CONN,
94 rpc_port: None,
95 experimental_rpc_endpoint: vec![],
96 rpc_max_connections: RPC_DEFAULT_MAX_CONNECTIONS,
97 rpc_message_buffer_capacity_per_connection: RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN,
98 rpc_disable_batch_requests: false,
99 rpc_max_batch_request_len: None,
100 rpc_cors: None,
101 };
102
103 let import_params = sc_cli::ImportParams {
108 pruning_params: sc_cli::PruningParams {
109 state_pruning: Some(sc_cli::DatabasePruningMode::Archive),
110 blocks_pruning: sc_cli::DatabasePruningMode::Archive,
111 },
112 database_params: sc_cli::DatabaseParams { database: None, database_cache_size: None },
113 wasm_method: DEFAULT_WASM_EXECUTION_METHOD,
114 wasmtime_instantiation_strategy: DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
115 wasm_runtime_overrides: None,
116 execution_strategies: sc_cli::ExecutionStrategiesParams {
117 execution_syncing: None,
118 execution_import_block: None,
119 execution_block_construction: None,
120 execution_offchain_worker: None,
121 execution_other: None,
122 execution: None,
123 },
124 trie_cache_size: 1024 * 1024 * 1024,
125 warm_up_trie_cache: None,
126 };
127
128 Self { shared_params, rpc_params, import_params }
129 }
130
131 pub fn set_base_path(&mut self, base_path: Option<PathBuf>) {
132 self.shared_params.base_path = base_path;
133 }
134}
135
136impl SubstrateCliConfiguration for SubstrateNodeConfig {
137 fn shared_params(&self) -> &sc_cli::SharedParams {
138 &self.shared_params
139 }
140
141 fn import_params(&self) -> Option<&sc_cli::ImportParams> {
142 Some(&self.import_params)
143 }
144
145 fn network_params(&self) -> Option<&sc_cli::NetworkParams> {
146 None
147 }
148
149 fn keystore_params(&self) -> Option<&sc_cli::KeystoreParams> {
150 None
151 }
152
153 fn offchain_worker_params(&self) -> Option<&sc_cli::OffchainWorkerParams> {
154 None
155 }
156
157 fn node_name(&self) -> sc_cli::Result<String> {
158 Ok("anvil-substrate".to_string())
159 }
160
161 fn dev_key_seed(&self, _is_dev: bool) -> sc_cli::Result<Option<String>> {
162 Ok(Some("//Alice".into()))
163 }
164
165 fn telemetry_endpoints(
166 &self,
167 _chain_spec: &Box<dyn sc_service::ChainSpec>,
168 ) -> sc_cli::Result<Option<sc_service::config::TelemetryEndpoints>> {
169 Ok(None)
170 }
171
172 fn role(&self, _is_dev: bool) -> sc_cli::Result<sc_service::Role> {
173 Ok(sc_service::Role::Authority)
174 }
175
176 fn force_authoring(&self) -> sc_cli::Result<bool> {
177 Ok(true)
178 }
179
180 fn prometheus_config(
181 &self,
182 _default_listen_port: u16,
183 _chain_spec: &Box<dyn sc_service::ChainSpec>,
184 ) -> sc_cli::Result<Option<sc_service::config::PrometheusConfig>> {
185 Ok(None)
186 }
187
188 fn disable_grandpa(&self) -> sc_cli::Result<bool> {
189 Ok(true)
190 }
191
192 fn rpc_max_connections(&self) -> sc_cli::Result<u32> {
193 Ok(self.rpc_params.rpc_max_connections)
194 }
195
196 fn rpc_cors(&self, _is_dev: bool) -> sc_cli::Result<Option<Vec<String>>> {
197 Ok(self.rpc_params.rpc_cors.clone().unwrap_or(Cors::All).into())
198 }
199
200 fn rpc_addr(
201 &self,
202 default_listen_port: u16,
203 ) -> sc_cli::Result<Option<Vec<sc_cli::RpcEndpoint>>> {
204 self.rpc_params.rpc_addr(true, true, default_listen_port)
205 }
206
207 fn rpc_methods(&self) -> sc_cli::Result<sc_service::RpcMethods> {
208 Ok(self.rpc_params.rpc_methods.into())
209 }
210
211 fn rpc_max_request_size(&self) -> sc_cli::Result<u32> {
212 Ok(self.rpc_params.rpc_max_request_size)
213 }
214
215 fn rpc_max_response_size(&self) -> sc_cli::Result<u32> {
216 Ok(self.rpc_params.rpc_max_response_size)
217 }
218
219 fn rpc_max_subscriptions_per_connection(&self) -> sc_cli::Result<u32> {
220 Ok(self.rpc_params.rpc_max_subscriptions_per_connection)
221 }
222
223 fn rpc_buffer_capacity_per_connection(&self) -> sc_cli::Result<u32> {
224 Ok(self.rpc_params.rpc_message_buffer_capacity_per_connection)
225 }
226
227 fn rpc_batch_config(&self) -> sc_cli::Result<sc_service::config::RpcBatchRequestConfig> {
228 self.rpc_params.rpc_batch_config()
229 }
230
231 fn rpc_rate_limit(&self) -> sc_cli::Result<Option<NonZeroU32>> {
232 Ok(self.rpc_params.rpc_rate_limit)
233 }
234
235 fn rpc_rate_limit_whitelisted_ips(&self) -> sc_cli::Result<Vec<sc_service::config::IpNetwork>> {
236 Ok(self.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
237 }
238
239 fn rpc_rate_limit_trust_proxy_headers(&self) -> sc_cli::Result<bool> {
240 Ok(self.rpc_params.rpc_rate_limit_trust_proxy_headers)
241 }
242
243 fn transaction_pool(
244 &self,
245 _is_dev: bool,
246 ) -> sc_cli::Result<sc_service::TransactionPoolOptions> {
247 Ok(sc_service::TransactionPoolOptions::new_with_params(
248 8192,
249 20480 * 1024,
250 None,
251 sc_cli::TransactionPoolType::SingleState.into(),
254 true,
255 ))
256 }
257
258 fn base_path(&self) -> sc_cli::Result<Option<sc_service::BasePath>> {
259 self.shared_params().base_path()
260 }
261}
262
263#[derive(Clone, Debug)]
265pub struct AnvilNodeConfig {
266 pub chain_id: Option<u64>,
268 pub base_fee: Option<u128>,
270 pub genesis_accounts: Vec<Keypair>,
272 pub genesis_balance: U256,
274 pub genesis_timestamp: Option<u64>,
276 pub genesis_block_number: Option<u64>,
278 pub signer_accounts: Vec<Keypair>,
280 pub block_time: Option<Duration>,
282 pub no_mining: bool,
284 pub mixed_mining: bool,
286 pub port: u16,
288 pub account_generator: Option<AccountGenerator>,
290 pub enable_tracing: bool,
292 pub server_config: ServerConfig,
294 pub host: Vec<IpAddr>,
296 pub config_out: Option<PathBuf>,
298 pub genesis: Option<Genesis>,
300 pub ipc_path: Option<Option<String>>,
302 pub enable_auto_impersonate: bool,
304 pub revive_rpc_block_limit: Option<usize>,
306 pub silent: bool,
308}
309
310impl AnvilNodeConfig {
311 fn as_string(&self) -> String {
312 let mut s: String = String::new();
313 let _ = write!(s, "\n{}", BANNER.green());
314 let _ = write!(s, "\n {VERSION_MESSAGE}");
315 let _ = write!(s, "\n {}", "https://github.com/paritytech/foundry-polkadot".green());
316
317 let _ = write!(
318 s,
319 r#"
320
321Available Accounts
322==================
323"#
324 );
325 let balance = alloy_primitives::utils::format_ether(self.genesis_balance);
326 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
327 write!(
328 s,
329 "\n({idx}) {} ({balance} ETH)",
330 Address::from(ReviveAddress::new(Account::from(wallet.clone()).address()))
331 )
332 .unwrap();
333 }
334
335 let _ = write!(
336 s,
337 r#"
338
339Private Keys
340==================
341"#
342 );
343
344 for (idx, wallet) in self.genesis_accounts.iter().enumerate() {
345 let hex = hex::encode(wallet.secret_key());
346 let _ = write!(s, "\n({idx}) 0x{hex}");
347 }
348
349 if let Some(ref rng_gen) = self.account_generator {
350 let _ = write!(
351 s,
352 r#"
353
354Wallet
355==================
356Mnemonic: {}
357Derivation path: {}
358"#,
359 rng_gen.phrase,
360 rng_gen.get_derivation_path()
361 );
362 }
363
364 let _ = write!(
365 s,
366 r#"
367
368Chain ID
369==================
370
371{}
372"#,
373 self.get_chain_id().green()
374 );
375
376 let _ = write!(
377 s,
378 r#"
379Base Fee
380==================
381
382{}
383"#,
384 self.get_base_fee().green()
385 );
386
387 let _ = write!(
388 s,
389 r#"
390Genesis Timestamp
391==================
392
393{}
394"#,
395 self.get_genesis_timestamp().green()
396 );
397
398 let _ = write!(
399 s,
400 r#"
401Genesis Number
402==================
403
404{}
405"#,
406 self.get_genesis_number().green()
407 );
408
409 s
410 }
411
412 fn as_json(&self) -> Value {
413 let mut wallet_description = HashMap::new();
414 let mut available_accounts = Vec::with_capacity(self.genesis_accounts.len());
415 let mut private_keys = Vec::with_capacity(self.genesis_accounts.len());
416
417 for wallet in &self.genesis_accounts {
418 available_accounts.push(format!("{:?}", Account::from(wallet.clone()).address()));
419 private_keys.push(hex::encode_prefixed(wallet.secret_key()));
420 }
421
422 if let Some(ref rng_gen) = self.account_generator {
423 let phrase = rng_gen.get_phrase().to_string();
424 let derivation_path = rng_gen.get_derivation_path().to_string();
425
426 wallet_description.insert("derivation_path".to_string(), derivation_path);
427 wallet_description.insert("mnemonic".to_string(), phrase);
428 };
429
430 json!({
431 "available_accounts": available_accounts,
432 "private_keys": private_keys,
433 "wallet": wallet_description,
434 "base_fee": format!("{}", self.get_base_fee()),
435 "genesis_timestamp": format!("{}", self.get_genesis_timestamp()),
436 })
437 }
438
439 pub fn test_config() -> Self {
440 let mut anvil_node_config = Self {
441 port: 0,
442 no_mining: true,
443 mixed_mining: false,
444 enable_tracing: false,
445 silent: true,
446 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(10000u64)),
447 ..Default::default()
448 };
449 let dev_accounts =
450 vec![subxt_signer::eth::dev::alith(), subxt_signer::eth::dev::baltathar()];
451 anvil_node_config.genesis_accounts.extend(dev_accounts.clone());
452 anvil_node_config.signer_accounts.extend(dev_accounts);
453 anvil_node_config
454 }
455}
456
457impl Default for AnvilNodeConfig {
458 fn default() -> Self {
459 let genesis_accounts = keypairs_from_private_keys(
461 &AccountGenerator::new(10)
462 .phrase(DEFAULT_MNEMONIC)
463 .rng_gen()
464 .expect("Invalid mnemonic."),
465 )
466 .expect("Invalid keys");
467
468 Self {
469 chain_id: None,
470 signer_accounts: genesis_accounts.clone(),
471 genesis_timestamp: None,
472 genesis_block_number: None,
473 genesis_accounts,
474 genesis_balance: Unit::ETHER.wei().saturating_mul(U256::from(100u64)),
476 block_time: None,
477 no_mining: false,
478 mixed_mining: false,
479 port: NODE_PORT,
480 account_generator: None,
481 base_fee: None,
482 enable_tracing: true,
483 enable_auto_impersonate: false,
484 revive_rpc_block_limit: None,
485 server_config: Default::default(),
486 host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
487 config_out: None,
488 genesis: None,
489 ipc_path: None,
490 silent: false,
491 }
492 }
493}
494
495impl AnvilNodeConfig {
496 pub fn get_base_fee(&self) -> u128 {
498 self.base_fee
499 .or_else(|| {
500 self.genesis.as_ref().and_then(|g| {
501 g.base_fee_per_gas
503 })
504 })
505 .unwrap_or(INITIAL_BASE_FEE)
506 }
507
508 #[must_use]
510 pub fn with_chain_id<U: Into<u64>>(mut self, chain_id: Option<U>) -> Self {
511 self.set_chain_id(chain_id);
512 self
513 }
514
515 pub fn get_chain_id(&self) -> u64 {
517 self.chain_id
518 .or_else(|| self.genesis.as_ref().map(|g| g.config.chain_id))
519 .unwrap_or(CHAIN_ID)
520 }
521
522 pub fn set_chain_id(&mut self, chain_id: Option<impl Into<u64>>) {
524 self.chain_id = chain_id.map(Into::into);
525 }
526
527 #[must_use]
529 pub fn with_revive_rpc_block_limit<U: Into<usize>>(
530 mut self,
531 revive_rpc_block_limit: Option<U>,
532 ) -> Self {
533 self.revive_rpc_block_limit = revive_rpc_block_limit.map(Into::into);
534 self
535 }
536
537 #[must_use]
539 pub fn with_base_fee(mut self, base_fee: Option<u64>) -> Self {
540 self.base_fee = base_fee.map(|bf| bf.into());
541 self
542 }
543
544 #[must_use]
546 pub fn with_genesis(mut self, genesis: Option<Genesis>) -> Self {
547 self.genesis = genesis;
548 self
549 }
550
551 pub fn get_genesis_timestamp(&self) -> u64 {
553 self.genesis_timestamp
554 .or_else(|| self.genesis.as_ref().map(|g| g.timestamp))
555 .unwrap_or_else(|| duration_since_unix_epoch().as_secs())
556 }
557
558 #[must_use]
560 pub fn with_genesis_timestamp<U: Into<u64>>(mut self, timestamp: Option<U>) -> Self {
561 if let Some(timestamp) = timestamp {
562 self.genesis_timestamp = Some(timestamp.into());
563 }
564 self
565 }
566
567 #[must_use]
569 pub fn with_genesis_block_number<U: Into<u64>>(mut self, number: Option<U>) -> Self {
570 if let Some(number) = number {
571 self.genesis_block_number = Some(number.into());
572 }
573 self
574 }
575
576 pub fn get_genesis_number(&self) -> u64 {
578 self.genesis_block_number
579 .or_else(|| self.genesis.as_ref().and_then(|g| g.number))
580 .unwrap_or(0)
581 }
582
583 #[must_use]
585 pub fn with_genesis_accounts(mut self, accounts: Vec<Keypair>) -> Self {
586 self.genesis_accounts = accounts;
587 self
588 }
589
590 #[must_use]
592 pub fn with_signer_accounts(mut self, accounts: Vec<Keypair>) -> Self {
593 self.signer_accounts = accounts;
594 self
595 }
596
597 pub fn with_account_generator(mut self, generator: AccountGenerator) -> eyre::Result<Self> {
600 let accounts = generator.rng_gen()?;
601 self.account_generator = Some(generator);
602 let accounts = keypairs_from_private_keys(&accounts)?;
603 Ok(self.with_signer_accounts(accounts.clone()).with_genesis_accounts(accounts))
604 }
605
606 #[must_use]
608 pub fn with_genesis_balance<U: Into<U256>>(mut self, balance: U) -> Self {
609 self.genesis_balance = balance.into();
610 self
611 }
612
613 #[must_use]
615 pub fn with_blocktime<D: Into<Duration>>(mut self, block_time: Option<D>) -> Self {
616 self.block_time = block_time.map(Into::into);
617 self
618 }
619
620 #[must_use]
621 pub fn with_mixed_mining<D: Into<Duration>>(
622 mut self,
623 mixed_mining: bool,
624 block_time: Option<D>,
625 ) -> Self {
626 self.block_time = block_time.map(Into::into);
627 self.mixed_mining = mixed_mining;
628 self
629 }
630
631 #[must_use]
633 pub fn with_no_mining(mut self, no_mining: bool) -> Self {
634 self.no_mining = no_mining;
635 self
636 }
637
638 #[must_use]
640 pub fn with_port(mut self, port: u16) -> Self {
641 self.port = port;
642 self
643 }
644
645 #[must_use]
652 pub fn with_ipc(mut self, ipc_path: Option<Option<String>>) -> Self {
653 self.ipc_path = ipc_path;
654 self
655 }
656
657 #[must_use]
659 pub fn set_config_out(mut self, config_out: Option<PathBuf>) -> Self {
660 self.config_out = config_out;
661 self
662 }
663
664 #[must_use]
666 pub fn with_tracing(mut self, enable_tracing: bool) -> Self {
667 self.enable_tracing = enable_tracing;
668 self
669 }
670
671 #[must_use]
673 pub fn with_auto_impersonate(mut self, enable_auto_impersonate: bool) -> Self {
674 self.enable_auto_impersonate = enable_auto_impersonate;
675 self
676 }
677
678 #[must_use]
679 pub fn with_server_config(mut self, config: ServerConfig) -> Self {
680 self.server_config = config;
681 self
682 }
683
684 #[must_use]
686 pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
687 self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
688 self
689 }
690
691 pub fn get_ipc_path(&self) -> Option<String> {
693 match &self.ipc_path {
694 Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())),
695 None => None,
696 }
697 }
698
699 pub fn print(&self) -> Result<()> {
701 if let Some(path) = &self.config_out {
702 let file = io::BufWriter::new(
703 File::create(path).wrap_err("unable to create anvil config description file")?,
704 );
705 let value = self.as_json();
706 serde_json::to_writer(file, &value).wrap_err("failed writing JSON")?;
707 }
708 if !self.silent {
709 sh_println!("{}", self.as_string())?;
710 }
711 Ok(())
712 }
713
714 #[must_use]
716 pub fn silent(self) -> Self {
717 self.set_silent(true)
718 }
719
720 #[must_use]
721 pub fn set_silent(mut self, silent: bool) -> Self {
722 self.silent = silent;
723 self
724 }
725}
726
727#[derive(Clone, Debug)]
729pub struct AccountGenerator {
730 amount: usize,
731 phrase: String,
732 derivation_path: Option<String>,
733}
734
735impl AccountGenerator {
736 pub fn new(amount: usize) -> Self {
737 Self {
738 amount,
739 phrase: Mnemonic::<English>::new(&mut thread_rng()).to_phrase(),
740 derivation_path: None,
741 }
742 }
743
744 #[must_use]
745 pub fn phrase(mut self, phrase: impl Into<String>) -> Self {
746 self.phrase = phrase.into();
747 self
748 }
749
750 fn get_phrase(&self) -> &str {
751 &self.phrase
752 }
753
754 #[must_use]
755 pub fn derivation_path(mut self, derivation_path: impl Into<String>) -> Self {
756 let mut derivation_path = derivation_path.into();
757 if !derivation_path.ends_with('/') {
758 derivation_path.push('/');
759 }
760 self.derivation_path = Some(derivation_path);
761 self
762 }
763
764 fn get_derivation_path(&self) -> &str {
765 self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/")
766 }
767}
768
769impl AccountGenerator {
770 pub fn rng_gen(&self) -> eyre::Result<Vec<PrivateKeySigner>> {
771 let builder = MnemonicBuilder::<English>::default().phrase(self.phrase.as_str());
772
773 let derivation_path = self.get_derivation_path();
775
776 let mut wallets = Vec::with_capacity(self.amount);
777 for idx in 0..self.amount {
778 let builder =
779 builder.clone().derivation_path(format!("{derivation_path}{idx}")).unwrap();
780 let wallet = builder.build()?;
781 wallets.push(wallet)
782 }
783 Ok(wallets)
784 }
785}
786
787fn keypairs_from_private_keys(
788 accounts: &[PrivateKeySigner],
789) -> Result<Vec<Keypair>, subxt_signer::eth::Error> {
790 accounts
791 .iter()
792 .map(|signer| {
793 let key = Keypair::from_secret_key(signer.credential().to_bytes().into())?;
794 Ok(key)
795 })
796 .collect()
797}