From 1b7f29a2293e3c485ebfaceea12e226c3a740eb7 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 13 Jan 2024 04:53:53 +0800 Subject: [PATCH 01/15] Introduce the StorageFeeInterface and the PalletId for pallet-domains The StorageFeeInterface is used to query the transaction_byte_fee and collect the charged bundle storage fee to the pallet-transaction-fees, which will reward the fee to the block author later Signed-off-by: linning --- crates/pallet-domains/src/lib.rs | 8 ++++++++ crates/pallet-domains/src/tests.rs | 13 ++++++++++++- crates/pallet-transaction-fees/src/lib.rs | 13 +++++++++++++ crates/subspace-runtime-primitives/src/lib.rs | 8 ++++++++ crates/subspace-runtime/src/lib.rs | 3 +++ test/subspace-test-runtime/src/lib.rs | 3 +++ 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 18f82dfe72..83eddc00b1 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -178,6 +178,7 @@ mod pallet { use sp_std::vec; use sp_std::vec::Vec; use subspace_core_primitives::U256; + use subspace_runtime_primitives::StorageFeeInterface; #[pallet::config] pub trait Config: frame_system::Config> { @@ -305,6 +306,13 @@ mod pallet { /// The sudo account id #[pallet::constant] type SudoId: Get; + + /// The pallet-domains's pallet id. + #[pallet::constant] + type PalletId: Get; + + /// Storage fee interface used to deal with bundle storage fee + type StorageFeeInterface: StorageFeeInterface>; } #[pallet::pallet] diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 78aff02bd2..9afbb1f080 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -49,7 +49,7 @@ use sp_trie::{LayoutV1, PrefixedMemoryDB, StorageProof, TrieMut}; use sp_version::RuntimeVersion; use std::sync::atomic::{AtomicU64, Ordering}; use subspace_core_primitives::{Randomness, U256 as P256}; -use subspace_runtime_primitives::{Moment, SSC}; +use subspace_runtime_primitives::{Moment, StorageFeeInterface, SSC}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -200,6 +200,7 @@ parameter_types! { pub const BlockReward: Balance = 10 * SSC; pub const MaxPendingStakingOperation: u32 = 100; pub const MaxNominators: u32 = 5; + pub const DomainsPalletId: PalletId = PalletId(*b"ssdomain"); } pub struct MockRandomness; @@ -219,6 +220,14 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } +pub struct DummyStorageFeeInterface; +impl StorageFeeInterface for DummyStorageFeeInterface { + fn transaction_byte_fee() -> Balance { + Default::default() + } + fn note_storage_fees(_fee: Balance) {} +} + impl pallet_domains::Config for Test { type RuntimeEvent = RuntimeEvent; type DomainHash = sp_core::H256; @@ -246,6 +255,8 @@ impl pallet_domains::Config for Test { type MaxNominators = MaxNominators; type Randomness = MockRandomness; type SudoId = (); + type PalletId = DomainsPalletId; + type StorageFeeInterface = DummyStorageFeeInterface; } pub struct ExtrinsicStorageFees; diff --git a/crates/pallet-transaction-fees/src/lib.rs b/crates/pallet-transaction-fees/src/lib.rs index 4fc5be07c6..53ab8e9b59 100644 --- a/crates/pallet-transaction-fees/src/lib.rs +++ b/crates/pallet-transaction-fees/src/lib.rs @@ -306,3 +306,16 @@ where }); } } + +impl subspace_runtime_primitives::StorageFeeInterface> for Pallet +where + BalanceOf: From, +{ + fn transaction_byte_fee() -> BalanceOf { + Self::transaction_byte_fee() + } + + fn note_storage_fees(storage_fee: BalanceOf) { + Self::note_transaction_fees(storage_fee, Zero::zero(), Zero::zero()) + } +} diff --git a/crates/subspace-runtime-primitives/src/lib.rs b/crates/subspace-runtime-primitives/src/lib.rs index f7586788ca..5abca31fec 100644 --- a/crates/subspace-runtime-primitives/src/lib.rs +++ b/crates/subspace-runtime-primitives/src/lib.rs @@ -90,6 +90,14 @@ pub trait FindVotingRewardAddresses { fn find_voting_reward_addresses() -> Vec; } +pub trait StorageFeeInterface { + /// Return the consensus transaction byte fee. + fn transaction_byte_fee() -> Balance; + + /// Note the charged storage fee. + fn note_storage_fees(fee: Balance); +} + parameter_types! { /// The portion of the `NORMAL_DISPATCH_RATIO` that we adjust the fees with. Blocks filled less /// than this will decrease the weight and more will increase. diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 3c4f15d758..dac50b7a49 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -639,6 +639,7 @@ parameter_types! { pub const MaxPendingStakingOperation: u32 = 100; pub const MaxNominators: u32 = 256; pub SudoId: AccountId = Sudo::key().expect("Sudo account must exist"); + pub const DomainsPalletId: PalletId = PalletId(*b"ssdomain"); } // Minimum operator stake must be >= minimum nominator stake since operator is also a nominator. @@ -671,6 +672,8 @@ impl pallet_domains::Config for Runtime { type MaxNominators = MaxNominators; type Randomness = Subspace; type SudoId = SudoId; + type PalletId = DomainsPalletId; + type StorageFeeInterface = TransactionFees; } parameter_types! { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 2ec3247127..9e96f4c49d 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -641,6 +641,7 @@ parameter_types! { pub const MaxPendingStakingOperation: u32 = 100; pub const MaxNominators: u32 = 100; pub SudoId: AccountId = Sudo::key().expect("Sudo account must exist"); + pub const DomainsPalletId: PalletId = PalletId(*b"ssdomain"); } // Minimum operator stake must be >= minimum nominator stake since operator is also a nominator. @@ -673,6 +674,8 @@ impl pallet_domains::Config for Runtime { type Randomness = Subspace; type SudoId = SudoId; type MinNominatorStake = MinNominatorStake; + type PalletId = DomainsPalletId; + type StorageFeeInterface = TransactionFees; } parameter_types! { From ff78c53b49bd452200ad713fdf7500c9e72a0877 Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 13 Jan 2024 04:58:20 +0800 Subject: [PATCH 02/15] Collect the submitted bundle size The bundle size info will be used to refund storage fee to the operator according to the total size of bundle they have submitted. Signed-off-by: linning --- crates/pallet-domains/src/block_tree.rs | 16 +++++++++++++++- crates/pallet-domains/src/lib.rs | 10 ++++++++++ crates/sp-domains/src/lib.rs | 7 +++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index e5e8bfc23b..eae3470977 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -14,6 +14,7 @@ use sp_domains::merkle_tree::MerkleTree; use sp_domains::{DomainId, ExecutionReceipt, OperatorId}; use sp_runtime::traits::{BlockNumberProvider, CheckedSub, One, Saturating, Zero}; use sp_std::cmp::Ordering; +use sp_std::collections::btree_map::BTreeMap; use sp_std::vec::Vec; /// Block tree specific errors @@ -259,6 +260,8 @@ pub(crate) struct ConfirmedDomainBlockInfo { pub operator_ids: Vec, pub rewards: Balance, pub invalid_bundle_authors: Vec, + pub total_storage_fee: Balance, + pub front_paid_storage: BTreeMap, } pub(crate) type ProcessExecutionReceiptResult = @@ -304,7 +307,8 @@ pub(crate) fn process_execution_receipt( execution_receipt.domain_block_hash, )); - // Collect the invalid bundle author + // Collect the front paid storage and the invalid bundle author + let mut front_paid_storage = BTreeMap::new(); let mut invalid_bundle_authors = Vec::new(); let bundle_digests = ExecutionInbox::::get(( domain_id, @@ -317,6 +321,11 @@ pub(crate) fn process_execution_receipt( // the `ER::bundles` have the same length of `ExecutionInbox` if execution_receipt.inboxed_bundles[index].is_invalid() { invalid_bundle_authors.push(bundle_author); + } else { + front_paid_storage + .entry(bundle_author) + .and_modify(|s| *s += bd.size) + .or_insert(bd.size); } } } @@ -335,6 +344,9 @@ pub(crate) fn process_execution_receipt( operator_ids, rewards: execution_receipt.total_rewards, invalid_bundle_authors, + // TODO: get the `total_storage_fee` from ER + total_storage_fee: Zero::zero(), + front_paid_storage, })); } } @@ -504,6 +516,7 @@ mod tests { vec![BundleDigest { header_hash: bundle_header_hash, extrinsics_root: bundle_extrinsics_root, + size: 0, }] ); assert!(InboxedBundleAuthor::::contains_key( @@ -788,6 +801,7 @@ mod tests { .map(|b| BundleDigest { header_hash: H256::random(), extrinsics_root: b.extrinsics_root, + size: 0, }) .collect::>(), ); diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 83eddc00b1..7ce3371d27 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -635,6 +635,8 @@ mod pallet { Receipt(BlockTreeError), /// Bundle size exceed the max bundle size limit in the domain config BundleTooLarge, + /// The actual bundle size is not match with the `bundle_size` field in bundle header + InvalidBundleSize, // Bundle with an invalid extrinsic root InvalidExtrinsicRoot, /// This bundle duplicated with an already submitted bundle @@ -856,6 +858,7 @@ mod pallet { let bundle_header_hash = opaque_bundle.sealed_header.pre_hash(); let extrinsics_root = opaque_bundle.extrinsics_root(); let operator_id = opaque_bundle.operator_id(); + let bundle_size = opaque_bundle.size(); let receipt = opaque_bundle.into_receipt(); #[cfg(not(feature = "runtime-benchmarks"))] @@ -952,6 +955,7 @@ mod pallet { BundleDigest { header_hash: bundle_header_hash, extrinsics_root, + size: bundle_size, }, ); @@ -1563,7 +1567,13 @@ impl Pallet { .extrinsics .iter() .fold(0, |acc, xt| acc + xt.encoded_size() as u32); + ensure!(max_size >= bundle_size, BundleError::BundleTooLarge); + ensure!( + opaque_bundle.size() == bundle_size, + BundleError::InvalidBundleSize + ); + Ok(()) } diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index bb9450b6cb..7e5e5cb9cf 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -320,6 +320,11 @@ impl { self.sealed_header.header.receipt } + + /// Return the bundle body size in bytes + pub fn size(&self) -> u32 { + self.sealed_header.header.bundle_size + } } /// Bundle with opaque extrinsics. @@ -394,6 +399,8 @@ pub struct BundleDigest { pub header_hash: Hash, /// The Merkle root of all new extrinsics included in this bundle. pub extrinsics_root: Hash, + /// The size of the bundle body in bytes. + pub size: u32, } /// Receipt of a domain block execution. From 0a0267befe591a9633da9cfc2dfdee9ce01af1ad Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 13 Jan 2024 06:58:19 +0800 Subject: [PATCH 03/15] Introduce bundle storage fund This fund is derive from the operator and used to pay the bundle storage fee when the operator submit the bundle and refund the storage fee that collected from the domain user back to the operator when the ER is confirmed Signed-off-by: linning --- .../pallet-domains/src/bundle_storage_fund.rs | 107 ++++++++++++++++++ crates/pallet-domains/src/lib.rs | 42 ++++++- crates/sp-domains-fraud-proof/src/lib.rs | 1 + 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 crates/pallet-domains/src/bundle_storage_fund.rs diff --git a/crates/pallet-domains/src/bundle_storage_fund.rs b/crates/pallet-domains/src/bundle_storage_fund.rs new file mode 100644 index 0000000000..75f37b7477 --- /dev/null +++ b/crates/pallet-domains/src/bundle_storage_fund.rs @@ -0,0 +1,107 @@ +//! Bundle storage fund + +use crate::{BalanceOf, Config, Operators}; +use codec::{Decode, Encode}; +use frame_support::traits::fungible::Mutate; +use frame_support::traits::tokens::{Fortitude, Precision}; +use frame_support::traits::Get; +use frame_support::PalletError; +use scale_info::TypeInfo; +use sp_domains::OperatorId; +use sp_runtime::traits::{AccountIdConversion, CheckedSub, Zero}; +use sp_runtime::Perbill; +use sp_std::collections::btree_map::BTreeMap; +use subspace_runtime_primitives::StorageFeeInterface; + +/// Bundle storage fund specific errors +#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)] +pub enum Error { + FailedToDeriveStorageFundAccount, + BundleStorageFeePayment, + BalanceUnderflow, + MintBalance, +} + +/// The type of system account being created. +#[derive(Encode, Decode)] +pub enum AccountType { + StorageFund, +} + +/// Return the bundle storage fund account of the given operator. +pub fn storage_fund_account(id: OperatorId) -> Result { + T::PalletId::get() + .try_into_sub_account((AccountType::StorageFund, id)) + .ok_or(Error::FailedToDeriveStorageFundAccount) +} + +/// Charge the bundle storage fee from the operator's bundle storage fund +pub fn charge_bundle_storage_fee( + operator_id: OperatorId, + bundle_size: u32, +) -> Result<(), Error> { + if bundle_size.is_zero() { + return Ok(()); + } + + let storage_fund_acc = storage_fund_account::(operator_id)?; + let storage_fee = T::StorageFeeInterface::transaction_byte_fee() * bundle_size.into(); + + T::Currency::burn_from( + &storage_fund_acc, + storage_fee, + Precision::Exact, + Fortitude::Polite, + ) + .map_err(|_| Error::BundleStorageFeePayment)?; + + // Note the storage fee, it will go to the consensus block author + T::StorageFeeInterface::note_storage_fees(storage_fee); + + Ok(()) +} + +/// Refund the front paid storage fee of a particular domain block back to the operator, the amount to +/// refund to a particular operator is determined by the total storage fee collected from the domain user +/// and the percentage of bundle storage that the operator have submitted for the domain block. +#[allow(dead_code)] +pub fn refund_storage_fee( + total_storage_fee: BalanceOf, + front_paid_storage: BTreeMap, +) -> Result<(), Error> { + if total_storage_fee.is_zero() { + return Ok(()); + } + + let total_paid_storage = front_paid_storage.values().sum::(); + let mut remaining_fee = total_storage_fee; + for (operator_id, paid_storage) in front_paid_storage { + // If the operator is deregistered and unlocked or slashed and finalized, the refund bundle storage + // fee will go to the treasury + if Operators::::get(operator_id).is_none() || paid_storage.is_zero() { + continue; + } + + let refund_amount = { + let share = Perbill::from_rational(paid_storage, total_paid_storage); + share.mul_floor(total_storage_fee) + }; + let storage_fund_acc = storage_fund_account::(operator_id)?; + T::Currency::mint_into(&storage_fund_acc, refund_amount).map_err(|_| Error::MintBalance)?; + + remaining_fee = remaining_fee + .checked_sub(&refund_amount) + .ok_or(Error::BalanceUnderflow)?; + } + + // Drop any dust and deregistered/slashed operator's bundle storage fee to the treasury + if !remaining_fee.is_zero() { + T::Currency::mint_into(&T::TreasuryAccount::get(), remaining_fee) + .map_err(|_| Error::MintBalance)?; + } + + Ok(()) +} + +// TODO: add deposit and withdraw function of the bundle storage fee and call them then +// staking deposit/withdraw happen diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 7ce3371d27..c4a426aa4d 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -26,6 +26,7 @@ mod benchmarking; mod tests; pub mod block_tree; +mod bundle_storage_fund; pub mod domain_registry; pub mod runtime_registry; mod staking; @@ -121,6 +122,9 @@ mod pallet { execution_receipt_type, process_execution_receipt, BlockTreeNode, Error as BlockTreeError, ReceiptType, }; + #[cfg(not(feature = "runtime-benchmarks"))] + use crate::bundle_storage_fund::refund_storage_fee; + use crate::bundle_storage_fund::{charge_bundle_storage_fee, Error as BundleStorageFundError}; use crate::domain_registry::{ do_instantiate_domain, do_update_domain_allow_list, DomainConfig, DomainObject, Error as DomainRegistryError, @@ -719,6 +723,12 @@ mod pallet { } } + impl From for Error { + fn from(err: BundleStorageFundError) -> Self { + Error::BundleStorageFund(err) + } + } + #[pallet::error] pub enum Error { /// Invalid fraud proof. @@ -733,6 +743,8 @@ mod pallet { DomainRegistry(DomainRegistryError), /// Block tree specific errors BlockTree(BlockTreeError), + /// Bundle storage fund specific errors + BundleStorageFund(BundleStorageFundError), } /// Reason for slashing an operator @@ -891,6 +903,12 @@ mod pallet { // - `do_finalize_domain_current_epoch` #[cfg(not(feature = "runtime-benchmarks"))] if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info { + refund_storage_fee::( + confirmed_block_info.total_storage_fee, + confirmed_block_info.front_paid_storage, + ) + .map_err(Error::::from)?; + do_reward_operators::( domain_id, confirmed_block_info.operator_ids.into_iter(), @@ -1397,7 +1415,15 @@ mod pallet { fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { match call { Call::submit_bundle { opaque_bundle } => Self::validate_bundle(opaque_bundle) - .map_err(|_| InvalidTransaction::Call.into()), + .map_err(|_| InvalidTransaction::Call.into()) + .and_then(|_| { + charge_bundle_storage_fee::( + opaque_bundle.operator_id(), + Zero::zero(), + // TODO: use `opaque_bundle.size()` when deposit to storage fund works + ) + .map_err(|_| InvalidTransaction::Call.into()) + }), Call::submit_fraud_proof { fraud_proof } => Self::validate_fraud_proof(fraud_proof) .map_err(|_| InvalidTransaction::Call.into()), _ => Err(InvalidTransaction::Call.into()), @@ -1437,6 +1463,20 @@ mod pallet { } } + if let Err(e) = charge_bundle_storage_fee::( + opaque_bundle.operator_id(), + Zero::zero(), + // TODO: use `opaque_bundle.size()` when deposit to storage fund works + ) { + log::debug!( + target: "runtime::domains", + "Operator {} unable to pay for the bundle storage fee, domain id {:?}, error: {e:?}", + opaque_bundle.operator_id(), + opaque_bundle.domain_id(), + ); + return InvalidTransactionCode::BundleStorageFeePayment.into(); + } + ValidTransaction::with_tag_prefix("SubspaceSubmitBundle") .priority(TransactionPriority::MAX) .longevity(T::ConfirmationDepthK::get().try_into().unwrap_or_else(|_| { diff --git a/crates/sp-domains-fraud-proof/src/lib.rs b/crates/sp-domains-fraud-proof/src/lib.rs index 1b417884b0..233cf001d9 100644 --- a/crates/sp-domains-fraud-proof/src/lib.rs +++ b/crates/sp-domains-fraud-proof/src/lib.rs @@ -64,6 +64,7 @@ pub enum InvalidTransactionCode { ExecutionReceipt = 103, Bundle = 104, FraudProof = 105, + BundleStorageFeePayment = 106, } impl From for InvalidTransaction { From 1af5b4d5d7b60bdaa97e4329d9fd0ec546329bf0 Mon Sep 17 00:00:00 2001 From: linning Date: Mon, 15 Jan 2024 23:30:47 +0800 Subject: [PATCH 04/15] Minor test fixes and TODO Signed-off-by: linning --- crates/pallet-domains/src/tests.rs | 5 +++++ .../domain-operator/src/domain_bundle_proposer.rs | 3 +++ domains/client/domain-operator/src/tests.rs | 10 ++++++++++ 3 files changed, 18 insertions(+) diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 9afbb1f080..07193cebcb 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -825,6 +825,11 @@ fn test_bundle_fromat_verification() { let mut valid_bundle = create_dummy_bundle(DOMAIN_ID, 0, System::parent_hash()); valid_bundle.extrinsics.push(opaque_extrinsic(1, 1)); valid_bundle.extrinsics.push(opaque_extrinsic(2, 2)); + valid_bundle.sealed_header.header.bundle_size = valid_bundle + .extrinsics + .iter() + .map(|tx| tx.encoded_size() as u32) + .sum::(); valid_bundle.sealed_header.header.bundle_extrinsics_root = BlakeTwo256::ordered_trie_root( valid_bundle .extrinsics diff --git a/domains/client/domain-operator/src/domain_bundle_proposer.rs b/domains/client/domain-operator/src/domain_bundle_proposer.rs index 0109059ab6..f5b3cf515e 100644 --- a/domains/client/domain-operator/src/domain_bundle_proposer.rs +++ b/domains/client/domain-operator/src/domain_bundle_proposer.rs @@ -150,6 +150,9 @@ where break; } + // TODO: stop including more tx once the operartor's available storage fund less than + // `next_bundle_size * consensus_transaction_byte_fee` + estimated_bundle_weight = next_estimated_bundle_weight; bundle_size = next_bundle_size; diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index cc709bb80b..585dcd14f1 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -1321,6 +1321,11 @@ async fn test_true_invalid_bundles_inherent_extrinsic_proof_creation_and_verific bundle_extrinsic_root = BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); opaque_bundle.sealed_header.header.bundle_extrinsics_root = bundle_extrinsic_root; + opaque_bundle.sealed_header.header.bundle_size = opaque_bundle + .extrinsics + .iter() + .map(|tx| tx.encoded_size() as u32) + .sum::(); opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice .pair() .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) @@ -1619,6 +1624,11 @@ async fn test_true_invalid_bundles_illegal_extrinsic_proof_creation_and_verifica bundle_extrinsic_root = BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); opaque_bundle.sealed_header.header.bundle_extrinsics_root = bundle_extrinsic_root; + opaque_bundle.sealed_header.header.bundle_size = opaque_bundle + .extrinsics + .iter() + .map(|tx| tx.encoded_size() as u32) + .sum::(); opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice .pair() .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) From f4bddcd6560c482b155cfb6e15842d05f7d107fa Mon Sep 17 00:00:00 2001 From: linning Date: Tue, 16 Jan 2024 18:32:49 +0800 Subject: [PATCH 05/15] Only update the bundle bundle_size and estimated_bundle_weight iff the extrinsic is added to the bundle body Signed-off-by: linning --- domains/client/domain-operator/src/domain_bundle_proposer.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/domains/client/domain-operator/src/domain_bundle_proposer.rs b/domains/client/domain-operator/src/domain_bundle_proposer.rs index f5b3cf515e..f61cec3131 100644 --- a/domains/client/domain-operator/src/domain_bundle_proposer.rs +++ b/domains/client/domain-operator/src/domain_bundle_proposer.rs @@ -153,9 +153,6 @@ where // TODO: stop including more tx once the operartor's available storage fund less than // `next_bundle_size * consensus_transaction_byte_fee` - estimated_bundle_weight = next_estimated_bundle_weight; - bundle_size = next_bundle_size; - // Double check the transaction validity, because the tx pool are re-validate the transaction // in pool asynchronously so there is race condition that the operator imported a domain block // and start producing bundle immediately before the re-validation based on the latest block @@ -184,6 +181,8 @@ where continue; } + estimated_bundle_weight = next_estimated_bundle_weight; + bundle_size = next_bundle_size; extrinsics.push(pending_tx_data.clone()); } } From bee2e0e62c4731c9027a3fa1b56196d00aebba41 Mon Sep 17 00:00:00 2001 From: linning Date: Thu, 18 Jan 2024 06:41:08 +0800 Subject: [PATCH 06/15] Add deposit function for the bundle storage fund Signed-off-by: linning --- .../pallet-domains/src/bundle_storage_fund.rs | 34 +++++++++++++++++-- crates/pallet-domains/src/staking.rs | 6 ++++ crates/pallet-domains/src/staking_epoch.rs | 6 ++++ 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/crates/pallet-domains/src/bundle_storage_fund.rs b/crates/pallet-domains/src/bundle_storage_fund.rs index 75f37b7477..e0b129ef57 100644 --- a/crates/pallet-domains/src/bundle_storage_fund.rs +++ b/crates/pallet-domains/src/bundle_storage_fund.rs @@ -3,7 +3,7 @@ use crate::{BalanceOf, Config, Operators}; use codec::{Decode, Encode}; use frame_support::traits::fungible::Mutate; -use frame_support::traits::tokens::{Fortitude, Precision}; +use frame_support::traits::tokens::{Fortitude, Precision, Preservation}; use frame_support::traits::Get; use frame_support::PalletError; use scale_info::TypeInfo; @@ -13,6 +13,9 @@ use sp_runtime::Perbill; use sp_std::collections::btree_map::BTreeMap; use subspace_runtime_primitives::StorageFeeInterface; +/// The proportion of staking fund reserved for the bundle storage fee +pub const STORAGE_FEE_RESERVE: Perbill = Perbill::from_percent(20); + /// Bundle storage fund specific errors #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)] pub enum Error { @@ -20,6 +23,7 @@ pub enum Error { BundleStorageFeePayment, BalanceUnderflow, MintBalance, + FailToDeposit, } /// The type of system account being created. @@ -103,5 +107,29 @@ pub fn refund_storage_fee( Ok(()) } -// TODO: add deposit and withdraw function of the bundle storage fee and call them then -// staking deposit/withdraw happen +/// Split a proportion of the deposit to reserve for the bundle storage fund +/// +/// Return new deposit amount after deduction of the reserved fund +pub fn deposit_reserve_for_storage_fund( + operator_id: OperatorId, + source: &T::AccountId, + deposit_amount: BalanceOf, +) -> Result, Error> { + let storage_fund_acc = storage_fund_account::(operator_id)?; + + let storage_fee_reserve = STORAGE_FEE_RESERVE.mul_floor(deposit_amount); + + T::Currency::transfer( + source, + &storage_fund_acc, + storage_fee_reserve, + Preservation::Preserve, + ) + .map_err(|_| Error::FailToDeposit)?; + + deposit_amount + .checked_sub(&storage_fee_reserve) + .ok_or(Error::BalanceUnderflow) +} + +// TODO: add withdraw function for the bundle storage fund and call it then withdraw happen diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index ca4fa5b24a..f9749eeeb4 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -1,5 +1,6 @@ //! Staking for domains +use crate::bundle_storage_fund::{self, deposit_reserve_for_storage_fund}; use crate::pallet::{ Deposits, DomainRegistry, DomainStakingSummary, LatestConfirmedDomainBlockNumber, NextOperatorId, NominatorCount, OperatorIdOwner, OperatorSigningKey, Operators, @@ -201,6 +202,7 @@ pub enum Error { EpochNotComplete, UnlockPeriodNotComplete, OperatorNotDeregistered, + BundleStorageFund(bundle_storage_fund::Error), } // Increase `PendingStakingOperationCount` by one and check if the `MaxPendingStakingOperation` @@ -417,6 +419,10 @@ pub(crate) fn do_nominate_operator( let domain_stake_summary = DomainStakingSummary::::get(operator.current_domain_id) .ok_or(Error::DomainNotInitialized)?; + // Reserve for the bundle storage fund + let amount = deposit_reserve_for_storage_fund::(operator_id, &nominator_id, amount) + .map_err(Error::BundleStorageFund)?; + hold_deposit::(&nominator_id, operator_id, amount)?; // increment total deposit for operator pool within this epoch diff --git a/crates/pallet-domains/src/staking_epoch.rs b/crates/pallet-domains/src/staking_epoch.rs index 01000f6e56..767f510ea3 100644 --- a/crates/pallet-domains/src/staking_epoch.rs +++ b/crates/pallet-domains/src/staking_epoch.rs @@ -1,4 +1,5 @@ //! Staking epoch transition for domain +use crate::bundle_storage_fund::deposit_reserve_for_storage_fund; use crate::pallet::{ Deposits, DomainStakingSummary, LastEpochStakingDistribution, OperatorIdOwner, Operators, PendingOperatorSwitches, PendingSlashes, PendingStakingOperationCount, @@ -77,6 +78,11 @@ pub(crate) fn operator_take_reward_tax_and_stake( T::Currency::mint_into(&nominator_id, operator_tax) .map_err(|_| TransitionError::MintBalance)?; + // Reserve for the bundle storage fund + let operator_tax = + deposit_reserve_for_storage_fund::(operator_id, &nominator_id, operator_tax) + .map_err(TransitionError::BundleStorageFund)?; + crate::staking::hold_deposit::( &nominator_id, operator_id, From 7cb8cc138a933349434071705e49a30dc70db2aa Mon Sep 17 00:00:00 2001 From: linning Date: Wed, 24 Jan 2024 17:39:36 +0800 Subject: [PATCH 07/15] Minor refactoring and rename Signed-off-by: linning --- crates/pallet-domains/src/block_tree.rs | 8 ++++---- crates/pallet-domains/src/bundle_storage_fund.rs | 16 ++++++++-------- crates/pallet-domains/src/lib.rs | 6 +++--- crates/pallet-domains/src/tests.rs | 10 +++++----- crates/pallet-transaction-fees/src/lib.rs | 2 +- crates/subspace-runtime-primitives/src/lib.rs | 2 +- crates/subspace-runtime/src/lib.rs | 4 ++-- test/subspace-test-runtime/src/lib.rs | 4 ++-- 8 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index eae3470977..6201917bb0 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -261,7 +261,7 @@ pub(crate) struct ConfirmedDomainBlockInfo { pub rewards: Balance, pub invalid_bundle_authors: Vec, pub total_storage_fee: Balance, - pub front_paid_storage: BTreeMap, + pub paid_bundle_storage_fees: BTreeMap, } pub(crate) type ProcessExecutionReceiptResult = @@ -308,7 +308,7 @@ pub(crate) fn process_execution_receipt( )); // Collect the front paid storage and the invalid bundle author - let mut front_paid_storage = BTreeMap::new(); + let mut paid_bundle_storage_fees = BTreeMap::new(); let mut invalid_bundle_authors = Vec::new(); let bundle_digests = ExecutionInbox::::get(( domain_id, @@ -322,7 +322,7 @@ pub(crate) fn process_execution_receipt( if execution_receipt.inboxed_bundles[index].is_invalid() { invalid_bundle_authors.push(bundle_author); } else { - front_paid_storage + paid_bundle_storage_fees .entry(bundle_author) .and_modify(|s| *s += bd.size) .or_insert(bd.size); @@ -346,7 +346,7 @@ pub(crate) fn process_execution_receipt( invalid_bundle_authors, // TODO: get the `total_storage_fee` from ER total_storage_fee: Zero::zero(), - front_paid_storage, + paid_bundle_storage_fees, })); } } diff --git a/crates/pallet-domains/src/bundle_storage_fund.rs b/crates/pallet-domains/src/bundle_storage_fund.rs index e0b129ef57..ad516b158d 100644 --- a/crates/pallet-domains/src/bundle_storage_fund.rs +++ b/crates/pallet-domains/src/bundle_storage_fund.rs @@ -11,7 +11,7 @@ use sp_domains::OperatorId; use sp_runtime::traits::{AccountIdConversion, CheckedSub, Zero}; use sp_runtime::Perbill; use sp_std::collections::btree_map::BTreeMap; -use subspace_runtime_primitives::StorageFeeInterface; +use subspace_runtime_primitives::StorageFee; /// The proportion of staking fund reserved for the bundle storage fee pub const STORAGE_FEE_RESERVE: Perbill = Perbill::from_percent(20); @@ -49,7 +49,7 @@ pub fn charge_bundle_storage_fee( } let storage_fund_acc = storage_fund_account::(operator_id)?; - let storage_fee = T::StorageFeeInterface::transaction_byte_fee() * bundle_size.into(); + let storage_fee = T::StorageFee::transaction_byte_fee() * bundle_size.into(); T::Currency::burn_from( &storage_fund_acc, @@ -60,7 +60,7 @@ pub fn charge_bundle_storage_fee( .map_err(|_| Error::BundleStorageFeePayment)?; // Note the storage fee, it will go to the consensus block author - T::StorageFeeInterface::note_storage_fees(storage_fee); + T::StorageFee::note_storage_fees(storage_fee); Ok(()) } @@ -71,15 +71,15 @@ pub fn charge_bundle_storage_fee( #[allow(dead_code)] pub fn refund_storage_fee( total_storage_fee: BalanceOf, - front_paid_storage: BTreeMap, + paid_bundle_storage_fees: BTreeMap, ) -> Result<(), Error> { if total_storage_fee.is_zero() { return Ok(()); } - let total_paid_storage = front_paid_storage.values().sum::(); + let total_paid_storage = paid_bundle_storage_fees.values().sum::(); let mut remaining_fee = total_storage_fee; - for (operator_id, paid_storage) in front_paid_storage { + for (operator_id, paid_storage) in paid_bundle_storage_fees { // If the operator is deregistered and unlocked or slashed and finalized, the refund bundle storage // fee will go to the treasury if Operators::::get(operator_id).is_none() || paid_storage.is_zero() { @@ -87,8 +87,8 @@ pub fn refund_storage_fee( } let refund_amount = { - let share = Perbill::from_rational(paid_storage, total_paid_storage); - share.mul_floor(total_storage_fee) + let paid_storage_percentage = Perbill::from_rational(paid_storage, total_paid_storage); + paid_storage_percentage.mul_floor(total_storage_fee) }; let storage_fund_acc = storage_fund_account::(operator_id)?; T::Currency::mint_into(&storage_fund_acc, refund_amount).map_err(|_| Error::MintBalance)?; diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 9f407b07fb..2cac4d5e71 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -178,7 +178,7 @@ mod pallet { use sp_std::vec; use sp_std::vec::Vec; use subspace_core_primitives::U256; - use subspace_runtime_primitives::StorageFeeInterface; + use subspace_runtime_primitives::StorageFee; #[pallet::config] pub trait Config: frame_system::Config> { @@ -312,7 +312,7 @@ mod pallet { type PalletId: Get; /// Storage fee interface used to deal with bundle storage fee - type StorageFeeInterface: StorageFeeInterface>; + type StorageFee: StorageFee>; } #[pallet::pallet] @@ -871,7 +871,7 @@ mod pallet { if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info { refund_storage_fee::( confirmed_block_info.total_storage_fee, - confirmed_block_info.front_paid_storage, + confirmed_block_info.paid_bundle_storage_fees, ) .map_err(Error::::from)?; diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 2fd2add80c..d0c6561759 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -49,7 +49,7 @@ use sp_trie::{LayoutV1, PrefixedMemoryDB, StorageProof, TrieMut}; use sp_version::RuntimeVersion; use std::sync::atomic::{AtomicU64, Ordering}; use subspace_core_primitives::{Randomness, U256 as P256}; -use subspace_runtime_primitives::{Moment, StorageFeeInterface, SSC}; +use subspace_runtime_primitives::{Moment, StorageFee, SSC}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -188,7 +188,7 @@ parameter_types! { pub const BlockReward: Balance = 10 * SSC; pub const MaxPendingStakingOperation: u32 = 100; pub const MaxNominators: u32 = 5; - pub const DomainsPalletId: PalletId = PalletId(*b"ssdomain"); + pub const DomainsPalletId: PalletId = PalletId(*b"domains_"); } pub struct MockRandomness; @@ -208,8 +208,8 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } -pub struct DummyStorageFeeInterface; -impl StorageFeeInterface for DummyStorageFeeInterface { +pub struct DummyStorageFee; +impl StorageFee for DummyStorageFee { fn transaction_byte_fee() -> Balance { Default::default() } @@ -244,7 +244,7 @@ impl pallet_domains::Config for Test { type Randomness = MockRandomness; type SudoId = (); type PalletId = DomainsPalletId; - type StorageFeeInterface = DummyStorageFeeInterface; + type StorageFee = DummyStorageFee; } pub struct ExtrinsicStorageFees; diff --git a/crates/pallet-transaction-fees/src/lib.rs b/crates/pallet-transaction-fees/src/lib.rs index 53ab8e9b59..a9af99f271 100644 --- a/crates/pallet-transaction-fees/src/lib.rs +++ b/crates/pallet-transaction-fees/src/lib.rs @@ -307,7 +307,7 @@ where } } -impl subspace_runtime_primitives::StorageFeeInterface> for Pallet +impl subspace_runtime_primitives::StorageFee> for Pallet where BalanceOf: From, { diff --git a/crates/subspace-runtime-primitives/src/lib.rs b/crates/subspace-runtime-primitives/src/lib.rs index 5abca31fec..7ec5a2cc5c 100644 --- a/crates/subspace-runtime-primitives/src/lib.rs +++ b/crates/subspace-runtime-primitives/src/lib.rs @@ -90,7 +90,7 @@ pub trait FindVotingRewardAddresses { fn find_voting_reward_addresses() -> Vec; } -pub trait StorageFeeInterface { +pub trait StorageFee { /// Return the consensus transaction byte fee. fn transaction_byte_fee() -> Balance; diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 172d2203fc..f58d0843cf 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -627,7 +627,7 @@ parameter_types! { pub const MaxPendingStakingOperation: u32 = 100; pub const MaxNominators: u32 = 256; pub SudoId: AccountId = Sudo::key().expect("Sudo account must exist"); - pub const DomainsPalletId: PalletId = PalletId(*b"ssdomain"); + pub const DomainsPalletId: PalletId = PalletId(*b"domains_"); } // Minimum operator stake must be >= minimum nominator stake since operator is also a nominator. @@ -661,7 +661,7 @@ impl pallet_domains::Config for Runtime { type Randomness = Subspace; type SudoId = SudoId; type PalletId = DomainsPalletId; - type StorageFeeInterface = TransactionFees; + type StorageFee = TransactionFees; } parameter_types! { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index f812e0577d..806a505c3f 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -629,7 +629,7 @@ parameter_types! { pub const MaxPendingStakingOperation: u32 = 100; pub const MaxNominators: u32 = 100; pub SudoId: AccountId = Sudo::key().expect("Sudo account must exist"); - pub const DomainsPalletId: PalletId = PalletId(*b"ssdomain"); + pub const DomainsPalletId: PalletId = PalletId(*b"domains_"); } // Minimum operator stake must be >= minimum nominator stake since operator is also a nominator. @@ -663,7 +663,7 @@ impl pallet_domains::Config for Runtime { type SudoId = SudoId; type MinNominatorStake = MinNominatorStake; type PalletId = DomainsPalletId; - type StorageFeeInterface = TransactionFees; + type StorageFee = TransactionFees; } parameter_types! { From 3181d43f2d742466e0a8e8e7ac96ae546016611d Mon Sep 17 00:00:00 2001 From: linning Date: Wed, 24 Jan 2024 23:06:00 +0800 Subject: [PATCH 08/15] Remove the bundle_size field from the bundle header Signed-off-by: linning --- crates/pallet-domains/src/lib.rs | 25 +++---------------- crates/pallet-domains/src/tests.rs | 12 +-------- .../src/bundle_equivocation.rs | 1 - crates/sp-domains/src/lib.rs | 10 +++----- .../src/malicious_bundle_tamper.rs | 1 - .../src/domain_bundle_proposer.rs | 1 - domains/client/domain-operator/src/tests.rs | 10 -------- 7 files changed, 9 insertions(+), 51 deletions(-) diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 2cac4d5e71..bfd9ef382f 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -593,8 +593,6 @@ mod pallet { Receipt(BlockTreeError), /// Bundle size exceed the max bundle size limit in the domain config BundleTooLarge, - /// The actual bundle size is not match with the `bundle_size` field in bundle header - InvalidBundleSize, // Bundle with an invalid extrinsic root InvalidExtrinsicRoot, /// This bundle duplicated with an already submitted bundle @@ -1583,24 +1581,6 @@ impl Pallet { Ok(()) } - fn check_bundle_size( - opaque_bundle: &OpaqueBundleOf, - max_size: u32, - ) -> Result<(), BundleError> { - let bundle_size = opaque_bundle - .extrinsics - .iter() - .fold(0, |acc, xt| acc + xt.encoded_size() as u32); - - ensure!(max_size >= bundle_size, BundleError::BundleTooLarge); - ensure!( - opaque_bundle.size() == bundle_size, - BundleError::InvalidBundleSize - ); - - Ok(()) - } - fn check_extrinsics_root(opaque_bundle: &OpaqueBundleOf) -> Result<(), BundleError> { let expected_extrinsics_root = ::Hashing::ordered_trie_root( opaque_bundle @@ -1644,7 +1624,10 @@ impl Pallet { // TODO: check bundle weight with `domain_config.max_block_weight` - Self::check_bundle_size(opaque_bundle, domain_config.max_block_size)?; + ensure!( + opaque_bundle.size() <= domain_config.max_block_size, + BundleError::BundleTooLarge + ); Self::check_extrinsics_root(opaque_bundle)?; diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index d0c6561759..2a37e624e0 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -462,7 +462,6 @@ pub(crate) fn create_dummy_bundle_with_receipts( let header = BundleHeader::<_, _, DomainHeader, _> { proof_of_election: ProofOfElection::dummy(domain_id, operator_id), receipt, - bundle_size: 0u32, estimated_bundle_weight: Default::default(), bundle_extrinsics_root, }; @@ -815,11 +814,6 @@ fn test_bundle_fromat_verification() { let mut valid_bundle = create_dummy_bundle(DOMAIN_ID, 0, System::parent_hash()); valid_bundle.extrinsics.push(opaque_extrinsic(1, 1)); valid_bundle.extrinsics.push(opaque_extrinsic(2, 2)); - valid_bundle.sealed_header.header.bundle_size = valid_bundle - .extrinsics - .iter() - .map(|tx| tx.encoded_size() as u32) - .sum::(); valid_bundle.sealed_header.header.bundle_extrinsics_root = BlakeTwo256::ordered_trie_root( valid_bundle .extrinsics @@ -828,10 +822,6 @@ fn test_bundle_fromat_verification() { .collect(), sp_core::storage::StateVersion::V1, ); - assert_ok!(pallet_domains::Pallet::::check_bundle_size( - &valid_bundle, - max_block_size - )); assert_ok!(pallet_domains::Pallet::::check_extrinsics_root( &valid_bundle )); @@ -844,7 +834,7 @@ fn test_bundle_fromat_verification() { .push(opaque_extrinsic(i as u64, i as u128)); } assert_err!( - pallet_domains::Pallet::::check_bundle_size(&too_large_bundle, max_block_size), + pallet_domains::Pallet::::validate_bundle(&too_large_bundle), BundleError::BundleTooLarge ); diff --git a/crates/sp-domains-fraud-proof/src/bundle_equivocation.rs b/crates/sp-domains-fraud-proof/src/bundle_equivocation.rs index 6639b174a1..81117c110b 100644 --- a/crates/sp-domains-fraud-proof/src/bundle_equivocation.rs +++ b/crates/sp-domains-fraud-proof/src/bundle_equivocation.rs @@ -190,7 +190,6 @@ mod test { execution_trace_root: Default::default(), total_rewards: 0, }, - bundle_size: 0, estimated_bundle_weight: Default::default(), bundle_extrinsics_root: Default::default(), }, diff --git a/crates/sp-domains/src/lib.rs b/crates/sp-domains/src/lib.rs index abdcc1c643..80418810f2 100644 --- a/crates/sp-domains/src/lib.rs +++ b/crates/sp-domains/src/lib.rs @@ -207,10 +207,6 @@ pub struct BundleHeader { HeaderHashFor, Balance, >, - /// The size of the bundle body in bytes. - /// - /// Used to calculate the storage cost. - pub bundle_size: u32, /// The total (estimated) weight of all extrinsics in the bundle. /// /// Used to prevent overloading the bundle with compute. @@ -323,7 +319,10 @@ impl u32 { - self.sealed_header.header.bundle_size + self.extrinsics + .iter() + .map(|tx| tx.encoded_size() as u32) + .sum::() } } @@ -380,7 +379,6 @@ pub fn dummy_opaque_bundle< let header = BundleHeader { proof_of_election: ProofOfElection::dummy(domain_id, operator_id), receipt, - bundle_size: 0u32, estimated_bundle_weight: Default::default(), bundle_extrinsics_root: Default::default(), }; diff --git a/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs b/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs index baed809796..145826f90b 100644 --- a/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs +++ b/crates/subspace-malicious-operator/src/malicious_bundle_tamper.rs @@ -270,7 +270,6 @@ where _ => return Ok(()), }; - opaque_bundle.sealed_header.header.bundle_size += invalid_tx.encoded_size() as u32; opaque_bundle.extrinsics.push(invalid_tx); opaque_bundle.sealed_header.header.bundle_extrinsics_root = HeaderHashingFor::::ordered_trie_root( diff --git a/domains/client/domain-operator/src/domain_bundle_proposer.rs b/domains/client/domain-operator/src/domain_bundle_proposer.rs index f61cec3131..08422b991e 100644 --- a/domains/client/domain-operator/src/domain_bundle_proposer.rs +++ b/domains/client/domain-operator/src/domain_bundle_proposer.rs @@ -197,7 +197,6 @@ where let header = BundleHeader { proof_of_election, receipt, - bundle_size, estimated_bundle_weight, bundle_extrinsics_root: extrinsics_root, }; diff --git a/domains/client/domain-operator/src/tests.rs b/domains/client/domain-operator/src/tests.rs index 585dcd14f1..cc709bb80b 100644 --- a/domains/client/domain-operator/src/tests.rs +++ b/domains/client/domain-operator/src/tests.rs @@ -1321,11 +1321,6 @@ async fn test_true_invalid_bundles_inherent_extrinsic_proof_creation_and_verific bundle_extrinsic_root = BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); opaque_bundle.sealed_header.header.bundle_extrinsics_root = bundle_extrinsic_root; - opaque_bundle.sealed_header.header.bundle_size = opaque_bundle - .extrinsics - .iter() - .map(|tx| tx.encoded_size() as u32) - .sum::(); opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice .pair() .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) @@ -1624,11 +1619,6 @@ async fn test_true_invalid_bundles_illegal_extrinsic_proof_creation_and_verifica bundle_extrinsic_root = BlakeTwo256::ordered_trie_root(extrinsics.clone(), StateVersion::V1); opaque_bundle.sealed_header.header.bundle_extrinsics_root = bundle_extrinsic_root; - opaque_bundle.sealed_header.header.bundle_size = opaque_bundle - .extrinsics - .iter() - .map(|tx| tx.encoded_size() as u32) - .sum::(); opaque_bundle.sealed_header.signature = Sr25519Keyring::Alice .pair() .sign(opaque_bundle.sealed_header.pre_hash().as_ref()) From c41a23085198962ba86214022386267407311010 Mon Sep 17 00:00:00 2001 From: linning Date: Wed, 31 Jan 2024 08:40:21 +0800 Subject: [PATCH 09/15] Support withdraw from the bundle storage fund Signed-off-by: linning --- .../pallet-domains/src/bundle_storage_fund.rs | 118 ++++++-- crates/pallet-domains/src/lib.rs | 1 + crates/pallet-domains/src/staking.rs | 276 ++++++++++++++---- crates/pallet-domains/src/staking_epoch.rs | 75 ++++- crates/pallet-domains/src/tests.rs | 5 + crates/sp-domains/src/lib.rs | 1 + crates/subspace-runtime/src/lib.rs | 4 + test/subspace-test-runtime/src/lib.rs | 4 + 8 files changed, 393 insertions(+), 91 deletions(-) diff --git a/crates/pallet-domains/src/bundle_storage_fund.rs b/crates/pallet-domains/src/bundle_storage_fund.rs index ad516b158d..8dd6324b60 100644 --- a/crates/pallet-domains/src/bundle_storage_fund.rs +++ b/crates/pallet-domains/src/bundle_storage_fund.rs @@ -1,8 +1,9 @@ //! Bundle storage fund -use crate::{BalanceOf, Config, Operators}; +use crate::staking::NewDeposit; +use crate::{BalanceOf, Config, HoldIdentifier, Operators}; use codec::{Decode, Encode}; -use frame_support::traits::fungible::Mutate; +use frame_support::traits::fungible::{Inspect, Mutate, MutateHold}; use frame_support::traits::tokens::{Fortitude, Precision, Preservation}; use frame_support::traits::Get; use frame_support::PalletError; @@ -19,11 +20,12 @@ pub const STORAGE_FEE_RESERVE: Perbill = Perbill::from_percent(20); /// Bundle storage fund specific errors #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)] pub enum Error { - FailedToDeriveStorageFundAccount, BundleStorageFeePayment, BalanceUnderflow, MintBalance, FailToDeposit, + WithdrawAndHold, + BalanceTransfer, } /// The type of system account being created. @@ -32,11 +34,33 @@ pub enum AccountType { StorageFund, } +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)] +pub struct StorageFundRedeemPrice((BalanceOf, BalanceOf)); + +impl StorageFundRedeemPrice { + pub(crate) fn new(total_balance: BalanceOf, total_deposit: BalanceOf) -> Self { + StorageFundRedeemPrice((total_balance, total_deposit)) + } + + /// Return the amount of balance can be redeemed by the given `deposit`, it is calculated + /// by `storage_fund_total_balance * deposit / total_deposit`. + /// + /// If the inflow of the storage fund (i.e. refund of the storage fee) is larger than its + /// outflow (i.e. payment of the storage fee), the return value will larger than `deposit` + /// otherwise smaller. + pub(crate) fn redeem(&self, deposit: BalanceOf) -> BalanceOf { + let (total_balance, total_deposit) = self.0; + if total_balance == total_deposit { + deposit + } else { + Perbill::from_rational(deposit, total_deposit).mul_floor(total_balance) + } + } +} + /// Return the bundle storage fund account of the given operator. -pub fn storage_fund_account(id: OperatorId) -> Result { - T::PalletId::get() - .try_into_sub_account((AccountType::StorageFund, id)) - .ok_or(Error::FailedToDeriveStorageFundAccount) +pub fn storage_fund_account(id: OperatorId) -> T::AccountId { + T::PalletId::get().into_sub_account_truncating((AccountType::StorageFund, id)) } /// Charge the bundle storage fee from the operator's bundle storage fund @@ -48,7 +72,7 @@ pub fn charge_bundle_storage_fee( return Ok(()); } - let storage_fund_acc = storage_fund_account::(operator_id)?; + let storage_fund_acc = storage_fund_account::(operator_id); let storage_fee = T::StorageFee::transaction_byte_fee() * bundle_size.into(); T::Currency::burn_from( @@ -90,7 +114,7 @@ pub fn refund_storage_fee( let paid_storage_percentage = Perbill::from_rational(paid_storage, total_paid_storage); paid_storage_percentage.mul_floor(total_storage_fee) }; - let storage_fund_acc = storage_fund_account::(operator_id)?; + let storage_fund_acc = storage_fund_account::(operator_id); T::Currency::mint_into(&storage_fund_acc, refund_amount).map_err(|_| Error::MintBalance)?; remaining_fee = remaining_fee @@ -107,15 +131,14 @@ pub fn refund_storage_fee( Ok(()) } -/// Split a proportion of the deposit to reserve for the bundle storage fund -/// -/// Return new deposit amount after deduction of the reserved fund +/// Split the new deposit into 2 parts: the staking deposit and the the storage fee deposit, +/// add the storage fee deposit to the bundle storage fund. pub fn deposit_reserve_for_storage_fund( operator_id: OperatorId, source: &T::AccountId, deposit_amount: BalanceOf, -) -> Result, Error> { - let storage_fund_acc = storage_fund_account::(operator_id)?; +) -> Result>, Error> { + let storage_fund_acc = storage_fund_account::(operator_id); let storage_fee_reserve = STORAGE_FEE_RESERVE.mul_floor(deposit_amount); @@ -127,9 +150,70 @@ pub fn deposit_reserve_for_storage_fund( ) .map_err(|_| Error::FailToDeposit)?; - deposit_amount + let staking = deposit_amount .checked_sub(&storage_fee_reserve) - .ok_or(Error::BalanceUnderflow) + .ok_or(Error::BalanceUnderflow)?; + + Ok(NewDeposit { + staking, + storage_fee: storage_fee_reserve, + }) } -// TODO: add withdraw function for the bundle storage fund and call it then withdraw happen +/// Transfer the given `withdraw_amount` of balance from the bundle storage fund to the +/// given `dest_account` and hold on the `dest_account` +pub fn withdraw_and_hold( + operator_id: OperatorId, + dest_account: &T::AccountId, + withdraw_amount: BalanceOf, +) -> Result, Error> { + if withdraw_amount.is_zero() { + return Ok(Zero::zero()); + } + + let storage_fund_acc = storage_fund_account::(operator_id); + let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); + T::Currency::transfer_and_hold( + &storage_fund_hold_id, + &storage_fund_acc, + dest_account, + withdraw_amount, + Precision::Exact, + Preservation::Expendable, + Fortitude::Force, + ) + .map_err(|_| Error::WithdrawAndHold) +} + +/// Return the total balance of the bundle storage fund the given `operator_id` +pub fn total_balance(operator_id: OperatorId) -> BalanceOf { + let storage_fund_acc = storage_fund_account::(operator_id); + T::Currency::reducible_balance( + &storage_fund_acc, + Preservation::Expendable, + Fortitude::Polite, + ) +} + +/// Return the bundle storage fund redeem price +pub fn storage_fund_redeem_price( + operator_id: OperatorId, + operartor_total_deposit: BalanceOf, +) -> StorageFundRedeemPrice { + let total_balance = total_balance::(operator_id); + StorageFundRedeemPrice::::new(total_balance, operartor_total_deposit) +} + +/// Transfer all of the balance of the bundle storage fund to the treasury +pub fn transfer_all_to_treasury(operator_id: OperatorId) -> Result<(), Error> { + let storage_fund_acc = storage_fund_account::(operator_id); + let total_balance = total_balance::(operator_id); + T::Currency::transfer( + &storage_fund_acc, + &T::TreasuryAccount::get(), + total_balance, + Preservation::Expendable, + ) + .map_err(|_| Error::BalanceTransfer)?; + Ok(()) +} diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index f2281b0686..129d011e79 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -80,6 +80,7 @@ pub(crate) type NominatorId = ::AccountId; pub trait HoldIdentifier { fn staking_staked(operator_id: OperatorId) -> FungibleHoldId; fn domain_instantiation_id(domain_id: DomainId) -> FungibleHoldId; + fn storage_fund(operator_id: OperatorId) -> FungibleHoldId; } pub type ExecutionReceiptOf = ExecutionReceipt< diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index dd2c9ff2ed..bbf2b5c431 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -29,7 +29,7 @@ use sp_std::vec::IntoIter; /// A nominators deposit. #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)] pub(crate) struct Deposit { - pub(crate) known: KnownDeposit, + pub(crate) known: KnownDeposit, pub(crate) pending: Option>, } @@ -83,10 +83,16 @@ impl From<(DomainId, EpochIndex)> for DomainEpoch { } } +pub struct NewDeposit { + pub(crate) staking: Balance, + pub(crate) storage_fee: Balance, +} + /// A nominator's shares against their deposits to given operator pool. #[derive(TypeInfo, Debug, Encode, Decode, Copy, Clone, PartialEq, Eq, Default)] -pub(crate) struct KnownDeposit { +pub(crate) struct KnownDeposit { pub(crate) shares: Share, + pub(crate) storage_fee_deposit: Balance, } /// A nominators pending deposit in SSC that needs to be converted to shares once domain epoch is complete. @@ -94,18 +100,44 @@ pub(crate) struct KnownDeposit { pub(crate) struct PendingDeposit { pub(crate) effective_domain_epoch: DomainEpoch, pub(crate) amount: Balance, + pub(crate) storage_fee_deposit: Balance, +} + +impl PendingDeposit { + fn total(&self) -> Result { + self.amount + .checked_add(&self.storage_fee_deposit) + .ok_or(Error::BalanceOverflow) + } } /// A nominator's withdrawal from a given operator pool. #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)] pub(crate) struct Withdrawal { - /// Total withdrawal amount requested by the nominator that are in unlocking state excluding withdrawal in shares. + /// Total withdrawal amount requested by the nominator that are in unlocking state excluding withdrawal + /// in shares and the storage fee pub(crate) total_withdrawal_amount: Balance, /// Individual withdrawal amounts with their unlocking block for a given domain - pub(crate) withdrawals: VecDeque<(DomainId, DomainBlockNumber, Balance)>, + pub(crate) withdrawals: VecDeque>, /// Withdrawal that was initiated by nominator and not converted to balance due to /// unfinished domain epoch. - pub(crate) withdrawal_in_shares: Option<(DomainEpoch, DomainBlockNumber, Share)>, + pub(crate) withdrawal_in_shares: Option>, +} + +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] +pub(crate) struct WithdrawalInBalance { + pub(crate) domain_id: DomainId, + pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber, + pub(crate) amount_to_unlock: Balance, + pub(crate) storage_fee: Balance, +} + +#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] +pub(crate) struct WithdrawalInShares { + pub(crate) domain_epoch: DomainEpoch, + pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber, + pub(crate) shares: Share, + pub(crate) storage_fee: Balance, } #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] @@ -153,6 +185,8 @@ pub struct Operator { pub deposits_in_epoch: Balance, /// Total withdrew shares during the previous epoch pub withdrawals_in_epoch: Share, + /// Total balance deposited to the bundle storage fund + pub total_storage_fee_deposit: Balance, } #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] @@ -273,7 +307,11 @@ pub(crate) fn do_register_operator( Error::MinimumOperatorStake ); - hold_deposit::(&operator_owner, operator_id, amount)?; + let new_deposit = + deposit_reserve_for_storage_fund::(operator_id, &operator_owner, amount) + .map_err(Error::BundleStorageFund)?; + + hold_deposit::(&operator_owner, operator_id, new_deposit.staking)?; let domain_stake_summary = maybe_domain_stake_summary .as_mut() @@ -296,8 +334,9 @@ pub(crate) fn do_register_operator( current_total_shares: Zero::zero(), status: OperatorStatus::Registered, // sum total deposits added during this epoch. - deposits_in_epoch: amount, + deposits_in_epoch: new_deposit.staking, withdrawals_in_epoch: Zero::zero(), + total_storage_fee_deposit: new_deposit.storage_fee, }; Operators::::insert(operator_id, operator); OperatorSigningKey::::insert(signing_key, operator_id); @@ -309,7 +348,7 @@ pub(crate) fn do_register_operator( operator_id, operator_owner, current_domain_epoch, - amount, + new_deposit, )?; Ok((operator_id, domain_stake_summary.current_epoch_index)) @@ -334,7 +373,7 @@ pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit, current_domain_epoch: DomainEpoch, - new_deposit: BalanceOf, + new_deposit: NewDeposit>, ) -> Result>, Error> { Deposits::::try_mutate(operator_id, nominator_id, |maybe_deposit| { let mut deposit = maybe_deposit.take().unwrap_or_default(); @@ -345,12 +384,13 @@ pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit { let pending_deposit = PendingDeposit { effective_domain_epoch: current_domain_epoch, - amount: new_deposit, + amount: new_deposit.staking, + storage_fee_deposit: new_deposit.storage_fee, }; let deposit_info = DepositInfo { nominating: !deposit.known.shares.is_zero(), - total_deposit: new_deposit, + total_deposit: pending_deposit.total()?, first_deposit_in_epoch: true, }; @@ -361,13 +401,17 @@ pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit( operator_id: OperatorId, deposit: &mut Deposit>, ) -> Result<(), Error> { - let maybe_pending_deposit = deposit.pending.take(); // if it is one of the previous domain epoch, then calculate shares for the epoch and update known deposit - deposit.pending = if let Some(PendingDeposit { - effective_domain_epoch, + let epoch_share_price = match deposit.pending.and_then(|pending_deposit| { + OperatorEpochSharePrice::::get(operator_id, pending_deposit.effective_domain_epoch) + }) { + Some(p) => p, + None => return Ok(()), + }; + + if let Some(PendingDeposit { amount, - }) = maybe_pending_deposit - && let Some(epoch_share_price) = - OperatorEpochSharePrice::::get(operator_id, effective_domain_epoch) + storage_fee_deposit, + .. + }) = deposit.pending.take() { let new_shares = epoch_share_price.stake_to_shares::(amount); deposit.known.shares = deposit @@ -400,10 +449,12 @@ pub(crate) fn do_convert_previous_epoch_deposits( .shares .checked_add(&new_shares) .ok_or(Error::ShareOverflow)?; - None - } else { - maybe_pending_deposit - }; + deposit.known.storage_fee_deposit = deposit + .known + .storage_fee_deposit + .checked_add(&storage_fee_deposit) + .ok_or(Error::BalanceOverflow)?; + } Ok(()) } @@ -414,11 +465,21 @@ pub(crate) fn do_convert_previous_epoch_withdrawal( operator_id: OperatorId, withdrawal: &mut Withdrawal, T::Share, DomainBlockNumberFor>, ) -> Result<(), Error> { - let withdrawal_in_shares = withdrawal.withdrawal_in_shares.take(); - withdrawal.withdrawal_in_shares = if let Some((domain_epoch, domain_block_number, shares)) = - withdrawal_in_shares - && let Some(epoch_share_price) = - OperatorEpochSharePrice::::get(operator_id, domain_epoch) + let epoch_share_price = match withdrawal + .withdrawal_in_shares + .as_ref() + .and_then(|withdraw| OperatorEpochSharePrice::::get(operator_id, withdraw.domain_epoch)) + { + Some(p) => p, + None => return Ok(()), + }; + + if let Some(WithdrawalInShares { + domain_epoch, + unlock_at_confirmed_domain_block_number, + shares, + storage_fee, + }) = withdrawal.withdrawal_in_shares.take() { let withdrawal_amount = epoch_share_price.shares_to_stake::(shares); withdrawal.total_withdrawal_amount = withdrawal @@ -426,13 +487,15 @@ pub(crate) fn do_convert_previous_epoch_withdrawal( .checked_add(&withdrawal_amount) .ok_or(Error::BalanceOverflow)?; let (domain_id, _) = domain_epoch.deconstruct(); - withdrawal - .withdrawals - .push_back((domain_id, domain_block_number, withdrawal_amount)); - None - } else { - withdrawal_in_shares - }; + + let withdraw_in_balance = WithdrawalInBalance { + domain_id, + unlock_at_confirmed_domain_block_number, + amount_to_unlock: withdrawal_amount, + storage_fee, + }; + withdrawal.withdrawals.push_back(withdraw_in_balance); + } Ok(()) } @@ -454,15 +517,21 @@ pub(crate) fn do_nominate_operator( .ok_or(Error::DomainNotInitialized)?; // Reserve for the bundle storage fund - let amount = deposit_reserve_for_storage_fund::(operator_id, &nominator_id, amount) + let new_deposit = deposit_reserve_for_storage_fund::(operator_id, &nominator_id, amount) .map_err(Error::BundleStorageFund)?; - hold_deposit::(&nominator_id, operator_id, amount)?; + hold_deposit::(&nominator_id, operator_id, new_deposit.staking)?; // increment total deposit for operator pool within this epoch operator.deposits_in_epoch = operator .deposits_in_epoch - .checked_add(&amount) + .checked_add(&new_deposit.staking) + .ok_or(Error::BalanceOverflow)?; + + // Increase total storage fee deposit as there is new deposit to the storage fund + operator.total_storage_fee_deposit = operator + .total_storage_fee_deposit + .checked_add(&new_deposit.storage_fee) .ok_or(Error::BalanceOverflow)?; let current_domain_epoch = ( @@ -479,7 +548,7 @@ pub(crate) fn do_nominate_operator( operator_id, nominator_id, current_domain_epoch, - amount, + new_deposit, )?; // if not a nominator, then ensure @@ -712,7 +781,15 @@ pub(crate) fn do_withdraw_stake( let share_price = SharePrice::new::(operator.current_total_shares, total_stake); - let remaining_stake = share_price.shares_to_stake::(remaining_shares); + + let remaining_storage_fee = + Perbill::from_rational(remaining_shares, known_shares) + .mul_floor(deposit.known.storage_fee_deposit); + + let remaining_stake = share_price + .shares_to_stake::(remaining_shares) + .checked_add(&remaining_storage_fee) + .ok_or(Error::BalanceOverflow)?; // ensure the remaining share value is atleast the defined minimum // MinOperatorStake if a nominator is operator pool owner @@ -729,6 +806,35 @@ pub(crate) fn do_withdraw_stake( } }; + // Withdraw storage fund, the `withdraw_storage_fee` amount of fund will be transfered + // and hold on the nominator account + let storage_fee_to_withdraw = Perbill::from_rational(shares_withdrew, known_shares) + .mul_floor(deposit.known.storage_fee_deposit); + + let withdraw_storage_fee = { + let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::( + operator_id, + operator.total_storage_fee_deposit, + ); + bundle_storage_fund::withdraw_and_hold::( + operator_id, + &nominator_id, + storage_fund_redeem_price.redeem(storage_fee_to_withdraw), + ) + .map_err(Error::BundleStorageFund)? + }; + + deposit.known.storage_fee_deposit = deposit + .known + .storage_fee_deposit + .checked_sub(&storage_fee_to_withdraw) + .ok_or(Error::BalanceOverflow)?; + + operator.total_storage_fee_deposit = operator + .total_storage_fee_deposit + .checked_sub(&storage_fee_to_withdraw) + .ok_or(Error::BalanceOverflow)?; + // update operator pool to note withdrew shares in the epoch operator.withdrawals_in_epoch = operator .withdrawals_in_epoch @@ -741,7 +847,7 @@ pub(crate) fn do_withdraw_stake( // if there is a pending deposit, then ensure // the new deposit is atleast minimum nominator stake ensure!( - pending_deposit.amount >= operator.minimum_nominator_stake, + pending_deposit.total()? >= operator.minimum_nominator_stake, Error::MinimumNominatorStake ); } else { @@ -762,25 +868,30 @@ pub(crate) fn do_withdraw_stake( let mut withdrawal = maybe_withdrawal.take().unwrap_or_default(); // if this is some, then the withdrawal was initiated in this current epoch due to conversion // of previous epoch withdrawals from shares to balances above. So just update it instead - let withdrawals_in_shares = withdrawal.withdrawal_in_shares.take(); - withdrawal.withdrawal_in_shares = Some( - if let Some((domain_epoch, _, shares)) = withdrawals_in_shares { - let updated_shares = shares + let new_withdrawal_in_shares = match withdrawal.withdrawal_in_shares.take() { + Some(WithdrawalInShares { + domain_epoch, + shares, + storage_fee, + .. + }) => WithdrawalInShares { + domain_epoch, + shares: shares .checked_add(&shares_withdrew) - .ok_or(Error::ShareOverflow)?; - ( - domain_epoch, - unlock_at_confirmed_domain_block_number, - updated_shares, - ) - } else { - ( - domain_current_epoch, - unlock_at_confirmed_domain_block_number, - shares_withdrew, - ) + .ok_or(Error::ShareOverflow)?, + unlock_at_confirmed_domain_block_number, + storage_fee: storage_fee + .checked_add(&withdraw_storage_fee) + .ok_or(Error::BalanceOverflow)?, }, - ); + None => WithdrawalInShares { + domain_epoch: domain_current_epoch, + unlock_at_confirmed_domain_block_number, + shares: shares_withdrew, + storage_fee: withdraw_storage_fee, + }, + }; + withdrawal.withdrawal_in_shares = Some(new_withdrawal_in_shares); *maybe_withdrawal = Some(withdrawal); Ok(()) @@ -803,7 +914,12 @@ pub(crate) fn do_unlock_funds( Withdrawals::::try_mutate_exists(operator_id, nominator_id.clone(), |maybe_withdrawal| { let withdrawal = maybe_withdrawal.as_mut().ok_or(Error::MissingWithdrawal)?; do_convert_previous_epoch_withdrawal::(operator_id, withdrawal)?; - let (domain_id, unlock_at_confirmed_domain_block_number, amount_to_unlock) = withdrawal + let WithdrawalInBalance { + domain_id, + unlock_at_confirmed_domain_block_number, + amount_to_unlock, + storage_fee, + } = withdrawal .withdrawals .pop_front() .ok_or(Error::MissingWithdrawal)?; @@ -834,6 +950,7 @@ pub(crate) fn do_unlock_funds( } }; + // Release staking fund T::Currency::release( &staked_hold_id, &nominator_id, @@ -842,6 +959,16 @@ pub(crate) fn do_unlock_funds( ) .map_err(|_| Error::RemoveLock)?; + // Release storage fund + let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); + T::Currency::release( + &storage_fund_hold_id, + &nominator_id, + storage_fee, + Precision::Exact, + ) + .map_err(|_| Error::RemoveLock)?; + // if there are no withdrawals, then delete the storage as well if withdrawal.withdrawals.is_empty() && withdrawal.withdrawal_in_shares.is_none() { *maybe_withdrawal = None; @@ -889,6 +1016,12 @@ pub(crate) fn do_unlock_operator(operator_id: OperatorId) -> Result<( let share_price = SharePrice::new::(total_shares, total_stake); let staked_hold_id = T::HoldIdentifier::staking_staked(operator_id); + + let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::( + operator_id, + operator.total_storage_fee_deposit, + ); + let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); Deposits::::drain_prefix(operator_id).try_for_each(|(nominator_id, mut deposit)| { // convert any deposits from the previous epoch to shares do_convert_previous_epoch_deposits::(operator_id, &mut deposit)?; @@ -908,7 +1041,7 @@ pub(crate) fn do_unlock_operator(operator_id: OperatorId) -> Result<( withdrawal.total_withdrawal_amount, withdrawal .withdrawal_in_shares - .map(|(_, _, shares)| shares) + .map(|WithdrawalInShares { shares, .. }| shares) .unwrap_or_default(), )) }) @@ -950,9 +1083,31 @@ pub(crate) fn do_unlock_operator(operator_id: OperatorId) -> Result<( total_stake = total_stake.saturating_sub(nominator_staked_amount); + // Withdraw all storage fee for the nominator + let nominator_total_storage_fee_deposit = deposit + .pending + .map(|pending_deposit| pending_deposit.storage_fee_deposit) + .unwrap_or(Zero::zero()) + .checked_add(&deposit.known.storage_fee_deposit) + .ok_or(Error::BalanceOverflow)?; + bundle_storage_fund::withdraw_and_hold::( + operator_id, + &nominator_id, + storage_fund_redeem_price.redeem(nominator_total_storage_fee_deposit), + ) + .map_err(Error::BundleStorageFund)?; + + // Release all storage fee that just withdraw above and withdraw previously + T::Currency::release_all(&storage_fund_hold_id, &nominator_id, Precision::Exact) + .map_err(|_| Error::RemoveLock)?; + Ok(()) })?; + // transfer any remaining storage fund to treasury + bundle_storage_fund::transfer_all_to_treasury::(operator_id) + .map_err(Error::BundleStorageFund)?; + // transfer any remaining amount to treasury mint_funds::(&T::TreasuryAccount::get(), total_stake)?; @@ -1287,6 +1442,7 @@ pub(crate) mod tests { status: OperatorStatus::Registered, deposits_in_epoch: 0, withdrawals_in_epoch: 0, + total_storage_fee_deposit: 0, } ); diff --git a/crates/pallet-domains/src/staking_epoch.rs b/crates/pallet-domains/src/staking_epoch.rs index cb1de50ca9..1acdb97e57 100644 --- a/crates/pallet-domains/src/staking_epoch.rs +++ b/crates/pallet-domains/src/staking_epoch.rs @@ -6,11 +6,11 @@ use crate::pallet::{ }; use crate::staking::{ do_convert_previous_epoch_deposits, do_convert_previous_epoch_withdrawal, DomainEpoch, - Error as TransitionError, OperatorStatus, SharePrice, + Error as TransitionError, OperatorStatus, SharePrice, WithdrawalInShares, }; use crate::{ - BalanceOf, Config, ElectionVerificationParams, Event, HoldIdentifier, OperatorEpochSharePrice, - Pallet, + bundle_storage_fund, BalanceOf, Config, ElectionVerificationParams, Event, HoldIdentifier, + OperatorEpochSharePrice, Pallet, }; use codec::{Decode, Encode}; use frame_support::traits::fungible::{InspectHold, Mutate, MutateHold}; @@ -71,28 +71,34 @@ pub(crate) fn operator_take_reward_tax_and_stake( }; // calculate operator tax, mint the balance, and stake them - let operator_tax = operator.nomination_tax.mul_floor(reward); - if !operator_tax.is_zero() { + let operator_tax_amount = operator.nomination_tax.mul_floor(reward); + if !operator_tax_amount.is_zero() { let nominator_id = OperatorIdOwner::::get(operator_id) .ok_or(TransitionError::MissingOperatorOwner)?; - T::Currency::mint_into(&nominator_id, operator_tax) + T::Currency::mint_into(&nominator_id, operator_tax_amount) .map_err(|_| TransitionError::MintBalance)?; // Reserve for the bundle storage fund - let operator_tax = - deposit_reserve_for_storage_fund::(operator_id, &nominator_id, operator_tax) + let operator_tax_deposit = + deposit_reserve_for_storage_fund::(operator_id, &nominator_id, operator_tax_amount) .map_err(TransitionError::BundleStorageFund)?; crate::staking::hold_deposit::( &nominator_id, operator_id, - operator_tax, + operator_tax_deposit.staking, )?; // increment total deposit for operator pool within this epoch operator.deposits_in_epoch = operator .deposits_in_epoch - .checked_add(&operator_tax) + .checked_add(&operator_tax_deposit.staking) + .ok_or(TransitionError::BalanceOverflow)?; + + // Increase total storage fee deposit as there is new deposit to the storage fund + operator.total_storage_fee_deposit = operator + .total_storage_fee_deposit + .checked_add(&operator_tax_deposit.storage_fee) .ok_or(TransitionError::BalanceOverflow)?; let current_domain_epoch = (domain_id, stake_summary.current_epoch_index).into(); @@ -100,18 +106,18 @@ pub(crate) fn operator_take_reward_tax_and_stake( operator_id, nominator_id, current_domain_epoch, - operator_tax, + operator_tax_deposit, )?; Pallet::::deposit_event(Event::OperatorTaxCollected { operator_id, - tax: operator_tax, + tax: operator_tax_amount, }); } // add remaining rewards to nominators to be distributed during the epoch transition let rewards = reward - .checked_sub(&operator_tax) + .checked_sub(&operator_tax_amount) .ok_or(TransitionError::BalanceUnderflow)?; operator.current_epoch_rewards = operator @@ -338,6 +344,12 @@ pub(crate) fn do_finalize_slashed_operators( let total_shares = operator.current_total_shares; let share_price = SharePrice::new::(total_shares, total_stake); + let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); + let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::( + operator_id, + operator.total_storage_fee_deposit, + ); + // transfer all the staked funds to the treasury account // any gains will be minted to treasury account Deposits::::drain_prefix(operator_id).try_for_each( @@ -361,7 +373,7 @@ pub(crate) fn do_finalize_slashed_operators( withdrawal.total_withdrawal_amount, withdrawal .withdrawal_in_shares - .map(|(_, _, shares)| shares) + .map(|WithdrawalInShares { shares, .. }| shares) .unwrap_or_default(), )) }) @@ -414,6 +426,37 @@ pub(crate) fn do_finalize_slashed_operators( T::Currency::release_all(&staked_hold_id, &nominator_id, Precision::BestEffort) .map_err(|_| TransitionError::RemoveLock)?; + // Transfer the deposited unstaked storage fee back to nominator + if let Some(pending_deposit) = deposit.pending { + let storage_fee_deposit = bundle_storage_fund::withdraw_and_hold::( + operator_id, + &nominator_id, + storage_fund_redeem_price.redeem(pending_deposit.storage_fee_deposit), + ) + .map_err(TransitionError::BundleStorageFund)?; + T::Currency::release( + &storage_fund_hold_id, + &nominator_id, + storage_fee_deposit, + Precision::Exact, + ) + .map_err(|_| TransitionError::RemoveLock)?; + } + + // Transfer all the storage fee on withdraw to the treasury + let withdraw_storage_fee_on_hold = + T::Currency::balance_on_hold(&storage_fund_hold_id, &nominator_id); + T::Currency::transfer_on_hold( + &storage_fund_hold_id, + &nominator_id, + &T::TreasuryAccount::get(), + withdraw_storage_fee_on_hold, + Precision::Exact, + Restriction::Free, + Fortitude::Force, + ) + .map_err(|_| TransitionError::RemoveLock)?; + Ok(()) }, )?; @@ -421,6 +464,10 @@ pub(crate) fn do_finalize_slashed_operators( // mint any gains to treasury account mint_funds::(&T::TreasuryAccount::get(), total_stake)?; + // Transfer all the storage fund to treasury + bundle_storage_fund::transfer_all_to_treasury::(operator_id) + .map_err(TransitionError::BundleStorageFund)?; + Ok(()) })?; } diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 7777361068..84fd1d2cc1 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -153,6 +153,10 @@ impl pallet_domains::HoldIdentifier for HoldIdentifier { fn domain_instantiation_id(domain_id: DomainId) -> FungibleHoldId { Self::Domains(DomainsHoldIdentifier::DomainInstantiation(domain_id)) } + + fn storage_fund(operator_id: OperatorId) -> Self { + Self::Domains(DomainsHoldIdentifier::StorageFund(operator_id)) + } } impl VariantCount for HoldIdentifier { @@ -564,6 +568,7 @@ pub(crate) fn register_genesis_domain(creator: u64, operator_ids: Vec for HoldIdentifier { fn domain_instantiation_id(domain_id: DomainId) -> Self { Self::Domains(DomainsHoldIdentifier::DomainInstantiation(domain_id)) } + + fn storage_fund(operator_id: OperatorId) -> Self { + Self::Domains(DomainsHoldIdentifier::StorageFund(operator_id)) + } } impl VariantCount for HoldIdentifier { diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 8db94a580f..69329827fb 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -325,6 +325,10 @@ impl pallet_domains::HoldIdentifier for HoldIdentifier { fn domain_instantiation_id(domain_id: DomainId) -> Self { Self::Domains(DomainsHoldIdentifier::DomainInstantiation(domain_id)) } + + fn storage_fund(operator_id: OperatorId) -> Self { + Self::Domains(DomainsHoldIdentifier::StorageFund(operator_id)) + } } impl VariantCount for HoldIdentifier { From 1f037b3910c715d1b8771162c5b87d464cc448f9 Mon Sep 17 00:00:00 2001 From: linning Date: Wed, 31 Jan 2024 08:44:43 +0800 Subject: [PATCH 10/15] Adjust staking tests and add test for the bundle storage fund The staking tests were dejusted due to the bundle storage fund introduce more calculation and rounding dust Signed-off-by: linning --- crates/pallet-domains/src/block_tree.rs | 18 +- crates/pallet-domains/src/domain_registry.rs | 2 +- crates/pallet-domains/src/staking.rs | 435 ++++++++++++++++--- crates/pallet-domains/src/staking_epoch.rs | 50 ++- crates/pallet-domains/src/tests.rs | 31 +- 5 files changed, 439 insertions(+), 97 deletions(-) diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index 24bc6a0ed9..36e804a9df 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -442,7 +442,7 @@ mod tests { fn test_genesis_receipt() { let mut ext = new_test_ext_with_extensions(); ext.execute_with(|| { - let domain_id = register_genesis_domain(0u64, vec![0u64]); + let domain_id = register_genesis_domain(0u128, vec![0u64]); // The genesis receipt should be added to the block tree let block_tree_node_at_0 = BlockTree::::get(domain_id, 0).unwrap(); @@ -473,7 +473,7 @@ mod tests { #[test] fn test_new_head_receipt() { - let creator = 0u64; + let creator = 0u128; let operator_id = 1u64; let block_tree_pruning_depth = ::BlockTreePruningDepth::get(); @@ -588,7 +588,7 @@ mod tests { #[test] fn test_confirm_current_head_receipt() { - let creator = 0u64; + let creator = 0u128; let operator_id1 = 1u64; let operator_id2 = 2u64; let mut ext = new_test_ext_with_extensions(); @@ -654,7 +654,7 @@ mod tests { #[test] fn test_non_head_receipt() { - let creator = 0u64; + let creator = 0u128; let operator_id1 = 1u64; let operator_id2 = 2u64; let mut ext = new_test_ext_with_extensions(); @@ -699,7 +699,7 @@ mod tests { #[test] fn test_previous_head_receipt() { - let creator = 0u64; + let creator = 0u128; let operator_id1 = 1u64; let operator_id2 = 2u64; let mut ext = new_test_ext_with_extensions(); @@ -740,7 +740,7 @@ mod tests { #[test] fn test_new_branch_receipt() { - let creator = 0u64; + let creator = 0u128; let operator_id1 = 1u64; let operator_id2 = 2u64; let mut ext = new_test_ext_with_extensions(); @@ -787,7 +787,7 @@ mod tests { #[test] fn test_invalid_receipt() { - let creator = 0u64; + let creator = 0u128; let operator_id = 1u64; let mut ext = new_test_ext_with_extensions(); ext.execute_with(|| { @@ -875,7 +875,7 @@ mod tests { #[test] fn test_invalid_trace_root_receipt() { - let creator = 0u64; + let creator = 0u128; let operator_id1 = 1u64; let operator_id2 = 2u64; let mut ext = new_test_ext_with_extensions(); @@ -937,7 +937,7 @@ mod tests { #[test] fn test_collect_invalid_bundle_author() { - let creator = 0u64; + let creator = 0u128; let challenge_period = BlockTreePruningDepth::get() as u64; let operator_set: Vec<_> = (1..15).collect(); let mut ext = new_test_ext_with_extensions(); diff --git a/crates/pallet-domains/src/domain_registry.rs b/crates/pallet-domains/src/domain_registry.rs index 588ea7d9fb..1469002936 100644 --- a/crates/pallet-domains/src/domain_registry.rs +++ b/crates/pallet-domains/src/domain_registry.rs @@ -233,7 +233,7 @@ mod tests { #[test] fn test_domain_instantiation() { - let creator = 1u64; + let creator = 1u128; let created_at = 0u64; // Construct an invalid domain config initially let mut domain_config = DomainConfig { diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index bbf2b5c431..be13cdf594 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -1238,13 +1238,13 @@ pub(crate) mod tests { PendingSlashes, Withdrawals, }; use crate::staking::{ - do_nominate_operator, do_reward_operators, do_slash_operators, do_unlock_funds, - do_withdraw_stake, Error as StakingError, Operator, OperatorConfig, OperatorStatus, - StakingSummary, + do_convert_previous_epoch_withdrawal, do_nominate_operator, do_reward_operators, + do_slash_operators, do_unlock_funds, do_withdraw_stake, Error as StakingError, Operator, + OperatorConfig, OperatorStatus, StakingSummary, }; use crate::staking_epoch::do_finalize_domain_current_epoch; use crate::tests::{new_test_ext, ExistentialDeposit, RuntimeOrigin, Test}; - use crate::{BalanceOf, Error, NominatorId, SlashedReason}; + use crate::{bundle_storage_fund, BalanceOf, Error, NominatorId, SlashedReason}; use frame_support::traits::fungible::Mutate; use frame_support::traits::Currency; use frame_support::weights::Weight; @@ -1255,12 +1255,14 @@ pub(crate) mod tests { ZERO_OPERATOR_SIGNING_KEY, }; use sp_runtime::traits::Zero; + use sp_runtime::{PerThing, Perbill}; use std::collections::{BTreeMap, BTreeSet}; use std::vec; use subspace_runtime_primitives::SSC; type Balances = pallet_balances::Pallet; type Domains = crate::Pallet; + const STORAGE_FEE_RESERVE: Perbill = Perbill::from_percent(20); pub(crate) fn register_operator( domain_id: DomainId, @@ -1407,7 +1409,9 @@ pub(crate) mod tests { let domain_id = DomainId::new(0); let operator_account = 1; let operator_free_balance = 1500 * SSC; - let operator_stake = 1000 * SSC; + let operator_total_stake = 1000 * SSC; + let operator_stake = 800 * SSC; + let operator_storage_fee_deposit = 200 * SSC; let pair = OperatorPair::from_seed(&U256::from(0u32).into()); let mut ext = new_test_ext(); @@ -1416,7 +1420,7 @@ pub(crate) mod tests { domain_id, operator_account, operator_free_balance, - operator_stake, + operator_total_stake, SSC, pair.public(), BTreeMap::new(), @@ -1442,7 +1446,7 @@ pub(crate) mod tests { status: OperatorStatus::Registered, deposits_in_epoch: 0, withdrawals_in_epoch: 0, - total_storage_fee_deposit: 0, + total_storage_fee_deposit: operator_storage_fee_deposit, } ); @@ -1452,7 +1456,7 @@ pub(crate) mod tests { assert_eq!( Balances::usable_balance(operator_account), - operator_free_balance - operator_stake - ExistentialDeposit::get() + operator_free_balance - operator_total_stake - ExistentialDeposit::get() ); // cannot register with same operator key @@ -1491,12 +1495,16 @@ pub(crate) mod tests { let domain_id = DomainId::new(0); let operator_account = 1; let operator_free_balance = 1500 * SSC; - let operator_stake = 1000 * SSC; + let operator_total_stake = 1000 * SSC; + let operator_stake = 800 * SSC; + let operator_storage_fee_deposit = 200 * SSC; let pair = OperatorPair::from_seed(&U256::from(0u32).into()); let nominator_account = 2; let nominator_free_balance = 150 * SSC; - let nominator_stake = 100 * SSC; + let nominator_total_stake = 100 * SSC; + let nominator_stake = 80 * SSC; + let nominator_storage_fee_deposit = 20 * SSC; let mut ext = new_test_ext(); ext.execute_with(|| { @@ -1504,12 +1512,12 @@ pub(crate) mod tests { domain_id, operator_account, operator_free_balance, - operator_stake, + operator_total_stake, 10 * SSC, pair.public(), BTreeMap::from_iter(vec![( nominator_account, - (nominator_free_balance, nominator_stake), + (nominator_free_balance, nominator_total_stake), )]), ); @@ -1519,37 +1527,63 @@ pub(crate) mod tests { let operator = Operators::::get(operator_id).unwrap(); assert_eq!(operator.current_total_stake, operator_stake); assert_eq!(operator.current_total_shares, operator_stake); + assert_eq!( + operator.total_storage_fee_deposit, + operator_storage_fee_deposit + nominator_storage_fee_deposit + ); assert_eq!(operator.deposits_in_epoch, nominator_stake); let pending_deposit = Deposits::::get(0, nominator_account) .unwrap() .pending - .unwrap() - .amount; - assert_eq!(pending_deposit, nominator_stake); + .unwrap(); + assert_eq!(pending_deposit.amount, nominator_stake); + assert_eq!( + pending_deposit.storage_fee_deposit, + nominator_storage_fee_deposit + ); + assert_eq!(pending_deposit.total().unwrap(), nominator_total_stake); assert_eq!( Balances::usable_balance(nominator_account), - nominator_free_balance - nominator_stake - ExistentialDeposit::get() + nominator_free_balance - nominator_total_stake - ExistentialDeposit::get() ); // another transfer with an existing transfer in place should lead to single + let addtional_nomination_total_stake = 40 * SSC; + let addtional_nomination_stake = 32 * SSC; + let addtional_nomination_storage_fee_deposit = 8 * SSC; let res = Domains::nominate_operator( RuntimeOrigin::signed(nominator_account), operator_id, - 40 * SSC, + addtional_nomination_total_stake, ); assert_ok!(res); let pending_deposit = Deposits::::get(0, nominator_account) .unwrap() .pending - .unwrap() - .amount; - assert_eq!(pending_deposit, nominator_stake + 40 * SSC); + .unwrap(); + assert_eq!( + pending_deposit.amount, + nominator_stake + addtional_nomination_stake + ); + assert_eq!( + pending_deposit.storage_fee_deposit, + nominator_storage_fee_deposit + addtional_nomination_storage_fee_deposit + ); let operator = Operators::::get(operator_id).unwrap(); assert_eq!(operator.current_total_stake, operator_stake); - assert_eq!(operator.deposits_in_epoch, nominator_stake + 40 * SSC); + assert_eq!( + operator.deposits_in_epoch, + nominator_stake + addtional_nomination_stake + ); + assert_eq!( + operator.total_storage_fee_deposit, + operator_storage_fee_deposit + + nominator_storage_fee_deposit + + addtional_nomination_storage_fee_deposit + ); let nominator_count = NominatorCount::::get(operator_id); assert_eq!(nominator_count, 1); @@ -1560,13 +1594,13 @@ pub(crate) mod tests { let operator = Operators::::get(operator_id).unwrap(); assert_eq!( operator.current_total_stake, - operator_stake + nominator_stake + 40 * SSC + operator_stake + nominator_stake + addtional_nomination_stake ); let domain_staking_summary = DomainStakingSummary::::get(domain_id).unwrap(); assert_eq!( domain_staking_summary.current_total_stake, - operator_stake + nominator_stake + 40 * SSC + operator_stake + nominator_stake + addtional_nomination_stake ); }); } @@ -1815,6 +1849,9 @@ pub(crate) mod tests { /// since ED is not holded back from usable balance when there are no holds on the account. type ExpectedWithdrawAmount = Option<(BalanceOf, bool)>; + /// The storage fund change in SSC, `true` means increase of the storage fund, `false` means decrease. + type StorageFundChange = (bool, u32); + pub(crate) type Share = ::Share; struct WithdrawParams { @@ -1826,6 +1863,7 @@ pub(crate) mod tests { maybe_deposit: Option>, expected_withdraw: ExpectedWithdrawAmount, expected_nominator_count_reduced_by: u32, + storage_fund_change: StorageFundChange, } fn withdraw_stake(params: WithdrawParams) { @@ -1838,11 +1876,16 @@ pub(crate) mod tests { maybe_deposit, expected_withdraw, expected_nominator_count_reduced_by, + storage_fund_change, } = params; let domain_id = DomainId::new(0); let operator_account = 0; let pair = OperatorPair::from_seed(&U256::from(0u32).into()); + let mut total_balance = nominators.iter().map(|n| n.1).sum::>() + + operator_reward + + maybe_deposit.unwrap_or(0); + let mut nominators = BTreeMap::from_iter( nominators .into_iter() @@ -1889,11 +1932,38 @@ pub(crate) mod tests { assert_ok!(res); } + let operator = Operators::::get(operator_id).unwrap(); + let (is_storage_fund_increased, storage_fund_change_amount) = storage_fund_change; + if is_storage_fund_increased { + bundle_storage_fund::refund_storage_fee::( + storage_fund_change_amount as u128 * SSC, + BTreeMap::from_iter([(operator_id, 1)]), + ) + .unwrap(); + assert_eq!( + operator.total_storage_fee_deposit + storage_fund_change_amount as u128 * SSC, + bundle_storage_fund::total_balance::(operator_id) + ); + total_balance += storage_fund_change_amount as u128 * SSC; + } else { + bundle_storage_fund::charge_bundle_storage_fee::( + operator_id, + storage_fund_change_amount, + ) + .unwrap(); + assert_eq!( + operator.total_storage_fee_deposit - storage_fund_change_amount as u128 * SSC, + bundle_storage_fund::total_balance::(operator_id) + ); + total_balance -= storage_fund_change_amount as u128 * SSC; + } + for (withdraw, expected_result) in withdraws { + let withdraw_share_amount = STORAGE_FEE_RESERVE.left_from_one().mul_ceil(withdraw); let res = Domains::withdraw_stake( RuntimeOrigin::signed(nominator_id), operator_id, - withdraw, + withdraw_share_amount, ); assert_eq!( res, @@ -1901,9 +1971,10 @@ pub(crate) mod tests { ); } + do_finalize_domain_current_epoch::(domain_id).unwrap(); + if let Some((withdraw, include_ed)) = expected_withdraw { let previous_usable_balance = Balances::usable_balance(nominator_id); - do_finalize_domain_current_epoch::(domain_id).unwrap(); // staking withdrawal is 5 blocks // to unlock funds, confirmed block should be atleast 105 @@ -1912,6 +1983,7 @@ pub(crate) mod tests { assert_ok!(do_unlock_funds::(operator_id, nominator_id)); let expected_balance = if include_ed { + total_balance += crate::tests::ExistentialDeposit::get(); previous_usable_balance + withdraw + crate::tests::ExistentialDeposit::get() } else { previous_usable_balance + withdraw @@ -1933,6 +2005,15 @@ pub(crate) mod tests { if new_nominator_count < nominator_count { assert!(Deposits::::get(operator_id, nominator_id).is_none()) } + + // The total balance is distributed in different places but never changed + let operator = Operators::::get(operator_id).unwrap(); + assert_eq!( + total_balance, + Balances::usable_balance(nominator_id) + + operator.current_total_stake + + bundle_storage_fund::total_balance::(operator_id) + ); }); } @@ -1947,6 +2028,7 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: None, expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -1961,6 +2043,7 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: None, expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -1975,6 +2058,7 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: None, expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -1989,8 +2073,9 @@ pub(crate) mod tests { // given the reward, operator will get 164.28 SSC // taking 58 shares will give this following approximate amount. maybe_deposit: None, - expected_withdraw: Some((63523809541959183678, false)), + expected_withdraw: Some((63523809519881179143, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2006,8 +2091,9 @@ pub(crate) mod tests { (5 * SSC, Err(StakingError::MinimumOperatorStake)), ], maybe_deposit: None, - expected_withdraw: Some((63523809541959183678, false)), + expected_withdraw: Some((63523809519881179143, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2020,8 +2106,9 @@ pub(crate) mod tests { nominator_id: 0, withdraws: vec![(53 * SSC, Ok(())), (5 * SSC, Ok(()))], maybe_deposit: None, - expected_withdraw: Some((63523809541959183678, false)), + expected_withdraw: Some((63523809515796643053, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2034,8 +2121,9 @@ pub(crate) mod tests { nominator_id: 0, withdraws: vec![(49 * SSC, Ok(()))], maybe_deposit: None, - expected_withdraw: Some((49 * SSC, false)), + expected_withdraw: Some((48999999980000000000, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2048,8 +2136,9 @@ pub(crate) mod tests { nominator_id: 0, withdraws: vec![(29 * SSC, Ok(())), (20 * SSC, Ok(()))], maybe_deposit: None, - expected_withdraw: Some((49 * SSC, false)), + expected_withdraw: Some((48999999986852892560, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2066,8 +2155,9 @@ pub(crate) mod tests { (20 * SSC, Err(StakingError::MinimumOperatorStake)), ], maybe_deposit: None, - expected_withdraw: Some((49 * SSC, false)), + expected_withdraw: Some((48999999986852892560, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2083,8 +2173,9 @@ pub(crate) mod tests { // we withdraw everything, so for their 50 shares with reward, // price would be following maybe_deposit: None, - expected_withdraw: Some((54761904777551020412, true)), + expected_withdraw: Some((54761904775759637192, true)), expected_nominator_count_reduced_by: 1, + storage_fund_change: (true, 0), }) } @@ -2100,8 +2191,9 @@ pub(crate) mod tests { // we withdraw everything, so for their 50 shares with reward, // price would be following maybe_deposit: None, - expected_withdraw: Some((54761904777551020412, true)), + expected_withdraw: Some((54761904775759637192, true)), expected_nominator_count_reduced_by: 1, + storage_fund_change: (true, 0), }) } @@ -2121,8 +2213,9 @@ pub(crate) mod tests { // we withdraw everything, so for their 50 shares with reward, // price would be following maybe_deposit: None, - expected_withdraw: Some((54761904777551020412, true)), + expected_withdraw: Some((54761904775759637192, true)), expected_nominator_count_reduced_by: 1, + storage_fund_change: (true, 0), }) } @@ -2137,6 +2230,7 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: Some((50 * SSC, true)), expected_nominator_count_reduced_by: 1, + storage_fund_change: (true, 0), }) } @@ -2151,6 +2245,7 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: Some((50 * SSC, true)), expected_nominator_count_reduced_by: 1, + storage_fund_change: (true, 0), }) } @@ -2169,6 +2264,7 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: Some((50 * SSC, true)), expected_nominator_count_reduced_by: 1, + storage_fund_change: (true, 0), }) } @@ -2181,8 +2277,9 @@ pub(crate) mod tests { nominator_id: 1, withdraws: vec![(40 * SSC, Ok(()))], maybe_deposit: None, - expected_withdraw: Some((43809523822040816330, false)), + expected_withdraw: Some((43809523820607709753, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2195,8 +2292,9 @@ pub(crate) mod tests { nominator_id: 1, withdraws: vec![(35 * SSC, Ok(())), (5 * SSC, Ok(()))], maybe_deposit: None, - expected_withdraw: Some((43809523822040816330, false)), + expected_withdraw: Some((43809523819607709753, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2213,8 +2311,9 @@ pub(crate) mod tests { (15 * SSC, Err(StakingError::InsufficientShares)), ], maybe_deposit: None, - expected_withdraw: Some((43809523822040816330, false)), + expected_withdraw: Some((43809523819607709753, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2229,6 +2328,7 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: Some((39 * SSC, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2239,10 +2339,11 @@ pub(crate) mod tests { nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], operator_reward: Zero::zero(), nominator_id: 1, - withdraws: vec![(35 * SSC, Ok(())), (5 * SSC, Ok(()))], + withdraws: vec![(35 * SSC, Ok(())), (5 * SSC - 100000000000, Ok(()))], maybe_deposit: None, - expected_withdraw: Some((40 * SSC, false)), + expected_withdraw: Some((39999999898000000000, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2255,12 +2356,13 @@ pub(crate) mod tests { nominator_id: 1, withdraws: vec![ (35 * SSC, Ok(())), - (5 * SSC, Ok(())), + (5 * SSC - 100000000000, Ok(())), (15 * SSC, Err(StakingError::InsufficientShares)), ], maybe_deposit: None, - expected_withdraw: Some((40 * SSC, false)), + expected_withdraw: Some((39999999898000000000, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2273,12 +2375,13 @@ pub(crate) mod tests { nominator_id: 1, withdraws: vec![ (35 * SSC, Ok(())), - (5 * SSC, Ok(())), + (5 * SSC - 100000000000, Ok(())), (10 * SSC, Err(StakingError::MinimumNominatorStake)), ], maybe_deposit: Some(2 * SSC), - expected_withdraw: Some((40 * SSC, false)), + expected_withdraw: Some((39999999898000000000, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2298,8 +2401,9 @@ pub(crate) mod tests { // we withdraw everything, so for their 50 shares with reward, // price would be following maybe_deposit: Some(2 * SSC), - expected_withdraw: Some((43809523822040816330, false)), + expected_withdraw: Some((43809523819607709753, false)), expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), }) } @@ -2314,6 +2418,94 @@ pub(crate) mod tests { maybe_deposit: None, expected_withdraw: None, expected_nominator_count_reduced_by: 0, + storage_fund_change: (true, 0), + }) + } + + #[test] + fn withdraw_stake_nominator_all_with_storage_fee_profit() { + withdraw_stake(WithdrawParams { + minimum_nominator_stake: 10 * SSC, + nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], + operator_reward: Zero::zero(), + nominator_id: 1, + withdraws: vec![(50 * SSC, Ok(()))], + maybe_deposit: None, + // The storage fund increased 50% (i.e. 21 * SSC) thus the nominator make 50% + // storage fee profit i.e. 5 * SSC with rounding dust deducted + storage_fund_change: (true, 21), + expected_withdraw: Some((54999999994000000000, true)), + expected_nominator_count_reduced_by: 1, + }) + } + + #[test] + fn withdraw_stake_nominator_all_with_storage_fee_loss() { + withdraw_stake(WithdrawParams { + minimum_nominator_stake: 10 * SSC, + nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], + operator_reward: Zero::zero(), + nominator_id: 1, + withdraws: vec![(50 * SSC, Ok(()))], + maybe_deposit: None, + // The storage fund decreased 50% (i.e. 21 * SSC) thus the nominator loss 50% + // storage fee deposit i.e. 5 * SSC with rounding dust deducted + storage_fund_change: (false, 21), + expected_withdraw: Some((44999999998000000000, true)), + expected_nominator_count_reduced_by: 1, + }) + } + + #[test] + fn withdraw_stake_nominator_all_with_storage_fee_loss_all() { + withdraw_stake(WithdrawParams { + minimum_nominator_stake: 10 * SSC, + nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], + operator_reward: Zero::zero(), + nominator_id: 1, + withdraws: vec![(50 * SSC, Ok(()))], + maybe_deposit: None, + // The storage fund decreased 100% (i.e. 42 * SSC) thus the nominator loss 100% + // storage fee deposit i.e. 10 * SSC + storage_fund_change: (false, 42), + expected_withdraw: Some((40 * SSC, true)), + expected_nominator_count_reduced_by: 1, + }) + } + + #[test] + fn withdraw_stake_nominator_multiple_withdraws_with_storage_fee_profit() { + withdraw_stake(WithdrawParams { + minimum_nominator_stake: 10 * SSC, + nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], + operator_reward: Zero::zero(), + nominator_id: 1, + withdraws: vec![(5 * SSC, Ok(())), (10 * SSC, Ok(())), (15 * SSC, Ok(()))], + maybe_deposit: None, + // The storage fund increased 50% (i.e. 21 * SSC) thus the nominator make 50% + // storage fee profit i.e. 5 * SSC with rounding dust deducted, withdraw 60% of + // the stake and the storage fee profit + storage_fund_change: (true, 21), + expected_withdraw: Some((30 * SSC + 2999999855527204374, false)), + expected_nominator_count_reduced_by: 0, + }) + } + + #[test] + fn withdraw_stake_nominator_multiple_withdraws_with_storage_fee_loss() { + withdraw_stake(WithdrawParams { + minimum_nominator_stake: 10 * SSC, + nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], + operator_reward: Zero::zero(), + nominator_id: 1, + withdraws: vec![(5 * SSC, Ok(())), (5 * SSC, Ok(())), (10 * SSC, Ok(()))], + maybe_deposit: None, + // The storage fund increased 50% (i.e. 21 * SSC) thus the nominator loss 50% + // storage fee i.e. 5 * SSC with rounding dust deducted, withdraw 40% of + // the stake and 40% of the storage fee loss are deducted + storage_fund_change: (false, 21), + expected_withdraw: Some((20 * SSC - 2 * SSC - 33331097576, false)), + expected_nominator_count_reduced_by: 0, }) } @@ -2343,6 +2535,9 @@ pub(crate) mod tests { (nominator_account, nominator_extra_deposit), ]; + let init_total_stake = STORAGE_FEE_RESERVE.left_from_one() * 300 * SSC; + let init_total_storage_fund = STORAGE_FEE_RESERVE * 300 * SSC; + let mut ext = new_test_ext(); ext.execute_with(|| { let (operator_id, _) = register_operator( @@ -2357,10 +2552,15 @@ pub(crate) mod tests { do_finalize_domain_current_epoch::(domain_id).unwrap(); let domain_stake_summary = DomainStakingSummary::::get(domain_id).unwrap(); - assert_eq!(domain_stake_summary.current_total_stake, 300 * SSC); + assert_eq!(domain_stake_summary.current_total_stake, init_total_stake); let operator = Operators::::get(operator_id).unwrap(); - assert_eq!(operator.current_total_stake, 300 * SSC); + assert_eq!(operator.current_total_stake, init_total_stake); + assert_eq!(operator.total_storage_fee_deposit, init_total_storage_fund); + assert_eq!( + operator.total_storage_fee_deposit, + bundle_storage_fund::total_balance::(operator_id) + ); for unlock in &unlocking { do_withdraw_stake::(operator_id, unlock.0, unlock.1).unwrap(); @@ -2370,15 +2570,43 @@ pub(crate) mod tests { .unwrap(); do_finalize_domain_current_epoch::(domain_id).unwrap(); - // post epoch transition, domain stake has 21.333 amount reduced due to withdrawal of 20 shares + // Manually convert previous withdrawal in share to balance + for id in [operator_account, nominator_account] { + Withdrawals::::try_mutate(operator_id, id, |maybe_withdrawal| { + do_convert_previous_epoch_withdrawal::( + operator_id, + maybe_withdrawal.as_mut().unwrap(), + ) + }) + .unwrap(); + } + + // post epoch transition, domain stake has 21.666 amount reduced and storage fund has 5 amount reduced + // due to withdrawal of 20 shares + let operator = Operators::::get(operator_id).unwrap(); let domain_stake_summary = DomainStakingSummary::::get(domain_id).unwrap(); + let operator_withdrawal = + Withdrawals::::get(operator_id, operator_account).unwrap(); + let nominator_withdrawal = + Withdrawals::::get(operator_id, nominator_account).unwrap(); + + let total_deposit = + domain_stake_summary.current_total_stake + operator.total_storage_fee_deposit; + let total_stake_withdrawal = operator_withdrawal.total_withdrawal_amount + + nominator_withdrawal.total_withdrawal_amount; + let total_storage_fee_withdrawal = operator_withdrawal.withdrawals[0].storage_fee + + nominator_withdrawal.withdrawals[0].storage_fee; + assert_eq!(293333333331527777778, total_deposit,); + assert_eq!(21666666668472222222, total_stake_withdrawal); + assert_eq!(5000000000000000000, total_storage_fee_withdrawal); assert_eq!( - domain_stake_summary.current_total_stake, - 298666666666666666667 + 320 * SSC, + total_deposit + total_stake_withdrawal + total_storage_fee_withdrawal + ); + assert_eq!( + operator.total_storage_fee_deposit, + bundle_storage_fund::total_balance::(operator_id) ); - - let operator = Operators::::get(operator_id).unwrap(); - assert_eq!(operator.current_total_stake, 298666666666666666667); for deposit in deposits { do_nominate_operator::(operator_id, deposit.0, deposit.1).unwrap(); @@ -2417,7 +2645,8 @@ pub(crate) mod tests { nominator_free_balance - nominator_stake ); - assert!(Balances::total_balance(&crate::tests::TreasuryAccount::get()) >= 320 * SSC) + assert!(Balances::total_balance(&crate::tests::TreasuryAccount::get()) >= 320 * SSC); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), 0); }); } @@ -2472,7 +2701,21 @@ pub(crate) mod tests { assert!(domain_stake_summary.next_operators.contains(&operator_id_1)); assert!(domain_stake_summary.next_operators.contains(&operator_id_2)); assert!(domain_stake_summary.next_operators.contains(&operator_id_3)); - assert_eq!(domain_stake_summary.current_total_stake, 600 * SSC); + assert_eq!( + domain_stake_summary.current_total_stake, + STORAGE_FEE_RESERVE.left_from_one() * 600 * SSC + ); + for operator_id in [operator_id_1, operator_id_2, operator_id_3] { + let operator = Operators::::get(operator_id).unwrap(); + assert_eq!( + operator.total_storage_fee_deposit, + STORAGE_FEE_RESERVE * operator_stake + ); + assert_eq!( + operator.total_storage_fee_deposit, + bundle_storage_fund::total_balance::(operator_id) + ); + } do_slash_operators::( vec![ @@ -2515,7 +2758,91 @@ pub(crate) mod tests { assert_eq!( Balances::total_balance(&crate::tests::TreasuryAccount::get()), 600 * SSC + ); + for operator_id in [operator_id_1, operator_id_2, operator_id_3] { + assert_eq!(bundle_storage_fund::total_balance::(operator_id), 0); + } + }); + } + + #[test] + fn bundle_storage_fund_charged_and_refund_storege_fee() { + let domain_id = DomainId::new(0); + let operator_account = 1; + let operator_free_balance = 150 * SSC; + let operator_total_stake = 100 * SSC; + let operator_stake = 80 * SSC; + let operator_storage_fee_deposit = 20 * SSC; + let pair = OperatorPair::from_seed(&U256::from(0u32).into()); + let nominator_account = 2; + + let mut ext = new_test_ext(); + ext.execute_with(|| { + let (operator_id, _) = register_operator( + domain_id, + operator_account, + operator_free_balance, + operator_total_stake, + SSC, + pair.public(), + BTreeMap::default(), + ); + + let domain_staking_summary = DomainStakingSummary::::get(domain_id).unwrap(); + assert_eq!(domain_staking_summary.current_total_stake, operator_stake); + + let operator = Operators::::get(operator_id).unwrap(); + assert_eq!(operator.current_total_stake, operator_stake); + assert_eq!(operator.current_total_shares, operator_stake); + assert_eq!( + operator.total_storage_fee_deposit, + operator_storage_fee_deposit + ); + + // Drain the bundle storage fund + bundle_storage_fund::charge_bundle_storage_fee::( + operator_id, + // the transaction fee is one SSC per byte thus div SSC here + (operator_storage_fee_deposit / SSC) as u32, ) + .unwrap(); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), 0); + assert_err!( + bundle_storage_fund::charge_bundle_storage_fee::(operator_id, 1,), + bundle_storage_fund::Error::BundleStorageFeePayment + ); + + // The operator add more stake thus add deposit to the bundle storage fund + do_nominate_operator::(operator_id, operator_account, 5 * SSC).unwrap(); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), SSC); + + bundle_storage_fund::charge_bundle_storage_fee::(operator_id, 1).unwrap(); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), 0); + + // New nominator add deposit to the bundle storage fund + Balances::set_balance(&nominator_account, 100 * SSC); + do_nominate_operator::(operator_id, nominator_account, 5 * SSC).unwrap(); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), SSC); + + bundle_storage_fund::charge_bundle_storage_fee::(operator_id, 1).unwrap(); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), 0); + + // Refund of the storage fee add deposit to the bundle storage fund + bundle_storage_fund::refund_storage_fee::( + 10 * SSC, + BTreeMap::from_iter([(operator_id, 1), (operator_id + 1, 9)]), + ) + .unwrap(); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), SSC); + + // The operator `operator_id + 1` not exist thus the refund storage fee added to treasury + assert_eq!( + Balances::total_balance(&crate::tests::TreasuryAccount::get()), + 9 * SSC + ); + + bundle_storage_fund::charge_bundle_storage_fee::(operator_id, 1).unwrap(); + assert_eq!(bundle_storage_fund::total_balance::(operator_id), 0); }); } } diff --git a/crates/pallet-domains/src/staking_epoch.rs b/crates/pallet-domains/src/staking_epoch.rs index 1acdb97e57..52c4cbdd1c 100644 --- a/crates/pallet-domains/src/staking_epoch.rs +++ b/crates/pallet-domains/src/staking_epoch.rs @@ -477,6 +477,7 @@ pub(crate) fn do_finalize_slashed_operators( #[cfg(test)] mod tests { + use crate::bundle_storage_fund::STORAGE_FEE_RESERVE; use crate::domain_registry::{DomainConfig, DomainObject}; use crate::pallet::{ Deposits, DomainRegistry, DomainStakingSummary, LastEpochStakingDistribution, @@ -500,7 +501,7 @@ mod tests { use sp_core::{Pair, U256}; use sp_domains::{DomainId, OperatorAllowList, OperatorPair}; use sp_runtime::traits::Zero; - use sp_runtime::Percent; + use sp_runtime::{PerThing, Percent}; use std::collections::{BTreeMap, BTreeSet}; use subspace_runtime_primitives::SSC; @@ -690,9 +691,9 @@ mod tests { vec![(2, 10 * SSC), (4, 10 * SSC)], vec![(1, 20 * SSC), (2, 10 * SSC)], vec![ - (1, 164285714332653061237), - (2, 64761904777551020412), - (3, 10952380955510204082), + (1, 164285714327278911577), + (2, 64761904775759637192), + (3, 10952380955151927438), (4, 10 * SSC), ], 20 * SSC, @@ -700,7 +701,7 @@ mod tests { } struct FinalizeDomainParams { - total_stake: BalanceOf, + total_deposit: BalanceOf, rewards: BalanceOf, nominators: Vec<(NominatorId, ::Share)>, deposits: Vec<(NominatorId, BalanceOf)>, @@ -712,7 +713,7 @@ mod tests { let pair = OperatorPair::from_seed(&U256::from(0u32).into()); let FinalizeDomainParams { - total_stake, + total_deposit, rewards, nominators, deposits, @@ -749,10 +750,10 @@ mod tests { do_finalize_domain_current_epoch::(domain_id).unwrap(); - let mut total_deposit = BalanceOf::::zero(); + let mut total_new_deposit = BalanceOf::::zero(); for deposit in &deposits { do_nominate_operator::(operator_id, deposit.0, deposit.1).unwrap(); - total_deposit += deposit.1; + total_new_deposit += deposit.1; } if !rewards.is_zero() { @@ -766,6 +767,7 @@ mod tests { } // should also store the previous epoch details in-block + let total_stake = STORAGE_FEE_RESERVE.left_from_one() * total_deposit; let election_params = LastEpochStakingDistribution::::get(domain_id).unwrap(); assert_eq!( election_params.operators, @@ -773,15 +775,18 @@ mod tests { ); assert_eq!(election_params.total_domain_stake, total_stake); - let total_updated_stake = total_stake + total_deposit + rewards; + let total_updated_stake = total_deposit + total_new_deposit + rewards; let operator = Operators::::get(operator_id).unwrap(); - assert_eq!(operator.current_total_stake, total_updated_stake); + assert_eq!( + operator.current_total_stake + operator.total_storage_fee_deposit, + total_updated_stake + ); assert_eq!(operator.current_epoch_rewards, Zero::zero()); let domain_stake_summary = DomainStakingSummary::::get(domain_id).unwrap(); assert_eq!( domain_stake_summary.current_total_stake, - total_updated_stake + total_updated_stake - operator.total_storage_fee_deposit ); // epoch should be 3 since we did 3 epoch transitions assert_eq!(domain_stake_summary.current_epoch_index, 3); @@ -791,7 +796,7 @@ mod tests { #[test] fn finalize_domain_epoch_no_rewards() { finalize_domain_epoch(FinalizeDomainParams { - total_stake: 210 * SSC, + total_deposit: 210 * SSC, rewards: 0, nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], deposits: vec![(1, 50 * SSC), (3, 10 * SSC)], @@ -801,7 +806,7 @@ mod tests { #[test] fn finalize_domain_epoch_with_rewards() { finalize_domain_epoch(FinalizeDomainParams { - total_stake: 210 * SSC, + total_deposit: 210 * SSC, rewards: 20 * SSC, nominators: vec![(0, 150 * SSC), (1, 50 * SSC), (2, 10 * SSC)], deposits: vec![(1, 50 * SSC), (3, 10 * SSC)], @@ -836,6 +841,7 @@ mod tests { // 10% tax let nomination_tax = Percent::from_parts(10); let mut operator = Operators::::get(operator_id).unwrap(); + let pre_storage_fund_deposit = operator.total_storage_fee_deposit; operator.nomination_tax = nomination_tax; Operators::::insert(operator_id, operator); let expected_operator_tax = nomination_tax.mul_ceil(operator_rewards); @@ -845,18 +851,30 @@ mod tests { operator_take_reward_tax_and_stake::(domain_id).unwrap(); let operator = Operators::::get(operator_id).unwrap(); + let new_storage_fund_deposit = + operator.total_storage_fee_deposit - pre_storage_fund_deposit; assert_eq!( operator.current_epoch_rewards, (10 * SSC - expected_operator_tax) ); - let deposit = Deposits::::get(operator_id, operator_account) + let staking_deposit = Deposits::::get(operator_id, operator_account) .unwrap() .pending .unwrap() .amount; - assert_eq!(deposit, expected_operator_tax); - + assert_eq!( + staking_deposit + new_storage_fund_deposit, + expected_operator_tax + ); + assert_eq!( + staking_deposit, + STORAGE_FEE_RESERVE.left_from_one() * expected_operator_tax + ); + assert_eq!( + new_storage_fund_deposit, + STORAGE_FEE_RESERVE * expected_operator_tax + ); let domain_stake_summary = DomainStakingSummary::::get(domain_id).unwrap(); assert!(domain_stake_summary.current_epoch_rewards.is_empty()) }); diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 84fd1d2cc1..1da48e285d 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -75,7 +75,7 @@ frame_support::construct_runtime!( type BlockNumber = u64; type Hash = H256; -type AccountId = u64; +type AccountId = u128; impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -190,7 +190,7 @@ parameter_types! { pub const MinNominatorStake: Balance = SSC; pub const StakeWithdrawalLockingPeriod: DomainBlockNumber = 5; pub const StakeEpochDuration: DomainBlockNumber = 5; - pub TreasuryAccount: u64 = PalletId(*b"treasury").into_account_truncating(); + pub TreasuryAccount: u128 = PalletId(*b"treasury").into_account_truncating(); pub const BlockReward: Balance = 10 * SSC; pub const MaxPendingStakingOperation: u32 = 100; pub const MaxNominators: u32 = 5; @@ -218,7 +218,7 @@ impl pallet_timestamp::Config for Test { pub struct DummyStorageFee; impl StorageFee for DummyStorageFee { fn transaction_byte_fee() -> Balance { - Default::default() + SSC } fn note_storage_fees(_fee: Balance) {} } @@ -523,7 +523,7 @@ pub(crate) fn run_to_block(block_number: BlockNumberFor, parent_ha as Hooks>>::on_initialize(block_number); } -pub(crate) fn register_genesis_domain(creator: u64, operator_ids: Vec) -> DomainId { +pub(crate) fn register_genesis_domain(creator: u128, operator_ids: Vec) -> DomainId { let raw_genesis_storage = RawGenesis::dummy(vec![1, 2, 3, 4]).encode(); assert_ok!(crate::Pallet::::register_domain_runtime( RawOrigin::Root.into(), @@ -804,7 +804,7 @@ fn test_calculate_tx_range() { #[test] fn test_bundle_fromat_verification() { - let opaque_extrinsic = |dest: u64, value: u128| -> OpaqueExtrinsic { + let opaque_extrinsic = |dest: u128, value: u128| -> OpaqueExtrinsic { UncheckedExtrinsic { signature: None, function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { @@ -856,12 +856,9 @@ fn test_bundle_fromat_verification() { for i in 0..max_extrincis_count { too_large_bundle .extrinsics - .push(opaque_extrinsic(i as u64, i as u128)); + .push(opaque_extrinsic(i as u128, i as u128)); } - assert_err!( - pallet_domains::Pallet::::validate_bundle(&too_large_bundle), - BundleError::BundleTooLarge - ); + assert!(too_large_bundle.size() > max_block_size); // Bundle with wrong value of `bundle_extrinsics_root` let mut invalid_extrinsic_root_bundle = valid_bundle.clone(); @@ -904,7 +901,7 @@ fn test_bundle_fromat_verification() { #[test] fn test_invalid_fraud_proof() { - let creator = 0u64; + let creator = 0u128; let operator_id = 1u64; let head_domain_number = 10; let mut ext = new_test_ext_with_extensions(); @@ -940,7 +937,7 @@ fn test_invalid_fraud_proof() { #[test] fn test_invalid_block_fees_fraud_proof() { - let creator = 0u64; + let creator = 0u128; let operator_id = 1u64; let head_domain_number = 10; let mut ext = new_test_ext_with_extensions(); @@ -1021,7 +1018,7 @@ fn storage_proof_for_key + AsTrieBackend( #[test] fn test_invalid_domain_block_hash_fraud_proof() { - let creator = 0u64; + let creator = 0u128; let operator_id = 1u64; let head_domain_number = 10; let mut ext = new_test_ext_with_extensions(); @@ -1303,7 +1300,7 @@ fn generate_invalid_domain_block_hash_fraud_proof( #[test] fn test_basic_fraud_proof_processing() { - let creator = 0u64; + let creator = 0u128; let operator_id = 1u64; let head_domain_number = BlockTreePruningDepth::get() - 1; let test_cases = vec![ From 7deeaebbbf92641969b256a93190bdac5f6547c6 Mon Sep 17 00:00:00 2001 From: linning Date: Wed, 31 Jan 2024 08:45:20 +0800 Subject: [PATCH 11/15] Enable charge and refund of the bundle storage fee Signed-off-by: linning --- crates/pallet-domains/src/block_tree.rs | 4 +--- crates/pallet-domains/src/lib.rs | 6 ++---- crates/pallet-transaction-fees/src/lib.rs | 13 +++++++------ test/subspace-test-runtime/src/lib.rs | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index 36e804a9df..4a40b6442a 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -352,11 +352,9 @@ pub(crate) fn process_execution_receipt( return Ok(Some(ConfirmedDomainBlockInfo { domain_block_number: to_prune, operator_ids, - // TODO: also distribute the `storage_fee` rewards: execution_receipt.block_fees.domain_execution_fee, invalid_bundle_authors, - // TODO: get the `total_storage_fee` from ER - total_storage_fee: Zero::zero(), + total_storage_fee: execution_receipt.block_fees.consensus_storage_fee, paid_bundle_storage_fees, })); } diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 129d011e79..8a3d24ef1c 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -1399,8 +1399,7 @@ mod pallet { .and_then(|_| { charge_bundle_storage_fee::( opaque_bundle.operator_id(), - Zero::zero(), - // TODO: use `opaque_bundle.size()` when deposit to storage fund works + opaque_bundle.size(), ) .map_err(|_| InvalidTransaction::Call.into()) }), @@ -1445,8 +1444,7 @@ mod pallet { if let Err(e) = charge_bundle_storage_fee::( opaque_bundle.operator_id(), - Zero::zero(), - // TODO: use `opaque_bundle.size()` when deposit to storage fund works + opaque_bundle.size(), ) { log::debug!( target: "runtime::domains", diff --git a/crates/pallet-transaction-fees/src/lib.rs b/crates/pallet-transaction-fees/src/lib.rs index a66c77e706..4a996e0bb9 100644 --- a/crates/pallet-transaction-fees/src/lib.rs +++ b/crates/pallet-transaction-fees/src/lib.rs @@ -274,12 +274,13 @@ where tip: BalanceOf, ) { CollectedBlockFees::::mutate(|collected_block_fees| { - let collected_block_fees = collected_block_fees - .as_mut() - .expect("`CollectedBlockFees` was set in `on_initialize`; qed"); - collected_block_fees.storage += storage_fee; - collected_block_fees.compute += compute_fee; - collected_block_fees.tips += tip; + // `CollectedBlockFees` was set in `on_initialize` if it is `None` means this + // function is called offchain (i.e. transaction validation) thus safe to skip + if let Some(collected_block_fees) = collected_block_fees.as_mut() { + collected_block_fees.storage += storage_fee; + collected_block_fees.compute += compute_fee; + collected_block_fees.tips += tip; + } }); } } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 69329827fb..735136b8fa 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -397,7 +397,7 @@ impl pallet_transaction_fees::Config for Runtime { type BlockchainHistorySize = BlockchainHistorySize; type Currency = Balances; type FindBlockRewardAddress = Subspace; - type DynamicCostOfStorage = ConstBool<{ !cfg!(feature = "do-not-enforce-cost-of-storage") }>; + type DynamicCostOfStorage = ConstBool; type WeightInfo = (); } From 6b11c52d1c543e73a4cb81855aaffd32cd287a55 Mon Sep 17 00:00:00 2001 From: linning Date: Wed, 31 Jan 2024 19:10:24 +0800 Subject: [PATCH 12/15] Fix typo, rename Signed-off-by: linning --- .../pallet-domains/src/bundle_storage_fund.rs | 6 ++-- crates/pallet-domains/src/staking.rs | 33 ++++++++++--------- crates/pallet-domains/src/staking_epoch.rs | 2 +- .../src/domain_bundle_proposer.rs | 2 +- 4 files changed, 22 insertions(+), 21 deletions(-) diff --git a/crates/pallet-domains/src/bundle_storage_fund.rs b/crates/pallet-domains/src/bundle_storage_fund.rs index 8dd6324b60..340fbcbcfe 100644 --- a/crates/pallet-domains/src/bundle_storage_fund.rs +++ b/crates/pallet-domains/src/bundle_storage_fund.rs @@ -156,7 +156,7 @@ pub fn deposit_reserve_for_storage_fund( Ok(NewDeposit { staking, - storage_fee: storage_fee_reserve, + storage_fee_deposit: storage_fee_reserve, }) } @@ -198,10 +198,10 @@ pub fn total_balance(operator_id: OperatorId) -> BalanceOf { /// Return the bundle storage fund redeem price pub fn storage_fund_redeem_price( operator_id: OperatorId, - operartor_total_deposit: BalanceOf, + operator_total_deposit: BalanceOf, ) -> StorageFundRedeemPrice { let total_balance = total_balance::(operator_id); - StorageFundRedeemPrice::::new(total_balance, operartor_total_deposit) + StorageFundRedeemPrice::::new(total_balance, operator_total_deposit) } /// Transfer all of the balance of the bundle storage fund to the treasury diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index be13cdf594..4655f4e7d5 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -85,7 +85,7 @@ impl From<(DomainId, EpochIndex)> for DomainEpoch { pub struct NewDeposit { pub(crate) staking: Balance, - pub(crate) storage_fee: Balance, + pub(crate) storage_fee_deposit: Balance, } /// A nominator's shares against their deposits to given operator pool. @@ -129,7 +129,7 @@ pub(crate) struct WithdrawalInBalance { pub(crate) domain_id: DomainId, pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber, pub(crate) amount_to_unlock: Balance, - pub(crate) storage_fee: Balance, + pub(crate) storage_fee_refund: Balance, } #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] @@ -137,7 +137,7 @@ pub(crate) struct WithdrawalInShares { pub(crate) domain_epoch: DomainEpoch, pub(crate) unlock_at_confirmed_domain_block_number: DomainBlockNumber, pub(crate) shares: Share, - pub(crate) storage_fee: Balance, + pub(crate) storage_fee_refund: Balance, } #[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)] @@ -336,7 +336,7 @@ pub(crate) fn do_register_operator( // sum total deposits added during this epoch. deposits_in_epoch: new_deposit.staking, withdrawals_in_epoch: Zero::zero(), - total_storage_fee_deposit: new_deposit.storage_fee, + total_storage_fee_deposit: new_deposit.storage_fee_deposit, }; Operators::::insert(operator_id, operator); OperatorSigningKey::::insert(signing_key, operator_id); @@ -385,7 +385,7 @@ pub(crate) fn do_calculate_previous_epoch_deposit_shares_and_add_new_deposit( domain_epoch, unlock_at_confirmed_domain_block_number, shares, - storage_fee, + storage_fee_refund, }) = withdrawal.withdrawal_in_shares.take() { let withdrawal_amount = epoch_share_price.shares_to_stake::(shares); @@ -492,7 +492,7 @@ pub(crate) fn do_convert_previous_epoch_withdrawal( domain_id, unlock_at_confirmed_domain_block_number, amount_to_unlock: withdrawal_amount, - storage_fee, + storage_fee_refund, }; withdrawal.withdrawals.push_back(withdraw_in_balance); } @@ -531,7 +531,7 @@ pub(crate) fn do_nominate_operator( // Increase total storage fee deposit as there is new deposit to the storage fund operator.total_storage_fee_deposit = operator .total_storage_fee_deposit - .checked_add(&new_deposit.storage_fee) + .checked_add(&new_deposit.storage_fee_deposit) .ok_or(Error::BalanceOverflow)?; let current_domain_epoch = ( @@ -872,7 +872,7 @@ pub(crate) fn do_withdraw_stake( Some(WithdrawalInShares { domain_epoch, shares, - storage_fee, + storage_fee_refund, .. }) => WithdrawalInShares { domain_epoch, @@ -880,7 +880,7 @@ pub(crate) fn do_withdraw_stake( .checked_add(&shares_withdrew) .ok_or(Error::ShareOverflow)?, unlock_at_confirmed_domain_block_number, - storage_fee: storage_fee + storage_fee_refund: storage_fee_refund .checked_add(&withdraw_storage_fee) .ok_or(Error::BalanceOverflow)?, }, @@ -888,7 +888,7 @@ pub(crate) fn do_withdraw_stake( domain_epoch: domain_current_epoch, unlock_at_confirmed_domain_block_number, shares: shares_withdrew, - storage_fee: withdraw_storage_fee, + storage_fee_refund: withdraw_storage_fee, }, }; withdrawal.withdrawal_in_shares = Some(new_withdrawal_in_shares); @@ -918,7 +918,7 @@ pub(crate) fn do_unlock_funds( domain_id, unlock_at_confirmed_domain_block_number, amount_to_unlock, - storage_fee, + storage_fee_refund, } = withdrawal .withdrawals .pop_front() @@ -964,7 +964,7 @@ pub(crate) fn do_unlock_funds( T::Currency::release( &storage_fund_hold_id, &nominator_id, - storage_fee, + storage_fee_refund, Precision::Exact, ) .map_err(|_| Error::RemoveLock)?; @@ -2594,8 +2594,9 @@ pub(crate) mod tests { domain_stake_summary.current_total_stake + operator.total_storage_fee_deposit; let total_stake_withdrawal = operator_withdrawal.total_withdrawal_amount + nominator_withdrawal.total_withdrawal_amount; - let total_storage_fee_withdrawal = operator_withdrawal.withdrawals[0].storage_fee - + nominator_withdrawal.withdrawals[0].storage_fee; + let total_storage_fee_withdrawal = operator_withdrawal.withdrawals[0] + .storage_fee_refund + + nominator_withdrawal.withdrawals[0].storage_fee_refund; assert_eq!(293333333331527777778, total_deposit,); assert_eq!(21666666668472222222, total_stake_withdrawal); assert_eq!(5000000000000000000, total_storage_fee_withdrawal); diff --git a/crates/pallet-domains/src/staking_epoch.rs b/crates/pallet-domains/src/staking_epoch.rs index 52c4cbdd1c..ee8e6df4a2 100644 --- a/crates/pallet-domains/src/staking_epoch.rs +++ b/crates/pallet-domains/src/staking_epoch.rs @@ -98,7 +98,7 @@ pub(crate) fn operator_take_reward_tax_and_stake( // Increase total storage fee deposit as there is new deposit to the storage fund operator.total_storage_fee_deposit = operator .total_storage_fee_deposit - .checked_add(&operator_tax_deposit.storage_fee) + .checked_add(&operator_tax_deposit.storage_fee_deposit) .ok_or(TransitionError::BalanceOverflow)?; let current_domain_epoch = (domain_id, stake_summary.current_epoch_index).into(); diff --git a/domains/client/domain-operator/src/domain_bundle_proposer.rs b/domains/client/domain-operator/src/domain_bundle_proposer.rs index 6ca13ca166..f62cfb5160 100644 --- a/domains/client/domain-operator/src/domain_bundle_proposer.rs +++ b/domains/client/domain-operator/src/domain_bundle_proposer.rs @@ -150,7 +150,7 @@ where break; } - // TODO: stop including more tx once the operartor's available storage fund less than + // TODO: stop including more tx once the operator's available storage fund less than // `next_bundle_size * consensus_transaction_byte_fee` // Double check the transaction validity, because the tx pool are re-validate the transaction From 5c633c21cd1df374a8a6e0d042a3d2b9af00d1dc Mon Sep 17 00:00:00 2001 From: linning Date: Sat, 3 Feb 2024 03:59:23 +0800 Subject: [PATCH 13/15] Add StorageFeeDeposited event, rename Signed-off-by: linning --- crates/pallet-domains/src/bundle_storage_fund.rs | 10 ++++++++-- crates/pallet-domains/src/lib.rs | 7 ++++++- crates/pallet-domains/src/staking.rs | 4 ++-- crates/pallet-domains/src/staking_epoch.rs | 2 +- crates/pallet-domains/src/tests.rs | 2 +- crates/subspace-runtime/src/lib.rs | 2 +- test/subspace-test-runtime/src/lib.rs | 2 +- 7 files changed, 20 insertions(+), 9 deletions(-) diff --git a/crates/pallet-domains/src/bundle_storage_fund.rs b/crates/pallet-domains/src/bundle_storage_fund.rs index 340fbcbcfe..09a7fa1d2b 100644 --- a/crates/pallet-domains/src/bundle_storage_fund.rs +++ b/crates/pallet-domains/src/bundle_storage_fund.rs @@ -1,7 +1,7 @@ //! Bundle storage fund use crate::staking::NewDeposit; -use crate::{BalanceOf, Config, HoldIdentifier, Operators}; +use crate::{BalanceOf, Config, Event, HoldIdentifier, Operators, Pallet}; use codec::{Decode, Encode}; use frame_support::traits::fungible::{Inspect, Mutate, MutateHold}; use frame_support::traits::tokens::{Fortitude, Precision, Preservation}; @@ -150,6 +150,12 @@ pub fn deposit_reserve_for_storage_fund( ) .map_err(|_| Error::FailToDeposit)?; + Pallet::::deposit_event(Event::StorageFeeDeposited { + operator_id, + nominator_id: source.clone(), + amount: storage_fee_reserve, + }); + let staking = deposit_amount .checked_sub(&storage_fee_reserve) .ok_or(Error::BalanceUnderflow)?; @@ -172,7 +178,7 @@ pub fn withdraw_and_hold( } let storage_fund_acc = storage_fund_account::(operator_id); - let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); + let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal(operator_id); T::Currency::transfer_and_hold( &storage_fund_hold_id, &storage_fund_acc, diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index 8a3d24ef1c..dfffc89501 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -80,7 +80,7 @@ pub(crate) type NominatorId = ::AccountId; pub trait HoldIdentifier { fn staking_staked(operator_id: OperatorId) -> FungibleHoldId; fn domain_instantiation_id(domain_id: DomainId) -> FungibleHoldId; - fn storage_fund(operator_id: OperatorId) -> FungibleHoldId; + fn storage_fund_withdrawal(operator_id: OperatorId) -> FungibleHoldId; } pub type ExecutionReceiptOf = ExecutionReceipt< @@ -789,6 +789,11 @@ mod pallet { operator_id: OperatorId, reason: SlashedReason, ReceiptHashFor>, }, + StorageFeeDeposited { + operator_id: OperatorId, + nominator_id: NominatorId, + amount: BalanceOf, + }, } /// Per-domain state for tx range calculation. diff --git a/crates/pallet-domains/src/staking.rs b/crates/pallet-domains/src/staking.rs index 4655f4e7d5..159ecca005 100644 --- a/crates/pallet-domains/src/staking.rs +++ b/crates/pallet-domains/src/staking.rs @@ -960,7 +960,7 @@ pub(crate) fn do_unlock_funds( .map_err(|_| Error::RemoveLock)?; // Release storage fund - let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); + let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal(operator_id); T::Currency::release( &storage_fund_hold_id, &nominator_id, @@ -1021,7 +1021,7 @@ pub(crate) fn do_unlock_operator(operator_id: OperatorId) -> Result<( operator_id, operator.total_storage_fee_deposit, ); - let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); + let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal(operator_id); Deposits::::drain_prefix(operator_id).try_for_each(|(nominator_id, mut deposit)| { // convert any deposits from the previous epoch to shares do_convert_previous_epoch_deposits::(operator_id, &mut deposit)?; diff --git a/crates/pallet-domains/src/staking_epoch.rs b/crates/pallet-domains/src/staking_epoch.rs index ee8e6df4a2..034bb3e8b1 100644 --- a/crates/pallet-domains/src/staking_epoch.rs +++ b/crates/pallet-domains/src/staking_epoch.rs @@ -344,7 +344,7 @@ pub(crate) fn do_finalize_slashed_operators( let total_shares = operator.current_total_shares; let share_price = SharePrice::new::(total_shares, total_stake); - let storage_fund_hold_id = T::HoldIdentifier::storage_fund(operator_id); + let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal(operator_id); let storage_fund_redeem_price = bundle_storage_fund::storage_fund_redeem_price::( operator_id, operator.total_storage_fee_deposit, diff --git a/crates/pallet-domains/src/tests.rs b/crates/pallet-domains/src/tests.rs index 1da48e285d..b2b000d4a0 100644 --- a/crates/pallet-domains/src/tests.rs +++ b/crates/pallet-domains/src/tests.rs @@ -154,7 +154,7 @@ impl pallet_domains::HoldIdentifier for HoldIdentifier { Self::Domains(DomainsHoldIdentifier::DomainInstantiation(domain_id)) } - fn storage_fund(operator_id: OperatorId) -> Self { + fn storage_fund_withdrawal(operator_id: OperatorId) -> Self { Self::Domains(DomainsHoldIdentifier::StorageFund(operator_id)) } } diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index d4505fdda6..10c42a25ca 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -387,7 +387,7 @@ impl pallet_domains::HoldIdentifier for HoldIdentifier { Self::Domains(DomainsHoldIdentifier::DomainInstantiation(domain_id)) } - fn storage_fund(operator_id: OperatorId) -> Self { + fn storage_fund_withdrawal(operator_id: OperatorId) -> Self { Self::Domains(DomainsHoldIdentifier::StorageFund(operator_id)) } } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 735136b8fa..d928d22518 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -326,7 +326,7 @@ impl pallet_domains::HoldIdentifier for HoldIdentifier { Self::Domains(DomainsHoldIdentifier::DomainInstantiation(domain_id)) } - fn storage_fund(operator_id: OperatorId) -> Self { + fn storage_fund_withdrawal(operator_id: OperatorId) -> Self { Self::Domains(DomainsHoldIdentifier::StorageFund(operator_id)) } } From 95f7fc7441b1c1cde10c163d3e535fc7584ac8d6 Mon Sep 17 00:00:00 2001 From: NingLin-P Date: Mon, 5 Feb 2024 18:12:14 +0800 Subject: [PATCH 14/15] Update crates/pallet-domains/src/block_tree.rs Co-authored-by: Vedhavyas Singareddi --- crates/pallet-domains/src/block_tree.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-domains/src/block_tree.rs b/crates/pallet-domains/src/block_tree.rs index a58cd7ed98..675a3bbf4d 100644 --- a/crates/pallet-domains/src/block_tree.rs +++ b/crates/pallet-domains/src/block_tree.rs @@ -319,7 +319,7 @@ pub(crate) fn process_execution_receipt( execution_receipt.domain_block_hash, )); - // Collect the front paid storage and the invalid bundle author + // Collect the paid bundle storage fees and the invalid bundle author let mut paid_bundle_storage_fees = BTreeMap::new(); let mut invalid_bundle_authors = Vec::new(); let bundle_digests = ExecutionInbox::::get(( From 6a35f515840e01e826d83b8402f2d58812ca623f Mon Sep 17 00:00:00 2001 From: NingLin-P Date: Mon, 5 Feb 2024 18:12:21 +0800 Subject: [PATCH 15/15] Update crates/pallet-domains/src/bundle_storage_fund.rs Co-authored-by: Vedhavyas Singareddi --- crates/pallet-domains/src/bundle_storage_fund.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/pallet-domains/src/bundle_storage_fund.rs b/crates/pallet-domains/src/bundle_storage_fund.rs index 09a7fa1d2b..332f87190f 100644 --- a/crates/pallet-domains/src/bundle_storage_fund.rs +++ b/crates/pallet-domains/src/bundle_storage_fund.rs @@ -89,7 +89,7 @@ pub fn charge_bundle_storage_fee( Ok(()) } -/// Refund the front paid storage fee of a particular domain block back to the operator, the amount to +/// Refund the paid bundle storage fee of a particular domain block back to the operator, the amount to /// refund to a particular operator is determined by the total storage fee collected from the domain user /// and the percentage of bundle storage that the operator have submitted for the domain block. #[allow(dead_code)]