Skip to content
Merged
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
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
180 changes: 177 additions & 3 deletions crates/anvil-polkadot/tests/it/genesis.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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::<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"
);
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::<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");

// 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::<Bytes>(
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");
}
Loading