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