From 101aca10068ab5206e26e42cbb10febca9322372 Mon Sep 17 00:00:00 2001 From: giuseppere Date: Thu, 23 Oct 2025 18:00:22 +0200 Subject: [PATCH 1/4] alloc customization for anvil genesis config --- .../src/substrate_node/chain_spec.rs | 2 +- .../src/substrate_node/genesis.rs | 110 +++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/crates/anvil-polkadot/src/substrate_node/chain_spec.rs b/crates/anvil-polkadot/src/substrate_node/chain_spec.rs index 00f63fbf80e06..1b3fe3214aa90 100644 --- a/crates/anvil-polkadot/src/substrate_node/chain_spec.rs +++ b/crates/anvil-polkadot/src/substrate_node/chain_spec.rs @@ -10,7 +10,6 @@ use polkadot_sdk::{ sp_runtime::BuildStorage, }; use substrate_runtime::WASM_BINARY; - /// This is a wrapper around the general Substrate ChainSpec type that allows manual changes to the /// genesis block. #[derive(Clone)] @@ -119,6 +118,7 @@ pub fn development_chain_spec( .with_id("dev") .with_chain_type(ChainType::Development) .with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET) + .with_genesis_config_patch(genesis_config.runtime_genesis_config_patch()) .with_properties(props()) .build(); Ok(DevelopmentChainSpec { inner, genesis_config }) diff --git a/crates/anvil-polkadot/src/substrate_node/genesis.rs b/crates/anvil-polkadot/src/substrate_node/genesis.rs index 18682cd440efc..bf4ba2cc86e23 100644 --- a/crates/anvil-polkadot/src/substrate_node/genesis.rs +++ b/crates/anvil-polkadot/src/substrate_node/genesis.rs @@ -1,21 +1,30 @@ //! Genesis settings -use crate::{config::AnvilNodeConfig, substrate_node::service::storage::well_known_keys}; +use crate::{ + api_server::revive_conversions::{ReviveAddress, SubstrateU256}, + config::AnvilNodeConfig, + substrate_node::service::storage::well_known_keys, +}; use alloy_genesis::GenesisAccount; -use alloy_primitives::Address; +use alloy_primitives::{Address, U256}; use codec::Encode; use polkadot_sdk::{ + pallet_revive::genesis::ContractData, + parachains_common::AccountId, sc_chain_spec::{BuildGenesisBlock, resolve_state_version_from_wasm}, sc_client_api::{BlockImportOperation, backend::Backend}, sc_executor::RuntimeVersionOf, sp_blockchain, - sp_core::storage::Storage, + sp_core::{H160, storage::Storage}, sp_runtime::{ BuildStorage, traits::{Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT}, }, }; +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; use std::{collections::BTreeMap, marker::PhantomData, sync::Arc}; +use substrate_runtime::Balance; /// Genesis settings #[derive(Clone, Debug, Default)] @@ -54,6 +63,28 @@ impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig { } } +/// Converts H160 address to AccountId32 by padding with 0xee bytes +/// This replicates the logic from AccountId32Mapper::to_account_id +fn revive_address_to_account_id(h160: H160) -> AccountId { + let h160_bytes = h160.as_fixed_bytes(); + let mut account_id_bytes = [0u8; 32]; + account_id_bytes[..20].copy_from_slice(h160_bytes); + account_id_bytes[20..].fill(0xee); + AccountId::from(account_id_bytes) +} + +// Used to provide genesis accounts to pallet-revive +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ReviveGenesisAccount { + pub address: H160, + #[serde(default)] + pub balance: U256, + #[serde(default)] + pub nonce: u64, + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub contract_data: Option, +} + impl GenesisConfig { pub fn as_storage_key_value(&self) -> Vec<(Vec, Vec)> { let storage = vec![ @@ -64,6 +95,79 @@ impl GenesisConfig { // TODO: add other fields storage } + + pub fn runtime_genesis_config_patch(&self) -> Value { + let accounts_with_balances: Vec<(AccountId, Balance)> = self + .alloc + .clone() + .unwrap_or_default() + .iter() + .map(|(address, account)| { + let revive_address = ReviveAddress::from(*address); + let account_id = revive_address_to_account_id(revive_address.inner()); + // Manual balance conversion following polkadot-sdk logic + let balance = { + let balance_u256 = SubstrateU256::from(account.balance).inner(); + let ed = substrate_runtime::ExistentialDeposit::get(); + + // Try to convert U256 to u128, following the same logic as BalanceWithDust + if let Ok(balance_u128) = balance_u256.try_into() { + // Add existential deposit to the balance + ed.saturating_add(balance_u128) + } else { + // If U256 is too large for u128, use u128::MAX as fallback + u128::MAX + } + }; + (account_id, balance) + }) + .collect(); + // Relies on ReviveGenesisAccount type from pallet-revive + let revive_genesis_accounts: Vec = self + .alloc + .clone() + .unwrap_or_default() + .iter() + .map(|(address, account)| { + let genesis_address: H160 = ReviveAddress::from(*address).inner(); + let genesis_balance: U256 = account.balance; + let genesis_nonce: u64 = account.nonce.unwrap_or_default(); + let contract_data: Option = if account.code.is_some() { + Some(ContractData { + code: account.code.clone().map(|code| code.to_vec()).unwrap_or_default(), + storage: account + .storage + .clone() + .map(|storage| { + storage + .into_iter() + .map(|(k, v)| (k.0.into(), v.0.into())) + .collect::>() + }) + .unwrap_or_default(), + }) + } else { + None + }; + + ReviveGenesisAccount { + address: genesis_address, + balance: genesis_balance, + nonce: genesis_nonce, + contract_data, + } + }) + .collect(); + + json!({ + "balances": { + "balances": accounts_with_balances, + }, + "revive": { + "accounts": revive_genesis_accounts, + }, + }) + } } pub struct DevelopmentGenesisBlockBuilder { From 8486d06dd783cdcbbfedca6cb9273fb0dd30f513 Mon Sep 17 00:00:00 2001 From: giuseppere Date: Thu, 23 Oct 2025 18:32:01 +0200 Subject: [PATCH 2/4] CR nits --- crates/anvil-polkadot/src/substrate_node/chain_spec.rs | 1 + crates/anvil-polkadot/src/substrate_node/genesis.rs | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/anvil-polkadot/src/substrate_node/chain_spec.rs b/crates/anvil-polkadot/src/substrate_node/chain_spec.rs index 1b3fe3214aa90..4f80fd9340bf1 100644 --- a/crates/anvil-polkadot/src/substrate_node/chain_spec.rs +++ b/crates/anvil-polkadot/src/substrate_node/chain_spec.rs @@ -10,6 +10,7 @@ use polkadot_sdk::{ sp_runtime::BuildStorage, }; use substrate_runtime::WASM_BINARY; + /// This is a wrapper around the general Substrate ChainSpec type that allows manual changes to the /// genesis block. #[derive(Clone)] diff --git a/crates/anvil-polkadot/src/substrate_node/genesis.rs b/crates/anvil-polkadot/src/substrate_node/genesis.rs index bf4ba2cc86e23..b47c312535e9b 100644 --- a/crates/anvil-polkadot/src/substrate_node/genesis.rs +++ b/crates/anvil-polkadot/src/substrate_node/genesis.rs @@ -63,13 +63,14 @@ impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig { } } -/// Converts H160 address to AccountId32 by padding with 0xee bytes -/// This replicates the logic from AccountId32Mapper::to_account_id +/// Converts H160 Eth address to AccountId32 by padding with 0xEE bytes +/// This should only be used for genesis as it replicates the logic from +/// AccountId32Mapper::to_account_id. Once the node is up and running, use the +/// endpoints exposed through pallet-revive for this purpose. fn revive_address_to_account_id(h160: H160) -> AccountId { let h160_bytes = h160.as_fixed_bytes(); - let mut account_id_bytes = [0u8; 32]; + let mut account_id_bytes = [0xEE; 32]; account_id_bytes[..20].copy_from_slice(h160_bytes); - account_id_bytes[20..].fill(0xee); AccountId::from(account_id_bytes) } From e3e5ec8a7432c38911ad6b375a30206394455958 Mon Sep 17 00:00:00 2001 From: giuseppere Date: Fri, 24 Oct 2025 16:02:04 +0200 Subject: [PATCH 3/4] fix alloc + add tests --- .../src/substrate_node/genesis.rs | 44 +---- crates/anvil-polkadot/tests/it/genesis.rs | 163 +++++++++++++++++- 2 files changed, 161 insertions(+), 46 deletions(-) diff --git a/crates/anvil-polkadot/src/substrate_node/genesis.rs b/crates/anvil-polkadot/src/substrate_node/genesis.rs index b47c312535e9b..a79035d845d1f 100644 --- a/crates/anvil-polkadot/src/substrate_node/genesis.rs +++ b/crates/anvil-polkadot/src/substrate_node/genesis.rs @@ -1,8 +1,7 @@ //! Genesis settings use crate::{ - api_server::revive_conversions::{ReviveAddress, SubstrateU256}, - config::AnvilNodeConfig, + api_server::revive_conversions::ReviveAddress, config::AnvilNodeConfig, substrate_node::service::storage::well_known_keys, }; use alloy_genesis::GenesisAccount; @@ -10,7 +9,6 @@ use alloy_primitives::{Address, U256}; use codec::Encode; use polkadot_sdk::{ pallet_revive::genesis::ContractData, - parachains_common::AccountId, sc_chain_spec::{BuildGenesisBlock, resolve_state_version_from_wasm}, sc_client_api::{BlockImportOperation, backend::Backend}, sc_executor::RuntimeVersionOf, @@ -24,7 +22,6 @@ use polkadot_sdk::{ use serde::{Deserialize, Serialize}; use serde_json::{Value, json}; use std::{collections::BTreeMap, marker::PhantomData, sync::Arc}; -use substrate_runtime::Balance; /// Genesis settings #[derive(Clone, Debug, Default)] @@ -63,17 +60,6 @@ impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig { } } -/// Converts H160 Eth address to AccountId32 by padding with 0xEE bytes -/// This should only be used for genesis as it replicates the logic from -/// AccountId32Mapper::to_account_id. Once the node is up and running, use the -/// endpoints exposed through pallet-revive for this purpose. -fn revive_address_to_account_id(h160: H160) -> AccountId { - let h160_bytes = h160.as_fixed_bytes(); - let mut account_id_bytes = [0xEE; 32]; - account_id_bytes[..20].copy_from_slice(h160_bytes); - AccountId::from(account_id_bytes) -} - // Used to provide genesis accounts to pallet-revive #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ReviveGenesisAccount { @@ -98,31 +84,6 @@ impl GenesisConfig { } pub fn runtime_genesis_config_patch(&self) -> Value { - let accounts_with_balances: Vec<(AccountId, Balance)> = self - .alloc - .clone() - .unwrap_or_default() - .iter() - .map(|(address, account)| { - let revive_address = ReviveAddress::from(*address); - let account_id = revive_address_to_account_id(revive_address.inner()); - // Manual balance conversion following polkadot-sdk logic - let balance = { - let balance_u256 = SubstrateU256::from(account.balance).inner(); - let ed = substrate_runtime::ExistentialDeposit::get(); - - // Try to convert U256 to u128, following the same logic as BalanceWithDust - if let Ok(balance_u128) = balance_u256.try_into() { - // Add existential deposit to the balance - ed.saturating_add(balance_u128) - } else { - // If U256 is too large for u128, use u128::MAX as fallback - u128::MAX - } - }; - (account_id, balance) - }) - .collect(); // Relies on ReviveGenesisAccount type from pallet-revive let revive_genesis_accounts: Vec = self .alloc @@ -161,9 +122,6 @@ impl GenesisConfig { .collect(); json!({ - "balances": { - "balances": accounts_with_balances, - }, "revive": { "accounts": revive_genesis_accounts, }, diff --git a/crates/anvil-polkadot/tests/it/genesis.rs b/crates/anvil-polkadot/tests/it/genesis.rs index 90ee9091c63df..9e0900635f778 100644 --- a/crates/anvil-polkadot/tests/it/genesis.rs +++ b/crates/anvil-polkadot/tests/it/genesis.rs @@ -1,10 +1,16 @@ -use crate::utils::{TestNode, assert_with_tolerance, to_hex_string, unwrap_response}; -use alloy_primitives::U256; +use crate::utils::{ + TestNode, assert_with_tolerance, get_contract_code, to_hex_string, unwrap_response, +}; +use alloy_genesis::GenesisAccount; +use alloy_primitives::{Address, B256, Bytes, U256}; +use alloy_rpc_types::BlockId; use anvil_core::eth::EthRequest; use anvil_polkadot::config::{AnvilNodeConfig, SubstrateNodeConfig}; +use std::collections::BTreeMap; +use subxt::utils::H160; #[tokio::test(flavor = "multi_thread")] -async fn test_genesis() { +async fn test_genesis_params() { let genesis_block_number: u32 = 1000; let anvil_genesis_timestamp: u64 = 42; let chain_id: u64 = 4242; @@ -43,3 +49,154 @@ async fn test_genesis() { "Timestamp is not increasing as expected from genesis.", ); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_genesis_alloc() { + // Create test EOA addresses + let test_eoa_bytes_1 = [0x01; 20]; + let test_eoa_bytes_2 = [0x02; 20]; + let test_eoa_1 = H160::from_slice(&test_eoa_bytes_1); + let test_eoa_2 = H160::from_slice(&test_eoa_bytes_2); + let test_eoa_balance_1: u128 = 1_000_000_000_000_000_000; // 1 DOT + let test_eoa_balance_2: u128 = 2_000_000_000_000_000_000; // 2 DOT + + // Create test contract address + let test_contract_bytes = [0x03; 20]; + let test_contract_address = H160::from_slice(&test_contract_bytes); + let test_contract_balance: u128 = 5_000_000_000_000_000_000; // 5 DOT + + // Get the SimpleStorage contract code + let contract_code = get_contract_code("SimpleStorage"); + let runtime_bytecode = contract_code.runtime.clone().unwrap(); + + // Set up initial storage for the contract + let mut genesis_storage = BTreeMap::new(); + // Set storage slot 0 to value 511 (as in state_injector tests) + genesis_storage.insert(B256::from(U256::from(0)), B256::from(U256::from(511))); + + // Create genesis alloc with both EOA and contract accounts + let mut alloc = BTreeMap::new(); + + // Add EOA accounts + alloc.insert( + Address::from(test_eoa_bytes_1), + GenesisAccount { + balance: U256::from(test_eoa_balance_1), + nonce: None, + code: None, + storage: None, + private_key: None, + }, + ); + alloc.insert( + Address::from(test_eoa_bytes_2), + GenesisAccount { + balance: U256::from(test_eoa_balance_2), + nonce: Some(10), + code: None, + storage: None, + private_key: None, + }, + ); + + // Add contract account + alloc.insert( + Address::from(test_contract_bytes), + GenesisAccount { + balance: U256::from(test_contract_balance), + nonce: Some(1), // Contract accounts typically start with nonce 1 + code: Some(Bytes::from(runtime_bytecode.clone())), + storage: Some(genesis_storage), + private_key: None, + }, + ); + + let genesis = alloy_genesis::Genesis { alloc, ..Default::default() }; + + // Create anvil node config with custom genesis + let anvil_node_config = AnvilNodeConfig::test_config().with_genesis(Some(genesis)); + let substrate_node_config = SubstrateNodeConfig::new(&anvil_node_config); + let mut node = TestNode::new(anvil_node_config, substrate_node_config).await.unwrap(); + + // Test all account balances + let eoa_balance_1 = node.get_balance(test_eoa_1, None).await; + let eoa_balance_2 = node.get_balance(test_eoa_2, None).await; + let contract_balance = node.get_balance(test_contract_address, None).await; + + assert_eq!( + eoa_balance_1, + U256::from(test_eoa_balance_1), + "First EOA should have correct balance" + ); + assert_eq!( + eoa_balance_2, + U256::from(test_eoa_balance_2), + "Second EOA should have correct balance" + ); + assert_eq!( + contract_balance, + U256::from(test_contract_balance), + "Genesis contract account should have correct balance" + ); + + // Test all account nonces + let eoa_nonce_1 = node.get_nonce(Address::from(test_eoa_bytes_1)).await; + let eoa_nonce_2 = node.get_nonce(Address::from(test_eoa_bytes_2)).await; + let contract_nonce = node.get_nonce(Address::from(test_contract_bytes)).await; + + assert_eq!(eoa_nonce_1, U256::from(0), "First EOA should have nonce 0"); + assert_eq!(eoa_nonce_2, U256::from(10), "Second EOA should have nonce 10"); + assert_eq!(contract_nonce, U256::from(1), "Genesis contract should have nonce 1"); + + // Test all account code (EOA should be empty, contract should have code) + let eoa_code_1 = unwrap_response::( + node.eth_rpc(EthRequest::EthGetCodeAt( + Address::from(test_eoa_bytes_1), + Some(BlockId::number(0)), + )) + .await + .unwrap(), + ) + .unwrap(); + let eoa_code_2 = unwrap_response::( + node.eth_rpc(EthRequest::EthGetCodeAt( + Address::from(test_eoa_bytes_2), + Some(BlockId::number(0)), + )) + .await + .unwrap(), + ) + .unwrap(); + let contract_code_result = unwrap_response::( + node.eth_rpc(EthRequest::EthGetCodeAt( + Address::from(test_contract_bytes), + Some(BlockId::number(0)), + )) + .await + .unwrap(), + ) + .unwrap(); + + assert!(eoa_code_1.is_empty(), "First EOA should have no code"); + assert!(eoa_code_2.is_empty(), "Second EOA should have no code"); + assert!(!contract_code_result.is_empty(), "Genesis contract should have code"); + assert_eq!( + contract_code_result.len(), + runtime_bytecode.len(), + "Genesis contract code length should match" + ); + + // Test contract storage + let result = node + .eth_rpc(EthRequest::EthGetStorageAt( + Address::from(test_contract_bytes), + U256::from(0), + None, + )) + .await + .unwrap(); + let hex_string = unwrap_response::(result).unwrap(); + let hex_value = hex_string.strip_prefix("0x").unwrap_or(&hex_string); + let stored_value = U256::from_str_radix(hex_value, 16).unwrap(); + assert_eq!(stored_value, 511, "Storage slot 0 of genesis contract should contain value 511"); +} From b1435832e7c74f74fea2bcdaaa4ebef2bcadda16 Mon Sep 17 00:00:00 2001 From: giuseppere Date: Mon, 27 Oct 2025 13:28:10 +0100 Subject: [PATCH 4/4] CR --- .../src/substrate_node/genesis.rs | 2 +- crates/anvil-polkadot/tests/it/genesis.rs | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/crates/anvil-polkadot/src/substrate_node/genesis.rs b/crates/anvil-polkadot/src/substrate_node/genesis.rs index a79035d845d1f..ed64d8602aa11 100644 --- a/crates/anvil-polkadot/src/substrate_node/genesis.rs +++ b/crates/anvil-polkadot/src/substrate_node/genesis.rs @@ -60,7 +60,7 @@ impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig { } } -// Used to provide genesis accounts to pallet-revive +/// Used to provide genesis accounts to pallet-revive #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ReviveGenesisAccount { pub address: H160, diff --git a/crates/anvil-polkadot/tests/it/genesis.rs b/crates/anvil-polkadot/tests/it/genesis.rs index 9e0900635f778..7ca7f4f65f9a1 100644 --- a/crates/anvil-polkadot/tests/it/genesis.rs +++ b/crates/anvil-polkadot/tests/it/genesis.rs @@ -1,9 +1,11 @@ -use crate::utils::{ - TestNode, assert_with_tolerance, get_contract_code, to_hex_string, unwrap_response, +use crate::{ + abi::SimpleStorage, + utils::{TestNode, assert_with_tolerance, get_contract_code, to_hex_string, unwrap_response}, }; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, Bytes, U256}; -use alloy_rpc_types::BlockId; +use alloy_rpc_types::{BlockId, TransactionInput, TransactionRequest}; +use alloy_sol_types::SolCall; use anvil_core::eth::EthRequest; use anvil_polkadot::config::{AnvilNodeConfig, SubstrateNodeConfig}; use std::collections::BTreeMap; @@ -185,6 +187,7 @@ async fn test_genesis_alloc() { runtime_bytecode.len(), "Genesis contract code length should match" ); + assert_eq!(contract_code_result, runtime_bytecode, "Genesis contract code should match"); // Test contract storage let result = node @@ -199,4 +202,18 @@ async fn test_genesis_alloc() { let hex_value = hex_string.strip_prefix("0x").unwrap_or(&hex_string); let stored_value = U256::from_str_radix(hex_value, 16).unwrap(); assert_eq!(stored_value, 511, "Storage slot 0 of genesis contract should contain value 511"); + + // Test contract functionality by calling getValue() + let tx = TransactionRequest::default() + .from(Address::from(test_eoa_bytes_1)) + .to(Address::from(test_contract_bytes)) + .input(TransactionInput::both(SimpleStorage::getValueCall.abi_encode().into())); + + let value = unwrap_response::( + node.eth_rpc(EthRequest::EthCall(tx.into(), None, None, None)).await.unwrap(), + ) + .unwrap(); + + let value = SimpleStorage::getValueCall::abi_decode_returns(&value.0).unwrap(); + assert_eq!(value, U256::from(511), "Contract getValue() should return 511"); }