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