pallet_domains/
domain_registry.rs

1//! Domain registry for domains
2
3#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6use crate::block_tree::import_genesis_receipt;
7use crate::pallet::DomainStakingSummary;
8use crate::staking::StakingSummary;
9use crate::{
10    BalanceOf, Config, DomainHashingFor, DomainRegistry, DomainSudoCalls, EvmChainIds,
11    ExecutionReceiptOf, HoldIdentifier, NextDomainId, RuntimeRegistry, into_complete_raw_genesis,
12};
13#[cfg(not(feature = "std"))]
14use alloc::string::String;
15#[cfg(not(feature = "std"))]
16use alloc::vec::Vec;
17use domain_runtime_primitives::MultiAccountId;
18use frame_support::traits::fungible::{Inspect, Mutate, MutateHold};
19use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
20use frame_support::weights::Weight;
21use frame_support::{PalletError, ensure};
22use frame_system::pallet_prelude::*;
23use parity_scale_codec::{Decode, Encode};
24use scale_info::TypeInfo;
25use sp_core::Get;
26use sp_domains::{
27    DomainBundleLimit, DomainId, DomainRuntimeInfo, DomainSudoCall, DomainsDigestItem,
28    DomainsTransfersTracker, OnDomainInstantiated, OperatorAllowList, RuntimeId, RuntimeType,
29    calculate_max_bundle_weight_and_size, derive_domain_block_hash,
30};
31use sp_runtime::DigestItem;
32use sp_runtime::traits::{CheckedAdd, Zero};
33use sp_std::collections::btree_map::BTreeMap;
34use sp_std::collections::btree_set::BTreeSet;
35
36/// Domain registry specific errors
37#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
38pub enum Error {
39    ExceedMaxDomainBlockWeight,
40    ExceedMaxDomainBlockSize,
41    MaxDomainId,
42    InvalidEVMChainId,
43    InvalidSlotProbability,
44    RuntimeNotFound,
45    InsufficientFund,
46    DomainNameTooLong,
47    BalanceFreeze,
48    FailedToGenerateGenesisStateRoot,
49    DomainNotFound,
50    NotDomainOwner,
51    InitialBalanceOverflow,
52    TransfersTracker,
53    MinInitialAccountBalance,
54    MaxInitialDomainAccounts,
55    DuplicateInitialAccounts,
56    FailedToGenerateRawGenesis(crate::runtime_registry::Error),
57    BundleLimitCalculationOverflow,
58    InvalidConfigForRuntimeType,
59}
60
61#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
62pub struct DomainConfig<AccountId: Ord, Balance> {
63    /// A user defined name for this domain, should be a human-readable UTF-8 encoded string.
64    pub domain_name: String,
65    /// A pointer to the `RuntimeRegistry` entry for this domain.
66    pub runtime_id: RuntimeId,
67    /// The max bundle size for this domain, may not exceed the system-wide `MaxDomainBlockSize` limit.
68    pub max_bundle_size: u32,
69    /// The max bundle weight for this domain, may not exceed the system-wide `MaxDomainBlockWeight` limit.
70    pub max_bundle_weight: Weight,
71    /// The probability of successful bundle in a slot (active slots coefficient). This defines the
72    /// expected bundle production rate, must be `> 0` and `≤ 1`.
73    pub bundle_slot_probability: (u64, u64),
74    /// Accounts allowed to operate on this domain.
75    pub operator_allow_list: OperatorAllowList<AccountId>,
76    // Initial balances for this domain.
77    pub initial_balances: Vec<(MultiAccountId, Balance)>,
78}
79
80/// Parameters of the `instantiate_domain` call, it is similar to `DomainConfig` except the `max_bundle_size/weight`
81/// is replaced by a optional `maybe_bundle_limit`.
82///
83/// It is used to derive `DomainConfig`, and if `maybe_bundle_limit` is `None` a default `max_bundle_size/weight`
84/// derived from the `bundle_slot_probability` and other system-wide parameters will be used.
85#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
86pub struct DomainConfigParams<AccountId: Ord, Balance> {
87    pub domain_name: String,
88    pub runtime_id: RuntimeId,
89    pub maybe_bundle_limit: Option<DomainBundleLimit>,
90    pub bundle_slot_probability: (u64, u64),
91    pub operator_allow_list: OperatorAllowList<AccountId>,
92    pub initial_balances: Vec<(MultiAccountId, Balance)>,
93    /// Configurations for a specific type of domain runtime, for example, EVM.
94    /// Currently these are all copied into `DomainObject.domain_runtime_info`, so they don't need
95    /// to be in `DomainConfig`.
96    pub domain_runtime_info: DomainRuntimeInfo,
97}
98
99pub fn into_domain_config<T: Config>(
100    domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
101) -> Result<DomainConfig<T::AccountId, BalanceOf<T>>, Error> {
102    let DomainConfigParams {
103        domain_name,
104        runtime_id,
105        maybe_bundle_limit,
106        bundle_slot_probability,
107        operator_allow_list,
108        initial_balances,
109        domain_runtime_info: _,
110    } = domain_config_params;
111
112    let DomainBundleLimit {
113        max_bundle_size,
114        max_bundle_weight,
115    } = match maybe_bundle_limit {
116        Some(b) => b,
117        None => calculate_max_bundle_weight_and_size(
118            T::MaxDomainBlockSize::get(),
119            T::MaxDomainBlockWeight::get(),
120            T::ConsensusSlotProbability::get(),
121            bundle_slot_probability,
122        )
123        .ok_or(Error::BundleLimitCalculationOverflow)?,
124    };
125
126    Ok(DomainConfig {
127        domain_name,
128        runtime_id,
129        max_bundle_size,
130        max_bundle_weight,
131        bundle_slot_probability,
132        operator_allow_list,
133        initial_balances,
134    })
135}
136
137impl<AccountId, Balance> DomainConfig<AccountId, Balance>
138where
139    AccountId: Ord,
140    Balance: Zero + CheckedAdd + PartialOrd,
141{
142    pub(crate) fn total_issuance(&self) -> Option<Balance> {
143        self.initial_balances
144            .iter()
145            .try_fold(Balance::zero(), |total, (_, balance)| {
146                total.checked_add(balance)
147            })
148    }
149
150    pub(crate) fn check_initial_balances<T: Config>(&self) -> Result<(), Error>
151    where
152        Balance: From<BalanceOf<T>>,
153    {
154        let accounts: BTreeSet<MultiAccountId> = self
155            .initial_balances
156            .iter()
157            .map(|(acc, _)| acc)
158            .cloned()
159            .collect();
160
161        ensure!(
162            accounts.len() == self.initial_balances.len(),
163            Error::DuplicateInitialAccounts
164        );
165
166        ensure!(
167            self.initial_balances.len() as u32 <= T::MaxInitialDomainAccounts::get(),
168            Error::MaxInitialDomainAccounts
169        );
170
171        for (_, balance) in &self.initial_balances {
172            ensure!(
173                *balance >= T::MinInitialDomainAccountBalance::get().into(),
174                Error::MinInitialAccountBalance
175            );
176        }
177
178        Ok(())
179    }
180}
181
182#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
183pub struct DomainObject<Number, ReceiptHash, AccountId: Ord, Balance> {
184    /// The address of the domain creator, used to validate updating the domain config.
185    pub owner_account_id: AccountId,
186    /// The consensus chain block number when the domain first instantiated.
187    pub created_at: Number,
188    /// The hash of the genesis execution receipt for this domain.
189    pub genesis_receipt_hash: ReceiptHash,
190    /// The domain config.
191    pub domain_config: DomainConfig<AccountId, Balance>,
192    /// Domain runtime specific information.
193    pub domain_runtime_info: DomainRuntimeInfo,
194    /// The amount of balance hold on the domain owner account
195    pub domain_instantiation_deposit: Balance,
196}
197
198pub(crate) fn can_instantiate_domain<T: Config>(
199    owner_account_id: &T::AccountId,
200    domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
201) -> Result<DomainConfig<T::AccountId, BalanceOf<T>>, Error> {
202    // `bundle_slot_probability` must be `> 0` and `≤ 1`
203    let (numerator, denominator) = domain_config_params.bundle_slot_probability;
204    ensure!(
205        numerator != 0 && denominator != 0 && numerator <= denominator,
206        Error::InvalidSlotProbability
207    );
208
209    let domain_config = into_domain_config::<T>(domain_config_params)?;
210
211    ensure!(
212        domain_config.domain_name.len() as u32 <= T::MaxDomainNameLength::get(),
213        Error::DomainNameTooLong,
214    );
215    ensure!(
216        RuntimeRegistry::<T>::contains_key(domain_config.runtime_id),
217        Error::RuntimeNotFound
218    );
219    ensure!(
220        domain_config.max_bundle_size <= T::MaxDomainBlockSize::get(),
221        Error::ExceedMaxDomainBlockSize
222    );
223    ensure!(
224        domain_config
225            .max_bundle_weight
226            .all_lte(T::MaxDomainBlockWeight::get()),
227        Error::ExceedMaxDomainBlockWeight
228    );
229
230    ensure!(
231        T::Currency::reducible_balance(owner_account_id, Preservation::Protect, Fortitude::Polite)
232            >= T::DomainInstantiationDeposit::get(),
233        Error::InsufficientFund
234    );
235
236    domain_config.check_initial_balances::<T>()?;
237
238    Ok(domain_config)
239}
240
241pub(crate) fn do_instantiate_domain<T: Config>(
242    domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
243    owner_account_id: T::AccountId,
244    created_at: BlockNumberFor<T>,
245) -> Result<DomainId, Error> {
246    let domain_runtime_info = domain_config_params.domain_runtime_info.clone();
247    let domain_config = can_instantiate_domain::<T>(&owner_account_id, domain_config_params)?;
248
249    let domain_instantiation_deposit = T::DomainInstantiationDeposit::get();
250    let domain_id = NextDomainId::<T>::get();
251    let runtime_obj = RuntimeRegistry::<T>::mutate(domain_config.runtime_id, |maybe_runtime_obj| {
252        let mut runtime_object = maybe_runtime_obj
253            .take()
254            .expect("Runtime object must exist as checked in `can_instantiate_domain`; qed");
255        runtime_object.instance_count = runtime_object.instance_count.saturating_add(1);
256        *maybe_runtime_obj = Some(runtime_object.clone());
257        runtime_object
258    });
259
260    let domain_runtime_info = match (runtime_obj.runtime_type, domain_runtime_info) {
261        (
262            RuntimeType::Evm,
263            DomainRuntimeInfo::Evm {
264                chain_id,
265                domain_runtime_config,
266            },
267        ) => {
268            // ensure chain_id is not already allocated.
269            ensure!(
270                !EvmChainIds::<T>::contains_key(chain_id),
271                Error::InvalidEVMChainId
272            );
273
274            // allocate the chain_id to this domain
275            EvmChainIds::<T>::insert(chain_id, domain_id);
276
277            DomainRuntimeInfo::Evm {
278                chain_id,
279                domain_runtime_config,
280            }
281        }
282        (
283            RuntimeType::AutoId,
284            DomainRuntimeInfo::AutoId {
285                domain_runtime_config,
286            },
287        ) => DomainRuntimeInfo::AutoId {
288            domain_runtime_config,
289        },
290        _ => return Err(Error::InvalidConfigForRuntimeType),
291    };
292
293    // burn total issuance on domain from owners account and track the domain balance
294    let total_issuance = domain_config
295        .total_issuance()
296        .ok_or(Error::InitialBalanceOverflow)?;
297
298    T::Currency::burn_from(
299        &owner_account_id,
300        total_issuance,
301        Preservation::Expendable,
302        Precision::Exact,
303        Fortitude::Polite,
304    )
305    .map_err(|_| Error::InsufficientFund)?;
306
307    T::DomainsTransfersTracker::initialize_domain_balance(domain_id, total_issuance)
308        .map_err(|_| Error::TransfersTracker)?;
309
310    let genesis_receipt = {
311        let state_version = runtime_obj.version.state_version();
312        let raw_genesis = into_complete_raw_genesis::<T>(
313            runtime_obj,
314            domain_id,
315            &domain_runtime_info,
316            total_issuance,
317            domain_config.initial_balances.clone(),
318        )
319        .map_err(Error::FailedToGenerateRawGenesis)?;
320        let state_root = raw_genesis.state_root::<DomainHashingFor<T>>(state_version);
321        let genesis_block_hash = derive_domain_block_hash::<T::DomainHeader>(
322            Zero::zero(),
323            sp_domains::EMPTY_EXTRINSIC_ROOT.into(),
324            state_root,
325            Default::default(),
326            Default::default(),
327        );
328
329        ExecutionReceiptOf::<T>::genesis(
330            state_root,
331            sp_domains::EMPTY_EXTRINSIC_ROOT.into(),
332            genesis_block_hash,
333            T::CurrentBundleAndExecutionReceiptVersion::get().execution_receipt_version,
334        )
335    };
336    let genesis_receipt_hash = genesis_receipt.hash::<DomainHashingFor<T>>();
337
338    let domain_obj = DomainObject {
339        owner_account_id: owner_account_id.clone(),
340        created_at,
341        genesis_receipt_hash,
342        domain_config,
343        domain_runtime_info,
344        domain_instantiation_deposit,
345    };
346    DomainRegistry::<T>::insert(domain_id, domain_obj);
347
348    let next_domain_id = domain_id.checked_add(&1.into()).ok_or(Error::MaxDomainId)?;
349    NextDomainId::<T>::set(next_domain_id);
350
351    // Lock up `domain_instantiation_deposit` amount of fund of the domain instance creator
352    T::Currency::hold(
353        &T::HoldIdentifier::domain_instantiation_id(),
354        &owner_account_id,
355        domain_instantiation_deposit,
356    )
357    .map_err(|_| Error::BalanceFreeze)?;
358
359    DomainStakingSummary::<T>::insert(
360        domain_id,
361        StakingSummary {
362            current_epoch_index: 0,
363            current_total_stake: Zero::zero(),
364            current_operators: BTreeMap::new(),
365            next_operators: BTreeSet::new(),
366            current_epoch_rewards: BTreeMap::new(),
367        },
368    );
369
370    import_genesis_receipt::<T>(domain_id, genesis_receipt);
371    T::OnDomainInstantiated::on_domain_instantiated(domain_id);
372
373    DomainSudoCalls::<T>::insert(domain_id, DomainSudoCall { maybe_call: None });
374
375    frame_system::Pallet::<T>::deposit_log(DigestItem::domain_instantiation(domain_id));
376
377    Ok(domain_id)
378}
379
380pub(crate) fn do_update_domain_allow_list<T: Config>(
381    domain_owner: T::AccountId,
382    domain_id: DomainId,
383    updated_operator_allow_list: OperatorAllowList<T::AccountId>,
384) -> Result<(), Error> {
385    let mut domain_obj = DomainRegistry::<T>::get(domain_id).ok_or(Error::DomainNotFound)?;
386
387    ensure!(
388        domain_obj.owner_account_id == domain_owner,
389        Error::NotDomainOwner,
390    );
391
392    domain_obj.domain_config.operator_allow_list = updated_operator_allow_list;
393    DomainRegistry::<T>::insert(domain_id, domain_obj);
394
395    Ok(())
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use crate::tests::{TEST_RUNTIME_APIS, Test, new_test_ext};
402    use domain_runtime_primitives::{AccountId20, AccountId20Converter, DEFAULT_EVM_CHAIN_ID};
403    use frame_support::traits::Currency;
404    use frame_support::{assert_err, assert_ok};
405    use hex_literal::hex;
406    use sp_domains::storage::RawGenesis;
407    use sp_domains::{EvmDomainRuntimeConfig, EvmType, PermissionedActionAllowedBy, RuntimeObject};
408    use sp_runtime::traits::Convert;
409    use sp_std::vec;
410    use sp_version::{RuntimeVersion, create_apis_vec};
411    use subspace_runtime_primitives::AI3;
412
413    type Balances = pallet_balances::Pallet<Test>;
414
415    #[test]
416    fn test_domain_instantiation() {
417        let creator = 1u128;
418        let created_at = 0u32;
419        // Construct an invalid domain config initially
420        let mut domain_config_params = DomainConfigParams {
421            domain_name: String::from_utf8(vec![0; 1024]).unwrap(),
422            runtime_id: 0,
423            maybe_bundle_limit: Some(DomainBundleLimit {
424                max_bundle_size: u32::MAX,
425                max_bundle_weight: Weight::MAX,
426            }),
427            bundle_slot_probability: (0, 0),
428            operator_allow_list: OperatorAllowList::Anyone,
429            initial_balances: Default::default(),
430            domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
431        };
432
433        let mut ext = new_test_ext();
434        ext.execute_with(|| {
435            assert_eq!(NextDomainId::<Test>::get(), 0.into());
436
437            // Failed to instantiate domain due to invalid `bundle_slot_probability`
438            assert_eq!(
439                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
440                Err(Error::InvalidSlotProbability)
441            );
442            domain_config_params.bundle_slot_probability = (1, 0);
443            assert_eq!(
444                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
445                Err(Error::InvalidSlotProbability)
446            );
447            domain_config_params.bundle_slot_probability = (0, 1);
448            assert_eq!(
449                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
450                Err(Error::InvalidSlotProbability)
451            );
452            domain_config_params.bundle_slot_probability = (2, 1);
453            assert_eq!(
454                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
455                Err(Error::InvalidSlotProbability)
456            );
457            // Recorrect `bundle_slot_probability`
458            domain_config_params.bundle_slot_probability = (1, 1);
459
460            // Failed to instantiate domain due to `domain_name` too long
461            assert_eq!(
462                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
463                Err(Error::DomainNameTooLong)
464            );
465            // Recorrect `domain_name`
466            "evm-domain".clone_into(&mut domain_config_params.domain_name);
467
468            // Failed to instantiate domain due to using unregistered runtime id
469            assert_eq!(
470                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
471                Err(Error::RuntimeNotFound)
472            );
473            // Register runtime id
474            RuntimeRegistry::<Test>::insert(
475                domain_config_params.runtime_id,
476                RuntimeObject {
477                    runtime_name: "evm".to_owned(),
478                    runtime_type: Default::default(),
479                    runtime_upgrades: 0,
480                    hash: Default::default(),
481                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
482                    version: RuntimeVersion {
483                        spec_name: "test".into(),
484                        spec_version: 1,
485                        impl_version: 1,
486                        transaction_version: 1,
487                        apis: create_apis_vec!(TEST_RUNTIME_APIS),
488                        ..Default::default()
489                    },
490                    created_at: Default::default(),
491                    updated_at: Default::default(),
492                    instance_count: 0,
493                },
494            );
495
496            // Failed to instantiate domain due to exceed max domain block size limit
497            assert_eq!(
498                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
499                Err(Error::ExceedMaxDomainBlockSize)
500            );
501            // Recorrect `max_bundle_size`
502            domain_config_params
503                .maybe_bundle_limit
504                .as_mut()
505                .unwrap()
506                .max_bundle_size = 1;
507
508            // Failed to instantiate domain due to exceed max domain block weight limit
509            assert_eq!(
510                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
511                Err(Error::ExceedMaxDomainBlockWeight)
512            );
513            // Recorrect `max_bundle_weight`
514            domain_config_params
515                .maybe_bundle_limit
516                .as_mut()
517                .unwrap()
518                .max_bundle_weight = Weight::from_parts(1, 0);
519
520            // Failed to instantiate domain due to creator don't have enough fund
521            assert_eq!(
522                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
523                Err(Error::InsufficientFund)
524            );
525            // Set enough fund to creator
526            Balances::make_free_balance_be(
527                &creator,
528                <Test as Config>::DomainInstantiationDeposit::get()
529                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
530            );
531            // Set `maybe_bundle_limit` to use the default bundle limit
532            domain_config_params.maybe_bundle_limit = None;
533
534            // `instantiate_domain` must success now
535            let domain_id =
536                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
537                    .unwrap();
538            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
539
540            assert_eq!(domain_obj.owner_account_id, creator);
541            assert_eq!(domain_obj.created_at, created_at);
542            assert_eq!(
543                domain_obj.domain_config,
544                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
545            );
546            assert_eq!(NextDomainId::<Test>::get(), 1.into());
547            // Fund locked up thus can't withdraw, and usable balance is zero since ED is 1
548            assert_eq!(Balances::usable_balance(creator), Zero::zero());
549
550            // instance count must be incremented
551            let runtime_obj =
552                RuntimeRegistry::<Test>::get(domain_config_params.runtime_id).unwrap();
553            assert_eq!(runtime_obj.instance_count, 1);
554
555            // cannot use the locked funds to create a new domain instance
556            assert_eq!(
557                do_instantiate_domain::<Test>(domain_config_params, creator, created_at),
558                Err(Error::InsufficientFund)
559            );
560
561            // update operator allow list
562            let updated_operator_allow_list =
563                OperatorAllowList::Operators(BTreeSet::from_iter(vec![1, 2, 3]));
564            assert_ok!(do_update_domain_allow_list::<Test>(
565                creator,
566                domain_id,
567                updated_operator_allow_list.clone()
568            ));
569            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
570            assert_eq!(
571                domain_obj.domain_config.operator_allow_list,
572                updated_operator_allow_list
573            );
574        });
575    }
576
577    #[test]
578    fn test_domain_instantiation_evm_accounts() {
579        let creator = 1u128;
580        let created_at = 0u32;
581        // Construct an invalid domain config initially
582        let mut domain_config_params = DomainConfigParams {
583            domain_name: "evm-domain".to_owned(),
584            runtime_id: 0,
585            maybe_bundle_limit: None,
586            bundle_slot_probability: (1, 1),
587            operator_allow_list: OperatorAllowList::Anyone,
588            initial_balances: vec![(MultiAccountId::Raw(vec![0, 1, 2, 3, 4, 5]), 1_000_000 * AI3)],
589            domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
590        };
591
592        let mut ext = new_test_ext();
593        ext.execute_with(|| {
594            assert_eq!(NextDomainId::<Test>::get(), 0.into());
595            // Register runtime id
596            RuntimeRegistry::<Test>::insert(
597                domain_config_params.runtime_id,
598                RuntimeObject {
599                    runtime_name: "evm".to_owned(),
600                    runtime_type: Default::default(),
601                    runtime_upgrades: 0,
602                    hash: Default::default(),
603                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
604                    version: RuntimeVersion {
605                        spec_name: "test".into(),
606                        spec_version: 1,
607                        impl_version: 1,
608                        transaction_version: 1,
609                        apis: create_apis_vec!(TEST_RUNTIME_APIS),
610                        ..Default::default()
611                    },
612                    created_at: Default::default(),
613                    updated_at: Default::default(),
614                    instance_count: 0,
615                },
616            );
617
618            // Set enough fund to creator
619            Balances::make_free_balance_be(
620                &creator,
621                <Test as Config>::DomainInstantiationDeposit::get()
622                    // for domain total issuance
623                    + 1_000_000 * AI3
624                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
625            );
626
627            // should fail due to invalid account ID type
628            assert_err!(
629                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
630                Error::FailedToGenerateRawGenesis(
631                    crate::runtime_registry::Error::InvalidAccountIdType
632                )
633            );
634
635            // duplicate accounts
636            domain_config_params.initial_balances = vec![
637                (
638                    AccountId20Converter::convert(AccountId20::from(hex!(
639                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
640                    ))),
641                    1_000_000 * AI3,
642                ),
643                (
644                    AccountId20Converter::convert(AccountId20::from(hex!(
645                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
646                    ))),
647                    1_000_000 * AI3,
648                ),
649            ];
650
651            assert_err!(
652                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
653                Error::DuplicateInitialAccounts
654            );
655
656            // max accounts
657            domain_config_params.initial_balances = vec![
658                (
659                    AccountId20Converter::convert(AccountId20::from(hex!(
660                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
661                    ))),
662                    1_000_000 * AI3,
663                ),
664                (
665                    AccountId20Converter::convert(AccountId20::from(hex!(
666                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cbc"
667                    ))),
668                    1_000_000 * AI3,
669                ),
670                (
671                    AccountId20Converter::convert(AccountId20::from(hex!(
672                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566ccc"
673                    ))),
674                    1_000_000 * AI3,
675                ),
676                (
677                    AccountId20Converter::convert(AccountId20::from(hex!(
678                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cdc"
679                    ))),
680                    1_000_000 * AI3,
681                ),
682                (
683                    AccountId20Converter::convert(AccountId20::from(hex!(
684                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cec"
685                    ))),
686                    1_000_000 * AI3,
687                ),
688                (
689                    AccountId20Converter::convert(AccountId20::from(hex!(
690                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cfc"
691                    ))),
692                    1_000_000 * AI3,
693                ),
694            ];
695
696            assert_err!(
697                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
698                Error::MaxInitialDomainAccounts
699            );
700
701            // min balance accounts
702            domain_config_params.initial_balances = vec![(
703                AccountId20Converter::convert(AccountId20::from(hex!(
704                    "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
705                ))),
706                1,
707            )];
708
709            assert_err!(
710                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
711                Error::MinInitialAccountBalance
712            );
713
714            domain_config_params.initial_balances = vec![(
715                AccountId20Converter::convert(AccountId20::from(hex!(
716                    "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
717                ))),
718                1_000_000 * AI3,
719            )];
720
721            // Set enough fund to creator
722            Balances::make_free_balance_be(
723                &creator,
724                <Test as Config>::DomainInstantiationDeposit::get()
725                    // for domain total issuance
726                    + 1_000_000 * AI3
727                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
728            );
729
730            // should be successful
731            domain_config_params.domain_runtime_info =
732                (DEFAULT_EVM_CHAIN_ID + 1, Default::default()).into();
733            let domain_id =
734                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
735                    .unwrap();
736            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
737
738            assert_eq!(domain_obj.owner_account_id, creator);
739            assert_eq!(domain_obj.created_at, created_at);
740            assert_eq!(
741                domain_obj.domain_config,
742                into_domain_config::<Test>(domain_config_params).unwrap()
743            );
744        });
745    }
746
747    #[test]
748    fn test_domain_instantiation_evm_contract_allow_list() {
749        let creator = 1u128;
750        let created_at = 0u32;
751        // Construct a valid default domain config
752        let mut domain_config_params = DomainConfigParams {
753            domain_name: "evm-domain".to_owned(),
754            runtime_id: 0,
755            maybe_bundle_limit: None,
756            bundle_slot_probability: (1, 1),
757            operator_allow_list: OperatorAllowList::Anyone,
758            initial_balances: vec![(
759                AccountId20Converter::convert(AccountId20::from(hex!(
760                    "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
761                ))),
762                1_000_000 * AI3,
763            )],
764            domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
765        };
766
767        let mut ext = new_test_ext();
768        ext.execute_with(|| {
769            assert_eq!(NextDomainId::<Test>::get(), 0.into());
770            // Register runtime id
771            RuntimeRegistry::<Test>::insert(
772                domain_config_params.runtime_id,
773                RuntimeObject {
774                    runtime_name: "evm".to_owned(),
775                    runtime_type: Default::default(),
776                    runtime_upgrades: 0,
777                    hash: Default::default(),
778                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
779                    version: RuntimeVersion {
780                        spec_name: "test".into(),
781                        spec_version: 1,
782                        impl_version: 1,
783                        transaction_version: 1,
784                        apis: create_apis_vec!(TEST_RUNTIME_APIS),
785                        ..Default::default()
786                    },
787                    created_at: Default::default(),
788                    updated_at: Default::default(),
789                    instance_count: 0,
790                },
791            );
792
793            // Set enough fund to creator
794            Balances::make_free_balance_be(
795                &creator,
796                <Test as Config>::DomainInstantiationDeposit::get()
797                    // for domain total issuance
798                    + 1_000_000 * AI3
799                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
800            );
801
802            // should be successful
803            let domain_id =
804                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
805                    .unwrap();
806            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
807
808            assert_eq!(domain_obj.owner_account_id, creator);
809            assert_eq!(domain_obj.created_at, created_at);
810            assert_eq!(
811                domain_obj.domain_config,
812                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
813            );
814            assert_eq!(
815                domain_obj
816                    .domain_runtime_info
817                    .initial_contract_creation_allow_list(),
818                None,
819                "default is public EVM, which does not have a contract creation allow list"
820            );
821
822            // Set public EVM
823            domain_config_params.domain_runtime_info = (
824                DEFAULT_EVM_CHAIN_ID + 1,
825                EvmDomainRuntimeConfig {
826                    evm_type: EvmType::Public,
827                },
828            )
829                .into();
830
831            // Set enough fund to creator
832            Balances::make_free_balance_be(
833                &creator,
834                <Test as Config>::DomainInstantiationDeposit::get()
835                                // for domain total issuance
836                                + 1_000_000 * AI3
837                                + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
838            );
839
840            // should be successful
841            let domain_id =
842                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
843                    .unwrap();
844            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
845
846            assert_eq!(domain_obj.owner_account_id, creator);
847            assert_eq!(domain_obj.created_at, created_at);
848            assert_eq!(
849                domain_obj.domain_config,
850                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
851            );
852            assert_eq!(
853                domain_obj
854                    .domain_runtime_info
855                    .initial_contract_creation_allow_list(),
856                None,
857                "public EVMs do not have a contract creation allow list"
858            );
859
860            // Set empty list
861            let mut list = vec![];
862            domain_config_params.domain_runtime_info = (
863                DEFAULT_EVM_CHAIN_ID + 2,
864                EvmDomainRuntimeConfig {
865                    evm_type: EvmType::Private {
866                        initial_contract_creation_allow_list: PermissionedActionAllowedBy::Accounts(
867                            list.clone(),
868                        ),
869                    },
870                },
871            )
872                .into();
873
874            // Set enough fund to creator
875            Balances::make_free_balance_be(
876                &creator,
877                <Test as Config>::DomainInstantiationDeposit::get()
878                    // for domain total issuance
879                    + 1_000_000 * AI3
880                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
881            );
882
883            // should be successful
884            let domain_id =
885                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
886                    .unwrap();
887            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
888
889            assert_eq!(domain_obj.owner_account_id, creator);
890            assert_eq!(domain_obj.created_at, created_at);
891            assert_eq!(
892                domain_obj.domain_config,
893                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
894            );
895            assert_eq!(
896                domain_obj
897                    .domain_runtime_info
898                    .initial_contract_creation_allow_list(),
899                Some(&PermissionedActionAllowedBy::Accounts(list)),
900                "empty list should work"
901            );
902
903            // Set 1 account in list
904            list = vec![hex!("0102030405060708091011121314151617181920").into()];
905            domain_config_params.domain_runtime_info = (
906                DEFAULT_EVM_CHAIN_ID + 3,
907                EvmDomainRuntimeConfig {
908                    evm_type: EvmType::Private {
909                        initial_contract_creation_allow_list: PermissionedActionAllowedBy::Accounts(
910                            list.clone(),
911                        ),
912                    },
913                },
914            )
915                .into();
916
917            // Set enough fund to creator
918            Balances::make_free_balance_be(
919                &creator,
920                <Test as Config>::DomainInstantiationDeposit::get()
921                    // for domain total issuance
922                    + 1_000_000 * AI3
923                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
924            );
925
926            // should be successful
927            let domain_id =
928                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
929                    .unwrap();
930            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
931
932            assert_eq!(domain_obj.owner_account_id, creator);
933            assert_eq!(domain_obj.created_at, created_at);
934            assert_eq!(
935                domain_obj.domain_config,
936                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
937            );
938            assert_eq!(
939                domain_obj
940                    .domain_runtime_info
941                    .initial_contract_creation_allow_list(),
942                Some(&PermissionedActionAllowedBy::Accounts(list)),
943                "1 account list should work"
944            );
945
946            // Set multi account list
947            list = vec![
948                hex!("0102030405060708091011121314151617181920").into(),
949                hex!("1102030405060708091011121314151617181920").into(),
950                hex!("2102030405060708091011121314151617181920").into(),
951            ];
952            domain_config_params.domain_runtime_info = (
953                DEFAULT_EVM_CHAIN_ID + 4,
954                EvmDomainRuntimeConfig {
955                    evm_type: EvmType::Private {
956                        initial_contract_creation_allow_list: PermissionedActionAllowedBy::Accounts(
957                            list.clone(),
958                        ),
959                    },
960                },
961            )
962                .into();
963
964            // Set enough fund to creator
965            Balances::make_free_balance_be(
966                &creator,
967                <Test as Config>::DomainInstantiationDeposit::get()
968                    // for domain total issuance
969                    + 1_000_000 * AI3
970                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
971            );
972
973            // should be successful
974            let domain_id =
975                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
976                    .unwrap();
977            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
978
979            assert_eq!(domain_obj.owner_account_id, creator);
980            assert_eq!(domain_obj.created_at, created_at);
981            assert_eq!(
982                domain_obj.domain_config,
983                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
984            );
985            assert_eq!(
986                domain_obj
987                    .domain_runtime_info
988                    .initial_contract_creation_allow_list(),
989                Some(&PermissionedActionAllowedBy::Accounts(list)),
990                "multi account list should work"
991            );
992        });
993    }
994}