Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
1 change: 1 addition & 0 deletions crates/anvil-polkadot/src/substrate_node/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
69 changes: 66 additions & 3 deletions crates/anvil-polkadot/src/substrate_node/genesis.rs
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<ContractData>,
}

impl GenesisConfig {
pub fn as_storage_key_value(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
let storage = vec![
Expand All @@ -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<ReviveGenesisAccount> = 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<ContractData> = 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::<BTreeMap<_, _>>()
})
.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<Block: BlockT, B, E> {
Expand Down
163 changes: 160 additions & 3 deletions crates/anvil-polkadot/tests/it/genesis.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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::<Bytes>(
node.eth_rpc(EthRequest::EthGetCodeAt(
Address::from(test_eoa_bytes_1),
Some(BlockId::number(0)),
))
.await
.unwrap(),
)
.unwrap();
let eoa_code_2 = unwrap_response::<Bytes>(
node.eth_rpc(EthRequest::EthGetCodeAt(
Address::from(test_eoa_bytes_2),
Some(BlockId::number(0)),
))
.await
.unwrap(),
)
.unwrap();
let contract_code_result = unwrap_response::<Bytes>(
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::<String>(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");
}
Loading