diff --git a/crates/anvil-polkadot/src/substrate_node/chain_spec.rs b/crates/anvil-polkadot/src/substrate_node/chain_spec.rs index 00f63fbf80e06..4f80fd9340bf1 100644 --- a/crates/anvil-polkadot/src/substrate_node/chain_spec.rs +++ b/crates/anvil-polkadot/src/substrate_node/chain_spec.rs @@ -119,6 +119,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..ed64d8602aa11 100644 --- a/crates/anvil-polkadot/src/substrate_node/genesis.rs +++ b/crates/anvil-polkadot/src/substrate_node/genesis.rs @@ -1,20 +1,26 @@ //! Genesis settings -use crate::{config::AnvilNodeConfig, substrate_node::service::storage::well_known_keys}; +use crate::{ + api_server::revive_conversions::ReviveAddress, 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, 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}; /// Genesis settings @@ -54,6 +60,18 @@ impl<'a> From<&'a AnvilNodeConfig> for GenesisConfig { } } +/// 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 +82,51 @@ impl GenesisConfig { // TODO: add other fields storage } + + pub fn runtime_genesis_config_patch(&self) -> Value { + // 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!({ + "revive": { + "accounts": revive_genesis_accounts, + }, + }) + } } pub struct DevelopmentGenesisBlockBuilder { diff --git a/crates/anvil-polkadot/tests/it/genesis.rs b/crates/anvil-polkadot/tests/it/genesis.rs index 90ee9091c63df..7ca7f4f65f9a1 100644 --- a/crates/anvil-polkadot/tests/it/genesis.rs +++ b/crates/anvil-polkadot/tests/it/genesis.rs @@ -1,10 +1,18 @@ -use crate::utils::{TestNode, assert_with_tolerance, to_hex_string, unwrap_response}; -use alloy_primitives::U256; +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, TransactionInput, TransactionRequest}; +use alloy_sol_types::SolCall; 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 +51,169 @@ 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" + ); + assert_eq!(contract_code_result, runtime_bytecode, "Genesis contract code 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"); + + // 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"); +}