mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 08:51:09 +00:00
Add low level traits to retrieve name, symbol, decimals and allowance 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>
This commit is contained in:
@@ -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> {
|
||||
@@ -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(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
//! Inspect and Mutate traits for Asset approvals
|
||||
|
||||
use crate::dispatch::DispatchResult;
|
||||
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
|
||||
// Check the amount approved by an owner to be spent by a delegate
|
||||
fn allowance(asset: Self::AssetId, owner: &AccountId, delegate: &AccountId) -> Self::Balance;
|
||||
}
|
||||
|
||||
pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
// Aprove a delegate account to spend an amount of tokens owned by an owner
|
||||
fn approve(
|
||||
asset: Self::AssetId,
|
||||
owner: &AccountId,
|
||||
delegate: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult;
|
||||
|
||||
// Transfer from a delegate account an amount approved by the owner of the asset
|
||||
fn transfer_from(
|
||||
asset: Self::AssetId,
|
||||
owner: &AccountId,
|
||||
delegate: &AccountId,
|
||||
dest: &AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd.
|
||||
// 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.
|
||||
|
||||
//! Inspect and Mutate traits for Asset metadata
|
||||
|
||||
use crate::dispatch::DispatchResult;
|
||||
use sp_std::vec::Vec;
|
||||
|
||||
pub trait Inspect<AccountId>: super::Inspect<AccountId> {
|
||||
// Get name for an AssetId.
|
||||
fn name(asset: Self::AssetId) -> Vec<u8>;
|
||||
// Get symbol for an AssetId.
|
||||
fn symbol(asset: Self::AssetId) -> Vec<u8>;
|
||||
// Get decimals for an AssetId.
|
||||
fn decimals(asset: Self::AssetId) -> u8;
|
||||
}
|
||||
|
||||
pub trait Mutate<AccountId>: Inspect<AccountId> {
|
||||
// Set name, symbol and decimals for a given assetId.
|
||||
fn set(
|
||||
asset: Self::AssetId,
|
||||
from: &AccountId,
|
||||
name: Vec<u8>,
|
||||
symbol: Vec<u8>,
|
||||
decimals: u8,
|
||||
) -> DispatchResult;
|
||||
}
|
||||
Reference in New Issue
Block a user