Files
pezkuwi-subxt/substrate/frame/assets/src/lib.rs
T
Andrew Jones 49b6dfd2e5 Enrich metadata with type information (#8615)
* Cargo.lock after merge

* Restore scale-info feature

* Fully qualify TypeInfo derive

* Skip PendingSwap T

* Add missing skip_type_params attr

* metadata docs features

* Reduce pallet event attribute to struct

* Cargo.lock

* Update frame/balances/src/tests_composite.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Line widths check

* Cargo.lock

* Add scale-info/std

* Update frame/system/src/lib.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Use `skip_type_params` to remove `TypeInfo` requirements on checks

* Revert "Remove unused Call metadata stuff"

This reverts commit 41311f85

* Skip BalanceSwapAction type parameter

* Remove unused event metadata macro

* Update frame-metadata

* Update primitives/npos-elections/compact/src/codec.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Manual TypeInfo for Header

* Remove TypeInfo requirement for consts in BoundedVec etc.

* Another TypeInfo bound removed

* review: fix indentation

* TypeInfo impls for Identity types

* Add some todos to add custom TypeInfo impls

* Update frame/support/procedural/src/pallet/expand/pallet_struct.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Add some todos to add custom TypeInfo impls

* Add a test for manual Data TypeInfo impl

* Add custom TypeInfo impl for Vote

* Era custom TypeInfo crimes

* Revert finality-grandpa version to 0.14.z

* review: renamed module to pallet_constants_metadata

* New line at end of file

* Add missing scale-info/std

* Update frame/support/src/storage/types/mod.rs

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>

* Remove StorageEntryType::Map unused flag

* Add missing scale-info dependency after merge

* SignedExtension::AdditionalSigned metadata

* Update frame-metadata, use abbreviated docs and args fields

* Update frame/example/Cargo.toml

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Add scale_info/std and remove unused scale-info dependency

* Remove scale-info dependency

* Remove treasury pallet::metadata

* Remove redundant Event test

* Add back scale-info as dev dependency

* fix error metadata when no error defined in decl_module

* Add Module3 to tests

* Fix metadata test

* Add docs feature to frame-support test

* WIP fixing pallet metadata test

* Remove redundant FunctionMetadata, FunctionArgumentMetadata as per https://github.com/paritytech/frame-metadata/pull/20

* Use main branch of frame-metadata

* Use patch of scale-info for latest changes

* Use latest patched scale-info

* Manual TypeInfo for DigestItem

* Manual TypeInfo for DigestItem

* Update scale-info

* Skip __Ignore variants for Error, depends on https://github.com/paritytech/scale-info/pull/117

* Named fields for FRAME v2 pallet Call variants

* Named fields for FRAME v1 pallet Call variants

* Add missing scale-info dependency

* WIP expand benchmark call variant

* fix benchmark with new function

create a new function for each variant of a pallet call.
This function is called by benchmarking macro in order not to break call
creation with unnamed argument

* fix tests

* more fix

* Fix staking tests

* Fix offchain workers calls

* Cherry pick rustfmt.toml from master

* cargo +nightly-2021-06-22 fmt --all

* Update to new call variant structs

* More call variant struct updates

* Remove unused import

* More call variant structs

* More call variant structs

* Even more call variant structs

* Mooar variant structs

* Evermore variant structs

* Call variant structs ad infinitum

* Fmt

* More call variants

* Last call variant

* Call variants all done?

* Fix SS58Prefix type

* Potential workaround for BitFlags<IdentityFields> TypeInfo

* Enable docs capturing for Call, Event, and Error types

* Fix IdentityFields TypeInfo

* Remove metadata-docs feature

* Add capture_docs = true for legacy Call, Event and Error types

* Fmt

* Fix metadata test type

* Update benchmarks with call struct variants

* Fmt

* More test fixes

* Fmt

* Fix benches

* Use latest capture_docs attr

* Latest scale_info

* Fmt

* review: change &Vec to &[]

* Remove pallet metadata attr

* review: remove commented out test code

* review: skip_type_params trailing comma suggestion

* Update to scale-info 0.10.0

* Update construct_runtime ui tests, different because of metadata TypeInfo impls

* Add some TypeInfo derives for UI tests

* Update storage ensure span ui stderrs

* Update call argument bound ui tests

Possibly changed because change from tuple to struct variants?

* Add scale-info dev dependency

* Update to latest finality-grandpa release

* review: missing newline

* review: missing scale-info/std

* review: remove duplicate scale-info/std

* review: remove fully qualified TypeInfo

* review: add missing scale-info/std

* review: remove unnecessary imports.

* Fmt

* Use crates.io RC version of frame-metadata

* Remove scale-info/std because it is a dev dependency

* Add missing scale_info dev-dependency for test

* Delete empty metadata folder

* Fix sp_std import

* review: improve manual UncheckedExtrinsic TypeInfo impl

* review: use full scale-info for dev-dependency

* Remove DefaultByteGetter impl

* review: derive TypeInfo for generic header

* Fmt

* Update primitives/runtime/src/generic/unchecked_extrinsic.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Update primitives/runtime/src/generic/unchecked_extrinsic.rs

Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>

* Update bin/node/executor/Cargo.toml

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/identity/src/types.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Update frame/support/src/dispatch.rs

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Remove redundant derive

* Simplify scale-info dependency

* Strip underscore prefix from call variant struct names

* Another underscore field

* More underscore fields

* Another underscore field

* Update to frame-metadata 14.0.0-rc.2 with combined StorageEntryType::Map

* Fmt

* Revert weights formatting

* Fix up some tests

* Fix up some tests for StorageEntryTypeMetadata

* scale-info dev dependency

* Fix test error

* Add missing TypeInfo derives

* Add back missing scale-info dependency

* Add back missing scale-info dependency

* Fix npos compact impls

* Cargo.lock

* Fmt

* Fix errors

* Fmt

* Fix renamed raw_solution field

* Fix error

* Fmt

* Fix some benchmarks

* Fmt

* Stray R

* Fix

* Add missing TypeInfos

* ui test fix

* Fix line widths

* Revert "ui test fix"

This reverts commit 2d15ec058a216e3f92d713f1174603a2bb1eac65.

* Upgrade to scale-info 0.11.0

* Revert "Upgrade to scale-info 0.11.0"

This reverts commit 047bb179085a0059c36cd20ab405f55cf0867e28.

* Add Runtime type

* Update to scale-info 0.12

* Update to scale-info 1.0

* Update frame-metadata to version 14.0.0

* Patch finality-grandpa until release available

* Fix metadata tests

* Fix metadata tests

* Fmt

* Remove patched finality-grandpa

* Fix tests, use scale_info imports

* Fix pallet tests

* Add BlockNumber TypeInfo bound

* ui test fix

* Cargo.lock

* Remove pallet metadata

* Cargo.lock

* Add missing scale-info dependency

* Remove pallet event metadata

* Fix error

* Fix collective errors

* Semicolol

* Fmt

* Remove another metadata attribute

* Add new variant to custom digest TypeInfo

* Fmt

* Cargo.lock from master

* Remove comma lol

* Fix example call error

* Fix example call error properly

Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Keith Yeung <kungfukeith11@gmail.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
2021-09-15 11:40:41 +00:00

1288 lines
46 KiB
Rust

// This file is part of Substrate.
// Copyright (C) 2017-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.
//! # Assets Pallet
//!
//! A simple, secure module for dealing with fungible assets.
//!
//! ## Overview
//!
//! The Assets module provides functionality for asset management of fungible asset classes
//! with a fixed supply, including:
//!
//! * Asset Issuance (Minting)
//! * Asset Transferal
//! * Asset Freezing
//! * Asset Destruction (Burning)
//! * Delegated Asset Transfers ("Approval API")
//!
//! To use it in your runtime, you need to implement the assets [`Config`].
//!
//! The supported dispatchable functions are documented in the [`Call`] enum.
//!
//! ### Terminology
//!
//! * **Admin**: An account ID uniquely privileged to be able to unfreeze (thaw) an account and it's
//! assets, as well as forcibly transfer a particular class of assets between arbitrary accounts
//! and reduce the balance of a particular class of assets of arbitrary accounts.
//! * **Asset issuance/minting**: The creation of a new asset, whose total supply will belong to the
//! account that issues the asset. This is a privileged operation.
//! * **Asset transfer**: The reduction of the balance of an asset of one account with the
//! corresponding increase in the balance of another.
//! * **Asset destruction**: The process of reduce the balance of an asset of one account. This is a
//! privileged operation.
//! * **Fungible asset**: An asset whose units are interchangeable.
//! * **Issuer**: An account ID uniquely privileged to be able to mint a particular class of assets.
//! * **Freezer**: An account ID uniquely privileged to be able to freeze an account from
//! transferring a particular class of assets.
//! * **Freezing**: Removing the possibility of an unpermissioned transfer of an asset from a
//! particular account.
//! * **Non-fungible asset**: An asset for which each unit has unique characteristics.
//! * **Owner**: An account ID uniquely privileged to be able to destroy a particular asset class,
//! or to set the Issuer, Freezer or Admin of that asset class.
//! * **Approval**: The act of allowing an account the permission to transfer some balance of asset
//! from the approving account into some third-party destination account.
//! * **Sufficiency**: The idea of a minimum-balance of an asset being sufficient to allow the
//! account's existence on the system without requiring any other existential-deposit.
//!
//! ### Goals
//!
//! The assets system in Substrate is designed to make the following possible:
//!
//! * Issue a new assets in a permissioned or permissionless way, if permissionless, then with a
//! deposit required.
//! * Allow accounts to be delegated the ability to transfer assets without otherwise existing
//! on-chain (*approvals*).
//! * Move assets between accounts.
//! * Update the asset's total supply.
//! * Allow administrative activities by specially privileged accounts including freezing account
//! balances and minting/burning assets.
//!
//! ## Interface
//!
//! ### Permissionless Functions
//!
//! * `create`: Creates a new asset class, taking the required deposit.
//! * `transfer`: Transfer sender's assets to another account.
//! * `transfer_keep_alive`: Transfer sender's assets to another account, keeping the sender alive.
//! * `set_metadata`: Set the metadata of an asset class.
//! * `clear_metadata`: Remove the metadata of an asset class.
//! * `approve_transfer`: Create or increase an delegated transfer.
//! * `cancel_approval`: Rescind a previous approval.
//! * `transfer_approved`: Transfer third-party's assets to another account.
//!
//! ### Permissioned Functions
//!
//! * `force_create`: Creates a new asset class without taking any deposit.
//! * `force_set_metadata`: Set the metadata of an asset class.
//! * `force_clear_metadata`: Remove the metadata of an asset class.
//! * `force_asset_status`: Alter an asset class's attributes.
//! * `force_cancel_approval`: Rescind a previous approval.
//!
//! ### Privileged Functions
//! * `destroy`: Destroys an entire asset class; called by the asset class's Owner.
//! * `mint`: Increases the asset balance of an account; called by the asset class's Issuer.
//! * `burn`: Decreases the asset balance of an account; called by the asset class's Admin.
//! * `force_transfer`: Transfers between arbitrary accounts; called by the asset class's Admin.
//! * `freeze`: Disallows further `transfer`s from an account; called by the asset class's Freezer.
//! * `thaw`: Allows further `transfer`s from an account; called by the asset class's Admin.
//! * `transfer_ownership`: Changes an asset class's Owner; called by the asset class's Owner.
//! * `set_team`: Changes an asset class's Admin, Freezer and Issuer; called by the asset class's
//! Owner.
//!
//! Please refer to the [`Call`] enum and its associated variants for documentation on each
//! function.
//!
//! ### Public Functions
//! <!-- Original author of descriptions: @gavofyork -->
//!
//! * `balance` - Get the asset `id` balance of `who`.
//! * `total_supply` - Get the total supply of an asset `id`.
//!
//! Please refer to the [`Pallet`] struct for details on publicly available functions.
//!
//! ## Related Modules
//!
//! * [`System`](../frame_system/index.html)
//! * [`Support`](../frame_support/index.html)
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
pub mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
mod extra_mutator;
pub use extra_mutator::*;
mod functions;
mod impl_fungibles;
mod impl_stored_map;
mod types;
pub use types::*;
use codec::HasCompact;
use frame_support::{
dispatch::{DispatchError, DispatchResult},
ensure,
traits::{
tokens::{fungibles, DepositConsequence, WithdrawConsequence},
BalanceStatus::Reserved,
Currency, ReservableCurrency, StoredMap,
},
};
use frame_system::Config as SystemConfig;
use sp_runtime::{
traits::{
AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero,
},
ArithmeticError, TokenError,
};
use sp_std::{borrow::Borrow, convert::TryInto, prelude::*};
pub use pallet::*;
pub use weights::WeightInfo;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{dispatch::DispatchResult, pallet_prelude::*};
use frame_system::pallet_prelude::*;
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::generate_storage_info]
pub struct Pallet<T, I = ()>(_);
#[pallet::config]
/// The module configuration trait.
pub trait Config<I: 'static = ()>: frame_system::Config {
/// The overarching event type.
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
/// The units in which we record balances.
type Balance: Member + Parameter + AtLeast32BitUnsigned + Default + Copy + MaxEncodedLen;
/// Identifier for the class of asset.
type AssetId: Member + Parameter + Default + Copy + HasCompact + MaxEncodedLen;
/// The currency mechanism.
type Currency: ReservableCurrency<Self::AccountId>;
/// The origin which may forcibly create or destroy an asset or otherwise alter privileged
/// attributes.
type ForceOrigin: EnsureOrigin<Self::Origin>;
/// The basic amount of funds that must be reserved for an asset.
#[pallet::constant]
type AssetDeposit: Get<DepositBalanceOf<Self, I>>;
/// The basic amount of funds that must be reserved when adding metadata to your asset.
#[pallet::constant]
type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
/// The additional funds that must be reserved for the number of bytes you store in your
/// metadata.
#[pallet::constant]
type MetadataDepositPerByte: Get<DepositBalanceOf<Self, I>>;
/// The amount of funds that must be reserved when creating a new approval.
#[pallet::constant]
type ApprovalDeposit: Get<DepositBalanceOf<Self, I>>;
/// The maximum length of a name or symbol stored on-chain.
#[pallet::constant]
type StringLimit: Get<u32>;
/// A hook to allow a per-asset, per-account minimum balance to be enforced. This must be
/// respected in all permissionless operations.
type Freezer: FrozenBalance<Self::AssetId, Self::AccountId, Self::Balance>;
/// Additional data to be stored with an account's asset balance.
type Extra: Member + Parameter + Default + MaxEncodedLen;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
#[pallet::storage]
/// Details of an asset.
pub(super) type Asset<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AssetId,
AssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T, I>>,
>;
#[pallet::storage]
/// The number of units of assets held by any given account.
pub(super) type Account<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AssetId,
Blake2_128Concat,
T::AccountId,
AssetBalance<T::Balance, T::Extra>,
ValueQuery,
GetDefault,
ConstU32<300_000>,
>;
#[pallet::storage]
/// Approved balance transfers. First balance is the amount approved for transfer. Second
/// is the amount of `T::Currency` reserved for storing this.
/// First key is the asset ID, second key is the owner and third key is the delegate.
pub(super) type Approvals<T: Config<I>, I: 'static = ()> = StorageNMap<
_,
(
NMapKey<Blake2_128Concat, T::AssetId>,
NMapKey<Blake2_128Concat, T::AccountId>, // owner
NMapKey<Blake2_128Concat, T::AccountId>, // delegate
),
Approval<T::Balance, DepositBalanceOf<T, I>>,
OptionQuery,
GetDefault,
ConstU32<300_000>,
>;
#[pallet::storage]
/// Metadata of an asset.
pub(super) type Metadata<T: Config<I>, I: 'static = ()> = StorageMap<
_,
Blake2_128Concat,
T::AssetId,
AssetMetadata<DepositBalanceOf<T, I>, BoundedVec<u8, T::StringLimit>>,
ValueQuery,
GetDefault,
ConstU32<300_000>,
>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// Some asset class was created. \[asset_id, creator, owner\]
Created(T::AssetId, T::AccountId, T::AccountId),
/// Some assets were issued. \[asset_id, owner, total_supply\]
Issued(T::AssetId, T::AccountId, T::Balance),
/// Some assets were transferred. \[asset_id, from, to, amount\]
Transferred(T::AssetId, T::AccountId, T::AccountId, T::Balance),
/// Some assets were destroyed. \[asset_id, owner, balance\]
Burned(T::AssetId, T::AccountId, T::Balance),
/// The management team changed \[asset_id, issuer, admin, freezer\]
TeamChanged(T::AssetId, T::AccountId, T::AccountId, T::AccountId),
/// The owner changed \[asset_id, owner\]
OwnerChanged(T::AssetId, T::AccountId),
/// Some account `who` was frozen. \[asset_id, who\]
Frozen(T::AssetId, T::AccountId),
/// Some account `who` was thawed. \[asset_id, who\]
Thawed(T::AssetId, T::AccountId),
/// Some asset `asset_id` was frozen. \[asset_id\]
AssetFrozen(T::AssetId),
/// Some asset `asset_id` was thawed. \[asset_id\]
AssetThawed(T::AssetId),
/// An asset class was destroyed.
Destroyed(T::AssetId),
/// Some asset class was force-created. \[asset_id, owner\]
ForceCreated(T::AssetId, T::AccountId),
/// New metadata has been set for an asset. \[asset_id, name, symbol, decimals, is_frozen\]
MetadataSet(T::AssetId, Vec<u8>, Vec<u8>, u8, bool),
/// Metadata has been cleared for an asset. \[asset_id\]
MetadataCleared(T::AssetId),
/// (Additional) funds have been approved for transfer to a destination account.
/// \[asset_id, source, delegate, amount\]
ApprovedTransfer(T::AssetId, T::AccountId, T::AccountId, T::Balance),
/// An approval for account `delegate` was cancelled by `owner`.
/// \[id, owner, delegate\]
ApprovalCancelled(T::AssetId, T::AccountId, T::AccountId),
/// An `amount` was transferred in its entirety from `owner` to `destination` by
/// the approved `delegate`.
/// \[id, owner, delegate, destination\]
TransferredApproved(T::AssetId, T::AccountId, T::AccountId, T::AccountId, T::Balance),
/// An asset has had its attributes changed by the `Force` origin.
/// \[id\]
AssetStatusChanged(T::AssetId),
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// Account balance must be greater than or equal to the transfer amount.
BalanceLow,
/// Balance should be non-zero.
BalanceZero,
/// The signing account has no permission to do the operation.
NoPermission,
/// The given asset ID is unknown.
Unknown,
/// The origin account is frozen.
Frozen,
/// The asset ID is already taken.
InUse,
/// Invalid witness data given.
BadWitness,
/// Minimum balance should be non-zero.
MinBalanceZero,
/// No provider reference exists to allow a non-zero balance of a non-self-sufficient
/// asset.
NoProvider,
/// Invalid metadata given.
BadMetadata,
/// No approval exists that would allow the transfer.
Unapproved,
/// The source account would not survive the transfer and it needs to stay alive.
WouldDie,
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Issue a new class of fungible assets from a public origin.
///
/// This new asset class has no assets initially and its owner is the origin.
///
/// The origin must be Signed and the sender must have sufficient funds free.
///
/// Funds of sender are reserved by `AssetDeposit`.
///
/// Parameters:
/// - `id`: The identifier of the new asset. This must not be currently in use to identify
/// an existing asset.
/// - `admin`: The admin of this class of assets. The admin is the initial address of each
/// member of the asset class's admin team.
/// - `min_balance`: The minimum balance of this new asset that any single account must
/// have. If an account's balance is reduced below this, then it collapses to zero.
///
/// Emits `Created` event when successful.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::create())]
pub fn create(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
admin: <T::Lookup as StaticLookup>::Source,
min_balance: T::Balance,
) -> DispatchResult {
let owner = ensure_signed(origin)?;
let admin = T::Lookup::lookup(admin)?;
ensure!(!Asset::<T, I>::contains_key(id), Error::<T, I>::InUse);
ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
let deposit = T::AssetDeposit::get();
T::Currency::reserve(&owner, deposit)?;
Asset::<T, I>::insert(
id,
AssetDetails {
owner: owner.clone(),
issuer: admin.clone(),
admin: admin.clone(),
freezer: admin.clone(),
supply: Zero::zero(),
deposit,
min_balance,
is_sufficient: false,
accounts: 0,
sufficients: 0,
approvals: 0,
is_frozen: false,
},
);
Self::deposit_event(Event::Created(id, owner, admin));
Ok(())
}
/// Issue a new class of fungible assets from a privileged origin.
///
/// This new asset class has no assets initially.
///
/// The origin must conform to `ForceOrigin`.
///
/// Unlike `create`, no funds are reserved.
///
/// - `id`: The identifier of the new asset. This must not be currently in use to identify
/// an existing asset.
/// - `owner`: The owner of this class of assets. The owner has full superuser permissions
/// over this asset, but may later change and configure the permissions using
/// `transfer_ownership` and `set_team`.
/// - `min_balance`: The minimum balance of this new asset that any single account must
/// have. If an account's balance is reduced below this, then it collapses to zero.
///
/// Emits `ForceCreated` event when successful.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::force_create())]
pub fn force_create(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
owner: <T::Lookup as StaticLookup>::Source,
is_sufficient: bool,
#[pallet::compact] min_balance: T::Balance,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
let owner = T::Lookup::lookup(owner)?;
ensure!(!Asset::<T, I>::contains_key(id), Error::<T, I>::InUse);
ensure!(!min_balance.is_zero(), Error::<T, I>::MinBalanceZero);
Asset::<T, I>::insert(
id,
AssetDetails {
owner: owner.clone(),
issuer: owner.clone(),
admin: owner.clone(),
freezer: owner.clone(),
supply: Zero::zero(),
deposit: Zero::zero(),
min_balance,
is_sufficient,
accounts: 0,
sufficients: 0,
approvals: 0,
is_frozen: false,
},
);
Self::deposit_event(Event::ForceCreated(id, owner));
Ok(())
}
/// Destroy a class of fungible assets.
///
/// The origin must conform to `ForceOrigin` or must be Signed and the sender must be the
/// owner of the asset `id`.
///
/// - `id`: The identifier of the asset to be destroyed. This must identify an existing
/// asset.
///
/// Emits `Destroyed` event when successful.
///
/// NOTE: It can be helpful to first freeze an asset before destroying it so that you
/// can provide accurate witness information and prevent users from manipulating state
/// in a way that can make it harder to destroy.
///
/// Weight: `O(c + p + a)` where:
/// - `c = (witness.accounts - witness.sufficients)`
/// - `s = witness.sufficients`
/// - `a = witness.approvals`
#[pallet::weight(T::WeightInfo::destroy(
witness.accounts.saturating_sub(witness.sufficients),
witness.sufficients,
witness.approvals,
))]
pub fn destroy(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
witness: DestroyWitness,
) -> DispatchResultWithPostInfo {
let maybe_check_owner = match T::ForceOrigin::try_origin(origin) {
Ok(_) => None,
Err(origin) => Some(ensure_signed(origin)?),
};
Asset::<T, I>::try_mutate_exists(id, |maybe_details| {
let mut details = maybe_details.take().ok_or(Error::<T, I>::Unknown)?;
if let Some(check_owner) = maybe_check_owner {
ensure!(details.owner == check_owner, Error::<T, I>::NoPermission);
}
ensure!(details.accounts <= witness.accounts, Error::<T, I>::BadWitness);
ensure!(details.sufficients <= witness.sufficients, Error::<T, I>::BadWitness);
ensure!(details.approvals <= witness.approvals, Error::<T, I>::BadWitness);
for (who, v) in Account::<T, I>::drain_prefix(id) {
Self::dead_account(id, &who, &mut details, v.sufficient);
}
debug_assert_eq!(details.accounts, 0);
debug_assert_eq!(details.sufficients, 0);
let metadata = Metadata::<T, I>::take(&id);
T::Currency::unreserve(
&details.owner,
details.deposit.saturating_add(metadata.deposit),
);
for ((owner, _), approval) in Approvals::<T, I>::drain_prefix((&id,)) {
T::Currency::unreserve(&owner, approval.deposit);
}
Self::deposit_event(Event::Destroyed(id));
Ok(Some(T::WeightInfo::destroy(
details.accounts.saturating_sub(details.sufficients),
details.sufficients,
details.approvals,
))
.into())
})
}
/// Mint assets of a particular class.
///
/// The origin must be Signed and the sender must be the Issuer of the asset `id`.
///
/// - `id`: The identifier of the asset to have some amount minted.
/// - `beneficiary`: The account to be credited with the minted assets.
/// - `amount`: The amount of the asset to be minted.
///
/// Emits `Issued` event when successful.
///
/// Weight: `O(1)`
/// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`.
#[pallet::weight(T::WeightInfo::mint())]
pub fn mint(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
beneficiary: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let beneficiary = T::Lookup::lookup(beneficiary)?;
Self::do_mint(id, &beneficiary, amount, Some(origin))?;
Ok(())
}
/// Reduce the balance of `who` by as much as possible up to `amount` assets of `id`.
///
/// Origin must be Signed and the sender should be the Manager of the asset `id`.
///
/// Bails with `BalanceZero` if the `who` is already dead.
///
/// - `id`: The identifier of the asset to have some amount burned.
/// - `who`: The account to be debited from.
/// - `amount`: The maximum amount by which `who`'s balance should be reduced.
///
/// Emits `Burned` with the actual amount burned. If this takes the balance to below the
/// minimum for the asset, then the amount burned is increased to take it to zero.
///
/// Weight: `O(1)`
/// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`.
#[pallet::weight(T::WeightInfo::burn())]
pub fn burn(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
who: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let who = T::Lookup::lookup(who)?;
let f = DebitFlags { keep_alive: false, best_effort: true };
let _ = Self::do_burn(id, &who, amount, Some(origin), f)?;
Ok(())
}
/// Move some assets from the sender account to another.
///
/// Origin must be Signed.
///
/// - `id`: The identifier of the asset to have some amount transferred.
/// - `target`: The account to be credited.
/// - `amount`: The amount by which the sender's balance of assets should be reduced and
/// `target`'s balance increased. The amount actually transferred may be slightly greater in
/// the case that the transfer would otherwise take the sender balance above zero but below
/// the minimum balance. Must be greater than zero.
///
/// Emits `Transferred` with the actual amount transferred. If this takes the source balance
/// to below the minimum for the asset, then the amount transferred is increased to take it
/// to zero.
///
/// Weight: `O(1)`
/// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of
/// `target`.
#[pallet::weight(T::WeightInfo::transfer())]
pub fn transfer(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
target: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(target)?;
let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
Self::do_transfer(id, &origin, &dest, amount, None, f).map(|_| ())
}
/// Move some assets from the sender account to another, keeping the sender account alive.
///
/// Origin must be Signed.
///
/// - `id`: The identifier of the asset to have some amount transferred.
/// - `target`: The account to be credited.
/// - `amount`: The amount by which the sender's balance of assets should be reduced and
/// `target`'s balance increased. The amount actually transferred may be slightly greater in
/// the case that the transfer would otherwise take the sender balance above zero but below
/// the minimum balance. Must be greater than zero.
///
/// Emits `Transferred` with the actual amount transferred. If this takes the source balance
/// to below the minimum for the asset, then the amount transferred is increased to take it
/// to zero.
///
/// Weight: `O(1)`
/// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of
/// `target`.
#[pallet::weight(T::WeightInfo::transfer_keep_alive())]
pub fn transfer_keep_alive(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
target: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let source = ensure_signed(origin)?;
let dest = T::Lookup::lookup(target)?;
let f = TransferFlags { keep_alive: true, best_effort: false, burn_dust: false };
Self::do_transfer(id, &source, &dest, amount, None, f).map(|_| ())
}
/// Move some assets from one account to another.
///
/// Origin must be Signed and the sender should be the Admin of the asset `id`.
///
/// - `id`: The identifier of the asset to have some amount transferred.
/// - `source`: The account to be debited.
/// - `dest`: The account to be credited.
/// - `amount`: The amount by which the `source`'s balance of assets should be reduced and
/// `dest`'s balance increased. The amount actually transferred may be slightly greater in
/// the case that the transfer would otherwise take the `source` balance above zero but
/// below the minimum balance. Must be greater than zero.
///
/// Emits `Transferred` with the actual amount transferred. If this takes the source balance
/// to below the minimum for the asset, then the amount transferred is increased to take it
/// to zero.
///
/// Weight: `O(1)`
/// Modes: Pre-existence of `dest`; Post-existence of `source`; Account pre-existence of
/// `dest`.
#[pallet::weight(T::WeightInfo::force_transfer())]
pub fn force_transfer(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
source: <T::Lookup as StaticLookup>::Source,
dest: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let source = T::Lookup::lookup(source)?;
let dest = T::Lookup::lookup(dest)?;
let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ())
}
/// Disallow further unprivileged transfers from an account.
///
/// Origin must be Signed and the sender should be the Freezer of the asset `id`.
///
/// - `id`: The identifier of the asset to be frozen.
/// - `who`: The account to be frozen.
///
/// Emits `Frozen`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::freeze())]
pub fn freeze(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
who: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
ensure!(&origin == &d.freezer, Error::<T, I>::NoPermission);
let who = T::Lookup::lookup(who)?;
ensure!(Account::<T, I>::contains_key(id, &who), Error::<T, I>::BalanceZero);
Account::<T, I>::mutate(id, &who, |a| a.is_frozen = true);
Self::deposit_event(Event::<T, I>::Frozen(id, who));
Ok(())
}
/// Allow unprivileged transfers from an account again.
///
/// Origin must be Signed and the sender should be the Admin of the asset `id`.
///
/// - `id`: The identifier of the asset to be frozen.
/// - `who`: The account to be unfrozen.
///
/// Emits `Thawed`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::thaw())]
pub fn thaw(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
who: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let details = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
ensure!(&origin == &details.admin, Error::<T, I>::NoPermission);
let who = T::Lookup::lookup(who)?;
ensure!(Account::<T, I>::contains_key(id, &who), Error::<T, I>::BalanceZero);
Account::<T, I>::mutate(id, &who, |a| a.is_frozen = false);
Self::deposit_event(Event::<T, I>::Thawed(id, who));
Ok(())
}
/// Disallow further unprivileged transfers for the asset class.
///
/// Origin must be Signed and the sender should be the Freezer of the asset `id`.
///
/// - `id`: The identifier of the asset to be frozen.
///
/// Emits `Frozen`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::freeze_asset())]
pub fn freeze_asset(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
Asset::<T, I>::try_mutate(id, |maybe_details| {
let d = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
ensure!(&origin == &d.freezer, Error::<T, I>::NoPermission);
d.is_frozen = true;
Self::deposit_event(Event::<T, I>::AssetFrozen(id));
Ok(())
})
}
/// Allow unprivileged transfers for the asset again.
///
/// Origin must be Signed and the sender should be the Admin of the asset `id`.
///
/// - `id`: The identifier of the asset to be thawed.
///
/// Emits `Thawed`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::thaw_asset())]
pub fn thaw_asset(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
Asset::<T, I>::try_mutate(id, |maybe_details| {
let d = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
ensure!(&origin == &d.admin, Error::<T, I>::NoPermission);
d.is_frozen = false;
Self::deposit_event(Event::<T, I>::AssetThawed(id));
Ok(())
})
}
/// Change the Owner of an asset.
///
/// Origin must be Signed and the sender should be the Owner of the asset `id`.
///
/// - `id`: The identifier of the asset.
/// - `owner`: The new Owner of this asset.
///
/// Emits `OwnerChanged`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::transfer_ownership())]
pub fn transfer_ownership(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
owner: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let owner = T::Lookup::lookup(owner)?;
Asset::<T, I>::try_mutate(id, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
ensure!(&origin == &details.owner, Error::<T, I>::NoPermission);
if details.owner == owner {
return Ok(())
}
let metadata_deposit = Metadata::<T, I>::get(id).deposit;
let deposit = details.deposit + metadata_deposit;
// Move the deposit to the new owner.
T::Currency::repatriate_reserved(&details.owner, &owner, deposit, Reserved)?;
details.owner = owner.clone();
Self::deposit_event(Event::OwnerChanged(id, owner));
Ok(())
})
}
/// Change the Issuer, Admin and Freezer of an asset.
///
/// Origin must be Signed and the sender should be the Owner of the asset `id`.
///
/// - `id`: The identifier of the asset to be frozen.
/// - `issuer`: The new Issuer of this asset.
/// - `admin`: The new Admin of this asset.
/// - `freezer`: The new Freezer of this asset.
///
/// Emits `TeamChanged`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::set_team())]
pub fn set_team(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
issuer: <T::Lookup as StaticLookup>::Source,
admin: <T::Lookup as StaticLookup>::Source,
freezer: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
let issuer = T::Lookup::lookup(issuer)?;
let admin = T::Lookup::lookup(admin)?;
let freezer = T::Lookup::lookup(freezer)?;
Asset::<T, I>::try_mutate(id, |maybe_details| {
let details = maybe_details.as_mut().ok_or(Error::<T, I>::Unknown)?;
ensure!(&origin == &details.owner, Error::<T, I>::NoPermission);
details.issuer = issuer.clone();
details.admin = admin.clone();
details.freezer = freezer.clone();
Self::deposit_event(Event::TeamChanged(id, issuer, admin, freezer));
Ok(())
})
}
/// Set the metadata for an asset.
///
/// Origin must be Signed and the sender should be the Owner of the asset `id`.
///
/// Funds of sender are reserved according to the formula:
/// `MetadataDepositBase + MetadataDepositPerByte * (name.len + symbol.len)` taking into
/// account any already reserved funds.
///
/// - `id`: The identifier of the asset to update.
/// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`.
/// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`.
/// - `decimals`: The number of decimals this asset uses to represent one unit.
///
/// Emits `MetadataSet`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::set_metadata(name.len() as u32, symbol.len() as u32))]
pub fn set_metadata(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
name: Vec<u8>,
symbol: Vec<u8>,
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(())
})
}
/// Clear the metadata for an asset.
///
/// Origin must be Signed and the sender should be the Owner of the asset `id`.
///
/// Any deposit is freed for the asset owner.
///
/// - `id`: The identifier of the asset to clear.
///
/// Emits `MetadataCleared`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::clear_metadata())]
pub fn clear_metadata(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
) -> DispatchResult {
let origin = ensure_signed(origin)?;
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| {
let deposit = metadata.take().ok_or(Error::<T, I>::Unknown)?.deposit;
T::Currency::unreserve(&d.owner, deposit);
Self::deposit_event(Event::MetadataCleared(id));
Ok(())
})
}
/// Force the metadata for an asset to some value.
///
/// Origin must be ForceOrigin.
///
/// Any deposit is left alone.
///
/// - `id`: The identifier of the asset to update.
/// - `name`: The user friendly name of this asset. Limited in length by `StringLimit`.
/// - `symbol`: The exchange symbol for this asset. Limited in length by `StringLimit`.
/// - `decimals`: The number of decimals this asset uses to represent one unit.
///
/// Emits `MetadataSet`.
///
/// Weight: `O(N + S)` where N and S are the length of the name and symbol respectively.
#[pallet::weight(T::WeightInfo::force_set_metadata(name.len() as u32, symbol.len() as u32))]
pub fn force_set_metadata(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
name: Vec<u8>,
symbol: Vec<u8>,
decimals: u8,
is_frozen: bool,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(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)?;
ensure!(Asset::<T, I>::contains_key(id), Error::<T, I>::Unknown);
Metadata::<T, I>::try_mutate_exists(id, |metadata| {
let deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit);
*metadata = Some(AssetMetadata {
deposit,
name: bounded_name,
symbol: bounded_symbol,
decimals,
is_frozen,
});
Self::deposit_event(Event::MetadataSet(id, name, symbol, decimals, is_frozen));
Ok(())
})
}
/// Clear the metadata for an asset.
///
/// Origin must be ForceOrigin.
///
/// Any deposit is returned.
///
/// - `id`: The identifier of the asset to clear.
///
/// Emits `MetadataCleared`.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::force_clear_metadata())]
pub fn force_clear_metadata(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
let d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
Metadata::<T, I>::try_mutate_exists(id, |metadata| {
let deposit = metadata.take().ok_or(Error::<T, I>::Unknown)?.deposit;
T::Currency::unreserve(&d.owner, deposit);
Self::deposit_event(Event::MetadataCleared(id));
Ok(())
})
}
/// Alter the attributes of a given asset.
///
/// Origin must be `ForceOrigin`.
///
/// - `id`: The identifier of the asset.
/// - `owner`: The new Owner of this asset.
/// - `issuer`: The new Issuer of this asset.
/// - `admin`: The new Admin of this asset.
/// - `freezer`: The new Freezer of this asset.
/// - `min_balance`: The minimum balance of this new asset that any single account must
/// have. If an account's balance is reduced below this, then it collapses to zero.
/// - `is_sufficient`: Whether a non-zero balance of this asset is deposit of sufficient
/// value to account for the state bloat associated with its balance storage. If set to
/// `true`, then non-zero balances may be stored without a `consumer` reference (and thus
/// an ED in the Balances pallet or whatever else is used to control user-account state
/// growth).
/// - `is_frozen`: Whether this asset class is frozen except for permissioned/admin
/// instructions.
///
/// Emits `AssetStatusChanged` with the identity of the asset.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::force_asset_status())]
pub fn force_asset_status(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
owner: <T::Lookup as StaticLookup>::Source,
issuer: <T::Lookup as StaticLookup>::Source,
admin: <T::Lookup as StaticLookup>::Source,
freezer: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] min_balance: T::Balance,
is_sufficient: bool,
is_frozen: bool,
) -> DispatchResult {
T::ForceOrigin::ensure_origin(origin)?;
Asset::<T, I>::try_mutate(id, |maybe_asset| {
let mut asset = maybe_asset.take().ok_or(Error::<T, I>::Unknown)?;
asset.owner = T::Lookup::lookup(owner)?;
asset.issuer = T::Lookup::lookup(issuer)?;
asset.admin = T::Lookup::lookup(admin)?;
asset.freezer = T::Lookup::lookup(freezer)?;
asset.min_balance = min_balance;
asset.is_sufficient = is_sufficient;
asset.is_frozen = is_frozen;
*maybe_asset = Some(asset);
Self::deposit_event(Event::AssetStatusChanged(id));
Ok(())
})
}
/// Approve an amount of asset for transfer by a delegated third-party account.
///
/// Origin must be Signed.
///
/// Ensures that `ApprovalDeposit` worth of `Currency` is reserved from signing account
/// for the purpose of holding the approval. If some non-zero amount of assets is already
/// approved from signing account to `delegate`, then it is topped up or unreserved to
/// meet the right value.
///
/// NOTE: The signing account does not need to own `amount` of assets at the point of
/// making this call.
///
/// - `id`: The identifier of the asset.
/// - `delegate`: The account to delegate permission to transfer asset.
/// - `amount`: The amount of asset that may be transferred by `delegate`. If there is
/// already an approval in place, then this acts additively.
///
/// Emits `ApprovedTransfer` on success.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::approve_transfer())]
pub fn approve_transfer(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
delegate: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> 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(())
}
/// Cancel all of some asset approved for delegated transfer by a third-party account.
///
/// Origin must be Signed and there must be an approval in place between signer and
/// `delegate`.
///
/// Unreserves any deposit previously reserved by `approve_transfer` for the approval.
///
/// - `id`: The identifier of the asset.
/// - `delegate`: The account delegated permission to transfer asset.
///
/// Emits `ApprovalCancelled` on success.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::cancel_approval())]
pub fn cancel_approval(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
delegate: <T::Lookup as StaticLookup>::Source,
) -> 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)?;
let approval =
Approvals::<T, I>::take((id, &owner, &delegate)).ok_or(Error::<T, I>::Unknown)?;
T::Currency::unreserve(&owner, approval.deposit);
d.approvals.saturating_dec();
Asset::<T, I>::insert(id, d);
Self::deposit_event(Event::ApprovalCancelled(id, owner, delegate));
Ok(())
}
/// Cancel all of some asset approved for delegated transfer by a third-party account.
///
/// Origin must be either ForceOrigin or Signed origin with the signer being the Admin
/// account of the asset `id`.
///
/// Unreserves any deposit previously reserved by `approve_transfer` for the approval.
///
/// - `id`: The identifier of the asset.
/// - `delegate`: The account delegated permission to transfer asset.
///
/// Emits `ApprovalCancelled` on success.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::force_cancel_approval())]
pub fn force_cancel_approval(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
owner: <T::Lookup as StaticLookup>::Source,
delegate: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
let mut d = Asset::<T, I>::get(id).ok_or(Error::<T, I>::Unknown)?;
T::ForceOrigin::try_origin(origin)
.map(|_| ())
.or_else(|origin| -> DispatchResult {
let origin = ensure_signed(origin)?;
ensure!(&origin == &d.admin, Error::<T, I>::NoPermission);
Ok(())
})?;
let owner = T::Lookup::lookup(owner)?;
let delegate = T::Lookup::lookup(delegate)?;
let approval =
Approvals::<T, I>::take((id, &owner, &delegate)).ok_or(Error::<T, I>::Unknown)?;
T::Currency::unreserve(&owner, approval.deposit);
d.approvals.saturating_dec();
Asset::<T, I>::insert(id, d);
Self::deposit_event(Event::ApprovalCancelled(id, owner, delegate));
Ok(())
}
/// Transfer some asset balance from a previously delegated account to some third-party
/// account.
///
/// Origin must be Signed and there must be an approval in place by the `owner` to the
/// signer.
///
/// If the entire amount approved for transfer is transferred, then any deposit previously
/// reserved by `approve_transfer` is unreserved.
///
/// - `id`: The identifier of the asset.
/// - `owner`: The account which previously approved for a transfer of at least `amount` and
/// from which the asset balance will be withdrawn.
/// - `destination`: The account to which the asset balance of `amount` will be transferred.
/// - `amount`: The amount of assets to transfer.
///
/// Emits `TransferredApproved` on success.
///
/// Weight: `O(1)`
#[pallet::weight(T::WeightInfo::transfer_approved())]
pub fn transfer_approved(
origin: OriginFor<T>,
#[pallet::compact] id: T::AssetId,
owner: <T::Lookup as StaticLookup>::Source,
destination: <T::Lookup as StaticLookup>::Source,
#[pallet::compact] amount: T::Balance,
) -> DispatchResult {
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(())
}
}
}