From 40950c53c9ff32d0a4b1a54879b5a5169cd3b742 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:34:52 +0600 Subject: [PATCH 01/11] Use canonical balances pallet, add assets support to wormhole --- Cargo.lock | 36 +- Cargo.toml | 3 +- node/src/benchmarking.rs | 4 + pallets/balances/Cargo.toml | 63 - pallets/balances/README.md | 127 -- pallets/balances/src/benchmarking.rs | 350 ---- pallets/balances/src/impl_currency.rs | 952 ---------- pallets/balances/src/impl_fungible.rs | 385 ---- pallets/balances/src/impl_proofs.rs | 28 - pallets/balances/src/lib.rs | 1338 -------------- pallets/balances/src/migration.rs | 103 -- pallets/balances/src/tests/currency_tests.rs | 1643 ----------------- .../balances/src/tests/dispatchable_tests.rs | 410 ---- .../src/tests/fungible_conformance_tests.rs | 141 -- pallets/balances/src/tests/fungible_tests.rs | 883 --------- pallets/balances/src/tests/general_tests.rs | 143 -- pallets/balances/src/tests/mod.rs | 330 ---- .../balances/src/tests/reentrancy_tests.rs | 212 --- .../src/tests/transfer_counter_tests.rs | 336 ---- pallets/balances/src/types.rs | 164 -- pallets/balances/src/weights.rs | 300 --- pallets/mining-rewards/src/lib.rs | 31 +- pallets/mining-rewards/src/mock.rs | 21 + pallets/wormhole/Cargo.toml | 1 + pallets/wormhole/src/lib.rs | 174 +- pallets/wormhole/src/mock.rs | 41 +- pallets/wormhole/src/tests.rs | 326 ++-- primitives/wormhole/src/lib.rs | 31 +- runtime/Cargo.toml | 2 + runtime/src/configs/mod.rs | 13 +- runtime/src/genesis_config_presets.rs | 15 +- runtime/src/lib.rs | 1 + runtime/src/transaction_extensions.rs | 251 ++- runtime/tests/governance/treasury.rs | 9 +- 34 files changed, 642 insertions(+), 8225 deletions(-) delete mode 100644 pallets/balances/Cargo.toml delete mode 100644 pallets/balances/README.md delete mode 100644 pallets/balances/src/benchmarking.rs delete mode 100644 pallets/balances/src/impl_currency.rs delete mode 100644 pallets/balances/src/impl_fungible.rs delete mode 100644 pallets/balances/src/impl_proofs.rs delete mode 100644 pallets/balances/src/lib.rs delete mode 100644 pallets/balances/src/migration.rs delete mode 100644 pallets/balances/src/tests/currency_tests.rs delete mode 100644 pallets/balances/src/tests/dispatchable_tests.rs delete mode 100644 pallets/balances/src/tests/fungible_conformance_tests.rs delete mode 100644 pallets/balances/src/tests/fungible_tests.rs delete mode 100644 pallets/balances/src/tests/general_tests.rs delete mode 100644 pallets/balances/src/tests/mod.rs delete mode 100644 pallets/balances/src/tests/reentrancy_tests.rs delete mode 100644 pallets/balances/src/tests/transfer_counter_tests.rs delete mode 100644 pallets/balances/src/types.rs delete mode 100644 pallets/balances/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 6a2353e4..b5fbec03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7103,26 +7103,6 @@ dependencies = [ "sp-staking", ] -[[package]] -name = "pallet-balances" -version = "40.0.1" -dependencies = [ - "docify", - "frame-benchmarking", - "frame-support", - "frame-system", - "log", - "pallet-transaction-payment", - "parity-scale-codec", - "paste", - "qp-poseidon", - "qp-wormhole", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", -] - [[package]] name = "pallet-balances" version = "42.0.0" @@ -7185,7 +7165,7 @@ dependencies = [ "frame-support", "frame-system", "log", - "pallet-balances 40.0.1", + "pallet-balances", "pallet-vesting", "parity-scale-codec", "scale-info", @@ -7223,7 +7203,7 @@ dependencies = [ "frame-support", "frame-system", "log", - "pallet-balances 40.0.1", + "pallet-balances", "parity-scale-codec", "qp-wormhole", "scale-info", @@ -7344,7 +7324,7 @@ dependencies = [ "log", "pallet-assets", "pallet-assets-holder", - "pallet-balances 40.0.1", + "pallet-balances", "pallet-preimage", "pallet-recovery", "pallet-scheduler", @@ -7603,7 +7583,7 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "log", - "pallet-balances 42.0.0", + "pallet-balances", "parity-scale-codec", "scale-info", "serde", @@ -7652,7 +7632,8 @@ dependencies = [ "hex", "lazy_static", "log", - "pallet-balances 40.0.1", + "pallet-assets", + "pallet-balances", "parity-scale-codec", "qp-dilithium-crypto", "qp-header", @@ -8250,7 +8231,7 @@ dependencies = [ "pallet-authority-discovery", "pallet-authorship", "pallet-babe", - "pallet-balances 42.0.0", + "pallet-balances", "pallet-broker", "pallet-message-queue", "pallet-mmr", @@ -9288,7 +9269,7 @@ dependencies = [ "log", "pallet-assets", "pallet-assets-holder", - "pallet-balances 40.0.1", + "pallet-balances", "pallet-conviction-voting", "pallet-merkle-airdrop", "pallet-mining-rewards", @@ -9313,6 +9294,7 @@ dependencies = [ "qp-header", "qp-poseidon", "qp-scheduler", + "qp-wormhole", "qp-wormhole-circuit", "qp-wormhole-verifier", "qp-zk-circuits-common", diff --git a/Cargo.toml b/Cargo.toml index 45a9c307..61ea142d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ members = [ "client/network", "miner-api", "node", - "pallets/balances", "pallets/merkle-airdrop", "pallets/mining-rewards", "pallets/qpow", @@ -121,7 +120,7 @@ wasm-timer = { version = "0.2.5" } zeroize = { version = "1.7.0", default-features = false } # Own dependencies -pallet-balances = { path = "./pallets/balances", default-features = false } +pallet-balances = { version = "42.0.0", default-features = false } pallet-merkle-airdrop = { path = "./pallets/merkle-airdrop", default-features = false } pallet-mining-rewards = { path = "./pallets/mining-rewards", default-features = false } pallet-qpow = { path = "./pallets/qpow", default-features = false } diff --git a/node/src/benchmarking.rs b/node/src/benchmarking.rs index 2d3d33eb..2dd9a8ef 100644 --- a/node/src/benchmarking.rs +++ b/node/src/benchmarking.rs @@ -127,6 +127,9 @@ pub fn create_benchmark_extrinsic( quantus_runtime::transaction_extensions::ReversibleTransactionExtension::< runtime::Runtime, >::new(), + quantus_runtime::transaction_extensions::WormholeProofRecorderExtension::< + runtime::Runtime, + >::new(), ); let raw_payload = runtime::SignedPayload::from_raw( @@ -143,6 +146,7 @@ pub fn create_benchmark_extrinsic( (), None, (), + (), ), ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); diff --git a/pallets/balances/Cargo.toml b/pallets/balances/Cargo.toml deleted file mode 100644 index 8c7fa972..00000000 --- a/pallets/balances/Cargo.toml +++ /dev/null @@ -1,63 +0,0 @@ -[package] -authors.workspace = true -description = "FRAME pallet to manage balances" -edition.workspace = true -homepage.workspace = true -license = "Apache-2.0" -name = "pallet-balances" -readme = "README.md" -repository.workspace = true -version = "40.0.1" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { features = ["derive", "max-encoded-len"], workspace = true } -docify = { workspace = true } -frame-benchmarking = { optional = true, workspace = true } -frame-support.workspace = true -frame-system.workspace = true -log.workspace = true -qp-poseidon = { workspace = true, features = ["serde"] } -qp-wormhole = { workspace = true } -scale-info = { features = ["derive"], workspace = true } -sp-runtime.workspace = true - -[dev-dependencies] -frame-support = { workspace = true, features = ["experimental"], default-features = true } -pallet-transaction-payment.features = ["std"] -pallet-transaction-payment.workspace = true -paste.workspace = true -sp-core.workspace = true -sp-io.workspace = true - -[features] -default = ["std"] -std = [ - "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - "log/std", - "pallet-transaction-payment/std", - "qp-poseidon/std", - "qp-wormhole/std", - "scale-info/std", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", -] -# Enable support for setting the existential deposit to zero. -insecure_zero_ed = [] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", -] diff --git a/pallets/balances/README.md b/pallets/balances/README.md deleted file mode 100644 index b1e9c82f..00000000 --- a/pallets/balances/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# Balances Module - -The Balances module provides functionality for handling accounts and balances. - -- [`Config`](https://docs.rs/pallet-balances/latest/pallet_balances/pallet/trait.Config.html) -- [`Call`](https://docs.rs/pallet-balances/latest/pallet_balances/pallet/enum.Call.html) -- [`Pallet`](https://docs.rs/pallet-balances/latest/pallet_balances/pallet/struct.Pallet.html) - -## Overview - -The Balances module provides functions for: - -- Getting and setting free balances. -- Retrieving total, reserved and unreserved balances. -- Repatriating a reserved balance to a beneficiary account that exists. -- Transferring a balance between accounts (when not reserved). -- Slashing an account balance. -- Account creation and removal. -- Managing total issuance. -- Setting and managing locks. - -### Terminology - -- **Existential Deposit:** The minimum balance required to create or keep an account open. This prevents "dust accounts" -from filling storage. When the free plus the reserved balance (i.e. the total balance) fall below this, then the account - is said to be dead; and it loses its functionality as well as any prior history and all information on it is removed - from the chain's state. No account should ever have a total balance that is strictly between 0 and the existential - deposit (exclusive). If this ever happens, it indicates either a bug in this module or an erroneous raw mutation of - storage. - -- **Total Issuance:** The total number of units in existence in a system. - -- **Reaping an account:** The act of removing an account by resetting its nonce. Happens after its total balance has -become zero (or, strictly speaking, less than the Existential Deposit). - -- **Free Balance:** The portion of a balance that is not reserved. The free balance is the only balance that matters for - most operations. - -- **Reserved Balance:** Reserved balance still belongs to the account holder, but is suspended. Reserved balance can - still be slashed, but only after all the free balance has been slashed. - -- **Imbalance:** A condition when some funds were credited or debited without equal and opposite accounting (i.e. a -difference between total issuance and account balances). Functions that result in an imbalance will return an object of -the `Imbalance` trait that can be managed within your runtime logic. (If an imbalance is simply dropped, it should -automatically maintain any book-keeping such as total issuance.) - -- **Lock:** A freeze on a specified amount of an account's free balance until a specified block number. Multiple locks -always operate over the same funds, so they "overlay" rather than "stack". - -### Implementations - -The Balances module provides implementations for the following traits. If these traits provide the functionality that -you need, then you can avoid coupling with the Balances module. - -- [`Currency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.Currency.html): Functions for dealing -with a fungible assets system. -- [`ReservableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.ReservableCurrency.html): -Functions for dealing with assets that can be reserved from an account. -- [`LockableCurrency`](https://docs.rs/frame-support/latest/frame_support/traits/trait.LockableCurrency.html): Functions -for dealing with accounts that allow liquidity restrictions. -- [`Imbalance`](https://docs.rs/frame-support/latest/frame_support/traits/trait.Imbalance.html): Functions for handling -imbalances between total issuance in the system and account balances. Must be used when a function creates new funds -(e.g. a reward) or destroys some funds (e.g. a system fee). -- [`IsDeadAccount`](https://docs.rs/frame-support/latest/frame_support/traits/trait.IsDeadAccount.html): Determiner to -say whether a given account is unused. - -## Interface - -### Dispatchable Functions - -- `transfer` - Transfer some liquid free balance to another account. -- `force_set_balance` - Set the balances of a given account. The origin of this call must be root. - -## Usage - -The following examples show how to use the Balances module in your custom module. - -### Examples from the FRAME - -The Contract module uses the `Currency` trait to handle gas payment, and its types inherit from `Currency`: - -```rust -use frame_support::traits::Currency; - -pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -pub type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; - -``` - -The Staking module uses the `LockableCurrency` trait to lock a stash account's funds: - -```rust -use frame_support::traits::{WithdrawReasons, LockableCurrency}; -use sp_runtime::traits::Bounded; -pub trait Config: frame_system::Config { - type Currency: LockableCurrency>; -} - -fn update_ledger( - controller: &T::AccountId, - ledger: &StakingLedger -) { - T::Currency::set_lock( - STAKING_ID, - &ledger.stash, - ledger.total, - WithdrawReasons::all() - ); - // >::insert(controller, ledger); // Commented out as we don't have access to Staking's storage here. -} -``` - -## Genesis config - -The Balances module depends on the -[`GenesisConfig`](https://docs.rs/pallet-balances/latest/pallet_balances/pallet/struct.GenesisConfig.html). - -## Assumptions - -- Total issued balanced of all accounts should be less than `Config::Balance::max_value()`. - -License: Apache-2.0 - - -## Release - -Polkadot SDK Stable 2412 diff --git a/pallets/balances/src/benchmarking.rs b/pallets/balances/src/benchmarking.rs deleted file mode 100644 index 06f3d24d..00000000 --- a/pallets/balances/src/benchmarking.rs +++ /dev/null @@ -1,350 +0,0 @@ -// This file is part of Substrate. - -// 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. - -//! Balances pallet benchmarking. - -#![cfg(feature = "runtime-benchmarks")] - -use super::*; -use crate::Pallet as Balances; - -use frame_benchmarking::v2::*; -use frame_system::RawOrigin; -use sp_runtime::traits::Bounded; -use types::ExtraFlags; - -const SEED: u32 = 0; -// existential deposit multiplier -const ED_MULTIPLIER: u32 = 10; - -fn minimum_balance, I: 'static>() -> T::Balance { - if cfg!(feature = "insecure_zero_ed") { - 100u32.into() - } else { - T::ExistentialDeposit::get() - } -} - -#[instance_benchmarks] -mod benchmarks { - use super::*; - - // Benchmark `transfer` extrinsic with the worst possible conditions: - // * Transfer will kill the sender account. - // * Transfer will create the recipient account. - #[benchmark] - fn transfer_allow_death() { - let existential_deposit: T::Balance = minimum_balance::(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()).max(1u32.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, - // and reap this user. - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - let transfer_amount = - existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert_eq!(Balances::::free_balance(&caller), Zero::zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // Benchmark `transfer` with the best possible condition: - // * Both accounts exist and will continue to exist. - #[benchmark(extra)] - fn transfer_best_case() { - let caller = whitelisted_caller(); - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - - // Give the sender account max funds for transfer (their account will never reasonably be - // killed). - let _ = - as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); - - // Give the recipient account existential deposit (thus their account already exists). - let existential_deposit: T::Balance = minimum_balance::(); - let _ = - as Currency<_>>::make_free_balance_be(&recipient, existential_deposit); - let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - - #[extrinsic_call] - transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert!(!Balances::::free_balance(&caller).is_zero()); - assert!(!Balances::::free_balance(&recipient).is_zero()); - } - - // Benchmark `transfer_keep_alive` with the worst possible condition: - // * The recipient account is created. - #[benchmark] - fn transfer_keep_alive() { - let caller = whitelisted_caller(); - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - - // Give the sender account max funds, thus a transfer will not kill account. - let _ = - as Currency<_>>::make_free_balance_be(&caller, T::Balance::max_value()); - let existential_deposit: T::Balance = minimum_balance::(); - let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert!(!Balances::::free_balance(&caller).is_zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // Benchmark `force_set_balance` coming from ROOT account. This always creates an account. - #[benchmark] - fn force_set_balance_creating() { - let user: T::AccountId = account("user", 0, SEED); - let user_lookup = T::Lookup::unlookup(user.clone()); - - // Give the user some initial balance. - let existential_deposit: T::Balance = minimum_balance::(); - let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); - - #[extrinsic_call] - force_set_balance(RawOrigin::Root, user_lookup, balance_amount); - - assert_eq!(Balances::::free_balance(&user), balance_amount); - } - - // Benchmark `force_set_balance` coming from ROOT account. This always kills an account. - #[benchmark] - fn force_set_balance_killing() { - let user: T::AccountId = account("user", 0, SEED); - let user_lookup = T::Lookup::unlookup(user.clone()); - - // Give the user some initial balance. - let existential_deposit: T::Balance = minimum_balance::(); - let balance_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); - - #[extrinsic_call] - force_set_balance(RawOrigin::Root, user_lookup, Zero::zero()); - - assert!(Balances::::free_balance(&user).is_zero()); - } - - // Benchmark `force_transfer` extrinsic with the worst possible conditions: - // * Transfer will kill the sender account. - // * Transfer will create the recipient account. - #[benchmark] - fn force_transfer() { - let existential_deposit: T::Balance = minimum_balance::(); - let source: T::AccountId = account("source", 0, SEED); - let source_lookup = T::Lookup::unlookup(source.clone()); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&source, balance); - - // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, - // and reap this user. - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - let transfer_amount = - existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); - - #[extrinsic_call] - _(RawOrigin::Root, source_lookup, recipient_lookup, transfer_amount); - - assert_eq!(Balances::::free_balance(&source), Zero::zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // This benchmark performs the same operation as `transfer` in the worst case scenario, - // but additionally introduces many new users into the storage, increasing the the merkle - // trie and PoV size. - #[benchmark(extra)] - fn transfer_increasing_users(u: Linear<0, 1_000>) { - // 1_000 is not very much, but this upper bound can be controlled by the CLI. - let existential_deposit: T::Balance = minimum_balance::(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Transfer `e - 1` existential deposits + 1 unit, which guarantees to create one account, - // and reap this user. - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - let transfer_amount = - existential_deposit.saturating_mul((ED_MULTIPLIER - 1).into()) + 1u32.into(); - - // Create a bunch of users in storage. - for i in 0..u { - // The `account` function uses `blake2_256` to generate unique accounts, so these - // should be quite random and evenly distributed in the trie. - let new_user: T::AccountId = account("new_user", i, SEED); - let _ = as Currency<_>>::make_free_balance_be(&new_user, balance); - } - - #[extrinsic_call] - transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); - - assert_eq!(Balances::::free_balance(&caller), Zero::zero()); - assert_eq!(Balances::::free_balance(&recipient), transfer_amount); - } - - // Benchmark `transfer_all` with the worst possible condition: - // * The recipient account is created - // * The sender is killed - #[benchmark] - fn transfer_all() { - let caller = whitelisted_caller(); - let recipient: T::AccountId = account("recipient", 0, SEED); - let recipient_lookup = T::Lookup::unlookup(recipient.clone()); - - // Give some multiple of the existential deposit - let existential_deposit: T::Balance = minimum_balance::(); - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), recipient_lookup, false); - - assert!(Balances::::free_balance(&caller).is_zero()); - assert_eq!(Balances::::free_balance(&recipient), balance); - } - - #[benchmark] - fn force_unreserve() -> Result<(), BenchmarkError> { - let user: T::AccountId = account("user", 0, SEED); - let user_lookup = T::Lookup::unlookup(user.clone()); - - // Give some multiple of the existential deposit - let ed = minimum_balance::(); - let balance = ed + ed; - let _ = as Currency<_>>::make_free_balance_be(&user, balance); - - // Reserve the balance - as ReservableCurrency<_>>::reserve(&user, ed)?; - assert_eq!(Balances::::reserved_balance(&user), ed); - assert_eq!(Balances::::free_balance(&user), ed); - - #[extrinsic_call] - _(RawOrigin::Root, user_lookup, balance); - - assert!(Balances::::reserved_balance(&user).is_zero()); - assert_eq!(Balances::::free_balance(&user), ed + ed); - - Ok(()) - } - - #[benchmark] - fn upgrade_accounts(u: Linear<1, 1_000>) { - let caller: T::AccountId = whitelisted_caller(); - let who = (0..u) - .map(|i| -> T::AccountId { - let user = account("old_user", i, SEED); - let account = AccountData { - free: minimum_balance::(), - reserved: minimum_balance::(), - frozen: Zero::zero(), - flags: ExtraFlags::old_logic(), - }; - frame_system::Pallet::::inc_providers(&user); - assert!(T::AccountStore::try_mutate_exists(&user, |a| -> DispatchResult { - *a = Some(account); - Ok(()) - }) - .is_ok()); - assert!(!Balances::::account(&user).flags.is_new_logic()); - assert_eq!(frame_system::Pallet::::providers(&user), 1); - assert_eq!(frame_system::Pallet::::consumers(&user), 0); - user - }) - .collect(); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), who); - - for i in 0..u { - let user: T::AccountId = account("old_user", i, SEED); - assert!(Balances::::account(&user).flags.is_new_logic()); - assert_eq!(frame_system::Pallet::::providers(&user), 1); - assert_eq!(frame_system::Pallet::::consumers(&user), 1); - } - } - - #[benchmark] - fn force_adjust_total_issuance() { - let ti = TotalIssuance::::get(); - let delta = 123u32.into(); - - #[extrinsic_call] - _(RawOrigin::Root, AdjustmentDirection::Increase, delta); - - assert_eq!(TotalIssuance::::get(), ti + delta); - } - - /// Benchmark `burn` extrinsic with the worst possible condition - burn kills the account. - #[benchmark] - fn burn_allow_death() { - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Burn enough to kill the account. - let burn_amount = balance - existential_deposit + 1u32.into(); - - #[extrinsic_call] - burn(RawOrigin::Signed(caller.clone()), burn_amount, false); - - assert_eq!(Balances::::free_balance(&caller), Zero::zero()); - } - - // Benchmark `burn` extrinsic with the case where account is kept alive. - #[benchmark] - fn burn_keep_alive() { - let existential_deposit = T::ExistentialDeposit::get(); - let caller = whitelisted_caller(); - - // Give some multiple of the existential deposit - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); - let _ = as Currency<_>>::make_free_balance_be(&caller, balance); - - // Burn minimum possible amount which should not kill the account. - let burn_amount = 1u32.into(); - - #[extrinsic_call] - burn(RawOrigin::Signed(caller.clone()), burn_amount, true); - - assert_eq!(Balances::::free_balance(&caller), balance - burn_amount); - } - - impl_benchmark_test_suite! { - Balances, - crate::tests::ExtBuilder::default().build(), - crate::tests::Test, - } -} diff --git a/pallets/balances/src/impl_currency.rs b/pallets/balances/src/impl_currency.rs deleted file mode 100644 index 547cca75..00000000 --- a/pallets/balances/src/impl_currency.rs +++ /dev/null @@ -1,952 +0,0 @@ -// This file is part of Substrate. - -// 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 the `Currency` family of traits. -//! -//! Note that `WithdrawReasons` are intentionally not used for anything in this implementation and -//! are expected to be removed in the near future, once migration to `fungible::*` traits is done. - -use super::*; -use core::cmp::Ordering; -use frame_support::{ - ensure, - pallet_prelude::DispatchResult, - traits::{ - tokens::{ - fungible, Balance, BalanceStatus as Status, Fortitude::Polite, Precision::BestEffort, - }, - Currency, DefensiveSaturating, - ExistenceRequirement::{self, AllowDeath}, - Get, Imbalance, InspectLockableCurrency, LockIdentifier, LockableCurrency, - NamedReservableCurrency, ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons, - }, -}; -use frame_system::pallet_prelude::BlockNumberFor; -pub use imbalances::{NegativeImbalance, PositiveImbalance}; -use sp_runtime::traits::Bounded; - -// wrapping these imbalances in a private module is necessary to ensure absolute privacy -// of the inner member. -mod imbalances { - use super::*; - use core::mem; - use frame_support::traits::{tokens::imbalance::TryMerge, SameOrOther}; - - /// Opaque, move-only struct with private fields that serves as a token denoting that - /// funds have been created without any equal and opposite accounting. - #[must_use] - #[derive(RuntimeDebug, PartialEq, Eq)] - pub struct PositiveImbalance, I: 'static = ()>(T::Balance); - - impl, I: 'static> PositiveImbalance { - /// Create a new positive imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - PositiveImbalance(amount) - } - } - - /// Opaque, move-only struct with private fields that serves as a token denoting that - /// funds have been destroyed without any equal and opposite accounting. - #[must_use] - #[derive(RuntimeDebug, PartialEq, Eq)] - pub struct NegativeImbalance, I: 'static = ()>(T::Balance); - - impl, I: 'static> NegativeImbalance { - /// Create a new negative imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - NegativeImbalance(amount) - } - } - - impl, I: 'static> TryDrop for PositiveImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl, I: 'static> Default for PositiveImbalance { - fn default() -> Self { - Self::zero() - } - } - - impl, I: 'static> Imbalance for PositiveImbalance { - type Opposite = NegativeImbalance; - - fn zero() -> Self { - Self(Zero::zero()) - } - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { - Ok(()) - } else { - Err(self) - } - } - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - fn extract(&mut self, amount: T::Balance) -> Self { - let new = self.0.min(amount); - self.0 -= new; - Self(new) - } - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - match a.cmp(&b) { - Ordering::Greater => SameOrOther::Same(Self(a - b)), - Ordering::Less => SameOrOther::Other(NegativeImbalance::new(b - a)), - Ordering::Equal => SameOrOther::None, - } - } - fn peek(&self) -> T::Balance { - self.0 - } - } - - impl, I: 'static> TryMerge for PositiveImbalance { - fn try_merge(self, other: Self) -> Result { - Ok(self.merge(other)) - } - } - - impl, I: 'static> TryDrop for NegativeImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl, I: 'static> Default for NegativeImbalance { - fn default() -> Self { - Self::zero() - } - } - - impl, I: 'static> Imbalance for NegativeImbalance { - type Opposite = PositiveImbalance; - - fn zero() -> Self { - Self(Zero::zero()) - } - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { - Ok(()) - } else { - Err(self) - } - } - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - fn extract(&mut self, amount: T::Balance) -> Self { - let new = self.0.min(amount); - self.0 -= new; - Self(new) - } - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - match a.cmp(&b) { - Ordering::Greater => SameOrOther::Same(Self(a - b)), - Ordering::Less => SameOrOther::Other(PositiveImbalance::new(b - a)), - Ordering::Equal => SameOrOther::None, - } - } - fn peek(&self) -> T::Balance { - self.0 - } - } - - impl, I: 'static> TryMerge for NegativeImbalance { - fn try_merge(self, other: Self) -> Result { - Ok(self.merge(other)) - } - } - - impl, I: 'static> Drop for PositiveImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - if !self.0.is_zero() { - >::mutate(|v| *v = v.saturating_add(self.0)); - Pallet::::deposit_event(Event::::Issued { amount: self.0 }); - } - } - } - - impl, I: 'static> Drop for NegativeImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - if !self.0.is_zero() { - >::mutate(|v| *v = v.saturating_sub(self.0)); - Pallet::::deposit_event(Event::::Rescinded { amount: self.0 }); - } - } - } -} - -impl, I: 'static> Currency for Pallet -where - T::Balance: Balance, -{ - type Balance = T::Balance; - type PositiveImbalance = PositiveImbalance; - type NegativeImbalance = NegativeImbalance; - - fn total_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).total() - } - - // Check if `value` amount of free balance can be slashed from `who`. - fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { - if value.is_zero() { - return true; - } - Self::free_balance(who) >= value - } - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - - fn active_issuance() -> Self::Balance { - >::active_issuance() - } - - fn deactivate(amount: Self::Balance) { - >::deactivate(amount); - } - - fn reactivate(amount: Self::Balance) { - >::reactivate(amount); - } - - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - - // Burn funds from the total issuance, returning a positive imbalance for the amount burned. - // Is a no-op if amount to be burned is zero. - fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { - if amount.is_zero() { - return PositiveImbalance::zero(); - } - >::mutate(|issued| { - *issued = issued.checked_sub(&amount).unwrap_or_else(|| { - amount = *issued; - Zero::zero() - }); - }); - - Pallet::::deposit_event(Event::::Rescinded { amount }); - PositiveImbalance::new(amount) - } - - // Create new funds into the total issuance, returning a negative imbalance - // for the amount issued. - // Is a no-op if amount to be issued it zero. - fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { - if amount.is_zero() { - return NegativeImbalance::zero(); - } - >::mutate(|issued| { - *issued = issued.checked_add(&amount).unwrap_or_else(|| { - amount = Self::Balance::max_value() - *issued; - Self::Balance::max_value() - }) - }); - - Pallet::::deposit_event(Event::::Issued { amount }); - NegativeImbalance::new(amount) - } - - fn free_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).free - } - - // Ensure that an account can withdraw from their free balance given any existing withdrawal - // restrictions like locks and vesting balance. - // Is a no-op if amount to be withdrawn is zero. - fn ensure_can_withdraw( - who: &T::AccountId, - amount: T::Balance, - _reasons: WithdrawReasons, - new_balance: T::Balance, - ) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - ensure!(new_balance >= Self::account(who).frozen, Error::::LiquidityRestrictions); - Ok(()) - } - - // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. - // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. - fn transfer( - transactor: &T::AccountId, - dest: &T::AccountId, - value: Self::Balance, - existence_requirement: ExistenceRequirement, - ) -> DispatchResult { - if value.is_zero() || transactor == dest { - return Ok(()); - } - let keep_alive = match existence_requirement { - ExistenceRequirement::KeepAlive => Preserve, - ExistenceRequirement::AllowDeath => Expendable, - }; - >::transfer(transactor, dest, value, keep_alive)?; - Ok(()) - } - - /// Slash a target account `who`, returning the negative imbalance created and any left over - /// amount that could not be slashed. - /// - /// Is a no-op if `value` to be slashed is zero or the account does not exist. - /// - /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn - /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid - /// having to draw from reserved funds, however we err on the side of punishment if things are - /// inconsistent or `can_slash` wasn't used appropriately. - fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()); - } - if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value); - } - - match Self::try_mutate_account_handling_dust( - who, - |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { - // Best value is the most amount we can slash following liveness rules. - let ed = T::ExistentialDeposit::get(); - let actual = match system::Pallet::::can_dec_provider(who) { - true => value.min(account.free), - false => value.min(account.free.saturating_sub(ed)), - }; - account.free.saturating_reduce(actual); - let remaining = value.saturating_sub(actual); - Ok((NegativeImbalance::new(actual), remaining)) - }, - ) { - Ok((imbalance, remaining)) => { - Self::deposit_event(Event::Slashed { - who: who.clone(), - amount: value.saturating_sub(remaining), - }); - (imbalance, remaining) - }, - Err(_) => (Self::NegativeImbalance::zero(), value), - } - } - - /// Deposit some `value` into the free balance of an existing target account `who`. - /// - /// Is a no-op if the `value` to be deposited is zero. - fn deposit_into_existing( - who: &T::AccountId, - value: Self::Balance, - ) -> Result { - if value.is_zero() { - return Ok(PositiveImbalance::zero()); - } - - Self::try_mutate_account_handling_dust( - who, - |account, is_new| -> Result { - ensure!(!is_new, Error::::DeadAccount); - account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); - Ok(PositiveImbalance::new(value)) - }, - ) - } - - /// Deposit some `value` into the free balance of `who`, possibly creating a new account. - /// - /// This function is a no-op if: - /// - the `value` to be deposited is zero; or - /// - the `value` to be deposited is less than the required ED and the account does not yet - /// exist; or - /// - the deposit would necessitate the account to exist and there are no provider references; - /// or - /// - `value` is so large it would cause the balance of `who` to overflow. - fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { - if value.is_zero() { - return Self::PositiveImbalance::zero(); - } - - Self::try_mutate_account_handling_dust( - who, - |account, is_new| -> Result { - let ed = T::ExistentialDeposit::get(); - ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); - - // defensive only: overflow should never happen, however in case it does, then this - // operation is a no-op. - account.free = match account.free.checked_add(&value) { - Some(x) => x, - None => return Ok(Self::PositiveImbalance::zero()), - }; - - Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); - Ok(PositiveImbalance::new(value)) - }, - ) - .unwrap_or_else(|_| Self::PositiveImbalance::zero()) - } - - /// Withdraw some free balance from an account, respecting existence requirements. - /// - /// Is a no-op if value to be withdrawn is zero. - fn withdraw( - who: &T::AccountId, - value: Self::Balance, - reasons: WithdrawReasons, - liveness: ExistenceRequirement, - ) -> result::Result { - if value.is_zero() { - return Ok(NegativeImbalance::zero()); - } - - Self::try_mutate_account_handling_dust( - who, - |account, _| -> Result { - let new_free_account = - account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; - - // bail if we need to keep the account alive and this would kill it. - let ed = T::ExistentialDeposit::get(); - let would_be_dead = new_free_account < ed; - let would_kill = would_be_dead && account.free >= ed; - ensure!(liveness == AllowDeath || !would_kill, Error::::Expendability); - - Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; - - account.free = new_free_account; - - Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value }); - Ok(NegativeImbalance::new(value)) - }, - ) - } - - /// Force the new free balance of a target account `who` to some new value `balance`. - fn make_free_balance_be( - who: &T::AccountId, - value: Self::Balance, - ) -> SignedImbalance { - Self::try_mutate_account_handling_dust( - who, - |account, - is_new| - -> Result, DispatchError> { - let ed = T::ExistentialDeposit::get(); - // If we're attempting to set an existing account to less than ED, then - // bypass the entire operation. It's a no-op if you follow it through, but - // since this is an instance where we might account for a negative imbalance - // (in the dust cleaner of set_account) before we account for its actual - // equal and opposite cause (returned as an Imbalance), then in the - // instance that there's no other accounts on the system at all, we might - // underflow the issuance and our arithmetic will be off. - ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); - - let imbalance = if account.free <= value { - SignedImbalance::Positive(PositiveImbalance::new(value - account.free)) - } else { - SignedImbalance::Negative(NegativeImbalance::new(account.free - value)) - }; - account.free = value; - Self::deposit_event(Event::BalanceSet { who: who.clone(), free: account.free }); - Ok(imbalance) - }, - ) - .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) - } -} - -impl, I: 'static> ReservableCurrency for Pallet -where - T::Balance: Balance, -{ - /// Check if `who` can reserve `value` from their free balance. - /// - /// Always `true` if value to be reserved is zero. - fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { - if value.is_zero() { - return true; - } - Self::account(who).free.checked_sub(&value).is_some_and(|new_balance| { - new_balance >= T::ExistentialDeposit::get() && - Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance) - .is_ok() - }) - } - - fn reserved_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).reserved - } - - /// Move `value` from the free balance from `who` to their reserved balance. - /// - /// Is a no-op if value to be reserved is zero. - fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { - if value.is_zero() { - return Ok(()); - } - - Self::try_mutate_account_handling_dust(who, |account, _| -> DispatchResult { - account.free = - account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; - account.reserved = - account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, account.free) - })?; - - Self::deposit_event(Event::Reserved { who: who.clone(), amount: value }); - Ok(()) - } - - /// Unreserve some funds, returning any amount that was unable to be unreserved. - /// - /// Is a no-op if the value to be unreserved is zero or the account does not exist. - /// - /// NOTE: returns amount value which wasn't successfully unreserved. - fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { - if value.is_zero() { - return Zero::zero(); - } - if Self::total_balance(who).is_zero() { - return value; - } - - let actual = match Self::mutate_account_handling_dust(who, |account| { - let actual = cmp::min(account.reserved, value); - account.reserved -= actual; - // defensive only: this can never fail since total issuance which is at least - // free+reserved fits into the same data type. - account.free = account.free.defensive_saturating_add(actual); - actual - }) { - Ok(x) => x, - Err(_) => { - // This should never happen since we don't alter the total amount in the account. - // If it ever does, then we should fail gracefully though, indicating that nothing - // could be done. - return value; - }, - }; - - Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual }); - value - actual - } - - /// Slash from reserved balance, returning the negative imbalance created, - /// and any amount that was unable to be slashed. - /// - /// Is a no-op if the value to be slashed is zero or the account does not exist. - fn slash_reserved( - who: &T::AccountId, - value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()); - } - if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value); - } - - // NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an - // account is attempted to be illegally destroyed. - - match Self::mutate_account_handling_dust(who, |account| { - let actual = value.min(account.reserved); - account.reserved.saturating_reduce(actual); - - // underflow should never happen, but it if does, there's nothing to be done here. - (NegativeImbalance::new(actual), value.saturating_sub(actual)) - }) { - Ok((imbalance, not_slashed)) => { - Self::deposit_event(Event::Slashed { - who: who.clone(), - amount: value.saturating_sub(not_slashed), - }); - (imbalance, not_slashed) - }, - Err(_) => (Self::NegativeImbalance::zero(), value), - } - } - - /// Move the reserved balance of one account into the balance of another, according to `status`. - /// - /// Is a no-op if: - /// - the value to be moved is zero; or - /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. - /// - /// This is `Polite` and thus will not repatriate any funds which would lead the total balance - /// to be less than the frozen amount. Returns `Ok` with the actual amount of funds moved, - /// which may be less than `value` since the operation is done an a `BestEffort` basis. - fn repatriate_reserved( - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: Status, - ) -> Result { - let actual = - Self::do_transfer_reserved(slashed, beneficiary, value, BestEffort, Polite, status)?; - Ok(value.saturating_sub(actual)) - } -} - -impl, I: 'static> NamedReservableCurrency for Pallet -where - T::Balance: Balance, -{ - type ReserveIdentifier = T::ReserveIdentifier; - - fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance { - let reserves = Self::reserves(who); - reserves - .binary_search_by_key(id, |data| data.id) - .map(|index| reserves[index].amount) - .unwrap_or_default() - } - - /// Move `value` from the free balance from `who` to a named reserve balance. - /// - /// Is a no-op if value to be reserved is zero. - fn reserve_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> DispatchResult { - if value.is_zero() { - return Ok(()); - } - - Reserves::::try_mutate(who, |reserves| -> DispatchResult { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - reserves[index].amount = reserves[index] - .amount - .checked_add(&value) - .ok_or(ArithmeticError::Overflow)?; - }, - Err(index) => { - reserves - .try_insert(index, ReserveData { id: *id, amount: value }) - .map_err(|_| Error::::TooManyReserves)?; - }, - }; - >::reserve(who, value)?; - Ok(()) - }) - } - - /// Unreserve some funds, returning any amount that was unable to be unreserved. - /// - /// Is a no-op if the value to be unreserved is zero. - fn unreserve_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> Self::Balance { - if value.is_zero() { - return Zero::zero(); - } - - Reserves::::mutate_exists(who, |maybe_reserves| -> Self::Balance { - if let Some(reserves) = maybe_reserves.as_mut() { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let remain = >::unreserve(who, to_change); - - // remain should always be zero but just to be defensive here. - let actual = to_change.defensive_saturating_sub(remain); - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; - - if reserves[index].amount.is_zero() { - if reserves.len() == 1 { - // no more named reserves - *maybe_reserves = None; - } else { - // remove this named reserve - reserves.remove(index); - } - } - - value - actual - }, - Err(_) => value, - } - } else { - value - } - }) - } - - /// Slash from reserved balance, returning the negative imbalance created, - /// and any amount that was unable to be slashed. - /// - /// Is a no-op if the value to be slashed is zero. - fn slash_reserved_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()); - } - - Reserves::::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let (imb, remain) = - >::slash_reserved(who, to_change); - - // remain should always be zero but just to be defensive here. - let actual = to_change.defensive_saturating_sub(remain); - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; - - Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual }); - (imb, value - actual) - }, - Err(_) => (NegativeImbalance::zero(), value), - } - }) - } - - /// Move the reserved balance of one account into the balance of another, according to `status`. - /// If `status` is `Reserved`, the balance will be reserved with given `id`. - /// - /// Is a no-op if: - /// - the value to be moved is zero; or - /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. - fn repatriate_reserved_named( - id: &Self::ReserveIdentifier, - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: Status, - ) -> Result { - if value.is_zero() { - return Ok(Zero::zero()); - } - - if slashed == beneficiary { - return match status { - Status::Free => Ok(Self::unreserve_named(id, slashed, value)), - Status::Reserved => - Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))), - }; - } - - Reserves::::try_mutate(slashed, |reserves| -> Result { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let actual = if status == Status::Reserved { - // make it the reserved under same identifier - Reserves::::try_mutate( - beneficiary, - |reserves| -> Result { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let remain = - >::repatriate_reserved( - slashed, - beneficiary, - to_change, - status, - )?; - - // remain should always be zero but just to be defensive - // here. - let actual = to_change.defensive_saturating_sub(remain); - - // this add can't overflow but just to be defensive. - reserves[index].amount = - reserves[index].amount.defensive_saturating_add(actual); - - Ok(actual) - }, - Err(index) => { - let remain = - >::repatriate_reserved( - slashed, - beneficiary, - to_change, - status, - )?; - - // remain should always be zero but just to be defensive - // here - let actual = to_change.defensive_saturating_sub(remain); - - reserves - .try_insert( - index, - ReserveData { id: *id, amount: actual }, - ) - .map_err(|_| Error::::TooManyReserves)?; - - Ok(actual) - }, - } - }, - )? - } else { - let remain = >::repatriate_reserved( - slashed, - beneficiary, - to_change, - status, - )?; - - // remain should always be zero but just to be defensive here - to_change.defensive_saturating_sub(remain) - }; - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; - - Ok(value - actual) - }, - Err(_) => Ok(value), - } - }) - } -} - -impl, I: 'static> LockableCurrency for Pallet -where - T::Balance: Balance, -{ - type Moment = BlockNumberFor; - - type MaxLocks = T::MaxLocks; - - // Set or alter a lock on the balance of `who`. - fn set_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - ) { - if reasons.is_empty() || amount.is_zero() { - Self::remove_lock(id, who); - return; - } - - let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock) - } - Self::update_locks(who, &locks[..]); - } - - // Extend a lock on the balance of `who`. - // Is a no-op if lock amount is zero or `reasons` `is_none()`. - fn extend_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - ) { - if amount.is_zero() || reasons.is_empty() { - return; - } - let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| { - if l.id == id { - new_lock.take().map(|nl| BalanceLock { - id: l.id, - amount: l.amount.max(nl.amount), - reasons: l.reasons | nl.reasons, - }) - } else { - Some(l) - } - }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock) - } - Self::update_locks(who, &locks[..]); - } - - fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let mut locks = Self::locks(who); - locks.retain(|l| l.id != id); - Self::update_locks(who, &locks[..]); - } -} - -impl, I: 'static> InspectLockableCurrency for Pallet { - fn balance_locked(id: LockIdentifier, who: &T::AccountId) -> Self::Balance { - Self::locks(who) - .into_iter() - .filter(|l| l.id == id) - .fold(Zero::zero(), |acc, l| acc + l.amount) - } -} diff --git a/pallets/balances/src/impl_fungible.rs b/pallets/balances/src/impl_fungible.rs deleted file mode 100644 index e5e43e04..00000000 --- a/pallets/balances/src/impl_fungible.rs +++ /dev/null @@ -1,385 +0,0 @@ -// This file is part of Substrate. - -// 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. - -//! Implementation of `fungible` traits for Balances pallet. -use super::*; -use frame_support::traits::{ - tokens::{ - Fortitude, - Preservation::{self, Preserve, Protect}, - Provenance::{self, Minted}, - }, - AccountTouch, -}; - -impl, I: 'static> fungible::Inspect for Pallet { - type Balance = T::Balance; - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - fn active_issuance() -> Self::Balance { - TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) - } - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - fn total_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).total() - } - fn balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).free - } - fn reducible_balance( - who: &T::AccountId, - preservation: Preservation, - force: Fortitude, - ) -> Self::Balance { - let a = Self::account(who); - let mut untouchable = Zero::zero(); - if force == Polite { - // Frozen balance applies to total. Anything on hold therefore gets discounted from the - // limit given by the freezes. - untouchable = a.frozen.saturating_sub(a.reserved); - } - // If we want to keep our provider ref.. - if preservation == Preserve - // ..or we don't want the account to die and our provider ref is needed for it to live.. - || preservation == Protect && !a.free.is_zero() && - frame_system::Pallet::::providers(who) == 1 - // ..or we don't care about the account dying but our provider ref is required.. - || preservation == Expendable && !a.free.is_zero() && - !frame_system::Pallet::::can_dec_provider(who) - { - // ..then the ED needed.. - untouchable = untouchable.max(T::ExistentialDeposit::get()); - } - // Liquid balance is what is neither on hold nor frozen/required for provider. - a.free.saturating_sub(untouchable) - } - fn can_deposit( - who: &T::AccountId, - amount: Self::Balance, - provenance: Provenance, - ) -> DepositConsequence { - if amount.is_zero() { - return DepositConsequence::Success; - } - - if provenance == Minted && TotalIssuance::::get().checked_add(&amount).is_none() { - return DepositConsequence::Overflow; - } - - let account = Self::account(who); - let new_free = match account.free.checked_add(&amount) { - None => return DepositConsequence::Overflow, - Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, - Some(x) => x, - }; - - match account.reserved.checked_add(&new_free) { - Some(_) => {}, - None => return DepositConsequence::Overflow, - }; - - // NOTE: We assume that we are a provider, so don't need to do any checks in the - // case of account creation. - - DepositConsequence::Success - } - fn can_withdraw( - who: &T::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - if amount.is_zero() { - return WithdrawConsequence::Success; - } - - if TotalIssuance::::get().checked_sub(&amount).is_none() { - return WithdrawConsequence::Underflow; - } - - let account = Self::account(who); - let new_free_balance = match account.free.checked_sub(&amount) { - Some(x) => x, - None => return WithdrawConsequence::BalanceLow, - }; - - let liquid = Self::reducible_balance(who, Expendable, Polite); - if amount > liquid { - return WithdrawConsequence::Frozen; - } - - // Provider restriction - total account balance cannot be reduced to zero if it cannot - // sustain the loss of a provider reference. - // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, - // then this will need to adapt accordingly. - let ed = T::ExistentialDeposit::get(); - let success = if new_free_balance < ed { - if frame_system::Pallet::::can_dec_provider(who) { - WithdrawConsequence::ReducedToZero(new_free_balance) - } else { - return WithdrawConsequence::WouldDie; - } - } else { - WithdrawConsequence::Success - }; - - let new_total_balance = new_free_balance.saturating_add(account.reserved); - - // Eventual free funds must be no less than the frozen balance. - if new_total_balance < account.frozen { - return WithdrawConsequence::Frozen; - } - - success - } -} - -impl, I: 'static> fungible::Unbalanced for Pallet { - fn handle_dust(dust: fungible::Dust) { - T::DustRemoval::on_unbalanced(dust.into_credit()); - } - fn write_balance( - who: &T::AccountId, - amount: Self::Balance, - ) -> Result, DispatchError> { - let max_reduction = - >::reducible_balance(who, Expendable, Force); - let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult { - // Make sure the reduction (if there is one) is no more than the maximum allowed. - let reduction = account.free.saturating_sub(amount); - ensure!(reduction <= max_reduction, Error::::InsufficientBalance); - - account.free = amount; - Ok(()) - })?; - result?; - Ok(maybe_dust) - } - - fn set_total_issuance(amount: Self::Balance) { - TotalIssuance::::mutate(|t| *t = amount); - } - - fn deactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| { - // InactiveIssuance cannot be greater than TotalIssuance. - *b = b.saturating_add(amount).min(TotalIssuance::::get()); - }); - } - - fn reactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); - } -} - -impl, I: 'static> fungible::Mutate for Pallet { - fn done_mint_into(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Minted { who: who.clone(), amount }); - } - fn done_burn_from(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Burned { who: who.clone(), amount }); - } - fn done_shelve(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Suspended { who: who.clone(), amount }); - } - fn done_restore(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Restored { who: who.clone(), amount }); - } - fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Transfer { - from: source.clone(), - to: dest.clone(), - amount, - }); - } -} - -impl, I: 'static> fungible::MutateHold for Pallet {} - -impl, I: 'static> fungible::InspectHold for Pallet { - type Reason = T::RuntimeHoldReason; - - fn total_balance_on_hold(who: &T::AccountId) -> T::Balance { - Self::account(who).reserved - } - fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance { - // The total balance must never drop below the freeze requirements if we're not forcing: - let a = Self::account(who); - let unavailable = if force == Force { - Self::Balance::zero() - } else { - // The freeze lock applies to the total balance, so we can discount the free balance - // from the amount which the total reserved balance must provide to satisfy it. - a.frozen.saturating_sub(a.free) - }; - a.reserved.saturating_sub(unavailable) - } - fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance { - Holds::::get(who) - .iter() - .find(|x| &x.id == reason) - .map_or_else(Zero::zero, |x| x.amount) - } - fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool { - if frame_system::Pallet::::providers(who) == 0 { - return false; - } - let holds = Holds::::get(who); - if holds.is_full() && !holds.iter().any(|x| &x.id == reason) { - return false; - } - true - } -} - -impl, I: 'static> fungible::UnbalancedHold for Pallet { - fn set_balance_on_hold( - reason: &Self::Reason, - who: &T::AccountId, - amount: Self::Balance, - ) -> DispatchResult { - let mut new_account = Self::account(who); - let mut holds = Holds::::get(who); - let mut increase = true; - let mut delta = amount; - - if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { - delta = item.amount.max(amount) - item.amount.min(amount); - increase = amount > item.amount; - item.amount = amount; - holds.retain(|x| !x.amount.is_zero()); - } else if !amount.is_zero() { - holds - .try_push(IdAmount { id: *reason, amount }) - .map_err(|_| Error::::TooManyHolds)?; - } - - new_account.reserved = if increase { - new_account.reserved.checked_add(&delta).ok_or(ArithmeticError::Overflow)? - } else { - new_account.reserved.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? - }; - - let (result, maybe_dust) = Self::try_mutate_account(who, |a, _| -> DispatchResult { - *a = new_account; - Ok(()) - })?; - debug_assert!( - maybe_dust.is_none(), - "Does not alter main balance; dust only happens when it is altered; qed" - ); - Holds::::insert(who, holds); - Ok(result) - } -} - -impl, I: 'static> fungible::InspectFreeze for Pallet { - type Id = T::FreezeIdentifier; - - fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance { - let locks = Freezes::::get(who); - locks.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount) - } - - fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool { - let l = Freezes::::get(who); - !l.is_full() || l.iter().any(|x| &x.id == id) - } -} - -impl, I: 'static> fungible::MutateFreeze for Pallet { - fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Self::thaw(id, who); - } - let mut locks = Freezes::::get(who); - if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { - i.amount = amount; - } else { - locks - .try_push(IdAmount { id: *id, amount }) - .map_err(|_| Error::::TooManyFreezes)?; - } - Self::update_freezes(who, locks.as_bounded_slice()) - } - - fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - let mut locks = Freezes::::get(who); - if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { - i.amount = i.amount.max(amount); - } else { - locks - .try_push(IdAmount { id: *id, amount }) - .map_err(|_| Error::::TooManyFreezes)?; - } - Self::update_freezes(who, locks.as_bounded_slice()) - } - - fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult { - let mut locks = Freezes::::get(who); - locks.retain(|l| &l.id != id); - Self::update_freezes(who, locks.as_bounded_slice()) - } -} - -impl, I: 'static> fungible::Balanced for Pallet { - type OnDropCredit = fungible::DecreaseIssuance; - type OnDropDebt = fungible::IncreaseIssuance; - - fn done_deposit(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Deposit { who: who.clone(), amount }); - } - fn done_withdraw(who: &T::AccountId, amount: Self::Balance) { - Self::deposit_event(Event::::Withdraw { who: who.clone(), amount }); - } - fn done_issue(amount: Self::Balance) { - if !amount.is_zero() { - Self::deposit_event(Event::::Issued { amount }); - } - } - fn done_rescind(amount: Self::Balance) { - Self::deposit_event(Event::::Rescinded { amount }); - } -} - -impl, I: 'static> fungible::BalancedHold for Pallet {} - -impl, I: 'static> - fungible::hold::DoneSlash for Pallet -{ - fn done_slash(reason: &T::RuntimeHoldReason, who: &T::AccountId, amount: T::Balance) { - T::DoneSlashHandler::done_slash(reason, who, amount); - } -} - -impl, I: 'static> AccountTouch<(), T::AccountId> for Pallet { - type Balance = T::Balance; - fn deposit_required(_: ()) -> Self::Balance { - Self::Balance::zero() - } - fn should_touch(_: (), _: &T::AccountId) -> bool { - false - } - fn touch(_: (), _: &T::AccountId, _: &T::AccountId) -> DispatchResult { - Ok(()) - } -} diff --git a/pallets/balances/src/impl_proofs.rs b/pallets/balances/src/impl_proofs.rs deleted file mode 100644 index 517225f5..00000000 --- a/pallets/balances/src/impl_proofs.rs +++ /dev/null @@ -1,28 +0,0 @@ -use super::*; -use qp_wormhole::TransferProofs; - -impl, I: 'static> TransferProofs - for Pallet -{ - fn transfer_proof_exists( - count: TransferCountType, - from: &T::AccountId, - to: &T::AccountId, - value: T::Balance, - ) -> bool { - TransferProof::::get((count, from, to, value)).is_some() - } - - fn store_transfer_proof(from: &T::AccountId, to: &T::AccountId, value: T::Balance) { - Pallet::::do_store_transfer_proof(from, to, value); - } - - fn transfer_proof_key( - transfer_count: u64, - from: T::AccountId, - to: T::AccountId, - value: T::Balance, - ) -> Vec { - Pallet::::transfer_proof_storage_key(transfer_count, from, to, value) - } -} diff --git a/pallets/balances/src/lib.rs b/pallets/balances/src/lib.rs deleted file mode 100644 index 20165bea..00000000 --- a/pallets/balances/src/lib.rs +++ /dev/null @@ -1,1338 +0,0 @@ -// This file is part of Substrate. - -#![allow(clippy::all)] -// 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. - -//! # Balances Pallet -//! -//! The Balances pallet provides functionality for handling accounts and balances for a single -//! token. -//! -//! It makes heavy use of concepts such as Holds and Freezes from the -//! [`frame_support::traits::fungible`] traits, therefore you should read and understand those docs -//! as a prerequisite to understanding this pallet. -//! -//! Also see the [`frame_tokens`] reference docs for higher level information regarding the -//! place of this palet in FRAME. -//! -//! ## Overview -//! -//! The Balances pallet provides functions for: -//! -//! - Getting and setting free balances. -//! - Retrieving total, reserved and unreserved balances. -//! - Repatriating a reserved balance to a beneficiary account that exists. -//! - Transferring a balance between accounts (when not reserved). -//! - Slashing an account balance. -//! - Account creation and removal. -//! - Managing total issuance. -//! - Setting and managing locks. -//! -//! ### Terminology -//! -//! - **Reaping an account:** The act of removing an account by resetting its nonce. Happens after -//! its total balance has become less than the Existential Deposit. -//! -//! ### Implementations -//! -//! The Balances pallet provides implementations for the following [`fungible`] traits. If these -//! traits provide the functionality that you need, then you should avoid tight coupling with the -//! Balances pallet. -//! -//! - [`fungible::Inspect`] -//! - [`fungible::Mutate`] -//! - [`fungible::Unbalanced`] -//! - [`fungible::Balanced`] -//! - [`fungible::BalancedHold`] -//! - [`fungible::InspectHold`] -//! - [`fungible::MutateHold`] -//! - [`fungible::InspectFreeze`] -//! - [`fungible::MutateFreeze`] -//! - [`fungible::Imbalance`] -//! -//! It also implements the following [`Currency`] related traits, however they are deprecated and -//! will eventually be removed. -//! -//! - [`Currency`]: Functions for dealing with a fungible assets system. -//! - [`ReservableCurrency`] -//! - [`NamedReservableCurrency`](frame_support::traits::NamedReservableCurrency): Functions for -//! dealing with assets that can be reserved from an account. -//! - [`LockableCurrency`](frame_support::traits::LockableCurrency): Functions for dealing with -//! accounts that allow liquidity restrictions. -//! - [`Imbalance`](frame_support::traits::Imbalance): Functions for handling imbalances between -//! total issuance in the system and account balances. Must be used when a function creates new -//! funds (e.g. a reward) or destroys some funds (e.g. a system fee). -//! -//! ## Usage -//! -//! The following examples show how to use the Balances pallet in your custom pallet. -//! -//! ### Examples from the FRAME -//! -//! The Contract pallet uses the `Currency` trait to handle gas payment, and its types inherit from -//! `Currency`: -//! -//! ``` -//! use frame_support::traits::Currency; -//! # pub trait Config: frame_system::Config { -//! # type Currency: Currency; -//! # } -//! -//! pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -//! pub type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; -//! -//! # fn main() {} -//! ``` -//! -//! The Staking pallet uses the `LockableCurrency` trait to lock a stash account's funds: -//! -//! ``` -//! use frame_support::traits::{WithdrawReasons, LockableCurrency}; -//! use sp_runtime::traits::Bounded; -//! pub trait Config: frame_system::Config { -//! type Currency: LockableCurrency>; -//! } -//! # struct StakingLedger { -//! # stash: ::AccountId, -//! # total: <::Currency as frame_support::traits::Currency<::AccountId>>::Balance, -//! # phantom: std::marker::PhantomData, -//! # } -//! # const STAKING_ID: [u8; 8] = *b"staking "; -//! -//! fn update_ledger( -//! controller: &T::AccountId, -//! ledger: &StakingLedger -//! ) { -//! T::Currency::set_lock( -//! STAKING_ID, -//! &ledger.stash, -//! ledger.total, -//! WithdrawReasons::all() -//! ); -//! // >::insert(controller, ledger); // Commented out as we don't have access to Staking's storage here. -//! } -//! # fn main() {} -//! ``` -//! -//! ## Genesis config -//! -//! The Balances pallet depends on the [`GenesisConfig`]. -//! -//! ## Assumptions -//! -//! * Total issued balanced of all accounts should be less than `Config::Balance::max_value()`. -//! * Existential Deposit is set to a value greater than zero. -//! -//! Note, you may find the Balances pallet still functions with an ED of zero when the -//! `insecure_zero_ed` cargo feature is enabled. However this is not a configuration which is -//! generally supported, nor will it be. -//! -//! [`frame_tokens`]: ../polkadot_sdk_docs/reference_docs/frame_tokens/index.html - -#![cfg_attr(not(feature = "std"), no_std)] -mod benchmarking; -mod impl_currency; -mod impl_fungible; -mod impl_proofs; -pub mod migration; -mod tests; -mod types; -pub mod weights; - -extern crate alloc; - -use alloc::vec::Vec; -use codec::{Codec, Decode, Encode, MaxEncodedLen}; -use core::{cmp, fmt::Debug, marker::PhantomData, mem, result}; -use frame_support::{ - __private::metadata_ir::StorageHasherIR, - ensure, - pallet_prelude::DispatchResult, - traits::{ - tokens::{ - fungible, Balance as BalanceT, BalanceStatus as Status, DepositConsequence, - Fortitude::{self, Force, Polite}, - IdAmount, - Preservation::{Expendable, Preserve, Protect}, - WithdrawConsequence, - }, - Currency, Defensive, Get, OnUnbalanced, ReservableCurrency, StoredMap, - }, - BoundedSlice, StorageHasher, WeakBoundedVec, -}; -use frame_system as system; -pub use impl_currency::{NegativeImbalance, PositiveImbalance}; -pub use pallet::*; -use qp_poseidon::PoseidonHasher as PoseidonCore; -use scale_info::TypeInfo; -use sp_runtime::{ - traits::{ - AtLeast32BitUnsigned, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating, - StaticLookup, Zero, - }, - ArithmeticError, DispatchError, FixedPointOperand, Perbill, RuntimeDebug, TokenError, -}; -pub use types::{ - AccountData, AdjustmentDirection, BalanceLock, DustCleaner, ExtraFlags, Reasons, ReserveData, -}; -pub use weights::WeightInfo; - -const LOG_TARGET: &str = "runtime::balances"; - -type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; - -pub struct PoseidonStorageHasher(PhantomData); - -impl StorageHasher - for PoseidonStorageHasher -{ - // We are lying here, but maybe it's ok because it's just metadata - const METADATA: StorageHasherIR = StorageHasherIR::Identity; - type Output = [u8; 32]; - - fn hash(x: &[u8]) -> Self::Output { - PoseidonCore::hash_storage::(x) - } - - fn max_len() -> usize { - 32 - } -} - -#[frame_support::pallet] -pub mod pallet { - use super::*; - use frame_support::{ - pallet_prelude::*, - traits::{fungible::Credit, tokens::Precision, VariantCount, VariantCountOf}, - }; - use frame_system::pallet_prelude::*; - - pub type CreditOf = Credit<::AccountId, Pallet>; - pub type TransferCountType = u64; - - /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. - pub mod config_preludes { - use super::*; - use frame_support::{derive_impl, traits::ConstU64}; - - pub struct TestDefaultConfig; - - #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)] - impl frame_system::DefaultConfig for TestDefaultConfig {} - - #[frame_support::register_default_impl(TestDefaultConfig)] - impl DefaultConfig for TestDefaultConfig { - #[inject_runtime_type] - type RuntimeHoldReason = (); - #[inject_runtime_type] - type RuntimeFreezeReason = (); - - type Balance = u64; - type ExistentialDeposit = ConstU64<1>; - - type ReserveIdentifier = (); - type FreezeIdentifier = Self::RuntimeFreezeReason; - - type DustRemoval = (); - - type MaxLocks = ConstU32<100>; - type MaxReserves = ConstU32<100>; - type MaxFreezes = VariantCountOf; - - type WeightInfo = (); - type DoneSlashHandler = (); - } - } - - #[pallet::config(with_default)] - pub trait Config: frame_system::Config { - /// The overarching hold reason. - #[pallet::no_default_bounds] - type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount; - - /// The overarching freeze reason. - #[pallet::no_default_bounds] - type RuntimeFreezeReason: VariantCount; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - - /// The balance of an account. - type Balance: Parameter - + Member - + AtLeast32BitUnsigned - + Codec - + Default - + Copy - + MaybeSerializeDeserialize - + Debug - + MaxEncodedLen - + TypeInfo - + FixedPointOperand - + BalanceT; - - /// Handler for the unbalanced reduction when removing a dust account. - #[pallet::no_default_bounds] - type DustRemoval: OnUnbalanced>; - - /// The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO! - /// - /// If you *really* need it to be zero, you can enable the feature `insecure_zero_ed` for - /// this pallet. However, you do so at your own risk: this will open up a major DoS vector. - /// In case you have multiple sources of provider references, you may also get unexpected - /// behaviour if you set this to zero. - /// - /// Bottom line: Do yourself a favour and make it at least one! - #[pallet::constant] - #[pallet::no_default_bounds] - type ExistentialDeposit: Get; - - /// The means of storing the balances of an account. - #[pallet::no_default] - type AccountStore: StoredMap>; - - /// The ID type for reserves. - /// - /// Use of reserves is deprecated in favour of holds. See `https://github.com/paritytech/substrate/pull/12951/` - type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; - - /// The ID type for freezes. - type FreezeIdentifier: Parameter + Member + MaxEncodedLen + Copy; - - /// The maximum number of locks that should exist on an account. - /// Not strictly enforced, but used for weight estimation. - /// - /// Use of locks is deprecated in favour of freezes. See `https://github.com/paritytech/substrate/pull/12951/` - #[pallet::constant] - type MaxLocks: Get; - - /// The maximum number of named reserves that can exist on an account. - /// - /// Use of reserves is deprecated in favour of holds. See `https://github.com/paritytech/substrate/pull/12951/` - #[pallet::constant] - type MaxReserves: Get; - - /// The maximum number of individual freeze locks that can exist on an account at any time. - #[pallet::constant] - type MaxFreezes: Get; - - /// Allows callbacks to other pallets so they can update their bookkeeping when a slash - /// occurs. - type DoneSlashHandler: fungible::hold::DoneSlash< - Self::RuntimeHoldReason, - Self::AccountId, - Self::Balance, - >; - } - - /// The in-code storage version. - const STORAGE_VERSION: frame_support::traits::StorageVersion = - frame_support::traits::StorageVersion::new(1); - - #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData<(T, I)>); - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event, I: 'static = ()> { - /// An account was created with some free balance. - Endowed { account: T::AccountId, free_balance: T::Balance }, - /// An account was removed whose balance was non-zero but below ExistentialDeposit, - /// resulting in an outright loss. - DustLost { account: T::AccountId, amount: T::Balance }, - /// Transfer succeeded. - Transfer { from: T::AccountId, to: T::AccountId, amount: T::Balance }, - /// A balance was set by root. - BalanceSet { who: T::AccountId, free: T::Balance }, - /// Some balance was reserved (moved from free to reserved). - Reserved { who: T::AccountId, amount: T::Balance }, - /// Some balance was unreserved (moved from reserved to free). - Unreserved { who: T::AccountId, amount: T::Balance }, - /// Some balance was moved from the reserve of the first account to the second account. - /// Final argument indicates the destination balance type. - ReserveRepatriated { - from: T::AccountId, - to: T::AccountId, - amount: T::Balance, - destination_status: Status, - }, - /// Some amount was deposited (e.g. for transaction fees). - Deposit { who: T::AccountId, amount: T::Balance }, - /// Some amount was withdrawn from the account (e.g. for transaction fees). - Withdraw { who: T::AccountId, amount: T::Balance }, - /// Some amount was removed from the account (e.g. for misbehavior). - Slashed { who: T::AccountId, amount: T::Balance }, - /// Some amount was minted into an account. - Minted { who: T::AccountId, amount: T::Balance }, - /// Some amount was burned from an account. - Burned { who: T::AccountId, amount: T::Balance }, - /// Some amount was suspended from an account (it can be restored later). - Suspended { who: T::AccountId, amount: T::Balance }, - /// Some amount was restored into an account. - Restored { who: T::AccountId, amount: T::Balance }, - /// An account was upgraded. - Upgraded { who: T::AccountId }, - /// Total issuance was increased by `amount`, creating a credit to be balanced. - Issued { amount: T::Balance }, - /// Total issuance was decreased by `amount`, creating a debt to be balanced. - Rescinded { amount: T::Balance }, - /// Some balance was locked. - Locked { who: T::AccountId, amount: T::Balance }, - /// Some balance was unlocked. - Unlocked { who: T::AccountId, amount: T::Balance }, - /// Some balance was frozen. - Frozen { who: T::AccountId, amount: T::Balance }, - /// Some balance was thawed. - Thawed { who: T::AccountId, amount: T::Balance }, - /// The `TotalIssuance` was forcefully changed. - TotalIssuanceForced { old: T::Balance, new: T::Balance }, - /// Transfer proof was stored. - TransferProofStored { - transfer_count: u64, - source: T::AccountId, - dest: T::AccountId, - funding_amount: T::Balance, - }, - } - - #[pallet::error] - pub enum Error { - /// Vesting balance too high to send value. - VestingBalance, - /// Account liquidity restrictions prevent withdrawal. - LiquidityRestrictions, - /// Balance too low to send value. - InsufficientBalance, - /// Value too low to create account due to existential deposit. - ExistentialDeposit, - /// Transfer/payment would kill account. - Expendability, - /// A vesting schedule already exists for this account. - ExistingVestingSchedule, - /// Beneficiary account must pre-exist. - DeadAccount, - /// Number of named reserves exceed `MaxReserves`. - TooManyReserves, - /// Number of holds exceed `VariantCountOf`. - TooManyHolds, - /// Number of freezes exceed `MaxFreezes`. - TooManyFreezes, - /// The issuance cannot be modified since it is already deactivated. - IssuanceDeactivated, - /// The delta cannot be zero. - DeltaZero, - } - - /// The total units issued in the system. - #[pallet::storage] - #[pallet::whitelist_storage] - pub type TotalIssuance, I: 'static = ()> = StorageValue<_, T::Balance, ValueQuery>; - - /// The total units of outstanding deactivated balance in the system. - #[pallet::storage] - #[pallet::whitelist_storage] - pub type InactiveIssuance, I: 'static = ()> = - StorageValue<_, T::Balance, ValueQuery>; - - /// The Balances pallet example of storing the balance of an account. - /// - /// # Example - /// - /// ```nocompile - /// impl pallet_balances::Config for Runtime { - /// type AccountStore = StorageMapShim, frame_system::Provider, AccountId, Self::AccountData> - /// } - /// ``` - /// - /// You can also store the balance of an account in the `System` pallet. - /// - /// # Example - /// - /// ```nocompile - /// impl pallet_balances::Config for Runtime { - /// type AccountStore = System - /// } - /// ``` - /// - /// But this comes with tradeoffs, storing account balances in the system pallet stores - /// `frame_system` data alongside the account data contrary to storing account balances in the - /// `Balances` pallet, which uses a `StorageMap` to store balances data only. - /// NOTE: This is only used in the case that this pallet is used to store balances. - #[pallet::storage] - pub type Account, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::AccountId, AccountData, ValueQuery>; - - /// Any liquidity locks on some account balances. - /// NOTE: Should only be accessed when setting, changing and freeing a lock. - /// - /// Use of locks is deprecated in favour of freezes. See `https://github.com/paritytech/substrate/pull/12951/` - #[pallet::storage] - pub type Locks, I: 'static = ()> = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - WeakBoundedVec, T::MaxLocks>, - ValueQuery, - >; - - /// Named reserves on some account balances. - /// - /// Use of reserves is deprecated in favour of holds. See `https://github.com/paritytech/substrate/pull/12951/` - #[pallet::storage] - pub type Reserves, I: 'static = ()> = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec, T::MaxReserves>, - ValueQuery, - >; - - /// Holds on account balances. - #[pallet::storage] - pub type Holds, I: 'static = ()> = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec< - IdAmount, - VariantCountOf, - >, - ValueQuery, - >; - - /// Freeze locks on account balances. - #[pallet::storage] - pub type Freezes, I: 'static = ()> = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - BoundedVec, T::MaxFreezes>, - ValueQuery, - >; - - /// Transfer proofs for a wormhole transfers - #[pallet::storage] - #[pallet::getter(fn transfer_proof)] - pub type TransferProof, I: 'static = ()> = StorageMap< - _, - PoseidonStorageHasher, - (u64, T::AccountId, T::AccountId, T::Balance), // (tx_count, from, to, amount) - (), - OptionQuery, // Returns None if not present - >; - - #[pallet::storage] - #[pallet::getter(fn transfer_count)] - pub type TransferCount, I: 'static = ()> = - StorageValue<_, TransferCountType, ValueQuery>; - - #[pallet::genesis_config] - pub struct GenesisConfig, I: 'static = ()> { - pub balances: Vec<(T::AccountId, T::Balance)>, - } - - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - Self { balances: Default::default() } - } - } - - #[pallet::genesis_build] - impl, I: 'static> BuildGenesisConfig for GenesisConfig { - fn build(&self) { - let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n); - - >::put(total); - - for (_, balance) in &self.balances { - assert!( - *balance >= >::ExistentialDeposit::get(), - "the balance of any account should always be at least the existential deposit.", - ) - } - - // ensure no duplicates exist. - let endowed_accounts = self - .balances - .iter() - .map(|(x, _)| x) - .cloned() - .collect::>(); - - assert!( - endowed_accounts.len() == self.balances.len(), - "duplicate balances in genesis." - ); - - for &(ref who, free) in self.balances.iter() { - frame_system::Pallet::::inc_providers(who); - assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() }) - .is_ok()); - } - } - } - - #[pallet::hooks] - impl, I: 'static> Hooks> for Pallet { - fn integrity_test() { - #[cfg(not(feature = "insecure_zero_ed"))] - assert!( - !>::ExistentialDeposit::get().is_zero(), - "The existential deposit must be greater than zero!" - ); - - assert!( - T::MaxFreezes::get() >= ::VARIANT_COUNT, - "MaxFreezes should be greater than or equal to the number of freeze reasons: {} < {}", - T::MaxFreezes::get(), ::VARIANT_COUNT, - ); - } - - #[cfg(feature = "try-runtime")] - fn try_state(_n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { - Holds::::iter_keys().try_for_each(|k| { - if Holds::::decode_len(k).unwrap_or(0) > - T::RuntimeHoldReason::VARIANT_COUNT as usize - { - Err("Found `Hold` with too many elements") - } else { - Ok(()) - } - })?; - - Freezes::::iter_keys().try_for_each(|k| { - if Freezes::::decode_len(k).unwrap_or(0) > T::MaxFreezes::get() as usize { - Err("Found `Freeze` with too many elements") - } else { - Ok(()) - } - })?; - - Ok(()) - } - } - - #[pallet::call(weight(>::WeightInfo))] - impl, I: 'static> Pallet { - /// Transfer some liquid free balance to another account. - /// - /// `transfer_allow_death` will set the `FreeBalance` of the sender and receiver. - /// If the sender's account is below the existential deposit as a result - /// of the transfer, the account will be reaped. - /// - /// The dispatch origin for this call must be `Signed` by the transactor. - #[pallet::call_index(0)] - pub fn transfer_allow_death( - origin: OriginFor, - dest: AccountIdLookupOf, - #[pallet::compact] value: T::Balance, - ) -> DispatchResult { - let source = ensure_signed(origin)?; - let dest = T::Lookup::lookup(dest)?; - >::transfer(&source, &dest, value, Expendable)?; - Self::do_store_transfer_proof(&source, &dest, value); - Ok(()) - } - - /// Exactly as `transfer_allow_death`, except the origin must be root and the source account - /// may be specified. - #[pallet::call_index(2)] - pub fn force_transfer( - origin: OriginFor, - source: AccountIdLookupOf, - dest: AccountIdLookupOf, - #[pallet::compact] value: T::Balance, - ) -> DispatchResult { - ensure_root(origin)?; - let source = T::Lookup::lookup(source)?; - let dest = T::Lookup::lookup(dest)?; - >::transfer(&source, &dest, value, Expendable)?; - Self::do_store_transfer_proof(&source, &dest, value); - Ok(()) - } - - /// Same as the [`transfer_allow_death`] call, but with a check that the transfer will not - /// kill the origin account. - /// - /// 99% of the time you want [`transfer_allow_death`] instead. - /// - /// [`transfer_allow_death`]: struct.Pallet.html#method.transfer - #[pallet::call_index(3)] - pub fn transfer_keep_alive( - origin: OriginFor, - dest: AccountIdLookupOf, - #[pallet::compact] value: T::Balance, - ) -> DispatchResult { - let source = ensure_signed(origin)?; - let dest = T::Lookup::lookup(dest)?; - >::transfer(&source, &dest, value, Preserve)?; - Self::do_store_transfer_proof(&source, &dest, value); - Ok(()) - } - - /// Transfer the entire transferable balance from the caller account. - /// - /// NOTE: This function only attempts to transfer _transferable_ balances. This means that - /// any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be - /// transferred by this function. To ensure that this function results in a killed account, - /// you might need to prepare the account by removing any reference counters, storage - /// deposits, etc... - /// - /// The dispatch origin of this call must be Signed. - /// - /// - `dest`: The recipient of the transfer. - /// - `keep_alive`: A boolean to determine if the `transfer_all` operation should send all - /// of the funds the account has, causing the sender account to be killed (false), or - /// transfer everything except at least the existential deposit, which will guarantee to - /// keep the sender account alive (true). - #[pallet::call_index(4)] - pub fn transfer_all( - origin: OriginFor, - dest: AccountIdLookupOf, - keep_alive: bool, - ) -> DispatchResult { - let transactor = ensure_signed(origin)?; - let keep_alive = if keep_alive { Preserve } else { Expendable }; - let reducible_balance = >::reducible_balance( - &transactor, - keep_alive, - Fortitude::Polite, - ); - let dest = T::Lookup::lookup(dest)?; - >::transfer( - &transactor, - &dest, - reducible_balance, - keep_alive, - )?; - Self::do_store_transfer_proof(&transactor, &dest, reducible_balance); - Ok(()) - } - - /// Unreserve some balance from a user by force. - /// - /// Can only be called by ROOT. - #[pallet::call_index(5)] - pub fn force_unreserve( - origin: OriginFor, - who: AccountIdLookupOf, - amount: T::Balance, - ) -> DispatchResult { - ensure_root(origin)?; - let who = T::Lookup::lookup(who)?; - let _leftover = >::unreserve(&who, amount); - Ok(()) - } - - /// Upgrade a specified account. - /// - /// - `origin`: Must be `Signed`. - /// - `who`: The account to be upgraded. - /// - /// This will waive the transaction fee if at least all but 10% of the accounts needed to - /// be upgraded. (We let some not have to be upgraded just in order to allow for the - /// possibility of churn). - #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::upgrade_accounts(who.len() as u32))] - pub fn upgrade_accounts( - origin: OriginFor, - who: Vec, - ) -> DispatchResultWithPostInfo { - ensure_signed(origin)?; - if who.is_empty() { - return Ok(Pays::Yes.into()); - } - let mut upgrade_count = 0; - for i in &who { - let upgraded = Self::ensure_upgraded(i); - if upgraded { - upgrade_count.saturating_inc(); - } - } - let proportion_upgraded = Perbill::from_rational(upgrade_count, who.len() as u32); - if proportion_upgraded >= Perbill::from_percent(90) { - Ok(Pays::No.into()) - } else { - Ok(Pays::Yes.into()) - } - } - - /// Set the regular balance of a given account. - /// - /// The dispatch origin for this call is `root`. - #[pallet::call_index(8)] - #[pallet::weight( - T::WeightInfo::force_set_balance_creating() // Creates a new account. - .max(T::WeightInfo::force_set_balance_killing()) // Kills an existing account. - )] - pub fn force_set_balance( - origin: OriginFor, - who: AccountIdLookupOf, - #[pallet::compact] new_free: T::Balance, - ) -> DispatchResult { - ensure_root(origin)?; - let who = T::Lookup::lookup(who)?; - let existential_deposit = Self::ed(); - - let wipeout = new_free < existential_deposit; - let new_free = if wipeout { Zero::zero() } else { new_free }; - - // First we try to modify the account's balance to the forced balance. - let old_free = Self::mutate_account_handling_dust(&who, |account| { - let old_free = account.free; - account.free = new_free; - old_free - })?; - - // This will adjust the total issuance, which was not done by the `mutate_account` - // above. - match new_free.cmp(&old_free) { - cmp::Ordering::Greater => { - mem::drop(PositiveImbalance::::new(new_free - old_free)); - }, - cmp::Ordering::Less => { - mem::drop(NegativeImbalance::::new(old_free - new_free)); - }, - cmp::Ordering::Equal => {}, - } - - Self::deposit_event(Event::BalanceSet { who, free: new_free }); - Ok(()) - } - - /// Adjust the total issuance in a saturating way. - /// - /// Can only be called by root and always needs a positive `delta`. - /// - /// # Example - #[doc = docify::embed!("./src/tests/dispatchable_tests.rs", force_adjust_total_issuance_example)] - #[pallet::call_index(9)] - #[pallet::weight(T::WeightInfo::force_adjust_total_issuance())] - pub fn force_adjust_total_issuance( - origin: OriginFor, - direction: AdjustmentDirection, - #[pallet::compact] delta: T::Balance, - ) -> DispatchResult { - ensure_root(origin)?; - - ensure!(delta > Zero::zero(), Error::::DeltaZero); - - let old = TotalIssuance::::get(); - let new = match direction { - AdjustmentDirection::Increase => old.saturating_add(delta), - AdjustmentDirection::Decrease => old.saturating_sub(delta), - }; - - ensure!(InactiveIssuance::::get() <= new, Error::::IssuanceDeactivated); - TotalIssuance::::set(new); - - Self::deposit_event(Event::::TotalIssuanceForced { old, new }); - - Ok(()) - } - - /// Burn the specified liquid free balance from the origin account. - /// - /// If the origin's account ends up below the existential deposit as a result - /// of the burn and `keep_alive` is false, the account will be reaped. - /// - /// Unlike sending funds to a _burn_ address, which merely makes the funds inaccessible, - /// this `burn` operation will reduce total issuance by the amount _burned_. - #[pallet::call_index(10)] - #[pallet::weight(if *keep_alive {T::WeightInfo::burn_allow_death() } else {T::WeightInfo::burn_keep_alive()})] - pub fn burn( - origin: OriginFor, - #[pallet::compact] value: T::Balance, - keep_alive: bool, - ) -> DispatchResult { - let source = ensure_signed(origin)?; - let preservation = if keep_alive { Preserve } else { Expendable }; - >::burn_from( - &source, - value, - preservation, - Precision::Exact, - Polite, - )?; - Ok(()) - } - } - - impl, I: 'static> Pallet { - pub(crate) fn do_store_transfer_proof( - from: &T::AccountId, - to: &T::AccountId, - value: T::Balance, - ) { - if from != to { - let current_count = Self::transfer_count(); - TransferProof::::insert((current_count, from.clone(), to.clone(), value), ()); - TransferCount::::put(current_count.saturating_add(One::one())); - - Self::deposit_event(Event::TransferProofStored { - transfer_count: current_count, - source: from.clone(), - dest: to.clone(), - funding_amount: value, - }); - } - } - - pub(crate) fn transfer_proof_storage_key( - transfer_count: u64, - from: T::AccountId, - to: T::AccountId, - amount: T::Balance, - ) -> Vec { - let key = (transfer_count, from, to, amount); - TransferProof::::hashed_key_for(&key) - } - } - - impl, I: 'static> Pallet { - /// Public function to get the total issuance. - pub fn total_issuance() -> T::Balance { - TotalIssuance::::get() - } - - /// Public function to get the inactive issuance. - pub fn inactive_issuance() -> T::Balance { - InactiveIssuance::::get() - } - - /// Public function to access the Locks storage. - pub fn locks(who: &T::AccountId) -> WeakBoundedVec, T::MaxLocks> { - Locks::::get(who) - } - - /// Public function to access the reserves storage. - pub fn reserves( - who: &T::AccountId, - ) -> BoundedVec, T::MaxReserves> { - Reserves::::get(who) - } - - fn ed() -> T::Balance { - T::ExistentialDeposit::get() - } - /// Ensure the account `who` is using the new logic. - /// - /// Returns `true` if the account did get upgraded, `false` if it didn't need upgrading. - pub fn ensure_upgraded(who: &T::AccountId) -> bool { - let mut a = T::AccountStore::get(who); - if a.flags.is_new_logic() { - return false; - } - a.flags.set_new_logic(); - if !a.reserved.is_zero() && a.frozen.is_zero() { - if system::Pallet::::providers(who) == 0 { - // Gah!! We have no provider refs :( - // This shouldn't practically happen, but we need a failsafe anyway: let's give - // them enough for an ED. - log::warn!( - target: LOG_TARGET, - "account with a non-zero reserve balance has no provider refs, account_id: '{:?}'.", - who - ); - a.free = a.free.max(Self::ed()); - system::Pallet::::inc_providers(who); - } - let _ = system::Pallet::::inc_consumers_without_limit(who).defensive(); - } - // Should never fail - we're only setting a bit. - let _ = T::AccountStore::try_mutate_exists(who, |account| -> DispatchResult { - *account = Some(a); - Ok(()) - }); - Self::deposit_event(Event::Upgraded { who: who.clone() }); - true - } - - /// Get the free balance of an account. - pub fn free_balance(who: impl core::borrow::Borrow) -> T::Balance { - Self::account(who.borrow()).free - } - - /// Get the balance of an account that can be used for transfers, reservations, or any other - /// non-locking, non-transaction-fee activity. Will be at most `free_balance`. - pub fn usable_balance(who: impl core::borrow::Borrow) -> T::Balance { - >::reducible_balance(who.borrow(), Expendable, Polite) - } - - /// Get the balance of an account that can be used for paying transaction fees (not tipping, - /// or any other kind of fees, though). Will be at most `free_balance`. - /// - /// This requires that the account stays alive. - pub fn usable_balance_for_fees(who: impl core::borrow::Borrow) -> T::Balance { - >::reducible_balance(who.borrow(), Protect, Polite) - } - - /// Get the reserved balance of an account. - pub fn reserved_balance(who: impl core::borrow::Borrow) -> T::Balance { - Self::account(who.borrow()).reserved - } - - /// Get both the free and reserved balances of an account. - pub(crate) fn account(who: &T::AccountId) -> AccountData { - T::AccountStore::get(who) - } - - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. - /// - /// It returns the result from the closure. Any dust is handled through the low-level - /// `fungible::Unbalanced` trap-door for legacy dust management. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - pub(crate) fn mutate_account_handling_dust( - who: &T::AccountId, - f: impl FnOnce(&mut AccountData) -> R, - ) -> Result { - let (r, maybe_dust) = Self::mutate_account(who, f)?; - if let Some(dust) = maybe_dust { - >::handle_raw_dust(dust); - } - Ok(r) - } - - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. - /// - /// It returns the result from the closure. Any dust is handled through the low-level - /// `fungible::Unbalanced` trap-door for legacy dust management. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - pub(crate) fn try_mutate_account_handling_dust>( - who: &T::AccountId, - f: impl FnOnce(&mut AccountData, bool) -> Result, - ) -> Result { - let (r, maybe_dust) = Self::try_mutate_account(who, f)?; - if let Some(dust) = maybe_dust { - >::handle_raw_dust(dust); - } - Ok(r) - } - - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. - /// - /// It returns both the result from the closure, and an optional amount of dust - /// which should be handled once it is known that all nested mutates that could affect - /// storage items what the dust handler touches have completed. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - pub(crate) fn mutate_account( - who: &T::AccountId, - f: impl FnOnce(&mut AccountData) -> R, - ) -> Result<(R, Option), DispatchError> { - Self::try_mutate_account(who, |a, _| -> Result { Ok(f(a)) }) - } - - /// Returns `true` when `who` has some providers or `insecure_zero_ed` feature is disabled. - /// Returns `false` otherwise. - #[cfg(not(feature = "insecure_zero_ed"))] - fn have_providers_or_no_zero_ed(_: &T::AccountId) -> bool { - true - } - - /// Returns `true` when `who` has some providers or `insecure_zero_ed` feature is disabled. - /// Returns `false` otherwise. - #[cfg(feature = "insecure_zero_ed")] - fn have_providers_or_no_zero_ed(who: &T::AccountId) -> bool { - frame_system::Pallet::::providers(who) > 0 - } - - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the - /// result of `f` is an `Err`. - /// - /// It returns both the result from the closure, and an optional amount of dust - /// which should be handled once it is known that all nested mutates that could affect - /// storage items what the dust handler touches have completed. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - pub(crate) fn try_mutate_account>( - who: &T::AccountId, - f: impl FnOnce(&mut AccountData, bool) -> Result, - ) -> Result<(R, Option), E> { - Self::ensure_upgraded(who); - let result = T::AccountStore::try_mutate_exists(who, |maybe_account| { - let is_new = maybe_account.is_none(); - let mut account = maybe_account.take().unwrap_or_default(); - let did_provide = - account.free >= Self::ed() && Self::have_providers_or_no_zero_ed(who); - let did_consume = - !is_new && (!account.reserved.is_zero() || !account.frozen.is_zero()); - - let result = f(&mut account, is_new)?; - - let does_provide = account.free >= Self::ed(); - let does_consume = !account.reserved.is_zero() || !account.frozen.is_zero(); - - if !did_provide && does_provide { - frame_system::Pallet::::inc_providers(who); - } - if did_consume && !does_consume { - frame_system::Pallet::::dec_consumers(who); - } - if !did_consume && does_consume { - frame_system::Pallet::::inc_consumers(who)?; - } - if does_consume && frame_system::Pallet::::consumers(who) == 0 { - // NOTE: This is a failsafe and should not happen for normal accounts. A normal - // account should have gotten a consumer ref in `!did_consume && does_consume` - // at some point. - log::error!(target: LOG_TARGET, "Defensively bumping a consumer ref."); - frame_system::Pallet::::inc_consumers(who)?; - } - if did_provide && !does_provide { - // This could reap the account so must go last. - frame_system::Pallet::::dec_providers(who).inspect_err(|_| { - // best-effort revert consumer change. - if did_consume && !does_consume { - let _ = frame_system::Pallet::::inc_consumers(who).defensive(); - } - if !did_consume && does_consume { - frame_system::Pallet::::dec_consumers(who); - } - })?; - } - - let maybe_endowed = if is_new { Some(account.free) } else { None }; - - // Handle any steps needed after mutating an account. - // - // This includes DustRemoval unbalancing, in the case than the `new` account's total - // balance is non-zero but below ED. - // - // Updates `maybe_account` to `Some` iff the account has sufficient balance. - // Evaluates `maybe_dust`, which is `Some` containing the dust to be dropped, iff - // some dust should be dropped. - // - // We should never be dropping if reserved is non-zero. Reserved being non-zero - // should imply that we have a consumer ref, so this is economically safe. - let ed = Self::ed(); - let maybe_dust = if account.free < ed && account.reserved.is_zero() { - if account.free.is_zero() { - None - } else { - Some(account.free) - } - } else { - assert!( - account.free.is_zero() || account.free >= ed || !account.reserved.is_zero() - ); - *maybe_account = Some(account); - None - }; - Ok((maybe_endowed, maybe_dust, result)) - }); - result.map(|(maybe_endowed, maybe_dust, result)| { - if let Some(endowed) = maybe_endowed { - Self::deposit_event(Event::Endowed { - account: who.clone(), - free_balance: endowed, - }); - } - if let Some(amount) = maybe_dust { - Pallet::::deposit_event(Event::DustLost { account: who.clone(), amount }); - } - (result, maybe_dust) - }) - } - - /// Update the account entry for `who`, given the locks. - pub(crate) fn update_locks(who: &T::AccountId, locks: &[BalanceLock]) { - let bounded_locks = WeakBoundedVec::<_, T::MaxLocks>::force_from( - locks.to_vec(), - Some("Balances Update Locks"), - ); - - if locks.len() as u32 > T::MaxLocks::get() { - log::warn!( - target: LOG_TARGET, - "Warning: A user has more currency locks than expected. \ - A runtime configuration adjustment may be needed." - ); - } - let freezes = Freezes::::get(who); - let mut prev_frozen = Zero::zero(); - let mut after_frozen = Zero::zero(); - // No way this can fail since we do not alter the existential balances. - // TODO: Revisit this assumption. - let res = Self::mutate_account(who, |b| { - prev_frozen = b.frozen; - b.frozen = Zero::zero(); - for l in locks.iter() { - b.frozen = b.frozen.max(l.amount); - } - for l in freezes.iter() { - b.frozen = b.frozen.max(l.amount); - } - after_frozen = b.frozen; - }); - debug_assert!(res.is_ok()); - if let Ok((_, maybe_dust)) = res { - debug_assert!(maybe_dust.is_none(), "Not altering main balance; qed"); - } - - match locks.is_empty() { - true => Locks::::remove(who), - false => Locks::::insert(who, bounded_locks), - } - - match prev_frozen.cmp(&after_frozen) { - cmp::Ordering::Greater => { - let amount = prev_frozen.saturating_sub(after_frozen); - Self::deposit_event(Event::Unlocked { who: who.clone(), amount }); - }, - cmp::Ordering::Less => { - let amount = after_frozen.saturating_sub(prev_frozen); - Self::deposit_event(Event::Locked { who: who.clone(), amount }); - }, - cmp::Ordering::Equal => {}, - } - } - - /// Update the account entry for `who`, given the locks. - pub(crate) fn update_freezes( - who: &T::AccountId, - freezes: BoundedSlice, T::MaxFreezes>, - ) -> DispatchResult { - let mut prev_frozen = Zero::zero(); - let mut after_frozen = Zero::zero(); - let (_, maybe_dust) = Self::mutate_account(who, |b| { - prev_frozen = b.frozen; - b.frozen = Zero::zero(); - for l in Locks::::get(who).iter() { - b.frozen = b.frozen.max(l.amount); - } - for l in freezes.iter() { - b.frozen = b.frozen.max(l.amount); - } - after_frozen = b.frozen; - })?; - debug_assert!(maybe_dust.is_none(), "Not altering main balance; qed"); - if freezes.is_empty() { - Freezes::::remove(who); - } else { - Freezes::::insert(who, freezes); - } - match prev_frozen.cmp(&after_frozen) { - cmp::Ordering::Greater => { - let amount = prev_frozen.saturating_sub(after_frozen); - Self::deposit_event(Event::Thawed { who: who.clone(), amount }); - }, - cmp::Ordering::Less => { - let amount = after_frozen.saturating_sub(prev_frozen); - Self::deposit_event(Event::Frozen { who: who.clone(), amount }); - }, - cmp::Ordering::Equal => {}, - } - Ok(()) - } - - /// Move the reserved balance of one account into the balance of another, according to - /// `status`. This will respect freezes/locks only if `fortitude` is `Polite`. - /// - /// Is a no-op if the value to be moved is zero. - /// - /// NOTE: returns actual amount of transferred value in `Ok` case. - pub(crate) fn do_transfer_reserved( - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: T::Balance, - precision: Precision, - fortitude: Fortitude, - status: Status, - ) -> Result { - if value.is_zero() { - return Ok(Zero::zero()); - } - - let max = >::reducible_total_balance_on_hold( - slashed, fortitude, - ); - let actual = match precision { - Precision::BestEffort => value.min(max), - Precision::Exact => value, - }; - ensure!(actual <= max, TokenError::FundsUnavailable); - if slashed == beneficiary { - return match status { - Status::Free => Ok(actual.saturating_sub(Self::unreserve(slashed, actual))), - Status::Reserved => Ok(actual), - }; - } - - let ((_, maybe_dust_1), maybe_dust_2) = Self::try_mutate_account( - beneficiary, - |to_account, is_new| -> Result<((), Option), DispatchError> { - ensure!(!is_new, Error::::DeadAccount); - Self::try_mutate_account(slashed, |from_account, _| -> DispatchResult { - match status { - Status::Free => - to_account.free = to_account - .free - .checked_add(&actual) - .ok_or(ArithmeticError::Overflow)?, - Status::Reserved => - to_account.reserved = to_account - .reserved - .checked_add(&actual) - .ok_or(ArithmeticError::Overflow)?, - } - from_account.reserved.saturating_reduce(actual); - Ok(()) - }) - }, - )?; - - if let Some(dust) = maybe_dust_1 { - >::handle_raw_dust(dust); - } - if let Some(dust) = maybe_dust_2 { - >::handle_raw_dust(dust); - } - - Self::deposit_event(Event::ReserveRepatriated { - from: slashed.clone(), - to: beneficiary.clone(), - amount: actual, - destination_status: status, - }); - Ok(actual) - } - } -} diff --git a/pallets/balances/src/migration.rs b/pallets/balances/src/migration.rs deleted file mode 100644 index ac2f7e2f..00000000 --- a/pallets/balances/src/migration.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -use super::*; -use frame_support::{ - pallet_prelude::*, - traits::{OnRuntimeUpgrade, PalletInfoAccess}, - weights::Weight, -}; - -fn migrate_v0_to_v1, I: 'static>(accounts: &[T::AccountId]) -> Weight { - let on_chain_version = Pallet::::on_chain_storage_version(); - - if on_chain_version == 0 { - let total = accounts - .iter() - .map(Pallet::::total_balance) - .fold(T::Balance::zero(), |a, e| a.saturating_add(e)); - Pallet::::deactivate(total); - - // Remove the old `StorageVersion` type. - frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( - Pallet::::name().as_bytes(), - "StorageVersion".as_bytes(), - )); - - // Set storage version to `1`. - StorageVersion::new(1).put::>(); - - log::info!(target: LOG_TARGET, "Storage to version 1"); - T::DbWeight::get().reads_writes(2 + accounts.len() as u64, 3) - } else { - log::info!( - target: LOG_TARGET, - "Migration did not execute. This probably should be removed" - ); - T::DbWeight::get().reads(1) - } -} - -// NOTE: This must be used alongside the account whose balance is expected to be inactive. -// Generally this will be used for the XCM teleport checking account. -pub struct MigrateToTrackInactive(PhantomData<(T, A, I)>); -impl, A: Get, I: 'static> OnRuntimeUpgrade - for MigrateToTrackInactive -{ - fn on_runtime_upgrade() -> Weight { - migrate_v0_to_v1::(&[A::get()]) - } -} - -// NOTE: This must be used alongside the accounts whose balance is expected to be inactive. -// Generally this will be used for the XCM teleport checking accounts. -pub struct MigrateManyToTrackInactive(PhantomData<(T, A, I)>); -impl, A: Get>, I: 'static> OnRuntimeUpgrade - for MigrateManyToTrackInactive -{ - fn on_runtime_upgrade() -> Weight { - migrate_v0_to_v1::(&A::get()) - } -} - -pub struct ResetInactive(PhantomData<(T, I)>); -impl, I: 'static> OnRuntimeUpgrade for ResetInactive { - fn on_runtime_upgrade() -> Weight { - let on_chain_version = Pallet::::on_chain_storage_version(); - - if on_chain_version == 1 { - // Remove the old `StorageVersion` type. - frame_support::storage::unhashed::kill(&frame_support::storage::storage_prefix( - Pallet::::name().as_bytes(), - "StorageVersion".as_bytes(), - )); - - InactiveIssuance::::kill(); - - // Set storage version to `0`. - StorageVersion::new(0).put::>(); - - log::info!(target: LOG_TARGET, "Storage to version 0"); - T::DbWeight::get().reads_writes(1, 3) - } else { - log::info!( - target: LOG_TARGET, - "Migration did not execute. This probably should be removed" - ); - T::DbWeight::get().reads(1) - } - } -} diff --git a/pallets/balances/src/tests/currency_tests.rs b/pallets/balances/src/tests/currency_tests.rs deleted file mode 100644 index cf22405a..00000000 --- a/pallets/balances/src/tests/currency_tests.rs +++ /dev/null @@ -1,1643 +0,0 @@ -// This file is part of Substrate. - -// 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 regarding the functionality of the `Currency` trait set implementations. - -use super::*; -use crate::{Event, NegativeImbalance}; -use frame_support::{ - traits::{ - BalanceStatus::{Free, Reserved}, - Currency, - ExistenceRequirement::{self, AllowDeath, KeepAlive}, - Hooks, InspectLockableCurrency, LockIdentifier, LockableCurrency, NamedReservableCurrency, - ReservableCurrency, WithdrawReasons, - }, - StorageNoopGuard, -}; -use frame_system::Event as SysEvent; -use sp_runtime::traits::DispatchTransaction; - -const ID_1: LockIdentifier = *b"1 "; -const ID_2: LockIdentifier = *b"2 "; - -pub const CALL: &::RuntimeCall = - &RuntimeCall::Balances(crate::Call::transfer_allow_death { - dest: sp_core::crypto::AccountId32::new([0u8; 32]), - value: 0, - }); - -#[test] -fn ed_should_work() { - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(1), 1000)); - assert_noop!( - >::transfer(&account_id(1), &account_id(10), 1000, KeepAlive), - TokenError::NotExpendable - ); - assert_ok!(>::transfer( - &account_id(1), - &account_id(10), - 1000, - AllowDeath - )); - }); -} - -#[test] -fn set_lock_with_amount_zero_removes_lock() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), Balance::MAX, WithdrawReasons::all()); - Balances::set_lock(ID_1, &account_id(1), 0, WithdrawReasons::all()); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 1, - AllowDeath - )); - }); -} - -#[test] -fn set_lock_with_withdraw_reasons_empty_removes_lock() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), Balance::MAX, WithdrawReasons::all()); - Balances::set_lock(ID_1, &account_id(1), Balance::MAX, WithdrawReasons::empty()); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 1, - AllowDeath - )); - }); -} - -#[test] -fn basic_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_eq!(Balances::free_balance(account_id(1)), 10); - Balances::set_lock(ID_1, &account_id(1), 9, WithdrawReasons::all()); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 5, AllowDeath), - TokenError::Frozen - ); - }); -} - -#[test] -fn inspect_lock_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), 10, WithdrawReasons::all()); - Balances::set_lock(ID_2, &account_id(1), 10, WithdrawReasons::all()); - Balances::set_lock(ID_1, &account_id(2), 20, WithdrawReasons::all()); - - assert_eq!( - >::balance_locked(ID_1, &account_id(1)), - 10 - ); - assert_eq!( - >::balance_locked(ID_2, &account_id(1)), - 10 - ); - assert_eq!( - >::balance_locked(ID_1, &account_id(2)), - 20 - ); - assert_eq!( - >::balance_locked(ID_2, &account_id(2)), - 0 - ); - assert_eq!( - >::balance_locked(ID_1, &account_id(3)), - 0 - ); - }) -} - -#[test] -fn account_should_be_reaped() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_eq!(Balances::free_balance(account_id(1)), 10); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 10, - AllowDeath - )); - assert_eq!(System::providers(&account_id(1)), 0); - assert_eq!(System::consumers(&account_id(1)), 0); - // Check that the account is dead. - assert!(!frame_system::Account::::contains_key(account_id(1))); - }); -} - -#[test] -fn reap_failed_due_to_provider_and_consumer() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // SCENARIO: only one provider and there are remaining consumers. - assert_ok!(System::inc_consumers(&account_id(1))); - assert!(!System::can_dec_provider(&account_id(1))); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 10, AllowDeath), - TokenError::Frozen - ); - assert!(System::account_exists(&account_id(1))); - assert_eq!(Balances::free_balance(account_id(1)), 10); - - // SCENARIO: more than one provider, but will not kill account due to other provider. - assert_eq!(System::inc_providers(&account_id(1)), frame_system::IncRefStatus::Existed); - assert_eq!(System::providers(&account_id(1)), 2); - assert!(System::can_dec_provider(&account_id(1))); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 10, - AllowDeath - )); - assert_eq!(System::providers(&account_id(1)), 1); - assert!(System::account_exists(&account_id(1))); - assert_eq!(Balances::free_balance(account_id(1)), 0); - }); -} - -#[test] -fn partial_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), 5, WithdrawReasons::all()); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 1, - AllowDeath - )); - }); -} - -#[test] -fn lock_removal_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), Balance::MAX, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - Balances::remove_lock(ID_1, &account_id(1)); - assert_eq!(System::consumers(&account_id(1)), 0); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 1, - AllowDeath - )); - }); -} - -#[test] -fn lock_replacement_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), Balance::MAX, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - Balances::set_lock(ID_1, &account_id(1), 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 1, - AllowDeath - )); - }); -} - -#[test] -fn double_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - Balances::set_lock(ID_2, &account_id(1), 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 1, - AllowDeath - )); - }); -} - -#[test] -fn combination_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_eq!(System::consumers(&account_id(1)), 0); - Balances::set_lock(ID_1, &account_id(1), Balance::MAX, WithdrawReasons::empty()); - assert_eq!(System::consumers(&account_id(1)), 0); - Balances::set_lock(ID_2, &account_id(1), 0, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 0); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 1, - AllowDeath - )); - }); -} - -#[test] -fn lock_value_extension_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &account_id(1), 2, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &account_id(1), 8, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 3, AllowDeath), - TokenError::Frozen - ); - }); -} - -#[test] -fn lock_should_work_reserve() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - pallet_transaction_payment::NextFeeMultiplier::::put( - Multiplier::saturating_from_integer(1), - ); - Balances::set_lock(ID_1, &account_id(1), 10, WithdrawReasons::RESERVE); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 1, AllowDeath), - TokenError::Frozen - ); - assert_noop!( - Balances::reserve(&account_id(1), 1), - Error::::LiquidityRestrictions, - ); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(1), - Some(account_id(1)).into(), - CALL, - &crate::tests::info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(0), - Some(account_id(1)).into(), - CALL, - &crate::tests::info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - }); -} - -#[test] -fn lock_should_work_tx_fee() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), 10, WithdrawReasons::TRANSACTION_PAYMENT); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 1, AllowDeath), - TokenError::Frozen - ); - assert_noop!( - Balances::reserve(&account_id(1), 1), - Error::::LiquidityRestrictions, - ); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(1), - Some(account_id(1)).into(), - CALL, - &crate::tests::info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - assert!(ChargeTransactionPayment::::validate_and_prepare( - ChargeTransactionPayment::from(0), - Some(account_id(1)).into(), - CALL, - &crate::tests::info_from_weight(Weight::from_parts(1, 0)), - 1, - 0, - ) - .is_err()); - }); -} - -#[test] -fn lock_block_number_extension_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &account_id(1), 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 6, AllowDeath), - TokenError::Frozen - ); - System::set_block_number(2); - Balances::extend_lock(ID_1, &account_id(1), 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 3, AllowDeath), - TokenError::Frozen - ); - }); -} - -#[test] -fn lock_reasons_extension_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - Balances::set_lock(ID_1, &account_id(1), 10, WithdrawReasons::TRANSFER); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &account_id(1), 10, WithdrawReasons::empty()); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 6, AllowDeath), - TokenError::Frozen - ); - Balances::extend_lock(ID_1, &account_id(1), 10, WithdrawReasons::RESERVE); - assert_noop!( - >::transfer(&account_id(1), &account_id(2), 6, AllowDeath), - TokenError::Frozen - ); - }); -} - -#[test] -fn reserved_balance_should_prevent_reclaim_count() { - ExtBuilder::default() - .existential_deposit(256) - .monied(true) - .build_and_execute_with(|| { - System::inc_account_nonce(account_id(2)); - assert_eq!(Balances::total_balance(&account_id(2)), 256 * 20); - assert_eq!(System::providers(&account_id(2)), 1); - System::inc_providers(&account_id(2)); - assert_eq!(System::providers(&account_id(2)), 2); - - assert_ok!(Balances::reserve(&account_id(2), 256 * 19 + 1)); // account 2 becomes mostly reserved - assert_eq!(System::providers(&account_id(2)), 1); - assert_eq!(Balances::free_balance(account_id(2)), 255); // "free" account would be deleted. - assert_eq!(Balances::total_balance(&account_id(2)), 256 * 20); // reserve still exists. - assert_eq!(System::account_nonce(account_id(2)), 1); - - // account 4 tries to take index 1 for account 5. - assert_ok!(Balances::transfer_allow_death( - Some(account_id(4)).into(), - account_id(5), - 256 + 0x69 - )); - assert_eq!(Balances::total_balance(&account_id(5)), 256 + 0x69); - - assert!(Balances::slash_reserved(&account_id(2), 256 * 19 + 1).1.is_zero()); // account 2 gets slashed - - // "reserve" account reduced to 255 (below ED) so account no longer consuming - assert_ok!(System::dec_providers(&account_id(2))); - assert_eq!(System::providers(&account_id(2)), 0); - // account deleted - assert_eq!(System::account_nonce(account_id(2)), 0); // nonce zero - assert_eq!(Balances::total_balance(&account_id(2)), 0); - - // account 4 tries to take index 1 again for account 6. - assert_ok!(Balances::transfer_allow_death( - Some(account_id(4)).into(), - account_id(6), - 256 + 0x69 - )); - assert_eq!(Balances::total_balance(&account_id(6)), 256 + 0x69); - }); -} - -#[test] -fn reward_should_work() { - ExtBuilder::default().monied(true).build_and_execute_with(|| { - assert_eq!(Balances::total_balance(&account_id(1)), 10); - assert_ok!(Balances::deposit_into_existing(&account_id(1), 10).map(drop)); - assert_eq!( - events(), - [ - RuntimeEvent::Balances(crate::Event::Deposit { who: account_id(1), amount: 10 }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 10 }), - ] - ); - assert_eq!(Balances::total_balance(&account_id(1)), 20); - assert_eq!(pallet_balances::TotalIssuance::::get(), 120); - }); -} - -#[test] -fn balance_works() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 42); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: account_id(1), - amount: 42, - })); - assert_eq!(Balances::free_balance(account_id(1)), 42); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - assert_eq!(Balances::total_balance(&account_id(1)), 42); - assert_eq!(Balances::free_balance(account_id(2)), 0); - assert_eq!(Balances::reserved_balance(account_id(2)), 0); - assert_eq!(Balances::total_balance(&account_id(2)), 0); - }); -} - -#[test] -fn reserving_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - - assert_eq!(Balances::total_balance(&account_id(1)), 111); - assert_eq!(Balances::free_balance(account_id(1)), 111); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - - assert_ok!(Balances::reserve(&account_id(1), 69)); - - assert_eq!(Balances::total_balance(&account_id(1)), 111); - assert_eq!(Balances::free_balance(account_id(1)), 42); - assert_eq!(Balances::reserved_balance(account_id(1)), 69); - }); -} - -#[test] -fn deducting_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - assert_ok!(Balances::reserve(&account_id(1), 69)); - assert_eq!(Balances::free_balance(account_id(1)), 42); - }); -} - -#[test] -fn refunding_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 42); - System::set_block_number(2); - Balances::unreserve(&account_id(1), 69); - assert_eq!(Balances::free_balance(account_id(1)), 42); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - }); -} - -#[test] -fn slashing_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - assert_ok!(Balances::reserve(&account_id(1), 69)); - assert_eq!(Balances::slash_reserved(&account_id(1), 69).1, 0); - assert_eq!(Balances::free_balance(account_id(1)), 42); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 42); - }); -} - -#[test] -fn withdrawing_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(2), 111); - let _ = Balances::withdraw( - &account_id(2), - 11, - WithdrawReasons::TRANSFER, - ExistenceRequirement::KeepAlive, - ); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Withdraw { - who: account_id(2), - amount: 11, - })); - assert_eq!(Balances::free_balance(account_id(2)), 100); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - }); -} - -#[test] -fn withdrawing_balance_should_fail_when_not_expendable() { - ExtBuilder::default().build_and_execute_with(|| { - ExistentialDeposit::set(10); - let _ = Balances::deposit_creating(&account_id(2), 20); - assert_ok!(Balances::reserve(&account_id(2), 5)); - assert_noop!( - Balances::withdraw( - &account_id(2), - 6, - WithdrawReasons::TRANSFER, - ExistenceRequirement::KeepAlive - ), - Error::::Expendability, - ); - assert_ok!(Balances::withdraw( - &account_id(2), - 5, - WithdrawReasons::TRANSFER, - ExistenceRequirement::KeepAlive - ),); - }); -} - -#[test] -fn slashing_incomplete_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 42); - assert_ok!(Balances::reserve(&account_id(1), 21)); - assert_eq!(Balances::slash_reserved(&account_id(1), 69).1, 48); - assert_eq!(Balances::free_balance(account_id(1)), 21); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 21); - }); -} - -#[test] -fn unreserving_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - assert_ok!(Balances::reserve(&account_id(1), 110)); - Balances::unreserve(&account_id(1), 41); - assert_eq!(Balances::reserved_balance(account_id(1)), 69); - assert_eq!(Balances::free_balance(account_id(1)), 42); - }); -} - -#[test] -fn slashing_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 112); - assert_ok!(Balances::reserve(&account_id(1), 111)); - assert_eq!(Balances::slash_reserved(&account_id(1), 42).1, 0); - assert_eq!(Balances::reserved_balance(account_id(1)), 69); - assert_eq!(Balances::free_balance(account_id(1)), 1); - assert_eq!(pallet_balances::TotalIssuance::::get(), 70); - }); -} - -#[test] -fn slashing_incomplete_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - assert_ok!(Balances::reserve(&account_id(1), 42)); - assert_eq!(Balances::slash_reserved(&account_id(1), 69).1, 27); - assert_eq!(Balances::free_balance(account_id(1)), 69); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 69); - }); -} - -#[test] -fn repatriating_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - let _ = Balances::deposit_creating(&account_id(2), 1); - assert_ok!(Balances::reserve(&account_id(1), 110)); - assert_ok!(Balances::repatriate_reserved(&account_id(1), &account_id(2), 41, Free), 0); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated { - from: account_id(1), - to: account_id(2), - amount: 41, - destination_status: Free, - })); - assert_eq!(Balances::reserved_balance(account_id(1)), 69); - assert_eq!(Balances::free_balance(account_id(1)), 1); - assert_eq!(Balances::reserved_balance(account_id(2)), 0); - assert_eq!(Balances::free_balance(account_id(2)), 42); - }); -} - -#[test] -fn transferring_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - let _ = Balances::deposit_creating(&account_id(2), 1); - assert_ok!(Balances::reserve(&account_id(1), 110)); - assert_ok!(Balances::repatriate_reserved(&account_id(1), &account_id(2), 41, Reserved), 0); - assert_eq!(Balances::reserved_balance(account_id(1)), 69); - assert_eq!(Balances::free_balance(account_id(1)), 1); - assert_eq!(Balances::reserved_balance(account_id(2)), 41); - assert_eq!(Balances::free_balance(account_id(2)), 1); - }); -} - -#[test] -fn transferring_reserved_balance_to_yourself_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 110); - assert_ok!(Balances::reserve(&account_id(1), 50)); - assert_ok!(Balances::repatriate_reserved(&account_id(1), &account_id(1), 50, Free), 0); - assert_eq!(Balances::free_balance(account_id(1)), 110); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - - assert_ok!(Balances::reserve(&account_id(1), 50)); - assert_ok!(Balances::repatriate_reserved(&account_id(1), &account_id(1), 60, Free), 10); - assert_eq!(Balances::free_balance(account_id(1)), 110); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - }); -} - -#[test] -fn transferring_reserved_balance_to_nonexistent_should_fail() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - assert_ok!(Balances::reserve(&account_id(1), 110)); - assert_noop!( - Balances::repatriate_reserved(&account_id(1), &account_id(2), 42, Free), - Error::::DeadAccount - ); - }); -} - -#[test] -fn transferring_incomplete_reserved_balance_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 110); - let _ = Balances::deposit_creating(&account_id(2), 1); - assert_ok!(Balances::reserve(&account_id(1), 41)); - assert_ok!(Balances::repatriate_reserved(&account_id(1), &account_id(2), 69, Free), 28); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - assert_eq!(Balances::free_balance(account_id(1)), 69); - assert_eq!(Balances::reserved_balance(account_id(2)), 0); - assert_eq!(Balances::free_balance(account_id(2)), 42); - }); -} - -#[test] -fn transferring_too_high_value_should_not_panic() { - ExtBuilder::default().build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), Balance::MAX); - Balances::make_free_balance_be(&account_id(2), 1); - - assert_err!( - >::transfer( - &account_id(1), - &account_id(2), - Balance::MAX, - AllowDeath - ), - ArithmeticError::Overflow, - ); - - assert_eq!(Balances::free_balance(account_id(1)), Balance::MAX); - assert_eq!(Balances::free_balance(account_id(2)), 1); - }); -} - -#[test] -fn account_create_on_free_too_low_with_other() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 100); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - - // No-op. - let _ = Balances::deposit_creating(&account_id(2), 50); - assert_eq!(Balances::free_balance(account_id(2)), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - }) -} - -#[test] -fn account_create_on_free_too_low() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // No-op. - let _ = Balances::deposit_creating(&account_id(2), 50); - assert_eq!(Balances::free_balance(account_id(2)), 0); - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - }) -} - -#[test] -fn account_removal_on_free_too_low() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - - // Setup two accounts with free balance above the existential threshold. - let _ = Balances::deposit_creating(&account_id(1), 110); - let _ = Balances::deposit_creating(&account_id(2), 110); - - assert_eq!(Balances::free_balance(account_id(1)), 110); - assert_eq!(Balances::free_balance(account_id(2)), 110); - assert_eq!(pallet_balances::TotalIssuance::::get(), 220); - - // Transfer funds from account 1 of such amount that after this transfer - // the balance of account 1 will be below the existential threshold. - // This should lead to the removal of all balance of this account. - assert_ok!(Balances::transfer_allow_death(Some(account_id(1)).into(), account_id(2), 20)); - - // Verify free balance removal of account 1. - assert_eq!(Balances::free_balance(account_id(1)), 0); - assert_eq!(Balances::free_balance(account_id(2)), 130); - - // Verify that TotalIssuance tracks balance removal when free balance is too low. - assert_eq!(pallet_balances::TotalIssuance::::get(), 130); - }); -} - -#[test] -fn burn_must_work() { - ExtBuilder::default().monied(true).build_and_execute_with(|| { - let init_total_issuance = pallet_balances::TotalIssuance::::get(); - let imbalance = >::burn(10); - assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance - 10); - drop(imbalance); - assert_eq!(pallet_balances::TotalIssuance::::get(), init_total_issuance); - }); -} - -#[test] -#[should_panic = "the balance of any account should always be at least the existential deposit."] -fn cannot_set_genesis_value_below_ed() { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = 11); - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - crate::GenesisConfig:: { balances: vec![(account_id(1), 10)] } - .assimilate_storage(&mut t) - .unwrap(); -} - -#[test] -#[should_panic = "duplicate balances in genesis."] -fn cannot_set_genesis_value_twice() { - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - crate::GenesisConfig:: { - balances: vec![(account_id(1), 10), (account_id(2), 20), (account_id(1), 15)], - } - .assimilate_storage(&mut t) - .unwrap(); -} - -#[test] -fn existential_deposit_respected_when_reserving() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 101)); - // Check balance - assert_eq!(Balances::free_balance(account_id(1)), 101); - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - - // Reserve some free balance - assert_ok!(Balances::reserve(&account_id(1), 1)); - // Check balance, the account should be ok. - assert_eq!(Balances::free_balance(account_id(1)), 100); - assert_eq!(Balances::reserved_balance(account_id(1)), 1); - - // Cannot reserve any more of the free balance. - assert_noop!(Balances::reserve(&account_id(1), 1), DispatchError::ConsumerRemaining); - }); -} - -#[test] -fn slash_fails_when_account_needed() { - ExtBuilder::default().existential_deposit(50).build_and_execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 52)); - assert_ok!(Balances::reserve(&account_id(1), 1)); - // Check balance - assert_eq!(Balances::free_balance(account_id(1)), 51); - assert_eq!(Balances::reserved_balance(account_id(1)), 1); - - // Slash a small amount - let res = Balances::slash(&account_id(1), 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); - - // The account should be dead. - assert_eq!(Balances::free_balance(account_id(1)), 50); - assert_eq!(Balances::reserved_balance(account_id(1)), 1); - - // Slashing again doesn't work since we require the ED - let res = Balances::slash(&account_id(1), 1); - assert_eq!(res, (NegativeImbalance::new(0), 1)); - - // The account should be dead. - assert_eq!(Balances::free_balance(account_id(1)), 50); - assert_eq!(Balances::reserved_balance(account_id(1)), 1); - }); -} - -#[test] -fn account_deleted_when_just_dust() { - ExtBuilder::default().existential_deposit(50).build_and_execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 50)); - // Check balance - assert_eq!(Balances::free_balance(account_id(1)), 50); - - // Slash a small amount - let res = Balances::slash(&account_id(1), 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); - - // The account should be dead. - assert_eq!(Balances::free_balance(account_id(1)), 0); - }); -} - -#[test] -fn emit_events_with_reserve_and_unreserve() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 100); - - System::set_block_number(2); - assert_ok!(Balances::reserve(&account_id(1), 10)); - - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Reserved { - who: account_id(1), - amount: 10, - })); - - System::set_block_number(3); - assert!(Balances::unreserve(&account_id(1), 5).is_zero()); - - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { - who: account_id(1), - amount: 5, - })); - - System::set_block_number(4); - assert_eq!(Balances::unreserve(&account_id(1), 6), 1); - - // should only unreserve 5 - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { - who: account_id(1), - amount: 5, - })); - }); -} - -#[test] -fn emit_events_with_changing_locks() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 100); - System::reset_events(); - - // Locks = [] --> [10] - Balances::set_lock(*b"LOCK_000", &account_id(1), 10, WithdrawReasons::TRANSFER); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Locked { who: account_id(1), amount: 10 })] - ); - - // Locks = [10] --> [15] - Balances::set_lock(*b"LOCK_000", &account_id(1), 15, WithdrawReasons::TRANSFER); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Locked { who: account_id(1), amount: 5 })] - ); - - // Locks = [15] --> [15, 20] - Balances::set_lock(*b"LOCK_001", &account_id(1), 20, WithdrawReasons::TRANSACTION_PAYMENT); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Locked { who: account_id(1), amount: 5 })] - ); - - // Locks = [15, 20] --> [17, 20] - Balances::set_lock(*b"LOCK_000", &account_id(1), 17, WithdrawReasons::TRANSACTION_PAYMENT); - for event in events() { - match event { - RuntimeEvent::Balances(crate::Event::Locked { .. }) => { - assert!(false, "unexpected lock event") - }, - RuntimeEvent::Balances(crate::Event::Unlocked { .. }) => { - assert!(false, "unexpected unlock event") - }, - _ => continue, - } - } - - // Locks = [17, 20] --> [17, 15] - Balances::set_lock(*b"LOCK_001", &account_id(1), 15, WithdrawReasons::TRANSFER); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Unlocked { who: account_id(1), amount: 3 })] - ); - - // Locks = [17, 15] --> [15] - Balances::remove_lock(*b"LOCK_000", &account_id(1)); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Unlocked { who: account_id(1), amount: 2 })] - ); - - // Locks = [15] --> [] - Balances::remove_lock(*b"LOCK_001", &account_id(1)); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Unlocked { who: account_id(1), amount: 15 })] - ); - }); -} - -#[test] -fn emit_events_with_existential_deposit() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 100)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::NewAccount { account: account_id(1) }), - RuntimeEvent::Balances(crate::Event::Endowed { - account: account_id(1), - free_balance: 100 - }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), - RuntimeEvent::Balances(crate::Event::BalanceSet { who: account_id(1), free: 100 }), - ] - ); - - let res = Balances::slash(&account_id(1), 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: account_id(1) }), - RuntimeEvent::Balances(crate::Event::DustLost { - account: account_id(1), - amount: 99 - }), - RuntimeEvent::Balances(crate::Event::Slashed { who: account_id(1), amount: 1 }), - RuntimeEvent::Balances(crate::Event::Rescinded { amount: 1 }), - ] - ); - }); -} - -#[test] -fn emit_events_with_no_existential_deposit_suicide() { - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 100); - - assert_eq!( - events(), - [ - RuntimeEvent::Balances(crate::Event::BalanceSet { who: account_id(1), free: 100 }), - RuntimeEvent::System(system::Event::NewAccount { account: account_id(1) }), - RuntimeEvent::Balances(crate::Event::Endowed { - account: account_id(1), - free_balance: 100 - }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), - ] - ); - - let res = Balances::slash(&account_id(1), 100); - assert_eq!(res, (NegativeImbalance::new(100), 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: account_id(1) }), - RuntimeEvent::Balances(crate::Event::Slashed { who: account_id(1), amount: 100 }), - RuntimeEvent::Balances(crate::Event::Rescinded { amount: 100 }), - ] - ); - }); -} - -#[test] -fn slash_over_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // SCENARIO: Over-slash will kill account, and report missing slash amount. - Balances::make_free_balance_be(&account_id(1), 1_000); - // Slashed full free_balance, and reports 300 not slashed - assert_eq!(Balances::slash(&account_id(1), 1_300), (NegativeImbalance::new(1000), 300)); - // Account is dead - assert!(!System::account_exists(&account_id(1))); - }); -} - -#[test] -fn slash_full_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - // Slashed completed in full - assert_eq!(Balances::slash(&account_id(1), 1_000), (NegativeImbalance::new(1000), 0)); - // Account is still alive - assert!(!System::account_exists(&account_id(1))); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: account_id(1), - amount: 1000, - })); - }); -} - -#[test] -fn slash_partial_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - // Slashed completed in full - assert_eq!(Balances::slash(&account_id(1), 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&account_id(1))); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: account_id(1), - amount: 900, - })); - }); -} - -#[test] -fn slash_dusting_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - // Slashed completed in full - assert_eq!(Balances::slash(&account_id(1), 950), (NegativeImbalance::new(950), 0)); - assert!(!System::account_exists(&account_id(1))); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: account_id(1), - amount: 950, - })); - }); -} - -#[test] -fn slash_does_not_take_from_reserve() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - assert_ok!(Balances::reserve(&account_id(1), 100)); - // Slashed completed in full - assert_eq!(Balances::slash(&account_id(1), 900), (NegativeImbalance::new(800), 100)); - assert_eq!(Balances::reserved_balance(account_id(1)), 100); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Slashed { - who: account_id(1), - amount: 800, - })); - }); -} - -#[test] -fn slash_consumed_slash_full_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - assert_ok!(System::inc_consumers(&account_id(1))); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash(&account_id(1), 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&account_id(1))); - }); -} - -#[test] -fn slash_consumed_slash_over_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - assert_ok!(System::inc_consumers(&account_id(1))); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash(&account_id(1), 1_000), (NegativeImbalance::new(900), 100)); - // Account is still alive - assert!(System::account_exists(&account_id(1))); - }); -} - -#[test] -fn slash_consumed_slash_partial_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - assert_ok!(System::inc_consumers(&account_id(1))); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash(&account_id(1), 800), (NegativeImbalance::new(800), 0)); - // Account is still alive - assert!(System::account_exists(&account_id(1))); - }); -} - -#[test] -fn slash_on_non_existent_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // Slash on non-existent account is okay. - assert_eq!(Balances::slash(&account_id(123), 1_300), (NegativeImbalance::new(0), 1300)); - }); -} - -#[test] -fn slash_reserved_slash_partial_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - assert_ok!(Balances::reserve(&account_id(1), 900)); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&account_id(1), 800), (NegativeImbalance::new(800), 0)); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_eq!(Balances::reserved_balance(account_id(1)), 100); - assert_eq!(Balances::free_balance(account_id(1)), 100); - }); -} - -#[test] -fn slash_reserved_slash_everything_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - Balances::make_free_balance_be(&account_id(1), 1_000); - assert_ok!(Balances::reserve(&account_id(1), 900)); - assert_eq!(System::consumers(&account_id(1)), 1); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&account_id(1), 900), (NegativeImbalance::new(900), 0)); - assert_eq!(System::consumers(&account_id(1)), 0); - // Account is still alive - assert!(System::account_exists(&account_id(1))); - }); -} - -#[test] -fn slash_reserved_overslash_does_not_touch_free_balance() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // SCENARIO: Over-slash doesn't touch free balance. - Balances::make_free_balance_be(&account_id(1), 1_000); - assert_ok!(Balances::reserve(&account_id(1), 800)); - // Slashed done - assert_eq!( - Balances::slash_reserved(&account_id(1), 900), - (NegativeImbalance::new(800), 100) - ); - assert_eq!(Balances::free_balance(account_id(1)), 200); - }); -} - -#[test] -fn slash_reserved_on_non_existent_works() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - // Slash on non-existent account is okay. - assert_eq!( - Balances::slash_reserved(&account_id(123), 1_300), - (NegativeImbalance::new(0), 1300) - ); - }); -} - -#[test] -fn operations_on_dead_account_should_not_change_state() { - // These functions all use `mutate_account` which may introduce a storage change when - // the account never existed to begin with, and shouldn't exist in the end. - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - assert!(!frame_system::Account::::contains_key(account_id(137))); - - // Unreserve - assert_storage_noop!(assert_eq!(Balances::unreserve(&account_id(137), 42), 42)); - // Reserve - assert_noop!( - Balances::reserve(&account_id(137), 42), - Error::::InsufficientBalance - ); - // Slash Reserve - assert_storage_noop!(assert_eq!(Balances::slash_reserved(&account_id(137), 42).1, 42)); - // Repatriate Reserve - assert_noop!( - Balances::repatriate_reserved(&account_id(137), &account_id(138), 42, Free), - Error::::DeadAccount - ); - // Slash - assert_storage_noop!(assert_eq!(Balances::slash(&account_id(137), 42).1, 42)); - }); -} - -#[test] -#[should_panic = "The existential deposit must be greater than zero!"] -fn zero_ed_is_prohibited() { - // These functions all use `mutate_account` which may introduce a storage change when - // the account never existed to begin with, and shouldn't exist in the end. - ExtBuilder::default().existential_deposit(0).build_and_execute_with(|| { - Balances::integrity_test(); - }); -} - -#[test] -fn named_reserve_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - - let id_1 = TestId::Foo; - let id_2 = TestId::Bar; - let id_3 = TestId::Baz; - - // reserve - - assert_noop!( - Balances::reserve_named(&id_1, &account_id(1), 112), - Error::::InsufficientBalance - ); - - assert_ok!(Balances::reserve_named(&id_1, &account_id(1), 12)); - - assert_eq!(Balances::reserved_balance(account_id(1)), 12); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 12); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 0); - - assert_ok!(Balances::reserve_named(&id_1, &account_id(1), 2)); - - assert_eq!(Balances::reserved_balance(account_id(1)), 14); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 0); - - assert_ok!(Balances::reserve_named(&id_2, &account_id(1), 23)); - - assert_eq!(Balances::reserved_balance(account_id(1)), 37); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 23); - - assert_ok!(Balances::reserve(&account_id(1), 34)); - - assert_eq!(Balances::reserved_balance(account_id(1)), 71); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 23); - - assert_eq!(Balances::total_balance(&account_id(1)), 111); - assert_eq!(Balances::free_balance(account_id(1)), 40); - - assert_noop!( - Balances::reserve_named(&id_3, &account_id(1), 2), - Error::::TooManyReserves - ); - - // unreserve - - assert_eq!(Balances::unreserve_named(&id_1, &account_id(1), 10), 0); - - assert_eq!(Balances::reserved_balance(account_id(1)), 61); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 4); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 23); - - assert_eq!(Balances::unreserve_named(&id_1, &account_id(1), 5), 1); - - assert_eq!(Balances::reserved_balance(account_id(1)), 57); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 23); - - assert_eq!(Balances::unreserve_named(&id_2, &account_id(1), 3), 0); - - assert_eq!(Balances::reserved_balance(account_id(1)), 54); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 20); - - assert_eq!(Balances::total_balance(&account_id(1)), 111); - assert_eq!(Balances::free_balance(account_id(1)), 57); - - // slash_reserved_named - - assert_ok!(Balances::reserve_named(&id_1, &account_id(1), 10)); - - assert_eq!(Balances::slash_reserved_named(&id_1, &account_id(1), 25).1, 15); - - assert_eq!(Balances::reserved_balance(account_id(1)), 54); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 20); - assert_eq!(Balances::total_balance(&account_id(1)), 101); - - assert_eq!(Balances::slash_reserved_named(&id_2, &account_id(1), 5).1, 0); - - assert_eq!(Balances::reserved_balance(account_id(1)), 49); - assert_eq!(Balances::reserved_balance_named(&id_1, &account_id(1)), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 15); - assert_eq!(Balances::total_balance(&account_id(1)), 96); - - // repatriate_reserved_named - - let _ = Balances::deposit_creating(&account_id(2), 100); - - assert_eq!( - Balances::repatriate_reserved_named( - &id_2, - &account_id(1), - &account_id(2), - 10, - Reserved - ) - .unwrap(), - 0 - ); - - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(2)), 10); - assert_eq!(Balances::reserved_balance(account_id(2)), 10); - - assert_eq!( - Balances::repatriate_reserved_named( - &id_2, - &account_id(2), - &account_id(1), - 11, - Reserved - ) - .unwrap(), - 1 - ); - - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 15); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(2)), 0); - assert_eq!(Balances::reserved_balance(account_id(2)), 0); - - assert_eq!( - Balances::repatriate_reserved_named(&id_2, &account_id(1), &account_id(2), 10, Free) - .unwrap(), - 0 - ); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(2)), 0); - assert_eq!(Balances::free_balance(account_id(2)), 110); - - // repatriate_reserved_named to self - - assert_eq!( - Balances::repatriate_reserved_named( - &id_2, - &account_id(1), - &account_id(1), - 10, - Reserved - ) - .unwrap(), - 5 - ); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 5); - - assert_eq!(Balances::free_balance(account_id(1)), 47); - - assert_eq!( - Balances::repatriate_reserved_named(&id_2, &account_id(1), &account_id(1), 15, Free) - .unwrap(), - 10 - ); - assert_eq!(Balances::reserved_balance_named(&id_2, &account_id(1)), 0); - - assert_eq!(Balances::free_balance(account_id(1)), 52); - }); -} - -#[test] -fn reserve_must_succeed_if_can_reserve_does() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 1); - let _ = Balances::deposit_creating(&account_id(2), 2); - assert!( - Balances::can_reserve(&account_id(1), 1) == - Balances::reserve(&account_id(1), 1).is_ok() - ); - assert!( - Balances::can_reserve(&account_id(2), 1) == - Balances::reserve(&account_id(2), 1).is_ok() - ); - }); -} - -#[test] -fn reserved_named_to_yourself_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 110); - - let id = TestId::Foo; - - assert_ok!(Balances::reserve_named(&id, &account_id(1), 50)); - assert_ok!( - Balances::repatriate_reserved_named(&id, &account_id(1), &account_id(1), 50, Free), - 0 - ); - assert_eq!(Balances::free_balance(account_id(1)), 110); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 0); - - assert_ok!(Balances::reserve_named(&id, &account_id(1), 50)); - assert_ok!( - Balances::repatriate_reserved_named(&id, &account_id(1), &account_id(1), 60, Free), - 10 - ); - assert_eq!(Balances::free_balance(account_id(1)), 110); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 0); - }); -} - -#[test] -fn ensure_reserved_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - - let id = TestId::Foo; - - assert_ok!(Balances::ensure_reserved_named(&id, &account_id(1), 15)); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 15); - - assert_ok!(Balances::ensure_reserved_named(&id, &account_id(1), 10)); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 10); - - assert_ok!(Balances::ensure_reserved_named(&id, &account_id(1), 20)); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 20); - }); -} - -#[test] -fn unreserve_all_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - - let id = TestId::Foo; - - assert_ok!(Balances::reserve_named(&id, &account_id(1), 15)); - - assert_eq!(Balances::unreserve_all_named(&id, &account_id(1)), 15); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 0); - assert_eq!(Balances::free_balance(account_id(1)), 111); - - assert_eq!(Balances::unreserve_all_named(&id, &account_id(1)), 0); - }); -} - -#[test] -fn slash_all_reserved_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - - let id = TestId::Foo; - - assert_ok!(Balances::reserve_named(&id, &account_id(1), 15)); - - assert_eq!(Balances::slash_all_reserved_named(&id, &account_id(1)).peek(), 15); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 0); - assert_eq!(Balances::free_balance(account_id(1)), 96); - - assert_eq!(Balances::slash_all_reserved_named(&id, &account_id(1)).peek(), 0); - }); -} - -#[test] -fn repatriate_all_reserved_named_should_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::deposit_creating(&account_id(1), 111); - let _ = Balances::deposit_creating(&account_id(2), 10); - let _ = Balances::deposit_creating(&account_id(3), 10); - - let id = TestId::Foo; - - assert_ok!(Balances::reserve_named(&id, &account_id(1), 15)); - - assert_ok!(Balances::repatriate_all_reserved_named( - &id, - &account_id(1), - &account_id(2), - Reserved - )); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(1)), 0); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(2)), 15); - - assert_ok!(Balances::repatriate_all_reserved_named( - &id, - &account_id(2), - &account_id(3), - Free - )); - assert_eq!(Balances::reserved_balance_named(&id, &account_id(2)), 0); - assert_eq!(Balances::free_balance(account_id(3)), 25); - }); -} - -#[test] -fn freezing_and_locking_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Consumer is shared between freezing and locking. - assert_eq!(System::consumers(&account_id(1)), 0); - assert_ok!(>::set_freeze( - &TestId::Foo, - &account_id(1), - 4 - )); - assert_eq!(System::consumers(&account_id(1)), 1); - Balances::set_lock(ID_1, &account_id(1), 5, WithdrawReasons::all()); - assert_eq!(System::consumers(&account_id(1)), 1); - - // Frozen and locked balances update correctly. - assert_eq!(Balances::account(&account_id(1)).frozen, 5); - assert_ok!(>::set_freeze( - &TestId::Foo, - &account_id(1), - 6 - )); - assert_eq!(Balances::account(&account_id(1)).frozen, 6); - assert_ok!(>::set_freeze( - &TestId::Foo, - &account_id(1), - 4 - )); - assert_eq!(Balances::account(&account_id(1)).frozen, 5); - Balances::set_lock(ID_1, &account_id(1), 3, WithdrawReasons::all()); - assert_eq!(Balances::account(&account_id(1)).frozen, 4); - Balances::set_lock(ID_1, &account_id(1), 5, WithdrawReasons::all()); - assert_eq!(Balances::account(&account_id(1)).frozen, 5); - - // Locks update correctly. - Balances::remove_lock(ID_1, &account_id(1)); - assert_eq!(Balances::account(&account_id(1)).frozen, 4); - assert_ok!(>::thaw(&TestId::Foo, &account_id(1))); - assert_eq!(Balances::account(&account_id(1)).frozen, 0); - assert_eq!(System::consumers(&account_id(1)), 0); - }); -} - -#[test] -fn self_transfer_noop() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - let _ = Balances::deposit_creating(&account_id(1), 100); - - // The account is set up properly: - assert_eq!( - events(), - [ - Event::Deposit { who: account_id(1), amount: 100 }.into(), - SysEvent::NewAccount { account: account_id(1) }.into(), - Event::Endowed { account: account_id(1), free_balance: 100 }.into(), - Event::Issued { amount: 100 }.into(), - ] - ); - assert_eq!(Balances::free_balance(account_id(1)), 100); - assert_eq!(pallet_balances::TotalIssuance::::get(), 100); - - // Transfers to self are No-OPs: - let _g = StorageNoopGuard::new(); - for i in 0..200 { - let r = Balances::transfer_allow_death(Some(account_id(1)).into(), account_id(1), i); - - if i <= 100 { - assert_ok!(r); - } else { - assert!(r.is_err()); - } - - assert!(events().is_empty()); - assert_eq!( - Balances::free_balance(account_id(1)), - 100, - "Balance unchanged by self transfer" - ); - assert_eq!( - pallet_balances::TotalIssuance::::get(), - 100, - "TI unchanged by self transfers" - ); - } - }); -} diff --git a/pallets/balances/src/tests/dispatchable_tests.rs b/pallets/balances/src/tests/dispatchable_tests.rs deleted file mode 100644 index 94991c96..00000000 --- a/pallets/balances/src/tests/dispatchable_tests.rs +++ /dev/null @@ -1,410 +0,0 @@ -// This file is part of Substrate. - -// 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 regarding the functionality of the dispatchables/extrinsics. - -use super::*; -use crate::{ - AdjustmentDirection::{Decrease as Dec, Increase as Inc}, - Event, -}; -use frame_support::traits::{fungible::Unbalanced, tokens::Preservation::Expendable}; -use fungible::{hold::Mutate as HoldMutate, Inspect, Mutate}; - -/// Alice account ID for more readable tests. -fn alice() -> AccountId { - account_id(1) -} - -#[test] -fn default_indexing_on_new_accounts_should_not_work2() { - ExtBuilder::default() - .existential_deposit(10) - .monied(true) - .build_and_execute_with(|| { - // account 5 should not exist - // ext_deposit is 10, value is 9, not satisfies for ext_deposit - assert_noop!( - Balances::transfer_allow_death(Some(account_id(1)).into(), account_id(5), 9), - TokenError::BelowMinimum, - ); - assert_eq!(Balances::free_balance(account_id(1)), 100); - }); -} - -#[test] -fn dust_account_removal_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .monied(true) - .build_and_execute_with(|| { - System::inc_account_nonce(account_id(2)); - assert_eq!(System::account_nonce(account_id(2)), 1); - assert_eq!(Balances::total_balance(&account_id(2)), 2000); - // index 1 (account 2) becomes zombie - assert_ok!(Balances::transfer_allow_death( - Some(account_id(2)).into(), - account_id(5), - 1901 - )); - assert_eq!(Balances::total_balance(&account_id(2)), 0); - assert_eq!(Balances::total_balance(&account_id(5)), 1901); - assert_eq!(System::account_nonce(account_id(2)), 0); - }); -} - -#[test] -fn balance_transfer_works() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&account_id(1), 111); - assert_ok!(Balances::transfer_allow_death(Some(account_id(1)).into(), account_id(2), 69)); - assert_eq!(Balances::total_balance(&account_id(1)), 42); - assert_eq!(Balances::total_balance(&account_id(2)), 69); - }); -} - -#[test] -fn force_transfer_works() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&account_id(1), 111); - assert_noop!( - Balances::force_transfer(Some(account_id(2)).into(), account_id(1), account_id(2), 69), - BadOrigin, - ); - assert_ok!(Balances::force_transfer( - RawOrigin::Root.into(), - account_id(1), - account_id(2), - 69 - )); - assert_eq!(Balances::total_balance(&account_id(1)), 42); - assert_eq!(Balances::total_balance(&account_id(2)), 69); - }); -} - -#[test] -fn balance_transfer_when_on_hold_should_not_work() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&account_id(1), 111); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 69)); - assert_noop!( - Balances::transfer_allow_death(Some(account_id(1)).into(), account_id(2), 69), - TokenError::FundsUnavailable, - ); - }); -} - -#[test] -fn transfer_keep_alive_works() { - ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { - let _ = Balances::mint_into(&account_id(1), 100); - assert_noop!( - Balances::transfer_keep_alive(Some(account_id(1)).into(), account_id(2), 100), - TokenError::NotExpendable - ); - assert_eq!(Balances::total_balance(&account_id(1)), 100); - assert_eq!(Balances::total_balance(&account_id(2)), 0); - }); -} - -#[test] -fn transfer_keep_alive_all_free_succeed() { - ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(1), 300)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 100)); - assert_ok!(Balances::transfer_keep_alive(Some(account_id(1)).into(), account_id(2), 100)); - assert_eq!(Balances::total_balance(&account_id(1)), 200); - assert_eq!(Balances::total_balance(&account_id(2)), 100); - }); -} - -#[test] -fn transfer_all_works_1() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(1), 200)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(2), 0)); - // transfer all and allow death - assert_ok!(Balances::transfer_all(Some(account_id(1)).into(), account_id(2), false)); - assert_eq!(Balances::total_balance(&account_id(1)), 0); - assert_eq!(Balances::total_balance(&account_id(2)), 200); - }); -} - -#[test] -fn transfer_all_works_2() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(1), 200)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(2), 0)); - // transfer all and keep alive - assert_ok!(Balances::transfer_all(Some(account_id(1)).into(), account_id(2), true)); - assert_eq!(Balances::total_balance(&account_id(1)), 100); - assert_eq!(Balances::total_balance(&account_id(2)), 100); - }); -} - -#[test] -fn transfer_all_works_3() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(1), 210)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 10)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(2), 0)); - // transfer all and allow death w/ reserved - assert_ok!(Balances::transfer_all(Some(account_id(1)).into(), account_id(2), false)); - assert_eq!(Balances::total_balance(&account_id(1)), 110); - assert_eq!(Balances::total_balance(&account_id(2)), 100); - }); -} - -#[test] -fn transfer_all_works_4() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // setup - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(1), 210)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 10)); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(2), 0)); - // transfer all and keep alive w/ reserved - assert_ok!(Balances::transfer_all(Some(account_id(1)).into(), account_id(2), true)); - assert_eq!(Balances::total_balance(&account_id(1)), 110); - assert_eq!(Balances::total_balance(&account_id(2)), 100); - }); -} - -#[test] -fn set_balance_handles_killing_account() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::mint_into(&account_id(1), 111); - assert_ok!(frame_system::Pallet::::inc_consumers(&account_id(1))); - assert_noop!( - Balances::force_set_balance(RuntimeOrigin::root(), account_id(1), 0), - DispatchError::ConsumerRemaining, - ); - }); -} - -#[test] -fn set_balance_handles_total_issuance() { - ExtBuilder::default().build_and_execute_with(|| { - let old_total_issuance = pallet_balances::TotalIssuance::::get(); - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(137), 69)); - assert_eq!(pallet_balances::TotalIssuance::::get(), old_total_issuance + 69); - assert_eq!(Balances::total_balance(&account_id(137)), 69); - assert_eq!(Balances::free_balance(account_id(137)), 69); - }); -} - -#[test] -fn upgrade_accounts_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - System::inc_providers(&account_id(7)); - assert_ok!(::AccountStore::try_mutate_exists( - &account_id(7), - |a| -> DispatchResult { - *a = Some(AccountData { - free: 5, - reserved: 5, - frozen: Zero::zero(), - flags: crate::types::ExtraFlags::old_logic(), - }); - Ok(()) - } - )); - assert!(!Balances::account(&account_id(7)).flags.is_new_logic()); - assert_eq!(System::providers(&account_id(7)), 1); - assert_eq!(System::consumers(&account_id(7)), 0); - assert_ok!(Balances::upgrade_accounts(Some(account_id(1)).into(), vec![account_id(7)])); - assert!(Balances::account(&account_id(7)).flags.is_new_logic()); - assert_eq!(System::providers(&account_id(7)), 1); - assert_eq!(System::consumers(&account_id(7)), 1); - - >::unreserve( - &account_id(7), - 5, - ); - assert_ok!(>::transfer( - &account_id(7), - &account_id(1), - 10, - Expendable - )); - assert_eq!(Balances::total_balance(&account_id(7)), 0); - assert_eq!(System::providers(&account_id(7)), 0); - assert_eq!(System::consumers(&account_id(7)), 0); - }); -} - -#[test] -#[docify::export] -fn force_adjust_total_issuance_example() { - ExtBuilder::default().build_and_execute_with(|| { - // First we set the TotalIssuance to 64 by giving Alice a balance of 64. - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), alice(), 64)); - let old_ti = pallet_balances::TotalIssuance::::get(); - assert_eq!(old_ti, 64, "TI should be 64"); - - // Now test the increase: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 32)); - let new_ti = pallet_balances::TotalIssuance::::get(); - assert_eq!(old_ti + 32, new_ti, "Should increase by 32"); - - // If Alice tries to call it, it errors: - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Signed(alice()).into(), Inc, 69), - BadOrigin, - ); - }); -} - -#[test] -fn force_adjust_total_issuance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(137), 64)); - let ti = pallet_balances::TotalIssuance::::get(); - - // Increase works: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 32)); - assert_eq!(pallet_balances::TotalIssuance::::get(), ti + 32); - System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced { - old: 64, - new: 96, - })); - - // Decrease works: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 64)); - assert_eq!(pallet_balances::TotalIssuance::::get(), ti - 32); - System::assert_last_event(RuntimeEvent::Balances(Event::TotalIssuanceForced { - old: 96, - new: 32, - })); - }); -} - -#[test] -fn force_adjust_total_issuance_saturates() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(137), 64)); - let ti = pallet_balances::TotalIssuance::::get(); - let max = ::Balance::max_value(); - assert_eq!(ti, 64); - - // Increment saturates: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, max)); - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 123)); - assert_eq!(pallet_balances::TotalIssuance::::get(), max); - - // Decrement saturates: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, max)); - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 123)); - assert_eq!(pallet_balances::TotalIssuance::::get(), 0); - }); -} - -#[test] -fn force_adjust_total_issuance_rejects_zero_delta() { - ExtBuilder::default().build_and_execute_with(|| { - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 0), - Error::::DeltaZero, - ); - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 0), - Error::::DeltaZero, - ); - }); -} - -#[test] -fn force_adjust_total_issuance_rejects_more_than_inactive() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), account_id(137), 64)); - Balances::deactivate(16u32.into()); - - assert_eq!(pallet_balances::TotalIssuance::::get(), 64); - assert_eq!(Balances::active_issuance(), 48); - - // Works with up to 48: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 40),); - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 8),); - assert_eq!(pallet_balances::TotalIssuance::::get(), 16); - assert_eq!(Balances::active_issuance(), 0); - // Errors with more than 48: - assert_noop!( - Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Dec, 1), - Error::::IssuanceDeactivated, - ); - // Increasing again increases the inactive issuance: - assert_ok!(Balances::force_adjust_total_issuance(RawOrigin::Root.into(), Inc, 10),); - assert_eq!(pallet_balances::TotalIssuance::::get(), 26); - assert_eq!(Balances::active_issuance(), 10); - }); -} - -#[test] -fn burn_works() { - ExtBuilder::default().build().execute_with(|| { - // Prepare account with initial balance - let (account, init_balance) = (account_id(1), 37); - assert_ok!(Balances::force_set_balance( - RuntimeOrigin::root(), - account.clone(), - init_balance - )); - let init_issuance = pallet_balances::TotalIssuance::::get(); - let (keep_alive, allow_death) = (true, false); - - // 1. Cannot burn more than what's available - assert_noop!( - Balances::burn(Some(account.clone()).into(), init_balance + 1, allow_death), - TokenError::FundsUnavailable, - ); - - // 2. Burn some funds, without reaping the account - let burn_amount_1 = 1; - assert_ok!(Balances::burn(Some(account.clone()).into(), burn_amount_1, allow_death)); - System::assert_last_event(RuntimeEvent::Balances(Event::Burned { - who: account.clone(), - amount: burn_amount_1, - })); - assert_eq!(pallet_balances::TotalIssuance::::get(), init_issuance - burn_amount_1); - assert_eq!(Balances::total_balance(&account), init_balance - burn_amount_1); - - // 3. Cannot burn funds below existential deposit if `keep_alive` is `true` - let burn_amount_2 = - init_balance - burn_amount_1 - ::ExistentialDeposit::get() + 1; - assert_noop!( - Balances::burn(Some(account.clone()).into(), init_balance + 1, keep_alive), - TokenError::FundsUnavailable, - ); - - // 4. Burn some more funds, this time reaping the account - assert_ok!(Balances::burn(Some(account.clone()).into(), burn_amount_2, allow_death)); - System::assert_last_event(RuntimeEvent::Balances(Event::Burned { - who: account.clone(), - amount: burn_amount_2, - })); - assert_eq!( - pallet_balances::TotalIssuance::::get(), - init_issuance - burn_amount_1 - burn_amount_2 - ); - assert!(Balances::total_balance(&account).is_zero()); - }); -} diff --git a/pallets/balances/src/tests/fungible_conformance_tests.rs b/pallets/balances/src/tests/fungible_conformance_tests.rs deleted file mode 100644 index 5c0c19a5..00000000 --- a/pallets/balances/src/tests/fungible_conformance_tests.rs +++ /dev/null @@ -1,141 +0,0 @@ -// This file is part of Substrate. - -// 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 frame_support::traits::fungible::{conformance_tests, Inspect, Mutate}; -use paste::paste; - -macro_rules! generate_tests { - // Handle a conformance test that requires special testing with and without a dust trap. - (dust_trap_variation, $base_path:path, $scope:expr, $trait:ident, $ext_deposit:expr, $($test_name:ident),*) => { - $( - paste! { - #[test] - fn [<$trait _ $scope _ $test_name _existential_deposit_ $ext_deposit _dust_trap_on >]() { - // Some random trap account. - let trap_account = ::AccountId::from(65174286u64); - let builder = ExtBuilder::default().existential_deposit($ext_deposit).dust_trap(trap_account); - builder.build_and_execute_with(|| { - Balances::set_balance(&trap_account, Balances::minimum_balance()); - $base_path::$scope::$trait::$test_name::< - Balances, - ::AccountId, - >(Some(trap_account)); - }); - } - - #[test] - fn [< $trait _ $scope _ $test_name _existential_deposit_ $ext_deposit _dust_trap_off >]() { - let builder = ExtBuilder::default().existential_deposit($ext_deposit); - builder.build_and_execute_with(|| { - $base_path::$scope::$trait::$test_name::< - Balances, - ::AccountId, - >(None); - }); - } - } - )* - }; - // Regular conformance test - ($base_path:path, $scope:expr, $trait:ident, $ext_deposit:expr, $($test_name:ident),*) => { - $( - paste! { - #[test] - fn [< $trait _ $scope _ $test_name _existential_deposit_ $ext_deposit>]() { - let builder = ExtBuilder::default().existential_deposit($ext_deposit); - builder.build_and_execute_with(|| { - $base_path::$scope::$trait::$test_name::< - Balances, - ::AccountId, - >(); - }); - } - } - )* - }; - ($base_path:path, $ext_deposit:expr) => { - // regular::mutate - generate_tests!( - dust_trap_variation, - $base_path, - regular, - mutate, - $ext_deposit, - transfer_expendable_dust - ); - generate_tests!( - $base_path, - regular, - mutate, - $ext_deposit, - 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_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 - ); - // regular::unbalanced - generate_tests!( - $base_path, - regular, - unbalanced, - $ext_deposit, - write_balance, - decrease_balance_expendable, - decrease_balance_preserve, - increase_balance, - set_total_issuance, - deactivate_and_reactivate - ); - // regular::balanced - generate_tests!( - $base_path, - regular, - balanced, - $ext_deposit, - issue_and_resolve_credit, - rescind_and_settle_debt, - deposit, - withdraw, - pair - ); - }; -} - -generate_tests!(conformance_tests, 1); -generate_tests!(conformance_tests, 5); -generate_tests!(conformance_tests, 1000); diff --git a/pallets/balances/src/tests/fungible_tests.rs b/pallets/balances/src/tests/fungible_tests.rs deleted file mode 100644 index 4840258e..00000000 --- a/pallets/balances/src/tests/fungible_tests.rs +++ /dev/null @@ -1,883 +0,0 @@ -// This file is part of Substrate. - -// 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 regarding the functionality of the `fungible` trait set implementations. - -use super::*; -use frame_support::traits::{ - tokens::{ - Fortitude::{Force, Polite}, - Precision::{BestEffort, Exact}, - Preservation::{Expendable, Preserve, Protect}, - Restriction::Free, - }, - Consideration, Footprint, LinearStoragePrice, MaybeConsideration, -}; -use fungible::{ - FreezeConsideration, HoldConsideration, Inspect, InspectFreeze, InspectHold, - LoneFreezeConsideration, LoneHoldConsideration, Mutate, MutateFreeze, MutateHold, Unbalanced, -}; -use sp_core::ConstU128; - -#[test] -fn inspect_trait_reducible_balance_basic_works() { - ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { - Balances::set_balance(&account_id(1), 100); - assert_eq!(Balances::reducible_balance(&account_id(1), Expendable, Polite), 100); - assert_eq!(Balances::reducible_balance(&account_id(1), Protect, Polite), 90); - assert_eq!(Balances::reducible_balance(&account_id(1), Preserve, Polite), 90); - assert_eq!(Balances::reducible_balance(&account_id(1), Expendable, Force), 100); - assert_eq!(Balances::reducible_balance(&account_id(1), Protect, Force), 90); - assert_eq!(Balances::reducible_balance(&account_id(1), Preserve, Force), 90); - }); -} - -#[test] -fn inspect_trait_reducible_balance_other_provide_works() { - ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { - Balances::set_balance(&account_id(1), 100); - System::inc_providers(&account_id(1)); - assert_eq!(Balances::reducible_balance(&account_id(1), Expendable, Polite), 100); - assert_eq!(Balances::reducible_balance(&account_id(1), Protect, Polite), 100); - assert_eq!(Balances::reducible_balance(&account_id(1), Preserve, Polite), 90); - assert_eq!(Balances::reducible_balance(&account_id(1), Expendable, Force), 100); - assert_eq!(Balances::reducible_balance(&account_id(1), Protect, Force), 100); - assert_eq!(Balances::reducible_balance(&account_id(1), Preserve, Force), 90); - }); -} - -#[test] -fn inspect_trait_reducible_balance_frozen_works() { - ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { - Balances::set_balance(&account_id(1), 100); - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 50)); - assert_eq!(Balances::reducible_balance(&account_id(1), Expendable, Polite), 50); - assert_eq!(Balances::reducible_balance(&account_id(1), Protect, Polite), 50); - assert_eq!(Balances::reducible_balance(&account_id(1), Preserve, Polite), 50); - assert_eq!(Balances::reducible_balance(&account_id(1), Expendable, Force), 90); - assert_eq!(Balances::reducible_balance(&account_id(1), Protect, Force), 90); - assert_eq!(Balances::reducible_balance(&account_id(1), Preserve, Force), 90); - }); -} - -#[test] -fn unbalanced_trait_set_balance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_eq!(>::balance(&account_id(137)), 0); - assert_ok!(Balances::write_balance(&account_id(137), 100)); - assert_eq!(>::balance(&account_id(137)), 100); - - assert_ok!(>::hold(&TestId::Foo, &account_id(137), 60)); - assert_eq!(>::balance(&account_id(137)), 40); - assert_eq!( - >::total_balance_on_hold(&account_id(137)), - 60 - ); - assert_eq!( - >::balance_on_hold(&TestId::Foo, &account_id(137)), - 60 - ); - - assert_noop!( - Balances::write_balance(&account_id(137), 0), - Error::::InsufficientBalance - ); - - assert_ok!(Balances::write_balance(&account_id(137), 1)); - assert_eq!(>::balance(&account_id(137)), 1); - assert_eq!( - >::balance_on_hold(&TestId::Foo, &account_id(137)), - 60 - ); - - assert_ok!(>::release( - &TestId::Foo, - &account_id(137), - 60, - Exact - )); - assert_eq!( - >::balance_on_hold(&TestId::Foo, &account_id(137)), - 0 - ); - assert_eq!( - >::total_balance_on_hold(&account_id(137)), - 0 - ); - }); -} - -#[test] -fn unbalanced_trait_set_total_issuance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_eq!(>::total_issuance(), 0); - Balances::set_total_issuance(100); - assert_eq!(>::total_issuance(), 100); - }); -} - -#[test] -fn unbalanced_trait_decrease_balance_simple_works() { - ExtBuilder::default().build_and_execute_with(|| { - // An Account that starts at 100 - assert_ok!(Balances::write_balance(&account_id(137), 100)); - assert_eq!(>::balance(&account_id(137)), 100); - // and reserves 50 - assert_ok!(>::hold(&TestId::Foo, &account_id(137), 50)); - assert_eq!(>::balance(&account_id(137)), 50); - // and is decreased by 20 - assert_ok!(Balances::decrease_balance(&account_id(137), 20, Exact, Expendable, Polite)); - assert_eq!(>::balance(&account_id(137)), 30); - }); -} - -#[test] -fn unbalanced_trait_decrease_balance_works_1() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::write_balance(&account_id(137), 100)); - assert_eq!(>::balance(&account_id(137)), 100); - - assert_noop!( - Balances::decrease_balance(&account_id(137), 101, Exact, Expendable, Polite), - TokenError::FundsUnavailable - ); - assert_eq!( - Balances::decrease_balance(&account_id(137), 100, Exact, Expendable, Polite), - Ok(100) - ); - assert_eq!(>::balance(&account_id(137)), 0); - }); -} - -#[test] -fn unbalanced_trait_decrease_balance_works_2() { - ExtBuilder::default().build_and_execute_with(|| { - // free: 40, reserved: 60 - assert_ok!(Balances::write_balance(&account_id(137), 100)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(137), 60)); - assert_eq!(>::balance(&account_id(137)), 40); - assert_eq!(Balances::total_balance_on_hold(&account_id(137)), 60); - assert_noop!( - Balances::decrease_balance(&account_id(137), 40, Exact, Expendable, Polite), - TokenError::FundsUnavailable - ); - assert_eq!( - Balances::decrease_balance(&account_id(137), 39, Exact, Expendable, Polite), - Ok(39) - ); - assert_eq!(>::balance(&account_id(137)), 1); - assert_eq!(Balances::total_balance_on_hold(&account_id(137)), 60); - }); -} - -#[test] -fn unbalanced_trait_decrease_balance_at_most_works_1() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::write_balance(&account_id(137), 100)); - assert_eq!(>::balance(&account_id(137)), 100); - - assert_eq!( - Balances::decrease_balance(&account_id(137), 101, BestEffort, Expendable, Polite), - Ok(100) - ); - assert_eq!(>::balance(&account_id(137)), 0); - }); -} - -#[test] -fn unbalanced_trait_decrease_balance_at_most_works_2() { - ExtBuilder::default().build_and_execute_with(|| { - assert_ok!(Balances::write_balance(&account_id(137), 99)); - assert_eq!( - Balances::decrease_balance(&account_id(137), 99, BestEffort, Expendable, Polite), - Ok(99) - ); - assert_eq!(>::balance(&account_id(137)), 0); - }); -} - -#[test] -fn unbalanced_trait_decrease_balance_at_most_works_3() { - ExtBuilder::default().build_and_execute_with(|| { - // free: 40, reserved: 60 - assert_ok!(Balances::write_balance(&account_id(137), 100)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(137), 60)); - assert_eq!(Balances::free_balance(account_id(137)), 40); - assert_eq!(Balances::total_balance_on_hold(&account_id(137)), 60); - assert_eq!( - Balances::decrease_balance(&account_id(137), 0, BestEffort, Expendable, Polite), - Ok(0) - ); - assert_eq!(Balances::free_balance(account_id(137)), 40); - assert_eq!(Balances::total_balance_on_hold(&account_id(137)), 60); - assert_eq!( - Balances::decrease_balance(&account_id(137), 10, BestEffort, Expendable, Polite), - Ok(10) - ); - assert_eq!(Balances::free_balance(account_id(137)), 30); - assert_eq!( - Balances::decrease_balance(&account_id(137), 200, BestEffort, Expendable, Polite), - Ok(29) - ); - assert_eq!(>::balance(&account_id(137)), 1); - assert_eq!(Balances::free_balance(account_id(137)), 1); - assert_eq!(Balances::total_balance_on_hold(&account_id(137)), 60); - }); -} - -#[test] -fn unbalanced_trait_increase_balance_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_noop!( - Balances::increase_balance(&account_id(137), 0, Exact), - TokenError::BelowMinimum - ); - assert_eq!(Balances::increase_balance(&account_id(137), 1, Exact), Ok(1)); - assert_noop!( - Balances::increase_balance(&account_id(137), Balance::MAX, Exact), - ArithmeticError::Overflow - ); - }); -} - -#[test] -fn unbalanced_trait_increase_balance_at_most_works() { - ExtBuilder::default().build_and_execute_with(|| { - assert_eq!(Balances::increase_balance(&account_id(137), 0, BestEffort), Ok(0)); - assert_eq!(Balances::increase_balance(&account_id(137), 1, BestEffort), Ok(1)); - assert_eq!( - Balances::increase_balance(&account_id(137), Balance::MAX, BestEffort), - Ok(Balance::MAX - 1) - ); - }); -} - -#[test] -fn freezing_and_holds_should_overlap() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 10)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 9)); - assert_eq!(Balances::account(&account_id(1)).free, 1); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_eq!(Balances::account(&account_id(1)).free, 1); - assert_eq!(Balances::account(&account_id(1)).frozen, 10); - assert_eq!(Balances::account(&account_id(1)).reserved, 9); - assert_eq!(Balances::total_balance_on_hold(&account_id(1)), 9); - }); -} - -#[test] -fn frozen_hold_balance_cannot_be_moved_without_force() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 10)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 9)); - assert_eq!(Balances::reducible_total_balance_on_hold(&account_id(1), Force), 9); - assert_eq!(Balances::reducible_total_balance_on_hold(&account_id(1), Polite), 0); - let e = TokenError::Frozen; - assert_noop!( - Balances::transfer_on_hold( - &TestId::Foo, - &account_id(1), - &account_id(2), - 1, - Exact, - Free, - Polite - ), - e - ); - assert_ok!(Balances::transfer_on_hold( - &TestId::Foo, - &account_id(1), - &account_id(2), - 1, - Exact, - Free, - Force - )); - }); -} - -#[test] -fn frozen_hold_balance_best_effort_transfer_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 5)); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 9)); - assert_eq!(Balances::reducible_total_balance_on_hold(&account_id(1), Force), 9); - assert_eq!(Balances::reducible_total_balance_on_hold(&account_id(1), Polite), 5); - assert_ok!(Balances::transfer_on_hold( - &TestId::Foo, - &account_id(1), - &account_id(2), - 10, - BestEffort, - Free, - Polite - )); - assert_eq!(Balances::total_balance(&account_id(1)), 5); - assert_eq!(Balances::total_balance(&account_id(2)), 25); - }); -} - -#[test] -fn partial_freezing_should_work() { - ExtBuilder::default() - .existential_deposit(10) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 5)); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 5, - Expendable - )); - // After transferring 5, balance is 95. With 10 frozen, can transfer up to 85 more - // (95-10=85) - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 85, - Expendable - )); - // Now balance is 10. Transferring 1 more would leave 9, which is < 10 frozen, so should - // fail - assert_noop!( - >::transfer( - &account_id(1), - &account_id(2), - 1, - Expendable - ), - TokenError::Frozen - ); - }); -} - -#[test] -fn thaw_should_work() { - ExtBuilder::default() - .existential_deposit(10) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), Balance::MAX)); - assert_ok!(Balances::thaw(&TestId::Foo, &account_id(1))); - assert_eq!(System::consumers(&account_id(1)), 0); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &account_id(1)), 0); - assert_eq!(Balances::account(&account_id(1)).frozen, 0); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 10, - Expendable - )); - }); -} - -#[test] -fn set_freeze_zero_should_work() { - ExtBuilder::default() - .existential_deposit(10) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), Balance::MAX)); - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 0)); - assert_eq!(System::consumers(&account_id(1)), 0); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &account_id(1)), 0); - assert_eq!(Balances::account(&account_id(1)).frozen, 0); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 10, - Expendable - )); - }); -} - -#[test] -fn set_freeze_should_work() { - ExtBuilder::default() - .existential_deposit(10) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), Balance::MAX)); - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 5)); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 5, - Expendable - )); - // After transferring 5, balance is 95. With 10 frozen, can transfer up to 85 more - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 85, - Expendable - )); - // Now balance is 10. Transferring 1 more would leave 9, which is < 10 frozen, so should - // fail - assert_noop!( - >::transfer( - &account_id(1), - &account_id(2), - 1, - Expendable - ), - TokenError::Frozen - ); - }); -} - -#[test] -fn extend_freeze_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 5)); - assert_ok!(Balances::extend_freeze(&TestId::Foo, &account_id(1), 10)); - assert_eq!(Balances::account(&account_id(1)).frozen, 10); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &account_id(1)), 10); - assert_noop!( - >::transfer( - &account_id(1), - &account_id(2), - 1, - Expendable - ), - TokenError::Frozen - ); - }); -} - -#[test] -fn debug_freeze_behavior() { - ExtBuilder::default() - .existential_deposit(10) - .monied(true) - .build_and_execute_with(|| { - println!("=== Debug freeze behavior ==="); - println!("Initial balance: {}", Balances::free_balance(account_id(1))); - - // Set a freeze - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 10)); - println!("After freeze(10):"); - println!(" Free balance: {}", Balances::free_balance(account_id(1))); - println!(" Frozen amount: {}", Balances::balance_frozen(&TestId::Foo, &account_id(1))); - println!(" Account frozen: {}", Balances::account(&account_id(1)).frozen); - - // Try transfers of different amounts - for amount in [1, 5, 10, 89, 90, 91] { - let balance_before = Balances::free_balance(account_id(1)); - let result = >::transfer( - &account_id(1), - &account_id(2), - amount, - Expendable, - ); - let balance_after = Balances::free_balance(account_id(1)); - - println!( - "Transfer {} units: {:?} (balance: {} -> {})", - amount, result, balance_before, balance_after - ); - - // Restore balance for next test - if result.is_ok() { - let _ = >::transfer( - &account_id(2), - &account_id(1), - amount, - Expendable, - ); - } - } - }); -} - -#[test] -fn double_freezing_should_work() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 5)); - assert_ok!(Balances::set_freeze(&TestId::Bar, &account_id(1), 5)); - assert_eq!(System::consumers(&account_id(1)), 1); - assert_ok!(>::transfer( - &account_id(1), - &account_id(2), - 5, - Expendable - )); - assert_noop!( - >::transfer( - &account_id(1), - &account_id(2), - 1, - Expendable - ), - TokenError::Frozen - ); - }); -} - -#[test] -fn can_hold_entire_balance_when_second_provider() { - ExtBuilder::default() - .existential_deposit(1) - .monied(false) - .build_and_execute_with(|| { - >::set_balance(&account_id(1), 100); - assert_noop!( - Balances::hold(&TestId::Foo, &account_id(1), 100), - TokenError::FundsUnavailable - ); - System::inc_providers(&account_id(1)); - assert_eq!(System::providers(&account_id(1)), 2); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 100)); - assert_eq!(System::providers(&account_id(1)), 1); - assert_noop!(System::dec_providers(&account_id(1)), DispatchError::ConsumerRemaining); - }); -} - -#[test] -fn unholding_frees_hold_slot() { - ExtBuilder::default() - .existential_deposit(1) - .monied(false) - .build_and_execute_with(|| { - >::set_balance(&account_id(1), 100); - assert_ok!(Balances::hold(&TestId::Foo, &account_id(1), 10)); - assert_ok!(Balances::hold(&TestId::Bar, &account_id(1), 10)); - assert_ok!(Balances::release(&TestId::Foo, &account_id(1), 10, Exact)); - assert_ok!(Balances::hold(&TestId::Baz, &account_id(1), 10)); - }); -} - -#[test] -fn sufficients_work_properly_with_reference_counting() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Only run PoC when the system pallet is enabled, since the underlying bug is in the - // system pallet it won't work with BalancesAccountStore - if UseSystem::get() { - // Start with a balance of 100 - >::set_balance(&account_id(1), 100); - // Emulate a sufficient, in reality this could be reached by transferring a - // sufficient asset to the account - System::inc_sufficients(&account_id(1)); - // Spend the same balance multiple times - assert_ok!(>::transfer( - &account_id(1), - &account_id(137), - 100, - Expendable - )); - assert_eq!(Balances::free_balance(account_id(1)), 0); - assert_noop!( - >::transfer( - &account_id(1), - &account_id(137), - 100, - Expendable - ), - TokenError::FundsUnavailable - ); - } - }); -} - -#[test] -fn emit_events_with_changing_freezes() { - ExtBuilder::default().build_and_execute_with(|| { - let _ = Balances::set_balance(&account_id(1), 100); - System::reset_events(); - - // Freeze = [] --> [10] - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 10)); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Frozen { who: account_id(1), amount: 10 })] - ); - - // Freeze = [10] --> [15] - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 15)); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Frozen { who: account_id(1), amount: 5 })] - ); - - // Freeze = [15] --> [15, 20] - assert_ok!(Balances::set_freeze(&TestId::Bar, &account_id(1), 20)); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Frozen { who: account_id(1), amount: 5 })] - ); - - // Freeze = [15, 20] --> [17, 20] - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 17)); - for event in events() { - match event { - RuntimeEvent::Balances(crate::Event::Frozen { .. }) => { - assert!(false, "unexpected freeze event") - }, - RuntimeEvent::Balances(crate::Event::Thawed { .. }) => { - assert!(false, "unexpected thaw event") - }, - _ => continue, - } - } - - // Freeze = [17, 20] --> [17, 15] - assert_ok!(Balances::set_freeze(&TestId::Bar, &account_id(1), 15)); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Thawed { who: account_id(1), amount: 3 })] - ); - - // Freeze = [17, 15] --> [15] - assert_ok!(Balances::thaw(&TestId::Foo, &account_id(1))); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Thawed { who: account_id(1), amount: 2 })] - ); - - // Freeze = [15] --> [] - assert_ok!(Balances::thaw(&TestId::Bar, &account_id(1))); - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Thawed { who: account_id(1), amount: 15 })] - ); - }); -} - -#[test] -fn withdraw_precision_exact_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - assert_ok!(Balances::set_freeze(&TestId::Foo, &account_id(1), 10)); - assert_eq!(Balances::account(&account_id(1)).free, 10); - assert_eq!(Balances::account(&account_id(1)).frozen, 10); - - // `BestEffort` will not reduce anything - assert_ok!(>::withdraw( - &account_id(1), - 5, - BestEffort, - Preserve, - Polite - )); - - assert_eq!(Balances::account(&account_id(1)).free, 10); - assert_eq!(Balances::account(&account_id(1)).frozen, 10); - - assert_noop!( - >::withdraw( - &account_id(1), - 5, - Exact, - Preserve, - Polite - ), - TokenError::FundsUnavailable - ); - }); -} - -#[test] -fn freeze_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = FreezeConsideration< - AccountId, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = account_id(4); - // freeze amount taken somewhere outside of our (Consideration) scope. - let extend_freeze = 15; - - let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); - - assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, extend_freeze)); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4 + extend_freeze); - - let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 8 + extend_freeze); - - let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), extend_freeze); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10 + extend_freeze); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), extend_freeze); - }); -} - -#[test] -fn hold_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = HoldConsideration< - AccountId, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = account_id(4); - // hold amount taken somewhere outside of our (Consideration) scope. - let extend_hold = 15; - - let ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - - let ticket = ticket.update(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); - - assert_ok!(Balances::hold(&TestId::Foo, &who, extend_hold)); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4 + extend_hold); - - let ticket = ticket.update(&who, Footprint::from_parts(8, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 8 + extend_hold); - - let ticket = ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(); - assert!(ticket.is_none()); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), extend_hold); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10 + extend_hold); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), extend_hold); - }); -} - -#[test] -fn lone_freeze_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = LoneFreezeConsideration< - AccountId, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = account_id(4); - let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); - - assert_ok!(Balances::increase_frozen(&TestId::Foo, &who, 5)); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 15); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 4); - - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 10); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_frozen(&TestId::Foo, &who), 0); - }); -} - -#[test] -fn lone_hold_consideration_works() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - type Consideration = LoneHoldConsideration< - AccountId, - Balances, - FooReason, - LinearStoragePrice, ConstU128<1>, Balance>, - Footprint, - >; - - let who = account_id(4); - let zero_ticket = Consideration::new(&who, Footprint::from_parts(0, 0)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); - - assert_ok!(Balances::hold(&TestId::Foo, &who, 5)); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 15); - - let ticket = ticket.update(&who, Footprint::from_parts(4, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 4); - - assert_eq!(ticket.update(&who, Footprint::from_parts(0, 0)).unwrap(), zero_ticket); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - - let ticket = Consideration::new(&who, Footprint::from_parts(10, 1)).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 10); - - ticket.drop(&who).unwrap(); - assert_eq!(Balances::balance_on_hold(&TestId::Foo, &who), 0); - }); -} diff --git a/pallets/balances/src/tests/general_tests.rs b/pallets/balances/src/tests/general_tests.rs deleted file mode 100644 index a56f9f98..00000000 --- a/pallets/balances/src/tests/general_tests.rs +++ /dev/null @@ -1,143 +0,0 @@ -// This file is part of Substrate. - -// 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. - -#![cfg(test)] - -use crate::{ - system::AccountInfo, - tests::{account_id, ensure_ti_valid, Balances, ExtBuilder, System, Test, TestId, UseSystem}, - AccountData, ExtraFlags, TotalIssuance, -}; -use frame_support::{ - assert_noop, assert_ok, hypothetically, - traits::{ - fungible::{Mutate, MutateHold}, - tokens::Precision, - }, -}; -use sp_runtime::DispatchError; - -/// There are some accounts that have one consumer ref too few. These accounts are at risk of losing -/// their held (reserved) balance. They do not just lose it - it is also not accounted for in the -/// Total Issuance. Here we test the case that the account does not reap in such a case, but gets -/// one consumer ref for its reserved balance. -#[test] -fn regression_historic_acc_does_not_evaporate_reserve() { - ExtBuilder::default().build_and_execute_with(|| { - UseSystem::set(true); - let (alice, bob) = (account_id(0), account_id(1)); - // Alice is in a bad state with consumer == 0 && reserved > 0: - Balances::set_balance(&alice, 100); - TotalIssuance::::put(100); - ensure_ti_valid(); - - assert_ok!(Balances::hold(&TestId::Foo, &alice, 10)); - // This is the issue of the account: - System::dec_consumers(&alice); - - assert_eq!( - System::account(&alice), - AccountInfo { - data: AccountData { - free: 90, - reserved: 10, - frozen: 0, - flags: ExtraFlags(1u128 << 127), - }, - nonce: 0, - consumers: 0, // should be 1 on a good acc - providers: 1, - sufficients: 0, - } - ); - - ensure_ti_valid(); - - // Reaping the account is prevented by the new logic: - assert_noop!( - Balances::transfer_allow_death(Some(alice.clone()).into(), bob.clone(), 90), - DispatchError::ConsumerRemaining - ); - assert_noop!( - Balances::transfer_all(Some(alice.clone()).into(), bob.clone(), false), - DispatchError::ConsumerRemaining - ); - - // normal transfers still work: - hypothetically!({ - assert_ok!(Balances::transfer_keep_alive(Some(alice.clone()).into(), bob.clone(), 40)); - // Alice got back her consumer ref: - assert_eq!(System::consumers(&alice), 1); - ensure_ti_valid(); - }); - hypothetically!({ - assert_ok!(Balances::transfer_all(Some(alice.clone()).into(), bob.clone(), true)); - // Alice got back her consumer ref: - assert_eq!(System::consumers(&alice), 1); - ensure_ti_valid(); - }); - - // un-reserving all does not add a consumer ref: - hypothetically!({ - assert_ok!(Balances::release(&TestId::Foo, &alice, 10, Precision::Exact)); - assert_eq!(System::consumers(&alice), 0); - assert_ok!(Balances::transfer_keep_alive(Some(alice.clone()).into(), bob.clone(), 40)); - assert_eq!(System::consumers(&alice), 0); - ensure_ti_valid(); - }); - // un-reserving some does add a consumer ref: - hypothetically!({ - assert_ok!(Balances::release(&TestId::Foo, &alice, 5, Precision::Exact)); - assert_eq!(System::consumers(&alice), 1); - assert_ok!(Balances::transfer_keep_alive(Some(alice.clone()).into(), bob.clone(), 40)); - assert_eq!(System::consumers(&alice), 1); - ensure_ti_valid(); - }); - }); -} - -#[cfg(feature = "try-runtime")] -#[test] -fn try_state_works() { - use crate::{Config, Freezes, Holds}; - use frame_support::{ - storage, - traits::{Get, Hooks, VariantCount}, - }; - - ExtBuilder::default().build_and_execute_with(|| { - storage::unhashed::put( - &Holds::::hashed_key_for(account_id(1)), - &vec![0u8; ::RuntimeHoldReason::VARIANT_COUNT as usize + 1], - ); - - assert!(format!("{:?}", Balances::try_state(0).unwrap_err()) - .contains("Found `Hold` with too many elements")); - }); - - ExtBuilder::default().build_and_execute_with(|| { - let max_freezes: u32 = ::MaxFreezes::get(); - - storage::unhashed::put( - &Freezes::::hashed_key_for(account_id(1)), - &vec![0u8; max_freezes as usize + 1], - ); - - assert!(format!("{:?}", Balances::try_state(0).unwrap_err()) - .contains("Found `Freeze` with too many elements")); - }); -} diff --git a/pallets/balances/src/tests/mod.rs b/pallets/balances/src/tests/mod.rs deleted file mode 100644 index cc469066..00000000 --- a/pallets/balances/src/tests/mod.rs +++ /dev/null @@ -1,330 +0,0 @@ -// This file is part of Substrate. - -// 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. - -#![cfg(test)] - -use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet, TotalIssuance}; -use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; -use frame_support::{ - assert_err, assert_noop, assert_ok, assert_storage_noop, derive_impl, - dispatch::{DispatchInfo, GetDispatchInfo}, - parameter_types, - traits::{ - fungible, ConstU32, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, StorageMapShim, - StoredMap, VariantCount, VariantCountOf, WhitelistedStorageKeys, - }, - weights::{IdentityFee, Weight}, -}; -use frame_system::{self as system, RawOrigin}; -use pallet_transaction_payment::{ChargeTransactionPayment, FungibleAdapter, Multiplier}; -use scale_info::TypeInfo; -use sp_core::hexdisplay::HexDisplay; -use sp_runtime::{ - traits::{BadOrigin, Zero}, - ArithmeticError, BuildStorage, DispatchError, DispatchResult, FixedPointNumber, RuntimeDebug, - TokenError, -}; -use std::collections::BTreeSet; - -mod currency_tests; -mod dispatchable_tests; -// mod fungible_conformance_tests; // Commented out due to AccountId32 incompatibility -mod fungible_tests; -mod general_tests; -mod reentrancy_tests; -mod transfer_counter_tests; -type Block = frame_system::mocking::MockBlock; -type AccountId = sp_core::crypto::AccountId32; - -#[derive( - Encode, - Decode, - Copy, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - MaxEncodedLen, - TypeInfo, - DecodeWithMemTracking, - RuntimeDebug, -)] -pub enum TestId { - Foo, - Bar, - Baz, -} - -impl VariantCount for TestId { - const VARIANT_COUNT: u32 = 3; -} - -frame_support::construct_runtime!( - pub enum Test { - System: frame_system, - Balances: pallet_balances, - TransactionPayment: pallet_transaction_payment, - } -); - -type Balance = u128; - -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - frame_support::weights::Weight::from_parts(1024, u64::MAX), - ); - pub static ExistentialDeposit: Balance = 1; -} - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Test { - type Block = Block; - type AccountId = AccountId; - type Lookup = sp_runtime::traits::IdentityLookup; - type AccountData = super::AccountData; -} - -#[derive_impl(pallet_transaction_payment::config_preludes::TestDefaultConfig)] -impl pallet_transaction_payment::Config for Test { - type OnChargeTransaction = FungibleAdapter, ()>; - type OperationalFeeMultiplier = ConstU8<5>; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; -} - -parameter_types! { - pub FooReason: TestId = TestId::Foo; -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl Config for Test { - type Balance = Balance; - type DustRemoval = DustTrap; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = TestAccountStore; - type MaxReserves = ConstU32<2>; - type ReserveIdentifier = TestId; - type RuntimeHoldReason = TestId; - type RuntimeFreezeReason = TestId; - type FreezeIdentifier = TestId; - type MaxFreezes = VariantCountOf; -} - -#[derive(Clone)] -pub struct ExtBuilder { - existential_deposit: Balance, - monied: bool, - dust_trap: Option, -} -impl Default for ExtBuilder { - fn default() -> Self { - Self { existential_deposit: 1, monied: false, dust_trap: None } - } -} -impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: Balance) -> Self { - self.existential_deposit = existential_deposit; - self - } - pub fn monied(mut self, monied: bool) -> Self { - self.monied = monied; - if self.existential_deposit == 0 { - self.existential_deposit = 1; - } - self - } - pub fn dust_trap(mut self, account: u64) -> Self { - self.dust_trap = Some(account); - self - } - pub fn set_associated_consts(&self) { - DUST_TRAP_TARGET.with(|v| v.replace(self.dust_trap)); - EXISTENTIAL_DEPOSIT.with(|v| v.replace(self.existential_deposit)); - } - pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { - balances: if self.monied { - vec![ - (account_id(1), 10 * self.existential_deposit), - (account_id(2), 20 * self.existential_deposit), - (account_id(3), 30 * self.existential_deposit), - (account_id(4), 40 * self.existential_deposit), - (account_id(12), 10 * self.existential_deposit), - ] - } else { - vec![] - }, - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } - pub fn build_and_execute_with(self, f: impl Fn()) { - let other = self.clone(); - UseSystem::set(false); - other.build().execute_with(&f); - UseSystem::set(true); - self.build().execute_with(f); - } -} - -parameter_types! { - static DustTrapTarget: Option = None; -} - -pub struct DustTrap; - -impl OnUnbalanced> for DustTrap { - fn on_nonzero_unbalanced(amount: CreditOf) { - match DustTrapTarget::get() { - None => drop(amount), - Some(a) => { - let account = account_id(a as u8); - let result = >::resolve(&account, amount); - debug_assert!(result.is_ok()); - }, - } - } -} - -parameter_types! { - pub static UseSystem: bool = false; -} - -type BalancesAccountStore = - StorageMapShim, AccountId, super::AccountData>; -type SystemAccountStore = frame_system::Pallet; - -pub struct TestAccountStore; -impl StoredMap> for TestAccountStore { - fn get(k: &AccountId) -> super::AccountData { - if UseSystem::get() { - >::get(k) - } else { - >::get(k) - } - } - fn try_mutate_exists>( - k: &AccountId, - f: impl FnOnce(&mut Option>) -> Result, - ) -> Result { - if UseSystem::get() { - >::try_mutate_exists(k, f) - } else { - >::try_mutate_exists(k, f) - } - } - fn mutate( - k: &AccountId, - f: impl FnOnce(&mut super::AccountData) -> R, - ) -> Result { - if UseSystem::get() { - >::mutate(k, f) - } else { - >::mutate(k, f) - } - } - fn mutate_exists( - k: &AccountId, - f: impl FnOnce(&mut Option>) -> R, - ) -> Result { - if UseSystem::get() { - >::mutate_exists(k, f) - } else { - >::mutate_exists(k, f) - } - } - fn insert(k: &AccountId, t: super::AccountData) -> Result<(), DispatchError> { - if UseSystem::get() { - >::insert(k, t) - } else { - >::insert(k, t) - } - } - fn remove(k: &AccountId) -> Result<(), DispatchError> { - if UseSystem::get() { - >::remove(k) - } else { - >::remove(k) - } - } -} - -pub fn events() -> Vec { - let evt = System::events().into_iter().map(|evt| evt.event).collect::>(); - System::reset_events(); - evt -} - -/// create a transaction info struct from weight. Handy to avoid building the whole struct. -pub fn info_from_weight(w: Weight) -> DispatchInfo { - DispatchInfo { call_weight: w, ..Default::default() } -} - -/// Helper function to convert a u8 to an AccountId32 -pub fn account_id(id: u8) -> AccountId { - sp_core::crypto::AccountId32::from([id; 32]) -} - -/// Check that the total-issuance matches the sum of all accounts' total balances. -pub fn ensure_ti_valid() { - let mut sum = 0; - - for acc in frame_system::Account::::iter_keys() { - if UseSystem::get() { - let data = frame_system::Pallet::::account(acc); - sum += data.data.total(); - } else { - let data = crate::Account::::get(acc); - sum += data.total(); - } - } - - assert_eq!(TotalIssuance::::get(), sum, "Total Issuance wrong"); -} - -#[test] -fn weights_sane() { - let info = crate::Call::::transfer_allow_death { dest: account_id(10), value: 4 } - .get_dispatch_info(); - assert_eq!(<() as crate::WeightInfo>::transfer_allow_death(), info.call_weight); - - let info = - crate::Call::::force_unreserve { who: account_id(10), amount: 4 }.get_dispatch_info(); - assert_eq!(<() as crate::WeightInfo>::force_unreserve(), info.call_weight); -} - -#[test] -fn check_whitelist() { - let whitelist: BTreeSet = AllPalletsWithSystem::whitelisted_storage_keys() - .iter() - .map(|s| HexDisplay::from(&s.key).to_string()) - .collect(); - // Inactive Issuance - assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f1ccde6872881f893a21de93dfe970cd5")); - // Total Issuance - assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80")); -} diff --git a/pallets/balances/src/tests/reentrancy_tests.rs b/pallets/balances/src/tests/reentrancy_tests.rs deleted file mode 100644 index 767f3ddf..00000000 --- a/pallets/balances/src/tests/reentrancy_tests.rs +++ /dev/null @@ -1,212 +0,0 @@ -// This file is part of Substrate. - -// 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 regarding the reentrancy functionality. - -use super::*; -use frame_support::traits::tokens::{ - Fortitude::Force, - Precision::BestEffort, - Preservation::{Expendable, Protect}, -}; -use fungible::Balanced; - -#[test] -fn transfer_dust_removal_tst1_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .dust_trap(1) - .build_and_execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 1000)); - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(2), 500)); - - // In this transaction, account 2 free balance - // drops below existential balance - // and dust balance is removed from account 2 - assert_ok!(Balances::transfer_allow_death( - RawOrigin::Signed(account_id(2)).into(), - account_id(3), - 450 - )); - - // As expected dust balance is removed. - assert_eq!(Balances::free_balance(account_id(2)), 0); - - // As expected beneficiary account 3 - // received the transferred fund. - assert_eq!(Balances::free_balance(account_id(3)), 450); - - // Dust balance is deposited to account 1 - // during the process of dust removal. - assert_eq!(Balances::free_balance(account_id(1)), 1050); - - // Verify the events - assert_eq!(System::events().len(), 15); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: account_id(2), - to: account_id(3), - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: account_id(2), - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: account_id(1), - amount: 50, - })); - }); -} - -#[test] -fn transfer_dust_removal_tst2_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .dust_trap(1) - .build_and_execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 1000)); - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(2), 500)); - - // In this transaction, account 2 free balance - // drops below existential balance - // and dust balance is removed from account 2 - assert_ok!(Balances::transfer_allow_death( - RawOrigin::Signed(account_id(2)).into(), - account_id(1), - 450 - )); - - // As expected dust balance is removed. - assert_eq!(Balances::free_balance(account_id(2)), 0); - - // Dust balance is deposited to account 1 - assert_eq!(Balances::free_balance(account_id(1)), 1000 + 450 + 50); - // Verify the events - assert_eq!(System::events().len(), 13); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: account_id(2), - to: account_id(1), - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: account_id(2), - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: account_id(1), - amount: 50, - })); - }); -} - -#[test] -fn repatriating_reserved_balance_dust_removal_should_work() { - ExtBuilder::default() - .existential_deposit(100) - .dust_trap(1) - .build_and_execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 1000)); - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(2), 500)); - - // Reserve a value on account 2, - // Such that free balance is lower than - // Existential deposit. - assert_ok!(Balances::transfer_allow_death( - RuntimeOrigin::signed(account_id(2)), - account_id(1), - 450 - )); - - // Since free balance of account 2 is lower than - // existential deposit, dust amount is - // removed from the account 2 - assert_eq!(Balances::reserved_balance(account_id(2)), 0); - assert_eq!(Balances::free_balance(account_id(2)), 0); - - // account 1 is credited with reserved amount - // together with dust balance during dust - // removal. - assert_eq!(Balances::reserved_balance(account_id(1)), 0); - assert_eq!(Balances::free_balance(account_id(1)), 1500); - - // Verify the events - assert_eq!(System::events().len(), 13); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: account_id(2), - to: account_id(1), - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: account_id(2), - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: account_id(1), - amount: 50, - })); - }); -} - -#[test] -fn emit_events_with_no_existential_deposit_suicide_with_dust() { - ExtBuilder::default().existential_deposit(2).build_and_execute_with(|| { - assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), account_id(1), 100)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::NewAccount { account: account_id(1) }), - RuntimeEvent::Balances(crate::Event::Endowed { - account: account_id(1), - free_balance: 100 - }), - RuntimeEvent::Balances(crate::Event::Issued { amount: 100 }), - RuntimeEvent::Balances(crate::Event::BalanceSet { who: account_id(1), free: 100 }), - ] - ); - - let res = Balances::withdraw(&account_id(1), 98, BestEffort, Protect, Force); - assert_eq!(res.unwrap().peek(), 98); - - // no events - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Withdraw { who: account_id(1), amount: 98 })] - ); - - let res = Balances::withdraw(&account_id(1), 1, BestEffort, Expendable, Force); - assert_eq!(res.unwrap().peek(), 1); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: account_id(1) }), - RuntimeEvent::Balances(crate::Event::DustLost { - account: account_id(1), - amount: 1 - }), - RuntimeEvent::Balances(crate::Event::Withdraw { who: account_id(1), amount: 1 }), - ] - ); - }); -} diff --git a/pallets/balances/src/tests/transfer_counter_tests.rs b/pallets/balances/src/tests/transfer_counter_tests.rs deleted file mode 100644 index 92cb360e..00000000 --- a/pallets/balances/src/tests/transfer_counter_tests.rs +++ /dev/null @@ -1,336 +0,0 @@ -// This file is part of Substrate. - -// 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 the global transfer counter functionality. - -use super::*; -use crate::{TransferCount, TransferProof}; -use sp_runtime::{ArithmeticError::Underflow, DispatchError::Arithmetic}; - -/// Alice account ID for more readable tests. -fn alice() -> AccountId { - account_id(1) -} -/// Bob account ID for more readable tests. -fn bob() -> AccountId { - account_id(2) -} -/// Charlie account ID for more readable tests. -fn charlie() -> AccountId { - account_id(3) -} - -#[test] -fn transfer_counter_starts_at_zero() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Transfer counter should start at 0 - assert_eq!(Balances::transfer_count(), 0); - }); -} - -#[test] -fn transfer_allow_death_increments_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // Perform a transfer - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), bob(), 5)); - - // Counter should increment to 1 - assert_eq!(Balances::transfer_count(), 1); - - // Perform another transfer - assert_ok!(Balances::transfer_allow_death(Some(bob()).into(), charlie(), 3)); - - // Counter should increment to 2 - assert_eq!(Balances::transfer_count(), 2); - }); -} - -#[test] -fn transfer_keep_alive_increments_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // Perform a transfer_keep_alive - assert_ok!(Balances::transfer_keep_alive(Some(alice()).into(), bob(), 5)); - - // Counter should increment to 1 - assert_eq!(Balances::transfer_count(), 1); - }); -} - -#[test] -fn force_transfer_increments_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // Perform a force transfer - assert_ok!(Balances::force_transfer(RuntimeOrigin::root(), alice(), bob(), 5)); - - // Counter should increment to 1 - assert_eq!(Balances::transfer_count(), 1); - }); -} - -#[test] -fn transfer_all_increments_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // Perform a transfer_all - assert_ok!(Balances::transfer_all(Some(alice()).into(), bob(), false)); - - // Counter should increment to 1 - assert_eq!(Balances::transfer_count(), 1); - }); -} - -#[test] -fn self_transfer_does_not_increment_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // Self transfer should not increment counter - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), alice(), 5)); - - // Counter should remain 0 since it's a self-transfer - assert_eq!(Balances::transfer_count(), 0); - }); -} - -#[test] -fn transfer_proof_storage_is_created() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Perform a transfer - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), bob(), 5)); - - // Check that transfer proof was stored with correct key - let key = (0u64, alice(), bob(), 5); - assert!(TransferProof::::contains_key(&key)); - }); -} - -#[test] -fn multiple_transfers_create_sequential_proofs() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // First transfer - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), bob(), 5)); - assert_eq!(Balances::transfer_count(), 1); - - // Check first proof exists - let key1 = (0u64, alice(), bob(), 5u128); - assert!(TransferProof::::contains_key(&key1)); - - // Second transfer - assert_ok!(Balances::transfer_allow_death(Some(bob()).into(), charlie(), 3)); - assert_eq!(Balances::transfer_count(), 2); - - // Check second proof exists - let key2 = (1u64, bob(), charlie(), 3u128); - assert!(TransferProof::::contains_key(&key2)); - - // Third transfer with different amount - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), charlie(), 1)); - assert_eq!(Balances::transfer_count(), 3); - - // Check third proof exists - let key3 = (2u64, alice(), charlie(), 1u128); - assert!(TransferProof::::contains_key(&key3)); - }); -} - -#[test] -fn failed_transfers_do_not_increment_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // Attempt transfer with insufficient funds - assert_noop!( - Balances::transfer_allow_death(Some(alice()).into(), bob(), 1000), - Arithmetic(Underflow) - ); - - // Counter should remain 0 since transfer failed - assert_eq!(Balances::transfer_count(), 0); - }); -} - -#[test] -fn transfer_proof_storage_key_generation() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - let transfer_count = 5u64; - let from = alice(); - let to = bob(); - let amount = 100u128; - - // Generate storage key - let key = Balances::transfer_proof_storage_key( - transfer_count, - from.clone(), - to.clone(), - amount, - ); - - // Key should not be empty - assert!(!key.is_empty()); - - // The same parameters should generate the same key - let key2 = Balances::transfer_proof_storage_key( - transfer_count, - from.clone(), - to.clone(), - amount, - ); - assert_eq!(key, key2); - - // Different parameters should generate different keys - let key3 = Balances::transfer_proof_storage_key(transfer_count + 1, from, to, amount); - assert_ne!(key, key3); - }); -} - -#[test] -fn counter_saturates_at_max_value() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Set counter to near maximum value (u64::MAX - 1) - let near_max = u64::MAX - 1; - TransferCount::::put(near_max); - - assert_eq!(Balances::transfer_count(), near_max); - - // Perform a transfer - should increment to u64::MAX - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), bob(), 5)); - assert_eq!(Balances::transfer_count(), u64::MAX); - - // Perform another transfer - should saturate at u64::MAX - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), charlie(), 3)); - assert_eq!(Balances::transfer_count(), u64::MAX); - }); -} - -#[test] -fn transfer_counter_persists_across_blocks() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Perform transfer in block 1 - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), bob(), 5)); - assert_eq!(Balances::transfer_count(), 1); - - // Move to block 2 - System::set_block_number(2); - - // Counter should persist - assert_eq!(Balances::transfer_count(), 1); - - // Perform another transfer in block 2 - assert_ok!(Balances::transfer_allow_death(Some(bob()).into(), charlie(), 3)); - assert_eq!(Balances::transfer_count(), 2); - }); -} - -#[test] -fn zero_value_transfers_increment_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // Perform a zero-value transfer - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), bob(), 0)); - - // Counter should increment even for zero-value transfers - assert_eq!(Balances::transfer_count(), 1); - - // Transfer proof should be created - let key = (0u64, alice(), bob(), 0u128); - assert!(TransferProof::::contains_key(&key)); - }); -} - -#[test] -fn different_transfer_types_all_increment_counter() { - ExtBuilder::default() - .existential_deposit(1) - .monied(true) - .build_and_execute_with(|| { - // Initial counter should be 0 - assert_eq!(Balances::transfer_count(), 0); - - // transfer_allow_death - assert_ok!(Balances::transfer_allow_death(Some(alice()).into(), bob(), 1)); - assert_eq!(Balances::transfer_count(), 1); - - // transfer_keep_alive - assert_ok!(Balances::transfer_keep_alive(Some(alice()).into(), charlie(), 1)); - assert_eq!(Balances::transfer_count(), 2); - - // force_transfer - assert_ok!(Balances::force_transfer(RuntimeOrigin::root(), bob(), charlie(), 1)); - assert_eq!(Balances::transfer_count(), 3); - - // transfer_all (transfer remaining balance) - let remaining = Balances::free_balance(alice()); - if remaining > 1 { - assert_ok!(Balances::transfer_all(Some(alice()).into(), bob(), false)); - assert_eq!(Balances::transfer_count(), 4); - } - }); -} diff --git a/pallets/balances/src/types.rs b/pallets/balances/src/types.rs deleted file mode 100644 index a7ccfd3d..00000000 --- a/pallets/balances/src/types.rs +++ /dev/null @@ -1,164 +0,0 @@ -// This file is part of Substrate. - -// 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. - -//! Types used in the pallet. - -use crate::{Config, CreditOf, Event, Pallet}; -use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; -use core::ops::BitOr; -use frame_support::traits::{Imbalance, LockIdentifier, OnUnbalanced, WithdrawReasons}; -use scale_info::TypeInfo; -use sp_runtime::{RuntimeDebug, Saturating}; - -/// Simplified reasons for withdrawing balance. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub enum Reasons { - /// Paying system transaction fees. - Fee = 0, - /// Any reason other than paying system transaction fees. - Misc = 1, - /// Any reason at all. - All = 2, -} - -impl From for Reasons { - fn from(r: WithdrawReasons) -> Reasons { - if r == WithdrawReasons::TRANSACTION_PAYMENT { - Reasons::Fee - } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { - Reasons::All - } else { - Reasons::Misc - } - } -} - -impl BitOr for Reasons { - type Output = Reasons; - fn bitor(self, other: Reasons) -> Reasons { - if self == other { - return self; - } - Reasons::All - } -} - -/// A single lock on a balance. There can be many of these on an account and they "overlap", so the -/// same balance is frozen by multiple locks. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct BalanceLock { - /// An identifier for this lock. Only one lock may be in existence for each identifier. - pub id: LockIdentifier, - /// The amount which the free balance may not drop below when this lock is in effect. - pub amount: Balance, - /// If true, then the lock remains in effect even for payment of transaction fees. - pub reasons: Reasons, -} - -/// Store named reserved balance. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct ReserveData { - /// The identifier for the named reserve. - pub id: ReserveIdentifier, - /// The amount of the named reserve. - pub amount: Balance, -} - -/// All balance information for an account. -#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct AccountData { - /// Non-reserved part of the balance which the account holder may be able to control. - /// - /// This is the only balance that matters in terms of most operations on tokens. - pub free: Balance, - /// Balance which is has active holds on it and may not be used at all. - /// - /// This is the sum of all individual holds together with any sums still under the (deprecated) - /// reserves API. - pub reserved: Balance, - /// The amount that `free + reserved` may not drop below when reducing the balance, except for - /// actions where the account owner cannot reasonably benefit from the balance reduction, such - /// as slashing. - pub frozen: Balance, - /// Extra information about this account. The MSB is a flag indicating whether the new ref- - /// counting logic is in place for this account. - pub flags: ExtraFlags, -} - -const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128; - -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct ExtraFlags(pub(crate) u128); -impl Default for ExtraFlags { - fn default() -> Self { - Self(IS_NEW_LOGIC) - } -} -impl ExtraFlags { - pub fn old_logic() -> Self { - Self(0) - } - pub fn set_new_logic(&mut self) { - self.0 |= IS_NEW_LOGIC - } - pub fn is_new_logic(&self) -> bool { - (self.0 & IS_NEW_LOGIC) == IS_NEW_LOGIC - } -} - -impl AccountData { - pub fn usable(&self) -> Balance { - self.free.saturating_sub(self.frozen) - } - - /// The total balance in this account including any that is reserved and ignoring any frozen. - pub fn total(&self) -> Balance { - self.free.saturating_add(self.reserved) - } -} - -pub struct DustCleaner, I: 'static = ()>( - pub(crate) Option<(T::AccountId, CreditOf)>, -); - -impl, I: 'static> Drop for DustCleaner { - fn drop(&mut self) { - if let Some((who, dust)) = self.0.take() { - Pallet::::deposit_event(Event::DustLost { account: who, amount: dust.peek() }); - T::DustRemoval::on_unbalanced(dust); - } - } -} - -/// Whether something should be interpreted as an increase or a decrease. -#[derive( - Encode, - Decode, - Clone, - PartialEq, - Eq, - RuntimeDebug, - MaxEncodedLen, - TypeInfo, - DecodeWithMemTracking, -)] -pub enum AdjustmentDirection { - /// Increase the amount. - Increase, - /// Decrease the amount. - Decrease, -} diff --git a/pallets/balances/src/weights.rs b/pallets/balances/src/weights.rs deleted file mode 100644 index 0c7a1354..00000000 --- a/pallets/balances/src/weights.rs +++ /dev/null @@ -1,300 +0,0 @@ -// This file is part of Substrate. - -// 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. - -//! Autogenerated weights for `pallet_balances` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2024-11-08, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runner-wiukf8gn-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` -//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` - -// Executed Command: -// ./target/production/substrate-node -// benchmark -// pallet -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_balances -// --no-storage-info -// --no-median-slopes -// --no-min-squares -// --extrinsic=* -// --wasm-execution=compiled -// --heap-pages=4096 -// --output=./substrate/frame/balances/src/weights.rs -// --header=./substrate/HEADER-APACHE2 -// --template=./substrate/.maintain/frame-weight-template.hbs - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; - -/// Weight functions needed for `pallet_balances`. -pub trait WeightInfo { - fn transfer_allow_death() -> Weight; - fn transfer_keep_alive() -> Weight; - fn force_set_balance_creating() -> Weight; - fn force_set_balance_killing() -> Weight; - fn force_transfer() -> Weight; - fn transfer_all() -> Weight; - fn force_unreserve() -> Weight; - fn upgrade_accounts(u: u32, ) -> Weight; - fn force_adjust_total_issuance() -> Weight; - fn burn_allow_death() -> Weight; - fn burn_keep_alive() -> Weight; -} - -/// Weights for `pallet_balances` using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_allow_death() -> Weight { - // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3593` - // Minimum execution time: 50_023_000 picoseconds. - Weight::from_parts(51_105_000, 3593) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3593` - // Minimum execution time: 39_923_000 picoseconds. - Weight::from_parts(40_655_000, 3593) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_set_balance_creating() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 15_062_000 picoseconds. - Weight::from_parts(15_772_000, 3593) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_set_balance_killing() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 21_797_000 picoseconds. - Weight::from_parts(22_287_000, 3593) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:2 w:2) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `155` - // Estimated: `6196` - // Minimum execution time: 51_425_000 picoseconds. - Weight::from_parts(52_600_000, 6196) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_all() -> Weight { - // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3593` - // Minimum execution time: 49_399_000 picoseconds. - Weight::from_parts(51_205_000, 3593) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_unreserve() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 18_119_000 picoseconds. - Weight::from_parts(18_749_000, 3593) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:999 w:999) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `u` is `[1, 1000]`. - fn upgrade_accounts(u: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + u * (135 ±0)` - // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 16_783_000 picoseconds. - Weight::from_parts(17_076_000, 990) - // Standard Error: 15_126 - .saturating_add(Weight::from_parts(14_834_157, 0).saturating_mul(u.into())) - .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) - .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) - } - fn force_adjust_total_issuance() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_048_000 picoseconds. - Weight::from_parts(6_346_000, 0) - } - fn burn_allow_death() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 30_215_000 picoseconds. - Weight::from_parts(30_848_000, 0) - } - fn burn_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 20_813_000 picoseconds. - Weight::from_parts(21_553_000, 0) - } -} - -// For backwards compatibility and tests. -impl WeightInfo for () { - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_allow_death() -> Weight { - // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3593` - // Minimum execution time: 50_023_000 picoseconds. - Weight::from_parts(51_105_000, 3593) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3593` - // Minimum execution time: 39_923_000 picoseconds. - Weight::from_parts(40_655_000, 3593) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_set_balance_creating() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 15_062_000 picoseconds. - Weight::from_parts(15_772_000, 3593) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_set_balance_killing() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 21_797_000 picoseconds. - Weight::from_parts(22_287_000, 3593) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:2 w:2) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_transfer() -> Weight { - // Proof Size summary in bytes: - // Measured: `155` - // Estimated: `6196` - // Minimum execution time: 51_425_000 picoseconds. - Weight::from_parts(52_600_000, 6196) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn transfer_all() -> Weight { - // Proof Size summary in bytes: - // Measured: `52` - // Estimated: `3593` - // Minimum execution time: 49_399_000 picoseconds. - Weight::from_parts(51_205_000, 3593) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:1 w:1) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - fn force_unreserve() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 18_119_000 picoseconds. - Weight::from_parts(18_749_000, 3593) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } - /// Storage: `System::Account` (r:999 w:999) - /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) - /// The range of component `u` is `[1, 1000]`. - fn upgrade_accounts(u: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `0 + u * (135 ±0)` - // Estimated: `990 + u * (2603 ±0)` - // Minimum execution time: 16_783_000 picoseconds. - Weight::from_parts(17_076_000, 990) - // Standard Error: 15_126 - .saturating_add(Weight::from_parts(14_834_157, 0).saturating_mul(u.into())) - .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(u.into()))) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(u.into()))) - .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) - } - fn force_adjust_total_issuance() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 6_048_000 picoseconds. - Weight::from_parts(6_346_000, 0) - } - fn burn_allow_death() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 30_215_000 picoseconds. - Weight::from_parts(30_848_000, 0) - } - fn burn_keep_alive() -> Weight { - // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 20_813_000 picoseconds. - Weight::from_parts(21_553_000, 0) - } -} diff --git a/pallets/mining-rewards/src/lib.rs b/pallets/mining-rewards/src/lib.rs index cd2a8a32..e135ff79 100644 --- a/pallets/mining-rewards/src/lib.rs +++ b/pallets/mining-rewards/src/lib.rs @@ -26,7 +26,7 @@ pub mod pallet { }, }; use frame_system::pallet_prelude::*; - use qp_wormhole::TransferProofs; + use qp_wormhole::TransferProofRecorder; use sp_consensus_pow::POW_ENGINE_ID; use sp_runtime::{ generic::DigestItem, @@ -48,9 +48,18 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; - /// Currency type that also stores zk proofs - type Currency: Mutate - + qp_wormhole::TransferProofs, Self::AccountId>; + /// Currency type for minting rewards + type Currency: Mutate; + + /// Asset ID type for the proof recorder + type AssetId: Default; + + /// Proof recorder for storing wormhole transfer proofs + type ProofRecorder: qp_wormhole::TransferProofRecorder< + Self::AccountId, + Self::AssetId, + BalanceOf, + >; /// The base block reward given to miners #[pallet::constant] @@ -165,7 +174,12 @@ pub mod pallet { Some(miner) => { let _ = T::Currency::mint_into(&miner, reward).defensive(); - T::Currency::store_transfer_proof(&mint_account, &miner, reward); + let _ = T::ProofRecorder::record_transfer_proof( + None, + mint_account.clone(), + miner.clone(), + reward, + ); Self::deposit_event(Event::MinerRewarded { miner: miner.clone(), reward }); @@ -180,7 +194,12 @@ pub mod pallet { let treasury = T::TreasuryPalletId::get().into_account_truncating(); let _ = T::Currency::mint_into(&treasury, reward).defensive(); - T::Currency::store_transfer_proof(&mint_account, &treasury, reward); + let _ = T::ProofRecorder::record_transfer_proof( + None, + mint_account.clone(), + treasury.clone(), + reward, + ); Self::deposit_event(Event::TreasuryRewarded { reward }); diff --git a/pallets/mining-rewards/src/mock.rs b/pallets/mining-rewards/src/mock.rs index 1b4529a3..175db2d2 100644 --- a/pallets/mining-rewards/src/mock.rs +++ b/pallets/mining-rewards/src/mock.rs @@ -68,6 +68,7 @@ impl frame_system::Config for Test { } impl pallet_balances::Config for Test { + type RuntimeEvent = RuntimeEvent; type RuntimeHoldReason = (); type RuntimeFreezeReason = (); type WeightInfo = (); @@ -88,8 +89,27 @@ parameter_types! { pub const MintingAccount: sp_core::crypto::AccountId32 = sp_core::crypto::AccountId32::new([99u8; 32]); } +// Mock proof recorder that does nothing +pub struct MockProofRecorder; +impl qp_wormhole::TransferProofRecorder + for MockProofRecorder +{ + type Error = (); + + fn record_transfer_proof( + _asset_id: Option, + _from: sp_core::crypto::AccountId32, + _to: sp_core::crypto::AccountId32, + _amount: u128, + ) -> Result<(), Self::Error> { + Ok(()) + } +} + impl pallet_mining_rewards::Config for Test { type Currency = Balances; + type AssetId = u32; + type ProofRecorder = MockProofRecorder; type WeightInfo = (); type MinerBlockReward = BlockReward; type TreasuryBlockReward = TreasuryBlockReward; @@ -116,6 +136,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_balances::GenesisConfig:: { balances: vec![(miner(), ExistentialDeposit::get()), (miner2(), ExistentialDeposit::get())], + dev_accounts: None, } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/wormhole/Cargo.toml b/pallets/wormhole/Cargo.toml index 78e624e7..f799c8af 100644 --- a/pallets/wormhole/Cargo.toml +++ b/pallets/wormhole/Cargo.toml @@ -31,6 +31,7 @@ sp-runtime.workspace = true [dev-dependencies] hex = { workspace = true, features = ["alloc"] } +pallet-assets = { workspace = true, features = ["std"] } qp-dilithium-crypto = { workspace = true, features = ["std"] } qp-plonky2 = { workspace = true, default-features = false } qp-poseidon-core.workspace = true diff --git a/pallets/wormhole/src/lib.rs b/pallets/wormhole/src/lib.rs index 86d8281a..8e953575 100644 --- a/pallets/wormhole/src/lib.rs +++ b/pallets/wormhole/src/lib.rs @@ -38,34 +38,59 @@ pub mod pallet { pallet_prelude::*, traits::{ fungible::{Mutate, Unbalanced}, + fungibles::{self, Inspect as FungiblesInspect}, + tokens::Preservation, Currency, ExistenceRequirement, WithdrawReasons, }, weights::WeightToFee, }; use frame_system::pallet_prelude::*; - use qp_wormhole::TransferProofs; use qp_wormhole_circuit::inputs::PublicCircuitInputs; use qp_wormhole_verifier::ProofWithPublicInputs; use qp_zk_circuits_common::circuit::{C, D, F}; use sp_runtime::{ - traits::{Saturating, Zero}, + traits::{Saturating, StaticLookup, Zero}, Perbill, }; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; + pub type AssetIdOf = <::Assets as fungibles::Inspect< + ::AccountId, + >>::AssetId; + pub type AssetBalanceOf = <::Assets as fungibles::Inspect< + ::AccountId, + >>::Balance; + pub type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[pallet::pallet] pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { - /// Currency type used for minting tokens and handling wormhole transfers + pub trait Config: frame_system::Config + where + AssetIdOf: Default, + BalanceOf: Default, + AssetBalanceOf: Into> + From>, + { + /// Currency type used for native token transfers and minting type Currency: Mutate> - + TransferProofs, Self::AccountId> + Unbalanced + Currency; + /// Assets type used for managing fungible assets + type Assets: fungibles::Inspect + + fungibles::Mutate + + fungibles::Create; + + /// Transfer count type used in storage + type TransferCount: Parameter + + MaxEncodedLen + + Default + + Saturating + + Copy + + sp_runtime::traits::One; + /// Account ID used as the "from" account when creating transfer proofs for minted tokens #[pallet::constant] type MintingAccount: Get; @@ -81,10 +106,41 @@ pub mod pallet { pub(super) type UsedNullifiers = StorageMap<_, Blake2_128Concat, [u8; 32], bool, ValueQuery>; + /// Transfer proofs for wormhole transfers (both native and assets) + #[pallet::storage] + #[pallet::getter(fn transfer_proof)] + pub type TransferProof = StorageMap< + _, + Blake2_128Concat, + (AssetIdOf, T::TransferCount, T::AccountId, T::AccountId, BalanceOf), /* (asset_id, tx_count, from, to, amount) */ + (), + OptionQuery, + >; + + /// Transfer count for all wormhole transfers + #[pallet::storage] + #[pallet::getter(fn transfer_count)] + pub type TransferCount = StorageValue<_, T::TransferCount, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - ProofVerified { exit_amount: BalanceOf }, + ProofVerified { + exit_amount: BalanceOf, + }, + NativeTransferred { + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + transfer_count: T::TransferCount, + }, + AssetTransferred { + asset_id: AssetIdOf, + from: T::AccountId, + to: T::AccountId, + amount: AssetBalanceOf, + transfer_count: T::TransferCount, + }, } #[pallet::error] @@ -99,6 +155,8 @@ pub mod pallet { StorageRootMismatch, BlockNotFound, InvalidBlockNumber, + AssetNotFound, + SelfTransfer, } #[pallet::call] @@ -196,12 +254,114 @@ pub mod pallet { // Create a transfer proof for the minted tokens let mint_account = T::MintingAccount::get(); - T::Currency::store_transfer_proof(&mint_account, &exit_account, exit_balance); + Self::record_transfer( + AssetIdOf::::default(), + mint_account, + exit_account, + exit_balance, + )?; // Emit event Self::deposit_event(Event::ProofVerified { exit_amount: exit_balance }); Ok(()) } + + /// Transfer native tokens and store proof for wormhole + #[pallet::call_index(1)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 2))] + pub fn transfer_native( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] amount: BalanceOf, + ) -> DispatchResult { + let source = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + + // Prevent self-transfers + ensure!(source != dest, Error::::SelfTransfer); + + // Perform the transfer + >::transfer(&source, &dest, amount, Preservation::Expendable)?; + + // Store proof with asset_id = Default (0 for native) + Self::record_transfer(AssetIdOf::::default(), source, dest, amount)?; + + Ok(()) + } + + /// Transfer asset tokens and store proof for wormhole + #[pallet::call_index(2)] + #[pallet::weight(T::DbWeight::get().reads_writes(2, 2))] + pub fn transfer_asset( + origin: OriginFor, + asset_id: AssetIdOf, + dest: AccountIdLookupOf, + #[pallet::compact] amount: AssetBalanceOf, + ) -> DispatchResult { + let source = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + + // Prevent self-transfers + ensure!(source != dest, Error::::SelfTransfer); + + // Check if asset exists + ensure!( + >::asset_exists(asset_id.clone()), + Error::::AssetNotFound + ); + + // Perform the transfer + >::transfer( + asset_id.clone(), + &source, + &dest, + amount, + Preservation::Expendable, + )?; + + // Store proof + Self::record_transfer(asset_id, source, dest, amount.into())?; + + Ok(()) + } + } + + // Helper functions for recording transfer proofs + impl Pallet { + /// Record a transfer proof + /// This should be called by transaction extensions or other runtime components + pub fn record_transfer( + asset_id: AssetIdOf, + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + ) -> DispatchResult { + let current_count = TransferCount::::get(); + TransferProof::::insert( + (asset_id, current_count, from.clone(), to.clone(), amount), + (), + ); + TransferCount::::put(current_count.saturating_add(T::TransferCount::one())); + + Ok(()) + } + } + + // Implement the TransferProofRecorder trait for other pallets to use + impl qp_wormhole::TransferProofRecorder, BalanceOf> + for Pallet + { + type Error = DispatchError; + + fn record_transfer_proof( + asset_id: Option>, + from: T::AccountId, + to: T::AccountId, + amount: BalanceOf, + ) -> Result<(), Self::Error> { + let asset_id_value = asset_id.unwrap_or_default(); + Self::record_transfer(asset_id_value, from, to, amount) + } } } diff --git a/pallets/wormhole/src/mock.rs b/pallets/wormhole/src/mock.rs index c902099c..04c7e4c1 100644 --- a/pallets/wormhole/src/mock.rs +++ b/pallets/wormhole/src/mock.rs @@ -1,7 +1,7 @@ use crate as pallet_wormhole; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU32, Everything}, + traits::{ConstU128, ConstU32, Everything}, weights::IdentityFee, }; use frame_system::mocking::MockUncheckedExtrinsic; @@ -14,6 +14,7 @@ construct_runtime!( pub enum Test { System: frame_system, Balances: pallet_balances, + Assets: pallet_assets, Wormhole: pallet_wormhole, } ); @@ -91,6 +92,32 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type MaxFreezes = (); type DoneSlashHandler = (); + type RuntimeEvent = RuntimeEvent; +} + +// --- PALLET ASSETS --- + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = + frame_support::traits::AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<1>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type RemoveItemsLimit = ConstU32<1000>; + type CallbackHandle = (); + type Holder = (); } // --- PALLET WORMHOLE --- @@ -106,19 +133,13 @@ impl pallet_wormhole::Config for Test { type WeightInfo = crate::weights::SubstrateWeight; type WeightToFee = IdentityFee; type Currency = Balances; + type Assets = Assets; + type TransferCount = u64; type MintingAccount = MintingAccount; } // Helper function to build a genesis configuration pub fn new_test_ext() -> sp_state_machine::TestExternalities { - let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - - let endowment = 1e18 as Balance; - pallet_balances::GenesisConfig:: { - balances: vec![(account_id(1), endowment), (account_id(2), endowment)], - } - .assimilate_storage(&mut t) - .unwrap(); - + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); t.into() } diff --git a/pallets/wormhole/src/tests.rs b/pallets/wormhole/src/tests.rs index ae2a7de1..34e6edda 100644 --- a/pallets/wormhole/src/tests.rs +++ b/pallets/wormhole/src/tests.rs @@ -1,262 +1,142 @@ #[cfg(test)] mod wormhole_tests { - use crate::{get_wormhole_verifier, mock::*}; - use codec::Encode; + use crate::mock::*; use frame_support::{ assert_ok, traits::fungible::{Inspect, Mutate}, }; - use plonky2::plonk::circuit_data::CircuitConfig; - use qp_poseidon::PoseidonHasher; - use qp_wormhole_circuit::{ - inputs::{CircuitInputs, PrivateCircuitInputs, PublicCircuitInputs}, - nullifier::Nullifier, - }; - use qp_wormhole_prover::WormholeProver; - use qp_wormhole_verifier::ProofWithPublicInputs; - use qp_zk_circuits_common::{ - circuit::{C, F}, - storage_proof::prepare_proof_for_circuit, - utils::{digest_felts_to_bytes, BytesDigest, Digest}, - }; - use sp_runtime::{traits::Header, DigestItem}; - - // Helper function to generate proof and inputs for - fn generate_proof(inputs: CircuitInputs) -> ProofWithPublicInputs { - let config = CircuitConfig::standard_recursion_config(); - let prover = WormholeProver::new(config); - let prover_next = prover.commit(&inputs).expect("proof failed"); - let proof = prover_next.prove().expect("valid proof"); - proof - } #[test] - fn test_wormhole_transfer_proof_generation() { - // Setup accounts - let alice = account_id(1); - let secret: BytesDigest = [1u8; 32].try_into().expect("valid secret"); - let unspendable_account = - qp_wormhole_circuit::unspendable_account::UnspendableAccount::from_secret(secret) - .account_id; - let unspendable_account_bytes_digest = digest_felts_to_bytes(unspendable_account); - let unspendable_account_bytes: [u8; 32] = unspendable_account_bytes_digest - .as_ref() - .try_into() - .expect("BytesDigest is always 32 bytes"); - let unspendable_account_id = AccountId::new(unspendable_account_bytes); - let exit_account_id = AccountId::new([42u8; 32]); - let funding_amount = 1_000_000_000_001u128; - - let mut ext = new_test_ext(); - - // Execute the transfer and get the header - let (storage_key, state_root, leaf_hash, event_transfer_count, header) = - ext.execute_with(|| { - System::set_block_number(1); - - // Add dummy digest items to match expected format - let pre_runtime_data = vec![ - 233, 182, 183, 107, 158, 1, 115, 19, 219, 126, 253, 86, 30, 208, 176, 70, 21, - 45, 180, 229, 9, 62, 91, 4, 6, 53, 245, 52, 48, 38, 123, 225, - ]; - let seal_data = vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 77, 142, - ]; - - System::deposit_log(DigestItem::PreRuntime(*b"pow_", pre_runtime_data)); - System::deposit_log(DigestItem::Seal(*b"pow_", seal_data)); - - assert_ok!(Balances::mint_into(&unspendable_account_id, funding_amount)); - assert_ok!(Balances::transfer_keep_alive( - frame_system::RawOrigin::Signed(alice.clone()).into(), - unspendable_account_id.clone(), - funding_amount, - )); - - let transfer_count = pallet_balances::TransferCount::::get(); - let event_transfer_count = transfer_count - 1; - - let leaf_hash = PoseidonHasher::hash_storage::( - &( - event_transfer_count, - alice.clone(), - unspendable_account_id.clone(), - funding_amount, - ) - .encode(), - ); - - let proof_address = pallet_balances::TransferProof::::hashed_key_for(&( - event_transfer_count, - alice.clone(), - unspendable_account_id.clone(), - funding_amount, - )); - let mut storage_key = proof_address; - storage_key.extend_from_slice(&leaf_hash); - - let header = System::finalize(); - let state_root = *header.state_root(); - - (storage_key, state_root, leaf_hash, event_transfer_count, header) - }); - - // Generate a storage proof for the specific storage key - use sp_state_machine::prove_read; - let proof = prove_read(ext.as_backend(), &[&storage_key]) - .expect("failed to generate storage proof"); - - let proof_nodes_vec: Vec> = proof.iter_nodes().map(|n| n.to_vec()).collect(); - - // Prepare the storage proof for the circuit - let processed_storage_proof = - prepare_proof_for_circuit(proof_nodes_vec, hex::encode(&state_root), leaf_hash) - .expect("failed to prepare proof for circuit"); - - // Build the header components - let parent_hash = *header.parent_hash(); - let extrinsics_root = *header.extrinsics_root(); - let digest = header.digest().encode(); - let digest_array: [u8; 110] = digest.try_into().expect("digest should be 110 bytes"); - let block_number: u32 = (*header.number()).try_into().expect("block number fits in u32"); + fn transfer_native_works() { + new_test_ext().execute_with(|| { + let alice = account_id(1); + let bob = account_id(2); + let amount = 1000u128; - // Compute block hash - let block_hash = header.hash(); + assert_ok!(Balances::mint_into(&alice, amount * 2)); - // Assemble circuit inputs - let circuit_inputs = CircuitInputs { - private: PrivateCircuitInputs { - secret, - transfer_count: event_transfer_count, - funding_account: BytesDigest::try_from(alice.as_ref() as &[u8]) - .expect("account is 32 bytes"), - storage_proof: processed_storage_proof, - unspendable_account: Digest::from(unspendable_account).into(), - state_root: BytesDigest::try_from(state_root.as_ref()) - .expect("state root is 32 bytes"), - extrinsics_root: BytesDigest::try_from(extrinsics_root.as_ref()) - .expect("extrinsics root is 32 bytes"), - digest: digest_array, - }, - public: PublicCircuitInputs { - funding_amount, - nullifier: Nullifier::from_preimage(secret, event_transfer_count).hash.into(), - exit_account: BytesDigest::try_from(exit_account_id.as_ref() as &[u8]) - .expect("account is 32 bytes"), - block_hash: BytesDigest::try_from(block_hash.as_ref()) - .expect("block hash is 32 bytes"), - parent_hash: BytesDigest::try_from(parent_hash.as_ref()) - .expect("parent hash is 32 bytes"), - block_number, - }, - }; + let count_before = Wormhole::transfer_count(); + assert_ok!(Wormhole::transfer_native( + frame_system::RawOrigin::Signed(alice.clone()).into(), + bob.clone(), + amount, + )); - // Generate the ZK proof - let proof = generate_proof(circuit_inputs); + assert_eq!(Balances::balance(&alice), amount); + assert_eq!(Balances::balance(&bob), amount); + assert_eq!(Wormhole::transfer_count(), count_before + 1); + assert!(Wormhole::transfer_proof((0u32, count_before, alice, bob, amount)).is_some()); + }); + } - // Verify the proof can be parsed - let public_inputs = - PublicCircuitInputs::try_from(&proof).expect("failed to parse public inputs"); + #[test] + fn transfer_native_fails_on_self_transfer() { + new_test_ext().execute_with(|| { + let alice = account_id(1); + let amount = 1000u128; - // Verify that the public inputs match what we expect - assert_eq!(public_inputs.funding_amount, funding_amount); - assert_eq!( - public_inputs.exit_account, - BytesDigest::try_from(exit_account_id.as_ref() as &[u8]).unwrap() - ); + assert_ok!(Balances::mint_into(&alice, amount)); - // Verify the proof using the verifier - let verifier = get_wormhole_verifier().expect("verifier should be available"); - verifier.verify(proof.clone()).expect("proof should verify"); + let result = Wormhole::transfer_native( + frame_system::RawOrigin::Signed(alice.clone()).into(), + alice.clone(), + amount, + ); - // Serialize the proof to bytes for extrinsic testing - let proof_bytes = proof.to_bytes(); + assert!(result.is_err()); + }); + } - // Now test the extrinsic in a new environment + #[test] + fn transfer_asset_works() { new_test_ext().execute_with(|| { - // Set up the blockchain state to have block 1 - System::set_block_number(1); + let alice = account_id(1); + let bob = account_id(2); + let asset_id = 1u32; + let amount = 1000u128; - // Add the same digest items - let pre_runtime_data = vec![ - 233, 182, 183, 107, 158, 1, 115, 19, 219, 126, 253, 86, 30, 208, 176, 70, 21, 45, - 180, 229, 9, 62, 91, 4, 6, 53, 245, 52, 48, 38, 123, 225, - ]; - let seal_data = vec![ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 30, 77, 142, - ]; + assert_ok!(Balances::mint_into(&alice, 1000)); + assert_ok!(Balances::mint_into(&bob, 1000)); - System::deposit_log(DigestItem::PreRuntime(*b"pow_", pre_runtime_data)); - System::deposit_log(DigestItem::Seal(*b"pow_", seal_data)); - - // Execute the same transfer to recreate the exact state - assert_ok!(Balances::mint_into(&unspendable_account_id, funding_amount)); - assert_ok!(Balances::transfer_keep_alive( + assert_ok!(Assets::create( frame_system::RawOrigin::Signed(alice.clone()).into(), - unspendable_account_id.clone(), - funding_amount, + asset_id.into(), + alice.clone(), + 1, )); - - // Finalize the block to get the same header and store the block hash - let block_1_header = System::finalize(); - - // Initialize block 2 to store block 1's hash - System::reset_events(); - System::initialize(&2, &block_1_header.hash(), block_1_header.digest()); - - // Check exit account balance before verification - let balance_before = Balances::balance(&exit_account_id); - assert_eq!(balance_before, 0); - - // Call the verify_wormhole_proof extrinsic - assert_ok!(Wormhole::verify_wormhole_proof( - frame_system::RawOrigin::None.into(), - proof_bytes.clone() + assert_ok!(Assets::mint( + frame_system::RawOrigin::Signed(alice.clone()).into(), + asset_id.into(), + alice.clone(), + amount * 2, )); - // Check that the exit account received the funds (minus fees) - let balance_after = Balances::balance(&exit_account_id); + let count_before = Wormhole::transfer_count(); + assert_ok!(Wormhole::transfer_asset( + frame_system::RawOrigin::Signed(alice.clone()).into(), + asset_id, + bob.clone(), + amount, + )); - // The balance should be funding_amount minus fees - // Weight fee + 0.1% volume fee - assert!(balance_after > 0, "Exit account should have received funds"); + assert_eq!(Assets::balance(asset_id, &alice), amount); + assert_eq!(Assets::balance(asset_id, &bob), amount); + assert_eq!(Wormhole::transfer_count(), count_before + 1); assert!( - balance_after < funding_amount, - "Exit account balance should be less than funding amount due to fees" + Wormhole::transfer_proof((asset_id, count_before, alice, bob, amount)).is_some() ); }); + } - // Test that proof fails when state doesn't match + #[test] + fn transfer_asset_fails_on_nonexistent_asset() { new_test_ext().execute_with(|| { - // Set up block 1 but DON'T recreate the exact same state - System::set_block_number(1); + let alice = account_id(1); + let bob = account_id(2); + let asset_id = 999u32; + let amount = 1000u128; - // Add different digest items with same 110-byte format but different content - let pre_runtime_data = vec![1u8; 32]; // Different data - let seal_data = vec![2u8; 64]; // Different data + let result = Wormhole::transfer_asset( + frame_system::RawOrigin::Signed(alice.clone()).into(), + asset_id, + bob.clone(), + amount, + ); - System::deposit_log(DigestItem::PreRuntime(*b"pow_", pre_runtime_data)); - System::deposit_log(DigestItem::Seal(*b"pow_", seal_data)); + assert!(result.is_err()); + }); + } - // Finalize block 1 with different state - let different_header = System::finalize(); + #[test] + fn transfer_asset_fails_on_self_transfer() { + new_test_ext().execute_with(|| { + let alice = account_id(1); + let asset_id = 1u32; + let amount = 1000u128; - // Initialize block 2 - System::reset_events(); - System::initialize(&2, &different_header.hash(), different_header.digest()); + assert_ok!(Balances::mint_into(&alice, 1000)); - // Try to use the proof with the original header (which has different block hash) - let result = Wormhole::verify_wormhole_proof( - frame_system::RawOrigin::None.into(), - proof_bytes.clone(), + assert_ok!(Assets::create( + frame_system::RawOrigin::Signed(alice.clone()).into(), + asset_id.into(), + alice.clone(), + 1, + )); + assert_ok!(Assets::mint( + frame_system::RawOrigin::Signed(alice.clone()).into(), + asset_id.into(), + alice.clone(), + amount, + )); + + let result = Wormhole::transfer_asset( + frame_system::RawOrigin::Signed(alice.clone()).into(), + asset_id, + alice.clone(), + amount, ); - // This should fail because the block hash in the proof doesn't match - assert!(result.is_err(), "Proof verification should fail with mismatched state"); + assert!(result.is_err()); }); } } diff --git a/primitives/wormhole/src/lib.rs b/primitives/wormhole/src/lib.rs index ac25ce16..8bc383eb 100644 --- a/primitives/wormhole/src/lib.rs +++ b/primitives/wormhole/src/lib.rs @@ -3,26 +3,19 @@ extern crate alloc; -use alloc::vec::Vec; +/// Trait for recording transfer proofs in the wormhole pallet. +/// Other pallets can use this to record proofs when they mint/transfer tokens. +pub trait TransferProofRecorder { + /// Error type for proof recording failures + type Error; -/// Trait for managing wormhole transfer proofs. -pub trait TransferProofs { - /// Get transfer proof, if any - fn transfer_proof_exists( - count: TxCount, - from: &AccountId, - to: &AccountId, - value: Balance, - ) -> bool; - - /// Get transfer proof key - fn transfer_proof_key( - count: TxCount, + /// Record a transfer proof for native or asset tokens + /// - `None` for native tokens (asset_id = 0) + /// - `Some(asset_id)` for specific assets + fn record_transfer_proof( + asset_id: Option, from: AccountId, to: AccountId, - value: Balance, - ) -> Vec; - - /// Store transfer proofs for a given wormhole transfer. - fn store_transfer_proof(from: &AccountId, to: &AccountId, value: Balance); + amount: Balance, + ) -> Result<(), Self::Error>; } diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 078a04d8..a10e9133 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -50,6 +50,7 @@ qp-dilithium-crypto.workspace = true qp-header = { workspace = true, features = ["serde"] } qp-poseidon = { workspace = true, features = ["serde"] } qp-scheduler.workspace = true +qp-wormhole.workspace = true qp-wormhole-circuit = { workspace = true, default-features = false } qp-wormhole-verifier = { workspace = true, default-features = false } qp-zk-circuits-common = { workspace = true, default-features = false } @@ -122,6 +123,7 @@ std = [ "qp-scheduler/std", "qp-wormhole-circuit/std", "qp-wormhole-verifier/std", + "qp-wormhole/std", "qp-zk-circuits-common/std", "scale-info/std", "serde_json/std", diff --git a/runtime/src/configs/mod.rs b/runtime/src/configs/mod.rs index 693319cf..c50898d8 100644 --- a/runtime/src/configs/mod.rs +++ b/runtime/src/configs/mod.rs @@ -63,10 +63,10 @@ use sp_version::RuntimeVersion; // Local module imports use super::{ - AccountId, Balance, Balances, Block, BlockNumber, Hash, Nonce, OriginCaller, PalletInfo, - Preimage, Referenda, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, - RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Scheduler, System, Timestamp, Vesting, DAYS, - EXISTENTIAL_DEPOSIT, MICRO_UNIT, TARGET_BLOCK_TIME_MS, UNIT, VERSION, + AccountId, Assets, Balance, Balances, Block, BlockNumber, Hash, Nonce, OriginCaller, + PalletInfo, Preimage, Referenda, Runtime, RuntimeCall, RuntimeEvent, RuntimeFreezeReason, + RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Scheduler, System, Timestamp, Vesting, Wormhole, + DAYS, EXISTENTIAL_DEPOSIT, MICRO_UNIT, TARGET_BLOCK_TIME_MS, UNIT, VERSION, }; use sp_core::U512; @@ -130,6 +130,8 @@ parameter_types! { impl pallet_mining_rewards::Config for Runtime { type Currency = Balances; + type AssetId = AssetId; + type ProofRecorder = Wormhole; type WeightInfo = pallet_mining_rewards::weights::SubstrateWeight; type MinerBlockReward = ConstU128<{ 10 * UNIT }>; // 10 tokens type TreasuryBlockReward = ConstU128<0>; // 0 tokens @@ -181,6 +183,7 @@ parameter_types! { } impl pallet_balances::Config for Runtime { + type RuntimeEvent = RuntimeEvent; type RuntimeHoldReason = RuntimeHoldReason; type RuntimeFreezeReason = RuntimeFreezeReason; type WeightInfo = pallet_balances::weights::SubstrateWeight; @@ -630,5 +633,7 @@ impl pallet_wormhole::Config for Runtime { type MintingAccount = WormholeMintingAccount; type WeightInfo = (); type Currency = Balances; + type Assets = Assets; + type TransferCount = u64; type WeightToFee = IdentityFee; } diff --git a/runtime/src/genesis_config_presets.rs b/runtime/src/genesis_config_presets.rs index 0fdba5ca..6f709daa 100644 --- a/runtime/src/genesis_config_presets.rs +++ b/runtime/src/genesis_config_presets.rs @@ -16,14 +16,15 @@ // limitations under the License. use crate::{ - configs::TreasuryPalletId, AccountId, BalancesConfig, RuntimeGenesisConfig, SudoConfig, UNIT, + configs::TreasuryPalletId, AccountId, AssetsConfig, BalancesConfig, RuntimeGenesisConfig, + SudoConfig, EXISTENTIAL_DEPOSIT, UNIT, }; use alloc::{vec, vec::Vec}; use qp_dilithium_crypto::pair::{crystal_alice, crystal_charlie, dilithium_bob}; use serde_json::Value; use sp_core::crypto::Ss58Codec; use sp_genesis_builder::{self, PresetId}; -use sp_runtime::traits::{AccountIdConversion, IdentifyAccount}; +use sp_runtime::traits::{AccountIdConversion, IdentifyAccount, Zero}; /// Identifier for the heisenberg runtime preset. pub const HEISENBERG_RUNTIME_PRESET: &str = "heisenberg"; @@ -59,8 +60,16 @@ fn genesis_template(endowed_accounts: Vec, root: AccountId) -> Value balances.push((treasury_account, ONE_BILLION * UNIT)); let config = RuntimeGenesisConfig { - balances: BalancesConfig { balances }, + balances: BalancesConfig { balances, dev_accounts: None }, sudo: SudoConfig { key: Some(root.clone()) }, + assets: AssetsConfig { + // We need to initialize and reserve the first asset id for the native token transfers + // with wormhole. + assets: vec![(Zero::zero(), root.clone(), false, EXISTENTIAL_DEPOSIT)], /* (asset_id, + * owner, is_sufficient, + * min_balance) */ + ..Default::default() + }, ..Default::default() }; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 2dbbb1bf..b486a5ed 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -168,6 +168,7 @@ pub type TxExtension = ( pallet_transaction_payment::ChargeTransactionPayment, frame_metadata_hash_extension::CheckMetadataHash, transaction_extensions::ReversibleTransactionExtension, + transaction_extensions::WormholeProofRecorderExtension, ); /// Unchecked extrinsic type as expected by this runtime. diff --git a/runtime/src/transaction_extensions.rs b/runtime/src/transaction_extensions.rs index 5afed1e6..6216b96d 100644 --- a/runtime/src/transaction_extensions.rs +++ b/runtime/src/transaction_extensions.rs @@ -2,12 +2,17 @@ use crate::*; use codec::{Decode, DecodeWithMemTracking, Encode}; use core::marker::PhantomData; -use frame_support::pallet_prelude::{InvalidTransaction, ValidTransaction}; - +use frame_support::pallet_prelude::{ + InvalidTransaction, TransactionValidityError, ValidTransaction, +}; use frame_system::ensure_signed; +use qp_wormhole::TransferProofRecorder; use scale_info::TypeInfo; use sp_core::Get; -use sp_runtime::{traits::TransactionExtension, Weight}; +use sp_runtime::{ + traits::{DispatchInfoOf, PostDispatchInfoOf, StaticLookup, TransactionExtension}, + DispatchResult, Weight, +}; /// Transaction extension for reversible accounts /// @@ -45,7 +50,7 @@ impl _call: &RuntimeCall, _info: &sp_runtime::traits::DispatchInfoOf, _len: usize, - ) -> Result { + ) -> Result { Ok(()) } @@ -59,11 +64,8 @@ impl _inherited_implication: &impl sp_runtime::traits::Implication, _source: frame_support::pallet_prelude::TransactionSource, ) -> sp_runtime::traits::ValidateResult { - let who = ensure_signed(origin.clone()).map_err(|_| { - frame_support::pallet_prelude::TransactionValidityError::Invalid( - InvalidTransaction::BadSigner, - ) - })?; + let who = ensure_signed(origin.clone()) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?; if ReversibleTransfers::is_high_security(&who).is_some() { // High-security accounts can only call schedule_transfer and cancel @@ -80,9 +82,7 @@ impl return Ok((ValidTransaction::default(), (), origin)); }, _ => { - return Err(frame_support::pallet_prelude::TransactionValidityError::Invalid( - InvalidTransaction::Custom(1), - )); + return Err(TransactionValidityError::Invalid(InvalidTransaction::Custom(1))); }, } } @@ -91,6 +91,165 @@ impl } } +/// Details of a transfer to be recorded +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TransferDetails { + from: AccountId, + to: AccountId, + amount: Balance, + asset_id: AssetId, +} + +/// Transaction extension that records transfer proofs in the wormhole pallet +/// +/// This extension: +/// - Extracts transfer details from balance/asset transfer calls +/// - Records proofs in wormhole storage after successful execution +/// - Increments transfer count +/// - Emits events +/// - Fails the transaction if proof recording fails +#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo, Debug, DecodeWithMemTracking)] +#[scale_info(skip_type_params(T))] +pub struct WormholeProofRecorderExtension(PhantomData); + +impl WormholeProofRecorderExtension { + /// Creates new extension + pub fn new() -> Self { + Self(PhantomData) + } + + /// Helper to convert lookup errors to transaction validity errors + fn lookup(address: &Address) -> Result { + ::Lookup::lookup(address.clone()) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::BadSigner)) + } + + /// Extract transfer details from a runtime call + fn extract_transfer_details( + origin: &RuntimeOrigin, + call: &RuntimeCall, + ) -> Result, TransactionValidityError> { + // Only process signed transactions + let who = match ensure_signed(origin.clone()) { + Ok(signer) => signer, + Err(_) => return Ok(None), + }; + + let details = match call { + // Native balance transfers + RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { dest, value }) => { + let to = Self::lookup(dest)?; + Some(TransferDetails { from: who, to, amount: *value, asset_id: 0 }) + }, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest, value }) => { + let to = Self::lookup(dest)?; + Some(TransferDetails { from: who, to, amount: *value, asset_id: 0 }) + }, + RuntimeCall::Balances(pallet_balances::Call::transfer_all { .. }) => None, + + // Asset transfers + RuntimeCall::Assets(pallet_assets::Call::transfer { id, target, amount }) => { + let to = Self::lookup(target)?; + Some(TransferDetails { asset_id: id.0, from: who, to, amount: *amount }) + }, + RuntimeCall::Assets(pallet_assets::Call::transfer_keep_alive { + id, + target, + amount, + }) => { + let to = Self::lookup(target)?; + Some(TransferDetails { asset_id: id.0, from: who, to, amount: *amount }) + }, + + _ => None, + }; + + Ok(details) + } + + /// Record the transfer proof using the TransferProofRecorder trait + fn record_proof(details: TransferDetails) -> Result<(), TransactionValidityError> { + let asset_id = if details.asset_id == 0 { None } else { Some(details.asset_id) }; + + >::record_transfer_proof( + asset_id, + details.from, + details.to, + details.amount, + ) + .map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Custom(100))) + } +} + +impl TransactionExtension + for WormholeProofRecorderExtension +{ + type Pre = Option; + type Val = (); + type Implicit = (); + + const IDENTIFIER: &'static str = "WormholeProofRecorderExtension"; + + fn weight(&self, call: &RuntimeCall) -> Weight { + // Account for proof recording in post_dispatch + match call { + RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { .. }) | + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) | + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) | + RuntimeCall::Assets(pallet_assets::Call::transfer_keep_alive { .. }) => { + // 2 writes: TransferProof insert + TransferCount update + // 1 read: TransferCount get + T::DbWeight::get().reads_writes(1, 2) + }, + _ => Weight::zero(), + } + } + + fn prepare( + self, + _val: Self::Val, + origin: &sp_runtime::traits::DispatchOriginOf, + call: &RuntimeCall, + _info: &sp_runtime::traits::DispatchInfoOf, + _len: usize, + ) -> Result { + // Extract transfer details to pass to post_dispatch + Self::extract_transfer_details(origin, call) + } + + fn validate( + &self, + _origin: sp_runtime::traits::DispatchOriginOf, + _call: &RuntimeCall, + _info: &DispatchInfoOf, + _len: usize, + _self_implicit: Self::Implicit, + _inherited_implication: &impl sp_runtime::traits::Implication, + _source: frame_support::pallet_prelude::TransactionSource, + ) -> sp_runtime::traits::ValidateResult { + // No validation needed - just return Ok + Ok((ValidTransaction::default(), (), _origin)) + } + + fn post_dispatch( + pre: Self::Pre, + _info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + _len: usize, + _result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + // Only record proof if the transaction succeeded (no error in post_info) + if post_info.actual_weight.is_some() || _result.is_ok() { + if let Some(details) = pre { + // Record the proof - if this fails, fail the whole transaction + Self::record_proof(details)?; + } + } + + Ok(()) + } +} + #[cfg(test)] mod tests { use super::*; @@ -117,6 +276,7 @@ mod tests { (bob(), EXISTENTIAL_DEPOSIT * 2), (charlie(), EXISTENTIAL_DEPOSIT * 100), ], + dev_accounts: None, } .assimilate_storage(&mut t) .unwrap(); @@ -326,8 +486,73 @@ mod tests { RuntimeCall::ReversibleTransfers(pallet_reversible_transfers::Call::cancel { tx_id: sp_core::H256::default(), }); - // High-security accounts can call cancel assert_ok!(check_call(call)); }); } + + #[test] + fn wormhole_proof_recorder_native_transfer() { + new_test_ext().execute_with(|| { + let alice_origin = RuntimeOrigin::signed(alice()); + let call = RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { + dest: MultiAddress::Id(bob()), + value: 100 * UNIT, + }); + + let details = WormholeProofRecorderExtension::::extract_transfer_details( + &alice_origin, + &call, + ) + .unwrap(); + + assert!(details.is_some()); + let details = details.unwrap(); + assert_eq!(details.from, alice()); + assert_eq!(details.to, bob()); + assert_eq!(details.amount, 100 * UNIT); + assert_eq!(details.asset_id, 0); + }); + } + + #[test] + fn wormhole_proof_recorder_asset_transfer() { + new_test_ext().execute_with(|| { + let alice_origin = RuntimeOrigin::signed(alice()); + let asset_id = 42u32; + let call = RuntimeCall::Assets(pallet_assets::Call::transfer { + id: codec::Compact(asset_id), + target: MultiAddress::Id(bob()), + amount: 500, + }); + + let details = WormholeProofRecorderExtension::::extract_transfer_details( + &alice_origin, + &call, + ) + .unwrap(); + + assert!(details.is_some()); + let details = details.unwrap(); + assert_eq!(details.from, alice()); + assert_eq!(details.to, bob()); + assert_eq!(details.amount, 500); + assert_eq!(details.asset_id, asset_id); + }); + } + + #[test] + fn wormhole_proof_recorder_ignores_non_transfer() { + new_test_ext().execute_with(|| { + let alice_origin = RuntimeOrigin::signed(alice()); + let call = RuntimeCall::System(frame_system::Call::remark { remark: vec![1, 2, 3] }); + + let details = WormholeProofRecorderExtension::::extract_transfer_details( + &alice_origin, + &call, + ) + .unwrap(); + + assert!(details.is_none()); + }); + } } diff --git a/runtime/tests/governance/treasury.rs b/runtime/tests/governance/treasury.rs index 3b4fe933..3218957a 100644 --- a/runtime/tests/governance/treasury.rs +++ b/runtime/tests/governance/treasury.rs @@ -88,9 +88,12 @@ mod tests { pub fn build(self) -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - pallet_balances::GenesisConfig:: { balances: self.balances } - .assimilate_storage(&mut t) - .unwrap(); + pallet_balances::GenesisConfig:: { + balances: self.balances, + dev_accounts: None, + } + .assimilate_storage(&mut t) + .unwrap(); // Pallet Treasury genesis (optional, as we fund it manually) // If your pallet_treasury::GenesisConfig needs setup, do it here. From 805e0f3e4c9686d0eecde068ef0786a51e68c47c Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:49:18 +0600 Subject: [PATCH 02/11] Ignore old tests --- pallets/wormhole/src/tests.rs | 226 +++++++++++++++++++++++++++++++++- 1 file changed, 225 insertions(+), 1 deletion(-) diff --git a/pallets/wormhole/src/tests.rs b/pallets/wormhole/src/tests.rs index 34e6edda..3ce5ed17 100644 --- a/pallets/wormhole/src/tests.rs +++ b/pallets/wormhole/src/tests.rs @@ -1,10 +1,234 @@ #[cfg(test)] mod wormhole_tests { - use crate::mock::*; + use crate::{get_wormhole_verifier, mock::*}; + use codec::Encode; use frame_support::{ assert_ok, traits::fungible::{Inspect, Mutate}, }; + use plonky2::plonk::circuit_data::CircuitConfig; + use qp_poseidon::PoseidonHasher; + use qp_wormhole_circuit::{ + inputs::{CircuitInputs, PrivateCircuitInputs, PublicCircuitInputs}, + nullifier::Nullifier, + }; + use qp_wormhole_prover::WormholeProver; + use qp_wormhole_verifier::ProofWithPublicInputs; + use qp_zk_circuits_common::{ + circuit::{C, F}, + storage_proof::prepare_proof_for_circuit, + utils::{digest_felts_to_bytes, BytesDigest, Digest}, + }; + use sp_runtime::{traits::Header, DigestItem}; + + fn generate_proof(inputs: CircuitInputs) -> ProofWithPublicInputs { + let config = CircuitConfig::standard_recursion_config(); + let prover = WormholeProver::new(config); + let prover_next = prover.commit(&inputs).expect("proof failed"); + let proof = prover_next.prove().expect("valid proof"); + proof + } + + #[test] + #[ignore = "Circuit needs to be updated to support asset_id in storage format (92 bytes vs 88 bytes)"] + fn test_wormhole_transfer_proof_generation() { + let alice = account_id(1); + let secret: BytesDigest = [1u8; 32].try_into().expect("valid secret"); + let unspendable_account = + qp_wormhole_circuit::unspendable_account::UnspendableAccount::from_secret(secret) + .account_id; + let unspendable_account_bytes_digest = digest_felts_to_bytes(unspendable_account); + let unspendable_account_bytes: [u8; 32] = unspendable_account_bytes_digest + .as_ref() + .try_into() + .expect("BytesDigest is always 32 bytes"); + let unspendable_account_id = AccountId::new(unspendable_account_bytes); + let exit_account_id = AccountId::new([42u8; 32]); + let funding_amount = 1_000_000_000_001u128; + + let mut ext = new_test_ext(); + + let (storage_key, state_root, leaf_hash, event_transfer_count, header) = + ext.execute_with(|| { + System::set_block_number(1); + + let pre_runtime_data = vec![ + 233, 182, 183, 107, 158, 1, 115, 19, 219, 126, 253, 86, 30, 208, 176, 70, 21, + 45, 180, 229, 9, 62, 91, 4, 6, 53, 245, 52, 48, 38, 123, 225, + ]; + let seal_data = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 77, 142, + ]; + + System::deposit_log(DigestItem::PreRuntime(*b"pow_", pre_runtime_data)); + System::deposit_log(DigestItem::Seal(*b"pow_", seal_data)); + + assert_ok!(Balances::mint_into(&alice, funding_amount)); + assert_ok!(Wormhole::transfer_native( + frame_system::RawOrigin::Signed(alice.clone()).into(), + unspendable_account_id.clone(), + funding_amount, + )); + + let event_transfer_count = 0u64; + + let leaf_hash = PoseidonHasher::hash_storage::( + &( + 0u32, + event_transfer_count, + alice.clone(), + unspendable_account_id.clone(), + funding_amount, + ) + .encode(), + ); + + let proof_address = crate::pallet::TransferProof::::hashed_key_for(&( + 0u32, + event_transfer_count, + alice.clone(), + unspendable_account_id.clone(), + funding_amount, + )); + let mut storage_key = proof_address; + storage_key.extend_from_slice(&leaf_hash); + + let header = System::finalize(); + let state_root = *header.state_root(); + + (storage_key, state_root, leaf_hash, event_transfer_count, header) + }); + + use sp_state_machine::prove_read; + let proof = prove_read(ext.as_backend(), &[&storage_key]) + .expect("failed to generate storage proof"); + + let proof_nodes_vec: Vec> = proof.iter_nodes().map(|n| n.to_vec()).collect(); + + let processed_storage_proof = + prepare_proof_for_circuit(proof_nodes_vec, hex::encode(&state_root), leaf_hash) + .expect("failed to prepare proof for circuit"); + + let parent_hash = *header.parent_hash(); + let extrinsics_root = *header.extrinsics_root(); + let digest = header.digest().encode(); + let digest_array: [u8; 110] = digest.try_into().expect("digest should be 110 bytes"); + let block_number: u32 = (*header.number()).try_into().expect("block number fits in u32"); + + let block_hash = header.hash(); + + let circuit_inputs = CircuitInputs { + private: PrivateCircuitInputs { + secret, + transfer_count: event_transfer_count, + funding_account: BytesDigest::try_from(alice.as_ref() as &[u8]) + .expect("account is 32 bytes"), + storage_proof: processed_storage_proof, + unspendable_account: Digest::from(unspendable_account).into(), + state_root: BytesDigest::try_from(state_root.as_ref()) + .expect("state root is 32 bytes"), + extrinsics_root: BytesDigest::try_from(extrinsics_root.as_ref()) + .expect("extrinsics root is 32 bytes"), + digest: digest_array, + }, + public: PublicCircuitInputs { + funding_amount, + nullifier: Nullifier::from_preimage(secret, event_transfer_count).hash.into(), + exit_account: BytesDigest::try_from(exit_account_id.as_ref() as &[u8]) + .expect("account is 32 bytes"), + block_hash: BytesDigest::try_from(block_hash.as_ref()) + .expect("block hash is 32 bytes"), + parent_hash: BytesDigest::try_from(parent_hash.as_ref()) + .expect("parent hash is 32 bytes"), + block_number, + }, + }; + + let proof = generate_proof(circuit_inputs); + + let public_inputs = + PublicCircuitInputs::try_from(&proof).expect("failed to parse public inputs"); + + assert_eq!(public_inputs.funding_amount, funding_amount); + assert_eq!( + public_inputs.exit_account, + BytesDigest::try_from(exit_account_id.as_ref() as &[u8]).unwrap() + ); + + let verifier = get_wormhole_verifier().expect("verifier should be available"); + verifier.verify(proof.clone()).expect("proof should verify"); + + let proof_bytes = proof.to_bytes(); + + new_test_ext().execute_with(|| { + System::set_block_number(1); + + let pre_runtime_data = vec![ + 233, 182, 183, 107, 158, 1, 115, 19, 219, 126, 253, 86, 30, 208, 176, 70, 21, 45, + 180, 229, 9, 62, 91, 4, 6, 53, 245, 52, 48, 38, 123, 225, + ]; + let seal_data = vec![ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 30, 77, 142, + ]; + + System::deposit_log(DigestItem::PreRuntime(*b"pow_", pre_runtime_data)); + System::deposit_log(DigestItem::Seal(*b"pow_", seal_data)); + + assert_ok!(Balances::mint_into(&alice, funding_amount)); + assert_ok!(Wormhole::transfer_native( + frame_system::RawOrigin::Signed(alice.clone()).into(), + unspendable_account_id.clone(), + funding_amount, + )); + + let block_1_header = System::finalize(); + + System::reset_events(); + System::initialize(&2, &block_1_header.hash(), block_1_header.digest()); + + let balance_before = Balances::balance(&exit_account_id); + assert_eq!(balance_before, 0); + + assert_ok!(Wormhole::verify_wormhole_proof( + frame_system::RawOrigin::None.into(), + proof_bytes.clone() + )); + + let balance_after = Balances::balance(&exit_account_id); + + assert!(balance_after > 0, "Exit account should have received funds"); + assert!( + balance_after < funding_amount, + "Exit account balance should be less than funding amount due to fees" + ); + }); + + new_test_ext().execute_with(|| { + System::set_block_number(1); + + let pre_runtime_data = vec![1u8; 32]; + let seal_data = vec![2u8; 64]; + + System::deposit_log(DigestItem::PreRuntime(*b"pow_", pre_runtime_data)); + System::deposit_log(DigestItem::Seal(*b"pow_", seal_data)); + + let different_header = System::finalize(); + + System::reset_events(); + System::initialize(&2, &different_header.hash(), different_header.digest()); + + let result = Wormhole::verify_wormhole_proof( + frame_system::RawOrigin::None.into(), + proof_bytes.clone(), + ); + + assert!(result.is_err(), "Proof verification should fail with mismatched state"); + }); + } #[test] fn transfer_native_works() { From 5de39960773afce6464c75b593d2d6edfd8ed445 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:54:30 +0600 Subject: [PATCH 03/11] Remove tests --- pallets/wormhole/src/mock.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pallets/wormhole/src/mock.rs b/pallets/wormhole/src/mock.rs index 04c7e4c1..7f0a4024 100644 --- a/pallets/wormhole/src/mock.rs +++ b/pallets/wormhole/src/mock.rs @@ -8,7 +8,6 @@ use frame_system::mocking::MockUncheckedExtrinsic; use qp_poseidon::PoseidonHasher; use sp_core::H256; use sp_runtime::{traits::IdentityLookup, BuildStorage}; -// --- MOCK RUNTIME --- construct_runtime!( pub enum Test { @@ -33,8 +32,6 @@ pub fn account_id(id: u64) -> AccountId { AccountId::new(bytes) } -// --- FRAME SYSTEM --- - parameter_types! { pub const BlockHashCount: u64 = 250; } @@ -72,8 +69,6 @@ impl frame_system::Config for Test { type PostTransactions = (); } -// --- PALLET BALANCES --- - parameter_types! { pub const ExistentialDeposit: Balance = 1; } @@ -95,8 +90,6 @@ impl pallet_balances::Config for Test { type RuntimeEvent = RuntimeEvent; } -// --- PALLET ASSETS --- - impl pallet_assets::Config for Test { type RuntimeEvent = RuntimeEvent; type Balance = Balance; @@ -120,8 +113,6 @@ impl pallet_assets::Config for Test { type Holder = (); } -// --- PALLET WORMHOLE --- - parameter_types! { pub const MintingAccount: AccountId = AccountId::new([ 231, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, From 0eaaabe4fc3c68b5f397a00bfb240854486460e2 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Tue, 9 Dec 2025 16:18:39 +0600 Subject: [PATCH 04/11] Override native asset id --- Cargo.lock | 10 ------ Cargo.toml | 10 +++--- pallets/wormhole/src/lib.rs | 61 ++++++++++++++++++++++++----------- pallets/wormhole/src/tests.rs | 3 +- 4 files changed, 49 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b5fbec03..36f7672e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9098,8 +9098,6 @@ version = "0.1.0" [[package]] name = "qp-wormhole-circuit" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b7357bec091287185850a6f7b67eb45f899a655b2a86825eae7e39b3bc6c3c" dependencies = [ "anyhow", "hex", @@ -9111,8 +9109,6 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit-builder" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faed51eaa66ba71526955b74aac2fdba9849905a405f74d5f4f26564a9eaa98" dependencies = [ "anyhow", "qp-plonky2", @@ -9123,8 +9119,6 @@ dependencies = [ [[package]] name = "qp-wormhole-prover" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a56d84dd9bf64944b88cb0ec971f179a569d3bdf148132ab01bfe2d56c069d" dependencies = [ "anyhow", "qp-plonky2", @@ -9135,8 +9129,6 @@ dependencies = [ [[package]] name = "qp-wormhole-verifier" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40387c4c43fe47419cba58eb4f13c2a3c32ac0e381c98c3d77293ebf53298de7" dependencies = [ "anyhow", "qp-plonky2", @@ -9147,8 +9139,6 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "366557f4c727379e2fd9c18b6667ae53c99bb241b973427df09a6b09584a11d4" dependencies = [ "anyhow", "hex", diff --git a/Cargo.toml b/Cargo.toml index 61ea142d..c0eaba37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -144,15 +144,15 @@ qp-poseidon = { version = "1.0.3", default-features = false } qp-poseidon-core = { version = "1.0.3", default-features = false, features = ["p2", "p3"] } qp-rusty-crystals-dilithium = { version = "2.0.0", default-features = false } qp-rusty-crystals-hdwallet = { version = "1.0.0" } -qp-wormhole-circuit = { version = "0.1.3", default-features = false } -qp-wormhole-circuit-builder = { version = "0.1.4", default-features = false } -qp-wormhole-prover = { version = "0.1.4", default-features = false, features = [ +qp-wormhole-circuit = { path = "../zk-circuits/wormhole/circuit", default-features = false } +qp-wormhole-circuit-builder = { path = "../zk-circuits/wormhole/circuit-builder", default-features = false } +qp-wormhole-prover = { path = "../zk-circuits/wormhole/prover", default-features = false, features = [ "no_random", ] } -qp-wormhole-verifier = { version = "0.1.4", default-features = false, features = [ +qp-wormhole-verifier = { path = "../zk-circuits/wormhole/verifier", default-features = false, features = [ "no_random", ] } -qp-zk-circuits-common = { version = "0.1.4", default-features = false, features = [ +qp-zk-circuits-common = { path = "../zk-circuits/common", default-features = false, features = [ "no_random", ] } diff --git a/pallets/wormhole/src/lib.rs b/pallets/wormhole/src/lib.rs index 8e953575..6ed465c9 100644 --- a/pallets/wormhole/src/lib.rs +++ b/pallets/wormhole/src/lib.rs @@ -38,7 +38,7 @@ pub mod pallet { pallet_prelude::*, traits::{ fungible::{Mutate, Unbalanced}, - fungibles::{self, Inspect as FungiblesInspect}, + fungibles::{self, Inspect as FungiblesInspect, Mutate as FungiblesMutate}, tokens::Preservation, Currency, ExistenceRequirement, WithdrawReasons, }, @@ -69,7 +69,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config where - AssetIdOf: Default, + AssetIdOf: Default + From + Clone, BalanceOf: Default, AssetBalanceOf: Into> + From>, { @@ -225,6 +225,10 @@ pub mod pallet { let exit_account = T::AccountId::decode(&mut &exit_account_bytes[..]) .map_err(|_| Error::::InvalidPublicInputs)?; + // Extract asset_id from public inputs + let asset_id_u32 = public_inputs.asset_id; + let asset_id: AssetIdOf = asset_id_u32.into(); + // Calculate fees first let weight = ::WeightInfo::verify_wormhole_proof(); let weight_fee = T::WeightToFee::weight_to_fee(&weight); @@ -232,30 +236,49 @@ pub mod pallet { let volume_fee = volume_fee_perbill * exit_balance; let total_fee = weight_fee.saturating_add(volume_fee); - // Mint tokens to the exit account - // This does not affect total issuance and does not create an imbalance - >::increase_balance( - &exit_account, - exit_balance, - frame_support::traits::tokens::Precision::Exact, - )?; - - // Withdraw fee from exit account if fees are non-zero - // This creates a negative imbalance that will be handled by the transaction payment - // pallet - if !total_fee.is_zero() { - let _fee_imbalance = T::Currency::withdraw( + // Handle native (asset_id = 0) or asset transfers + if asset_id == AssetIdOf::::default() { + // Native token transfer + // Mint tokens to the exit account + // This does not affect total issuance and does not create an imbalance + >::increase_balance( &exit_account, - total_fee, - WithdrawReasons::TRANSACTION_PAYMENT, - ExistenceRequirement::KeepAlive, + exit_balance, + frame_support::traits::tokens::Precision::Exact, )?; + + // Withdraw fee from exit account if fees are non-zero + // This creates a negative imbalance that will be handled by the transaction payment + // pallet + if !total_fee.is_zero() { + let _fee_imbalance = T::Currency::withdraw( + &exit_account, + total_fee, + WithdrawReasons::TRANSACTION_PAYMENT, + ExistenceRequirement::KeepAlive, + )?; + } + } else { + // Asset transfer + let asset_balance: AssetBalanceOf = exit_balance.into(); + >::mint_into(asset_id.clone(), &exit_account, asset_balance)?; + + // For assets, we still need to charge fees in native currency + // The exit account must have enough native balance to pay fees + if !total_fee.is_zero() { + let _fee_imbalance = T::Currency::withdraw( + &exit_account, + total_fee, + WithdrawReasons::TRANSACTION_PAYMENT, + ExistenceRequirement::AllowDeath, + )?; + } } // Create a transfer proof for the minted tokens let mint_account = T::MintingAccount::get(); Self::record_transfer( - AssetIdOf::::default(), + asset_id, mint_account, exit_account, exit_balance, diff --git a/pallets/wormhole/src/tests.rs b/pallets/wormhole/src/tests.rs index 3ce5ed17..c81745b5 100644 --- a/pallets/wormhole/src/tests.rs +++ b/pallets/wormhole/src/tests.rs @@ -122,10 +122,10 @@ mod wormhole_tests { let circuit_inputs = CircuitInputs { private: PrivateCircuitInputs { secret, + storage_proof: processed_storage_proof, transfer_count: event_transfer_count, funding_account: BytesDigest::try_from(alice.as_ref() as &[u8]) .expect("account is 32 bytes"), - storage_proof: processed_storage_proof, unspendable_account: Digest::from(unspendable_account).into(), state_root: BytesDigest::try_from(state_root.as_ref()) .expect("state root is 32 bytes"), @@ -134,6 +134,7 @@ mod wormhole_tests { digest: digest_array, }, public: PublicCircuitInputs { + asset_id: 0u32, funding_amount, nullifier: Nullifier::from_preimage(secret, event_transfer_count).hash.into(), exit_account: BytesDigest::try_from(exit_account_id.as_ref() as &[u8]) From 339caea0b4f96c1f933ca5f5ecf79ace760d31a7 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Tue, 9 Dec 2025 20:26:57 +0600 Subject: [PATCH 05/11] Use poseidon hasher --- .../reversible-transfers/src/tests/mock.rs | 1 + pallets/wormhole/Cargo.toml | 1 + pallets/wormhole/common.bin | Bin 1905 -> 1037 bytes pallets/wormhole/src/lib.rs | 39 ++++++++++++++---- pallets/wormhole/verifier.bin | Bin 552 -> 552 bytes 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/pallets/reversible-transfers/src/tests/mock.rs b/pallets/reversible-transfers/src/tests/mock.rs index 39aa6052..3c078314 100644 --- a/pallets/reversible-transfers/src/tests/mock.rs +++ b/pallets/reversible-transfers/src/tests/mock.rs @@ -349,6 +349,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { // Treasury account for fee collection tests (must meet existential deposit) (account_id(99), 1), ], + dev_accounts: None, } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/wormhole/Cargo.toml b/pallets/wormhole/Cargo.toml index f799c8af..a1a1d064 100644 --- a/pallets/wormhole/Cargo.toml +++ b/pallets/wormhole/Cargo.toml @@ -27,6 +27,7 @@ scale-info = { workspace = true, default-features = false, features = [ ] } sp-core.workspace = true sp-io.workspace = true +sp-metadata-ir.workspace = true sp-runtime.workspace = true [dev-dependencies] diff --git a/pallets/wormhole/common.bin b/pallets/wormhole/common.bin index 9232df9c3bdbf92f77f73c7a28db999bdfafe660..81c43fbdb560a05e0ae97102c2d9c88e9ce85afd 100644 GIT binary patch delta 112 zcmey!*UK@XhmmPwuQ(Sk0~D}LJg77A75Buy9E=Q;)fnYDIiM2N6X%L=mStYSWWfd% Z;RMpmK+FW?^FZ15P(FwffwH-QGyrMZ4S)au delta 939 zcmZ9LO-{ow7zLdMN@$@i(9()OQAAm>;zHbl4GUj@N?2^U143MYi*N-b7HkoD&Kt>U zRCV6ijuZQH*3Hl8`6=B`(reTH29w}xmELdD=P-RW%gRl?4c7KLt>@`?5&QO?4K{YT zYlj0n9NOWY9q!xVsPntv5XK$=S=52sK|lS?BX7miAy0!$4w)1ldR92K!(%&KbRL64 zIPm~@W``Gccxi`Mc6e=vD?1B*%-(MDkvnD|H`QD3oN+3TN=(_c5#yKd`ZTif`My#r z3CAUrl6#mb_aM24qm+A)imldjlwF#bh0~#qvPQg*+L=J)Fep Result<&'static WormholeVerifier, &'static str WORMHOLE_VERIFIER.as_ref().ok_or("Wormhole verifier not available") } +pub struct PoseidonStorageHasher(PhantomData); + +impl StorageHasher + for PoseidonStorageHasher +{ + // We are lying here, but maybe it's ok because it's just metadata + const METADATA: StorageHasherIR = StorageHasherIR::Identity; + type Output = [u8; 32]; + + fn hash(x: &[u8]) -> Self::Output { + PoseidonCore::hash_storage::(x) + } + + fn max_len() -> usize { + 32 + } +} + #[frame_support::pallet] pub mod pallet { use crate::WeightInfo; @@ -45,6 +68,7 @@ pub mod pallet { weights::WeightToFee, }; use frame_system::pallet_prelude::*; + use qp_poseidon::PoseidonHasher; use qp_wormhole_circuit::inputs::PublicCircuitInputs; use qp_wormhole_verifier::ProofWithPublicInputs; use qp_zk_circuits_common::circuit::{C, D, F}; @@ -111,7 +135,7 @@ pub mod pallet { #[pallet::getter(fn transfer_proof)] pub type TransferProof = StorageMap< _, - Blake2_128Concat, + PoseidonStorageHasher, (AssetIdOf, T::TransferCount, T::AccountId, T::AccountId, BalanceOf), /* (asset_id, tx_count, from, to, amount) */ (), OptionQuery, @@ -261,7 +285,11 @@ pub mod pallet { } else { // Asset transfer let asset_balance: AssetBalanceOf = exit_balance.into(); - >::mint_into(asset_id.clone(), &exit_account, asset_balance)?; + >::mint_into( + asset_id.clone(), + &exit_account, + asset_balance, + )?; // For assets, we still need to charge fees in native currency // The exit account must have enough native balance to pay fees @@ -277,12 +305,7 @@ pub mod pallet { // Create a transfer proof for the minted tokens let mint_account = T::MintingAccount::get(); - Self::record_transfer( - asset_id, - mint_account, - exit_account, - exit_balance, - )?; + Self::record_transfer(asset_id, mint_account, exit_account, exit_balance)?; // Emit event Self::deposit_event(Event::ProofVerified { exit_amount: exit_balance }); diff --git a/pallets/wormhole/verifier.bin b/pallets/wormhole/verifier.bin index 121eb2b02d5badfed2fbdcc2c29e9d7a7ffec909..6f7404b667329a8017f4474738e1864f35740b79 100644 GIT binary patch literal 552 zcmV+@0@wWn000000002bB((3dmQ#8Pd=u(5Q_MO*fJ;f8QBx9Z942FUwJ!)ZY!Ao9 z8>68O>uJ)jvp@aiEmphF1 zt=EwEV3`!C%DOHzBcO`Nn}z?iC(n1J%E~RK{>@7be>23SGfkcp+}r6(aMku|TbmAU zXrvkRZ{VGl_&}lwx_azzDm4Q*Gdc-%leoNxAW=R=MBgK|-WN=A{TuL#eQa`NHwWXG zl?|0r;!mqt+Z>~oULuS3xo$NuM1my_ktngy@30kh)^C;PHLCwM6LD`H#8}24RErRi zenXR9((a41u@*5HO9_1Oa?B~~$@I(eA820>@lPW(Kqgl$_O z8ZgGiQ^L7iT)4ai5L7ua~{&D*+DfTc5EAyADHBW`zC`Z&G<3L;3+W+ ztfy#C=~AZ_jGRSU#(7p{k{q-A;z0H12G=@*vtvHf49ARUh_WGBEmm2P8 qXKRDCF!y7cTf!)t9w)1yPwkj)P{8vPStKjYr_ZO+O{pLv{XmN~s0O6~ literal 552 zcmV+@0@wWn000000001{fgdmlpg^a6wNw|p^n-O4(K!Freq^lh(_{C)mkWt!KDUW$ zE?PNjBmiDLoZ{RA6jg7PSxr{BizIHJW~f*HO{9pc&uXqZjrcId3x@z<4KDbJIfrOx z3FS>VcY)vNi24;Io?*Yn7P+jd*v`UX^%^YUdrQkHvuml_sF!}-T;4qK!GOD-UtYyb zZfVj=Uoi;ph0JLtv6@F>U9H=K?8W=9Um3#E&28RBK*imrCh|tLm``n>T0iw%B&g~U zLHCzUTEj;0(O=t!{eN>!%#+cA6Jn(-a7yeFbw!h6R3ADGoPx(lp`OJ9E9cB0yYUCvl07q z2E9l&(l;2u;gF8HIhzbxcX&Omio1+M9&4fZM%uPN(d_}gXv3BeaZh4RY>l>s?G9bk z&ekwT5?b=MPr;MuP0MkQ%(e&ddMhGQVUJ5L>W-xNqo|R;gFc|_#5f`v2^nITGT}?g z)0Gs3-u}JJUGbhZm!M0U++70}*uA7{Dveh_&fT$~77p?a*$|iW9q^4&?iN?J93InB z_r-UE{N^JNuC9aIJk+lv!L++*S;2N+gcngm8J6`v+%Xqm<$jET;|f!$tR5)JxbPf~ qKk Date: Wed, 10 Dec 2025 18:45:08 +0600 Subject: [PATCH 06/11] Use poseidon storage hasher --- pallets/wormhole/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/wormhole/src/lib.rs b/pallets/wormhole/src/lib.rs index 3443b7f3..ec635407 100644 --- a/pallets/wormhole/src/lib.rs +++ b/pallets/wormhole/src/lib.rs @@ -16,6 +16,7 @@ mod mock; #[cfg(test)] mod tests; pub mod weights; +use sp_metadata_ir::StorageHasherIR; pub use weights::*; #[cfg(feature = "runtime-benchmarks")] @@ -54,7 +55,7 @@ impl StorageHasher #[frame_support::pallet] pub mod pallet { - use crate::WeightInfo; + use crate::{PoseidonStorageHasher, WeightInfo}; use alloc::vec::Vec; use codec::Decode; use frame_support::{ @@ -68,7 +69,6 @@ pub mod pallet { weights::WeightToFee, }; use frame_system::pallet_prelude::*; - use qp_poseidon::PoseidonHasher; use qp_wormhole_circuit::inputs::PublicCircuitInputs; use qp_wormhole_verifier::ProofWithPublicInputs; use qp_zk_circuits_common::circuit::{C, D, F}; From a3401ba1badfec9831314bf3a06f49fe49fa0fdf Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Thu, 11 Dec 2025 18:09:45 +0600 Subject: [PATCH 07/11] Passing wormhole proof tests --- Cargo.lock | 51 +++++++++++++++++++++-------- Cargo.toml | 4 +-- pallets/merkle-airdrop/src/mock.rs | 2 ++ pallets/wormhole/common.bin | Bin 1037 -> 1905 bytes pallets/wormhole/src/tests.rs | 1 - pallets/wormhole/verifier.bin | Bin 552 -> 552 bytes 6 files changed, 42 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7de2c0a6..9c12d858 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7234,7 +7234,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "primitive-types 0.13.1", - "qp-poseidon-core", + "qp-poseidon-core 1.0.2", "qpow-math", "scale-info", "sp-arithmetic", @@ -7617,7 +7617,7 @@ dependencies = [ "qp-header", "qp-plonky2", "qp-poseidon", - "qp-poseidon-core", + "qp-poseidon-core 1.0.2", "qp-wormhole", "qp-wormhole-circuit", "qp-wormhole-circuit-builder", @@ -8927,7 +8927,7 @@ dependencies = [ "p3-goldilocks", "parity-scale-codec", "qp-poseidon", - "qp-poseidon-core", + "qp-poseidon-core 1.0.2", "scale-info", "serde", "serde_json", @@ -8956,7 +8956,7 @@ dependencies = [ "plonky2_maybe_rayon", "plonky2_util", "qp-plonky2-field", - "qp-poseidon-constants", + "qp-poseidon-constants 1.0.1", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -8984,15 +8984,14 @@ dependencies = [ [[package]] name = "qp-poseidon" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ebc5e9fe1f91f5006aa2b45650d0049887d41d80c669ef5a78a45086895054d" +version = "1.0.2" +source = "git+https://github.com/Quantus-Network/qp-poseidon?branch=feat%2Ftransfer-proof-asset-id#9ae96dea18464d6ea43de5c78a066f9d1f17b801" dependencies = [ "log", "p3-field", "p3-goldilocks", "parity-scale-codec", - "qp-poseidon-core", + "qp-poseidon-core 1.0.2", "scale-info", "serde", "sp-core", @@ -9014,6 +9013,32 @@ dependencies = [ "rand_chacha 0.9.0", ] +[[package]] +name = "qp-poseidon-constants" +version = "1.0.2" +source = "git+https://github.com/Quantus-Network/qp-poseidon?branch=feat%2Ftransfer-proof-asset-id#9ae96dea18464d6ea43de5c78a066f9d1f17b801" +dependencies = [ + "p3-field", + "p3-goldilocks", + "p3-poseidon2", + "rand 0.9.2", + "rand_chacha 0.9.0", +] + +[[package]] +name = "qp-poseidon-core" +version = "1.0.2" +source = "git+https://github.com/Quantus-Network/qp-poseidon?branch=feat%2Ftransfer-proof-asset-id#9ae96dea18464d6ea43de5c78a066f9d1f17b801" +dependencies = [ + "p3-field", + "p3-goldilocks", + "p3-poseidon2", + "p3-symmetric", + "qp-plonky2", + "qp-poseidon-constants 1.0.2", + "rand_chacha 0.9.0", +] + [[package]] name = "qp-poseidon-core" version = "1.0.3" @@ -9025,7 +9050,7 @@ dependencies = [ "p3-poseidon2", "p3-symmetric", "qp-plonky2", - "qp-poseidon-constants", + "qp-poseidon-constants 1.0.1", "rand_chacha 0.9.0", ] @@ -9051,7 +9076,7 @@ dependencies = [ "hex", "hex-literal", "nam-tiny-hderive", - "qp-poseidon-core", + "qp-poseidon-core 1.0.3", "qp-rusty-crystals-dilithium", "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -9081,7 +9106,7 @@ dependencies = [ "anyhow", "hex", "qp-plonky2", - "qp-poseidon-core", + "qp-poseidon-core 1.0.3", "qp-zk-circuits-common", ] @@ -9122,7 +9147,7 @@ dependencies = [ "anyhow", "hex", "qp-plonky2", - "qp-poseidon-core", + "qp-poseidon-core 1.0.3", "serde", ] @@ -9135,7 +9160,7 @@ dependencies = [ "num-bigint", "num-traits", "primitive-types 0.13.1", - "qp-poseidon-core", + "qp-poseidon-core 1.0.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 77cbd3e1..d266583a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,8 +149,8 @@ sp-consensus-qpow = { path = "./primitives/consensus/qpow", default-features = f # Quantus network dependencies qp-plonky2 = { version = "1.1.3", default-features = false } -qp-poseidon = { version = "1.0.3", default-features = false } -qp-poseidon-core = { version = "1.0.3", default-features = false, features = ["p2", "p3"] } +qp-poseidon = { git = "https://github.com/Quantus-Network/qp-poseidon", branch = "feat/transfer-proof-asset-id", default-features = false } +qp-poseidon-core = { git = "https://github.com/Quantus-Network/qp-poseidon", branch = "feat/transfer-proof-asset-id", default-features = false, features = ["p2", "p3"] } qp-rusty-crystals-dilithium = { version = "2.0.0", default-features = false } qp-rusty-crystals-hdwallet = { version = "1.0.0" } qp-wormhole-circuit = { path = "../zk-circuits/wormhole/circuit", default-features = false } diff --git a/pallets/merkle-airdrop/src/mock.rs b/pallets/merkle-airdrop/src/mock.rs index 0a5c865c..a2486392 100644 --- a/pallets/merkle-airdrop/src/mock.rs +++ b/pallets/merkle-airdrop/src/mock.rs @@ -80,6 +80,7 @@ impl pallet_balances::Config for Test { type MaxFreezes = (); type RuntimeFreezeReason = (); type DoneSlashHandler = (); + type RuntimeEvent = RuntimeEvent; } parameter_types! { @@ -121,6 +122,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10_000_000), (MerkleAirdrop::account_id(), 1)], + dev_accounts: None, } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/wormhole/common.bin b/pallets/wormhole/common.bin index 81c43fbdb560a05e0ae97102c2d9c88e9ce85afd..5fbea0f3cae044a80d0ad7def777f01eaf656179 100644 GIT binary patch delta 939 zcmZ9LO-{ow7zLdMN@$@iP}+(=QAAm>;zHbl4GUj@N?2^U143MYi*N-b7HkoD&Kt>U zRCV6ijuZQH*3Hl8`6=B`(reTH29w}xmELdD=P-RW%gRl?4c7KLt>@`?5&QO?4K{YT zYlj0n9NOWY9q!xVsPntv5XK$=S=52sK|lS?BX7miAy0!$4w)1ldR92K!)StH&@?MxtY7?i1*GKWH$+9|Uu$}CBF m8XnsJ_L3au)MXZ=c9x*LRgBp#GM|ga@X07sA7wrfE%qPf$Q$!LJ&H?$lj`_+_9t%hCW4zaBPj=_8eTF~a6FK|@U2RLAy-w|Jsn3X4pZ%}e`V#gD}k z8QTcVvn|M^O}>lCT+5+B}3aBy>%DnzEpORc68i!VX^I z-h>q_C%|qn7H!$eblY*WzR}^G52h31YVi>Fx*HI*q<<4W>i?XvI1~XFgDbx}xo@{q zp@}5$M*4X-oRIhyPJw`lBx(7{t)OF6^N^s&!$HDocfHLqxHcMFPri#g7Ysk|FnckP zh%FhYHzC42a)lOow>H_wD;@&*-1~mhHyIm@cp^1q5reONb`4F5ygK>DG?f|A{0Op6HCuH;y2e*uY`!7y60Qs?;Cxn>8By2J)6y6_#_xAv_I-M_RG+L+w z;x~}q%5DjCbx~E{xKy^iNP-^0O<%}u?DF~$_@TZ1lu5o{k~JDr__M5~fN9r4-=BM` zDj^)kZT^Dy68O>uJ)jvp@aiEmphF1 zt=EwEV3`!C%DOHzBcO`Nn}z?iC(n1J%E~RK{>@7be>23SGfkcp+}r6(aMku|TbmAU zXrvkRZ{VGl_&}lwx_azzDm4Q*Gdc-%leoNxAW=R=MBgK|-WN=A{TuL#eQa`NHwWXG zl?|0r;!mqt+Z>~oULuS3xo$NuM1my_ktngy@30kh)^C;PHLCwM6LD`H#8}24RErRi zenXR9((a41u@*5HO9_1Oa?B~~$@I(eA820>@lPW(Kqgl$_O z8ZgGiQ^L7iT)4ai5L7ua~{&D*+DfTc5EAyADHBW`zC`Z&G<3L;3+W+ ztfy#C=~AZ_jGRSU#(7p{k{q-A;z0H12G=@*vtvHf49ARUh_WGBEmm2P8 qXKRDCF!y7cTf!)t9w)1yPwkj)P{8vPStKjYr_ZO+O{pLv{XmN~s0O6~ From dad0b1976e1933a1e0d913249686a6b04d009d58 Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Fri, 12 Dec 2025 12:29:18 +0600 Subject: [PATCH 08/11] Update binaries --- pallets/wormhole/common.bin | Bin 1905 -> 1905 bytes pallets/wormhole/verifier.bin | Bin 552 -> 552 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/pallets/wormhole/common.bin b/pallets/wormhole/common.bin index 5fbea0f3cae044a80d0ad7def777f01eaf656179..9232df9c3bdbf92f77f73c7a28db999bdfafe660 100644 GIT binary patch delta 14 Vcmey!_mOXdC^MtzW-;by762x!1PA~C delta 14 Vcmey!_mOXdC^MtjW-;by762x)1PK5D diff --git a/pallets/wormhole/verifier.bin b/pallets/wormhole/verifier.bin index 6426dfe4e1bcf60075bb21152feff6375fce43d5..bf377a08f7665d29af6e8c56b127c54b72d505d7 100644 GIT binary patch literal 552 zcmV+@0@wWn0000000000DOE~iWfX;&^>eAr<5Y?jLaxS*0)LdDA+d?KpdoeyV&?8| zm;CkpQS`9?AD!6QP!{*J@N4!ZCV}65gvg=2=f#~#rGmdR6~}q&hano;jx%*WgA^9W z>yQ4;97EAhGBc8(9V=g0a0~*|COxVIDaQ0njt1M~`rs&LH-R%+iqDJ;1SK6$^1L~~ zV|E^*R~EW6O2J+@gB=7wgz|V|+u&i0O2jo;crBsi4qGPJ>_4JLiHKMa`4D$=C_%AW zLR9!^2f*5>`9R@BB}d-YqWD<8>g>7V2YO;F8af?g&9MP&6but`7tOldmbKgITP>t= zq@H^YQ4@J6tVxiXL}ecZHQ0Y5A1BX)|HhIjT@Qo<5N1POK-kujx)!;y0O`1idB10e zZ!x{1ycW^v7;)xYUL^~OA&6Y-VXP7X+RggXgl>hV4oHbYO3%2TM`0!byAYSP$xiOs z)qBIbeM(&$l>{gkiycO=E;SC>aRr?@SkV=*TOisLkB2`2_5ji@q#{pj_}9YDALT7a zMwOuYm*3##9+{^-Yh%N{b(c0HX~vPHE5#F*Otu8S#Se^>q}-mO-;cvr0bnoO*WM~` zfg^%*uDDQ!vUWqzQ1Jws*m5mVJ@RGr}r9$!LJ&H?$lj`_+_9t%hCW4zaBPj=_8eTF~a6FK|@U2RLAy-w|Jsn3X4pZ%}e`V#gD}k z8QTcVvn|M^O}>lCT+5+B}3aBy>%DnzEpORc68i!VX^I z-h>q_C%|qn7H!$eblY*WzR}^G52h31YVi>Fx*HI*q<<4W>i?XvI1~XFgDbx}xo@{q zp@}5$M*4X-oRIhyPJw`lBx(7{t)OF6^N^s&!$HDocfHLqxHcMFPri#g7Ysk|FnckP zh%FhYHzC42a)lOow>H_wD;@&*-1~mhHyIm@cp^1q5reONb`4F5ygK>DG?f|A{0Op6HCuH;y2e*uY`!7y60Qs?;Cxn>8By2J)6y6_#_xAv_I-M_RG+L+w z;x~}q%5DjCbx~E{xKy^iNP-^0O<%}u?DF~$_@TZ1lu5o{k~JDr__M5~fN9r4-=BM` zDj^)kZT^Dy Date: Fri, 12 Dec 2025 13:14:24 +0600 Subject: [PATCH 09/11] Update binaries --- pallets/wormhole/common.bin | Bin 1905 -> 1905 bytes pallets/wormhole/verifier.bin | Bin 552 -> 552 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/pallets/wormhole/common.bin b/pallets/wormhole/common.bin index 9232df9c3bdbf92f77f73c7a28db999bdfafe660..5fbea0f3cae044a80d0ad7def777f01eaf656179 100644 GIT binary patch delta 14 Vcmey!_mOXdC^MtjW-;by762x)1PK5D delta 14 Vcmey!_mOXdC^MtzW-;by762x!1PA~C diff --git a/pallets/wormhole/verifier.bin b/pallets/wormhole/verifier.bin index bf377a08f7665d29af6e8c56b127c54b72d505d7..229b59d97d5e8e140b16a1bc97e9549f0e4bc250 100644 GIT binary patch literal 552 zcmV+@0@wWn000000001mTjTr3B2agUHfFrUGtEY?c|aBKvf6rpL30{-^KRXXhj{y2 zz8rIqg$hj))SW(&TT7nI=r=es;;pp^jB9#{Wjfz5a$)`k+E?!pk^>&gHRX$Ingu`y8n-c zJ4+g-gc+nLhyn3)HX@t(d~8{_e(RT+vai%?7x2){Ce*}7_VylH$PGOLr)tkX#p})s zr7qItfqi#*J=?eEk2o=|CDSY_n{R`|O1U8T??>5(E688U=+Uj_pQxX(RL~e&kD1Pi z_i`~Yi$@m0KytTd7b+bf7E}nZ_#UPwlzOM)`cxy{9CBMxTGcWQxCcW?M<{4g+(VC~ z4v$C+4f{RIosj!{M*%kuv>w@IFe22t-faOfu;e#vsJH)K`f9dloD`s(J){@l1q|hK qi>>->!lCIbJkXeAr<5Y?jLaxS*0)LdDA+d?KpdoeyV&?8| zm;CkpQS`9?AD!6QP!{*J@N4!ZCV}65gvg=2=f#~#rGmdR6~}q&hano;jx%*WgA^9W z>yQ4;97EAhGBc8(9V=g0a0~*|COxVIDaQ0njt1M~`rs&LH-R%+iqDJ;1SK6$^1L~~ zV|E^*R~EW6O2J+@gB=7wgz|V|+u&i0O2jo;crBsi4qGPJ>_4JLiHKMa`4D$=C_%AW zLR9!^2f*5>`9R@BB}d-YqWD<8>g>7V2YO;F8af?g&9MP&6but`7tOldmbKgITP>t= zq@H^YQ4@J6tVxiXL}ecZHQ0Y5A1BX)|HhIjT@Qo<5N1POK-kujx)!;y0O`1idB10e zZ!x{1ycW^v7;)xYUL^~OA&6Y-VXP7X+RggXgl>hV4oHbYO3%2TM`0!byAYSP$xiOs z)qBIbeM(&$l>{gkiycO=E;SC>aRr?@SkV=*TOisLkB2`2_5ji@q#{pj_}9YDALT7a zMwOuYm*3##9+{^-Yh%N{b(c0HX~vPHE5#F*Otu8S#Se^>q}-mO-;cvr0bnoO*WM~` zfg^%*uDDQ!vUWqzQ1Jws*m5mVJ@RGr}r9 Date: Fri, 12 Dec 2025 13:59:30 +0600 Subject: [PATCH 10/11] Update zk-circuits crates --- Cargo.lock | 14 +++++++++----- Cargo.toml | 11 +++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ae63b23e..eae87855 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8842,7 +8842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck 0.5.0", - "itertools 0.14.0", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -8875,7 +8875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.110", @@ -9049,7 +9049,6 @@ dependencies = [ "p3-goldilocks", "p3-poseidon2", "p3-symmetric", - "qp-plonky2", "qp-poseidon-constants 1.0.1", "rand_chacha 0.9.0", ] @@ -9102,17 +9101,19 @@ version = "0.1.0" [[package]] name = "qp-wormhole-circuit" version = "0.1.4" +source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" dependencies = [ "anyhow", "hex", "qp-plonky2", - "qp-poseidon-core 1.0.3", + "qp-poseidon-core 1.0.2", "qp-zk-circuits-common", ] [[package]] name = "qp-wormhole-circuit-builder" version = "0.1.4" +source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" dependencies = [ "anyhow", "qp-plonky2", @@ -9123,6 +9124,7 @@ dependencies = [ [[package]] name = "qp-wormhole-prover" version = "0.1.4" +source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" dependencies = [ "anyhow", "qp-plonky2", @@ -9133,6 +9135,7 @@ dependencies = [ [[package]] name = "qp-wormhole-verifier" version = "0.1.4" +source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" dependencies = [ "anyhow", "qp-plonky2", @@ -9143,11 +9146,12 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" version = "0.1.4" +source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" dependencies = [ "anyhow", "hex", "qp-plonky2", - "qp-poseidon-core 1.0.3", + "qp-poseidon-core 1.0.2", "serde", ] diff --git a/Cargo.toml b/Cargo.toml index d266583a..9bb0aabb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,15 +153,15 @@ qp-poseidon = { git = "https://github.com/Quantus-Network/qp-poseidon", branch = qp-poseidon-core = { git = "https://github.com/Quantus-Network/qp-poseidon", branch = "feat/transfer-proof-asset-id", default-features = false, features = ["p2", "p3"] } qp-rusty-crystals-dilithium = { version = "2.0.0", default-features = false } qp-rusty-crystals-hdwallet = { version = "1.0.0" } -qp-wormhole-circuit = { path = "../zk-circuits/wormhole/circuit", default-features = false } -qp-wormhole-circuit-builder = { path = "../zk-circuits/wormhole/circuit-builder", default-features = false } -qp-wormhole-prover = { path = "../zk-circuits/wormhole/prover", default-features = false, features = [ +qp-wormhole-circuit = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false } +qp-wormhole-circuit-builder = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false } +qp-wormhole-prover = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false, features = [ "no_random", ] } -qp-wormhole-verifier = { path = "../zk-circuits/wormhole/verifier", default-features = false, features = [ +qp-wormhole-verifier = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false, features = [ "no_random", ] } -qp-zk-circuits-common = { path = "../zk-circuits/common", default-features = false, features = [ +qp-zk-circuits-common = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false, features = [ "no_random", ] } @@ -243,7 +243,6 @@ sc-network = { path = "client/network" } sp-state-machine = { path = "./primitives/state-machine" } sp-trie = { path = "./primitives/trie" } - [profile.release] opt-level = 3 panic = "unwind" From 8e8ba8e84ba7e6a270bbe7bcf2a63a44b9bdf06e Mon Sep 17 00:00:00 2001 From: Dastan <88332432+dastansam@users.noreply.github.com> Date: Mon, 22 Dec 2025 01:25:29 +0600 Subject: [PATCH 11/11] Use crates.io dep versions --- Cargo.lock | 85 +++++++++++++++++++++--------------------------------- Cargo.toml | 14 ++++----- 2 files changed, 40 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eae87855..9bfabe60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7234,7 +7234,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "primitive-types 0.13.1", - "qp-poseidon-core 1.0.2", + "qp-poseidon-core", "qpow-math", "scale-info", "sp-arithmetic", @@ -7617,7 +7617,7 @@ dependencies = [ "qp-header", "qp-plonky2", "qp-poseidon", - "qp-poseidon-core 1.0.2", + "qp-poseidon-core", "qp-wormhole", "qp-wormhole-circuit", "qp-wormhole-circuit-builder", @@ -8842,7 +8842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck 0.5.0", - "itertools 0.10.5", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -8875,7 +8875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.110", @@ -8927,7 +8927,7 @@ dependencies = [ "p3-goldilocks", "parity-scale-codec", "qp-poseidon", - "qp-poseidon-core 1.0.2", + "qp-poseidon-core", "scale-info", "serde", "serde_json", @@ -8956,7 +8956,7 @@ dependencies = [ "plonky2_maybe_rayon", "plonky2_util", "qp-plonky2-field", - "qp-poseidon-constants 1.0.1", + "qp-poseidon-constants", "rand 0.8.5", "rand_chacha 0.3.1", "serde", @@ -8984,14 +8984,15 @@ dependencies = [ [[package]] name = "qp-poseidon" -version = "1.0.2" -source = "git+https://github.com/Quantus-Network/qp-poseidon?branch=feat%2Ftransfer-proof-asset-id#9ae96dea18464d6ea43de5c78a066f9d1f17b801" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5029d1c8223c0312a0247ebc745c5b09622dcbebe104f0fdb9de358b87ef032a" dependencies = [ "log", "p3-field", "p3-goldilocks", "parity-scale-codec", - "qp-poseidon-core 1.0.2", + "qp-poseidon-core", "scale-info", "serde", "sp-core", @@ -9013,43 +9014,18 @@ dependencies = [ "rand_chacha 0.9.0", ] -[[package]] -name = "qp-poseidon-constants" -version = "1.0.2" -source = "git+https://github.com/Quantus-Network/qp-poseidon?branch=feat%2Ftransfer-proof-asset-id#9ae96dea18464d6ea43de5c78a066f9d1f17b801" -dependencies = [ - "p3-field", - "p3-goldilocks", - "p3-poseidon2", - "rand 0.9.2", - "rand_chacha 0.9.0", -] - -[[package]] -name = "qp-poseidon-core" -version = "1.0.2" -source = "git+https://github.com/Quantus-Network/qp-poseidon?branch=feat%2Ftransfer-proof-asset-id#9ae96dea18464d6ea43de5c78a066f9d1f17b801" -dependencies = [ - "p3-field", - "p3-goldilocks", - "p3-poseidon2", - "p3-symmetric", - "qp-plonky2", - "qp-poseidon-constants 1.0.2", - "rand_chacha 0.9.0", -] - [[package]] name = "qp-poseidon-core" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52c70df221c356b3ce63afabfae623aae632c27d3e078cd20eec4348096c2d7" +checksum = "71dd1bf5d2997abf70247fcd23c8f04d7093b1faf33b775a42fb00c07e0a0e05" dependencies = [ "p3-field", "p3-goldilocks", "p3-poseidon2", "p3-symmetric", - "qp-poseidon-constants 1.0.1", + "qp-plonky2", + "qp-poseidon-constants", "rand_chacha 0.9.0", ] @@ -9075,7 +9051,7 @@ dependencies = [ "hex", "hex-literal", "nam-tiny-hderive", - "qp-poseidon-core 1.0.3", + "qp-poseidon-core", "qp-rusty-crystals-dilithium", "rand_chacha 0.9.0", "rand_core 0.9.3", @@ -9100,20 +9076,22 @@ version = "0.1.0" [[package]] name = "qp-wormhole-circuit" -version = "0.1.4" -source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcbccee20e314a1c52f36d8e78b1fa8205da46050c18da60d4964f41875bd4f6" dependencies = [ "anyhow", "hex", "qp-plonky2", - "qp-poseidon-core 1.0.2", + "qp-poseidon-core", "qp-zk-circuits-common", ] [[package]] name = "qp-wormhole-circuit-builder" -version = "0.1.4" -source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aec473c32d69e2e0941d192ab8cf8712761ee2e84d3829fc211087f53be15bf3" dependencies = [ "anyhow", "qp-plonky2", @@ -9123,8 +9101,9 @@ dependencies = [ [[package]] name = "qp-wormhole-prover" -version = "0.1.4" -source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd377a2fa936e6edb069ffecbf41afe10803b342e5cda8ff3aab199f77fde5d" dependencies = [ "anyhow", "qp-plonky2", @@ -9134,8 +9113,9 @@ dependencies = [ [[package]] name = "qp-wormhole-verifier" -version = "0.1.4" -source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08e114bc7ad7a7589c502960abc570685b726993ccce05443577a9ddd8dc4ee1" dependencies = [ "anyhow", "qp-plonky2", @@ -9145,13 +9125,14 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" -version = "0.1.4" -source = "git+https://github.com/Quantus-Network/zk-circuits?branch=feat%2Fasset-id-wormhole#27fec229513e541237ec39497c6fd8bec69358cb" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445cc21c39959d1b553c4d8ea94d058ceab84cd70e5d47ec82b11535494cec46" dependencies = [ "anyhow", "hex", "qp-plonky2", - "qp-poseidon-core 1.0.2", + "qp-poseidon-core", "serde", ] @@ -9164,7 +9145,7 @@ dependencies = [ "num-bigint", "num-traits", "primitive-types 0.13.1", - "qp-poseidon-core 1.0.2", + "qp-poseidon-core", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9bb0aabb..4878ed36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -149,19 +149,19 @@ sp-consensus-qpow = { path = "./primitives/consensus/qpow", default-features = f # Quantus network dependencies qp-plonky2 = { version = "1.1.3", default-features = false } -qp-poseidon = { git = "https://github.com/Quantus-Network/qp-poseidon", branch = "feat/transfer-proof-asset-id", default-features = false } -qp-poseidon-core = { git = "https://github.com/Quantus-Network/qp-poseidon", branch = "feat/transfer-proof-asset-id", default-features = false, features = ["p2", "p3"] } +qp-poseidon = { version = "1.0.5", default-features = false } +qp-poseidon-core = { version = "1.0.5", default-features = false, features = ["p2", "p3"] } qp-rusty-crystals-dilithium = { version = "2.0.0", default-features = false } qp-rusty-crystals-hdwallet = { version = "1.0.0" } -qp-wormhole-circuit = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false } -qp-wormhole-circuit-builder = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false } -qp-wormhole-prover = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false, features = [ +qp-wormhole-circuit = { version = "0.1.7", default-features = false } +qp-wormhole-circuit-builder = { version = "0.1.7", default-features = false } +qp-wormhole-prover = { version = "0.1.7", default-features = false, features = [ "no_random", ] } -qp-wormhole-verifier = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false, features = [ +qp-wormhole-verifier = { version = "0.1.7", default-features = false, features = [ "no_random", ] } -qp-zk-circuits-common = { git = "https://github.com/Quantus-Network/zk-circuits", branch = "feat/asset-id-wormhole", default-features = false, features = [ +qp-zk-circuits-common = { version = "0.1.7", default-features = false, features = [ "no_random", ] }