Newer
Older
// for the amount issued.
// Is a no-op if amount to be issued it zero.
fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance {
if amount.is_zero() { return NegativeImbalance::zero() }
<TotalIssuance<T, I>>::mutate(|issued|
*issued = issued.checked_add(&amount).unwrap_or_else(|| {
amount = Self::Balance::max_value() - *issued;
Self::Balance::max_value()
})
);
NegativeImbalance::new(amount)
}
fn free_balance(who: &T::AccountId) -> Self::Balance {
Self::account(who).free
}
// Ensure that an account can withdraw from their free balance given any existing withdrawal
// restrictions like locks and vesting balance.
// Is a no-op if amount to be withdrawn is zero.
//
// # <weight>
// Despite iterating over a list of locks, they are limited by the number of
// lock IDs, which means the number of runtime pallets that intend to use and create locks.
// # </weight>
fn ensure_can_withdraw(
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
new_balance: T::Balance,
if amount.is_zero() { return Ok(()) }
let min_balance = Self::account(who).frozen(reasons.into());
ensure!(new_balance >= min_balance, Error::<T, I>::LiquidityRestrictions);
Ok(())
// Transfer some free balance from `transactor` to `dest`, respecting existence requirements.
// Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`.
fn transfer(
transactor: &T::AccountId,
dest: &T::AccountId,
value: Self::Balance,
existence_requirement: ExistenceRequirement,
if value.is_zero() || transactor == dest { return Ok(()) }
Self::try_mutate_account_with_dust(
dest,
|to_account, _| -> Result<DustCleaner<T, I>, DispatchError> {
Self::try_mutate_account_with_dust(
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
|from_account, _| -> DispatchResult {
from_account.free = from_account.free.checked_sub(&value)
.ok_or(Error::<T, I>::InsufficientBalance)?;
// NOTE: total stake being stored in the same type means that this could never overflow
// but better to be safe than sorry.
to_account.free = to_account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
let ed = T::ExistentialDeposit::get();
ensure!(to_account.total() >= ed, Error::<T, I>::ExistentialDeposit);
Self::ensure_can_withdraw(
transactor,
value,
WithdrawReasons::TRANSFER,
from_account.free,
).map_err(|_| Error::<T, I>::LiquidityRestrictions)?;
// TODO: This is over-conservative. There may now be other providers, and this pallet
// may not even be a provider.
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
let allow_death = allow_death && !system::Pallet::<T>::is_provider_required(transactor);
ensure!(allow_death || from_account.total() >= ed, Error::<T, I>::KeepAlive);
Ok(())
}
).map(|(_, maybe_dust_cleaner)| maybe_dust_cleaner)
}
)?;
Self::deposit_event(Event::Transfer(transactor.clone(), dest.clone(), value));
Ok(())
}
/// Slash a target account `who`, returning the negative imbalance created and any left over
/// amount that could not be slashed.
///
/// Is a no-op if `value` to be slashed is zero or the account does not exist.
///
/// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn
/// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid having
/// to draw from reserved funds, however we err on the side of punishment if things are inconsistent
/// or `can_slash` wasn't used appropriately.
fn slash(
who: &T::AccountId,
value: Self::Balance
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
if Self::total_balance(&who).is_zero() { return (NegativeImbalance::zero(), value) }
for attempt in 0..2 {
match Self::try_mutate_account(who,
|account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), StoredMapError> {
// Best value is the most amount we can slash following liveness rules.
let best_value = match attempt {
// First attempt we try to slash the full amount, and see if liveness issues happen.
0 => value,
// If acting as a critical provider (i.e. first attempt failed), then slash
// as much as possible while leaving at least at ED.
_ => value.min((account.free + account.reserved).saturating_sub(T::ExistentialDeposit::get())),
};
let free_slash = cmp::min(account.free, best_value);
account.free -= free_slash; // Safe because of above check
let remaining_slash = best_value - free_slash; // Safe because of above check
if !remaining_slash.is_zero() {
// If we have remaining slash, take it from reserved balance.
let reserved_slash = cmp::min(account.reserved, remaining_slash);
account.reserved -= reserved_slash; // Safe because of above check
Ok((
NegativeImbalance::new(free_slash + reserved_slash),
value - free_slash - reserved_slash, // Safe because value is gt or eq total slashed
))
} else {
// Else we are done!
Ok((
NegativeImbalance::new(free_slash),
value - free_slash, // Safe because value is gt or eq to total slashed
))
}
}
) {
Ok(r) => return r,
Err(_) => (),
}
// Should never get here. But we'll be defensive anyway.
(Self::NegativeImbalance::zero(), value)
/// Deposit some `value` into the free balance of an existing target account `who`.
///
/// Is a no-op if the `value` to be deposited is zero.
fn deposit_into_existing(
who: &T::AccountId,
value: Self::Balance
) -> Result<Self::PositiveImbalance, DispatchError> {
if value.is_zero() { return Ok(PositiveImbalance::zero()) }
Self::try_mutate_account(who, |account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
ensure!(!is_new, Error::<T, I>::DeadAccount);
account.free = account.free.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Ok(PositiveImbalance::new(value))
})
/// Deposit some `value` into the free balance of `who`, possibly creating a new account.
///
/// This function is a no-op if:
/// - the `value` to be deposited is zero; or
/// - the `value` to be deposited is less than the required ED and the account does not yet exist; or
/// - the deposit would necessitate the account to exist and there are no provider references; or
/// - `value` is so large it would cause the balance of `who` to overflow.
fn deposit_creating(
who: &T::AccountId,
value: Self::Balance,
) -> Self::PositiveImbalance {
if value.is_zero() { return Self::PositiveImbalance::zero() }
let r = Self::try_mutate_account(who, |account, is_new| -> Result<Self::PositiveImbalance, DispatchError> {
ensure!(value >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
// defensive only: overflow should never happen, however in case it does, then this
// operation is a no-op.
account.free = match account.free.checked_add(&value) {
Some(x) => x,
None => return Ok(Self::PositiveImbalance::zero()),
};
}).unwrap_or_else(|_| Self::PositiveImbalance::zero());
r
/// Withdraw some free balance from an account, respecting existence requirements.
///
/// Is a no-op if value to be withdrawn is zero.
fn withdraw(
who: &T::AccountId,
value: Self::Balance,
reasons: WithdrawReasons,
liveness: ExistenceRequirement,
) -> result::Result<Self::NegativeImbalance, DispatchError> {
if value.is_zero() { return Ok(NegativeImbalance::zero()); }
Self::try_mutate_account(who, |account, _|
-> Result<Self::NegativeImbalance, DispatchError>
{
let new_free_account = account.free.checked_sub(&value)
.ok_or(Error::<T, I>::InsufficientBalance)?;
// bail if we need to keep the account alive and this would kill it.
let ed = T::ExistentialDeposit::get();
let would_be_dead = new_free_account + account.reserved < ed;
let would_kill = would_be_dead && account.free + account.reserved >= ed;
ensure!(liveness == AllowDeath || !would_kill, Error::<T, I>::KeepAlive);
Self::ensure_can_withdraw(who, value, reasons, new_free_account)?;
account.free = new_free_account;
Ok(NegativeImbalance::new(value))
})
/// Force the new free balance of a target account `who` to some new value `balance`.
fn make_free_balance_be(who: &T::AccountId, value: Self::Balance)
-> SignedImbalance<Self::Balance, Self::PositiveImbalance>
{
Self::try_mutate_account(who, |account, is_new|
-> Result<SignedImbalance<Self::Balance, Self::PositiveImbalance>, DispatchError>
let total = value.saturating_add(account.reserved);
// If we're attempting to set an existing account to less than ED, then
// bypass the entire operation. It's a no-op if you follow it through, but
// since this is an instance where we might account for a negative imbalance
// (in the dust cleaner of set_account) before we account for its actual
// equal and opposite cause (returned as an Imbalance), then in the
// instance that there's no other accounts on the system at all, we might
// underflow the issuance and our arithmetic will be off.
ensure!(total >= ed || !is_new, Error::<T, I>::ExistentialDeposit);
let imbalance = if account.free <= value {
SignedImbalance::Positive(PositiveImbalance::new(value - account.free))
} else {
SignedImbalance::Negative(NegativeImbalance::new(account.free - value))
};
account.free = value;
Ok(imbalance)
}).unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero()))
impl<T: Config<I>, I: 'static> ReservableCurrency<T::AccountId> for Pallet<T, I> where
T::Balance: MaybeSerializeDeserialize + Debug
/// Check if `who` can reserve `value` from their free balance.
///
/// Always `true` if value to be reserved is zero.
fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool {
if value.is_zero() { return true }
.checked_sub(&value)
.map_or(false, |new_balance|
Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance).is_ok()
)
}
fn reserved_balance(who: &T::AccountId) -> Self::Balance {
/// Move `value` from the free balance from `who` to their reserved balance.
///
/// Is a no-op if value to be reserved is zero.
fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult {
if value.is_zero() { return Ok(()) }
Self::try_mutate_account(who, |account, _| -> DispatchResult {
account.free = account.free.checked_sub(&value).ok_or(Error::<T, I>::InsufficientBalance)?;
account.reserved = account.reserved.checked_add(&value).ok_or(Error::<T, I>::Overflow)?;
Self::ensure_can_withdraw(&who, value.clone(), WithdrawReasons::RESERVE, account.free)
})?;
Self::deposit_event(Event::Reserved(who.clone(), value));
/// Unreserve some funds, returning any amount that was unable to be unreserved.
///
/// Is a no-op if the value to be unreserved is zero or the account does not exist.
fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance {
if value.is_zero() { return Zero::zero() }
if Self::total_balance(&who).is_zero() { return value }
let actual = match Self::mutate_account(who, |account| {
let actual = cmp::min(account.reserved, value);
account.reserved -= actual;
// defensive only: this can never fail since total issuance which is at least free+reserved
account.free = account.free.saturating_add(actual);
}) {
Ok(x) => x,
Err(_) => {
// This should never happen since we don't alter the total amount in the account.
// If it ever does, then we should fail gracefully though, indicating that nothing
// could be done.
return value
}
};
Self::deposit_event(Event::Unreserved(who.clone(), actual.clone()));
value - actual
/// Slash from reserved balance, returning the negative imbalance created,
/// and any amount that was unable to be slashed.
///
/// Is a no-op if the value to be slashed is zero or the account does not exist.
fn slash_reserved(
who: &T::AccountId,
value: Self::Balance
) -> (Self::NegativeImbalance, Self::Balance) {
if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) }
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
if Self::total_balance(&who).is_zero() { return (NegativeImbalance::zero(), value) }
// NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an
// account is attempted to be illegally destroyed.
for attempt in 0..2 {
match Self::mutate_account(who, |account| {
let best_value = match attempt {
0 => value,
// If acting as a critical provider (i.e. first attempt failed), then ensure
// slash leaves at least the ED.
_ => value.min((account.free + account.reserved).saturating_sub(T::ExistentialDeposit::get())),
};
let actual = cmp::min(account.reserved, best_value);
account.reserved -= actual;
// underflow should never happen, but it if does, there's nothing to be done here.
(NegativeImbalance::new(actual), value - actual)
}) {
Ok(r) => return r,
Err(_) => (),
}
}
// Should never get here as we ensure that ED is left in the second attempt.
// In case we do, though, then we fail gracefully.
(Self::NegativeImbalance::zero(), value)
/// Move the reserved balance of one account into the balance of another, according to `status`.
/// Is a no-op if:
/// - the value to be moved is zero; or
/// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`.
fn repatriate_reserved(
slashed: &T::AccountId,
beneficiary: &T::AccountId,
status: Status,
) -> Result<Self::Balance, DispatchError> {
if value.is_zero() { return Ok(Zero::zero()) }
return match status {
Status::Free => Ok(Self::unreserve(slashed, value)),
Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))),
};
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
let ((actual, _maybe_one_dust), _maybe_other_dust) = Self::try_mutate_account_with_dust(
beneficiary,
|to_account, is_new| -> Result<(Self::Balance, DustCleaner<T, I>), DispatchError> {
ensure!(!is_new, Error::<T, I>::DeadAccount);
Self::try_mutate_account_with_dust(
slashed,
|from_account, _| -> Result<Self::Balance, DispatchError> {
let actual = cmp::min(from_account.reserved, value);
match status {
Status::Free => to_account.free = to_account.free
.checked_add(&actual)
.ok_or(Error::<T, I>::Overflow)?,
Status::Reserved => to_account.reserved = to_account.reserved
.checked_add(&actual)
.ok_or(Error::<T, I>::Overflow)?,
}
from_account.reserved -= actual;
Ok(actual)
}
)
}
)?;
Self::deposit_event(Event::ReserveRepatriated(slashed.clone(), beneficiary.clone(), actual, status));
Ok(value - actual)
impl<T: Config<I>, I: 'static> LockableCurrency<T::AccountId> for Pallet<T, I>
T::Balance: MaybeSerializeDeserialize + Debug
{
type Moment = T::BlockNumber;
type MaxLocks = T::MaxLocks;
// Set a lock on the balance of `who`.
// Is a no-op if lock amount is zero or `reasons` `is_none()`.
fn set_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
if amount.is_zero() || reasons.is_empty() { return }
let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
let mut locks = Self::locks(who).into_iter()
.filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) })
.collect::<Vec<_>>();
if let Some(lock) = new_lock {
locks.push(lock)
}
// Extend a lock on the balance of `who`.
// Is a no-op if lock amount is zero or `reasons` `is_none()`.
fn extend_lock(
id: LockIdentifier,
who: &T::AccountId,
amount: T::Balance,
reasons: WithdrawReasons,
) {
if amount.is_zero() || reasons.is_empty() { return }
let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() });
let mut locks = Self::locks(who).into_iter().filter_map(|l|
if l.id == id {
new_lock.take().map(|nl| {
BalanceLock {
id: l.id,
amount: l.amount.max(nl.amount),
reasons: l.reasons | nl.reasons,
}
})
} else {
}).collect::<Vec<_>>();
if let Some(lock) = new_lock {
locks.push(lock)
}
}
fn remove_lock(
id: LockIdentifier,
who: &T::AccountId,
) {
let mut locks = Self::locks(who);
locks.retain(|l| l.id != id);
Self::update_locks(who, &locks[..]);