pallet_domains/
bundle_storage_fund.rs

1//! Bundle storage fund
2
3use crate::staking::NewDeposit;
4use crate::staking_epoch::mint_into_treasury;
5use crate::{BalanceOf, Config, Event, HoldIdentifier, Operators, Pallet};
6use frame_support::traits::fungible::{Inspect, Mutate, MutateHold};
7use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
8use frame_support::traits::Get;
9use frame_support::PalletError;
10use parity_scale_codec::{Decode, Encode};
11use scale_info::TypeInfo;
12use sp_domains::OperatorId;
13use sp_runtime::traits::{AccountIdConversion, CheckedSub, Zero};
14use sp_runtime::Perbill;
15use sp_std::collections::btree_map::BTreeMap;
16use subspace_runtime_primitives::StorageFee;
17
18/// The proportion of staking fund reserved for the bundle storage fee
19pub const STORAGE_FEE_RESERVE: Perbill = Perbill::from_percent(20);
20
21/// Bundle storage fund specific errors
22#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
23pub enum Error {
24    BundleStorageFeePayment,
25    BalanceUnderflow,
26    MintBalance,
27    FailToDeposit,
28    WithdrawAndHold,
29    BalanceTransfer,
30    FailToWithdraw,
31}
32
33/// The type of system account being created.
34#[derive(Encode, Decode)]
35pub enum AccountType {
36    StorageFund,
37}
38
39#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq, Default)]
40pub struct StorageFundRedeemPrice<T: Config>((BalanceOf<T>, BalanceOf<T>));
41
42impl<T: Config> StorageFundRedeemPrice<T> {
43    pub(crate) fn new(total_balance: BalanceOf<T>, total_deposit: BalanceOf<T>) -> Self {
44        StorageFundRedeemPrice((total_balance, total_deposit))
45    }
46
47    /// Return the amount of balance can be redeemed by the given `deposit`, it is calculated
48    /// by `storage_fund_total_balance * deposit / total_deposit`.
49    ///
50    /// If the inflow of the storage fund (i.e. refund of the storage fee) is larger than its
51    /// outflow (i.e. payment of the storage fee), the return value will larger than `deposit`
52    /// otherwise smaller.
53    pub(crate) fn redeem(&self, deposit: BalanceOf<T>) -> BalanceOf<T> {
54        let (total_balance, total_deposit) = self.0;
55        if total_balance == total_deposit {
56            deposit
57        } else {
58            Perbill::from_rational(deposit, total_deposit).mul_floor(total_balance)
59        }
60    }
61}
62
63/// Return the bundle storage fund account of the given operator.
64pub fn storage_fund_account<T: Config>(id: OperatorId) -> T::AccountId {
65    T::PalletId::get().into_sub_account_truncating((AccountType::StorageFund, id))
66}
67
68/// Charge the bundle storage fee from the operator's bundle storage fund
69pub fn charge_bundle_storage_fee<T: Config>(
70    operator_id: OperatorId,
71    bundle_size: u32,
72) -> Result<(), Error> {
73    if bundle_size.is_zero() {
74        return Ok(());
75    }
76
77    let storage_fund_acc = storage_fund_account::<T>(operator_id);
78    let storage_fee = T::StorageFee::transaction_byte_fee() * bundle_size.into();
79
80    if let Err(err) = T::Currency::burn_from(
81        &storage_fund_acc,
82        storage_fee,
83        Preservation::Expendable,
84        Precision::Exact,
85        Fortitude::Polite,
86    ) {
87        let total_balance = total_balance::<T>(operator_id);
88        log::debug!(
89            target: "runtime::domains",
90            "Operator {operator_id:?} unable to pay for the bundle storage fee {storage_fee:?}, storage fund total balance {total_balance:?}, err {err:?}",
91        );
92        return Err(Error::BundleStorageFeePayment);
93    }
94
95    // Note the storage fee, it will go to the consensus block author
96    T::StorageFee::note_storage_fees(storage_fee);
97
98    Ok(())
99}
100
101/// Refund the paid bundle storage fee of a particular domain block back to the operator, the amount to
102/// refund to a particular operator is determined by the total storage fee collected from the domain user
103/// and the percentage of bundle storage that the operator have submitted for the domain block.
104pub fn refund_storage_fee<T: Config>(
105    total_storage_fee: BalanceOf<T>,
106    paid_bundle_storage_fees: BTreeMap<OperatorId, u32>,
107) -> Result<(), Error> {
108    if total_storage_fee.is_zero() {
109        return Ok(());
110    }
111
112    let total_paid_storage = paid_bundle_storage_fees.values().sum::<u32>();
113    let mut remaining_fee = total_storage_fee;
114    for (operator_id, paid_storage) in paid_bundle_storage_fees {
115        // If the operator is deregistered and unlocked or slashed and finalized, the refund bundle storage
116        // fee will go to the treasury
117        if Operators::<T>::get(operator_id).is_none() || paid_storage.is_zero() {
118            continue;
119        }
120
121        let refund_amount = {
122            let paid_storage_percentage = Perbill::from_rational(paid_storage, total_paid_storage);
123            paid_storage_percentage.mul_floor(total_storage_fee)
124        };
125        let storage_fund_acc = storage_fund_account::<T>(operator_id);
126        T::Currency::mint_into(&storage_fund_acc, refund_amount).map_err(|_| Error::MintBalance)?;
127
128        remaining_fee = remaining_fee
129            .checked_sub(&refund_amount)
130            .ok_or(Error::BalanceUnderflow)?;
131    }
132
133    // Drop any dust and deregistered/slashed operator's bundle storage fee to the treasury
134    mint_into_treasury::<T>(remaining_fee).map_err(|_| Error::MintBalance)?;
135
136    Ok(())
137}
138
139/// Split the new deposit into 2 parts: the staking deposit and the the storage fee deposit,
140/// add the storage fee deposit to the bundle storage fund.
141pub fn deposit_reserve_for_storage_fund<T: Config>(
142    operator_id: OperatorId,
143    source: &T::AccountId,
144    deposit_amount: BalanceOf<T>,
145) -> Result<NewDeposit<BalanceOf<T>>, Error> {
146    let storage_fund_acc = storage_fund_account::<T>(operator_id);
147
148    let storage_fee_reserve = STORAGE_FEE_RESERVE.mul_floor(deposit_amount);
149
150    T::Currency::transfer(
151        source,
152        &storage_fund_acc,
153        storage_fee_reserve,
154        Preservation::Preserve,
155    )
156    .map_err(|_| Error::FailToDeposit)?;
157
158    Pallet::<T>::deposit_event(Event::StorageFeeDeposited {
159        operator_id,
160        nominator_id: source.clone(),
161        amount: storage_fee_reserve,
162    });
163
164    let staking = deposit_amount
165        .checked_sub(&storage_fee_reserve)
166        .ok_or(Error::BalanceUnderflow)?;
167
168    Ok(NewDeposit {
169        staking,
170        storage_fee_deposit: storage_fee_reserve,
171    })
172}
173
174/// Transfer the given `withdraw_amount` of balance from the bundle storage fund to the
175/// given `dest_account` and hold on the `dest_account`
176pub fn withdraw_and_hold<T: Config>(
177    operator_id: OperatorId,
178    dest_account: &T::AccountId,
179    withdraw_amount: BalanceOf<T>,
180) -> Result<BalanceOf<T>, Error> {
181    if withdraw_amount.is_zero() {
182        return Ok(Zero::zero());
183    }
184
185    let storage_fund_acc = storage_fund_account::<T>(operator_id);
186    let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
187    T::Currency::transfer_and_hold(
188        &storage_fund_hold_id,
189        &storage_fund_acc,
190        dest_account,
191        withdraw_amount,
192        Precision::Exact,
193        Preservation::Expendable,
194        Fortitude::Force,
195    )
196    .map_err(|_| Error::WithdrawAndHold)
197}
198
199/// Transfer the given `withdraw_amount` of balance from the bundle storage fund to the
200/// given `dest_account`
201pub fn withdraw_to<T: Config>(
202    operator_id: OperatorId,
203    dest_account: &T::AccountId,
204    withdraw_amount: BalanceOf<T>,
205) -> Result<BalanceOf<T>, Error> {
206    if withdraw_amount.is_zero() {
207        return Ok(Zero::zero());
208    }
209
210    let storage_fund_acc = storage_fund_account::<T>(operator_id);
211    T::Currency::transfer(
212        &storage_fund_acc,
213        dest_account,
214        withdraw_amount,
215        Preservation::Expendable,
216    )
217    .map_err(|_| Error::FailToWithdraw)
218}
219
220/// Return the total balance of the bundle storage fund the given `operator_id`
221pub fn total_balance<T: Config>(operator_id: OperatorId) -> BalanceOf<T> {
222    let storage_fund_acc = storage_fund_account::<T>(operator_id);
223    T::Currency::reducible_balance(
224        &storage_fund_acc,
225        Preservation::Expendable,
226        Fortitude::Polite,
227    )
228}
229
230/// Return the bundle storage fund redeem price
231pub fn storage_fund_redeem_price<T: Config>(
232    operator_id: OperatorId,
233    operator_total_deposit: BalanceOf<T>,
234) -> StorageFundRedeemPrice<T> {
235    let total_balance = total_balance::<T>(operator_id);
236    StorageFundRedeemPrice::<T>::new(total_balance, operator_total_deposit)
237}
238
239/// Transfer all of the balance of the bundle storage fund to the treasury
240pub fn transfer_all_to_treasury<T: Config>(operator_id: OperatorId) -> Result<(), Error> {
241    let storage_fund_acc = storage_fund_account::<T>(operator_id);
242    let total_balance = total_balance::<T>(operator_id);
243    T::Currency::transfer(
244        &storage_fund_acc,
245        &T::TreasuryAccount::get(),
246        total_balance,
247        Preservation::Expendable,
248    )
249    .map_err(|_| Error::BalanceTransfer)?;
250    Ok(())
251}