Fungibles trait and impl for Assets pallet (#8425)

* Fungibles trait and impl for Assets pallet

* Comment & whitespace

* Fixes

* Fix up CI/CD for the new labels.

* New labels.

* Fix labels

* Fix labels

* Whitespace

* Bump impl version.

* Fix accidental change

* Fixes

* Questionable fix.

* Better benchmark
This commit is contained in:
Gavin Wood
2021-03-23 14:10:36 +01:00
committed by GitHub
parent 956262a182
commit b5b0ef592e
5 changed files with 192 additions and 107 deletions
+1 -1
View File
@@ -114,7 +114,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 265,
impl_version: 0,
impl_version: 1,
apis: RUNTIME_API_VERSIONS,
transaction_version: 2,
};
+10 -1
View File
@@ -127,6 +127,14 @@ fn assert_last_event<T: Config>(generic_event: <T as Config>::Event) {
assert_eq!(event, &system_event);
}
fn assert_event<T: Config>(generic_event: <T as Config>::Event) {
let system_event: <T as frame_system::Config>::Event = generic_event.into();
let events = frame_system::Pallet::<T>::events();
assert!(events.iter().any(|event_record| {
matches!(&event_record, frame_system::EventRecord { event, .. } if &system_event == event)
}));
}
benchmarks! {
create {
let caller: T::AccountId = whitelisted_caller();
@@ -383,7 +391,8 @@ benchmarks! {
let dest_lookup = T::Lookup::unlookup(dest.clone());
}: _(SystemOrigin::Signed(delegate.clone()), id, owner_lookup, dest_lookup, amount)
verify {
assert_last_event::<T>(Event::TransferredApproved(id, owner, delegate, dest, amount).into());
assert!(T::Currency::reserved_balance(&owner).is_zero());
assert_event::<T>(Event::Transferred(id, owner, dest, amount).into());
}
cancel_approval {
+162 -101
View File
@@ -138,16 +138,49 @@ use sp_runtime::{
}
};
use codec::{Encode, Decode, HasCompact};
use frame_support::{
ensure,
traits::{Currency, ReservableCurrency, BalanceStatus::Reserved},
dispatch::{DispatchError, DispatchResult},
};
use frame_support::{ensure, dispatch::{DispatchError, DispatchResult}};
use frame_support::traits::{Currency, ReservableCurrency, BalanceStatus::Reserved, Fungibles};
use frame_system::Config as SystemConfig;
pub use weights::WeightInfo;
pub use pallet::*;
type DepositBalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
impl<T: Config> Fungibles<<T as SystemConfig>::AccountId> for Pallet<T> {
type AssetId = T::AssetId;
type Balance = T::Balance;
fn balance(
asset: Self::AssetId,
who: &<T as SystemConfig>::AccountId,
) -> Self::Balance {
Pallet::<T>::balance(asset, who)
}
fn can_deposit(
asset: Self::AssetId,
who: &<T as SystemConfig>::AccountId,
amount: Self::Balance,
) -> bool {
Pallet::<T>::can_deposit(asset, who, amount)
}
fn deposit(
asset: Self::AssetId,
who: <T as SystemConfig>::AccountId,
amount: Self::Balance,
) -> DispatchResult {
Pallet::<T>::increase_balance(asset, who, amount, None)
}
fn withdraw(
asset: Self::AssetId,
who: <T as SystemConfig>::AccountId,
amount: Self::Balance,
) -> DispatchResult {
Pallet::<T>::reduce_balance(asset, who, amount, None)
}
}
type DepositBalanceOf<T> = <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)]
pub struct AssetDetails<
@@ -273,7 +306,7 @@ pub mod pallet {
/// The units in which we record balances.
type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy;
/// The arithmetic type of asset identifier.
/// Identifier for the class of asset.
type AssetId: Member + Parameter + Default + Copy + HasCompact;
/// The currency mechanism.
@@ -435,8 +468,7 @@ pub mod pallet {
///
/// The origin must be Signed and the sender must have sufficient funds free.
///
/// Funds of sender are reserved according to the formula:
/// `AssetDepositBase + AssetDepositPerZombie * max_zombies`.
/// Funds of sender are reserved by `AssetDeposit`.
///
/// Parameters:
/// - `id`: The identifier of the new asset. This must not be currently in use to identify
@@ -611,25 +643,7 @@ pub mod pallet {
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?;
Asset::<T>::try_mutate(id, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
ensure!(&origin == &details.issuer, Error::<T>::NoPermission);
details.supply = details.supply.checked_add(&amount).ok_or(Error::<T>::Overflow)?;
Account::<T>::try_mutate(id, &beneficiary, |t| -> DispatchResult {
let new_balance = t.balance.saturating_add(amount);
ensure!(new_balance >= details.min_balance, Error::<T>::BalanceLow);
if t.balance.is_zero() {
t.sufficient = Self::new_account(&beneficiary, details)?;
}
t.balance = new_balance;
Ok(())
})?;
Self::deposit_event(Event::Issued(id, beneficiary, amount));
Ok(())
})
Self::increase_balance(id, beneficiary, amount, Some(origin))
}
/// Reduce the balance of `who` by as much as possible up to `amount` assets of `id`.
@@ -657,33 +671,7 @@ pub mod pallet {
let origin = ensure_signed(origin)?;
let who = T::Lookup::lookup(who)?;
Asset::<T>::try_mutate(id, |maybe_details| {
let d = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
ensure!(&origin == &d.admin, Error::<T>::NoPermission);
let burned = Account::<T>::try_mutate_exists(
id,
&who,
|maybe_account| -> Result<T::Balance, DispatchError> {
let mut account = maybe_account.take().ok_or(Error::<T>::BalanceZero)?;
let mut burned = amount.min(account.balance);
account.balance -= burned;
*maybe_account = if account.balance < d.min_balance {
burned += account.balance;
Self::dead_account(&who, d, account.sufficient);
None
} else {
Some(account)
};
Ok(burned)
}
)?;
d.supply = d.supply.saturating_sub(burned);
Self::deposit_event(Event::Burned(id, who, burned));
Ok(())
})
Self::reduce_balance(id, who, amount, Some(origin))
}
/// Move some assets from the sender account to another.
@@ -714,9 +702,7 @@ pub mod pallet {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(target)?;
Self::do_transfer(id, &origin, &dest, amount, None, false)?;
Self::deposit_event(Event::Transferred(id, origin, dest, amount));
Ok(())
Self::do_transfer(id, origin, dest, amount, None, false)
}
/// Move some assets from the sender account to another, keeping the sender account alive.
@@ -747,9 +733,7 @@ pub mod pallet {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(target)?;
Self::do_transfer(id, &origin, &dest, amount, None, true)?;
Self::deposit_event(Event::Transferred(id, origin, dest, amount));
Ok(())
Self::do_transfer(id, origin, dest, amount, None, true)
}
/// Move some assets from one account to another.
@@ -783,9 +767,7 @@ pub mod pallet {
let source = T::Lookup::lookup(source)?;
let dest = T::Lookup::lookup(dest)?;
Self::do_transfer(id, &source, &dest, amount, Some(origin), false)?;
Self::deposit_event(Event::Transferred(id, source, dest, amount));
Ok(())
Self::do_transfer(id, source, dest, amount, Some(origin), false)
}
/// Disallow further unprivileged transfers from an account.
@@ -1338,7 +1320,7 @@ pub mod pallet {
let mut approved = maybe_approved.take().ok_or(Error::<T>::Unapproved)?;
let remaining = approved.amount.checked_sub(&amount).ok_or(Error::<T>::Unapproved)?;
Self::do_transfer(id, &key.owner, &destination, amount, None, false)?;
Self::do_transfer(id, key.owner.clone(), destination, amount, None, false)?;
if remaining.is_zero() {
T::Currency::unreserve(&key.owner, approved.deposit);
@@ -1348,8 +1330,6 @@ pub mod pallet {
}
Ok(())
})?;
let event = Event::TransferredApproved(id, key.owner, key.delegate, destination, amount);
Self::deposit_event(event);
Ok(())
}
}
@@ -1360,8 +1340,8 @@ impl<T: Config> Pallet<T> {
// Public immutables
/// Get the asset `id` balance of `who`.
pub fn balance(id: T::AssetId, who: T::AccountId) -> T::Balance {
Account::<T>::get(id, who).balance
pub fn balance(id: T::AssetId, who: impl sp_std::borrow::Borrow<T::AccountId>) -> T::Balance {
Account::<T>::get(id, who.borrow()).balance
}
/// Get the total supply of an asset `id`.
@@ -1400,15 +1380,97 @@ impl<T: Config> Pallet<T> {
d.accounts = d.accounts.saturating_sub(1);
}
fn can_deposit(id: T::AssetId, who: &T::AccountId, amount: T::Balance) -> bool {
let details = match Asset::<T>::get(id) {
Some(details) => details,
None => return false,
};
if details.supply.checked_add(&amount).is_none() { return false }
let account = Account::<T>::get(id, who);
if account.balance.checked_add(&amount).is_none() { return false }
if account.balance.is_zero() {
if amount < details.min_balance { return false }
if !details.is_sufficient && frame_system::Pallet::<T>::providers(who) == 0 { return false }
if details.is_sufficient && details.sufficients.checked_add(1).is_none() { return false }
}
true
}
fn increase_balance(
id: T::AssetId,
beneficiary: T::AccountId,
amount: T::Balance,
maybe_check_issuer: Option<T::AccountId>,
) -> DispatchResult {
Asset::<T>::try_mutate(id, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
if let Some(check_issuer) = maybe_check_issuer {
ensure!(&check_issuer == &details.issuer, Error::<T>::NoPermission);
}
details.supply = details.supply.checked_add(&amount).ok_or(Error::<T>::Overflow)?;
Account::<T>::try_mutate(id, &beneficiary, |t| -> DispatchResult {
let new_balance = t.balance.saturating_add(amount);
ensure!(new_balance >= details.min_balance, Error::<T>::BalanceLow);
if t.balance.is_zero() {
t.sufficient = Self::new_account(&beneficiary, details)?;
}
t.balance = new_balance;
Ok(())
})?;
Self::deposit_event(Event::Issued(id, beneficiary, amount));
Ok(())
})
}
fn reduce_balance(
id: T::AssetId,
target: T::AccountId,
amount: T::Balance,
maybe_check_admin: Option<T::AccountId>,
) -> DispatchResult {
Asset::<T>::try_mutate(id, |maybe_details| {
let d = maybe_details.as_mut().ok_or(Error::<T>::Unknown)?;
if let Some(check_admin) = maybe_check_admin {
ensure!(&check_admin == &d.admin, Error::<T>::NoPermission);
}
let burned = Account::<T>::try_mutate_exists(
id,
&target,
|maybe_account| -> Result<T::Balance, DispatchError> {
let mut account = maybe_account.take().ok_or(Error::<T>::BalanceZero)?;
let mut burned = amount.min(account.balance);
account.balance -= burned;
*maybe_account = if account.balance < d.min_balance {
burned += account.balance;
Self::dead_account(&target, d, account.sufficient);
None
} else {
Some(account)
};
Ok(burned)
}
)?;
d.supply = d.supply.saturating_sub(burned);
Self::deposit_event(Event::Burned(id, target, burned));
Ok(())
})
}
fn do_transfer(
id: T::AssetId,
source: &T::AccountId,
dest: &T::AccountId,
source: T::AccountId,
dest: T::AccountId,
amount: T::Balance,
maybe_need_admin: Option<T::AccountId>,
keep_alive: bool,
) -> DispatchResult {
let mut source_account = Account::<T>::get(id, source);
let mut source_account = Account::<T>::get(id, &source);
ensure!(!source_account.is_frozen, Error::<T>::Frozen);
source_account.balance = source_account.balance.checked_sub(&amount)
@@ -1422,38 +1484,37 @@ impl<T: Config> Pallet<T> {
ensure!(&need_admin == &details.admin, Error::<T>::NoPermission);
}
if dest == source || amount.is_zero() {
return Ok(())
}
let mut amount = amount;
if source_account.balance < details.min_balance {
ensure!(!keep_alive, Error::<T>::WouldDie);
amount += source_account.balance;
source_account.balance = Zero::zero();
}
Account::<T>::try_mutate(id, dest, |a| -> DispatchResult {
let new_balance = a.balance.saturating_add(amount);
// This is impossible since `new_balance > amount > min_balance`, but we can
// handle it, so we do.
ensure!(new_balance >= details.min_balance, Error::<T>::BalanceLow);
if a.balance.is_zero() {
a.sufficient = Self::new_account(dest, details)?;
if dest != source && !amount.is_zero() {
let mut amount = amount;
if source_account.balance < details.min_balance {
ensure!(!keep_alive, Error::<T>::WouldDie);
amount += source_account.balance;
source_account.balance = Zero::zero();
}
a.balance = new_balance;
Ok(())
})?;
if source_account.balance.is_zero() {
Self::dead_account(source, details, source_account.sufficient);
Account::<T>::remove(id, source);
} else {
Account::<T>::insert(id, source, &source_account)
Account::<T>::try_mutate(id, &dest, |a| -> DispatchResult {
let new_balance = a.balance.saturating_add(amount);
// This is impossible since `new_balance > amount > min_balance`, but we can
// handle it, so we do.
ensure!(new_balance >= details.min_balance, Error::<T>::BalanceLow);
if a.balance.is_zero() {
a.sufficient = Self::new_account(&dest, details)?;
}
a.balance = new_balance;
Ok(())
})?;
if source_account.balance.is_zero() {
Self::dead_account(&source, details, source_account.sufficient);
Account::<T>::remove(id, &source);
} else {
Account::<T>::insert(id, &source, &source_account)
}
}
Self::deposit_event(Event::Transferred(id, source, dest, amount));
Ok(())
})
}
+3 -4
View File
@@ -79,7 +79,9 @@ pub fn compute_inflation<P: PerThing>(
falloff.lstrip();
let ln2 = {
let ln2 = P::from_rational(LN2.deconstruct().into(), Perquintill::ACCURACY.into());
/// `ln(2)` expressed in as perquintillionth.
const LN2: u64 = 0_693_147_180_559_945_309;
let ln2 = P::from_rational(LN2.into(), Perquintill::ACCURACY.into());
BigUint::from(ln2.deconstruct().into())
};
@@ -119,9 +121,6 @@ struct INPoSParam {
accuracy: BigUint,
}
/// `ln(2)` expressed in as perquintillionth.
const LN2: Perquintill = Perquintill::from_parts(0_693_147_180_559_945_309);
/// Compute `2^((x_ideal - x) / d)` using taylor serie.
///
/// x must be strictly more than x_ideal.
+16
View File
@@ -1128,6 +1128,22 @@ pub trait Currency<AccountId> {
) -> SignedImbalance<Self::Balance, Self::PositiveImbalance>;
}
/// Trait for providing an ERC-20 style set of named fungible assets.
pub trait Fungibles<AccountId> {
/// Means of identifying one asset class from another.
type AssetId: FullCodec + Copy + Default;
/// Scalar type for storing balance of an account.
type Balance: AtLeast32BitUnsigned + FullCodec + Copy + Default;
/// Get the `asset` balance of `who`.
fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance;
/// Returns `true` if the `asset` balance of `who` may be increased by `amount`.
fn can_deposit(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> bool;
/// Increase the `asset` balance of `who` by `amount`.
fn deposit(asset: Self::AssetId, who: AccountId, amount: Self::Balance) -> DispatchResult;
/// Attempt to reduce the `asset` balance of `who` by `amount`.
fn withdraw(asset: Self::AssetId, who: AccountId, amount: Self::Balance) -> DispatchResult;
}
/// Status of funds.
#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug)]
pub enum BalanceStatus {