From e9b684d87ec3e684dd48974629a9a474f4b7f580 Mon Sep 17 00:00:00 2001 From: wangjj9219 <183318287@qq.com> Date: Tue, 15 Aug 2023 20:28:09 +0800 Subject: [PATCH] add conformance for fungibles --- tokens/Cargo.toml | 1 + .../inspect_mutate.rs | 1119 +++++++++++++++++ tokens/src/fungibles_conformance_tests/mod.rs | 3 + tokens/src/lib.rs | 2 + tokens/src/tests_fungibles_conformance.rs | 75 ++ 5 files changed, 1200 insertions(+) create mode 100644 tokens/src/fungibles_conformance_tests/inspect_mutate.rs create mode 100644 tokens/src/fungibles_conformance_tests/mod.rs create mode 100644 tokens/src/tests_fungibles_conformance.rs diff --git a/tokens/Cargo.toml b/tokens/Cargo.toml index 6fa9c3e8f..0cc857d5c 100644 --- a/tokens/Cargo.toml +++ b/tokens/Cargo.toml @@ -26,6 +26,7 @@ pallet-treasury = { git = "/~https://github.com/paritytech/substrate", branch = "p sp-core = { git = "/~https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } sp-io = { git = "/~https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } sp-staking = { git = "/~https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" } +paste = "1.0.12" [features] default = ["std"] diff --git a/tokens/src/fungibles_conformance_tests/inspect_mutate.rs b/tokens/src/fungibles_conformance_tests/inspect_mutate.rs new file mode 100644 index 000000000..2210b5d24 --- /dev/null +++ b/tokens/src/fungibles_conformance_tests/inspect_mutate.rs @@ -0,0 +1,1119 @@ +use super::*; +use core::fmt::Debug; +use frame_support::traits::{ + fungibles::{Inspect, Mutate}, + tokens::{DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence}, +}; +use sp_arithmetic::traits::AtLeast8BitUnsigned; +use sp_runtime::traits::{Bounded, Zero}; + +/// Test the `mint_into` function for successful token minting. +/// +/// This test checks the `mint_into` function in the `Mutate` trait +/// implementation for type `T`. It ensures that account balances and total +/// issuance values are updated correctly after minting tokens into two distinct +/// accounts. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn mint_into_success(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account_0 = AccountId::from([10u8; 32]); + let account_1 = AccountId::from([11u8; 32]); + + // Test: Mint an amount into each account + let amount_0 = T::minimum_balance(asset_id); + let amount_1 = T::minimum_balance(asset_id) + 5.into(); + T::mint_into(asset_id, &account_0, amount_0).unwrap(); + T::mint_into(asset_id, &account_1, amount_1).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(asset_id, &account_0), amount_0); + assert_eq!(T::total_balance(asset_id, &account_1), amount_1); + assert_eq!(T::balance(asset_id, &account_0), amount_0); + assert_eq!(T::balance(asset_id, &account_1), amount_1); + + // Verify: Total issuance is updated correctly + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + amount_0 + amount_1 + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + amount_0 + amount_1 + ); +} + +/// Test the `mint_into` function for overflow prevention. +/// +/// This test ensures that minting tokens beyond the maximum balance value for +/// an account returns an error and does not change the account balance or total +/// issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn mint_into_overflow(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account = AccountId::from([10u8; 32]); + let amount = T::Balance::max_value() - 5.into() - initial_total_issuance; + + // Mint just below the maximum balance + T::mint_into(asset_id, &account, amount).unwrap(); + + // Verify: Minting beyond the maximum balance value returns an Err + T::mint_into(asset_id, &account, 10.into()).unwrap_err(); + + // Verify: The balance did not change + assert_eq!(T::total_balance(asset_id, &account), amount); + assert_eq!(T::balance(asset_id, &account), amount); + + // Verify: The total issuance did not change + assert_eq!(T::total_issuance(asset_id), initial_total_issuance + amount); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance + amount); +} + +/// Test the `mint_into` function for handling balances below the minimum value. +/// +/// This test verifies that minting tokens below the minimum balance for an +/// account returns an error and has no impact on the account balance or total +/// issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn mint_into_below_minimum( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + // Skip if there is no minimum balance + if T::minimum_balance(asset_id) == T::Balance::zero() { + return; + } + + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account = AccountId::from([10u8; 32]); + let amount = T::minimum_balance(asset_id) - 1.into(); + + // Verify: Minting below the minimum balance returns Err + T::mint_into(asset_id, &account, amount).unwrap_err(); + + // Verify: noop + assert_eq!(T::total_balance(asset_id, &account), T::Balance::zero()); + assert_eq!(T::balance(asset_id, &account), T::Balance::zero()); + assert_eq!(T::total_issuance(asset_id), initial_total_issuance); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance); +} + +/// Test the `burn_from` function for successfully burning an exact amount of +/// tokens. +/// +/// This test checks that the `burn_from` function with `Precision::Exact` +/// correctly reduces the account balance and total issuance values by the +/// burned amount. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate` for `AccountId`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn burn_from_exact_success( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + + // Setup account + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Test: Burn an exact amount from the account + let amount_to_burn = T::Balance::from(5); + let precision = Precision::Exact; + let force = Fortitude::Polite; + T::burn_from(asset_id, &account, amount_to_burn, precision, force).unwrap(); + + // Verify: The balance and total issuance should be reduced by the burned amount + assert_eq!(T::balance(asset_id, &account), initial_balance - amount_to_burn); + assert_eq!(T::total_balance(asset_id, &account), initial_balance - amount_to_burn); + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + initial_balance - amount_to_burn + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + initial_balance - amount_to_burn + ); +} + +/// Test the `burn_from` function for successfully burning tokens with a +/// best-effort approach. +/// +/// This test verifies that the `burn_from` function with +/// `Precision::BestEffort` correctly reduces the account balance and total +/// issuance values by the reducible balance when attempting to burn an amount +/// greater than the reducible balance. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate` for `AccountId`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn burn_from_best_effort_success( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + + // Setup account + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Get reducible balance + let force = Fortitude::Polite; + let reducible_balance = T::reducible_balance(asset_id, &account, Preservation::Expendable, force); + + // Test: Burn a best effort amount from the account that is greater than the + // reducible balance + let amount_to_burn = reducible_balance + 5.into(); + let precision = Precision::BestEffort; + assert!(amount_to_burn > reducible_balance); + assert!(amount_to_burn > T::balance(asset_id, &account)); + T::burn_from(asset_id, &account, amount_to_burn, precision, force).unwrap(); + + // Verify: The balance and total issuance should be reduced by the + // reducible_balance + assert_eq!(T::balance(asset_id, &account), initial_balance - reducible_balance); + assert_eq!( + T::total_balance(asset_id, &account), + initial_balance - reducible_balance + ); + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + initial_balance - reducible_balance + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + initial_balance - reducible_balance + ); +} + +/// Test the `burn_from` function for handling insufficient funds with +/// `Precision::Exact`. +/// +/// This test verifies that burning an amount greater than the account's balance +/// with `Precision::Exact` returns an error and does not change the account +/// balance or total issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn burn_from_exact_insufficient_funds( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + // Set up the initial conditions and parameters for the test + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + + // Verify: Burn an amount greater than the account's balance with Exact + // precision returns Err + let amount_to_burn = initial_balance + 10.into(); + let precision = Precision::Exact; + let force = Fortitude::Polite; + T::burn_from(asset_id, &account, amount_to_burn, precision, force).unwrap_err(); + + // Verify: The balance and total issuance should remain unchanged + assert_eq!(T::balance(asset_id, &account), initial_balance); + assert_eq!(T::total_balance(asset_id, &account), initial_balance); + assert_eq!(T::total_issuance(asset_id), initial_total_issuance); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance); +} + +/// Test the `restore` function for successful restoration. +/// +/// This test verifies that restoring an amount into each account updates their +/// balances and the total issuance values correctly. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn restore_success(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let account_0 = AccountId::from([10u8; 32]); + let account_1 = AccountId::from([11u8; 32]); + + // Test: Restore an amount into each account + let amount_0 = T::minimum_balance(asset_id); + let amount_1 = T::minimum_balance(asset_id) + 5.into(); + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + T::restore(asset_id, &account_0, amount_0).unwrap(); + T::restore(asset_id, &account_1, amount_1).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(asset_id, &account_0), amount_0); + assert_eq!(T::total_balance(asset_id, &account_1), amount_1); + assert_eq!(T::balance(asset_id, &account_0), amount_0); + assert_eq!(T::balance(asset_id, &account_1), amount_1); + + // Verify: Total issuance is updated correctly + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + amount_0 + amount_1 + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + amount_0 + amount_1 + ); +} + +/// Test the `restore` function for handling balance overflow. +/// +/// This test verifies that restoring an amount beyond the maximum balance +/// returns an error and does not change the account balance or total issuance +/// values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn restore_overflow(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account = AccountId::from([10u8; 32]); + let amount = T::Balance::max_value() - 5.into() - initial_total_issuance; + + // Restore just below the maximum balance + T::restore(asset_id, &account, amount).unwrap(); + + // Verify: Restoring beyond the maximum balance returns an Err + T::restore(asset_id, &account, 10.into()).unwrap_err(); + + // Verify: The balance and total issuance did not change + assert_eq!(T::total_balance(asset_id, &account), amount); + assert_eq!(T::balance(asset_id, &account), amount); + assert_eq!(T::total_issuance(asset_id), initial_total_issuance + amount); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance + amount); +} + +/// Test the `restore` function for handling restoration below the minimum +/// balance. +/// +/// This test verifies that restoring an amount below the minimum balance +/// returns an error and does not change the account balance or total issuance +/// values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn restore_below_minimum(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + // Skip if there is no minimum balance + if T::minimum_balance(asset_id) == T::Balance::zero() { + return; + } + + let account = AccountId::from([10u8; 32]); + let amount = T::minimum_balance(asset_id) - 1.into(); + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + + // Verify: Restoring below the minimum balance returns Err + T::restore(asset_id, &account, amount).unwrap_err(); + + // Verify: noop + assert_eq!(T::total_balance(asset_id, &account), T::Balance::zero()); + assert_eq!(T::balance(asset_id, &account), T::Balance::zero()); + assert_eq!(T::total_issuance(asset_id), initial_total_issuance); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance); +} + +/// Test the `shelve` function for successful shelving. +/// +/// This test verifies that shelving an amount from an account reduces the +/// account balance and total issuance values by the shelved amount. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn shelve_success(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + + // Setup account + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + + T::restore(asset_id, &account, initial_balance).unwrap(); + + // Test: Shelve an amount from the account + let amount_to_shelve = T::Balance::from(5); + T::shelve(asset_id, &account, amount_to_shelve).unwrap(); + + // Verify: The balance and total issuance should be reduced by the shelved + // amount + assert_eq!(T::balance(asset_id, &account), initial_balance - amount_to_shelve); + assert_eq!(T::total_balance(asset_id, &account), initial_balance - amount_to_shelve); + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + initial_balance - amount_to_shelve + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + initial_balance - amount_to_shelve + ); +} + +/// Test the `shelve` function for handling insufficient funds. +/// +/// This test verifies that attempting to shelve an amount greater than the +/// account's balance returns an error and does not change the account balance +/// or total issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn shelve_insufficient_funds( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + + // Set up the initial conditions and parameters for the test + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::restore(asset_id, &account, initial_balance).unwrap(); + + // Verify: Shelving greater than the balance with Exact precision returns Err + let amount_to_shelve = initial_balance + 10.into(); + T::shelve(asset_id, &account, amount_to_shelve).unwrap_err(); + + // Verify: The balance and total issuance should remain unchanged + assert_eq!(T::balance(asset_id, &account), initial_balance); + assert_eq!(T::total_balance(asset_id, &account), initial_balance); + assert_eq!(T::total_issuance(asset_id), initial_total_issuance + initial_balance); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance + initial_balance); +} + +/// Test the `transfer` function for a successful transfer. +/// +/// This test verifies that transferring an amount between two accounts with +/// `Preservation::Expendable` updates the account balances and maintains the +/// total issuance and active issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn transfer_success(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account_0 = AccountId::from([10u8; 32]); + let account_1 = AccountId::from([11u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::set_balance(asset_id, &account_0, initial_balance); + T::set_balance(asset_id, &account_1, initial_balance); + + // Test: Transfer an amount from account_0 to account_1 + let transfer_amount = T::Balance::from(3); + T::transfer( + asset_id, + &account_0, + &account_1, + transfer_amount, + Preservation::Expendable, + ) + .unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!( + T::total_balance(asset_id, &account_0), + initial_balance - transfer_amount + ); + assert_eq!( + T::total_balance(asset_id, &account_1), + initial_balance + transfer_amount + ); + assert_eq!(T::balance(asset_id, &account_0), initial_balance - transfer_amount); + assert_eq!(T::balance(asset_id, &account_1), initial_balance + transfer_amount); + + // Verify: Total issuance doesn't change + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + initial_balance * 2.into() + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + initial_balance * 2.into() + ); +} + +/// Test the `transfer` function with `Preservation::Expendable` for +/// transferring the entire balance. +/// +/// This test verifies that transferring the entire balance from one account to +/// another with `Preservation::Expendable` updates the account balances and +/// maintains the total issuance and active issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn transfer_expendable_all( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account_0 = AccountId::from([10u8; 32]); + let account_1 = AccountId::from([11u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::set_balance(asset_id, &account_0, initial_balance); + T::set_balance(asset_id, &account_1, initial_balance); + + // Test: Transfer entire balance from account_0 to account_1 + let preservation = Preservation::Expendable; + let transfer_amount = initial_balance; + T::transfer(asset_id, &account_0, &account_1, transfer_amount, preservation).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(asset_id, &account_0), T::Balance::zero()); + assert_eq!(T::total_balance(asset_id, &account_1), initial_balance * 2.into()); + assert_eq!(T::balance(asset_id, &account_0), T::Balance::zero()); + assert_eq!(T::balance(asset_id, &account_1), initial_balance * 2.into()); + + // Verify: Total issuance doesn't change + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + initial_balance * 2.into() + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + initial_balance * 2.into() + ); +} + +/// Test the transfer function with Preservation::Expendable for transferring +/// amounts that leaves an account with less than the minimum balance. +/// +/// This test verifies that when transferring an amount using +/// Preservation::Expendable and an account will be left with less than the +/// minimum balance, the account balances are updated, dust is collected +/// properly depending on whether a dust_trap exists, and the total issuance and +/// active issuance values remain consistent. +/// +/// # Parameters +/// +/// - dust_trap: An optional account identifier to which dust will be collected. +/// If None, dust will be removed from the total and active issuance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn transfer_expendable_dust( + asset_id: >::AssetId, + dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + if T::minimum_balance(asset_id) == T::Balance::zero() { + return; + } + + let account_0 = AccountId::from([10u8; 32]); + let account_1 = AccountId::from([20u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::set_balance(asset_id, &account_0, initial_balance); + T::set_balance(asset_id, &account_1, initial_balance); + + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let initial_dust_trap_balance = match dust_trap { + Some(ref dust_trap) => T::total_balance(asset_id, &dust_trap), + None => T::Balance::zero(), + }; + + // Test: Transfer balance + let preservation = Preservation::Expendable; + let transfer_amount = T::Balance::from(11); + T::transfer(asset_id, &account_0, &account_1, transfer_amount, preservation).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(asset_id, &account_0), T::Balance::zero()); + assert_eq!( + T::total_balance(asset_id, &account_1), + initial_balance + transfer_amount + ); + assert_eq!(T::balance(asset_id, &account_0), T::Balance::zero()); + assert_eq!(T::balance(asset_id, &account_1), initial_balance + transfer_amount); + + match dust_trap { + Some(ref dust_trap) => { + // Verify: Total issuance and active issuance don't change + assert_eq!(T::total_issuance(asset_id), initial_total_issuance); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance); + // Verify: Dust is collected into dust trap + assert_eq!( + T::total_balance(asset_id, &dust_trap), + initial_dust_trap_balance + T::minimum_balance(asset_id) - 1.into() + ); + assert_eq!( + T::balance(asset_id, &dust_trap), + initial_dust_trap_balance + T::minimum_balance(asset_id) - 1.into() + ); + } + None => { + // Verify: Total issuance and active issuance are reduced by the dust amount + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance - T::minimum_balance(asset_id) + 1.into() + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance - T::minimum_balance(asset_id) + 1.into() + ); + } + } +} + +/// Test the `transfer` function with `Preservation::Protect` and +/// `Preservation::Preserve` for transferring the entire balance. +/// +/// This test verifies that attempting to transfer the entire balance with +/// `Preservation::Protect` or `Preservation::Preserve` returns an error, and +/// the account balances, total issuance, and active issuance values remain +/// unchanged. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn transfer_protect_preserve( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + // This test means nothing if there is no minimum balance + if T::minimum_balance(asset_id) == T::Balance::zero() { + return; + } + + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account_0 = AccountId::from([10u8; 32]); + let account_1 = AccountId::from([11u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::set_balance(asset_id, &account_0, initial_balance); + T::set_balance(asset_id, &account_1, initial_balance); + + // Verify: Transfer Protect entire balance from account_0 to account_1 should + // Err + let preservation = Preservation::Protect; + let transfer_amount = initial_balance; + T::transfer(asset_id, &account_0, &account_1, transfer_amount, preservation).unwrap_err(); + + // Verify: Noop + assert_eq!(T::total_balance(asset_id, &account_0), initial_balance); + assert_eq!(T::total_balance(asset_id, &account_1), initial_balance); + assert_eq!(T::balance(asset_id, &account_0), initial_balance); + assert_eq!(T::balance(asset_id, &account_1), initial_balance); + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + initial_balance * 2.into() + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + initial_balance * 2.into() + ); + + // Verify: Transfer Preserve entire balance from account_0 to account_1 should + // Err + let preservation = Preservation::Preserve; + T::transfer(asset_id, &account_0, &account_1, transfer_amount, preservation).unwrap_err(); + + // Verify: Noop + assert_eq!(T::total_balance(asset_id, &account_0), initial_balance); + assert_eq!(T::total_balance(asset_id, &account_1), initial_balance); + assert_eq!(T::balance(asset_id, &account_0), initial_balance); + assert_eq!(T::balance(asset_id, &account_1), initial_balance); + assert_eq!( + T::total_issuance(asset_id), + initial_total_issuance + initial_balance * 2.into() + ); + assert_eq!( + T::active_issuance(asset_id), + initial_active_issuance + initial_balance * 2.into() + ); +} + +/// Test the set_balance function for successful minting. +/// +/// This test verifies that minting a balance using set_balance updates the +/// account balance, total issuance, and active issuance correctly. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn set_balance_mint_success( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Test: Increase the account balance with set_balance + let increase_amount: T::Balance = 5.into(); + let new = T::set_balance(asset_id, &account, initial_balance + increase_amount); + + // Verify: set_balance returned the new balance + let expected_new = initial_balance + increase_amount; + assert_eq!(new, expected_new); + + // Verify: Balance and issuance is updated correctly + assert_eq!(T::total_balance(asset_id, &account), expected_new); + assert_eq!(T::balance(asset_id, &account), expected_new); + assert_eq!(T::total_issuance(asset_id), initial_total_issuance + expected_new); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance + expected_new); +} + +/// Test the set_balance function for successful burning. +/// +/// This test verifies that burning a balance using set_balance updates the +/// account balance, total issuance, and active issuance correctly. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn set_balance_burn_success( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let initial_total_issuance = T::total_issuance(asset_id); + let initial_active_issuance = T::active_issuance(asset_id); + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Test: Increase the account balance with set_balance + let burn_amount: T::Balance = 5.into(); + let new = T::set_balance(asset_id, &account, initial_balance - burn_amount); + + // Verify: set_balance returned the new balance + let expected_new = initial_balance - burn_amount; + assert_eq!(new, expected_new); + + // Verify: Balance and issuance is updated correctly + assert_eq!(T::total_balance(asset_id, &account), expected_new); + assert_eq!(T::balance(asset_id, &account), expected_new); + assert_eq!(T::total_issuance(asset_id), initial_total_issuance + expected_new); + assert_eq!(T::active_issuance(asset_id), initial_active_issuance + expected_new); +} + +/// Test the can_deposit function for returning a success value. +/// +/// This test verifies that the can_deposit function returns +/// DepositConsequence::Success when depositing a reasonable amount. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_deposit_success(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Test: can_deposit a reasonable amount + let ret = T::can_deposit(asset_id, &account, 5.into(), Provenance::Minted); + + // Verify: Returns success + assert_eq!(ret, DepositConsequence::Success); +} + +/// Test the can_deposit function for returning a minimum balance error. +/// +/// This test verifies that the can_deposit function returns +/// DepositConsequence::BelowMinimum when depositing below the minimum balance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_deposit_below_minimum( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + // can_deposit always returns Success for amount 0 + if T::minimum_balance(asset_id) < 2.into() { + return; + } + + let account = AccountId::from([10u8; 32]); + + // Test: can_deposit below the minimum + let ret = T::can_deposit( + asset_id, + &account, + T::minimum_balance(asset_id) - 1.into(), + Provenance::Minted, + ); + + // Verify: Returns success + assert_eq!(ret, DepositConsequence::BelowMinimum); +} + +/// Test the can_deposit function for returning an overflow error. +/// +/// This test verifies that the can_deposit function returns +/// DepositConsequence::Overflow when depositing an amount that would cause an +/// overflow. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_deposit_overflow(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let account = AccountId::from([10u8; 32]); + + // Test: Try deposit over the max balance + let initial_balance = T::Balance::max_value() - 5.into() - T::total_issuance(asset_id); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + let ret = T::can_deposit(asset_id, &account, 10.into(), Provenance::Minted); + + // Verify: Returns success + assert_eq!(ret, DepositConsequence::Overflow); +} + +/// Test the can_withdraw function for returning a success value. +/// +/// This test verifies that the can_withdraw function returns +/// WithdrawConsequence::Success when withdrawing a reasonable amount. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_withdraw_success(asset_id: >::AssetId, _dust_trap: Option) +where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Test: can_withdraw a reasonable amount + let ret = T::can_withdraw(asset_id, &account, 5.into()); + + // Verify: Returns success + assert_eq!(ret, WithdrawConsequence::Success); +} + +/// Test the can_withdraw function for withdrawal resulting in a reduced balance +/// of zero. +/// +/// This test verifies that the can_withdraw function returns +/// WithdrawConsequence::ReducedToZero when withdrawing an amount that would +/// reduce the account balance below the minimum balance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_withdraw_reduced_to_zero( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + if T::minimum_balance(asset_id) == T::Balance::zero() { + return; + } + + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Verify: can_withdraw below the minimum balance returns ReducedToZero + let ret = T::can_withdraw(asset_id, &account, 1.into()); + assert_eq!( + ret, + WithdrawConsequence::ReducedToZero(T::minimum_balance(asset_id) - 1.into()) + ); +} + +/// Test the can_withdraw function for returning a low balance error. +/// +/// This test verifies that the can_withdraw function returns +/// WithdrawConsequence::BalanceLow when withdrawing an amount that would result +/// in an account balance below the current balance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_withdraw_balance_low( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + if T::minimum_balance(asset_id) == T::Balance::zero() { + return; + } + + let account = AccountId::from([10u8; 32]); + let other_account = AccountId::from([100u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 5.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + T::mint_into(asset_id, &other_account, initial_balance * 2.into()).unwrap(); + + // Verify: can_withdraw below the account balance returns BalanceLow + let ret = T::can_withdraw(asset_id, &account, initial_balance + 1.into()); + assert_eq!(ret, WithdrawConsequence::BalanceLow); +} + +/// Test the reducible_balance function with Preservation::Expendable. +/// +/// This test verifies that the reducible_balance function returns the full +/// account balance when using Preservation::Expendable. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn reducible_balance_expendable( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Verify: reducible_balance returns the full balance + let ret = T::reducible_balance(asset_id, &account, Preservation::Expendable, Fortitude::Polite); + assert_eq!(ret, initial_balance); +} + +/// Test the reducible_balance function with Preservation::Protect and +/// Preservation::Preserve. +/// +/// This test verifies that the reducible_balance function returns the account +/// balance minus the minimum balance when using either Preservation::Protect or +/// Preservation::Preserve. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn reducible_balance_protect_preserve( + asset_id: >::AssetId, + _dust_trap: Option, +) where + T: Mutate, + >::AssetId: Copy, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: From<[u8; 32]>, +{ + let account = AccountId::from([10u8; 32]); + let initial_balance = T::minimum_balance(asset_id) + 10.into(); + T::mint_into(asset_id, &account, initial_balance).unwrap(); + + // Verify: reducible_balance returns the full balance - min balance + let ret = T::reducible_balance(asset_id, &account, Preservation::Protect, Fortitude::Polite); + assert_eq!(ret, initial_balance - T::minimum_balance(asset_id)); + let ret = T::reducible_balance(asset_id, &account, Preservation::Preserve, Fortitude::Polite); + assert_eq!(ret, initial_balance - T::minimum_balance(asset_id)); +} diff --git a/tokens/src/fungibles_conformance_tests/mod.rs b/tokens/src/fungibles_conformance_tests/mod.rs new file mode 100644 index 000000000..291223473 --- /dev/null +++ b/tokens/src/fungibles_conformance_tests/mod.rs @@ -0,0 +1,3 @@ +#![cfg(test)] + +pub mod inspect_mutate; diff --git a/tokens/src/lib.rs b/tokens/src/lib.rs index f1df0aed9..10f18f35f 100644 --- a/tokens/src/lib.rs +++ b/tokens/src/lib.rs @@ -69,6 +69,7 @@ use orml_traits::{ MultiReservableCurrency, NamedMultiReservableCurrency, }; +mod fungibles_conformance_tests; mod impl_currency; mod impl_fungibles; mod impls; @@ -77,6 +78,7 @@ mod tests; mod tests_currency_adapter; mod tests_events; mod tests_fungibles; +mod tests_fungibles_conformance; mod tests_multicurrency; mod weights; diff --git a/tokens/src/tests_fungibles_conformance.rs b/tokens/src/tests_fungibles_conformance.rs new file mode 100644 index 000000000..090455270 --- /dev/null +++ b/tokens/src/tests_fungibles_conformance.rs @@ -0,0 +1,75 @@ +#![cfg(test)] + +use super::*; +use crate::fungibles_conformance_tests; +use mock::*; +use paste::paste; + +macro_rules! run_tests { + ($path:path, $currency_id:expr, $($name:ident),*) => { + $( + paste! { + #[test] + fn [< $name _ $currency_id _dust_trap_on >]() { + let trap_account = DustReceiverAccount::get(); + let builder = ExtBuilder::default(); + builder.build().execute_with(|| { + >::set_balance($currency_id, &trap_account, Tokens::minimum_balance($currency_id)); + $path::$name::< + Tokens, + ::AccountId, + >($currency_id, Some(trap_account)); + }); + } + + #[test] + fn [< $name _ $currency_id _dust_trap_off >]() { + let trap_account = DustReceiverAccount::get(); + let builder = ExtBuilder::default(); + builder.build().execute_with(|| { + GetDustReceiverAccount::set(None); + $path::$name::< + Tokens, + ::AccountId, + >($currency_id, None); + }); + } + } + )* + }; + ($path:path, $currency_id:expr) => { + run_tests!( + $path, + $currency_id, + mint_into_success, + mint_into_overflow, + mint_into_below_minimum, + burn_from_exact_success, + burn_from_best_effort_success, + burn_from_exact_insufficient_funds, + restore_success, + restore_overflow, + restore_below_minimum, + shelve_success, + shelve_insufficient_funds, + transfer_success, + transfer_expendable_all, + transfer_expendable_dust, + transfer_protect_preserve, + set_balance_mint_success, + set_balance_burn_success, + can_deposit_success, + can_deposit_below_minimum, + can_deposit_overflow, + can_withdraw_success, + can_withdraw_reduced_to_zero, + can_withdraw_balance_low, + reducible_balance_expendable, + reducible_balance_protect_preserve + ); + }; +} + +run_tests!(fungibles_conformance_tests::inspect_mutate, DOT); +run_tests!(fungibles_conformance_tests::inspect_mutate, BTC); +run_tests!(fungibles_conformance_tests::inspect_mutate, ETH);