Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Commit

Permalink
Add low level traits to retrieve name, symbol, decimals and allowance…
Browse files Browse the repository at this point in the history
… in pallet-assets (#9757)

* Add ERC20 compatible trait to retrieve name, symbol, decimals and allowance

* delegate instead of spender

* Remove erc20 trait and divide it into lower level traits

* add import

* approvals and metadata depend on fungibles

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
  • Loading branch information
girazoki and shawntabrizi authored Oct 7, 2021
1 parent 5e2b0f0 commit 62f011c
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 93 deletions.
125 changes: 125 additions & 0 deletions frame/assets/src/functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! Functions for the Assets pallet.

use super::*;
use frame_support::{traits::Get, BoundedVec};

// The main implementation block for the module.
impl<T: Config<I>, I: 'static> Pallet<T, I> {
Expand Down Expand Up @@ -562,4 +563,128 @@ impl<T: Config<I>, I: 'static> Pallet<T, I> {
})
})
}

/// Creates an approval from `owner` to spend `amount` of asset `id` tokens by 'delegate'
/// while reserving `T::ApprovalDeposit` from owner
///
/// If an approval already exists, the new amount is added to such existing approval
pub(super) fn do_approve_transfer(
id: T::AssetId,
owner: &T::AccountId,
delegate: &T::AccountId,
amount: T::Balance,
) -> DispatchResult {
let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
ensure!(!d.is_frozen, Error::<T, I>::Frozen);
Approvals::<T, I>::try_mutate(
(id, &owner, &delegate),
|maybe_approved| -> DispatchResult {
let mut approved = match maybe_approved.take() {
// an approval already exists and is being updated
Some(a) => a,
// a new approval is created
None => {
d.approvals.saturating_inc();
Default::default()
},
};
let deposit_required = T::ApprovalDeposit::get();
if approved.deposit < deposit_required {
T::Currency::reserve(&owner, deposit_required - approved.deposit)?;
approved.deposit = deposit_required;
}
approved.amount = approved.amount.saturating_add(amount);
*maybe_approved = Some(approved);
Ok(())
},
)?;
Asset::<T, I>::insert(id, d);
Self::deposit_event(Event::ApprovedTransfer(id, owner.clone(), delegate.clone(), amount));

Ok(())
}

/// Reduces the asset `id` balance of `owner` by some `amount` and increases the balance of
/// `dest` by (similar) amount, checking that 'delegate' has an existing approval from `owner`
/// to spend`amount`.
///
/// Will fail if `amount` is greater than the approval from `owner` to 'delegate'
/// Will unreserve the deposit from `owner` if the entire approved `amount` is spent by
/// 'delegate'
pub(super) fn do_transfer_approved(
id: T::AssetId,
owner: &T::AccountId,
delegate: &T::AccountId,
destination: &T::AccountId,
amount: T::Balance,
) -> DispatchResult {
Approvals::<T, I>::try_mutate_exists(
(id, &owner, delegate),
|maybe_approved| -> DispatchResult {
let mut approved = maybe_approved.take().ok_or(Error::<T, I>::Unapproved)?;
let remaining =
approved.amount.checked_sub(&amount).ok_or(Error::<T, I>::Unapproved)?;

let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
Self::do_transfer(id, &owner, &destination, amount, None, f)?;

if remaining.is_zero() {
T::Currency::unreserve(&owner, approved.deposit);
Asset::<T, I>::mutate(id, |maybe_details| {
if let Some(details) = maybe_details {
details.approvals.saturating_dec();
}
});
} else {
approved.amount = remaining;
*maybe_approved = Some(approved);
}
Ok(())
},
)?;
Ok(())
}

