// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Implementation of `fungible` traits for Balances pezpallet. use super::*; use pezframe_support::traits::{ tokens::{ Fortitude, Preservation::{self, Preserve, Protect}, Provenance::{self, Minted}, }, AccountTouch, }; impl, I: 'static> fungible::Inspect for Pezpallet { type Balance = T::Balance; fn total_issuance() -> Self::Balance { TotalIssuance::::get() } fn active_issuance() -> Self::Balance { TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) } fn minimum_balance() -> Self::Balance { T::ExistentialDeposit::get() } fn total_balance(who: &T::AccountId) -> Self::Balance { Self::account(who).total() } fn balance(who: &T::AccountId) -> Self::Balance { Self::account(who).free } fn reducible_balance( who: &T::AccountId, preservation: Preservation, force: Fortitude, ) -> Self::Balance { let a = Self::account(who); let mut untouchable = Zero::zero(); if force == Polite { // Frozen balance applies to total. Anything on hold therefore gets discounted from the // limit given by the freezes. untouchable = a.frozen.saturating_sub(a.reserved); } // If we want to keep our provider ref.. if preservation == Preserve // ..or we don't want the account to die and our provider ref is needed for it to live.. || preservation == Protect && !a.free.is_zero() && pezframe_system::Pezpallet::::providers(who) == 1 // ..or we don't care about the account dying but our provider ref is required.. || preservation == Expendable && !a.free.is_zero() && !pezframe_system::Pezpallet::::can_dec_provider(who) { // ..then the ED needed.. untouchable = untouchable.max(T::ExistentialDeposit::get()); } // Liquid balance is what is neither on hold nor frozen/required for provider. a.free.saturating_sub(untouchable) } fn can_deposit( who: &T::AccountId, amount: Self::Balance, provenance: Provenance, ) -> DepositConsequence { if amount.is_zero() { return DepositConsequence::Success; } if provenance == Minted && TotalIssuance::::get().checked_add(&amount).is_none() { return DepositConsequence::Overflow; } let account = Self::account(who); let new_free = match account.free.checked_add(&amount) { None => return DepositConsequence::Overflow, Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, Some(x) => x, }; match account.reserved.checked_add(&new_free) { Some(_) => {}, None => return DepositConsequence::Overflow, }; // NOTE: We assume that we are a provider, so don't need to do any checks in the // case of account creation. DepositConsequence::Success } fn can_withdraw( who: &T::AccountId, amount: Self::Balance, ) -> WithdrawConsequence { if amount.is_zero() { return WithdrawConsequence::Success; } if TotalIssuance::::get().checked_sub(&amount).is_none() { return WithdrawConsequence::Underflow; } let account = Self::account(who); let new_free_balance = match account.free.checked_sub(&amount) { Some(x) => x, None => return WithdrawConsequence::BalanceLow, }; let liquid = Self::reducible_balance(who, Expendable, Polite); if amount > liquid { return WithdrawConsequence::Frozen; } // Provider restriction - total account balance cannot be reduced to zero if it cannot // sustain the loss of a provider reference. // NOTE: This assumes that the pezpallet is a provider (which is true). Is this ever // changes, then this will need to adapt accordingly. let ed = T::ExistentialDeposit::get(); let success = if new_free_balance < ed { if pezframe_system::Pezpallet::::can_dec_provider(who) { WithdrawConsequence::ReducedToZero(new_free_balance) } else { return WithdrawConsequence::WouldDie; } } else { WithdrawConsequence::Success }; let new_total_balance = new_free_balance.saturating_add(account.reserved); // Eventual free funds must be no less than the frozen balance. if new_total_balance < account.frozen { return WithdrawConsequence::Frozen; } success } } impl, I: 'static> fungible::Unbalanced for Pezpallet { fn handle_dust(dust: fungible::Dust) { T::DustRemoval::on_unbalanced(dust.into_credit()); } fn write_balance( who: &T::AccountId, amount: Self::Balance, ) -> Result, DispatchError> { let max_reduction = >::reducible_balance(who, Expendable, Force); let (result, maybe_dust) = Self::mutate_account(who, false, |account| -> DispatchResult { // Make sure the reduction (if there is one) is no more than the maximum allowed. let reduction = account.free.saturating_sub(amount); ensure!(reduction <= max_reduction, Error::::InsufficientBalance); account.free = amount; Ok(()) })?; result?; Ok(maybe_dust) } fn set_total_issuance(amount: Self::Balance) { TotalIssuance::::mutate(|t| *t = amount); } fn deactivate(amount: Self::Balance) { InactiveIssuance::::mutate(|b| { // InactiveIssuance cannot be greater than TotalIssuance. *b = b.saturating_add(amount).min(TotalIssuance::::get()); }); } fn reactivate(amount: Self::Balance) { InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); } } impl, I: 'static> fungible::Mutate for Pezpallet { fn done_mint_into(who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Minted { who: who.clone(), amount }); } fn done_burn_from(who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Burned { who: who.clone(), amount }); } fn done_shelve(who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Suspended { who: who.clone(), amount }); } fn done_restore(who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Restored { who: who.clone(), amount }); } fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Transfer { from: source.clone(), to: dest.clone(), amount, }); } } impl, I: 'static> fungible::MutateHold for Pezpallet { fn done_hold(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Held { reason: *reason, who: who.clone(), amount }); } fn done_release(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Released { reason: *reason, who: who.clone(), amount }); } fn done_burn_held(reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::BurnedHeld { reason: *reason, who: who.clone(), amount, }); } fn done_transfer_on_hold( reason: &Self::Reason, source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance, ) { // Emit on-hold transfer event Self::deposit_event(Event::::TransferOnHold { reason: *reason, source: source.clone(), dest: dest.clone(), amount, }); } fn done_transfer_and_hold( reason: &Self::Reason, source: &T::AccountId, dest: &T::AccountId, transferred: Self::Balance, ) { Self::deposit_event(Event::::TransferAndHold { reason: *reason, source: source.clone(), dest: dest.clone(), transferred, }) } } impl, I: 'static> fungible::InspectHold for Pezpallet { type Reason = T::RuntimeHoldReason; fn total_balance_on_hold(who: &T::AccountId) -> T::Balance { Self::account(who).reserved } fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance { // The total balance must never drop below the freeze requirements if we're not forcing: let a = Self::account(who); let unavailable = if force == Force { Self::Balance::zero() } else { // The freeze lock applies to the total balance, so we can discount the free balance // from the amount which the total reserved balance must provide to satisfy it. a.frozen.saturating_sub(a.free) }; a.reserved.saturating_sub(unavailable) } fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance { Holds::::get(who) .iter() .find(|x| &x.id == reason) .map_or_else(Zero::zero, |x| x.amount) } fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool { if pezframe_system::Pezpallet::::providers(who) == 0 { return false; } let holds = Holds::::get(who); if holds.is_full() && !holds.iter().any(|x| &x.id == reason) { return false; } true } } impl, I: 'static> fungible::UnbalancedHold for Pezpallet { fn set_balance_on_hold( reason: &Self::Reason, who: &T::AccountId, amount: Self::Balance, ) -> DispatchResult { let mut new_account = Self::account(who); let mut holds = Holds::::get(who); let mut increase = true; let mut delta = amount; if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { delta = item.amount.max(amount) - item.amount.min(amount); increase = amount > item.amount; item.amount = amount; holds.retain(|x| !x.amount.is_zero()); } else { if !amount.is_zero() { holds .try_push(IdAmount { id: *reason, amount }) .map_err(|_| Error::::TooManyHolds)?; } } new_account.reserved = if increase { new_account.reserved.checked_add(&delta).ok_or(ArithmeticError::Overflow)? } else { new_account.reserved.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? }; let (result, maybe_dust) = Self::try_mutate_account(who, false, |a, _| -> DispatchResult { *a = new_account; Ok(()) })?; debug_assert!( maybe_dust.is_none(), "Does not alter main balance; dust only happens when it is altered; qed" ); Holds::::insert(who, holds); Ok(result) } } impl, I: 'static> fungible::InspectFreeze for Pezpallet { type Id = T::FreezeIdentifier; fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance { let locks = Freezes::::get(who); locks.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount) } fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool { let l = Freezes::::get(who); !l.is_full() || l.iter().any(|x| &x.id == id) } } impl, I: 'static> fungible::MutateFreeze for Pezpallet { fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Self::thaw(id, who); } let mut locks = Freezes::::get(who); if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { i.amount = amount; } else { locks .try_push(IdAmount { id: *id, amount }) .map_err(|_| Error::::TooManyFreezes)?; } Self::update_freezes(who, locks.as_bounded_slice()) } fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { if amount.is_zero() { return Ok(()); } let mut locks = Freezes::::get(who); if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { i.amount = i.amount.max(amount); } else { locks .try_push(IdAmount { id: *id, amount }) .map_err(|_| Error::::TooManyFreezes)?; } Self::update_freezes(who, locks.as_bounded_slice()) } fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult { let mut locks = Freezes::::get(who); locks.retain(|l| &l.id != id); Self::update_freezes(who, locks.as_bounded_slice()) } } impl, I: 'static> fungible::Balanced for Pezpallet { type OnDropCredit = NegativeImbalance; type OnDropDebt = PositiveImbalance; fn done_deposit(who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Deposit { who: who.clone(), amount }); } fn done_withdraw(who: &T::AccountId, amount: Self::Balance) { Self::deposit_event(Event::::Withdraw { who: who.clone(), amount }); } fn done_issue(amount: Self::Balance) { if !amount.is_zero() { Self::deposit_event(Event::::Issued { amount }); } } fn done_rescind(amount: Self::Balance) { Self::deposit_event(Event::::Rescinded { amount }); } } impl, I: 'static> fungible::BalancedHold for Pezpallet {} impl, I: 'static> fungible::hold::DoneSlash for Pezpallet { fn done_slash(reason: &T::RuntimeHoldReason, who: &T::AccountId, amount: T::Balance) { T::DoneSlashHandler::done_slash(reason, who, amount); } } impl, I: 'static> AccountTouch<(), T::AccountId> for Pezpallet { type Balance = T::Balance; fn deposit_required(_: ()) -> Self::Balance { Self::Balance::zero() } fn should_touch(_: (), _: &T::AccountId) -> bool { false } fn touch(_: (), _: &T::AccountId, _: &T::AccountId) -> DispatchResult { Ok(()) } }