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(
130 from: &T::AccountId,
131 dst: Funds<T::AccountId>,
132 ) -> Result<BalanceOf<T>, DispatchError>;
133
134 fn migrate_native_to_pgas(
146 reason: HoldReason,
147 contract: &T::AccountId,
148 amount: BalanceOf<T>,
149 ) -> DispatchResult;
150}
151
152impl<T: Config> Deposit<T> for () {
154 const SUPPORTS_PGAS: bool = false;
155
156 fn init_contract(to: &T::AccountId) -> DispatchResult {
162 let ed = T::Currency::minimum_balance();
163 T::Currency::mint_into(to, ed)?;
164 T::Currency::deactivate(ed);
165 Ok(())
166 }
167
168 fn destroy_contract(contract: &T::AccountId) -> DispatchResult {
169 let ed = T::Currency::minimum_balance();
170 T::Currency::burn_from(
171 contract,
172 ed,
173 Preservation::Expendable,
174 Precision::Exact,
175 Fortitude::Polite,
176 )?;
177 T::Currency::reactivate(ed);
180 Ok(())
181 }
182
183 fn charge_and_hold(
184 reason: HoldReason,
185 src: Funds<T::AccountId>,
186 to: &T::AccountId,
187 amount: BalanceOf<T>,
188 ) -> DispatchResult {
189 match src {
190 Funds::Balance(from) => {
191 T::Currency::transfer_and_hold(
192 &reason.into(),
193 from,
194 to,
195 amount,
196 Precision::Exact,
197 Preservation::Preserve,
198 Fortitude::Polite,
199 )?;
200 },
201 Funds::TxFee(_) => {
202 let credit = T::FeeInfo::withdraw_txfee(amount)
203 .ok_or(DispatchError::Token(TokenError::FundsUnavailable))?;
204 T::Currency::resolve(to, credit)
205 .map_err(|_| DispatchError::Token(TokenError::FundsUnavailable))?;
206 T::Currency::hold(&reason.into(), to, amount)?;
207 },
208 }
209 Ok(())
210 }
211
212 fn refund_on_hold(
213 reason: HoldReason,
214 from: &T::AccountId,
215 dst: Funds<T::AccountId>,
216 amount: BalanceOf<T>,
217 ) -> DispatchResult {
218 match dst {
219 Funds::Balance(to) => {
220 T::Currency::transfer_on_hold(
221 &reason.into(),
222 from,
223 to,
224 amount,
225 Precision::Exact,
226 Restriction::Free,
227 Fortitude::Polite,
228 )?;
229 },
230 Funds::TxFee(_) => {
231 let released =
232 T::Currency::release(&reason.into(), from, amount, Precision::Exact)?;
233 let credit = T::Currency::withdraw(
234 from,
235 released,
236 Precision::Exact,
237 Preservation::Preserve,
238 Fortitude::Polite,
239 )?;
240 T::FeeInfo::deposit_txfee(credit);
241 },
242 }
243 Ok(())
244 }
245
246 fn total_on_hold(reason: HoldReason, who: &T::AccountId) -> BalanceOf<T> {
247 T::Currency::balance_on_hold(&reason.into(), who)
248 }
249
250 fn refund_all(
251 from: &T::AccountId,
252 dst: Funds<T::AccountId>,
253 ) -> Result<BalanceOf<T>, DispatchError> {
254 let reason = HoldReason::StorageDepositReserve;
255 let amount = T::Currency::balance_on_hold(&reason.into(), from);
256 if !amount.is_zero() {
257 <Self as Deposit<T>>::refund_on_hold(reason, from, dst, amount)?;
258 }
259 Ok(amount)
260 }
261
262 fn migrate_native_to_pgas(
263 _reason: HoldReason,
264 _contract: &T::AccountId,
265 _amount: BalanceOf<T>,
266 ) -> DispatchResult {
267 Ok(())
268 }
269}
270
271pub struct PGasDeposit<T, Mutator, Holder, Freezer, Id, RefundPercent>(
275 PhantomData<(T, Mutator, Holder, Freezer, Id, RefundPercent)>,
276);
277
278impl<T, Mutator, Holder, Freezer, Id, RefundPercent> Deposit<T>
279 for PGasDeposit<T, Mutator, Holder, Freezer, Id, RefundPercent>
280where
281 T: Config,
282 Mutator: fungibles::Mutate<T::AccountId, Balance = BalanceOf<T>>,
283 Holder: fungibles::MutateHold<
284 T::AccountId,
285 Balance = BalanceOf<T>,
286 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
287 >,
288 <Holder as fungibles::InspectHold<T::AccountId>>::Reason: From<HoldReason>,
289 Freezer: fungibles::freeze::Mutate<
290 T::AccountId,
291 Balance = BalanceOf<T>,
292 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
293 >,
294 <Freezer as fungibles::freeze::Inspect<T::AccountId>>::Id: From<FreezeReason>,
295 Id: Get<<Mutator as fungibles::Inspect<T::AccountId>>::AssetId>,
296 RefundPercent: Get<Perbill>,
297{
298 const SUPPORTS_PGAS: bool = true;
299
300 fn init_contract(to: &T::AccountId) -> DispatchResult {
308 <() as Deposit<T>>::init_contract(to)?;
309 let pgas_ed = <Mutator as fungibles::Inspect<T::AccountId>>::minimum_balance(Id::get());
310 <Mutator as fungibles::Mutate<T::AccountId>>::mint_into(Id::get(), to, pgas_ed)?;
311 <Freezer as fungibles::freeze::Mutate<T::AccountId>>::set_freeze(
312 Id::get(),
313 &FreezeReason::PGasMinBalance.into(),
314 to,
315 pgas_ed,
316 )?;
317 Ok(())
318 }
319
320 fn destroy_contract(contract: &T::AccountId) -> DispatchResult {
322 <() as Deposit<T>>::destroy_contract(contract)?;
323
324 <Freezer as fungibles::freeze::Mutate<T::AccountId>>::thaw(
325 Id::get(),
326 &FreezeReason::PGasMinBalance.into(),
327 contract,
328 )?;
329 let ed = <Mutator as fungibles::Inspect<T::AccountId>>::balance(Id::get(), contract);
330 <Mutator as fungibles::Mutate<T::AccountId>>::burn_from(
331 Id::get(),
332 contract,
333 ed,
334 Preservation::Expendable,
335 Precision::BestEffort,
336 Fortitude::Polite,
337 )?;
338
339 Ok(())
340 }
341
342 fn charge_and_hold(
349 reason: HoldReason,
350 src: Funds<T::AccountId>,
351 to: &T::AccountId,
352 amount: BalanceOf<T>,
353 ) -> DispatchResult {
354 let from = match &src {
355 Funds::Balance(from) | Funds::TxFee(from) => *from,
356 };
357
358 if Self::pgas_reducible_balance(from) >= amount {
359 <Holder as fungibles::MutateHold<T::AccountId>>::transfer_and_hold(
360 Id::get(),
361 &reason.into(),
362 from,
363 to,
364 amount,
365 Precision::Exact,
366 Preservation::Expendable,
367 Fortitude::Polite,
368 )?;
369 } else {
370 <() as Deposit<T>>::charge_and_hold(reason, src, to, amount)?;
371 Self::record_native_deposit(from, to, amount);
372 }
373
374 Ok(())
375 }
376
377 fn refund_on_hold(
385 reason: HoldReason,
386 from: &T::AccountId,
387 dst: Funds<T::AccountId>,
388 amount: BalanceOf<T>,
389 ) -> DispatchResult {
390 let to = match &dst {
391 Funds::Balance(to) | Funds::TxFee(to) => *to,
392 };
393 let contribution = NativeDepositOf::<T>::get(from, to);
394 let native_requested = amount.min(contribution);
395
396 let native_refunded = if !native_requested.is_zero() {
397 <() as Deposit<T>>::refund_on_hold(reason, from, dst, native_requested)?;
398 let new_val = contribution.saturating_sub(native_requested);
399 if new_val.is_zero() {
400 NativeDepositOf::<T>::remove(from, to);
401 } else {
402 NativeDepositOf::<T>::insert(from, to, new_val);
403 }
404 native_requested
405 } else {
406 BalanceOf::<T>::zero()
407 };
408
409 let pgas_needed = amount.saturating_sub(native_refunded);
410 Self::settle_pgas_refund(reason, from, to, pgas_needed)?;
411 Ok(())
412 }
413
414 fn total_on_hold(reason: HoldReason, who: &T::AccountId) -> BalanceOf<T> {
416 let native_held = <() as Deposit<T>>::total_on_hold(reason, who);
417 let pgas_held = Self::pgas_on_hold(reason, who);
418 native_held.saturating_add(pgas_held)
419 }
420
421 fn refund_all(
428 from: &T::AccountId,
429 dst: Funds<T::AccountId>,
430 ) -> Result<BalanceOf<T>, DispatchError> {
431 let to = match &dst {
432 Funds::Balance(to) | Funds::TxFee(to) => *to,
433 };
434 let native = <() as Deposit<T>>::refund_all(from, dst)?;
435 let reason = HoldReason::StorageDepositReserve;
436
437 let pgas = Self::pgas_on_hold(reason, from);
438 let pgas = Self::settle_pgas_refund(reason, from, to, pgas)?;
439 Ok(native.saturating_add(pgas))
440 }
441
442 fn migrate_native_to_pgas(
446 reason: HoldReason,
447 contract: &T::AccountId,
448 amount: BalanceOf<T>,
449 ) -> DispatchResult {
450 let pgas_ed = <Mutator as fungibles::Inspect<T::AccountId>>::minimum_balance(Id::get());
451 let freeze_id = FreezeReason::PGasMinBalance.into();
452 if <Freezer as fungibles::freeze::Inspect<T::AccountId>>::balance_frozen(
453 Id::get(),
454 &freeze_id,
455 contract,
456 ) < pgas_ed
457 {
458 if <Mutator as fungibles::Inspect<T::AccountId>>::balance(Id::get(), contract) < pgas_ed
459 {
460 <Mutator as fungibles::Mutate<T::AccountId>>::mint_into(
461 Id::get(),
462 contract,
463 pgas_ed,
464 )
465 .inspect_err(|err| {
466 log::debug!(
467 target: LOG_TARGET,
468 "Failed to mint PGAS ED for contract: {err:?}",
469 )
470 })?;
471 }
472 <Freezer as fungibles::freeze::Mutate<T::AccountId>>::set_freeze(
473 Id::get(),
474 &freeze_id,
475 contract,
476 pgas_ed,
477 )
478 .inspect_err(|err| {
479 log::debug!(
480 target: LOG_TARGET,
481 "Failed to freeze PGAS ED for contract: {err:?}",
482 )
483 })?;
484 }
485
486 if amount.is_zero() {
487 return Ok(());
488 }
489
490 T::Currency::burn_held(
491 &reason.into(),
492 contract,
493 amount,
494 Precision::Exact,
495 Fortitude::Polite,
496 )
497 .inspect_err(
498 |err| log::debug!(target: LOG_TARGET, "Failed to burn held amount {amount:?}: {err:?}"),
499 )?;
500
501 <Mutator as fungibles::Mutate<T::AccountId>>::mint_into(Id::get(), contract, amount)
502 .inspect_err(
503 |err| log::debug!(target: LOG_TARGET, "Failed to mint to {contract:?} amount: {amount:?}: {err:?}"),
504 )?;
505
506 <Holder as fungibles::MutateHold<T::AccountId>>::hold(
507 Id::get(),
508 &reason.into(),
509 contract,
510 amount,
511 )
512 .inspect_err(
513 |err| log::debug!(target: LOG_TARGET, "Failed to hold amount in {contract:?}: {amount:?}: {err:?}"),
514 )?;
515 Ok(())
516 }
517}
518
519impl<T, Mutator, Holder, Freezer, Id, RefundPercent>
520 PGasDeposit<T, Mutator, Holder, Freezer, Id, RefundPercent>
521where
522 T: Config,
523 Mutator: fungibles::Mutate<T::AccountId, Balance = BalanceOf<T>>,
524 Holder: fungibles::MutateHold<
525 T::AccountId,
526 Balance = BalanceOf<T>,
527 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
528 >,
529 <Holder as fungibles::InspectHold<T::AccountId>>::Reason: From<HoldReason>,
530 Freezer: fungibles::freeze::Mutate<
531 T::AccountId,
532 Balance = BalanceOf<T>,
533 AssetId = <Mutator as fungibles::Inspect<T::AccountId>>::AssetId,
534 >,
535 <Freezer as fungibles::freeze::Inspect<T::AccountId>>::Id: From<FreezeReason>,
536 Id: Get<<Mutator as fungibles::Inspect<T::AccountId>>::AssetId>,
537 RefundPercent: Get<Perbill>,
538{
539 fn pgas_reducible_balance(who: &T::AccountId) -> BalanceOf<T> {
540 <Mutator as fungibles::Inspect<T::AccountId>>::reducible_balance(
541 Id::get(),
542 who,
543 Preservation::Expendable,
544 Fortitude::Polite,
545 )
546 }
547
548 fn pgas_on_hold(reason: HoldReason, who: &T::AccountId) -> BalanceOf<T> {
549 <Holder as fungibles::InspectHold<T::AccountId>>::balance_on_hold(
550 Id::get(),
551 &reason.into(),
552 who,
553 )
554 }
555
556 fn record_native_deposit(from: &T::AccountId, to: &T::AccountId, amount: BalanceOf<T>) {
559 NativeDepositOf::<T>::mutate(to, from, |entitlement| {
560 *entitlement = entitlement.saturating_add(amount);
561 });
562 }
563
564 fn settle_pgas_refund(
576 reason: HoldReason,
577 from: &T::AccountId,
578 to: &T::AccountId,
579 amount: BalanceOf<T>,
580 ) -> Result<BalanceOf<T>, DispatchError> {
581 if amount.is_zero() {
582 return Ok(BalanceOf::<T>::zero());
583 }
584 let pgas_held = Self::pgas_on_hold(reason, from);
588 let amount = amount.min(pgas_held);
589 if amount.is_zero() {
590 return Ok(BalanceOf::<T>::zero());
591 }
592 let refund = RefundPercent::get().mul_floor(amount);
593 let mut burn = amount.saturating_sub(refund);
594 let mut refunded = BalanceOf::<T>::zero();
595
596 if !refund.is_zero() {
597 let can_credit = matches!(
598 <Mutator as fungibles::Inspect<T::AccountId>>::can_deposit(
599 Id::get(),
600 to,
601 refund,
602 Provenance::Extant,
603 ),
604 DepositConsequence::Success
605 );
606 if can_credit {
607 refunded = <Holder as fungibles::MutateHold<T::AccountId>>::transfer_on_hold(
608 Id::get(),
609 &reason.into(),
610 from,
611 to,
612 refund,
613 Precision::BestEffort,
614 Restriction::Free,
615 Fortitude::Polite,
616 )?;
617 } else {
618 burn = burn.saturating_add(refund);
619 }
620 }
621
622 if !burn.is_zero() {
623 <Holder as fungibles::MutateHold<T::AccountId>>::burn_held(
624 Id::get(),
625 &reason.into(),
626 from,
627 burn,
628 Precision::Exact,
629 Fortitude::Polite,
630 )?;
631 }
632 Ok(refunded)
633 }
634}