diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index c87f335e8c9..e52996e1761 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -10,8 +10,10 @@ use frame_support::{ensure, PalletError}; use scale_info::TypeInfo; use sp_core::Get; use sp_domains::merkle_tree::MerkleTree; -use sp_domains::{ConfirmedDomainBlock, DomainId, ExecutionReceipt, OperatorId}; -use sp_runtime::traits::{BlockNumberProvider, CheckedSub, One, Saturating, Zero}; +use sp_domains::{ + ConfirmedDomainBlock, DomainId, DomainsTransfersTracker, ExecutionReceipt, OperatorId, +}; +use sp_runtime::traits::{BlockNumberProvider, CheckedAdd, CheckedSub, One, Saturating, Zero}; use sp_std::cmp::Ordering; use sp_std::collections::btree_map::BTreeMap; use sp_std::vec::Vec; @@ -34,6 +36,8 @@ pub enum Error { InvalidExecutionTrace, UnavailableConsensusBlockHash, InvalidStateRoot, + BalanceOverflow, + DomainTransfersTracking, } #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] @@ -344,6 +348,26 @@ pub(crate) fn process_execution_receipt( execution_receipt.consensus_block_number, ); + // tracking the transfer + // 1. Block fees are burned on domain, so it is considered transferred out + // 2. XDM transfers from the Domain + // 3. XDM transfers into the domain + let transfer_out_balance = execution_receipt + .block_fees + .total_fees() + .and_then(|total| total.checked_add(&execution_receipt.transfers.transfers_out)) + .ok_or(Error::BalanceOverflow)?; + + // track the transfers out and then track transfers in + T::DomainsTransfersTracker::transfer_out(domain_id, transfer_out_balance) + .map_err(|_| Error::DomainTransfersTracking)?; + + T::DomainsTransfersTracker::transfer_in( + domain_id, + execution_receipt.transfers.transfers_in, + ) + .map_err(|_| Error::DomainTransfersTracking)?; + LatestConfirmedDomainBlock::::insert( domain_id, ConfirmedDomainBlock { @@ -371,8 +395,8 @@ pub(crate) fn process_execution_receipt( let er_hash = execution_receipt.hash::>(); BlockTreeNodes::::mutate(er_hash, |maybe_node| { let node = maybe_node.as_mut().expect( - "The domain block of `CurrentHead` receipt is checked to be exist in `execution_receipt_type`; qed" - ); + "The domain block of `CurrentHead` receipt is checked to be exist in `execution_receipt_type`; qed" + ); node.operator_ids.push(submitter); }); } @@ -687,7 +711,7 @@ mod tests { H256::random(), stale_receipt, ); - assert!(crate::Pallet::::submit_bundle(RawOrigin::None.into(), bundle,).is_err()); + assert!(crate::Pallet::::submit_bundle(RawOrigin::None.into(), bundle).is_err()); assert_eq!( BlockTreeNodes::::get(stale_receipt_hash) @@ -735,7 +759,7 @@ mod tests { H256::random(), previous_head_receipt, ); - assert!(crate::Pallet::::submit_bundle(RawOrigin::None.into(), bundle,).is_err()); + assert!(crate::Pallet::::submit_bundle(RawOrigin::None.into(), bundle).is_err()); }); } diff --git a/crates/pallet-domains/src/domain_registry.rs b/crates/pallet-domains/src/domain_registry.rs index 9af5d080584..c9872fed07a 100644 --- a/crates/pallet-domains/src/domain_registry.rs +++ b/crates/pallet-domains/src/domain_registry.rs @@ -11,16 +11,16 @@ use crate::{ use alloc::string::String; use codec::{Decode, Encode}; use domain_runtime_primitives::MultiAccountId; -use frame_support::traits::fungible::{Inspect, MutateHold}; -use frame_support::traits::tokens::{Fortitude, Preservation}; +use frame_support::traits::fungible::{Inspect, Mutate, MutateHold}; +use frame_support::traits::tokens::{Fortitude, Precision, Preservation}; use frame_support::weights::Weight; use frame_support::{ensure, PalletError}; use frame_system::pallet_prelude::*; use scale_info::TypeInfo; use sp_core::Get; use sp_domains::{ - derive_domain_block_hash, DomainId, DomainsDigestItem, OperatorAllowList, RuntimeId, - RuntimeType, + derive_domain_block_hash, DomainId, DomainsDigestItem, DomainsTransfersTracker, + OperatorAllowList, RuntimeId, RuntimeType, }; use sp_runtime::traits::{CheckedAdd, Zero}; use sp_runtime::DigestItem; @@ -44,6 +44,8 @@ pub enum Error { FailedToGenerateGenesisStateRoot, DomainNotFound, NotDomainOwner, + InitialBalanceOverflow, + TransfersTracker, FailedToGenerateRawGenesis(crate::runtime_registry::Error), } @@ -68,6 +70,20 @@ pub struct DomainConfig { pub initial_balances: Vec<(MultiAccountId, Balance)>, } +impl DomainConfig +where + AccountId: Ord, + Balance: Zero + CheckedAdd, +{ + pub(crate) fn total_issuance(&self) -> Option { + self.initial_balances + .iter() + .try_fold(Balance::zero(), |total, (_, balance)| { + total.checked_add(balance) + }) + } +} + #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] pub struct DomainObject { /// The address of the domain creator, used to validate updating the domain config. @@ -146,13 +162,29 @@ pub(crate) fn do_instantiate_domain( } }; - // TODO: burn the total initial balance for domain from the domain owner account + // burn total issuance on domain from owners account and track the domain balance + let total_issuance = domain_config + .total_issuance() + .ok_or(Error::InitialBalanceOverflow)?; + + T::Currency::burn_from( + &owner_account_id, + total_issuance, + Precision::Exact, + Fortitude::Polite, + ) + .map_err(|_| Error::InsufficientFund)?; + + T::DomainsTransfersTracker::transfer_in(domain_id, total_issuance) + .map_err(|_| Error::TransfersTracker)?; + let genesis_receipt = { let state_version = runtime_obj.version.state_version(); let raw_genesis = runtime_obj .into_complete_raw_genesis::( domain_id, domain_runtime_info, + total_issuance, domain_config.initial_balances.clone(), ) .map_err(Error::FailedToGenerateRawGenesis)?; @@ -443,7 +475,9 @@ mod tests { Balances::make_free_balance_be( &creator, ::DomainInstantiationDeposit::get() - + ::ExistentialDeposit::get(), + // for domain total issuance + + 1_000_000 * SSC + + ::ExistentialDeposit::get(), ); // should fail due to invalid account ID type @@ -461,6 +495,15 @@ mod tests { 1_000_000 * SSC, )]; + // Set enough fund to creator + Balances::make_free_balance_be( + &creator, + ::DomainInstantiationDeposit::get() + // for domain total issuance + + 1_000_000 * SSC + + ::ExistentialDeposit::get(), + ); + // should be successful let domain_id = do_instantiate_domain::(domain_config.clone(), creator, created_at).unwrap(); diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index b3aa5ea2530..6f43487bc39 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -159,8 +159,8 @@ mod pallet { use sp_core::H256; use sp_domains::bundle_producer_election::ProofOfElectionError; use sp_domains::{ - BundleDigest, ConfirmedDomainBlock, DomainId, EpochIndex, GenesisDomain, OperatorAllowList, - OperatorId, OperatorPublicKey, RuntimeId, RuntimeType, + BundleDigest, ConfirmedDomainBlock, DomainId, DomainsTransfersTracker, EpochIndex, + GenesisDomain, OperatorAllowList, OperatorId, OperatorPublicKey, RuntimeId, RuntimeType, }; use sp_domains_fraud_proof::fraud_proof::FraudProof; use sp_domains_fraud_proof::InvalidTransactionCode; @@ -311,6 +311,9 @@ mod pallet { /// Storage fee interface used to deal with bundle storage fee type StorageFee: StorageFee>; + + /// Transfers tracker. + type DomainsTransfersTracker: DomainsTransfersTracker>; } #[pallet::pallet] @@ -333,6 +336,7 @@ mod pallet { /// Starting EVM chain ID for evm runtimes. pub struct StartingEVMChainId; + impl Get for StartingEVMChainId { fn get() -> EVMChainId { // after looking at `https://chainlist.org/?testnets=false` @@ -1497,10 +1501,12 @@ impl Pallet { let domain_obj = DomainRegistry::::get(domain_id)?; let runtime_object = RuntimeRegistry::::get(domain_obj.domain_config.runtime_id)?; let runtime_type = runtime_object.runtime_type.clone(); + let total_issuance = domain_obj.domain_config.total_issuance()?; let raw_genesis = runtime_object .into_complete_raw_genesis::( domain_id, domain_obj.domain_runtime_info, + total_issuance, domain_obj.domain_config.initial_balances, ) .ok()?; diff --git a/crates/pallet-domains/src/runtime_registry.rs b/crates/pallet-domains/src/runtime_registry.rs index 0be55940fb4..49dbce5c9a5 100644 --- a/crates/pallet-domains/src/runtime_registry.rs +++ b/crates/pallet-domains/src/runtime_registry.rs @@ -31,7 +31,6 @@ pub enum Error { MaxScheduledBlockNumber, FailedToDecodeRawGenesis, RuntimeCodeNotFoundInRawGenesis, - InitialBalanceOverflow, InvalidAccountIdType, } @@ -62,19 +61,11 @@ impl Default for DomainRuntimeInfo { } fn derive_initial_balances_storages( + total_issuance: BalanceOf, balances: BTreeMap>, -) -> Result, Error> { - let mut initial_storages = vec![]; - let total_balance = balances - .iter() - .try_fold(BalanceOf::::zero(), |total, (_, balance)| { - total.checked_add(balance) - }) - .ok_or(Error::InitialBalanceOverflow)?; - +) -> Vec<(StorageKey, StorageData)> { let total_issuance_key = sp_domains::domain_total_issuance_storage_key(); - initial_storages.push((total_issuance_key, StorageData(total_balance.encode()))); - + let mut initial_storages = vec![(total_issuance_key, StorageData(total_issuance.encode()))]; for (account_id, balance) in balances { let account_storage_key = sp_domains::domain_account_storage_key(account_id); let account_info = AccountInfo { @@ -91,7 +82,7 @@ fn derive_initial_balances_storages( initial_storages.push((account_storage_key, StorageData(account_info.encode()))) } - Ok(initial_storages) + initial_storages } impl RuntimeObject { @@ -100,6 +91,7 @@ impl RuntimeObject { self, domain_id: DomainId, domain_runtime_info: DomainRuntimeInfo, + total_issuance: BalanceOf, initial_balances: Vec<(MultiAccountId, BalanceOf)>, ) -> Result { let RuntimeObject { @@ -128,8 +120,10 @@ impl RuntimeObject { }, ) .ok_or(Error::InvalidAccountIdType)?; - raw_genesis - .set_top_storages(derive_initial_balances_storages::(initial_balances)?); + raw_genesis.set_top_storages(derive_initial_balances_storages::( + total_issuance, + initial_balances, + )); } } diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index cee4530139b..aa5e3c8db9f 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -76,6 +76,7 @@ frame_support::construct_runtime!( type BlockNumber = u64; type Hash = H256; type AccountId = u128; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -207,6 +208,7 @@ impl frame_support::traits::Randomness for MockRandomness { } const SLOT_DURATION: u64 = 1000; + impl pallet_timestamp::Config for Test { /// A timestamp: milliseconds since the unix epoch. type Moment = Moment; @@ -216,6 +218,7 @@ impl pallet_timestamp::Config for Test { } pub struct DummyStorageFee; + impl StorageFee for DummyStorageFee { fn transaction_byte_fee() -> Balance { SSC @@ -223,6 +226,24 @@ impl StorageFee for DummyStorageFee { fn note_storage_fees(_fee: Balance) {} } +pub struct MockDomainsTransfersTracker; + +impl sp_domains::DomainsTransfersTracker for MockDomainsTransfersTracker { + type Error = (); + + fn balance_on_domain(_domain_id: DomainId) -> Result { + Ok(Default::default()) + } + + fn transfer_in(_domain_id: DomainId, _amount: Balance) -> Result<(), Self::Error> { + Ok(()) + } + + fn transfer_out(_domain_id: DomainId, _amount: Balance) -> Result<(), Self::Error> { + Ok(()) + } +} + impl pallet_domains::Config for Test { type RuntimeEvent = RuntimeEvent; type DomainHash = sp_core::H256; @@ -252,9 +273,11 @@ impl pallet_domains::Config for Test { type SudoId = (); type PalletId = DomainsPalletId; type StorageFee = DummyStorageFee; + type DomainsTransfersTracker = MockDomainsTransfersTracker; } pub struct ExtrinsicStorageFees; + impl domain_pallet_executive::ExtrinsicStorageFees for ExtrinsicStorageFees { fn extract_signer(_xt: MockUncheckedExtrinsic) -> (Option, DispatchInfo) { (None, DispatchInfo::default()) diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 317e3c2ecbd..e27889634fe 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -455,6 +455,14 @@ pub struct BundleDigest { pub size: u32, } +#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone, Default)] +pub struct Transfers { + /// Total transfers that came into the domain. + pub transfers_in: Balance, + /// Total transfers that went out of the domain. + pub transfers_out: Balance, +} + /// Receipt of a domain block execution. #[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)] pub struct ExecutionReceipt { @@ -487,7 +495,7 @@ pub struct ExecutionReceipt { /// storage fees are given to the consensus block author. pub block_fees: BlockFees, /// List of transfers from this Domain to other chains - pub transfers: BTreeMap, + pub transfers: Transfers, } impl diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 525f9975b1f..bbbf99ccc3e 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -604,6 +604,7 @@ impl pallet_domains::Config for Runtime { type SudoId = SudoId; type PalletId = DomainsPalletId; type StorageFee = TransactionFees; + type DomainsTransfersTracker = Transporter; } parameter_types! { diff --git a/domains/pallets/transporter/src/lib.rs b/domains/pallets/transporter/src/lib.rs index db1df15e5b6..3c9f2343a8c 100644 --- a/domains/pallets/transporter/src/lib.rs +++ b/domains/pallets/transporter/src/lib.rs @@ -76,13 +76,13 @@ mod pallet { use frame_support::traits::{Currency, ExistenceRequirement, WithdrawReasons}; use frame_support::weights::Weight; use frame_system::pallet_prelude::*; - use sp_domains::DomainId; + use sp_domains::{DomainId, Transfers}; use sp_messenger::endpoint::{ Endpoint, EndpointHandler as EndpointHandlerT, EndpointId, EndpointRequest, EndpointResponse, Sender, }; use sp_messenger::messages::ChainId; - use sp_runtime::traits::Convert; + use sp_runtime::traits::{CheckedAdd, Convert}; use sp_std::vec; #[pallet::config] @@ -133,6 +133,13 @@ mod pallet { pub(super) type DomainBalances = StorageMap<_, Identity, DomainId, BalanceOf, ValueQuery>; + /// A temporary storage that tracks total transfers from this chain. + /// Clears on on_initialize for every block. + #[pallet::storage] + #[pallet::getter(fn chain_transfers)] + pub(super) type ChainTransfers = + StorageValue<_, Transfers>, ValueQuery>; + /// Events emitted by pallet-transporter. #[pallet::event] #[pallet::generate_deposit(pub (super) fn deposit_event)] @@ -245,10 +252,27 @@ mod pallet { chain_id: dst_chain_id, message_id, }); + + ChainTransfers::::try_mutate(|transfers| { + transfers.transfers_out = transfers + .transfers_out + .checked_add(&amount) + .ok_or(Error::::BalanceOverflow)?; + Ok::<(), Error>(()) + })?; + Ok(()) } } + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + ChainTransfers::::kill(); + T::DbWeight::get().writes(1) + } + } + /// Endpoint handler implementation for pallet transporter. #[derive(Debug)] pub struct EndpointHandler(pub PhantomData); @@ -283,6 +307,15 @@ mod pallet { .ok_or(Error::::InvalidAccountId)?; let _imbalance = T::Currency::deposit_creating(&account_id, req.amount); + + ChainTransfers::::try_mutate(|transfers| { + transfers.transfers_in = transfers + .transfers_in + .checked_add(&req.amount) + .ok_or(Error::::BalanceOverflow)?; + Ok::<(), Error>(()) + })?; + frame_system::Pallet::::deposit_event(Into::<::RuntimeEvent>::into( Event::::IncomingTransferSuccessful { chain_id: src_chain_id, @@ -331,6 +364,15 @@ mod pallet { T::AccountIdConverter::try_convert_back(transfer.sender.account_id) .ok_or(Error::::InvalidAccountId)?; let _imbalance = T::Currency::deposit_creating(&account_id, transfer.amount); + + ChainTransfers::::try_mutate(|transfers| { + transfers.transfers_in = transfers + .transfers_in + .checked_add(&transfer.amount) + .ok_or(Error::::BalanceOverflow)?; + Ok::<(), Error>(()) + })?; + frame_system::Pallet::::deposit_event( Into::<::RuntimeEvent>::into( Event::::OutgoingTransferFailed { diff --git a/domains/primitives/runtime/src/lib.rs b/domains/primitives/runtime/src/lib.rs index 67fc7359c37..11f84fbd923 100644 --- a/domains/primitives/runtime/src/lib.rs +++ b/domains/primitives/runtime/src/lib.rs @@ -28,7 +28,9 @@ use parity_scale_codec::{Decode, Encode}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_runtime::generic::{Era, UncheckedExtrinsic}; -use sp_runtime::traits::{Block as BlockT, Convert, IdentifyAccount, NumberFor, Verify}; +use sp_runtime::traits::{ + Block as BlockT, CheckedAdd, Convert, IdentifyAccount, NumberFor, Verify, +}; use sp_runtime::transaction_validity::TransactionValidityError; use sp_runtime::{Digest, MultiAddress, MultiSignature, Perbill}; use sp_std::vec::Vec; @@ -209,13 +211,22 @@ pub struct BlockFees { pub domain_execution_fee: Balance, } -impl BlockFees { +impl BlockFees +where + Balance: CheckedAdd, +{ pub fn new(domain_execution_fee: Balance, consensus_storage_fee: Balance) -> Self { BlockFees { consensus_storage_fee, domain_execution_fee, } } + + /// Returns the total fees that was collected and burned on the Domain. + pub fn total_fees(&self) -> Option { + self.consensus_storage_fee + .checked_add(&self.domain_execution_fee) + } } /// Opaque types. These are used by the CLI to instantiate machinery that don't need to know @@ -308,6 +319,7 @@ sp_api::decl_runtime_apis! { #[cfg(test)] mod test { use super::block_weights; + #[test] fn test_block_weights() { // validate and build block weights diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index ea59448193a..ce0f9b0c4fb 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -640,6 +640,7 @@ impl pallet_domains::Config for Runtime { type MinNominatorStake = MinNominatorStake; type PalletId = DomainsPalletId; type StorageFee = TransactionFees; + type DomainsTransfersTracker = Transporter; } parameter_types! {