1use 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
18pub const STORAGE_FEE_RESERVE: Perbill = Perbill::from_percent(20);
20
21#[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#[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 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
63pub fn storage_fund_account<T: Config>(id: OperatorId) -> T::AccountId {
65 T::PalletId::get().into_sub_account_truncating((AccountType::StorageFund, id))
66}
67
68pub 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 T::StorageFee::note_storage_fees(storage_fee);
97
98 Ok(())
99}
100
101pub 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 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 mint_into_treasury::<T>(remaining_fee).map_err(|_| Error::MintBalance)?;
135
136 Ok(())
137}
138
139pub 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
174pub 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
199pub 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
220pub 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
230pub 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
239pub 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}