/// Do set metadata
pub(super) fn do_set_metadata(
id: T::AssetId,
from: &T::AccountId,
name: Vec<u8>,
symbol: Vec<u8>,
decimals: u8,
) -> DispatchResult {
let bounded_name: BoundedVec<u8, T::StringLimit> =
name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
let bounded_symbol: BoundedVec<u8, T::StringLimit> =
symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;

let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
ensure!(from == &d.owner, Error::<T, I>::NoPermission);

Metadata::<T, I>::try_mutate_exists(id, |metadata| {
ensure!(metadata.as_ref().map_or(true, |m| !m.is_frozen), Error::<T, I>::NoPermission);

let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
let new_deposit = T::MetadataDepositPerByte::get()
.saturating_mul(((name.len() + symbol.len()) as u32).into())
.saturating_add(T::MetadataDepositBase::get());

if new_deposit > old_deposit {
T::Currency::reserve(from, new_deposit - old_deposit)?;
} else {
T::Currency::unreserve(from, old_deposit - new_deposit);
}

*metadata = Some(AssetMetadata {
deposit: new_deposit,
name: bounded_name,
symbol: bounded_symbol,
decimals,
is_frozen: false,
});

Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, false));
Ok(())
})
}
}
69 changes: 69 additions & 0 deletions frame/assets/src/impl_fungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,72 @@ impl<T: Config<I>, I: 'static> fungibles::Destroy<T::AccountId> for Pallet<T, I>
Self::do_destroy(id, witness, maybe_check_owner)
}
}

impl<T: Config<I>, I: 'static> fungibles::metadata::Inspect<<T as SystemConfig>::AccountId>
for Pallet<T, I>
{
fn name(asset: T::AssetId) -> Vec<u8> {
Metadata::<T, I>::get(asset).name.to_vec()
}

fn symbol(asset: T::AssetId) -> Vec<u8> {
Metadata::<T, I>::get(asset).symbol.to_vec()
}

fn decimals(asset: T::AssetId) -> u8 {
Metadata::<T, I>::get(asset).decimals
}
}

impl<T: Config<I>, I: 'static> fungibles::metadata::Mutate<<T as SystemConfig>::AccountId>
for Pallet<T, I>
{
fn set(
asset: T::AssetId,
from: &<T as SystemConfig>::AccountId,
name: Vec<u8>,
symbol: Vec<u8>,
decimals: u8,
) -> DispatchResult {
Self::do_set_metadata(asset, from, name, symbol, decimals)
}
}

impl<T: Config<I>, I: 'static> fungibles::approvals::Inspect<<T as SystemConfig>::AccountId>
for Pallet<T, I>
{
// Check the amount approved to be spent by an owner to a delegate
fn allowance(
asset: T::AssetId,
owner: &<T as SystemConfig>::AccountId,
delegate: &<T as SystemConfig>::AccountId,
) -> T::Balance {
Approvals::<T, I>::get((asset, &owner, &delegate))
.map(|x| x.amount)
.unwrap_or_else(Zero::zero)
}
}

impl<T: Config<I>, I: 'static> fungibles::approvals::Mutate<<T as SystemConfig>::AccountId>
for Pallet<T, I>
{
fn approve(
asset: T::AssetId,
owner: &<T as SystemConfig>::AccountId,
delegate: &<T as SystemConfig>::AccountId,
amount: T::Balance,
) -> DispatchResult {
Self::do_approve_transfer(asset, owner, delegate, amount)
}

// Aprove spending tokens from a given account
fn transfer_from(
asset: T::AssetId,
owner: &<T as SystemConfig>::AccountId,
delegate: &<T as SystemConfig>::AccountId,
dest: &<T as SystemConfig>::AccountId,
amount: T::Balance,
) -> DispatchResult {
Self::do_transfer_approved(asset, owner, delegate, dest, amount)
}
}
96 changes: 3 additions & 93 deletions frame/assets/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -948,43 +948,7 @@ pub mod pallet {
decimals: u8,
) -> DispatchResult {
let origin = ensure_signed(origin)?;

let bounded_name: BoundedVec<u8, T::StringLimit> =
name.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;
let bounded_symbol: BoundedVec<u8, T::StringLimit> =
symbol.clone().try_into().map_err(|_| Error::<T, I>::BadMetadata)?;

let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
ensure!(&origin == &d.owner, Error::<T, I>::NoPermission);

Metadata::<T, I>::try_mutate_exists(id, |metadata| {
ensure!(
metadata.as_ref().map_or(true, |m| !m.is_frozen),
Error::<T, I>::NoPermission
);

let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
let new_deposit = T::MetadataDepositPerByte::get()
.saturating_mul(((name.len() + symbol.len()) as u32).into())
.saturating_add(T::MetadataDepositBase::get());

if new_deposit > old_deposit {
T::Currency::reserve(&origin, new_deposit - old_deposit)?;
} else {
T::Currency::unreserve(&origin, old_deposit - new_deposit);
}

*metadata = Some(AssetMetadata {
deposit: new_deposit,
name: bounded_name,
symbol: bounded_symbol,
decimals,
is_frozen: false,
});

Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, false));
Ok(())
})
Self::do_set_metadata(id, &origin, name, symbol, decimals)
}

