1use crate::{
24 BalanceOf, Config, FreezeReason, HoldReason, LOG_TARGET, NativeDepositOf,
25 evm::fees::InfoT as FeeInfo,
26};
27use core::marker::PhantomData;
28use frame_support::traits::{
29 Get,
30 fungible::{
31 Balanced as _, Inspect as _, InspectHold as _, Mutate as _, MutateHold as _,
32 Unbalanced as _,
33 },
34 tokens::{
35 DepositConsequence, Fortitude, Precision, Preservation, Provenance, Restriction, fungibles,
36 },
37};
38use sp_runtime::{
39 DispatchError, DispatchResult, Perbill, TokenError,
40 traits::{Saturating, Zero},
41};
42
43mod sealed {
44 use super::PGasDeposit;
45
46 pub trait Sealed {}
47
48 impl Sealed for () {}
49
50 impl<T, Mutator, Holder, Freezer, Id, RefundPercent> Sealed
51 for PGasDeposit<T, Mutator, Holder, Freezer, Id, RefundPercent>
52 {
53 }
54}
55
56pub enum Funds<'a, AccountId> {
60 Balance(&'a AccountId),
62 TxFee(&'a AccountId),
64}
65
66pub trait Deposit<T: Config>: sealed::Sealed {
68 const SUPPORTS_PGAS: bool;
73
74 fn init_contract(contract: &T::AccountId) -> DispatchResult;
78
79 fn destroy_contract(contract: &T::AccountId) -> DispatchResult;
83
84 fn charge_and_hold(
92 reason: HoldReason,
93 src: Funds<T::AccountId>,
94 to: &T::AccountId,
95 amount: BalanceOf<T>,
96 ) -> DispatchResult;
97
98 fn refund_on_hold(
107 reason: HoldReason,
108 from: &T::AccountId,
109 dst: Funds<T::AccountId>,
110 amount: BalanceOf<T>,
111 ) -> DispatchResult;
112
113 fn total_on_hold(reason: HoldReason, who: &T::AccountId) -> BalanceOf<T>;
119
120 fn refund_all(
132 from: &T::AccountId,
133 dst: Funds<T::AccountId>,
134 ) -> Result<BalanceOf<T>, DispatchError>;
135
136 fn migrate_native_to_pgas(
148 reason: HoldReason,
149 contract: &T::AccountId,
150 amount: BalanceOf<T>,
151 ) -> DispatchResult;
152}
153
154impl<T: Config> Deposit<T> for () {
156 const SUPPORTS_PGAS: bool = false;
157
158 fn init_contract(to: &T::AccountId) -> DispatchResult {
164 let ed = T::Currency::minimum_balance();
165 T::Currency::mint_into(to, ed)?;
166 T::Currency::deactivate(ed);
167 Ok(())
168 }
169
170 fn destroy_contract(contract: &T::AccountId) -> DispatchResult {
171 let ed = T::Currency::minimum_balance();
172 T::Currency::burn_from(
173 contract,
174 ed,
175 Preservation::Expendable,
176 Precision::Exact,
177 Fortitude::Polite,
178 )?;
179 T::Currency::reactivate(ed);
182 Ok(())
183 }
184
185 fn charge_and_hold(
186 reason: HoldReason,
187 src: Funds<T::AccountId>,
188 to: &T::AccountId,
189 amount: BalanceOf<T>,
190 ) -> DispatchResult {
191 match src {
192 Funds::Balance(from) => {
193 T::Currency::transfer_and_hold(
194 &reason.into(),
195 from,
196 to,
197 amount,
198 Precision::Exact,
199 Preservation::Preserve,
200 Fortitude::Polite,
201 )?;
202 },
203 Funds::TxFee(_) => {
204 let credit = T::FeeInfo::withdraw_txfee(amount)
205 .ok_or(DispatchError::Token(TokenError::FundsUnavailable))?;
206 T::Currency::resolve(to, credit)
207 .map_err(|_| DispatchError::Token(TokenError::FundsUnavailable))?;
208 T::Currency::hold(&reason.into(), to, amount)?;
209 },
210 }
211 Ok(())
212 }
213
214 fn refund_on_hold(
215 reason: HoldReason,
216 from: &T::AccountId,
217 dst: Funds<T::AccountId>,
218 amount: BalanceOf<T>,
219 ) -> DispatchResult {
220 match dst {
221 Funds::Balance(to) => {
222 T::Currency::transfer_on_hold(
223 &reason.into(),
224 from,
225 to,
226 amount,
227 Precision::Exact,
228 Restriction::Free,
229 Fortitude::Polite,
230 )?;
231 },
232 Funds::TxFee(_) => {
233 let released =
234 T::Currency::release(&reason.into(), from, amount, Precision::Exact)?;
235 let credit = T::Currency::withdraw(
236 from,
237 released,
238 Precision::Exact,
239 Preservation::Preserve,
240 Fortitude::Polite,
241 )?;
242 T::FeeInfo::deposit_txfee(credit);
243 },
244 }
245 Ok(())
246 }
247
248 fn total_on_hold(reason: HoldReason, who: &T::AccountId) -> BalanceOf<T> {
249 T::Currency::balance_on_hold(&reason.into(), who)
250 }
251
252 fn refund_all(
253 from: &T::AccountId,
254 dst: Funds<T::AccountId>,
255 ) -> Result<BalanceOf<T>, DispatchError> {
256 let reason = HoldReason::StorageDepositReserve;
257 let amount = T::Currency::balance_on_hold(&reason.into(), from)
260 .min(T::Currency::reducible_total_balance_on_hold(from, Fortitude::Polite));
261 if !amount.is_zero() {
262 <Self as Deposit<T>>::refund_on_hold(reason, from, dst, amount)?;
263 }
264 Ok(amount)
265 }
266
267 fn migrate_native_to_pgas(
268 _reason: HoldReason,
269 _contract: &T::AccountId,
270 _amount: BalanceOf<T>,
271 ) -> DispatchResult {
272 Ok(())
273 }
274}
275
276pub struct PGasDeposit<T, Mutator, Holder, Freezer, Id, RefundPercent>(
280 PhantomData<(T, Mutator, Holder, Freezer, Id, RefundPercent)>,
281);
282
283impl<T, Mutator, Holder, Freezer, Id, RefundPercent> Deposit<T>
284 for PGasDeposit<T, Mutator, Holder, Freezer, Id, RefundPercent>
285where
286 T: Config,
287 Mutator: fungibles::Mutate<T::AccountId, Balance = BalanceOf<T>>,
288 Holder: fungibles::MutateHold<
289 T::AccountId,
290 Balance = BalanceOf<T>,
291 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
292 >,
293 <Holder as fungibles::InspectHold<T::AccountId>>::Reason: From<HoldReason>,
294 Freezer: fungibles::freeze::Mutate<
295 T::AccountId,
296 Balance = BalanceOf<T>,
297 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
298 >,
299 <Freezer as fungibles::freeze::Inspect<T::AccountId>>::Id: From<FreezeReason>,
300 Id: Get<<Mutator as fungibles::Inspect<T::AccountId>>::AssetId>,
301 RefundPercent: Get<Perbill>,
302{
303 const SUPPORTS_PGAS: bool = true;
304
305 fn init_contract(to: &T::AccountId) -> DispatchResult {
313 <() as Deposit<T>>::init_contract(to)?;
314 let pgas_ed = <Mutator as fungibles::Inspect<T::AccountId>>::minimum_balance(Id::get());
315 <Mutator as fungibles::Mutate<T::AccountId>>::mint_into(Id::get(), to, pgas_ed)?;
316 <Freezer as fungibles::freeze::Mutate<T::AccountId>>::set_freeze(
317 Id::get(),
318 &FreezeReason::PGasMinBalance.into(),
319 to,
320 pgas_ed,
321 )?;
322 Ok(())
323 }
324
325 fn destroy_contract(contract: &T::AccountId) -> DispatchResult {
327 <() as Deposit<T>>::destroy_contract(contract)?;
328
329 <Freezer as fungibles::freeze::Mutate<T::AccountId>>::thaw(
330 Id::get(),
331 &FreezeReason::PGasMinBalance.into(),
332 contract,
333 )?;
334 let ed = <Mutator as fungibles::Inspect<T::AccountId>>::balance(Id::get(), contract);
335 <Mutator as fungibles::Mutate<T::AccountId>>::burn_from(
336 Id::get(),
337 contract,
338 ed,
339 Preservation::Expendable,
340 Precision::BestEffort,
341 Fortitude::Polite,
342 )?;
343
344 Ok(())
345 }
346
347 fn charge_and_hold(
354 reason: HoldReason,
355 src: Funds<T::AccountId>,
356 to: &T::AccountId,
357 amount: BalanceOf<T>,
358 ) -> DispatchResult {
359 let from = match &src {
360 Funds::Balance(from) | Funds::TxFee(from) => *from,
361 };
362
363 if Self::pgas_reducible_balance(from) >= amount {
364 <Holder as fungibles::MutateHold<T::AccountId>>::transfer_and_hold(
365 Id::get(),
366 &reason.into(),
367 from,
368 to,
369 amount,
370 Precision::Exact,
371 Preservation::Expendable,
372 Fortitude::Polite,
373 )?;
374 } else {
375 <() as Deposit<T>>::charge_and_hold(reason, src, to, amount)?;
376 Self::record_native_deposit(from, to, amount);
377 }
378
379 Ok(())
380 }
381
382 fn refund_on_hold(
390 reason: HoldReason,
391 from: &T::AccountId,
392 dst: Funds<T::AccountId>,
393 amount: BalanceOf<T>,
394 ) -> DispatchResult {
395 let to = match &dst {
396 Funds::Balance(to) | Funds::TxFee(to) => *to,
397 };
398 let contribution = NativeDepositOf::<T>::get(from, to);
399 let native_requested = amount.min(contribution);
400
401 let native_refunded = if !native_requested.is_zero() {
402 <() as Deposit<T>>::refund_on_hold(reason, from, dst, native_requested)?;
403 let new_val = contribution.saturating_sub(native_requested);
404 if new_val.is_zero() {
405 NativeDepositOf::<T>::remove(from, to);
406 } else {
407 NativeDepositOf::<T>::insert(from, to, new_val);
408 }
409 native_requested
410 } else {
411 BalanceOf::<T>::zero()
412 };
413
414 let pgas_needed = amount.saturating_sub(native_refunded);
415 Self::settle_pgas_refund(reason, from, to, pgas_needed)?;
416 Ok(())
417 }
418
419 fn total_on_hold(reason: HoldReason, who: &T::AccountId) -> BalanceOf<T> {
421 let native_held = <() as Deposit<T>>::total_on_hold(reason, who);
422 let pgas_held = Self::pgas_on_hold(reason, who);
423 native_held.saturating_add(pgas_held)
424 }
425
426 fn refund_all(
434 from: &T::AccountId,
435 dst: Funds<T::AccountId>,
436 ) -> Result<BalanceOf<T>, DispatchError> {
437 let to = match &dst {
438 Funds::Balance(to) | Funds::TxFee(to) => *to,
439 };
440 let native = <() as Deposit<T>>::refund_all(from, dst)?;
441 let reason = HoldReason::StorageDepositReserve;
442
443 let pgas = Self::pgas_on_hold(reason, from);
444 let pgas = Self::settle_pgas_refund(reason, from, to, pgas)?;
445 Ok(native.saturating_add(pgas))
446 }
447
448 fn migrate_native_to_pgas(
452 reason: HoldReason,
453 contract: &T::AccountId,
454 amount: BalanceOf<T>,
455 ) -> DispatchResult {
456 let pgas_ed = <Mutator as fungibles::Inspect<T::AccountId>>::minimum_balance(Id::get());
457 let freeze_id = FreezeReason::PGasMinBalance.into();
458 if <Freezer as fungibles::freeze::Inspect<T::AccountId>>::balance_frozen(
459 Id::get(),
460 &freeze_id,
461 contract,
462 ) < pgas_ed
463 {
464 if <Mutator as fungibles::Inspect<T::AccountId>>::balance(Id::get(), contract) < pgas_ed
465 {
466 <Mutator as fungibles::Mutate<T::AccountId>>::mint_into(
467 Id::get(),
468 contract,
469 pgas_ed,
470 )
471 .inspect_err(|err| {
472 log::debug!(
473 target: LOG_TARGET,
474 "Failed to mint PGAS ED for contract: {err:?}",
475 )
476 })?;
477 }
478 <Freezer as fungibles::freeze::Mutate<T::AccountId>>::set_freeze(
479 Id::get(),
480 &freeze_id,
481 contract,
482 pgas_ed,
483 )
484 .inspect_err(|err| {
485 log::debug!(
486 target: LOG_TARGET,
487 "Failed to freeze PGAS ED for contract: {err:?}",
488 )
489 })?;
490 }
491
492 if amount.is_zero() {
493 return Ok(());
494 }
495
496 T::Currency::burn_held(
497 &reason.into(),
498 contract,
499 amount,
500 Precision::Exact,
501 Fortitude::Polite,
502 )
503 .inspect_err(
504 |err| log::debug!(target: LOG_TARGET, "Failed to burn held amount {amount:?}: {err:?}"),
505 )?;
506
507 <Mutator as fungibles::Mutate<T::AccountId>>::mint_into(Id::get(), contract, amount)
508 .inspect_err(
509 |err| log::debug!(target: LOG_TARGET, "Failed to mint to {contract:?} amount: {amount:?}: {err:?}"),
510 )?;
511
512 <Holder as fungibles::MutateHold<T::AccountId>>::hold(
513 Id::get(),
514 &reason.into(),
515 contract,
516 amount,
517 )
518 .inspect_err(
519 |err| log::debug!(target: LOG_TARGET, "Failed to hold amount in {contract:?}: {amount:?}: {err:?}"),
520 )?;
521 Ok(())
522 }
523}
524
525impl<T, Mutator, Holder, Freezer, Id, RefundPercent>
526 PGasDeposit<T, Mutator, Holder, Freezer, Id, RefundPercent>
527where
528 T: Config,
529 Mutator: fungibles::Mutate<T::AccountId, Balance = BalanceOf<T>>,
530 Holder: fungibles::MutateHold<
531 T::AccountId,
532 Balance = BalanceOf<T>,
533 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
534 >,
535 <Holder as fungibles::InspectHold<T::AccountId>>::Reason: From<HoldReason>,
536 Freezer: fungibles::freeze::Mutate<
537 T::AccountId,
538 Balance = BalanceOf<T>,
539 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
540 >,
541 <Freezer as fungibles::freeze::Inspect<T::AccountId>>::Id: From<FreezeReason>,
542 Id: Get<<Mutator as fungibles::Inspect<T::AccountId>>::AssetId>,
543 RefundPercent: Get<Perbill>,
544{
545 fn pgas_reducible_balance(who: &T::AccountId) -> BalanceOf<T> {
546 <Mutator as fungibles::Inspect<T::AccountId>>::reducible_balance(
547 Id::get(),
548 who,
549 Preservation::Expendable,
550 Fortitude::Polite,
551 )
552 }
553
554 fn pgas_on_hold(reason: HoldReason, who: &T::AccountId) -> BalanceOf<T> {
555 <Holder as fungibles::InspectHold<T::AccountId>>::balance_on_hold(
556 Id::get(),
557 &reason.into(),
558 who,
559 )
560 }
561
562 fn record_native_deposit(from: &T::AccountId, to: &T::AccountId, amount: BalanceOf<T>) {
565 NativeDepositOf::<T>::mutate(to, from, |entitlement| {
566 *entitlement = entitlement.saturating_add(amount);
567 });
568 }
569
570 fn settle_pgas_refund(
582 reason: HoldReason,
583 from: &T::AccountId,
584 to: &T::AccountId,
585 amount: BalanceOf<T>,
586 ) -> Result<BalanceOf<T>, DispatchError> {
587 if amount.is_zero() {
588 return Ok(BalanceOf::<T>::zero());
589 }
590 let pgas_held = Self::pgas_on_hold(reason, from);
594 let amount = amount.min(pgas_held);
595 if amount.is_zero() {
596 return Ok(BalanceOf::<T>::zero());
597 }
598 let refund = RefundPercent::get().mul_floor(amount);
599 let mut burn = amount.saturating_sub(refund);
600 let mut refunded = BalanceOf::<T>::zero();
601
602 if !refund.is_zero() {
603 let can_credit = matches!(
604 <Mutator as fungibles::Inspect<T::AccountId>>::can_deposit(
605 Id::get(),
606 to,
607 refund,
608 Provenance::Extant,
609 ),
610 DepositConsequence::Success
611 );
612 if can_credit {
613 refunded = <Holder as fungibles::MutateHold<T::AccountId>>::transfer_on_hold(
614 Id::get(),
615 &reason.into(),
616 from,
617 to,
618 refund,
619 Precision::BestEffort,
620 Restriction::Free,
621 Fortitude::Polite,
622 )?;
623 } else {
624 burn = burn.saturating_add(refund);
625 }
626 }
627
628 if !burn.is_zero() {
629 <Holder as fungibles::MutateHold<T::AccountId>>::burn_held(
630 Id::get(),
631 &reason.into(),
632 from,
633 burn,
634 Precision::Exact,
635 Fortitude::Polite,
636 )?;
637 }
638 Ok(refunded)
639 }
640}