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::mock::Test;
402    use crate::tests::{TEST_RUNTIME_APIS, new_test_ext};
403    use domain_runtime_primitives::{AccountId20, AccountId20Converter, DEFAULT_EVM_CHAIN_ID};
404    use frame_support::traits::Currency;
405    use frame_support::{assert_err, assert_ok};
406    use hex_literal::hex;
407    use sp_domains::storage::RawGenesis;
408    use sp_domains::{EvmDomainRuntimeConfig, EvmType, PermissionedActionAllowedBy, RuntimeObject};
409    use sp_runtime::traits::Convert;
410    use sp_std::vec;
411    use sp_version::{RuntimeVersion, create_apis_vec};
412    use subspace_runtime_primitives::AI3;
413
414    type Balances = pallet_balances::Pallet<Test>;
415
416    #[test]
417    fn test_domain_instantiation() {
418        let creator = 1u128;
419        let created_at = 0u32;
420        // Construct an invalid domain config initially
421        let mut domain_config_params = DomainConfigParams {
422            domain_name: String::from_utf8(vec![0; 1024]).unwrap(),
423            runtime_id: 0,
424            maybe_bundle_limit: Some(DomainBundleLimit {
425                max_bundle_size: u32::MAX,
426                max_bundle_weight: Weight::MAX,
427            }),
428            bundle_slot_probability: (0, 0),
429            operator_allow_list: OperatorAllowList::Anyone,
430            initial_balances: Default::default(),
431            domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
432        };
433
434        let mut ext = new_test_ext();
435        ext.execute_with(|| {
436            assert_eq!(NextDomainId::<Test>::get(), 0.into());
437
438            // Failed to instantiate domain due to invalid `bundle_slot_probability`
439            assert_eq!(
440                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
441                Err(Error::InvalidSlotProbability)
442            );
443            domain_config_params.bundle_slot_probability = (1, 0);
444            assert_eq!(
445                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
446                Err(Error::InvalidSlotProbability)
447            );
448            domain_config_params.bundle_slot_probability = (0, 1);
449            assert_eq!(
450                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
451                Err(Error::InvalidSlotProbability)
452            );
453            domain_config_params.bundle_slot_probability = (2, 1);
454            assert_eq!(
455                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
456                Err(Error::InvalidSlotProbability)
457            );
458            // Recorrect `bundle_slot_probability`
459            domain_config_params.bundle_slot_probability = (1, 1);
460
461            // Failed to instantiate domain due to `domain_name` too long
462            assert_eq!(
463                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
464                Err(Error::DomainNameTooLong)
465            );
466            // Recorrect `domain_name`
467            "evm-domain".clone_into(&mut domain_config_params.domain_name);
468
469            // Failed to instantiate domain due to using unregistered runtime id
470            assert_eq!(
471                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
472                Err(Error::RuntimeNotFound)
473            );
474            // Register runtime id
475            RuntimeRegistry::<Test>::insert(
476                domain_config_params.runtime_id,
477                RuntimeObject {
478                    runtime_name: "evm".to_owned(),
479                    runtime_type: Default::default(),
480                    runtime_upgrades: 0,
481                    hash: Default::default(),
482                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
483                    version: RuntimeVersion {
484                        spec_name: "test".into(),
485                        spec_version: 1,
486                        impl_version: 1,
487                        transaction_version: 1,
488                        apis: create_apis_vec!(TEST_RUNTIME_APIS),
489                        ..Default::default()
490                    },
491                    created_at: Default::default(),
492                    updated_at: Default::default(),
493                    instance_count: 0,
494                },
495            );
496
497            // Failed to instantiate domain due to exceed max domain block size limit
498            assert_eq!(
499                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
500                Err(Error::ExceedMaxDomainBlockSize)
501            );
502            // Recorrect `max_bundle_size`
503            domain_config_params
504                .maybe_bundle_limit
505                .as_mut()
506                .unwrap()
507                .max_bundle_size = 1;
508
509            // Failed to instantiate domain due to exceed max domain block weight limit
510            assert_eq!(
511                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
512                Err(Error::ExceedMaxDomainBlockWeight)
513            );
514            // Recorrect `max_bundle_weight`
515            domain_config_params
516                .maybe_bundle_limit
517                .as_mut()
518                .unwrap()
519                .max_bundle_weight = Weight::from_parts(1, 0);
520
521            // Failed to instantiate domain due to creator don't have enough fund
522            assert_eq!(
523                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
524                Err(Error::InsufficientFund)
525            );
526            // Set enough fund to creator
527            Balances::make_free_balance_be(
528                &creator,
529                <Test as Config>::DomainInstantiationDeposit::get()
530                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
531            );
532            // Set `maybe_bundle_limit` to use the default bundle limit
533            domain_config_params.maybe_bundle_limit = None;
534
535            // `instantiate_domain` must success now
536            let domain_id =
537                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
538                    .unwrap();
539            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
540
541            assert_eq!(domain_obj.owner_account_id, creator);
542            assert_eq!(domain_obj.created_at, created_at);
543            assert_eq!(
544                domain_obj.domain_config,
545                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
546            );
547            assert_eq!(NextDomainId::<Test>::get(), 1.into());
548            // Fund locked up thus can't withdraw, and usable balance is zero since ED is 1
549            assert_eq!(Balances::usable_balance(creator), Zero::zero());
550
551            // instance count must be incremented
552            let runtime_obj =
553                RuntimeRegistry::<Test>::get(domain_config_params.runtime_id).unwrap();
554            assert_eq!(runtime_obj.instance_count, 1);
555
556            // cannot use the locked funds to create a new domain instance
557            assert_eq!(
558                do_instantiate_domain::<Test>(domain_config_params, creator, created_at),
559                Err(Error::InsufficientFund)
560            );
561
562            // update operator allow list
563            let updated_operator_allow_list =
564                OperatorAllowList::Operators(BTreeSet::from_iter(vec![1, 2, 3]));
565            assert_ok!(do_update_domain_allow_list::<Test>(
566                creator,
567                domain_id,
568                updated_operator_allow_list.clone()
569            ));
570            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
571            assert_eq!(
572                domain_obj.domain_config.operator_allow_list,
573                updated_operator_allow_list
574            );
575        });
576    }
577
578    #[test]
579    fn test_domain_instantiation_evm_accounts() {
580        let creator = 1u128;
581        let created_at = 0u32;
582        // Construct an invalid domain config initially
583        let mut domain_config_params = DomainConfigParams {
584            domain_name: "evm-domain".to_owned(),
585            runtime_id: 0,
586            maybe_bundle_limit: None,
587            bundle_slot_probability: (1, 1),
588            operator_allow_list: OperatorAllowList::Anyone,
589            initial_balances: vec![(MultiAccountId::Raw(vec![0, 1, 2, 3, 4, 5]), 1_000_000 * AI3)],
590            domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
591        };
592
593        let mut ext = new_test_ext();
594        ext.execute_with(|| {
595            assert_eq!(NextDomainId::<Test>::get(), 0.into());
596            // Register runtime id
597            RuntimeRegistry::<Test>::insert(
598                domain_config_params.runtime_id,
599                RuntimeObject {
600                    runtime_name: "evm".to_owned(),
601                    runtime_type: Default::default(),
602                    runtime_upgrades: 0,
603                    hash: Default::default(),
604                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
605                    version: RuntimeVersion {
606                        spec_name: "test".into(),
607                        spec_version: 1,
608                        impl_version: 1,
609                        transaction_version: 1,
610                        apis: create_apis_vec!(TEST_RUNTIME_APIS),
611                        ..Default::default()
612                    },
613                    created_at: Default::default(),
614                    updated_at: Default::default(),
615                    instance_count: 0,
616                },
617            );
618
619            // Set enough fund to creator
620            Balances::make_free_balance_be(
621                &creator,
622                <Test as Config>::DomainInstantiationDeposit::get()
623                    // for domain total issuance
624                    + 1_000_000 * AI3
625                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
626            );
627
628            // should fail due to invalid account ID type
629            assert_err!(
630                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
631                Error::FailedToGenerateRawGenesis(
632                    crate::runtime_registry::Error::InvalidAccountIdType
633                )
634            );
635
636            // duplicate accounts
637            domain_config_params.initial_balances = vec![
638                (
639                    AccountId20Converter::convert(AccountId20::from(hex!(
640                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
641                    ))),
642                    1_000_000 * AI3,
643                ),
644                (
645                    AccountId20Converter::convert(AccountId20::from(hex!(
646                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
647                    ))),
648                    1_000_000 * AI3,
649                ),
650            ];
651
652            assert_err!(
653                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
654                Error::DuplicateInitialAccounts
655            );
656
657            // max accounts
658            domain_config_params.initial_balances = vec![
659                (
660                    AccountId20Converter::convert(AccountId20::from(hex!(
661                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
662                    ))),
663                    1_000_000 * AI3,
664                ),
665                (
666                    AccountId20Converter::convert(AccountId20::from(hex!(
667                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cbc"
668                    ))),
669                    1_000_000 * AI3,
670                ),
671                (
672                    AccountId20Converter::convert(AccountId20::from(hex!(
673                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566ccc"
674                    ))),
675                    1_000_000 * AI3,
676                ),
677                (
678                    AccountId20Converter::convert(AccountId20::from(hex!(
679                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cdc"
680                    ))),
681                    1_000_000 * AI3,
682                ),
683                (
684                    AccountId20Converter::convert(AccountId20::from(hex!(
685                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cec"
686                    ))),
687                    1_000_000 * AI3,
688                ),
689                (
690                    AccountId20Converter::convert(AccountId20::from(hex!(
691                        "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cfc"
692                    ))),
693                    1_000_000 * AI3,
694                ),
695            ];
696
697            assert_err!(
698                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
699                Error::MaxInitialDomainAccounts
700            );
701
702            // min balance accounts
703            domain_config_params.initial_balances = vec![(
704                AccountId20Converter::convert(AccountId20::from(hex!(
705                    "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
706                ))),
707                1,
708            )];
709
710            assert_err!(
711                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at),
712                Error::MinInitialAccountBalance
713            );
714
715            domain_config_params.initial_balances = vec![(
716                AccountId20Converter::convert(AccountId20::from(hex!(
717                    "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
718                ))),
719                1_000_000 * AI3,
720            )];
721
722            // Set enough fund to creator
723            Balances::make_free_balance_be(
724                &creator,
725                <Test as Config>::DomainInstantiationDeposit::get()
726                    // for domain total issuance
727                    + 1_000_000 * AI3
728                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
729            );
730
731            // should be successful
732            domain_config_params.domain_runtime_info =
733                (DEFAULT_EVM_CHAIN_ID + 1, Default::default()).into();
734            let domain_id =
735                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
736                    .unwrap();
737            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
738
739            assert_eq!(domain_obj.owner_account_id, creator);
740            assert_eq!(domain_obj.created_at, created_at);
741            assert_eq!(
742                domain_obj.domain_config,
743                into_domain_config::<Test>(domain_config_params).unwrap()
744            );
745        });
746    }
747
748    #[test]
749    fn test_domain_instantiation_evm_contract_allow_list() {
750        let creator = 1u128;
751        let created_at = 0u32;
752        // Construct a valid default domain config
753        let mut domain_config_params = DomainConfigParams {
754            domain_name: "evm-domain".to_owned(),
755            runtime_id: 0,
756            maybe_bundle_limit: None,
757            bundle_slot_probability: (1, 1),
758            operator_allow_list: OperatorAllowList::Anyone,
759            initial_balances: vec![(
760                AccountId20Converter::convert(AccountId20::from(hex!(
761                    "f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"
762                ))),
763                1_000_000 * AI3,
764            )],
765            domain_runtime_info: (DEFAULT_EVM_CHAIN_ID, Default::default()).into(),
766        };
767
768        let mut ext = new_test_ext();
769        ext.execute_with(|| {
770            assert_eq!(NextDomainId::<Test>::get(), 0.into());
771            // Register runtime id
772            RuntimeRegistry::<Test>::insert(
773                domain_config_params.runtime_id,
774                RuntimeObject {
775                    runtime_name: "evm".to_owned(),
776                    runtime_type: Default::default(),
777                    runtime_upgrades: 0,
778                    hash: Default::default(),
779                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
780                    version: RuntimeVersion {
781                        spec_name: "test".into(),
782                        spec_version: 1,
783                        impl_version: 1,
784                        transaction_version: 1,
785                        apis: create_apis_vec!(TEST_RUNTIME_APIS),
786                        ..Default::default()
787                    },
788                    created_at: Default::default(),
789                    updated_at: Default::default(),
790                    instance_count: 0,
791                },
792            );
793
794            // Set enough fund to creator
795            Balances::make_free_balance_be(
796                &creator,
797                <Test as Config>::DomainInstantiationDeposit::get()
798                    // for domain total issuance
799                    + 1_000_000 * AI3
800                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
801            );
802
803            // should be successful
804            let domain_id =
805                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
806                    .unwrap();
807            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
808
809            assert_eq!(domain_obj.owner_account_id, creator);
810            assert_eq!(domain_obj.created_at, created_at);
811            assert_eq!(
812                domain_obj.domain_config,
813                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
814            );
815            assert_eq!(
816                domain_obj
817                    .domain_runtime_info
818                    .initial_contract_creation_allow_list(),
819                None,
820                "default is public EVM, which does not have a contract creation allow list"
821            );
822
823            // Set public EVM
824            domain_config_params.domain_runtime_info = (
825                DEFAULT_EVM_CHAIN_ID + 1,
826                EvmDomainRuntimeConfig {
827                    evm_type: EvmType::Public,
828                },
829            )
830                .into();
831
832            // Set enough fund to creator
833            Balances::make_free_balance_be(
834                &creator,
835                <Test as Config>::DomainInstantiationDeposit::get()
836                                // for domain total issuance
837                                + 1_000_000 * AI3
838                                + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
839            );
840
841            // should be successful
842            let domain_id =
843                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
844                    .unwrap();
845            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
846
847            assert_eq!(domain_obj.owner_account_id, creator);
848            assert_eq!(domain_obj.created_at, created_at);
849            assert_eq!(
850                domain_obj.domain_config,
851                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
852            );
853            assert_eq!(
854                domain_obj
855                    .domain_runtime_info
856                    .initial_contract_creation_allow_list(),
857                None,
858                "public EVMs do not have a contract creation allow list"
859            );
860
861            // Set empty list
862            let mut list = vec![];
863            domain_config_params.domain_runtime_info = (
864                DEFAULT_EVM_CHAIN_ID + 2,
865                EvmDomainRuntimeConfig {
866                    evm_type: EvmType::Private {
867                        initial_contract_creation_allow_list: PermissionedActionAllowedBy::Accounts(
868                            list.clone(),
869                        ),
870                    },
871                },
872            )
873                .into();
874
875            // Set enough fund to creator
876            Balances::make_free_balance_be(
877                &creator,
878                <Test as Config>::DomainInstantiationDeposit::get()
879                    // for domain total issuance
880                    + 1_000_000 * AI3
881                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
882            );
883
884            // should be successful
885            let domain_id =
886                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
887                    .unwrap();
888            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
889
890            assert_eq!(domain_obj.owner_account_id, creator);
891            assert_eq!(domain_obj.created_at, created_at);
892            assert_eq!(
893                domain_obj.domain_config,
894                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
895            );
896            assert_eq!(
897                domain_obj
898                    .domain_runtime_info
899                    .initial_contract_creation_allow_list(),
900                Some(&PermissionedActionAllowedBy::Accounts(list)),
901                "empty list should work"
902            );
903
904            // Set 1 account in list
905            list = vec![hex!("0102030405060708091011121314151617181920").into()];
906            domain_config_params.domain_runtime_info = (
907                DEFAULT_EVM_CHAIN_ID + 3,
908                EvmDomainRuntimeConfig {
909                    evm_type: EvmType::Private {
910                        initial_contract_creation_allow_list: PermissionedActionAllowedBy::Accounts(
911                            list.clone(),
912                        ),
913                    },
914                },
915            )
916                .into();
917
918            // Set enough fund to creator
919            Balances::make_free_balance_be(
920                &creator,
921                <Test as Config>::DomainInstantiationDeposit::get()
922                    // for domain total issuance
923                    + 1_000_000 * AI3
924                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
925            );
926
927            // should be successful
928            let domain_id =
929                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
930                    .unwrap();
931            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
932
933            assert_eq!(domain_obj.owner_account_id, creator);
934            assert_eq!(domain_obj.created_at, created_at);
935            assert_eq!(
936                domain_obj.domain_config,
937                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
938            );
939            assert_eq!(
940                domain_obj
941                    .domain_runtime_info
942                    .initial_contract_creation_allow_list(),
943                Some(&PermissionedActionAllowedBy::Accounts(list)),
944                "1 account list should work"
945            );
946
947            // Set multi account list
948            list = vec![
949                hex!("0102030405060708091011121314151617181920").into(),
950                hex!("1102030405060708091011121314151617181920").into(),
951                hex!("2102030405060708091011121314151617181920").into(),
952            ];
953            domain_config_params.domain_runtime_info = (
954                DEFAULT_EVM_CHAIN_ID + 4,
955                EvmDomainRuntimeConfig {
956                    evm_type: EvmType::Private {
957                        initial_contract_creation_allow_list: PermissionedActionAllowedBy::Accounts(
958                            list.clone(),
959                        ),
960                    },
961                },
962            )
963                .into();
964
965            // Set enough fund to creator
966            Balances::make_free_balance_be(
967                &creator,
968                <Test as Config>::DomainInstantiationDeposit::get()
969                    // for domain total issuance
970                    + 1_000_000 * AI3
971                    + <Test as pallet_balances::Config>::ExistentialDeposit::get(),
972            );
973
974            // should be successful
975            let domain_id =
976                do_instantiate_domain::<Test>(domain_config_params.clone(), creator, created_at)
977                    .unwrap();
978            let domain_obj = DomainRegistry::<Test>::get(domain_id).unwrap();
979
980            assert_eq!(domain_obj.owner_account_id, creator);
981            assert_eq!(domain_obj.created_at, created_at);
982            assert_eq!(
983                domain_obj.domain_config,
984                into_domain_config::<Test>(domain_config_params.clone()).unwrap()
985            );
986            assert_eq!(
987                domain_obj
988                    .domain_runtime_info
989                    .initial_contract_creation_allow_list(),
990                Some(&PermissionedActionAllowedBy::Accounts(list)),
991                "multi account list should work"
992            );
993        });
994    }
995}