/// Clear the metadata for an asset.
Expand Down Expand Up @@ -1171,35 +1135,7 @@ pub mod pallet {
) -> DispatchResult {
let owner = ensure_signed(origin)?;
let delegate = T::Lookup::lookup(delegate)?;

let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
ensure!(!d.is_frozen, Error::<T, I>::Frozen);
Approvals::<T, I>::try_mutate(
(id, &owner, &delegate),
|maybe_approved| -> DispatchResult {
let mut approved = match maybe_approved.take() {
// an approval already exists and is being updated
Some(a) => a,
// a new approval is created
None => {
d.approvals.saturating_inc();
Default::default()
},
};
let deposit_required = T::ApprovalDeposit::get();
if approved.deposit < deposit_required {
T::Currency::reserve(&owner, deposit_required - approved.deposit)?;
approved.deposit = deposit_required;
}
approved.amount = approved.amount.saturating_add(amount);
*maybe_approved = Some(approved);
Ok(())
},
)?;
Asset::<T, I>::insert(id, d);
Self::deposit_event(Event::ApprovedTransfer(id, owner, delegate, amount));

Ok(())
Self::do_approve_transfer(id, &owner, &delegate, amount)
}

/// Cancel all of some asset approved for delegated transfer by a third-party account.
Expand Down Expand Up @@ -1306,33 +1242,7 @@ pub mod pallet {
let delegate = ensure_signed(origin)?;
let owner = T::Lookup::lookup(owner)?;
let destination = T::Lookup::lookup(destination)?;

Approvals::<T, I>::try_mutate_exists(
(id, &owner, delegate),
|maybe_approved| -> DispatchResult {
let mut approved = maybe_approved.take().ok_or(Error::<T, I>::Unapproved)?;
let remaining =
approved.amount.checked_sub(&amount).ok_or(Error::<T, I>::Unapproved)?;

let f =
TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
Self::do_transfer(id, &owner, &destination, amount, None, f)?;

if remaining.is_zero() {
T::Currency::unreserve(&owner, approved.deposit);
Asset::<T, I>::mutate(id, |maybe_details| {
if let Some(details) = maybe_details {
details.approvals.saturating_dec();
}
});
} else {
approved.amount = remaining;
*maybe_approved = Some(approved);
}
Ok(())
},
)?;
Ok(())
Self::do_transfer_approved(id, &owner, &delegate, &destination, amount)
}
}
}
34 changes: 34 additions & 0 deletions frame/assets/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -794,3 +794,37 @@ fn assets_from_genesis_should_exist() {
assert_eq!(Assets::total_supply(999), 100);
});
}

#[test]
fn querying_name_symbol_and_decimals_should_work() {
new_test_ext().execute_with(|| {
use frame_support::traits::tokens::fungibles::metadata::Inspect;
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1));
assert_ok!(Assets::force_set_metadata(
Origin::root(),
0,
vec![0u8; 10],
vec![1u8; 10],
12,
false
));
assert_eq!(Assets::name(0), vec![0u8; 10]);
assert_eq!(Assets::symbol(0), vec![1u8; 10]);
assert_eq!(Assets::decimals(0), 12);
});
}

#[test]
fn querying_allowance_should_work() {
new_test_ext().execute_with(|| {
use frame_support::traits::tokens::fungibles::approvals::{Inspect, Mutate};
assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1));
assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100));
Balances::make_free_balance_be(&1, 1);
assert_ok!(Assets::approve(0, &1, &2, 50));
assert_eq!(Assets::allowance(0, &1, &2), 50);
// Transfer asset 0, from owner 1 and delegate 2 to destination 3
assert_ok!(Assets::transfer_from(0, &1, &2, &3, 50));
assert_eq!(Assets::allowance(0, &1, &2), 0);
});
}
2 changes: 2 additions & 0 deletions frame/support/src/traits/tokens/fungibles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ use crate::dispatch::{DispatchError, DispatchResult};
use sp_runtime::traits::Saturating;
use sp_std::vec::Vec;

pub mod approvals;
mod balanced;
pub mod metadata;
pub use balanced::{Balanced, Unbalanced};
mod imbalance;
pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance};
Expand Down
Loading

0 comments on commit 62f011c

Please sign in to comment.