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