1use 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
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 "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 T::StorageFee::note_storage_fees(storage_fee);
96
97 Ok(())
98}
99
100pub 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 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 mint_into_treasury::<T>(remaining_fee).map_err(|_| Error::MintBalance)?;
134
135 Ok(())
136}
137
138pub 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
173pub 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
198pub 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
219pub 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
229pub 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
238pub 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}