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::PalletError;
7use frame_support::traits::Get;
8use frame_support::traits::fungible::{Inspect, Mutate, MutateHold};
9use frame_support::traits::tokens::{Fortitude, Precision, Preservation};
10use parity_scale_codec::{Decode, Encode};
11use scale_info::TypeInfo;
12use sp_domains::OperatorId;
13use sp_runtime::Perbill;
14use sp_runtime::traits::{AccountIdConversion, CheckedSub, Zero};
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            "Operator {operator_id:?} unable to pay for the bundle storage fee {storage_fee:?}, storage fund total balance {total_balance:?}, err {err:?}",
90        );
91        return Err(Error::BundleStorageFeePayment);
92    }
93
94    // Note the storage fee, it will go to the consensus block author
95    T::StorageFee::note_storage_fees(storage_fee);
96
97    Ok(())
98}
99
100/// Refund the paid bundle storage fee of a particular domain block back to the operator, the amount to
101/// refund to a particular operator is determined by the total storage fee collected from the domain user
102/// and the percentage of bundle storage that the operator have submitted for the domain block.
103pub fn refund_storage_fee<T: Config>(
104    total_storage_fee: BalanceOf<T>,
105    paid_bundle_storage_fees: BTreeMap<OperatorId, u32>,
106) -> Result<(), Error> {
107    if total_storage_fee.is_zero() {
108        return Ok(());
109    }
110
111    let total_paid_storage = paid_bundle_storage_fees.values().sum::<u32>();
112    let mut remaining_fee = total_storage_fee;
113    for (operator_id, paid_storage) in paid_bundle_storage_fees {
114        // If the operator is deregistered and unlocked or slashed and finalized, the refund bundle storage
115        // fee will go to the treasury
116        if Operators::<T>::get(operator_id).is_none() || paid_storage.is_zero() {
117            continue;
118        }
119
120        let refund_amount = {
121            let paid_storage_percentage = Perbill::from_rational(paid_storage, total_paid_storage);
122            paid_storage_percentage.mul_floor(total_storage_fee)
123        };
124        let storage_fund_acc = storage_fund_account::<T>(operator_id);
125        T::Currency::mint_into(&storage_fund_acc, refund_amount).map_err(|_| Error::MintBalance)?;
126
127        remaining_fee = remaining_fee
128            .checked_sub(&refund_amount)
129            .ok_or(Error::BalanceUnderflow)?;
130    }
131
132    // Drop any dust and deregistered/slashed operator's bundle storage fee to the treasury
133    mint_into_treasury::<T>(remaining_fee).map_err(|_| Error::MintBalance)?;
134
135    Ok(())
136}
137
138/// Split the new deposit into 2 parts: the staking deposit and the storage fee deposit,
139/// add the storage fee deposit to the bundle storage fund.
140pub fn deposit_reserve_for_storage_fund<T: Config>(
141    operator_id: OperatorId,
142    source: &T::AccountId,
143    deposit_amount: BalanceOf<T>,
144) -> Result<NewDeposit<BalanceOf<T>>, Error> {
145    let storage_fund_acc = storage_fund_account::<T>(operator_id);
146
147    let storage_fee_reserve = STORAGE_FEE_RESERVE.mul_floor(deposit_amount);
148
149    T::Currency::transfer(
150        source,
151        &storage_fund_acc,
152        storage_fee_reserve,
153        Preservation::Preserve,
154    )
155    .map_err(|_| Error::FailToDeposit)?;
156
157    Pallet::<T>::deposit_event(Event::StorageFeeDeposited {
158        operator_id,
159        nominator_id: source.clone(),
160        amount: storage_fee_reserve,
161    });
162
163    let staking = deposit_amount
164        .checked_sub(&storage_fee_reserve)
165        .ok_or(Error::BalanceUnderflow)?;
166
167    Ok(NewDeposit {
168        staking,
169        storage_fee_deposit: storage_fee_reserve,
170    })
171}
172
173/// Transfer the given `withdraw_amount` of balance from the bundle storage fund to the
174/// given `dest_account` and hold on the `dest_account`
175pub fn withdraw_and_hold<T: Config>(
176    operator_id: OperatorId,
177    dest_account: &T::AccountId,
178    withdraw_amount: BalanceOf<T>,
179) -> Result<BalanceOf<T>, Error> {
180    if withdraw_amount.is_zero() {
181        return Ok(Zero::zero());
182    }
183
184    let storage_fund_acc = storage_fund_account::<T>(operator_id);
185    let storage_fund_hold_id = T::HoldIdentifier::storage_fund_withdrawal();
186    T::Currency::transfer_and_hold(
187        &storage_fund_hold_id,
188        &storage_fund_acc,
189        dest_account,
190        withdraw_amount,
191        Precision::Exact,
192        Preservation::Expendable,
193        Fortitude::Force,
194    )
195    .map_err(|_| Error::WithdrawAndHold)
196}
197
198/// Transfer the given `withdraw_amount` of balance from the bundle storage fund to the
199/// given `dest_account`
200pub fn withdraw_to<T: Config>(
201    operator_id: OperatorId,
202    dest_account: &T::AccountId,
203    withdraw_amount: BalanceOf<T>,
204) -> Result<BalanceOf<T>, Error> {
205    if withdraw_amount.is_zero() {
206        return Ok(Zero::zero());
207    }
208
209    let storage_fund_acc = storage_fund_account::<T>(operator_id);
210    T::Currency::transfer(
211        &storage_fund_acc,
212        dest_account,
213        withdraw_amount,
214        Preservation::Expendable,
215    )
216    .map_err(|_| Error::FailToWithdraw)
217}
218
219/// Return the total balance of the bundle storage fund the given `operator_id`
220pub fn total_balance<T: Config>(operator_id: OperatorId) -> BalanceOf<T> {
221    let storage_fund_acc = storage_fund_account::<T>(operator_id);
222    T::Currency::reducible_balance(
223        &storage_fund_acc,
224        Preservation::Expendable,
225        Fortitude::Polite,
226    )
227}
228
229/// Return the bundle storage fund redeem price
230pub fn storage_fund_redeem_price<T: Config>(
231    operator_id: OperatorId,
232    operator_total_deposit: BalanceOf<T>,
233) -> StorageFundRedeemPrice<T> {
234    let total_balance = total_balance::<T>(operator_id);
235    StorageFundRedeemPrice::<T>::new(total_balance, operator_total_deposit)
236}
237
238/// Transfer all of the balance of the bundle storage fund to the treasury
239pub fn transfer_all_to_treasury<T: Config>(operator_id: OperatorId) -> Result<(), Error> {
240    let storage_fund_acc = storage_fund_account::<T>(operator_id);
241    let total_balance = total_balance::<T>(operator_id);
242    T::Currency::transfer(
243        &storage_fund_acc,
244        &T::TreasuryAccount::get(),
245        total_balance,
246        Preservation::Expendable,
247    )
248    .map_err(|_| Error::BalanceTransfer)?;
249    Ok(())
250}