diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index 6abcee8455..4c0697535c 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -17,8 +17,8 @@ use scale_info::TypeInfo; use sp_core::Get; use sp_domains::merkle_tree::MerkleTree; use sp_domains::{ - ChainId, ConfirmedDomainBlock, DomainId, DomainsTransfersTracker, ExecutionReceipt, OperatorId, - Transfers, + ChainId, ConfirmedDomainBlock, DomainId, DomainsTransfersTracker, ExecutionReceipt, + OnChainRewards, OperatorId, Transfers, }; use sp_runtime::traits::{BlockNumberProvider, CheckedSub, One, Saturating, Zero}; use sp_std::cmp::Ordering; @@ -377,6 +377,15 @@ pub(crate) fn process_execution_receipt( update_domain_transfers::(domain_id, &execution_receipt.transfers, block_fees) .map_err(|_| Error::DomainTransfersTracking)?; + // handle chain rewards from the domain + execution_receipt + .block_fees + .chain_rewards + .into_iter() + .for_each(|(chain_id, reward)| { + T::OnChainRewards::on_chain_rewards(chain_id, reward) + }); + LatestConfirmedDomainBlock::::insert( domain_id, ConfirmedDomainBlock { diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 27f5e8ecd3..a7c7ad143a 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -40,7 +40,7 @@ use crate::domain_registry::Error as DomainRegistryError; use crate::runtime_registry::into_complete_raw_genesis; #[cfg(feature = "runtime-benchmarks")] pub use crate::staking::do_register_operator; -use crate::staking::OperatorStatus; +use crate::staking::{do_reward_operators, OperatorStatus}; use crate::staking_epoch::EpochTransitionResult; use crate::weights::WeightInfo; #[cfg(not(feature = "std"))] @@ -222,7 +222,7 @@ mod pallet { use sp_domains::bundle_producer_election::ProofOfElectionError; use sp_domains::{ BundleDigest, ConfirmedDomainBlock, DomainBundleSubmitted, DomainId, - DomainsTransfersTracker, EpochIndex, GenesisDomain, OnDomainInstantiated, + DomainsTransfersTracker, EpochIndex, GenesisDomain, OnChainRewards, OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorPublicKey, OperatorSignature, RuntimeId, RuntimeObject, RuntimeType, }; @@ -418,6 +418,9 @@ mod pallet { /// Fraud proof storage key provider type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider; + + /// Hook to handle chain rewards. + type OnChainRewards: OnChainRewards>; } #[pallet::pallet] @@ -2379,6 +2382,18 @@ impl Pallet { ) } + /// Reward the active operators of this domain epoch. + pub fn reward_domain_operators(domain_id: DomainId, rewards: BalanceOf) { + // If domain is not instantiated, then we don't care at the moment. + if let Some(domain_stake_summary) = DomainStakingSummary::::get(domain_id) { + let operators = domain_stake_summary + .current_epoch_rewards + .into_keys() + .collect::>(); + let _ = do_reward_operators::(domain_id, operators.into_iter(), rewards); + } + } + #[cfg(not(feature = "runtime-benchmarks"))] fn actual_slash_operator_weight(slashed_nominators: u32) -> Weight { T::WeightInfo::slash_operator(slashed_nominators) @@ -2485,6 +2500,11 @@ impl Pallet { Ok(leaf_data.state_root()) } + + /// Returns true if the Domain is registered. + pub fn is_domain_registered(domain_id: DomainId) -> bool { + DomainStakingSummary::::contains_key(domain_id) + } } impl sp_domains::DomainOwner for Pallet { diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 52c9b6fe2b..0dfcfe83c5 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -280,6 +280,7 @@ impl pallet_domains::Config for Test { type MmrHash = H256; type MmrProofVerifier = (); type FraudProofStorageKeyProvider = (); + type OnChainRewards = (); } pub struct ExtrinsicStorageFees; diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index 3bffaeb972..3a4bcabb5f 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -279,6 +279,10 @@ pub struct BlockFees { pub domain_execution_fee: Balance, /// Burned balances on domain chain pub burned_balance: Balance, + /// Rewards for the chain. + // TODO: remove this before mainnet. Skipping to maintain compatibility with Gemini + #[codec(skip)] + pub chain_rewards: BTreeMap, } impl BlockFees @@ -289,11 +293,13 @@ where domain_execution_fee: Balance, consensus_storage_fee: Balance, burned_balance: Balance, + chain_rewards: BTreeMap, ) -> Self { BlockFees { consensus_storage_fee, domain_execution_fee, burned_balance, + chain_rewards, } } @@ -1393,6 +1399,15 @@ pub struct OperatorSigningKeyProofOfOwnershipData { pub operator_owner: AccountId, } +/// Hook to handle chain rewards. +pub trait OnChainRewards { + fn on_chain_rewards(chain_id: ChainId, reward: Balance); +} + +impl OnChainRewards for () { + fn on_chain_rewards(_chain_id: ChainId, _reward: Balance) {} +} + sp_api::decl_runtime_apis! { /// API necessary for domains pallet. #[api_version(4)] diff --git a/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs b/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs index bbe43c5045..46c9ffa0ac 100644 --- a/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs +++ b/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs @@ -129,8 +129,12 @@ where match bad_receipt_type { BadReceiptType::BlockFees => { - receipt.block_fees = - BlockFees::new(random_seed.into(), random_seed.into(), random_seed.into()); + receipt.block_fees = BlockFees::new( + random_seed.into(), + random_seed.into(), + random_seed.into(), + BTreeMap::default(), + ); } BadReceiptType::Transfers => { receipt.transfers.transfers_in = diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 4de14c1731..99ba044018 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -83,7 +83,7 @@ use sp_runtime::traits::{ }; use sp_runtime::transaction_validity::{TransactionSource, TransactionValidity}; use sp_runtime::{ - create_runtime_str, generic, AccountId32, ApplyExtrinsicResult, ExtrinsicInclusionMode, + create_runtime_str, generic, AccountId32, ApplyExtrinsicResult, ExtrinsicInclusionMode, Perbill, }; use sp_std::collections::btree_map::BTreeMap; use sp_std::marker::PhantomData; @@ -450,6 +450,14 @@ impl sp_messenger::OnXDMRewards for OnXDMRewards { let _ = Balances::deposit_creating(&block_author, reward); } } + + fn on_chain_protocol_fees(chain_id: ChainId, fees: Balance) { + // on consensus chain, reward the domain operators + // balance is already on this consensus runtime + if let ChainId::Domain(domain_id) = chain_id { + Domains::reward_domain_operators(domain_id, fees) + } + } } pub struct MmrProofVerifier; @@ -507,6 +515,14 @@ impl sp_messenger::StorageKeys for StorageKeys { parameter_types! { // TODO: update value pub const ChannelReserveFee: Balance = 100 * SSC; + pub const ChannelInitReservePortion: Perbill = Perbill::from_percent(20); +} + +pub struct DomainRegistration; +impl sp_messenger::DomainRegistration for DomainRegistration { + fn is_domain_registered(domain_id: DomainId) -> bool { + Domains::is_domain_registered(domain_id) + } } impl pallet_messenger::Config for Runtime { @@ -531,6 +547,8 @@ impl pallet_messenger::Config for Runtime { type DomainOwner = Domains; type HoldIdentifier = HoldIdentifier; type ChannelReserveFee = ChannelReserveFee; + type ChannelInitReservePortion = ChannelInitReservePortion; + type DomainRegistration = DomainRegistration; } impl frame_system::offchain::SendTransactionTypes for Runtime @@ -627,6 +645,21 @@ impl pallet_domains::BlockSlot for BlockSlot { } } +pub struct OnChainRewards; + +impl sp_domains::OnChainRewards for OnChainRewards { + fn on_chain_rewards(chain_id: ChainId, reward: Balance) { + match chain_id { + ChainId::Consensus => { + if let Some(block_author) = Subspace::find_block_reward_address() { + let _ = Balances::deposit_creating(&block_author, reward); + } + } + ChainId::Domain(domain_id) => Domains::reward_domain_operators(domain_id, reward), + } + } +} + impl pallet_domains::Config for Runtime { type RuntimeEvent = RuntimeEvent; type DomainHash = DomainHash; @@ -666,6 +699,7 @@ impl pallet_domains::Config for Runtime { type MmrHash = mmr::Hash; type MmrProofVerifier = MmrProofVerifier; type FraudProofStorageKeyProvider = StorageKeyProvider; + type OnChainRewards = OnChainRewards; } parameter_types! { diff --git a/domains/pallets/block-fees/src/lib.rs b/domains/pallets/block-fees/src/lib.rs index 8d7555968a..ba2d95127b 100644 --- a/domains/pallets/block-fees/src/lib.rs +++ b/domains/pallets/block-fees/src/lib.rs @@ -34,7 +34,7 @@ mod pallet { use frame_system::pallet_prelude::*; use scale_info::TypeInfo; use sp_block_fees::{InherentError, InherentType, INHERENT_IDENTIFIER}; - use sp_domains::BlockFees; + use sp_domains::{BlockFees, ChainId}; use sp_runtime::traits::{AtLeast32BitUnsigned, MaybeSerializeDeserialize, Saturating}; use sp_runtime::{FixedPointOperand, SaturatedConversion}; use sp_std::fmt::Debug; @@ -194,6 +194,17 @@ mod pallet { }); } + /// Note chain reward fees. + pub fn note_chain_rewards(chain_id: ChainId, balance: T::Balance) { + CollectedBlockFees::::mutate(|block_fees| { + let total_balance = match block_fees.chain_rewards.get(&chain_id) { + None => balance, + Some(prev_balance) => prev_balance.saturating_add(balance), + }; + block_fees.chain_rewards.insert(chain_id, total_balance) + }); + } + /// Return the final domain transaction byte fee, which consist of: /// - The `ConsensusChainByteFee` for the consensus chain storage cost since the domain /// transaction need to be bundled and submitted to the consensus chain first. diff --git a/domains/pallets/messenger/src/benchmarking.rs b/domains/pallets/messenger/src/benchmarking.rs index fc482bfb26..5d5bacd645 100644 --- a/domains/pallets/messenger/src/benchmarking.rs +++ b/domains/pallets/messenger/src/benchmarking.rs @@ -84,6 +84,7 @@ mod benchmarks { dst_chain_id, dummy_channel_params::(), None, + true, )); let channel = Channels::::get(dst_chain_id, channel_id).expect("channel should exist"); assert_eq!(channel.state, ChannelState::Initiated); @@ -248,7 +249,12 @@ mod benchmarks { let channel_id = NextChannelId::::get(dst_chain_id); let list = BTreeSet::from([dst_chain_id]); ChainAllowlist::::put(list); - assert_ok!(Messenger::::do_init_channel(dst_chain_id, params, None)); + assert_ok!(Messenger::::do_init_channel( + dst_chain_id, + params, + None, + true + )); let channel = Channels::::get(dst_chain_id, channel_id).expect("channel should exist"); assert_eq!(channel.state, ChannelState::Initiated); diff --git a/domains/pallets/messenger/src/lib.rs b/domains/pallets/messenger/src/lib.rs index abc2f35681..0e7e40a31c 100644 --- a/domains/pallets/messenger/src/lib.rs +++ b/domains/pallets/messenger/src/lib.rs @@ -18,7 +18,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![forbid(unsafe_code)] #![warn(rust_2018_idioms)] -#![feature(let_chains, variant_count)] +#![feature(let_chains, variant_count, if_let_guard)] #[cfg(feature = "runtime-benchmarks")] mod benchmarking; @@ -159,9 +159,10 @@ mod pallet { MessageWeightTag, Payload, ProtocolMessageRequest, RequestResponse, VersionedPayload, }; use sp_messenger::{ - InherentError, InherentType, OnXDMRewards, StorageKeys, INHERENT_IDENTIFIER, + DomainRegistration, InherentError, InherentType, OnXDMRewards, StorageKeys, + INHERENT_IDENTIFIER, }; - use sp_runtime::ArithmeticError; + use sp_runtime::{ArithmeticError, Perbill, Saturating}; use sp_subspace_mmr::MmrProofVerifier; #[cfg(feature = "std")] use std::collections::BTreeSet; @@ -201,6 +202,12 @@ mod pallet { /// Channel reserve fee to open a channel. #[pallet::constant] type ChannelReserveFee: Get>; + /// Portion of Channel reserve taken by the protocol + /// if the channel is in init state and is requested to be closed. + #[pallet::constant] + type ChannelInitReservePortion: Get; + /// Type to check if a given domain is registered on Consensus chain. + type DomainRegistration: DomainRegistration; } /// Pallet messenger used to communicate between chains and other blockchains. @@ -562,7 +569,8 @@ mod pallet { let owner = ensure_signed(origin)?; // initiate the channel config - let channel_id = Self::do_init_channel(dst_chain_id, params, Some(owner.clone()))?; + let channel_id = + Self::do_init_channel(dst_chain_id, params, Some(owner.clone()), true)?; // reserve channel open fees let hold_id = T::HoldIdentifier::messenger_channel(dst_chain_id, channel_id); @@ -661,6 +669,13 @@ mod pallet { Error::::InvalidAllowedChain ); + if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update { + ensure!( + T::DomainRegistration::is_domain_registered(domain_id), + Error::::InvalidChain + ); + } + ChainAllowlist::::mutate(|list| match update { ChainAllowlistUpdate::Add(chain_id) => list.insert(chain_id), ChainAllowlistUpdate::Remove(chain_id) => list.remove(&chain_id), @@ -691,6 +706,13 @@ mod pallet { ensure!(dst_domain_id != domain_id, Error::::InvalidAllowedChain); } + if let ChainAllowlistUpdate::Add(ChainId::Domain(domain_id)) = update { + ensure!( + T::DomainRegistration::is_domain_registered(domain_id), + Error::::InvalidChain + ); + } + DomainChainAllowlistUpdate::::mutate(domain_id, |maybe_domain_updates| { let mut domain_updates = maybe_domain_updates.take().unwrap_or_default(); match update { @@ -844,7 +866,7 @@ mod pallet { max_outgoing_messages: 100, fee_model, }; - let channel_id = Self::do_init_channel(dst_chain_id, init_params, None)?; + let channel_id = Self::do_init_channel(dst_chain_id, init_params, None, true)?; Self::do_open_channel(dst_chain_id, channel_id)?; Ok(()) } @@ -924,7 +946,7 @@ mod pallet { let channel = maybe_channel.as_mut().ok_or(Error::::MissingChannel)?; ensure!( - channel.state == ChannelState::Open, + channel.state != ChannelState::Closed, Error::::InvalidChannelState ); @@ -935,7 +957,24 @@ mod pallet { if let Some(owner) = &channel.maybe_owner { let hold_id = T::HoldIdentifier::messenger_channel(chain_id, channel_id); let locked_amount = T::Currency::balance_on_hold(&hold_id, owner); - T::Currency::release(&hold_id, owner, locked_amount, Precision::Exact) + let amount_to_release = { + if channel.state == ChannelState::Open { + locked_amount + } else { + let protocol_fee = T::ChannelInitReservePortion::get() * locked_amount; + let release_amount = locked_amount.saturating_sub(protocol_fee); + T::Currency::burn_held( + &hold_id, + owner, + protocol_fee, + Precision::Exact, + Fortitude::Force, + )?; + T::OnXDMRewards::on_chain_protocol_fees(chain_id, protocol_fee); + release_amount + } + }; + T::Currency::release(&hold_id, owner, amount_to_release, Precision::Exact) .map_err(|_| Error::::BalanceUnlock)?; } @@ -955,17 +994,20 @@ mod pallet { dst_chain_id: ChainId, init_params: InitiateChannelParams>, maybe_owner: Option, + check_allowlist: bool, ) -> Result { ensure!( T::SelfChainId::get() != dst_chain_id, Error::::InvalidChain, ); - let chain_allowlist = ChainAllowlist::::get(); - ensure!( - chain_allowlist.contains(&dst_chain_id), - Error::::ChainNotAllowed - ); + if check_allowlist { + let chain_allowlist = ChainAllowlist::::get(); + ensure!( + chain_allowlist.contains(&dst_chain_id), + Error::::ChainNotAllowed + ); + } let channel_id = NextChannelId::::get(dst_chain_id); let next_channel_id = channel_id @@ -998,33 +1040,27 @@ mod pallet { pub fn validate_relay_message( xdm: &CrossDomainMessage, T::Hash, T::MmrHash>, ) -> Result>, TransactionValidityError> { - let mut should_init_channel = false; - let next_nonce = match Channels::::get(xdm.src_chain_id, xdm.channel_id) { - None => { - // if there is no channel config, this must the Channel open request. - // so nonce is 0 - should_init_channel = true; - log::debug!( - "Initiating new channel: {:?} to chain: {:?}", - xdm.channel_id, - xdm.src_chain_id - ); - Nonce::zero() - } - Some(channel) => { - // Ensure channel is ready to receive messages - log::debug!( - "Message to channel: {:?} from chain: {:?}", - xdm.channel_id, - xdm.src_chain_id - ); - ensure!( - channel.state == ChannelState::Open, - InvalidTransaction::Call - ); - channel.next_inbox_nonce - } - }; + let (next_nonce, maybe_channel) = + match Channels::::get(xdm.src_chain_id, xdm.channel_id) { + None => { + // if there is no channel config, this must the Channel open request. + // so nonce is 0 + log::debug!( + "Initiating new channel: {:?} to chain: {:?}", + xdm.channel_id, + xdm.src_chain_id + ); + (Nonce::zero(), None) + } + Some(channel) => { + log::debug!( + "Message to channel: {:?} from chain: {:?}", + xdm.channel_id, + xdm.src_chain_id + ); + (channel.next_inbox_nonce, Some(channel)) + } + }; // derive the key as stored on the src_chain. let key = StorageKey( @@ -1038,23 +1074,46 @@ mod pallet { // verify and decode message let msg = Self::do_verify_xdm(next_nonce, key, xdm)?; - // if there is no channel config, this must be the Channel open request - if should_init_channel { - match msg.payload { - VersionedPayload::V0(Payload::Protocol(RequestResponse::Request( - ProtocolMessageRequest::ChannelOpen(_), - ))) => {} - _ => { - log::error!("Unexpected call instead of channel open request: {:?}", msg,); - return Err(InvalidTransaction::Call.into()); + let is_valid_call = match &msg.payload { + VersionedPayload::V0(payload) => match payload { + Payload::Protocol(RequestResponse::Request(req)) => match req { + // channel open should ensure there is no Channel present already + ProtocolMessageRequest::ChannelOpen(_) => maybe_channel.is_none(), + // we allow channel close only if it is init or open state + ProtocolMessageRequest::ChannelClose => { + if let Some(ref channel) = maybe_channel { + !(channel.state == ChannelState::Closed) + } else { + false + } + } + }, + // endpoint request messages are only allowed when + // channel is open, or + // channel is closed. Channel can be closed by dst_chain simultaneously + // while src_chain already sent a message. We allow the message but return an + // error in the response so that src_chain can revert any necessary actions + Payload::Endpoint(RequestResponse::Request(_)) => { + if let Some(ref channel) = maybe_channel { + !(channel.state == ChannelState::Initiated) + } else { + false + } } - } + // any other message variants are not allowed + _ => false, + }, + }; + + if !is_valid_call { + log::error!("Unexpected call instead of channel open request: {:?}", msg,); + return Err(InvalidTransaction::Call.into()); } Ok(ValidatedRelayMessage { msg, next_nonce, - should_init_channel, + should_init_channel: maybe_channel.is_none(), }) } @@ -1069,15 +1128,18 @@ mod pallet { { // channel is being opened without an owner since this is a relay message // from other chain - Self::do_init_channel(msg.src_chain_id, params, None).map_err(|err| { - log::error!( - "Error initiating channel: {:?} with chain: {:?}: {:?}", - msg.channel_id, - msg.src_chain_id, - err - ); - InvalidTransaction::Call - })?; + // we do not check the allowlist to finish the end to end flow + Self::do_init_channel(msg.src_chain_id, params, None, false).map_err( + |err| { + log::error!( + "Error initiating channel: {:?} with chain: {:?}: {:?}", + msg.channel_id, + msg.src_chain_id, + err + ); + InvalidTransaction::Call + }, + )?; } else { log::error!("Unexpected call instead of channel open request: {:?}", msg,); return Err(InvalidTransaction::Call.into()); diff --git a/domains/pallets/messenger/src/messages.rs b/domains/pallets/messenger/src/messages.rs index fa8744013d..a32890b6be 100644 --- a/domains/pallets/messenger/src/messages.rs +++ b/domains/pallets/messenger/src/messages.rs @@ -3,8 +3,9 @@ extern crate alloc; use crate::pallet::ChainAllowlist; use crate::{ - BalanceOf, BlockMessages as BlockMessagesStore, ChannelId, Channels, CloseChannelBy, Config, - Error, Event, InboxResponses, Nonce, Outbox, OutboxMessageResult, Pallet, + BalanceOf, BlockMessages as BlockMessagesStore, ChannelId, ChannelState, Channels, + CloseChannelBy, Config, Error, Event, InboxResponses, Nonce, Outbox, OutboxMessageResult, + Pallet, }; #[cfg(not(feature = "std"))] use alloc::boxed::Box; @@ -225,6 +226,12 @@ impl Pallet { return Err(Error::::WeightTagNotMatch.into()); } + let channel = + Channels::::get(dst_chain_id, channel_id).ok_or(Error::::MissingChannel)?; + if channel.state != ChannelState::Open { + return Err(Error::::InvalidChannelState.into()); + } + // store fees for inbox message execution Self::store_fees_for_inbox_message( (dst_chain_id, (channel_id, nonce)), diff --git a/domains/pallets/messenger/src/mock.rs b/domains/pallets/messenger/src/mock.rs index 36cfa25eda..8e2b9ccbeb 100644 --- a/domains/pallets/messenger/src/mock.rs +++ b/domains/pallets/messenger/src/mock.rs @@ -38,6 +38,8 @@ macro_rules! impl_runtime { use sp_domains::MessengerHoldIdentifier; use frame_support::traits::VariantCount; use core::mem; + use sp_runtime::Perbill; + use sp_domains::DomainId; type Block = frame_system::mocking::MockBlock; @@ -59,6 +61,7 @@ macro_rules! impl_runtime { parameter_types! { pub SelfChainId: ChainId = $chain_id.into(); pub const ChannelReserveFee: Balance = 10; + pub const ChannelInitReservePortion: Perbill = Perbill::from_percent(20); } #[derive( @@ -78,6 +81,13 @@ macro_rules! impl_runtime { } } + pub struct DomainRegistration; + impl sp_messenger::DomainRegistration for DomainRegistration { + fn is_domain_registered(_domain_id: DomainId) -> bool { + true + } + } + impl crate::Config for $runtime { type RuntimeEvent = RuntimeEvent; type SelfChainId = SelfChainId; @@ -90,7 +100,9 @@ macro_rules! impl_runtime { type StorageKeys = (); type DomainOwner = (); type ChannelReserveFee = ChannelReserveFee; + type ChannelInitReservePortion = ChannelInitReservePortion; type HoldIdentifier = MockHoldIdentifer; + type DomainRegistration = DomainRegistration; /// function to fetch endpoint response handler by Endpoint. fn get_endpoint_handler( #[allow(unused_variables)] endpoint: &Endpoint, diff --git a/domains/pallets/messenger/src/tests.rs b/domains/pallets/messenger/src/tests.rs index 3c595ea81b..00a4939413 100644 --- a/domains/pallets/messenger/src/tests.rs +++ b/domains/pallets/messenger/src/tests.rs @@ -12,6 +12,7 @@ use crate::{ OutboxResponses, Pallet, U256, }; use frame_support::traits::fungible::Inspect; +use frame_support::traits::tokens::{Fortitude, Preservation}; use frame_support::{assert_err, assert_ok}; use pallet_transporter::Location; use sp_core::storage::StorageKey; @@ -160,19 +161,6 @@ fn test_close_missing_channel() { }); } -#[test] -fn test_close_not_open_channel() { - new_chain_a_ext().execute_with(|| { - let chain_id = 2.into(); - let channel_id = U256::zero(); - create_channel(chain_id, channel_id, Default::default()); - assert_err!( - Messenger::close_channel(RuntimeOrigin::root(), chain_id, channel_id,), - Error::::InvalidChannelState - ); - }); -} - #[test] fn test_close_open_channel() { new_chain_a_ext().execute_with(|| { @@ -286,6 +274,7 @@ fn open_channel_between_chains( true, MessageWeightTag::ProtocolChannelOpen, None, + true, ); // check channel state be open on chain_b @@ -372,6 +361,7 @@ fn send_message_between_chains( false, Default::default(), Some(Endpoint::Id(0)), + true, ); // check state on chain_b @@ -422,6 +412,7 @@ fn close_channel_between_chains( true, MessageWeightTag::ProtocolChannelClose, None, + true, ); // check channel state be close on chain_b @@ -483,6 +474,7 @@ fn force_toggle_channel_state( dst_chain_id: ChainId, channel_id: ChannelId, toggle: bool, + add_to_allow_list: bool, ) { let fee_model = FeeModel { relay_fee: Default::default(), @@ -494,8 +486,11 @@ fn force_toggle_channel_state( let channel = Pallet::::channels(dst_chain_id, channel_id).unwrap_or_else(|| { let list = BTreeSet::from([dst_chain_id]); - ChainAllowlist::::put(list); - Pallet::::do_init_channel(dst_chain_id, init_params, None).unwrap(); + if add_to_allow_list { + ChainAllowlist::::put(list); + } + Pallet::::do_init_channel(dst_chain_id, init_params, None, add_to_allow_list) + .unwrap(); Pallet::::channels(dst_chain_id, channel_id).unwrap() }); @@ -513,6 +508,7 @@ fn force_toggle_channel_state( } } +#[allow(clippy::too_many_arguments)] fn channel_relay_request_and_response( chain_a_test_ext: &mut TestExternalities, chain_b_test_ext: &mut TestExternalities, @@ -521,6 +517,7 @@ fn channel_relay_request_and_response( toggle_channel_state: bool, weight_tag: MessageWeightTag, maybe_endpoint: Option, + add_to_allowlist: bool, ) { let chain_a_id = chain_a::SelfChainId::get(); let chain_b_id = chain_b::SelfChainId::get(); @@ -555,6 +552,7 @@ fn channel_relay_request_and_response( chain_a_id, channel_id, toggle_channel_state, + add_to_allowlist, ); Inbox::::set(Some(msg)); @@ -611,6 +609,7 @@ fn channel_relay_request_and_response( chain_b_id, channel_id, toggle_channel_state, + true, ); OutboxResponses::::set(Some(msg)); @@ -666,6 +665,97 @@ fn test_close_channel_between_chains() { close_channel_between_chains(&mut chain_a_test_ext, &mut chain_b_test_ext, channel_id) } +#[test] +fn close_init_channels_between_chains() { + let mut chain_a_test_ext = chain_a::new_test_ext(); + let mut chain_b_test_ext = chain_b::new_test_ext(); + + let chain_a_id = chain_a::SelfChainId::get(); + let chain_b_id = chain_b::SelfChainId::get(); + + let fee_model = FeeModel { + relay_fee: Default::default(), + }; + + let pre_user_account_balance = chain_a_test_ext.execute_with(|| { + >>::reducible_balance( + &USER_ACCOUNT, + Preservation::Protect, + Fortitude::Polite, + ) + }); + + // initiate channel open on chain_a + let channel_id = chain_a_test_ext.execute_with(|| -> ChannelId { + let channel_id = U256::zero(); + create_channel(chain_b_id, channel_id, fee_model); + channel_id + }); + + chain_a_test_ext.execute_with(|| { + let channel = Channels::::get(chain_b_id, channel_id).unwrap(); + assert_eq!(channel.state, ChannelState::Initiated) + }); + + let post_channel_init_balance = chain_a_test_ext.execute_with(|| { + >>::reducible_balance( + &USER_ACCOUNT, + Preservation::Protect, + Fortitude::Polite, + ) + }); + + assert_eq!( + post_channel_init_balance, + pre_user_account_balance - chain_a::ChannelReserveFee::get() + ); + + channel_relay_request_and_response( + &mut chain_a_test_ext, + &mut chain_b_test_ext, + channel_id, + Nonce::zero(), + false, + MessageWeightTag::ProtocolChannelOpen, + None, + false, + ); + + chain_a_test_ext.execute_with(|| { + let channel = Channels::::get(chain_b_id, channel_id).unwrap(); + assert_eq!(channel.state, ChannelState::Initiated) + }); + + chain_b_test_ext.execute_with(|| { + let channel = Channels::::get(chain_a_id, channel_id).unwrap(); + assert_eq!(channel.state, ChannelState::Initiated) + }); + + // close channel + chain_a_test_ext.execute_with(|| close_channel(chain_b_id, channel_id, Some(Nonce::zero()))); + + chain_a_test_ext.execute_with(|| { + let channel = Channels::::get(chain_b_id, channel_id).unwrap(); + assert_eq!(channel.state, ChannelState::Closed) + }); + + let post_channel_close_balance = chain_a_test_ext.execute_with(|| { + >>::reducible_balance( + &USER_ACCOUNT, + Preservation::Protect, + Fortitude::Polite, + ) + }); + + // user will only get 80% of reserve since 20% is taken by the protocol + let protocol_fee = + chain_a::ChannelInitReservePortion::get() * chain_a::ChannelReserveFee::get(); + assert_eq!( + post_channel_close_balance, + pre_user_account_balance - protocol_fee + ); +} + #[test] fn test_update_consensus_channel_allowlist() { let mut consensus_chain_test_ext = consensus_chain::new_test_ext(); @@ -860,6 +950,7 @@ fn test_transport_funds_between_chains() { false, Default::default(), Some(Endpoint::Id(100)), + true, ); // post check @@ -894,6 +985,7 @@ fn test_transport_funds_between_chains_if_src_chain_disallows_after_message_is_s false, Default::default(), Some(Endpoint::Id(100)), + true, ); // post check should be successful since the chain_a already initiated the @@ -940,6 +1032,7 @@ fn test_transport_funds_between_chains_if_dst_chain_disallows_after_message_is_s false, Default::default(), Some(Endpoint::Id(100)), + true, ); // post check should be not be successful since the chain_b rejected the transfer diff --git a/domains/primitives/messenger/src/lib.rs b/domains/primitives/messenger/src/lib.rs index 1ffcf8fe1a..06e6175f32 100644 --- a/domains/primitives/messenger/src/lib.rs +++ b/domains/primitives/messenger/src/lib.rs @@ -39,10 +39,24 @@ pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"messengr"; /// Trait to handle XDM rewards. pub trait OnXDMRewards { fn on_xdm_rewards(rewards: Balance); + fn on_chain_protocol_fees(chain_id: ChainId, fees: Balance); } impl OnXDMRewards for () { fn on_xdm_rewards(_: Balance) {} + + fn on_chain_protocol_fees(_chain_id: ChainId, _fees: Balance) {} +} + +/// Trait to check if the domain is registered. +pub trait DomainRegistration { + fn is_domain_registered(domain_id: DomainId) -> bool; +} + +impl DomainRegistration for () { + fn is_domain_registered(_domain_id: DomainId) -> bool { + false + } } /// Trait that return various storage keys for storages on Consensus chain and domains diff --git a/domains/runtime/auto-id/src/lib.rs b/domains/runtime/auto-id/src/lib.rs index f49ac8cb5e..8ab6de76b7 100644 --- a/domains/runtime/auto-id/src/lib.rs +++ b/domains/runtime/auto-id/src/lib.rs @@ -314,6 +314,12 @@ impl sp_messenger::OnXDMRewards for OnXDMRewards { fn on_xdm_rewards(rewards: Balance) { BlockFees::note_domain_execution_fee(rewards) } + fn on_chain_protocol_fees(chain_id: ChainId, fees: Balance) { + // note the burned balance from this chain + BlockFees::note_burned_balance(fees); + // note the chain rewards + BlockFees::note_chain_rewards(chain_id, fees); + } } type MmrHash = ::Output; @@ -387,6 +393,7 @@ impl pallet_messenger::HoldIdentifier for HoldIdentifier { parameter_types! { pub const ChannelReserveFee: Balance = 100 * SSC; + pub const ChannelInitReservePortion: Perbill = Perbill::from_percent(20); } impl pallet_messenger::Config for Runtime { @@ -411,6 +418,8 @@ impl pallet_messenger::Config for Runtime { type DomainOwner = (); type HoldIdentifier = HoldIdentifier; type ChannelReserveFee = ChannelReserveFee; + type ChannelInitReservePortion = ChannelInitReservePortion; + type DomainRegistration = (); } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/domains/runtime/evm/src/lib.rs b/domains/runtime/evm/src/lib.rs index fe47c3e630..6ebd2671b7 100644 --- a/domains/runtime/evm/src/lib.rs +++ b/domains/runtime/evm/src/lib.rs @@ -443,6 +443,13 @@ impl sp_messenger::OnXDMRewards for OnXDMRewards { fn on_xdm_rewards(rewards: Balance) { BlockFees::note_domain_execution_fee(rewards) } + + fn on_chain_protocol_fees(chain_id: ChainId, fees: Balance) { + // note the burned balance from this chain + BlockFees::note_burned_balance(fees); + // note the chain rewards + BlockFees::note_chain_rewards(chain_id, fees); + } } type MmrHash = ::Output; @@ -516,6 +523,7 @@ impl pallet_messenger::HoldIdentifier for HoldIdentifier { parameter_types! { pub const ChannelReserveFee: Balance = 100 * SSC; + pub const ChannelInitReservePortion: Perbill = Perbill::from_percent(20); } impl pallet_messenger::Config for Runtime { @@ -540,6 +548,8 @@ impl pallet_messenger::Config for Runtime { type DomainOwner = (); type HoldIdentifier = HoldIdentifier; type ChannelReserveFee = ChannelReserveFee; + type ChannelInitReservePortion = ChannelInitReservePortion; + type DomainRegistration = (); } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/domains/test/runtime/evm/src/lib.rs b/domains/test/runtime/evm/src/lib.rs index 58bb39df82..bc1b06aff1 100644 --- a/domains/test/runtime/evm/src/lib.rs +++ b/domains/test/runtime/evm/src/lib.rs @@ -429,6 +429,12 @@ impl sp_messenger::OnXDMRewards for OnXDMRewards { fn on_xdm_rewards(rewards: Balance) { BlockFees::note_domain_execution_fee(rewards) } + fn on_chain_protocol_fees(chain_id: ChainId, fees: Balance) { + // note the burned balance from this chain + BlockFees::note_burned_balance(fees); + // note the chain rewards + BlockFees::note_chain_rewards(chain_id, fees); + } } type MmrHash = ::Output; @@ -476,6 +482,7 @@ impl pallet_messenger::HoldIdentifier for HoldIdentifier { parameter_types! { pub const ChannelReserveFee: Balance = SSC; + pub const ChannelInitReservePortion: Perbill = Perbill::from_percent(20); } pub struct StorageKeys; @@ -522,6 +529,8 @@ impl pallet_messenger::Config for Runtime { type DomainOwner = (); type HoldIdentifier = HoldIdentifier; type ChannelReserveFee = ChannelReserveFee; + type ChannelInitReservePortion = ChannelInitReservePortion; + type DomainRegistration = (); } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index a6896a24e6..2d16d8edab 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -93,7 +93,8 @@ use subspace_core_primitives::{ SolutionRange, U256, }; use subspace_runtime_primitives::{ - AccountId, Balance, BlockNumber, Hash, Moment, Nonce, Signature, MIN_REPLICATION_FACTOR, + AccountId, Balance, BlockNumber, FindBlockRewardAddress, Hash, Moment, Nonce, Signature, + MIN_REPLICATION_FACTOR, }; sp_runtime::impl_opaque_keys! { @@ -593,8 +594,16 @@ impl sp_messenger::StorageKeys for StorageKeys { } } +pub struct DomainRegistration; +impl sp_messenger::DomainRegistration for DomainRegistration { + fn is_domain_registered(domain_id: DomainId) -> bool { + Domains::is_domain_registered(domain_id) + } +} + parameter_types! { pub const ChannelReserveFee: Balance = SSC; + pub const ChannelInitReservePortion: Perbill = Perbill::from_percent(20); } impl pallet_messenger::Config for Runtime { @@ -619,6 +628,8 @@ impl pallet_messenger::Config for Runtime { type DomainOwner = Domains; type HoldIdentifier = HoldIdentifier; type ChannelReserveFee = ChannelReserveFee; + type ChannelInitReservePortion = ChannelInitReservePortion; + type DomainRegistration = DomainRegistration; } impl frame_system::offchain::SendTransactionTypes for Runtime @@ -705,6 +716,21 @@ impl pallet_domains::BlockSlot for BlockSlot { } } +pub struct OnChainRewards; + +impl sp_domains::OnChainRewards for OnChainRewards { + fn on_chain_rewards(chain_id: ChainId, reward: Balance) { + match chain_id { + ChainId::Consensus => { + if let Some(block_author) = Subspace::find_block_reward_address() { + let _ = Balances::deposit_creating(&block_author, reward); + } + } + ChainId::Domain(domain_id) => Domains::reward_domain_operators(domain_id, reward), + } + } +} + impl pallet_domains::Config for Runtime { type RuntimeEvent = RuntimeEvent; type DomainHash = DomainHash; @@ -744,6 +770,7 @@ impl pallet_domains::Config for Runtime { type MmrHash = mmr::Hash; type MmrProofVerifier = MmrProofVerifier; type FraudProofStorageKeyProvider = StorageKeyProvider; + type OnChainRewards = OnChainRewards; } parameter_types! {