diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs index f2f1d859699a5..6d159013cf8ca 100644 --- a/polkadot/xcm/pallet-xcm/src/mock.rs +++ b/polkadot/xcm/pallet-xcm/src/mock.rs @@ -769,7 +769,7 @@ pub(crate) fn new_test_ext_with_balances_and_xcm_version( .assimilate_storage(&mut t) .unwrap(); - pallet_revive::GenesisConfig:: { mapped_accounts: vec![ALICE] } + pallet_revive::GenesisConfig:: { mapped_accounts: vec![ALICE], ..Default::default() } .assimilate_storage(&mut t) .unwrap(); diff --git a/prdoc/pr_9557.prdoc b/prdoc/pr_9557.prdoc new file mode 100644 index 0000000000000..d1fc00ed84d2b --- /dev/null +++ b/prdoc/pr_9557.prdoc @@ -0,0 +1,9 @@ +title: '[pallet-revive] Update genesis config' +doc: +- audience: Runtime Dev + description: |- + Update pallet-revive Genesis config + Make it possible to define accounts (contracts or EOA) that we want to setup at Genesis +crates: +- name: pallet-revive + bump: minor diff --git a/substrate/bin/node/cli/tests/res/default_genesis_config.json b/substrate/bin/node/cli/tests/res/default_genesis_config.json index 3ad6163cd4e6d..38e735501507a 100644 --- a/substrate/bin/node/cli/tests/res/default_genesis_config.json +++ b/substrate/bin/node/cli/tests/res/default_genesis_config.json @@ -40,7 +40,6 @@ }, "revive": { - "mappedAccounts": [] }, "democracy": {}, "council": { diff --git a/substrate/frame/revive/src/address.rs b/substrate/frame/revive/src/address.rs index fcba05c99771f..a6e8e94953887 100644 --- a/substrate/frame/revive/src/address.rs +++ b/substrate/frame/revive/src/address.rs @@ -62,8 +62,9 @@ pub trait AddressMapper: private::Sealed { /// `account_id` instead of the fallback account id. fn map(account_id: &T::AccountId) -> DispatchResult; - #[cfg(feature = "runtime-benchmarks")] - fn bench_map(account_id: &T::AccountId) -> DispatchResult { + /// Map an account id without taking any deposit. + /// This is only useful for genesis configuration, or benchmarks. + fn map_no_deposit(account_id: &T::AccountId) -> DispatchResult { Self::map(account_id) } @@ -145,9 +146,7 @@ where Ok(()) } - /// Convenience function for benchmarking, to map an account id without taking any deposit. - #[cfg(feature = "runtime-benchmarks")] - fn bench_map(account_id: &T::AccountId) -> DispatchResult { + fn map_no_deposit(account_id: &T::AccountId) -> DispatchResult { ensure!(!Self::is_mapped(account_id), >::AccountAlreadyMapped); >::insert(Self::to_address(account_id), account_id); Ok(()) diff --git a/substrate/frame/revive/src/benchmarking.rs b/substrate/frame/revive/src/benchmarking.rs index d07dd0c460432..3650bfb266bfe 100644 --- a/substrate/frame/revive/src/benchmarking.rs +++ b/substrate/frame/revive/src/benchmarking.rs @@ -781,7 +781,7 @@ mod benchmarks { fn seal_balance_of() { let len = ::max_encoded_len(); let account = account::("target", 0, 0); - ::AddressMapper::bench_map(&account).unwrap(); + ::AddressMapper::map_no_deposit(&account).unwrap(); let address = T::AddressMapper::to_address(&account); let balance = Pallet::::min_balance() * 2u32.into(); diff --git a/substrate/frame/revive/src/evm/api/byte.rs b/substrate/frame/revive/src/evm/api/byte.rs index 21a7b6fb42463..57fa07e17d985 100644 --- a/substrate/frame/revive/src/evm/api/byte.rs +++ b/substrate/frame/revive/src/evm/api/byte.rs @@ -80,6 +80,7 @@ impl Bytes { impl_hex!(Byte, u8, 0u8); impl_hex!(Bytes, Vec, vec![]); impl_hex!(Bytes8, [u8; 8], [0u8; 8]); +impl_hex!(Bytes32, [u8; 32], [0u8; 32]); impl_hex!(Bytes256, [u8; 256], [0u8; 256]); #[test] diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs index 57d9e3699afc9..7266d22625ba6 100644 --- a/substrate/frame/revive/src/lib.rs +++ b/substrate/frame/revive/src/lib.rs @@ -88,7 +88,7 @@ pub use crate::{ create1, create2, is_eth_derived, AccountId32Mapper, AddressMapper, TestAccountMapper, }, exec::{Key, MomentOf, Origin}, - pallet::*, + pallet::{genesis, *}, storage::{AccountInfo, ContractInfo}, }; pub use codec; @@ -534,16 +534,58 @@ pub mod pallet { #[pallet::storage] pub(crate) type OriginalAccount = StorageMap<_, Identity, H160, AccountId32>; + pub mod genesis { + use super::*; + use crate::evm::Bytes32; + + /// Genesis configuration for contract-specific data. + #[derive(Clone, PartialEq, Debug, Default, serde::Serialize, serde::Deserialize)] + pub struct ContractData { + /// Contract code. + pub code: Vec, + /// Initial storage entries as 32-byte key/value pairs. + pub storage: alloc::collections::BTreeMap, + } + + /// Genesis configuration for a contract account. + #[derive(PartialEq, Default, Debug, Clone, serde::Serialize, serde::Deserialize)] + pub struct Account { + /// Contract address. + pub address: H160, + /// Contract balance. + #[serde(default)] + pub balance: U256, + /// Account nonce + #[serde(default)] + pub nonce: T::Nonce, + /// Contract-specific data (code and storage). None for EOAs. + #[serde(flatten, skip_serializing_if = "Option::is_none")] + pub contract_data: Option, + } + } + #[pallet::genesis_config] - #[derive(frame_support::DefaultNoBound)] + #[derive(Debug, PartialEq, frame_support::DefaultNoBound)] pub struct GenesisConfig { - /// Genesis mapped accounts + /// List of native Substrate accounts (typically `AccountId32`) to be mapped at genesis + /// block, enabling them to interact with smart contracts. + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub mapped_accounts: Vec, + + /// Account entries (both EOAs and contracts) + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub accounts: Vec>, } #[pallet::genesis_build] - impl BuildGenesisConfig for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig + where + BalanceOf: Into + TryFrom, + { fn build(&self) { + use crate::{exec::Key, vm::ContractBlob}; + use frame_support::traits::fungible::Mutate; + if !System::::account_exists(&Pallet::::account_id()) { let _ = T::Currency::mint_into( &Pallet::::account_id(), @@ -552,10 +594,75 @@ pub mod pallet { } for id in &self.mapped_accounts { - if let Err(err) = T::AddressMapper::map(id) { + if let Err(err) = T::AddressMapper::map_no_deposit(id) { log::error!(target: LOG_TARGET, "Failed to map account {id:?}: {err:?}"); } } + + let owner = Pallet::::account_id(); + + for genesis::Account { address, balance, nonce, contract_data } in &self.accounts { + let Ok(balance_with_dust) = + BalanceWithDust::>::from_value::(*balance).inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to convert balance for {address:?}: {err:?}"); + }) + else { + continue; + }; + let account_id = T::AddressMapper::to_account_id(address); + let (value, dust) = balance_with_dust.deconstruct(); + + let _ = T::Currency::set_balance(&account_id, value); + frame_system::Account::::mutate(&account_id, |info| { + info.nonce = (*nonce).into(); + }); + + match contract_data { + None => { + AccountInfoOf::::insert( + address, + AccountInfo { account_type: AccountType::EOA, dust }, + ); + }, + Some(genesis::ContractData { code, storage }) => { + let blob = if code.starts_with(&polkavm_common::program::BLOB_MAGIC) { + ContractBlob::::from_pvm_code( code.clone(), owner.clone()).inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to create PVM ContractBlob for {address:?}: {err:?}"); + }) + } else { + ContractBlob::::from_evm_runtime_code(code.clone(), account_id).inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to create EVM ContractBlob for {address:?}: {err:?}"); + }) + }; + + let Ok(blob) = blob else { + continue; + }; + + let code_hash = *blob.code_hash(); + let Ok(info) = >::new(&address, 0u32.into(), code_hash) + .inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to create ContractInfo for {address:?}: {err:?}"); + }) + else { + continue; + }; + + AccountInfoOf::::insert( + address, + AccountInfo { account_type: info.clone().into(), dust }, + ); + + >::insert(blob.code_hash(), code); + >::insert(blob.code_hash(), blob.code_info().clone()); + for (k, v) in storage { + let _ = info.write(&Key::from_fixed(k.0), Some(v.0.to_vec()), None, false).inspect_err(|err| { + log::error!(target: LOG_TARGET, "Failed to write genesis storage for {address:?} at key {k:?}: {err:?}"); + }); + } + }, + } + } } } diff --git a/substrate/frame/revive/src/tests.rs b/substrate/frame/revive/src/tests.rs index 2a3e3e2180692..a1ed36240577e 100644 --- a/substrate/frame/revive/src/tests.rs +++ b/substrate/frame/revive/src/tests.rs @@ -21,8 +21,11 @@ mod pvm; mod sol; use crate::{ - self as pallet_revive, test_utils::*, AccountId32Mapper, BalanceOf, BalanceWithDust, - CodeInfoOf, Config, Origin, Pallet, + self as pallet_revive, + genesis::{Account, ContractData}, + test_utils::*, + AccountId32Mapper, AddressMapper, BalanceOf, BalanceWithDust, CodeInfoOf, Config, + GenesisConfig, Origin, Pallet, PristineCode, }; use frame_support::{ assert_ok, derive_impl, @@ -31,7 +34,9 @@ use frame_support::{ traits::{ConstU32, ConstU64, FindAuthor, StorageVersion}, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight}, }; +use pallet_revive_fixtures::compile_module; use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; +use sp_core::U256; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ traits::{BlakeTwo256, Convert, IdentityLookup, One}, @@ -450,3 +455,65 @@ impl Default for Origin { Self::Signed(ALICE) } } + +#[test] +fn ext_builder_with_genesis_config_works() { + let pvm_contract = Account { + address: BOB_ADDR, + balance: U256::from(100), + nonce: 42, + contract_data: Some(ContractData { + code: compile_module("dummy").unwrap().0, + storage: [([1u8; 32].into(), [2u8; 32].into())].into_iter().collect(), + }), + }; + + let evm_contract = Account { + address: CHARLIE_ADDR, + balance: U256::from(100), + nonce: 43, + contract_data: Some(ContractData { + code: vec![revm::bytecode::opcode::RETURN], + storage: [([3u8; 32].into(), [4u8; 32].into())].into_iter().collect(), + }), + }; + + let eoa = + Account { address: ALICE_ADDR, balance: U256::from(100), nonce: 44, contract_data: None }; + + let config = GenesisConfig:: { + mapped_accounts: vec![EVE], + accounts: vec![eoa.clone(), pvm_contract.clone(), evm_contract.clone()], + }; + + // Genesis serialization works + let json = serde_json::to_string(&config).unwrap(); + assert_eq!(config, serde_json::from_str::>(&json).unwrap()); + + ExtBuilder::default().genesis_config(Some(config)).build().execute_with(|| { + // account is mapped + assert!(::AddressMapper::is_mapped(&EVE)); + + // EOA is created + assert_eq!(Pallet::::evm_balance(&eoa.address), eoa.balance); + + // Contract is created + for contract in [pvm_contract, evm_contract] { + let contract_data = contract.contract_data.unwrap(); + let contract_info = test_utils::get_contract(&contract.address); + assert_eq!( + PristineCode::::get(&contract_info.code_hash).unwrap(), + contract_data.code + ); + assert_eq!(Pallet::::evm_nonce(&contract.address), contract.nonce); + assert_eq!(Pallet::::evm_balance(&contract.address), contract.balance); + + for (key, value) in contract_data.storage.iter() { + assert_eq!( + Pallet::::get_storage(contract.address, key.0), + Ok(Some(value.0.to_vec())) + ); + } + } + }); +}