feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
[package]
|
||||
name = "pezpallet-assets"
|
||||
version = "29.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME asset management pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
impl-trait-for-tuples = { workspace = true }
|
||||
log = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
# Needed for various traits. In our case, `OnFinalize`.
|
||||
pezsp-runtime = { workspace = true }
|
||||
# Needed for type-safe access to storage DB.
|
||||
pezframe-support = { workspace = true }
|
||||
# `system` module provides us with all sorts of useful stuff and macros depend on it being around.
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,124 @@
|
||||
# Assets Module
|
||||
|
||||
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
|
||||
* Asset Transfer
|
||||
* Asset Destruction
|
||||
|
||||
To use it in your runtime, you need to implement the assets
|
||||
[`assets::Config`](https://docs.rs/pezpallet-assets/latest/pallet_assets/pallet/trait.Config.html).
|
||||
|
||||
The supported dispatchable functions are documented in the
|
||||
[`assets::Call`](https://docs.rs/pezpallet-assets/latest/pallet_assets/pallet/enum.Call.html) enum.
|
||||
|
||||
### Terminology
|
||||
|
||||
* **Asset issuance:** The creation of a new asset, whose total supply will belong to the account that issues the asset.
|
||||
* **Asset transfer:** The action of transferring assets from one account to another.
|
||||
* **Asset destruction:** The process of an account removing its entire holding of an asset.
|
||||
* **Fungible asset:** An asset whose units are interchangeable.
|
||||
* **Non-fungible asset:** An asset for which each unit has unique characteristics.
|
||||
|
||||
### Goals
|
||||
|
||||
The assets system in Bizinikiwi is designed to make the following possible:
|
||||
|
||||
* Issue a unique asset to its creator's account.
|
||||
* Move assets between accounts.
|
||||
* Remove an account's balance of an asset when requested by that account's owner and update the asset's total supply.
|
||||
|
||||
## Interface
|
||||
|
||||
### Dispatchable Functions
|
||||
|
||||
* `issue` - Issues the total supply of a new fungible asset to the account of the caller of the function.
|
||||
* `transfer` - Transfers an `amount` of units of fungible asset `id` from the balance of the function caller's account
|
||||
(`origin`) to a `target` account.
|
||||
* `destroy` - Destroys the entire holding of a fungible asset `id` associated with the account that called the function.
|
||||
|
||||
Please refer to the [`Call`](https://docs.rs/pezpallet-assets/latest/pallet_assets/enum.Call.html) 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`](https://docs.rs/pezpallet-assets/latest/pallet_assets/pallet/struct.Pallet.html) struct for
|
||||
details on publicly available functions.
|
||||
|
||||
## Usage
|
||||
|
||||
The following example shows how to use the Assets module in your runtime by exposing public functions to:
|
||||
|
||||
* Issue a new fungible asset for a token distribution event (airdrop).
|
||||
* Query the fungible asset holding balance of an account.
|
||||
* Query the total supply of a fungible asset that has been issued.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Import the Assets module and types and derive your runtime's configuration traits from the Assets module trait.
|
||||
|
||||
### Simple Code Snippet
|
||||
|
||||
```rust
|
||||
use pallet_assets as assets;
|
||||
use sp_runtime::ArithmeticError;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + assets::Config {}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn issue_token_airdrop(origin: OriginFor<T>) -> DispatchResult {
|
||||
let sender = ensure_signed(origin)?;
|
||||
|
||||
const ACCOUNT_ALICE: u64 = 1;
|
||||
const ACCOUNT_BOB: u64 = 2;
|
||||
const COUNT_AIRDROP_RECIPIENTS: u64 = 2;
|
||||
const TOKENS_FIXED_SUPPLY: u64 = 100;
|
||||
|
||||
ensure!(!COUNT_AIRDROP_RECIPIENTS.is_zero(), ArithmeticError::DivisionByZero);
|
||||
|
||||
let asset_id = Self::next_asset_id();
|
||||
|
||||
<NextAssetId<T>>::mutate(|asset_id| *asset_id += 1);
|
||||
<Balances<T>>::insert((asset_id, &ACCOUNT_ALICE), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS);
|
||||
<Balances<T>>::insert((asset_id, &ACCOUNT_BOB), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS);
|
||||
<TotalSupply<T>>::insert(asset_id, TOKENS_FIXED_SUPPLY);
|
||||
|
||||
Self::deposit_event(Event::Issued(asset_id, sender, TOKENS_FIXED_SUPPLY));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Assumptions
|
||||
|
||||
Below are assumptions that must be held when using this module. If any of them are violated, the behavior of this
|
||||
module is undefined.
|
||||
|
||||
* The total count of assets should be less than `Config::AssetId::max_value()`.
|
||||
|
||||
## Related Modules
|
||||
|
||||
* [`System`](https://docs.rs/pezframe-system/latest/frame_system/)
|
||||
* [`Support`](https://docs.rs/pezframe-support/latest/frame_support/)
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,62 @@
|
||||
[package]
|
||||
name = "pezpallet-assets-precompiles"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Provides precompiles for `pezpallet-assets`"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
ethereum-standards = { workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezpallet-assets = { workspace = true }
|
||||
pezpallet-revive = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
codec = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
scale-info = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-assets/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-revive/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-revive/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-assets/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-revive/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,325 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::marker::PhantomData;
|
||||
use ethereum_standards::{
|
||||
IERC20,
|
||||
IERC20::{IERC20Calls, IERC20Events},
|
||||
};
|
||||
use pezpallet_assets::{weights::WeightInfo, Call, Config, TransferFlags};
|
||||
use pezpallet_revive::precompiles::{
|
||||
alloy::{
|
||||
self,
|
||||
primitives::IntoLogData,
|
||||
sol_types::{Revert, SolCall},
|
||||
},
|
||||
AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, H160, H256,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Mean of extracting the asset id from the precompile address.
|
||||
pub trait AssetIdExtractor {
|
||||
type AssetId;
|
||||
/// Extracts the asset id from the address.
|
||||
fn asset_id_from_address(address: &[u8; 20]) -> Result<Self::AssetId, Error>;
|
||||
}
|
||||
|
||||
/// The configuration of a pezpallet-assets precompile.
|
||||
pub trait AssetPrecompileConfig {
|
||||
/// The Address matcher used by the precompile.
|
||||
const MATCHER: AddressMatcher;
|
||||
|
||||
/// The [`AssetIdExtractor`] used by the precompile.
|
||||
type AssetIdExtractor: AssetIdExtractor;
|
||||
}
|
||||
|
||||
/// An `AssetIdExtractor` that stores the asset id directly inside the address.
|
||||
pub struct InlineAssetIdExtractor;
|
||||
|
||||
impl AssetIdExtractor for InlineAssetIdExtractor {
|
||||
type AssetId = u32;
|
||||
fn asset_id_from_address(addr: &[u8; 20]) -> Result<Self::AssetId, Error> {
|
||||
let bytes: [u8; 4] = addr[0..4].try_into().expect("slice is 4 bytes; qed");
|
||||
let index = u32::from_be_bytes(bytes);
|
||||
return Ok(index.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// A precompile configuration that uses a prefix [`AddressMatcher`].
|
||||
pub struct InlineIdConfig<const PREFIX: u16>;
|
||||
|
||||
impl<const P: u16> AssetPrecompileConfig for InlineIdConfig<P> {
|
||||
const MATCHER: AddressMatcher = AddressMatcher::Prefix(core::num::NonZero::new(P).unwrap());
|
||||
type AssetIdExtractor = InlineAssetIdExtractor;
|
||||
}
|
||||
|
||||
/// An ERC20 precompile.
|
||||
pub struct ERC20<Runtime, PrecompileConfig, Instance = ()> {
|
||||
_phantom: PhantomData<(Runtime, PrecompileConfig, Instance)>,
|
||||
}
|
||||
|
||||
impl<Runtime, PrecompileConfig, Instance: 'static> Precompile
|
||||
for ERC20<Runtime, PrecompileConfig, Instance>
|
||||
where
|
||||
PrecompileConfig: AssetPrecompileConfig,
|
||||
Runtime: crate::Config<Instance> + pezpallet_revive::Config,
|
||||
<<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
|
||||
Into<<Runtime as Config<Instance>>::AssetId>,
|
||||
Call<Runtime, Instance>: Into<<Runtime as pezpallet_revive::Config>::RuntimeCall>,
|
||||
alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
|
||||
|
||||
// Note can't use From as it's not implemented for alloy::primitives::U256 for unsigned types
|
||||
alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
|
||||
{
|
||||
type T = Runtime;
|
||||
type Interface = IERC20::IERC20Calls;
|
||||
const MATCHER: AddressMatcher = PrecompileConfig::MATCHER;
|
||||
const HAS_CONTRACT_INFO: bool = false;
|
||||
|
||||
fn call(
|
||||
address: &[u8; 20],
|
||||
input: &Self::Interface,
|
||||
env: &mut impl Ext<T = Self::T>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let asset_id = PrecompileConfig::AssetIdExtractor::asset_id_from_address(address)?.into();
|
||||
|
||||
match input {
|
||||
IERC20Calls::transfer(_) | IERC20Calls::approve(_) | IERC20Calls::transferFrom(_)
|
||||
if env.is_read_only() =>
|
||||
Err(Error::Error(pezpallet_revive::Error::<Self::T>::StateChangeDenied.into())),
|
||||
|
||||
IERC20Calls::transfer(call) => Self::transfer(asset_id, call, env),
|
||||
IERC20Calls::totalSupply(_) => Self::total_supply(asset_id, env),
|
||||
IERC20Calls::balanceOf(call) => Self::balance_of(asset_id, call, env),
|
||||
IERC20Calls::allowance(call) => Self::allowance(asset_id, call, env),
|
||||
IERC20Calls::approve(call) => Self::approve(asset_id, call, env),
|
||||
IERC20Calls::transferFrom(call) => Self::transfer_from(asset_id, call, env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ERR_INVALID_CALLER: &str = "Invalid caller";
|
||||
const ERR_BALANCE_CONVERSION_FAILED: &str = "Balance conversion failed";
|
||||
|
||||
impl<Runtime, PrecompileConfig, Instance: 'static> ERC20<Runtime, PrecompileConfig, Instance>
|
||||
where
|
||||
PrecompileConfig: AssetPrecompileConfig,
|
||||
Runtime: crate::Config<Instance> + pezpallet_revive::Config,
|
||||
<<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
|
||||
Into<<Runtime as Config<Instance>>::AssetId>,
|
||||
Call<Runtime, Instance>: Into<<Runtime as pezpallet_revive::Config>::RuntimeCall>,
|
||||
alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
|
||||
|
||||
// Note can't use From as it's not implemented for alloy::primitives::U256 for unsigned types
|
||||
alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
|
||||
{
|
||||
/// Get the caller as an `H160` address.
|
||||
fn caller(env: &mut impl Ext<T = Runtime>) -> Result<H160, Error> {
|
||||
env.caller()
|
||||
.account_id()
|
||||
.map(<Runtime as pezpallet_revive::Config>::AddressMapper::to_address)
|
||||
.map_err(|_| Error::Revert(Revert { reason: ERR_INVALID_CALLER.into() }))
|
||||
}
|
||||
|
||||
/// Convert a `U256` value to the balance type of the pallet.
|
||||
fn to_balance(
|
||||
value: alloy::primitives::U256,
|
||||
) -> Result<<Runtime as Config<Instance>>::Balance, Error> {
|
||||
value
|
||||
.try_into()
|
||||
.map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
|
||||
}
|
||||
|
||||
/// Convert a balance to a `U256` value.
|
||||
/// Note this is needed cause From is not implemented for unsigned integer types
|
||||
fn to_u256(
|
||||
value: <Runtime as Config<Instance>>::Balance,
|
||||
) -> Result<alloy::primitives::U256, Error> {
|
||||
alloy::primitives::U256::try_from(value)
|
||||
.map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
|
||||
}
|
||||
|
||||
/// Deposit an event to the runtime.
|
||||
fn deposit_event(env: &mut impl Ext<T = Runtime>, event: IERC20Events) -> Result<(), Error> {
|
||||
let (topics, data) = event.into_log_data().split();
|
||||
let topics = topics.into_iter().map(|v| H256(v.0)).collect::<Vec<_>>();
|
||||
env.gas_meter_mut().charge(RuntimeCosts::DepositEvent {
|
||||
num_topic: topics.len() as u32,
|
||||
len: topics.len() as u32,
|
||||
})?;
|
||||
env.deposit_event(topics, data.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute the transfer call.
|
||||
fn transfer(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::transferCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer())?;
|
||||
|
||||
let from = Self::caller(env)?;
|
||||
let dest = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(
|
||||
&call.to.into_array().into(),
|
||||
);
|
||||
|
||||
let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
|
||||
pezpallet_assets::Pallet::<Runtime, Instance>::do_transfer(
|
||||
asset_id,
|
||||
&<Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&from),
|
||||
&dest,
|
||||
Self::to_balance(call.value)?,
|
||||
None,
|
||||
f,
|
||||
)?;
|
||||
|
||||
Self::deposit_event(
|
||||
env,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: from.0.into(),
|
||||
to: call.to,
|
||||
value: call.value,
|
||||
}),
|
||||
)?;
|
||||
|
||||
return Ok(IERC20::transferCall::abi_encode_returns(&true));
|
||||
}
|
||||
|
||||
/// Execute the total supply call.
|
||||
fn total_supply(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
use pezframe_support::traits::fungibles::Inspect;
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::total_issuance())?;
|
||||
|
||||
let value =
|
||||
Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id))?;
|
||||
return Ok(IERC20::totalSupplyCall::abi_encode_returns(&value));
|
||||
}
|
||||
|
||||
/// Execute the balance_of call.
|
||||
fn balance_of(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::balanceOfCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::balance())?;
|
||||
let account = call.account.into_array().into();
|
||||
let account = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&account);
|
||||
let value =
|
||||
Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::balance(asset_id, account))?;
|
||||
return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
|
||||
}
|
||||
|
||||
/// Execute the allowance call.
|
||||
fn allowance(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::allowanceCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::allowance())?;
|
||||
use pezframe_support::traits::fungibles::approvals::Inspect;
|
||||
let owner = call.owner.into_array().into();
|
||||
let owner = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&owner);
|
||||
|
||||
let spender = call.spender.into_array().into();
|
||||
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
||||
let value = Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::allowance(
|
||||
asset_id, &owner, &spender,
|
||||
))?;
|
||||
|
||||
return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
|
||||
}
|
||||
|
||||
/// Execute the approve call.
|
||||
fn approve(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::approveCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::approve_transfer())?;
|
||||
let owner = Self::caller(env)?;
|
||||
let spender = call.spender.into_array().into();
|
||||
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
||||
|
||||
pezpallet_assets::Pallet::<Runtime, Instance>::do_approve_transfer(
|
||||
asset_id,
|
||||
&<Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&owner),
|
||||
&spender,
|
||||
Self::to_balance(call.value)?,
|
||||
)?;
|
||||
|
||||
Self::deposit_event(
|
||||
env,
|
||||
IERC20Events::Approval(IERC20::Approval {
|
||||
owner: owner.0.into(),
|
||||
spender: call.spender,
|
||||
value: call.value,
|
||||
}),
|
||||
)?;
|
||||
|
||||
return Ok(IERC20::approveCall::abi_encode_returns(&true));
|
||||
}
|
||||
|
||||
/// Execute the transfer_from call.
|
||||
fn transfer_from(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::transferFromCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer_approved())?;
|
||||
let spender = Self::caller(env)?;
|
||||
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
||||
|
||||
let from = call.from.into_array().into();
|
||||
let from = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&from);
|
||||
|
||||
let to = call.to.into_array().into();
|
||||
let to = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&to);
|
||||
|
||||
pezpallet_assets::Pallet::<Runtime, Instance>::do_transfer_approved(
|
||||
asset_id,
|
||||
&from,
|
||||
&spender,
|
||||
&to,
|
||||
Self::to_balance(call.value)?,
|
||||
)?;
|
||||
|
||||
Self::deposit_event(
|
||||
env,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: call.from,
|
||||
to: call.to,
|
||||
value: call.value,
|
||||
}),
|
||||
)?;
|
||||
|
||||
return Ok(IERC20::transferFromCall::abi_encode_returns(&true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Tests mock for `pezpallet-assets-freezer`.
|
||||
|
||||
pub use super::*;
|
||||
use pezframe_support::{derive_impl, traits::AsEnsureOriginWithArg};
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
#[pezframe_support::runtime]
|
||||
mod runtime {
|
||||
#[runtime::runtime]
|
||||
#[runtime::derive(
|
||||
RuntimeCall,
|
||||
RuntimeEvent,
|
||||
RuntimeError,
|
||||
RuntimeOrigin,
|
||||
RuntimeTask,
|
||||
RuntimeHoldReason,
|
||||
RuntimeFreezeReason
|
||||
)]
|
||||
pub struct Test;
|
||||
|
||||
#[runtime::pezpallet_index(0)]
|
||||
pub type System = pezframe_system;
|
||||
#[runtime::pezpallet_index(10)]
|
||||
pub type Balances = pezpallet_balances;
|
||||
#[runtime::pezpallet_index(20)]
|
||||
pub type Assets = pezpallet_assets;
|
||||
#[runtime::pezpallet_index(21)]
|
||||
pub type Revive = pezpallet_revive;
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig as pezpallet_balances::DefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_assets::config_preludes::TestDefaultConfig as pezpallet_assets::DefaultConfig)]
|
||||
impl pezpallet_assets::Config for Test {
|
||||
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<u64>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type Currency = Balances;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_revive::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_revive::Config for Test {
|
||||
type AddressMapper = pezpallet_revive::TestAccountMapper<Self>;
|
||||
type Balance = u64;
|
||||
type Currency = Balances;
|
||||
type Precompiles = (ERC20<Self, InlineIdConfig<0x0120>>,);
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = RuntimeGenesisConfig {
|
||||
assets: pezpallet_assets::GenesisConfig {
|
||||
assets: vec![(1, 0, true, 1)],
|
||||
metadata: vec![],
|
||||
accounts: vec![(1, 1, 100)],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
},
|
||||
system: Default::default(),
|
||||
balances: Default::default(),
|
||||
revive: Default::default(),
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
let mut ext: pezsp_io::TestExternalities = t.into();
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
alloy::hex,
|
||||
mock::{new_test_ext, Assets, Balances, RuntimeEvent, RuntimeOrigin, System, Test},
|
||||
};
|
||||
use alloy::primitives::U256;
|
||||
use pezframe_support::{assert_ok, traits::Currency};
|
||||
use pezpallet_revive::ExecConfig;
|
||||
use pezsp_core::H160;
|
||||
use pezsp_runtime::Weight;
|
||||
|
||||
fn assert_contract_event(contract: H160, event: IERC20Events) {
|
||||
let (topics, data) = event.into_log_data().split();
|
||||
let topics = topics.into_iter().map(|v| H256(v.0)).collect::<Vec<_>>();
|
||||
System::assert_has_event(RuntimeEvent::Revive(pezpallet_revive::Event::ContractEmitted {
|
||||
contract,
|
||||
data: data.to_vec(),
|
||||
topics,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asset_id_extractor_works() {
|
||||
let address: [u8; 20] =
|
||||
hex::const_decode_to_array(b"0000053900000000000000000000000001200000").unwrap();
|
||||
assert!(InlineIdConfig::<0x0120>::MATCHER.matches(&address));
|
||||
assert_eq!(
|
||||
<InlineIdConfig<0x0120> as AssetPrecompileConfig>::AssetIdExtractor::asset_id_from_address(
|
||||
&address
|
||||
)
|
||||
.unwrap(),
|
||||
1337u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn precompile_transfer_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr = H160::from(
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap(),
|
||||
);
|
||||
|
||||
let from = 123456789;
|
||||
let to = 987654321;
|
||||
|
||||
Balances::make_free_balance_be(&from, 100);
|
||||
Balances::make_free_balance_be(&to, 100);
|
||||
|
||||
let from_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&from);
|
||||
let to_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&to);
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, from, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(from), asset_id, from, 100));
|
||||
|
||||
let data =
|
||||
IERC20::transferCall { to: to_addr.0.into(), value: U256::from(10) }.abi_encode();
|
||||
|
||||
pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(from),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
|
||||
assert_contract_event(
|
||||
asset_addr,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: from_addr.0.into(),
|
||||
to: to_addr.0.into(),
|
||||
value: U256::from(10),
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(Assets::balance(asset_id, from), 90);
|
||||
assert_eq!(Assets::balance(asset_id, to), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_supply_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr =
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap();
|
||||
|
||||
let owner = 123456789;
|
||||
|
||||
Balances::make_free_balance_be(&owner, 100);
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset_id, owner, 1000));
|
||||
|
||||
let data = IERC20::totalSupplyCall {}.abi_encode();
|
||||
|
||||
let data = pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let ret = IERC20::totalSupplyCall::abi_decode_returns(&data).unwrap();
|
||||
assert_eq!(ret, U256::from(1000));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_of_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr =
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap();
|
||||
|
||||
let owner = 123456789;
|
||||
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset_id, owner, 1000));
|
||||
|
||||
let account = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&owner).0.into();
|
||||
let data = IERC20::balanceOfCall { account }.abi_encode();
|
||||
|
||||
let data = pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let ret = IERC20::balanceOfCall::abi_decode_returns(&data).unwrap();
|
||||
assert_eq!(ret, U256::from(1000));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_works() {
|
||||
use pezframe_support::traits::fungibles::approvals::Inspect;
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr = H160::from(
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap(),
|
||||
);
|
||||
|
||||
let owner = 123456789;
|
||||
let spender = 987654321;
|
||||
let other = 1122334455;
|
||||
|
||||
Balances::make_free_balance_be(&owner, 100);
|
||||
Balances::make_free_balance_be(&spender, 100);
|
||||
Balances::make_free_balance_be(&other, 100);
|
||||
|
||||
let owner_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&owner);
|
||||
let spender_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&spender);
|
||||
let other_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&other);
|
||||
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset_id, owner, 100));
|
||||
|
||||
let data = IERC20::approveCall { spender: spender_addr.0.into(), value: U256::from(25) }
|
||||
.abi_encode();
|
||||
|
||||
pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
|
||||
assert_contract_event(
|
||||
asset_addr,
|
||||
IERC20Events::Approval(IERC20::Approval {
|
||||
owner: owner_addr.0.into(),
|
||||
spender: spender_addr.0.into(),
|
||||
value: U256::from(25),
|
||||
}),
|
||||
);
|
||||
|
||||
let data =
|
||||
IERC20::allowanceCall { owner: owner_addr.0.into(), spender: spender_addr.0.into() }
|
||||
.abi_encode();
|
||||
|
||||
let data = pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let ret = IERC20::allowanceCall::abi_decode_returns(&data).unwrap();
|
||||
assert_eq!(ret, U256::from(25));
|
||||
|
||||
let data = IERC20::transferFromCall {
|
||||
from: owner_addr.0.into(),
|
||||
to: other_addr.0.into(),
|
||||
value: U256::from(10),
|
||||
}
|
||||
.abi_encode();
|
||||
|
||||
pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(spender),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
assert_eq!(Assets::balance(asset_id, owner), 90);
|
||||
assert_eq!(Assets::allowance(asset_id, &owner, &spender), 15);
|
||||
assert_eq!(Assets::balance(asset_id, other), 10);
|
||||
|
||||
assert_contract_event(
|
||||
asset_addr,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: owner_addr.0.into(),
|
||||
to: other_addr.0.into(),
|
||||
value: U256::from(10),
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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 benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
use pezframe_benchmarking::{
|
||||
v1::{
|
||||
account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError,
|
||||
},
|
||||
BenchmarkResult,
|
||||
};
|
||||
use pezframe_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable};
|
||||
use pezframe_system::RawOrigin as SystemOrigin;
|
||||
use pezsp_runtime::{traits::Bounded, Weight};
|
||||
|
||||
use crate::Pallet as Assets;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
const MIN_BALANCE: u32 = 1;
|
||||
|
||||
fn default_asset_id<T: Config<I>, I: 'static>() -> T::AssetIdParameter {
|
||||
T::BenchmarkHelper::create_asset_id_parameter(0)
|
||||
}
|
||||
|
||||
fn create_default_asset<T: Config<I>, I: 'static>(
|
||||
is_sufficient: bool,
|
||||
) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let root = SystemOrigin::Root.into();
|
||||
assert!(Assets::<T, I>::force_create(
|
||||
root,
|
||||
asset_id.clone(),
|
||||
caller_lookup.clone(),
|
||||
is_sufficient,
|
||||
MIN_BALANCE.into(),
|
||||
)
|
||||
.is_ok());
|
||||
(asset_id, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn create_default_reserves<T: Config<I>, I: 'static>(
|
||||
) -> (T::AssetIdParameter, T::AccountId, Vec<T::ReserveData>) {
|
||||
// create asset
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
// build max number of reserves
|
||||
let mut reserves = Vec::<T::ReserveData>::new();
|
||||
for i in 0..MAX_RESERVES {
|
||||
reserves.push(T::BenchmarkHelper::create_reserve_id_parameter(i));
|
||||
}
|
||||
(asset_id, caller, reserves)
|
||||
}
|
||||
|
||||
pub fn create_default_minted_asset<T: Config<I>, I: 'static>(
|
||||
is_sufficient: bool,
|
||||
amount: T::Balance,
|
||||
) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(is_sufficient);
|
||||
if !is_sufficient {
|
||||
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance());
|
||||
}
|
||||
assert!(Assets::<T, I>::mint(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
caller_lookup.clone(),
|
||||
amount,
|
||||
)
|
||||
.is_ok());
|
||||
(asset_id, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn swap_is_sufficient<T: Config<I>, I: 'static>(s: &mut bool) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
Asset::<T, I>::mutate(&asset_id.into(), |maybe_a| {
|
||||
if let Some(ref mut a) = maybe_a {
|
||||
core::mem::swap(s, &mut a.is_sufficient)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_sufficients<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let origin = SystemOrigin::Signed(minter);
|
||||
let mut s = true;
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
for i in 0..n {
|
||||
let target = account("sufficient", i, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
assert!(Assets::<T, I>::mint(
|
||||
origin.clone().into(),
|
||||
asset_id.clone(),
|
||||
target_lookup,
|
||||
100u32.into()
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
}
|
||||
|
||||
fn add_approvals<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let _ = T::Currency::deposit_creating(
|
||||
&minter,
|
||||
T::ApprovalDeposit::get() * n.into() + T::Currency::minimum_balance(),
|
||||
);
|
||||
let minter_lookup = T::Lookup::unlookup(minter.clone());
|
||||
let origin = SystemOrigin::Signed(minter);
|
||||
Assets::<T, I>::mint(
|
||||
origin.clone().into(),
|
||||
asset_id.clone(),
|
||||
minter_lookup,
|
||||
(100 * (n + 1)).into(),
|
||||
)
|
||||
.unwrap();
|
||||
let enough = T::Currency::minimum_balance();
|
||||
for i in 0..n {
|
||||
let target = account("approval", i, SEED);
|
||||
T::Currency::make_free_balance_be(&target, enough);
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
Assets::<T, I>::approve_transfer(
|
||||
origin.clone().into(),
|
||||
asset_id.clone(),
|
||||
target_lookup,
|
||||
100u32.into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
|
||||
pezframe_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
fn assert_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
|
||||
pezframe_system::Pallet::<T>::assert_has_event(generic_event.into());
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
create {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let origin = T::CreateOrigin::try_successful_origin(&asset_id.clone().into())
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let caller = T::CreateOrigin::ensure_origin(origin.clone(), &asset_id.clone().into()).unwrap();
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
}: _<T::RuntimeOrigin>(origin, asset_id.clone(), caller_lookup, 1u32.into())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Created { asset_id: asset_id.into(), creator: caller.clone(), owner: caller }.into());
|
||||
}
|
||||
|
||||
force_create {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
}: _(SystemOrigin::Root, asset_id.clone(), caller_lookup, true, 1u32.into())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ForceCreated { asset_id: asset_id.into(), owner: caller }.into());
|
||||
}
|
||||
|
||||
start_destroy {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::DestructionStarted { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
destroy_accounts {
|
||||
let c in 0 .. T::RemoveItemsLimit::get();
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
add_sufficients::<T, I>(caller.clone(), c);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id.clone())?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AccountsDestroyed {
|
||||
asset_id: asset_id.into(),
|
||||
accounts_destroyed: c,
|
||||
accounts_remaining: 0,
|
||||
}.into());
|
||||
}
|
||||
|
||||
destroy_approvals {
|
||||
let a in 0 .. T::RemoveItemsLimit::get();
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
add_approvals::<T, I>(caller.clone(), a);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id.clone())?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovalsDestroyed {
|
||||
asset_id: asset_id.into(),
|
||||
approvals_destroyed: a,
|
||||
approvals_remaining: 0,
|
||||
}.into());
|
||||
}
|
||||
|
||||
finish_destroy {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(true);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id.clone())?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Destroyed {
|
||||
asset_id: asset_id.into(),
|
||||
}.into()
|
||||
);
|
||||
}
|
||||
|
||||
mint {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(true);
|
||||
let amount = T::Balance::from(100u32);
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Issued { asset_id: asset_id.into(), owner: caller, amount }.into());
|
||||
}
|
||||
|
||||
burn {
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Burned { asset_id: asset_id.into(), owner: caller, balance: amount }.into());
|
||||
}
|
||||
|
||||
transfer {
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), target_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into());
|
||||
}
|
||||
|
||||
transfer_keep_alive {
|
||||
let mint_amount = T::Balance::from(200u32);
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, mint_amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), target_lookup, amount)
|
||||
verify {
|
||||
assert!(pezframe_system::Pallet::<T>::account_exists(&caller));
|
||||
assert_last_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into());
|
||||
}
|
||||
|
||||
force_transfer {
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, target_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(
|
||||
Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into()
|
||||
);
|
||||
}
|
||||
|
||||
freeze {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Frozen { asset_id: asset_id.into(), who: caller }.into());
|
||||
}
|
||||
|
||||
thaw {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
Assets::<T, I>::freeze(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
caller_lookup.clone(),
|
||||
)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Thawed { asset_id: asset_id.into(), who: caller }.into());
|
||||
}
|
||||
|
||||
freeze_asset {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetFrozen { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
thaw_asset {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetThawed { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
transfer_ownership {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), target_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::OwnerChanged { asset_id: asset_id.into(), owner: target }.into());
|
||||
}
|
||||
|
||||
set_team {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
let target0 = T::Lookup::unlookup(account("target", 0, SEED));
|
||||
let target1 = T::Lookup::unlookup(account("target", 1, SEED));
|
||||
let target2 = T::Lookup::unlookup(account("target", 2, SEED));
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), target0, target1, target2)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::TeamChanged {
|
||||
asset_id: asset_id.into(),
|
||||
issuer: account("target", 0, SEED),
|
||||
admin: account("target", 1, SEED),
|
||||
freezer: account("target", 2, SEED),
|
||||
}.into());
|
||||
}
|
||||
|
||||
set_reserves {
|
||||
let (asset_id, caller, reserves) = create_default_reserves::<T, I>();
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), reserves.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ReservesUpdated { asset_id: asset_id.into(), reserves: reserves }.into());
|
||||
}
|
||||
|
||||
set_metadata {
|
||||
let n in 0 .. T::StringLimit::get();
|
||||
let s in 0 .. T::StringLimit::get();
|
||||
|
||||
let name = vec![0u8; n as usize];
|
||||
let symbol = vec![0u8; s as usize];
|
||||
let decimals = 12;
|
||||
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), name.clone(), symbol.clone(), decimals)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into());
|
||||
}
|
||||
|
||||
clear_metadata {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
let dummy = vec![0u8; T::StringLimit::get() as usize];
|
||||
let origin = SystemOrigin::Signed(caller.clone()).into();
|
||||
Assets::<T, I>::set_metadata(origin, asset_id.clone(), dummy.clone(), dummy, 12)?;
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataCleared { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
force_set_metadata {
|
||||
let n in 0 .. T::StringLimit::get();
|
||||
let s in 0 .. T::StringLimit::get();
|
||||
|
||||
let name = vec![0u8; n as usize];
|
||||
let symbol = vec![0u8; s as usize];
|
||||
let decimals = 12;
|
||||
|
||||
let (asset_id, _, _) = create_default_asset::<T, I>(true);
|
||||
|
||||
let origin =
|
||||
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let call = Call::<T, I>::force_set_metadata {
|
||||
id: asset_id.clone(),
|
||||
name: name.clone(),
|
||||
symbol: symbol.clone(),
|
||||
decimals,
|
||||
is_frozen: false,
|
||||
};
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into());
|
||||
}
|
||||
|
||||
force_clear_metadata {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
let dummy = vec![0u8; T::StringLimit::get() as usize];
|
||||
let origin = SystemOrigin::Signed(caller).into();
|
||||
Assets::<T, I>::set_metadata(origin, asset_id.clone(), dummy.clone(), dummy, 12)?;
|
||||
|
||||
let origin =
|
||||
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let call = Call::<T, I>::force_clear_metadata { id: asset_id.clone() };
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataCleared { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
force_asset_status {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(true);
|
||||
|
||||
let origin =
|
||||
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let call = Call::<T, I>::force_asset_status {
|
||||
id: asset_id.clone(),
|
||||
owner: caller_lookup.clone(),
|
||||
issuer: caller_lookup.clone(),
|
||||
admin: caller_lookup.clone(),
|
||||
freezer: caller_lookup,
|
||||
min_balance: 100u32.into(),
|
||||
is_sufficient: true,
|
||||
is_frozen: false,
|
||||
};
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetStatusChanged { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
approve_transfer {
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), delegate_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovedTransfer { asset_id: asset_id.into(), source: caller, delegate, amount }.into());
|
||||
}
|
||||
|
||||
transfer_approved {
|
||||
let (asset_id, owner, owner_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&owner, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
whitelist_account!(delegate);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
let origin = SystemOrigin::Signed(owner.clone()).into();
|
||||
Assets::<T, I>::approve_transfer(origin, asset_id.clone(), delegate_lookup, amount)?;
|
||||
|
||||
let dest: T::AccountId = account("dest", 0, SEED);
|
||||
let dest_lookup = T::Lookup::unlookup(dest.clone());
|
||||
}: _(SystemOrigin::Signed(delegate.clone()), asset_id.clone(), owner_lookup, dest_lookup, amount)
|
||||
verify {
|
||||
assert!(T::Currency::reserved_balance(&owner).is_zero());
|
||||
assert_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: owner, to: dest, amount }.into());
|
||||
}
|
||||
|
||||
cancel_approval {
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
let origin = SystemOrigin::Signed(caller.clone()).into();
|
||||
Assets::<T, I>::approve_transfer(origin, asset_id.clone(), delegate_lookup.clone(), amount)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), delegate_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into());
|
||||
}
|
||||
|
||||
force_cancel_approval {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
let origin = SystemOrigin::Signed(caller.clone()).into();
|
||||
Assets::<T, I>::approve_transfer(origin, asset_id.clone(), delegate_lookup.clone(), amount)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, delegate_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into());
|
||||
}
|
||||
|
||||
set_min_balance {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(false);
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), 50u32.into())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetMinBalanceChanged { asset_id: asset_id.into(), new_min_balance: 50u32.into() }.into());
|
||||
}
|
||||
|
||||
touch {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
T::Currency::make_free_balance_be(&new_account, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(!Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(new_account.clone()), asset_id.clone())
|
||||
verify {
|
||||
assert!(Account::<T, I>::contains_key(asset_id.into(), &new_account));
|
||||
}
|
||||
|
||||
touch_other {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
let new_account_lookup = T::Lookup::unlookup(new_account.clone());
|
||||
T::Currency::make_free_balance_be(&asset_owner, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(!Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(asset_owner.clone()), asset_id.clone(), new_account_lookup)
|
||||
verify {
|
||||
assert!(Account::<T, I>::contains_key(asset_id.into(), &new_account));
|
||||
}
|
||||
|
||||
refund {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
T::Currency::make_free_balance_be(&new_account, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(Assets::<T, I>::touch(
|
||||
SystemOrigin::Signed(new_account.clone()).into(),
|
||||
asset_id.clone()
|
||||
).is_ok());
|
||||
// `touch` should reserve balance of the caller according to the `AssetAccountDeposit` amount...
|
||||
assert_eq!(T::Currency::reserved_balance(&new_account), T::AssetAccountDeposit::get());
|
||||
// ...and also create an `Account` entry.
|
||||
assert!(Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(new_account.clone()), asset_id, true)
|
||||
verify {
|
||||
// `refund`ing should of course repatriate the reserve
|
||||
assert!(T::Currency::reserved_balance(&new_account).is_zero());
|
||||
}
|
||||
|
||||
refund_other {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
let new_account_lookup = T::Lookup::unlookup(new_account.clone());
|
||||
T::Currency::make_free_balance_be(&asset_owner, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(Assets::<T, I>::touch_other(
|
||||
SystemOrigin::Signed(asset_owner.clone()).into(),
|
||||
asset_id.clone(),
|
||||
new_account_lookup.clone()
|
||||
).is_ok());
|
||||
// `touch` should reserve balance of the caller according to the `AssetAccountDeposit` amount...
|
||||
assert_eq!(T::Currency::reserved_balance(&asset_owner), T::AssetAccountDeposit::get());
|
||||
assert!(Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(asset_owner.clone()), asset_id, new_account_lookup.clone())
|
||||
verify {
|
||||
// this should repatriate the reserved balance of the freezer
|
||||
assert!(T::Currency::reserved_balance(&asset_owner).is_zero());
|
||||
}
|
||||
|
||||
block {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Blocked { asset_id: asset_id.into(), who: caller }.into());
|
||||
}
|
||||
|
||||
transfer_all {
|
||||
let amount = T::Balance::from(2 * MIN_BALANCE);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), target_lookup, false)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into());
|
||||
}
|
||||
|
||||
total_issuance {
|
||||
use pezframe_support::traits::fungibles::Inspect;
|
||||
let (asset_id, _, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
let amount;
|
||||
}: {
|
||||
amount = Pallet::<T, I>::total_issuance(asset_id.into());
|
||||
} verify {
|
||||
assert_eq!(amount, 100u32.into());
|
||||
}
|
||||
|
||||
balance {
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
let amount;
|
||||
}: {
|
||||
amount = Pallet::<T, I>::balance(asset_id.into(), caller);
|
||||
} verify {
|
||||
assert_eq!(amount, 100u32.into());
|
||||
}
|
||||
|
||||
allowance {
|
||||
use pezframe_support::traits::fungibles::approvals::Inspect;
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
add_approvals::<T, I>(caller.clone(), 1);
|
||||
let delegate: T::AccountId = account("approval", 0, SEED);
|
||||
let amount;
|
||||
}: {
|
||||
amount = Pallet::<T, I>::allowance(asset_id.into(), &caller, &delegate);
|
||||
} verify {
|
||||
assert_eq!(amount, 100u32.into());
|
||||
}
|
||||
|
||||
migration_v2_foreign_asset_set_reserve_weight {
|
||||
let (id, _, _) = create_default_asset::<T, I>(true);
|
||||
let id: <T as pallet::Config<I>>::AssetId = id.into();
|
||||
let reserve = T::BenchmarkHelper::create_reserve_id_parameter(42);
|
||||
}: {
|
||||
let asset_id = Asset::<T, I>::iter_keys().next()
|
||||
.ok_or_else(|| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
|
||||
assert_eq!(id, asset_id);
|
||||
Pallet::<T, I>::unchecked_update_reserves(asset_id, vec![reserve.clone()]).unwrap();
|
||||
}
|
||||
verify {
|
||||
assert_eq!(Reserves::<T, I>::get(id)[0], reserve);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Datatype for easy mutation of the extra "sidecar" data.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A mutator type allowing inspection and possible modification of the extra "sidecar" data.
|
||||
///
|
||||
/// This may be used as a `Deref` for the pallet's extra data. If mutated (using `DerefMut`), then
|
||||
/// any uncommitted changes (see `commit` function) will be automatically committed to storage when
|
||||
/// dropped. Changes, even after committed, may be reverted to their original values with the
|
||||
/// `revert` function.
|
||||
pub struct ExtraMutator<T: Config<I>, I: 'static = ()> {
|
||||
id: T::AssetId,
|
||||
who: T::AccountId,
|
||||
original: T::Extra,
|
||||
pending: Option<T::Extra>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Drop for ExtraMutator<T, I> {
|
||||
fn drop(&mut self) {
|
||||
debug_assert!(self.commit().is_ok(), "attempt to write to non-existent asset account");
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> core::ops::Deref for ExtraMutator<T, I> {
|
||||
type Target = T::Extra;
|
||||
fn deref(&self) -> &T::Extra {
|
||||
match self.pending {
|
||||
Some(ref value) => value,
|
||||
None => &self.original,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> core::ops::DerefMut for ExtraMutator<T, I> {
|
||||
fn deref_mut(&mut self) -> &mut T::Extra {
|
||||
if self.pending.is_none() {
|
||||
self.pending = Some(self.original.clone());
|
||||
}
|
||||
self.pending.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> ExtraMutator<T, I> {
|
||||
pub(super) fn maybe_new(
|
||||
id: T::AssetId,
|
||||
who: impl core::borrow::Borrow<T::AccountId>,
|
||||
) -> Option<ExtraMutator<T, I>> {
|
||||
if let Some(a) = Account::<T, I>::get(&id, who.borrow()) {
|
||||
Some(ExtraMutator::<T, I> {
|
||||
id,
|
||||
who: who.borrow().clone(),
|
||||
original: a.extra,
|
||||
pending: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit any changes to storage.
|
||||
pub fn commit(&mut self) -> Result<(), ()> {
|
||||
if let Some(extra) = self.pending.take() {
|
||||
Account::<T, I>::try_mutate(&self.id, &self.who, |maybe_account| {
|
||||
maybe_account.as_mut().ok_or(()).map(|account| account.extra = extra)
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Revert any changes, even those already committed by `self` and drop self.
|
||||
pub fn revert(mut self) -> Result<(), ()> {
|
||||
self.pending = None;
|
||||
Account::<T, I>::try_mutate(&self.id, &self.who, |maybe_account| {
|
||||
maybe_account
|
||||
.as_mut()
|
||||
.ok_or(())
|
||||
.map(|account| account.extra = self.original.clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,360 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Implementations for fungibles trait.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use pezframe_support::{
|
||||
defensive,
|
||||
traits::tokens::{
|
||||
Fortitude,
|
||||
Precision::{self, BestEffort},
|
||||
Preservation::{self, Expendable},
|
||||
Provenance::{self, Minted},
|
||||
},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T, I>::get(asset).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T, I>::get(asset).map(|x| x.min_balance).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
|
||||
Pallet::<T, I>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn total_balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
|
||||
Pallet::<T, I>::balance(asset.clone(), who)
|
||||
.saturating_add(T::Holder::balance_on_hold(asset, who).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn reducible_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
preservation: Preservation,
|
||||
_: Fortitude,
|
||||
) -> Self::Balance {
|
||||
Pallet::<T, I>::reducible_balance(asset, who, !matches!(preservation, Expendable))
|
||||
.unwrap_or(Zero::zero())
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence {
|
||||
Pallet::<T, I>::can_increase(asset, who, amount, provenance == Minted)
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance> {
|
||||
Pallet::<T, I>::can_decrease(asset, who, amount, false)
|
||||
}
|
||||
|
||||
fn asset_exists(asset: Self::AssetId) -> bool {
|
||||
Asset::<T, I>::contains_key(asset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T, I> {
|
||||
fn done_mint_into(
|
||||
asset_id: Self::AssetId,
|
||||
beneficiary: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Issued { asset_id, owner: beneficiary.clone(), amount })
|
||||
}
|
||||
|
||||
fn done_burn_from(
|
||||
asset_id: Self::AssetId,
|
||||
target: &<T as SystemConfig>::AccountId,
|
||||
balance: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Burned { asset_id, owner: target.clone(), balance });
|
||||
}
|
||||
|
||||
fn done_transfer(
|
||||
asset_id: Self::AssetId,
|
||||
source: &<T as SystemConfig>::AccountId,
|
||||
dest: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Transferred {
|
||||
asset_id,
|
||||
from: source.clone(),
|
||||
to: dest.clone(),
|
||||
amount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Balanced<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
type OnDropCredit = fungibles::DecreaseIssuance<T::AccountId, Self>;
|
||||
type OnDropDebt = fungibles::IncreaseIssuance<T::AccountId, Self>;
|
||||
|
||||
fn done_deposit(
|
||||
asset_id: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Deposited { asset_id, who: who.clone(), amount })
|
||||
}
|
||||
|
||||
fn done_withdraw(
|
||||
asset_id: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Withdrawn { asset_id, who: who.clone(), amount })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T, I> {
|
||||
fn handle_raw_dust(_: Self::AssetId, _: Self::Balance) {}
|
||||
fn handle_dust(_: fungibles::Dust<T::AccountId, Self>) {
|
||||
defensive!("`decrease_balance` and `increase_balance` have non-default impls; nothing else calls this; qed");
|
||||
}
|
||||
fn write_balance(
|
||||
_: Self::AssetId,
|
||||
_: &T::AccountId,
|
||||
_: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, DispatchError> {
|
||||
defensive!("write_balance is not used if other functions are impl'd");
|
||||
Err(DispatchError::Unavailable)
|
||||
}
|
||||
fn set_total_issuance(id: T::AssetId, amount: Self::Balance) {
|
||||
Asset::<T, I>::mutate_exists(id, |maybe_asset| {
|
||||
if let Some(ref mut asset) = maybe_asset {
|
||||
asset.supply = amount
|
||||
}
|
||||
});
|
||||
}
|
||||
fn decrease_balance(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
_: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags {
|
||||
keep_alive: preservation != Expendable,
|
||||
best_effort: precision == BestEffort,
|
||||
};
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
|
||||
}
|
||||
fn increase_balance(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
_: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Self::increase_balance(asset, who, amount, |_| Ok(()))?;
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
// TODO: #13196 implement deactivate/reactivate once we have inactive balance tracking.
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Create<T::AccountId> for Pallet<T, I> {
|
||||
fn create(
|
||||
id: T::AssetId,
|
||||
admin: T::AccountId,
|
||||
is_sufficient: bool,
|
||||
min_balance: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
Self::do_force_create(id, admin, is_sufficient, min_balance)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Destroy<T::AccountId> for Pallet<T, I> {
|
||||
fn start_destroy(id: T::AssetId, maybe_check_owner: Option<T::AccountId>) -> DispatchResult {
|
||||
Self::do_start_destroy(id, maybe_check_owner)
|
||||
}
|
||||
|
||||
fn destroy_accounts(id: T::AssetId, max_items: u32) -> Result<u32, DispatchError> {
|
||||
Self::do_destroy_accounts(id, max_items)
|
||||
}
|
||||
|
||||
fn destroy_approvals(id: T::AssetId, max_items: u32) -> Result<u32, DispatchError> {
|
||||
Self::do_destroy_approvals(id, max_items)
|
||||
}
|
||||
|
||||
fn finish_destroy(id: T::AssetId) -> DispatchResult {
|
||||
Self::do_finish_destroy(id)
|
||||
}
|
||||
}
|
||||
|
||||
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::metadata::MetadataDeposit<
|
||||
<T::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance,
|
||||
> for Pallet<T, I>
|
||||
{
|
||||
fn calc_metadata_deposit(
|
||||
name: &[u8],
|
||||
symbol: &[u8],
|
||||
) -> <T::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance {
|
||||
Self::calc_metadata_deposit(&name, &symbol)
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
{
|
||||
// Approve spending tokens from a given account
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::roles::Inspect<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
fn owner(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.owner)
|
||||
}
|
||||
|
||||
fn issuer(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.issuer)
|
||||
}
|
||||
|
||||
fn admin(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.admin)
|
||||
}
|
||||
|
||||
fn freezer(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.freezer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::InspectEnumerable<T::AccountId> for Pallet<T, I> {
|
||||
type AssetsIterator = KeyPrefixIterator<<T as Config<I>>::AssetId>;
|
||||
|
||||
/// Returns an iterator of the assets in existence.
|
||||
///
|
||||
/// NOTE: iterating this list invokes a storage read per item.
|
||||
fn asset_ids() -> Self::AssetsIterator {
|
||||
Asset::<T, I>::iter_keys()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::roles::ResetTeam<T::AccountId> for Pallet<T, I> {
|
||||
fn reset_team(
|
||||
id: T::AssetId,
|
||||
owner: T::AccountId,
|
||||
admin: T::AccountId,
|
||||
issuer: T::AccountId,
|
||||
freezer: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
Self::do_reset_team(id, owner, admin, issuer, freezer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Refund<T::AccountId> for Pallet<T, I> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = DepositBalanceOf<T, I>;
|
||||
fn deposit_held(id: Self::AssetId, who: T::AccountId) -> Option<(T::AccountId, Self::Balance)> {
|
||||
use ExistenceReason::*;
|
||||
match Account::<T, I>::get(&id, &who).ok_or(Error::<T, I>::NoDeposit).ok()?.reason {
|
||||
DepositHeld(b) => Some((who, b)),
|
||||
DepositFrom(d, b) => Some((d, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn refund(id: Self::AssetId, who: T::AccountId) -> DispatchResult {
|
||||
match Self::deposit_held(id.clone(), who.clone()) {
|
||||
Some((d, _)) if d == who => Self::do_refund(id, who, false),
|
||||
Some(..) => Self::do_refund_other(id, &who, None),
|
||||
None => Err(Error::<T, I>::NoDeposit.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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's `StoredMap` implementation.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T: Config<I>, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> for Pallet<T, I> {
|
||||
fn get(id_who: &(T::AssetId, T::AccountId)) -> T::Extra {
|
||||
let (id, who) = id_who;
|
||||
Account::<T, I>::get(id, who).map(|a| a.extra).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn try_mutate_exists<R, E: From<DispatchError>>(
|
||||
id_who: &(T::AssetId, T::AccountId),
|
||||
f: impl FnOnce(&mut Option<T::Extra>) -> Result<R, E>,
|
||||
) -> Result<R, E> {
|
||||
let (id, who) = id_who;
|
||||
let mut maybe_extra = Account::<T, I>::get(id, who).map(|a| a.extra);
|
||||
let r = f(&mut maybe_extra)?;
|
||||
// They want to write some value or delete it.
|
||||
// If the account existed and they want to write a value, then we write.
|
||||
// If the account didn't exist and they want to delete it, then we let it pass.
|
||||
// Otherwise, we fail.
|
||||
Account::<T, I>::try_mutate(id, who, |maybe_account| {
|
||||
if let Some(extra) = maybe_extra {
|
||||
// They want to write a value. Let this happen only if the account actually exists.
|
||||
if let Some(ref mut account) = maybe_account {
|
||||
account.extra = extra;
|
||||
} else {
|
||||
return Err(DispatchError::NoProviders.into());
|
||||
}
|
||||
} else {
|
||||
// They want to delete it. Let this pass if the item never existed anyway.
|
||||
ensure!(maybe_account.is_none(), DispatchError::ConsumerRemaining);
|
||||
}
|
||||
Ok(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::traits::OnRuntimeUpgrade;
|
||||
use log;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
|
||||
pub mod next_asset_id {
|
||||
use super::*;
|
||||
use pezsp_core::Get;
|
||||
|
||||
/// Set [`NextAssetId`] to the value of `ID` if [`NextAssetId`] does not exist yet.
|
||||
pub struct SetNextAssetId<ID, T: Config<I>, I: 'static = ()>(
|
||||
core::marker::PhantomData<(ID, T, I)>,
|
||||
);
|
||||
impl<ID, T: Config<I>, I: 'static> OnRuntimeUpgrade for SetNextAssetId<ID, T, I>
|
||||
where
|
||||
T::AssetId: Incrementable,
|
||||
ID: Get<T::AssetId>,
|
||||
{
|
||||
fn on_runtime_upgrade() -> pezframe_support::weights::Weight {
|
||||
if !NextAssetId::<T, I>::exists() {
|
||||
NextAssetId::<T, I>::put(ID::get());
|
||||
T::DbWeight::get().reads_writes(1, 1)
|
||||
} else {
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use pezframe_support::{pezpallet_prelude::*, weights::Weight};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Decode)]
|
||||
pub struct OldAssetDetails<Balance, AccountId, DepositBalance> {
|
||||
pub owner: AccountId,
|
||||
pub issuer: AccountId,
|
||||
pub admin: AccountId,
|
||||
pub freezer: AccountId,
|
||||
pub supply: Balance,
|
||||
pub deposit: DepositBalance,
|
||||
pub min_balance: Balance,
|
||||
pub is_sufficient: bool,
|
||||
pub accounts: u32,
|
||||
pub sufficients: u32,
|
||||
pub approvals: u32,
|
||||
pub is_frozen: bool,
|
||||
}
|
||||
|
||||
impl<Balance, AccountId, DepositBalance> OldAssetDetails<Balance, AccountId, DepositBalance> {
|
||||
fn migrate_to_v1(self) -> AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
let status = if self.is_frozen { AssetStatus::Frozen } else { AssetStatus::Live };
|
||||
|
||||
AssetDetails {
|
||||
owner: self.owner,
|
||||
issuer: self.issuer,
|
||||
admin: self.admin,
|
||||
freezer: self.freezer,
|
||||
supply: self.supply,
|
||||
deposit: self.deposit,
|
||||
min_balance: self.min_balance,
|
||||
is_sufficient: self.is_sufficient,
|
||||
accounts: self.accounts,
|
||||
sufficients: self.sufficients,
|
||||
approvals: self.approvals,
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MigrateToV1<T>(core::marker::PhantomData<T>);
|
||||
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let in_code_version = Pallet::<T>::in_code_storage_version();
|
||||
let on_chain_version = Pallet::<T>::on_chain_storage_version();
|
||||
if on_chain_version == 0 && in_code_version == 1 {
|
||||
let mut translated = 0u64;
|
||||
Asset::<T>::translate::<
|
||||
OldAssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
_,
|
||||
>(|_key, old_value| {
|
||||
translated.saturating_inc();
|
||||
Some(old_value.migrate_to_v1())
|
||||
});
|
||||
in_code_version.put::<Pallet<T>>();
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Upgraded {} pools, storage to version {:?}",
|
||||
translated,
|
||||
in_code_version
|
||||
);
|
||||
T::DbWeight::get().reads_writes(translated + 1, translated + 1)
|
||||
} else {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Migration did not execute. This probably should be removed"
|
||||
);
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
pezframe_support::ensure!(
|
||||
Pallet::<T>::on_chain_storage_version() == 0,
|
||||
"must upgrade linearly"
|
||||
);
|
||||
let prev_count = Asset::<T>::iter().count();
|
||||
Ok((prev_count as u32).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(prev_count: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect(
|
||||
"the state parameter should be something that was generated by pre_upgrade",
|
||||
);
|
||||
let post_count = Asset::<T>::iter().count() as u32;
|
||||
ensure!(
|
||||
prev_count == post_count,
|
||||
"the asset count before and after the migration should be the same"
|
||||
);
|
||||
|
||||
let in_code_version = Pallet::<T>::in_code_storage_version();
|
||||
let on_chain_version = Pallet::<T>::on_chain_storage_version();
|
||||
|
||||
pezframe_support::ensure!(in_code_version == 1, "must_upgrade");
|
||||
ensure!(
|
||||
in_code_version == on_chain_version,
|
||||
"after migration, the in_code_version and on_chain_version should be the same"
|
||||
);
|
||||
|
||||
Asset::<T>::iter().try_for_each(|(_id, asset)| -> Result<(), TryRuntimeError> {
|
||||
ensure!(
|
||||
asset.status == AssetStatus::Live || asset.status == AssetStatus::Frozen,
|
||||
"assets should only be live or frozen. None should be in destroying status, or undefined state"
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Test environment for Assets pallet.
|
||||
|
||||
use super::*;
|
||||
use crate as pezpallet_assets;
|
||||
|
||||
use codec::Encode;
|
||||
use pezframe_support::{
|
||||
assert_ok, construct_runtime, derive_impl, parameter_types,
|
||||
traits::{AsEnsureOriginWithArg, ConstU32},
|
||||
};
|
||||
use pezsp_io::storage;
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Assets: pezpallet_assets,
|
||||
}
|
||||
);
|
||||
|
||||
type AccountId = u64;
|
||||
type AssetId = u32;
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
type MaxConsumers = ConstU32<3>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
pub struct AssetsCallbackHandle;
|
||||
impl AssetsCallback<AssetId, AccountId> for AssetsCallbackHandle {
|
||||
fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
|
||||
if Self::should_err() {
|
||||
Err(())
|
||||
} else {
|
||||
storage::set(Self::CREATED.as_bytes(), &().encode());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(_id: &AssetId) -> Result<(), ()> {
|
||||
if Self::should_err() {
|
||||
Err(())
|
||||
} else {
|
||||
storage::set(Self::DESTROYED.as_bytes(), &().encode());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetsCallbackHandle {
|
||||
pub const CREATED: &'static str = "asset_created";
|
||||
pub const DESTROYED: &'static str = "asset_destroyed";
|
||||
|
||||
const RETURN_ERROR: &'static str = "return_error";
|
||||
|
||||
// Configures `Self` to return `Ok` when callbacks are invoked
|
||||
pub fn set_return_ok() {
|
||||
storage::clear(Self::RETURN_ERROR.as_bytes());
|
||||
}
|
||||
|
||||
// Configures `Self` to return `Err` when callbacks are invoked
|
||||
pub fn set_return_error() {
|
||||
storage::set(Self::RETURN_ERROR.as_bytes(), &().encode());
|
||||
}
|
||||
|
||||
// If `true`, callback should return `Err`, `Ok` otherwise.
|
||||
fn should_err() -> bool {
|
||||
storage::exists(Self::RETURN_ERROR.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(crate::config_preludes::TestDefaultConfig)]
|
||||
impl Config for Test {
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<u64>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type Freezer = TestFreezer;
|
||||
type Holder = TestHolder;
|
||||
type CallbackHandle = (AssetsCallbackHandle, AutoIncAssetId<Test>);
|
||||
type ReserveData = u128;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = AssetsBenchmarkHelper;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub struct AssetsBenchmarkHelper;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl<AssetIdParameter: From<u32>, ReserveIdParameter: From<u32>>
|
||||
BenchmarkHelper<AssetIdParameter, ReserveIdParameter> for AssetsBenchmarkHelper
|
||||
{
|
||||
fn create_asset_id_parameter(id: u32) -> AssetIdParameter {
|
||||
id.into()
|
||||
}
|
||||
fn create_reserve_id_parameter(id: u32) -> ReserveIdParameter {
|
||||
id.into()
|
||||
}
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Hook {
|
||||
Died(u32, u64),
|
||||
}
|
||||
parameter_types! {
|
||||
static Frozen: HashMap<(u32, u64), u64> = Default::default();
|
||||
static OnHold: HashMap<(u32, u64), u64> = Default::default();
|
||||
static Hooks: Vec<Hook> = Default::default();
|
||||
}
|
||||
|
||||
pub struct TestHolder;
|
||||
impl BalanceOnHold<u32, u64, u64> for TestHolder {
|
||||
fn balance_on_hold(asset: u32, who: &u64) -> Option<u64> {
|
||||
OnHold::get().get(&(asset, *who)).cloned()
|
||||
}
|
||||
|
||||
fn died(asset: u32, who: &u64) {
|
||||
Hooks::mutate(|v| v.push(Hook::Died(asset, *who)))
|
||||
}
|
||||
|
||||
fn contains_holds(asset: AssetId) -> bool {
|
||||
OnHold::get().iter().any(|((k, _), _)| &asset == k)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_balance_on_hold(asset: u32, who: u64, amount: u64) {
|
||||
OnHold::mutate(|v| {
|
||||
let amount_on_hold = v.get(&(asset, who)).unwrap_or(&0);
|
||||
|
||||
if &amount > amount_on_hold {
|
||||
// Hold more funds
|
||||
let amount = amount - amount_on_hold;
|
||||
let f = DebitFlags { keep_alive: true, best_effort: false };
|
||||
assert_ok!(Assets::decrease_balance(asset, &who, amount, f, |_, _| Ok(())));
|
||||
} else {
|
||||
// Release funds on hold
|
||||
let amount = amount_on_hold - amount;
|
||||
assert_ok!(Assets::increase_balance(asset, &who, amount, |_| Ok(())));
|
||||
}
|
||||
|
||||
// Asset amount still "exists", we just store it here
|
||||
v.insert((asset, who), amount);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn clear_balance_on_hold(asset: u32, who: u64) {
|
||||
OnHold::mutate(|v| {
|
||||
v.remove(&(asset, who));
|
||||
});
|
||||
}
|
||||
pub struct TestFreezer;
|
||||
impl FrozenBalance<u32, u64, u64> for TestFreezer {
|
||||
fn frozen_balance(asset: u32, who: &u64) -> Option<u64> {
|
||||
Frozen::get().get(&(asset, *who)).cloned()
|
||||
}
|
||||
|
||||
fn died(asset: u32, who: &u64) {
|
||||
Hooks::mutate(|v| v.push(Hook::Died(asset, *who)));
|
||||
|
||||
// Sanity check: dead accounts have no balance.
|
||||
assert!(Assets::balance(asset, *who).is_zero());
|
||||
}
|
||||
|
||||
/// Return a value that indicates if there are registered freezes for a given asset.
|
||||
fn contains_freezes(asset: AssetId) -> bool {
|
||||
Frozen::get().iter().any(|((k, _), _)| &asset == k)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) {
|
||||
Frozen::mutate(|v| {
|
||||
v.insert((asset, who), amount);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn clear_frozen_balance(asset: u32, who: u64) {
|
||||
Frozen::mutate(|v| {
|
||||
v.remove(&(asset, who));
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn hooks() -> Vec<Hook> {
|
||||
Hooks::get().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn take_hooks() -> Vec<Hook> {
|
||||
Hooks::take()
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut storage = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
let config: pezpallet_assets::GenesisConfig<Test> = pezpallet_assets::GenesisConfig {
|
||||
assets: vec![
|
||||
// id, owner, is_sufficient, min_balance
|
||||
(999, 0, true, 1),
|
||||
],
|
||||
metadata: vec![
|
||||
// id, name, symbol, decimals
|
||||
(999, "Token Name".into(), "TOKEN".into(), 10),
|
||||
],
|
||||
accounts: vec![
|
||||
// id, account_id, balance
|
||||
(999, 1, 100),
|
||||
],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
};
|
||||
|
||||
config.assimilate_storage(&mut storage).unwrap();
|
||||
|
||||
let mut ext: pezsp_io::TestExternalities = storage.into();
|
||||
// Clear thread local vars for https://github.com/pezkuwichain/kurdistan-sdk/issues/2.
|
||||
ext.execute_with(|| take_hooks());
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,358 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Tests for [`ItemOf`], [`fungible::UnionOf`] and [`fungibles::UnionOf`] set types.
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::{
|
||||
parameter_types,
|
||||
traits::{
|
||||
fungible,
|
||||
fungible::ItemOf,
|
||||
fungibles,
|
||||
tokens::{
|
||||
fungibles::{
|
||||
Balanced as FungiblesBalanced, Create as FungiblesCreate,
|
||||
Inspect as FungiblesInspect, Mutate as FungiblesMutate,
|
||||
},
|
||||
Fortitude, Precision, Preservation,
|
||||
},
|
||||
},
|
||||
};
|
||||
use pezsp_runtime::{traits::ConvertToValue, Either};
|
||||
|
||||
const FIRST_ASSET: u32 = 0;
|
||||
const UNKNOWN_ASSET: u32 = 10;
|
||||
|
||||
parameter_types! {
|
||||
pub const LeftAsset: Either<(), u32> = Either::Left(());
|
||||
pub const RightAsset: Either<u32, ()> = Either::Right(());
|
||||
pub const RightUnitAsset: Either<(), ()> = Either::Right(());
|
||||
}
|
||||
|
||||
/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a
|
||||
/// single asset class from [`T`] identified by [`FIRST_ASSET`].
|
||||
type FirstFungible<T> = ItemOf<T, pezframe_support::traits::ConstU32<{ FIRST_ASSET }>, u64>;
|
||||
|
||||
/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a
|
||||
/// single asset class from [`T`] identified by [`UNKNOWN_ASSET`].
|
||||
type UnknownFungible<T> = ItemOf<T, pezframe_support::traits::ConstU32<{ UNKNOWN_ASSET }>, u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`FirstFungible`] from the left.
|
||||
type LeftFungible<T> = fungible::UnionOf<FirstFungible<T>, T, ConvertToValue<LeftAsset>, (), u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`LeftFungible`] from the right.
|
||||
type RightFungible<T> =
|
||||
fungible::UnionOf<UnknownFungible<T>, LeftFungible<T>, ConvertToValue<RightUnitAsset>, (), u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`RightFungible`] from the left.
|
||||
type LeftFungibles<T> = fungibles::UnionOf<RightFungible<T>, T, ConvertToValue<LeftAsset>, (), u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`LeftFungibles`] from the right.
|
||||
///
|
||||
/// By using this type, we can navigate through each branch of [`fungible::UnionOf`],
|
||||
/// [`fungibles::UnionOf`], and [`ItemOf`] to access the underlying `fungibles::*`
|
||||
/// implementation provided by the pallet.
|
||||
type First<T> = fungibles::UnionOf<T, LeftFungibles<T>, ConvertToValue<RightAsset>, (), u64>;
|
||||
|
||||
#[test]
|
||||
fn deposit_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1 = 0;
|
||||
let account1 = 1;
|
||||
let account2 = 2;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::deposit((), &account2, 50, Precision::Exact).unwrap();
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::Deposited {
|
||||
asset_id: asset1,
|
||||
who: account2,
|
||||
amount: 50,
|
||||
}));
|
||||
|
||||
assert_eq!(imb.peek(), 50);
|
||||
|
||||
let (imb1, imb2) = imb.split(30);
|
||||
assert_eq!(imb1.peek(), 30);
|
||||
assert_eq!(imb2.peek(), 20);
|
||||
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 120);
|
||||
|
||||
assert!(First::<Assets>::settle(&account1, imb1, Preservation::Preserve).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 70);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 120);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::issue((), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(imb.peek(), 100);
|
||||
|
||||
let (imb1, imb2) = imb.split(30);
|
||||
assert_eq!(imb1.peek(), 30);
|
||||
assert_eq!(imb2.peek(), 70);
|
||||
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 130);
|
||||
|
||||
assert!(First::<Assets>::resolve(&account1, imb1).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 130);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 130);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pair_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let (debt, credit) = First::<Assets>::pair((), 100).unwrap();
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(debt.peek(), 100);
|
||||
assert_eq!(credit.peek(), 100);
|
||||
|
||||
let (debt1, debt2) = debt.split(30);
|
||||
assert_eq!(debt1.peek(), 30);
|
||||
assert_eq!(debt2.peek(), 70);
|
||||
|
||||
drop(debt2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 170);
|
||||
|
||||
assert!(First::<Assets>::settle(&account1, debt1, Preservation::Preserve).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 70);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 170);
|
||||
|
||||
let (credit1, credit2) = credit.split(40);
|
||||
assert_eq!(credit1.peek(), 40);
|
||||
assert_eq!(credit2.peek(), 60);
|
||||
|
||||
drop(credit2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 110);
|
||||
|
||||
assert!(First::<Assets>::resolve(&account1, credit1).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 110);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 110);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rescind_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::rescind((), 20);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 80);
|
||||
|
||||
assert_eq!(imb.peek(), 20);
|
||||
|
||||
let (imb1, imb2) = imb.split(15);
|
||||
assert_eq!(imb1.peek(), 15);
|
||||
assert_eq!(imb2.peek(), 5);
|
||||
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 85);
|
||||
|
||||
assert!(First::<Assets>::settle(&account1, imb1, Preservation::Preserve).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 85);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 85);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
let account2: u64 = 2;
|
||||
let ed = 11;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, ed));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::issue((), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(imb.peek(), 100);
|
||||
|
||||
let (imb1, imb2) = imb.split(10);
|
||||
assert_eq!(imb1.peek(), 10);
|
||||
assert_eq!(imb2.peek(), 90);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
|
||||
// ed requirements not met.
|
||||
let imb1 = First::<Assets>::resolve(&account2, imb1).unwrap_err();
|
||||
assert_eq!(imb1.peek(), 10);
|
||||
drop(imb1);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 0);
|
||||
|
||||
// resolve to new account `2`.
|
||||
assert_ok!(First::<Assets>::resolve(&account2, imb2));
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 90);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settle_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
let account2: u64 = 2;
|
||||
let ed = 11;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, ed));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
assert_ok!(Assets::mint_into(asset1, &account2, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::rescind((), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(imb.peek(), 100);
|
||||
|
||||
let (imb1, imb2) = imb.split(10);
|
||||
assert_eq!(imb1.peek(), 10);
|
||||
assert_eq!(imb2.peek(), 90);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
|
||||
// ed requirements not met.
|
||||
let imb2 = First::<Assets>::settle(&account2, imb2, Preservation::Preserve).unwrap_err();
|
||||
assert_eq!(imb2.peek(), 90);
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 100);
|
||||
|
||||
// settle to account `1`.
|
||||
assert_ok!(First::<Assets>::settle(&account2, imb1, Preservation::Preserve));
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 90);
|
||||
|
||||
let imb = First::<Assets>::rescind((), 85);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 105);
|
||||
assert_eq!(imb.peek(), 85);
|
||||
|
||||
// settle to account `1` and expect some dust.
|
||||
let imb = First::<Assets>::settle(&account2, imb, Preservation::Expendable).unwrap();
|
||||
assert_eq!(imb.peek(), 5);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 105);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 0);
|
||||
|
||||
drop(imb);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1 = 0;
|
||||
let account1 = 1;
|
||||
let account2 = 2;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
assert_ok!(Assets::mint_into(asset1, &account2, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::withdraw(
|
||||
(),
|
||||
&account2,
|
||||
50,
|
||||
Precision::Exact,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::Withdrawn {
|
||||
asset_id: asset1,
|
||||
who: account2,
|
||||
amount: 50,
|
||||
}));
|
||||
|
||||
assert_eq!(imb.peek(), 50);
|
||||
drop(imb);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 150);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) 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.
|
||||
|
||||
//! Various basic types for use in the assets pallet.
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
traits::{fungible, tokens::ConversionToAssetBalance},
|
||||
};
|
||||
use pezsp_runtime::{traits::Convert, FixedPointNumber, FixedU128};
|
||||
|
||||
pub type DepositBalanceOf<T, I = ()> =
|
||||
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
|
||||
pub type AssetAccountOf<T, I> = AssetAccount<
|
||||
<T as Config<I>>::Balance,
|
||||
DepositBalanceOf<T, I>,
|
||||
<T as Config<I>>::Extra,
|
||||
<T as SystemConfig>::AccountId,
|
||||
>;
|
||||
pub type ExistenceReasonOf<T, I> =
|
||||
ExistenceReason<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
|
||||
|
||||
/// AssetStatus holds the current state of the asset. It could either be Live and available for use,
|
||||
/// or in a Destroying state.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub enum AssetStatus {
|
||||
/// The asset is active and able to be used.
|
||||
Live,
|
||||
/// Whether the asset is frozen for non-admin transfers.
|
||||
Frozen,
|
||||
/// The asset is currently being destroyed, and all actions are no longer permitted on the
|
||||
/// asset. Once set to `Destroying`, the asset can never transition back to a `Live` state.
|
||||
Destroying,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
/// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
|
||||
pub owner: AccountId,
|
||||
/// Can mint tokens.
|
||||
pub issuer: AccountId,
|
||||
/// Can thaw tokens, force transfers and burn tokens from any account.
|
||||
pub admin: AccountId,
|
||||
/// Can freeze tokens.
|
||||
pub freezer: AccountId,
|
||||
/// The total supply across all accounts.
|
||||
pub supply: Balance,
|
||||
/// The balance deposited for this asset. This pays for the data stored here.
|
||||
pub deposit: DepositBalance,
|
||||
/// The ED for virtual accounts.
|
||||
pub min_balance: Balance,
|
||||
/// If `true`, then any account with this asset is given a provider reference. Otherwise, it
|
||||
/// requires a consumer reference.
|
||||
pub is_sufficient: bool,
|
||||
/// The total number of accounts.
|
||||
pub accounts: u32,
|
||||
/// The total number of accounts for which we have placed a self-sufficient reference.
|
||||
pub sufficients: u32,
|
||||
/// The total number of approvals.
|
||||
pub approvals: u32,
|
||||
/// The status of the asset
|
||||
pub status: AssetStatus,
|
||||
}
|
||||
|
||||
/// Data concerning an approval.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)]
|
||||
pub struct Approval<Balance, DepositBalance> {
|
||||
/// The amount of funds approved for the balance transfer from the owner to some delegated
|
||||
/// target.
|
||||
pub amount: Balance,
|
||||
/// The amount reserved on the owner's account to hold this item in storage.
|
||||
pub deposit: DepositBalance,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_bool_decodes_to_consumer_or_sufficient() {
|
||||
assert_eq!(false.encode(), ExistenceReason::<(), ()>::Consumer.encode());
|
||||
assert_eq!(true.encode(), ExistenceReason::<(), ()>::Sufficient.encode());
|
||||
}
|
||||
|
||||
/// The reason for an account's existence within an asset class.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub enum ExistenceReason<Balance, AccountId> {
|
||||
/// A consumer reference was used to create this account.
|
||||
#[codec(index = 0)]
|
||||
Consumer,
|
||||
/// The asset class is `sufficient` for account existence.
|
||||
#[codec(index = 1)]
|
||||
Sufficient,
|
||||
/// The account holder has placed a deposit to exist within an asset class.
|
||||
#[codec(index = 2)]
|
||||
DepositHeld(Balance),
|
||||
/// A deposit was placed for this account to exist, but it has been refunded.
|
||||
#[codec(index = 3)]
|
||||
DepositRefunded,
|
||||
/// Some other `AccountId` has placed a deposit to make this account exist.
|
||||
/// An account with such a reason might not be referenced in `system`.
|
||||
#[codec(index = 4)]
|
||||
DepositFrom(AccountId, Balance),
|
||||
}
|
||||
|
||||
impl<Balance, AccountId> ExistenceReason<Balance, AccountId>
|
||||
where
|
||||
AccountId: Clone,
|
||||
{
|
||||
pub fn take_deposit(&mut self) -> Option<Balance> {
|
||||
if !matches!(self, ExistenceReason::DepositHeld(_)) {
|
||||
return None;
|
||||
}
|
||||
if let ExistenceReason::DepositHeld(deposit) =
|
||||
core::mem::replace(self, ExistenceReason::DepositRefunded)
|
||||
{
|
||||
Some(deposit)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_deposit_from(&mut self) -> Option<(AccountId, Balance)> {
|
||||
if !matches!(self, ExistenceReason::DepositFrom(..)) {
|
||||
return None;
|
||||
}
|
||||
if let ExistenceReason::DepositFrom(depositor, deposit) =
|
||||
core::mem::replace(self, ExistenceReason::DepositRefunded)
|
||||
{
|
||||
Some((depositor, deposit))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_bool_decodes_to_liquid_or_frozen() {
|
||||
assert_eq!(false.encode(), AccountStatus::Liquid.encode());
|
||||
assert_eq!(true.encode(), AccountStatus::Frozen.encode());
|
||||
}
|
||||
|
||||
/// The status of an asset account.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub enum AccountStatus {
|
||||
/// Asset account can receive and transfer the assets.
|
||||
Liquid,
|
||||
/// Asset account cannot transfer the assets.
|
||||
Frozen,
|
||||
/// Asset account cannot receive and transfer the assets.
|
||||
Blocked,
|
||||
}
|
||||
impl AccountStatus {
|
||||
/// Returns `true` if frozen or blocked.
|
||||
pub fn is_frozen(&self) -> bool {
|
||||
matches!(self, AccountStatus::Frozen | AccountStatus::Blocked)
|
||||
}
|
||||
/// Returns `true` if blocked.
|
||||
pub fn is_blocked(&self) -> bool {
|
||||
matches!(self, AccountStatus::Blocked)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AssetAccount<Balance, DepositBalance, Extra, AccountId> {
|
||||
/// The account's balance.
|
||||
///
|
||||
/// The part of the `balance` may be frozen by the [`Config::Freezer`]. The on-hold portion is
|
||||
/// not included here and is tracked by the [`Config::Holder`].
|
||||
pub balance: Balance,
|
||||
/// The status of the account.
|
||||
pub status: AccountStatus,
|
||||
/// The reason for the existence of the account.
|
||||
pub reason: ExistenceReason<DepositBalance, AccountId>,
|
||||
/// Additional "sidecar" data, in case some other pallet wants to use this storage item.
|
||||
pub extra: Extra,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AssetMetadata<DepositBalance, BoundedString> {
|
||||
/// The balance deposited for this metadata.
|
||||
///
|
||||
/// This pays for the data stored in this struct.
|
||||
pub deposit: DepositBalance,
|
||||
/// The user friendly name of this asset. Limited in length by `StringLimit`.
|
||||
pub name: BoundedString,
|
||||
/// The ticker symbol for this asset. Limited in length by `StringLimit`.
|
||||
pub symbol: BoundedString,
|
||||
/// The number of decimals this asset uses to represent one unit.
|
||||
pub decimals: u8,
|
||||
/// Whether the asset metadata may be changed by a non Force origin.
|
||||
pub is_frozen: bool,
|
||||
}
|
||||
|
||||
/// Trait for allowing a minimum balance on the account to be specified, beyond the
|
||||
/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be
|
||||
/// met *and then* anything here in addition.
|
||||
pub trait FrozenBalance<AssetId, AccountId, Balance> {
|
||||
/// Return the frozen balance.
|
||||
///
|
||||
/// Generally, the balance of every account must be at least the sum of this (if `Some`) and
|
||||
/// the asset's `minimum_balance` (the latter since there may be complications to destroying an
|
||||
/// asset's account completely).
|
||||
///
|
||||
/// Under normal behaviour, the account balance should not go below the sum of this (if `Some`)
|
||||
/// and the asset's minimum balance. However, the account balance may reasonably begin below
|
||||
/// this sum (e.g. if less than the sum had ever been transferred into the account).
|
||||
///
|
||||
/// In special cases (privileged intervention) the account balance may also go below the sum.
|
||||
///
|
||||
/// If `None` is returned, then nothing special is enforced.
|
||||
fn frozen_balance(asset: AssetId, who: &AccountId) -> Option<Balance>;
|
||||
|
||||
/// Called after an account has been removed.
|
||||
fn died(asset: AssetId, who: &AccountId);
|
||||
|
||||
/// Return a value that indicates if there are registered freezes for a given asset.
|
||||
fn contains_freezes(asset: AssetId) -> bool;
|
||||
}
|
||||
|
||||
impl<AssetId, AccountId, Balance> FrozenBalance<AssetId, AccountId, Balance> for () {
|
||||
fn frozen_balance(_: AssetId, _: &AccountId) -> Option<Balance> {
|
||||
None
|
||||
}
|
||||
fn died(_: AssetId, _: &AccountId) {}
|
||||
fn contains_freezes(_: AssetId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait indicates a balance that is _on hold_ for an asset account.
|
||||
///
|
||||
/// A balance _on hold_ is a balance that, while is assigned to an account,
|
||||
/// is outside the direct control of it. Instead, is being _held_ by the
|
||||
/// system logic (i.e. Pallets) and can be eventually burned or released.
|
||||
pub trait BalanceOnHold<AssetId, AccountId, Balance> {
|
||||
/// Return the held balance.
|
||||
///
|
||||
/// If `Some`, it means some balance is _on hold_, and it can be
|
||||
/// infallibly burned.
|
||||
///
|
||||
/// If `None` is returned, then no balance is _on hold_ for `who`'s asset
|
||||
/// account.
|
||||
fn balance_on_hold(asset: AssetId, who: &AccountId) -> Option<Balance>;
|
||||
|
||||
/// Called after an account has been removed.
|
||||
///
|
||||
/// It is expected that this method is called only when there is no balance
|
||||
/// on hold. Otherwise, an account should not be removed.
|
||||
fn died(asset: AssetId, who: &AccountId);
|
||||
|
||||
/// Return a value that indicates if there are registered holds for a given asset.
|
||||
fn contains_holds(asset: AssetId) -> bool;
|
||||
}
|
||||
|
||||
impl<AssetId, AccountId, Balance> BalanceOnHold<AssetId, AccountId, Balance> for () {
|
||||
fn balance_on_hold(_: AssetId, _: &AccountId) -> Option<Balance> {
|
||||
None
|
||||
}
|
||||
fn died(_: AssetId, _: &AccountId) {}
|
||||
fn contains_holds(_: AssetId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TransferFlags {
|
||||
/// The debited account must stay alive at the end of the operation; an error is returned if
|
||||
/// this cannot be achieved legally.
|
||||
pub keep_alive: bool,
|
||||
/// Less than the amount specified needs be debited by the operation for it to be considered
|
||||
/// successful. If `false`, then the amount debited will always be at least the amount
|
||||
/// specified.
|
||||
pub best_effort: bool,
|
||||
/// Any additional funds debited (due to minimum balance requirements) should be burned rather
|
||||
/// than credited to the destination account.
|
||||
pub burn_dust: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct DebitFlags {
|
||||
/// The debited account must stay alive at the end of the operation; an error is returned if
|
||||
/// this cannot be achieved legally.
|
||||
pub keep_alive: bool,
|
||||
/// Less than the amount specified needs be debited by the operation for it to be considered
|
||||
/// successful. If `false`, then the amount debited will always be at least the amount
|
||||
/// specified.
|
||||
pub best_effort: bool,
|
||||
}
|
||||
|
||||
impl From<TransferFlags> for DebitFlags {
|
||||
fn from(f: TransferFlags) -> Self {
|
||||
Self { keep_alive: f.keep_alive, best_effort: f.best_effort }
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible errors when converting between external and asset balances.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, RuntimeDebug, Encode, Decode)]
|
||||
pub enum ConversionError {
|
||||
/// The external minimum balance must not be zero.
|
||||
MinBalanceZero,
|
||||
/// The asset is not present in storage.
|
||||
AssetMissing,
|
||||
/// The asset is not sufficient and thus does not have a reliable `min_balance` so it cannot be
|
||||
/// converted.
|
||||
AssetNotSufficient,
|
||||
}
|
||||
|
||||
// Type alias for `pezframe_system`'s account id.
|
||||
type AccountIdOf<T> = <T as pezframe_system::Config>::AccountId;
|
||||
// This pallet's asset id and balance type.
|
||||
type AssetIdOf<T, I> = <T as Config<I>>::AssetId;
|
||||
type AssetBalanceOf<T, I> = <T as Config<I>>::Balance;
|
||||
// Generic fungible balance type.
|
||||
type BalanceOf<F, T> = <F as fungible::Inspect<AccountIdOf<T>>>::Balance;
|
||||
|
||||
/// Converts a balance value into an asset balance based on the ratio between the fungible's
|
||||
/// minimum balance and the minimum asset balance.
|
||||
pub struct BalanceToAssetBalance<F, T, CON, I = ()>(PhantomData<(F, T, CON, I)>);
|
||||
impl<F, T, CON, I> ConversionToAssetBalance<BalanceOf<F, T>, AssetIdOf<T, I>, AssetBalanceOf<T, I>>
|
||||
for BalanceToAssetBalance<F, T, CON, I>
|
||||
where
|
||||
F: fungible::Inspect<AccountIdOf<T>>,
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
CON: Convert<BalanceOf<F, T>, AssetBalanceOf<T, I>>,
|
||||
{
|
||||
type Error = ConversionError;
|
||||
|
||||
/// Convert the given balance value into an asset balance based on the ratio between the
|
||||
/// fungible's minimum balance and the minimum asset balance.
|
||||
///
|
||||
/// Will return `Err` if the asset is not found, not sufficient or the fungible's minimum
|
||||
/// balance is zero.
|
||||
fn to_asset_balance(
|
||||
balance: BalanceOf<F, T>,
|
||||
asset_id: AssetIdOf<T, I>,
|
||||
) -> Result<AssetBalanceOf<T, I>, ConversionError> {
|
||||
let asset = Asset::<T, I>::get(asset_id).ok_or(ConversionError::AssetMissing)?;
|
||||
// only sufficient assets have a min balance with reliable value
|
||||
ensure!(asset.is_sufficient, ConversionError::AssetNotSufficient);
|
||||
let min_balance = CON::convert(F::minimum_balance());
|
||||
// make sure we don't divide by zero
|
||||
ensure!(!min_balance.is_zero(), ConversionError::MinBalanceZero);
|
||||
let balance = CON::convert(balance);
|
||||
// balance * asset.min_balance / min_balance
|
||||
Ok(FixedU128::saturating_from_rational(asset.min_balance, min_balance)
|
||||
.saturating_mul_int(balance))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user