Skip to content

Commit 75e9bd8

Browse files
authored
Merge pull request #3496 from autonomys/xdm-fee-bookkeeping
Correctly count the cross-chain XDM fee transfer and ensure the balance bookkeeping match the domain total issuance
2 parents f937e18 + e56ee32 commit 75e9bd8

File tree

18 files changed

+405
-21
lines changed

18 files changed

+405
-21
lines changed

crates/pallet-domains/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ mod benchmarking;
1010
mod tests;
1111

1212
pub mod block_tree;
13-
mod bundle_storage_fund;
13+
pub mod bundle_storage_fund;
1414
pub mod domain_registry;
1515
pub mod extensions;
1616
pub mod migrations;

crates/subspace-runtime/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -767,6 +767,7 @@ impl pallet_messenger::Config for Runtime {
767767
type MaxOutgoingMessages = MaxOutgoingMessages;
768768
type MessengerOrigin = pallet_messenger::EnsureMessengerOrigin;
769769
type MessageVersion = MessageVersion;
770+
type NoteChainTransfer = Transporter;
770771
}
771772

772773
impl<C> frame_system::offchain::CreateTransactionBase<C> for Runtime
@@ -1800,10 +1801,22 @@ impl_runtime_apis! {
18001801
#[cfg(test)]
18011802
mod tests {
18021803
use crate::{Runtime, SubspaceBlockWeights as BlockWeights};
1804+
use pallet_domains::bundle_storage_fund::AccountType;
1805+
use sp_domains::OperatorId;
1806+
use sp_runtime::traits::AccountIdConversion;
18031807
use subspace_runtime_primitives::tests_utils::FeeMultiplierUtils;
18041808

18051809
#[test]
18061810
fn multiplier_can_grow_from_zero() {
18071811
FeeMultiplierUtils::<Runtime, BlockWeights>::multiplier_can_grow_from_zero()
18081812
}
1813+
1814+
#[test]
1815+
fn test_bundle_storage_fund_account_uniqueness() {
1816+
let _: <Runtime as frame_system::Config>::AccountId = <Runtime as pallet_domains::Config>::PalletId::get()
1817+
.try_into_sub_account((AccountType::StorageFund, OperatorId::MAX))
1818+
.expect(
1819+
"The `AccountId` type must be large enough to fit the seed of the bundle storage fund account",
1820+
);
1821+
}
18091822
}

domains/client/domain-operator/src/tests.rs

+107-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ use std::time::Duration;
7575
use subspace_core_primitives::pot::PotOutput;
7676
use subspace_runtime_primitives::opaque::Block as CBlock;
7777
use subspace_runtime_primitives::{Balance, SSC};
78-
use subspace_test_primitives::DOMAINS_BLOCK_PRUNING_DEPTH;
78+
use subspace_test_primitives::{OnchainStateApi as _, DOMAINS_BLOCK_PRUNING_DEPTH};
7979
use subspace_test_runtime::Runtime;
8080
use subspace_test_service::{
8181
produce_block_with, produce_blocks, produce_blocks_until, MockConsensusNode,
@@ -8426,6 +8426,112 @@ async fn test_invalid_chain_reward_receipt() {
84268426
}
84278427
}
84288428

8429+
#[tokio::test(flavor = "multi_thread")]
8430+
async fn test_domain_total_issuance_match_consensus_chain_bookkeeping_with_xdm() {
8431+
let directory = TempDir::new().expect("Must be able to create temporary directory");
8432+
8433+
let mut builder = sc_cli::LoggerBuilder::new("");
8434+
builder.with_colors(false);
8435+
let _ = builder.init();
8436+
8437+
let tokio_handle = tokio::runtime::Handle::current();
8438+
8439+
// Start Ferdie
8440+
let mut ferdie = MockConsensusNode::run(
8441+
tokio_handle.clone(),
8442+
Sr25519Alice,
8443+
BasePath::new(directory.path().join("ferdie")),
8444+
);
8445+
8446+
// Run Alice (a system domain authority node)
8447+
let mut alice = domain_test_service::DomainNodeBuilder::new(
8448+
tokio_handle.clone(),
8449+
BasePath::new(directory.path().join("alice")),
8450+
)
8451+
.build_evm_node(Role::Authority, Alice, &mut ferdie)
8452+
.await;
8453+
8454+
// Run the cross domain gossip message worker
8455+
ferdie.start_cross_domain_gossip_message_worker();
8456+
8457+
produce_blocks!(ferdie, alice, 3).await.unwrap();
8458+
8459+
// Open XDM channel between the consensus chain and the EVM domain
8460+
open_xdm_channel(&mut ferdie, &mut alice).await;
8461+
8462+
for i in 0..10 {
8463+
let transfer_amount = 123456789987654321 * i;
8464+
let domain_to_consensus = i % 2 == 0;
8465+
8466+
// Transfer balance from
8467+
let pre_alice_free_balance = alice.free_balance(alice.key.to_account_id());
8468+
let pre_ferdie_free_balance = ferdie.free_balance(ferdie.key.to_account_id());
8469+
8470+
if domain_to_consensus {
8471+
alice
8472+
.construct_and_send_extrinsic(pallet_transporter::Call::transfer {
8473+
dst_location: pallet_transporter::Location {
8474+
chain_id: ChainId::Consensus,
8475+
account_id: AccountIdConverter::convert(Sr25519Alice.into()),
8476+
},
8477+
amount: transfer_amount,
8478+
})
8479+
.await
8480+
.expect("Failed to construct and send extrinsic");
8481+
} else {
8482+
ferdie
8483+
.construct_and_send_extrinsic_with(pallet_transporter::Call::transfer {
8484+
dst_location: pallet_transporter::Location {
8485+
chain_id: ChainId::Domain(EVM_DOMAIN_ID),
8486+
account_id: AccountId20Converter::convert(Alice.to_account_id()),
8487+
},
8488+
amount: transfer_amount,
8489+
})
8490+
.await
8491+
.expect("Failed to construct and send extrinsic");
8492+
}
8493+
8494+
// Wait until transfer succeed
8495+
produce_blocks_until!(ferdie, alice, {
8496+
let post_alice_free_balance = alice.free_balance(alice.key.to_account_id());
8497+
let post_ferdie_free_balance = ferdie.free_balance(ferdie.key.to_account_id());
8498+
8499+
if domain_to_consensus {
8500+
post_alice_free_balance <= pre_alice_free_balance - transfer_amount
8501+
&& post_ferdie_free_balance == pre_ferdie_free_balance + transfer_amount
8502+
} else {
8503+
post_ferdie_free_balance <= pre_ferdie_free_balance - transfer_amount
8504+
&& post_alice_free_balance == pre_alice_free_balance + transfer_amount
8505+
}
8506+
})
8507+
.await
8508+
.unwrap();
8509+
8510+
let ferdie_best_hash = ferdie.client.info().best_hash;
8511+
let (_, confirmed_domain_hash) = ferdie
8512+
.client
8513+
.runtime_api()
8514+
.latest_confirmed_domain_block(ferdie_best_hash, EVM_DOMAIN_ID)
8515+
.unwrap()
8516+
.unwrap();
8517+
let domain_total_balance = ferdie
8518+
.client
8519+
.runtime_api()
8520+
.domain_balance(ferdie_best_hash, EVM_DOMAIN_ID)
8521+
.unwrap();
8522+
let domain_total_issuance = alice
8523+
.client
8524+
.runtime_api()
8525+
.total_issuance(confirmed_domain_hash)
8526+
.unwrap();
8527+
assert_eq!(domain_total_balance, domain_total_issuance);
8528+
8529+
ferdie.clear_tx_pool().await.unwrap();
8530+
alice.clear_tx_pool().await;
8531+
produce_blocks!(ferdie, alice, 5).await.unwrap();
8532+
}
8533+
}
8534+
84298535
fn bundle_to_tx(
84308536
ferdie: &MockConsensusNode,
84318537
opaque_bundle: OpaqueBundleOf<Runtime>,

domains/pallets/messenger/src/fees.rs

+49-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
#[cfg(not(feature = "std"))]
22
extern crate alloc;
3-
use crate::pallet::{InboxFee, InboxResponseMessageWeightTags, InboxResponses, OutboxFee};
3+
use crate::pallet::{
4+
InboxFee, InboxFeesOnHold, InboxFeesOnHoldStartAt, InboxResponseMessageWeightTags,
5+
InboxResponses, OutboxFee, OutboxFeesOnHold, OutboxFeesOnHoldStartAt,
6+
};
47
use crate::{BalanceOf, Config, Error, Pallet};
5-
use frame_support::traits::fungible::Mutate;
8+
#[cfg(not(feature = "std"))]
9+
use alloc::vec;
10+
use frame_support::traits::fungible::{Balanced, Mutate};
611
use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
712
use frame_support::weights::WeightToFee;
813
use sp_core::Get;
914
use sp_messenger::endpoint::{CollectedFee, Endpoint};
1015
use sp_messenger::messages::{ChainId, ChannelId, FeeModel, MessageId, Nonce};
1116
use sp_messenger::OnXDMRewards;
12-
use sp_runtime::traits::{CheckedAdd, CheckedMul, Zero};
17+
use sp_runtime::traits::{CheckedAdd, CheckedMul, CheckedSub, Zero};
1318
use sp_runtime::{DispatchError, DispatchResult, Saturating};
1419

1520
impl<T: Config> Pallet<T> {
@@ -136,6 +141,9 @@ impl<T: Config> Pallet<T> {
136141

137142
let mut current_nonce = latest_confirmed_nonce;
138143
let mut inbox_fees = BalanceOf::<T>::zero();
144+
let on_hold_start_at_nonce =
145+
InboxFeesOnHoldStartAt::<T>::get(channel_id).unwrap_or(Nonce::MAX);
146+
let mut on_hold_inbox_fees = BalanceOf::<T>::zero();
139147
while let Some(nonce) = current_nonce {
140148
if InboxResponses::<T>::take((dst_chain_id, channel_id, nonce)).is_none() {
141149
// Make the loop efficient, by breaking as soon as there are no more responses.
@@ -150,12 +158,30 @@ impl<T: Config> Pallet<T> {
150158
// For every inbox response we take, distribute the reward to the operators.
151159
if let Some(inbox_fee) = InboxFee::<T>::take((dst_chain_id, (channel_id, nonce))) {
152160
inbox_fees = inbox_fees.saturating_add(inbox_fee);
161+
if on_hold_start_at_nonce <= nonce {
162+
on_hold_inbox_fees = on_hold_inbox_fees.saturating_add(inbox_fee);
163+
}
153164
}
154165

155166
current_nonce = nonce.checked_sub(Nonce::one())
156167
}
157168

158169
if !inbox_fees.is_zero() {
170+
if !on_hold_inbox_fees.is_zero() {
171+
InboxFeesOnHold::<T>::mutate(|inbox_fees_on_hold| {
172+
*inbox_fees_on_hold = inbox_fees_on_hold
173+
.checked_sub(&on_hold_inbox_fees)
174+
.ok_or(Error::<T>::BalanceUnderflow)?;
175+
176+
// If the `imbalance` is dropped without consuming it will increase the total issuance by
177+
// the same amount as we rescinded here, thus we need to manually `mem::forget` it.
178+
let imbalance = T::Currency::rescind(on_hold_inbox_fees);
179+
core::mem::forget(imbalance);
180+
181+
Ok::<(), Error<T>>(())
182+
})?;
183+
}
184+
159185
Self::reward_operators(inbox_fees);
160186
}
161187

@@ -165,10 +191,29 @@ impl<T: Config> Pallet<T> {
165191
pub(crate) fn reward_operators_for_outbox_execution(
166192
dst_chain_id: ChainId,
167193
message_id: MessageId,
168-
) {
194+
) -> DispatchResult {
169195
if let Some(fee) = OutboxFee::<T>::take((dst_chain_id, message_id)) {
196+
let update_on_hold = OutboxFeesOnHoldStartAt::<T>::get(message_id.0)
197+
.map(|start_at_nonce| start_at_nonce <= message_id.1)
198+
.unwrap_or(false);
199+
if update_on_hold {
200+
OutboxFeesOnHold::<T>::mutate(|outbox_fees_on_hold| {
201+
*outbox_fees_on_hold = outbox_fees_on_hold
202+
.checked_sub(&fee)
203+
.ok_or(Error::<T>::BalanceUnderflow)?;
204+
205+
// If the `imbalance` is dropped without consuming it will increase the total issuance by
206+
// the same amount as we rescinded here, thus we need to manually `mem::forget` it.
207+
let imbalance = T::Currency::rescind(fee);
208+
core::mem::forget(imbalance);
209+
210+
Ok::<(), Error<T>>(())
211+
})?;
212+
}
213+
170214
Self::reward_operators(fee);
171215
}
216+
Ok(())
172217
}
173218

174219
/// Increments the current block's relayer rewards.

0 commit comments

Comments
 (0)