diff --git a/Cargo.lock b/Cargo.lock index 702aa75e47a5d..aa26f901b908c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,6 +540,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", + "pallet-transaction-payment-rpc", "parity-scale-codec", "sc-client-api", "sc-rpc-api", @@ -563,7 +564,6 @@ dependencies = [ "xpallet-gateway-records-rpc-runtime-api", "xpallet-mining-staking-rpc", "xpallet-mining-staking-rpc-runtime-api", - "xpallet-transaction-payment-rpc", ] [[package]] @@ -577,6 +577,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "pallet-aura", "pallet-authorship", + "pallet-balances 2.0.0-rc4", "pallet-grandpa", "pallet-im-online", "pallet-multisig", @@ -585,6 +586,8 @@ dependencies = [ "pallet-session", "pallet-sudo", "pallet-timestamp", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", "pallet-utility", "parity-scale-codec", "serde", @@ -620,8 +623,6 @@ dependencies = [ "xpallet-protocol", "xpallet-support", "xpallet-system", - "xpallet-transaction-payment", - "xpallet-transaction-payment-rpc-runtime-api", ] [[package]] @@ -3046,6 +3047,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-balances" +version = "2.0.0-rc4" +dependencies = [ + "chainx-primitives", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-transaction-payment", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-finality-tracker" version = "2.0.0-rc4" @@ -3123,7 +3141,7 @@ source = "git+https://github.com/paritytech/substrate.git?tag=v2.0.0-rc4#00768a1 dependencies = [ "frame-support", "frame-system", - "pallet-balances", + "pallet-balances 2.0.0-rc4 (git+https://github.com/paritytech/substrate.git?tag=v2.0.0-rc4)", "parity-scale-codec", "serde", "sp-runtime", @@ -3195,6 +3213,55 @@ dependencies = [ "sp-timestamp", ] +[[package]] +name = "pallet-transaction-payment" +version = "2.0.0-rc4" +dependencies = [ + "frame-support", + "frame-system", + "pallet-balances 2.0.0-rc4", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "serde", + "smallvec 1.4.0", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-storage", + "xpallet-support", +] + +[[package]] +name = "pallet-transaction-payment-rpc" +version = "2.0.0-rc4" +dependencies = [ + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "pallet-transaction-payment-rpc-runtime-api", + "parity-scale-codec", + "serde", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-rpc", + "sp-runtime", +] + +[[package]] +name = "pallet-transaction-payment-rpc-runtime-api" +version = "2.0.0-rc4" +dependencies = [ + "frame-support", + "parity-scale-codec", + "serde", + "serde_json", + "sp-api", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-utility" version = "2.0.0-rc4" @@ -7022,11 +7089,14 @@ version = "0.1.0" dependencies = [ "bitmask", "chainx-primitives", + "env_logger", "frame-support", "frame-system", + "pallet-balances 2.0.0-rc4 (git+https://github.com/paritytech/substrate.git?tag=v2.0.0-rc4)", "parity-scale-codec", "serde", "sp-core", + "sp-io", "sp-runtime", "sp-std", "xpallet-protocol", @@ -7070,7 +7140,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "pallet-balances", + "pallet-balances 2.0.0-rc4 (git+https://github.com/paritytech/substrate.git?tag=v2.0.0-rc4)", "pallet-randomness-collective-flip", "pallet-timestamp", "parity-scale-codec", @@ -7141,6 +7211,7 @@ dependencies = [ "env_logger", "frame-support", "frame-system", + "pallet-balances 2.0.0-rc4 (git+https://github.com/paritytech/substrate.git?tag=v2.0.0-rc4)", "parity-scale-codec", "serde", "sp-arithmetic", @@ -7287,6 +7358,7 @@ dependencies = [ "env_logger", "frame-support", "frame-system", + "pallet-balances 2.0.0-rc4 (git+https://github.com/paritytech/substrate.git?tag=v2.0.0-rc4)", "pallet-session", "pallet-timestamp", "parity-scale-codec", @@ -7312,6 +7384,7 @@ dependencies = [ "env_logger", "frame-support", "frame-system", + "pallet-balances 2.0.0-rc4 (git+https://github.com/paritytech/substrate.git?tag=v2.0.0-rc4)", "pallet-session", "pallet-timestamp", "parity-scale-codec", @@ -7324,7 +7397,6 @@ dependencies = [ "sp-std", "xp-mining-common", "xp-mining-staking", - "xpallet-assets", "xpallet-protocol", "xpallet-support", ] @@ -7392,56 +7464,6 @@ dependencies = [ "xpallet-protocol", ] -[[package]] -name = "xpallet-transaction-payment" -version = "2.0.0-rc4" -dependencies = [ - "frame-support", - "frame-system", - "pallet-balances", - "parity-scale-codec", - "serde", - "smallvec 1.4.0", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", - "sp-storage", - "xpallet-assets", - "xpallet-support", - "xpallet-transaction-payment-rpc-runtime-api", -] - -[[package]] -name = "xpallet-transaction-payment-rpc" -version = "2.0.0-rc4" -dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "parity-scale-codec", - "serde", - "sp-api", - "sp-blockchain", - "sp-core", - "sp-rpc", - "sp-runtime", - "xpallet-transaction-payment-rpc-runtime-api", -] - -[[package]] -name = "xpallet-transaction-payment-rpc-runtime-api" -version = "2.0.0-rc4" -dependencies = [ - "frame-support", - "parity-scale-codec", - "serde", - "serde_json", - "sp-api", - "sp-runtime", - "sp-std", -] - [[package]] name = "yaml-rust" version = "0.4.4" diff --git a/Cargo.toml b/Cargo.toml index b77b9cc73a2c7..31ba3e9e58de9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,11 @@ members = [ "primitives/mining/staking", "rpc", + "frame/balances", + "frame/transaction-payment", + "frame/transaction-payment/rpc", + "frame/transaction-payment/rpc/runtime-api", + "xpallets/assets", "xpallets/assets/rpc", "xpallets/assets/rpc/runtime-api", @@ -37,9 +42,6 @@ members = [ "xpallets/mining/staking/rpc/runtime-api", "xpallets/protocol", "xpallets/support", - "xpallets/transaction-payment", - "xpallets/transaction-payment/rpc", - "xpallets/transaction-payment/rpc/runtime-api", ] [profile.release] diff --git a/cli/src/chain_spec.rs b/cli/src/chain_spec.rs index ee71208d9602a..ac50a6425b1aa 100644 --- a/cli/src/chain_spec.rs +++ b/cli/src/chain_spec.rs @@ -2,13 +2,13 @@ use std::collections::BTreeMap; use std::convert::TryFrom; use chainx_runtime::{ - constants, trustees, AssetInfo, AssetRestriction, AssetRestrictions, BtcNetwork, BtcParams, - BtcTxVerifier, Chain, ContractsSchedule, NetworkType, TrusteeInfoConfig, + constants, trustees, AssetInfo, AssetRestriction, AssetRestrictions, BtcParams, BtcTxVerifier, + Chain, ContractsSchedule, NetworkType, TrusteeInfoConfig, }; use chainx_runtime::{AccountId, AssetId, Balance, Runtime, Signature, WASM_BINARY}; use chainx_runtime::{ - AuraConfig, GenesisConfig, GrandpaConfig, ImOnlineConfig, SessionConfig, SessionKeys, - SudoConfig, SystemConfig, XAssetsConfig, XContractsConfig, XGatewayBitcoinConfig, + AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, ImOnlineConfig, SessionConfig, + SessionKeys, SudoConfig, SystemConfig, XAssetsConfig, XContractsConfig, XGatewayBitcoinConfig, XGatewayCommonConfig, XMiningAssetConfig, XSpotConfig, XStakingConfig, XSystemConfig, }; @@ -270,6 +270,29 @@ fn testnet_genesis( )>, enable_println: bool, ) -> GenesisConfig { + const ENDOWMENT: Balance = 10_000_000 * constants::currency::DOLLARS; + // const STASH: Balance = 100 * constants::currency::DOLLARS; + + let balances = endowed + .get(&xpallet_protocol::PCX) + .expect("PCX endowed; qed") + .iter() + .cloned() + .map(|(k, _)| (k, ENDOWMENT)) + .collect::>(); + + let validators = { + let staking_authorities = initial_authorities + .iter() + .map(|(s, _, _, _, _)| s) + .collect::>(); + balances + .clone() + .into_iter() + .filter(|(v, _)| staking_authorities.contains(&v)) + .collect() + }; + GenesisConfig { frame_system: Some(SystemConfig { code: WASM_BINARY.to_vec(), @@ -294,6 +317,7 @@ fn testnet_genesis( }) .collect::>(), }), + pallet_balances: Some(BalancesConfig { balances }), pallet_sudo: Some(SudoConfig { key: root_key }), xpallet_system: Some(XSystemConfig { network_props: NetworkType::Testnet, @@ -326,26 +350,7 @@ fn testnet_genesis( }) }, xpallet_mining_staking: Some(XStakingConfig { - validators: { - let pcx_endowed: std::collections::HashMap = endowed - .get(&xpallet_protocol::PCX) - .expect("PCX endowed; qed") - .iter() - .cloned() - .collect(); - initial_authorities - .iter() - .map(|x| { - ( - x.0.clone(), - pcx_endowed - .get(&x.0) - .expect("initial validators must have some balances; qed") - .clone(), - ) - }) - .collect() - }, + validators, validator_count: 1000, minimum_validator_count: 4, sessions_per_era: 12, diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml new file mode 100644 index 0000000000000..4ac8176a9b6bc --- /dev/null +++ b/frame/balances/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "pallet-balances" +version = "2.0.0-rc4" +authors = ["Parity Technologies "] +edition = "2018" +license = "Apache-2.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet to manage balances" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.101", optional = true } +codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] } +sp-std = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +frame-benchmarking = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false, optional = true } +frame-support = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } + +chainx-primitives = { path = "../../primitives", default-features = false } + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +pallet-transaction-payment = { path = "../transaction-payment", default-features = false } + +[features] +default = ["std"] +std = [ + "serde", + "codec/std", + "sp-std/std", + "sp-runtime/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + + "chainx-primitives/std", +] +runtime-benchmarks = ["frame-benchmarking"] diff --git a/frame/balances/src/benchmarking.rs b/frame/balances/src/benchmarking.rs new file mode 100644 index 0000000000000..98a4da8ece2eb --- /dev/null +++ b/frame/balances/src/benchmarking.rs @@ -0,0 +1,167 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 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 frame_benchmarking::{account, benchmarks}; +use frame_system::RawOrigin; +use sp_runtime::traits::Bounded; + +use crate::Module as Balances; + +const SEED: u32 = 0; +const MAX_EXISTENTIAL_DEPOSIT: u32 = 1000; +const MAX_USER_INDEX: u32 = 1000; + +benchmarks! { + _ { + let e in 2 .. MAX_EXISTENTIAL_DEPOSIT => (); + let u in 1 .. MAX_USER_INDEX => (); + } + + // Benchmark `transfer` extrinsic with the worst possible conditions: + // * Transfer will kill the sender account. + // * Transfer will create the recipient account. + transfer { + let u in ...; + let e in ...; + + let existential_deposit = T::ExistentialDeposit::get(); + let caller = account("caller", u, SEED); + + // Give some multiple of the existential deposit + creation fee + transfer fee + let balance = existential_deposit.saturating_mul(e.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", u, SEED); + let recipient_lookup: ::Source = T::Lookup::unlookup(recipient.clone()); + let transfer_amount = existential_deposit.saturating_mul((e - 1).into()) + 1.into(); + }: _(RawOrigin::Signed(caller), recipient_lookup, transfer_amount) + verify { + assert_eq!(Balances::::free_balance(&recipient), transfer_amount); + } + + // Benchmark `transfer` with the best possible condition: + // * Both accounts exist and will continue to exist. + transfer_best_case { + let u in ...; + let e in ...; + + let caller = account("caller", u, SEED); + let recipient: T::AccountId = account("recipient", u, SEED); + let recipient_lookup: ::Source = 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::ExistentialDeposit::get(); + let _ = as Currency<_>>::make_free_balance_be(&recipient, existential_deposit); + let transfer_amount = existential_deposit.saturating_mul(e.into()); + }: transfer(RawOrigin::Signed(caller), recipient_lookup, transfer_amount) + + // Benchmark `transfer_keep_alive` with the worst possible condition: + // * The recipient account is created. + transfer_keep_alive { + let u in ...; + let e in ...; + + let caller = account("caller", u, SEED); + let recipient = account("recipient", u, SEED); + let recipient_lookup: ::Source = T::Lookup::unlookup(recipient); + + // 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::ExistentialDeposit::get(); + let transfer_amount = existential_deposit.saturating_mul(e.into()); + }: _(RawOrigin::Signed(caller), recipient_lookup, transfer_amount) + + // Benchmark `set_balance` coming from ROOT account. This always creates an account. + set_balance { + let u in ...; + let e in ...; + + let user: T::AccountId = account("user", u, SEED); + let user_lookup: ::Source = T::Lookup::unlookup(user.clone()); + + // Give the user some initial balance. + let existential_deposit = T::ExistentialDeposit::get(); + let balance_amount = existential_deposit.saturating_mul(e.into()); + let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); + }: _(RawOrigin::Root, user_lookup, balance_amount, balance_amount) + + // Benchmark `set_balance` coming from ROOT account. This always kills an account. + set_balance_killing { + let u in ...; + let e in ...; + + let user: T::AccountId = account("user", u, SEED); + let user_lookup: ::Source = T::Lookup::unlookup(user.clone()); + + // Give the user some initial balance. + let existential_deposit = T::ExistentialDeposit::get(); + let balance_amount = existential_deposit.saturating_mul(e.into()); + let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); + }: set_balance(RawOrigin::Root, user_lookup, 0.into(), 0.into()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests_composite::{ExtBuilder, Test}; + use frame_support::assert_ok; + + #[test] + fn transfer() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(test_benchmark_transfer::()); + }); + } + + #[test] + fn transfer_best_case() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(test_benchmark_transfer_best_case::()); + }); + } + + #[test] + fn transfer_keep_alive() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(test_benchmark_transfer_keep_alive::()); + }); + } + + #[test] + fn transfer_set_balance() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(test_benchmark_set_balance::()); + }); + } + + #[test] + fn transfer_set_balance_killing() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(test_benchmark_set_balance_killing::()); + }); + } +} diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs new file mode 100644 index 0000000000000..e503008155ef2 --- /dev/null +++ b/frame/balances/src/lib.rs @@ -0,0 +1,1474 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2020 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 Module +//! +//! The Balances module provides functionality for handling accounts and balances. +//! +//! - [`balances::Trait`](./trait.Trait.html) +//! - [`Call`](./enum.Call.html) +//! - [`Module`](./struct.Module.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`](../frame_support/traits/trait.Currency.html): Functions for dealing with a +//! fungible assets system. +//! - [`ReservableCurrency`](../frame_support/traits/trait.ReservableCurrency.html): +//! Functions for dealing with assets that can be reserved from an account. +//! - [`LockableCurrency`](../frame_support/traits/trait.LockableCurrency.html): Functions for +//! dealing with accounts that allow liquidity restrictions. +//! - [`Imbalance`](../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`](../frame_system/trait.IsDeadAccount.html): Determiner to say whether a +//! given account is unused. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! - `transfer` - Transfer some liquid free balance to another account. +//! - `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`: +//! +//! ``` +//! use frame_support::traits::Currency; +//! # pub trait Trait: frame_system::Trait { +//! # type Currency: Currency; +//! # } +//! +//! pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +//! pub type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; +//! +//! # fn main() {} +//! ``` +//! +//! The Staking module uses the `LockableCurrency` trait to lock a stash account's funds: +//! +//! ``` +//! use frame_support::traits::{WithdrawReasons, LockableCurrency}; +//! use sp_runtime::traits::Bounded; +//! pub trait Trait: frame_system::Trait { +//! 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 module depends on the [`GenesisConfig`](./struct.GenesisConfig.html). +//! +//! ## Assumptions +//! +//! * Total issued balanced of all accounts should be less than `Trait::Balance::max_value()`. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[macro_use] +mod tests; +mod benchmarking; +mod tests_composite; +mod tests_local; + +use codec::{Codec, Decode, Encode}; +use frame_support::{ + decl_error, decl_event, decl_module, decl_storage, ensure, + traits::{ + BalanceStatus as Status, Currency, ExistenceRequirement, ExistenceRequirement::AllowDeath, + ExistenceRequirement::KeepAlive, Get, Imbalance, IsDeadAccount, LockIdentifier, + LockableCurrency, OnKilledAccount, OnUnbalanced, ReservableCurrency, SignedImbalance, + StoredMap, TryDrop, WithdrawReason, WithdrawReasons, + }, + Parameter, StorageValue, +}; +use frame_system::{self as system, ensure_root, ensure_signed}; +use sp_runtime::{ + traits::{ + AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, + Saturating, StaticLookup, Zero, + }, + DispatchError, DispatchResult, RuntimeDebug, +}; +use sp_std::prelude::*; +use sp_std::{cmp, convert::Infallible, fmt::Debug, mem, ops::BitOr, result}; + +use chainx_primitives::Memo; + +pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; + +pub trait Subtrait: frame_system::Trait { + /// The balance of an account. + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + Copy + + MaybeSerializeDeserialize + + Debug; + + /// The minimum amount required to keep an account open. + type ExistentialDeposit: Get; + + /// The means of storing the balances of an account. + type AccountStore: StoredMap>; +} + +pub trait Trait: frame_system::Trait { + /// The balance of an account. + type Balance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + Copy + + MaybeSerializeDeserialize + + Debug; + + /// Handler for the unbalanced reduction when removing a dust account. + type DustRemoval: OnUnbalanced>; + + /// The overarching event type. + type Event: From> + Into<::Event>; + + /// The minimum amount required to keep an account open. + type ExistentialDeposit: Get; + + /// The means of storing the balances of an account. + type AccountStore: StoredMap>; +} + +impl, I: Instance> Subtrait for T { + type Balance = T::Balance; + type ExistentialDeposit = T::ExistentialDeposit; + type AccountStore = T::AccountStore; +} + +decl_event!( + pub enum Event where + ::AccountId, + >::Balance + { + /// An account was created with some free balance. + Endowed(AccountId, Balance), + /// An account was removed whose balance was non-zero but below ExistentialDeposit, + /// resulting in an outright loss. + DustLost(AccountId, Balance), + /// Transfer succeeded (from, to, value). + Transfer(AccountId, AccountId, Balance), + /// A balance was set by root (who, free, reserved). + BalanceSet(AccountId, Balance, Balance), + /// Some amount was deposited (e.g. for transaction fees). + Deposit(AccountId, Balance), + /// Some balance was reserved (moved from free to reserved). + Reserved(AccountId, Balance), + /// Some balance was unreserved (moved from reserved to free). + Unreserved(AccountId, Balance), + /// Some balance was moved from the reserve of the first account to the second account. + /// Final argument indicates the destination balance type. + ReserveRepatriated(AccountId, AccountId, Balance, Status), + } +); + +decl_error! { + pub enum Error for Module, I: Instance> { + /// Vesting balance too high to send value + VestingBalance, + /// Account liquidity restrictions prevent withdrawal + LiquidityRestrictions, + /// Got an overflow after adding + Overflow, + /// Balance too low to send value + InsufficientBalance, + /// Value too low to create account due to existential deposit + ExistentialDeposit, + /// Transfer/payment would kill account + KeepAlive, + /// A vesting schedule already exists for this account + ExistingVestingSchedule, + /// Beneficiary account must pre-exist + DeadAccount, + } +} + +/// Simplified reasons for withdrawing balance. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)] +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::from(WithdrawReason::TransactionPayment) { + Reasons::Fee + } else if r.contains(WithdrawReason::TransactionPayment) { + 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)] +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, +} + +/// All balance information for an account. +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug)] +pub struct AccountData { + /// Non-reserved part of the balance. There may still be restrictions on this, but it is the + /// total pool what may in principle be transferred, reserved and used for tipping. + /// + /// This is the only balance that matters in terms of most operations on tokens. It + /// alone is used to determine the balance when in the contract execution environment. + pub free: Balance, + /// Balance which is reserved and may not be used at all. + /// + /// This can still get slashed, but gets slashed last of all. + /// + /// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens + /// that are still 'owned' by the account holder, but which are suspendable. + pub reserved: Balance, + /// The amount that `free` may not drop below when withdrawing for *anything except transaction + /// fee payment*. + pub misc_frozen: Balance, + /// The amount that `free` may not drop below when withdrawing specifically for transaction + /// fee payment. + pub fee_frozen: Balance, +} + +impl AccountData { + /// How much this account's balance can be reduced for the given `reasons`. + fn usable(&self, reasons: Reasons) -> Balance { + self.free.saturating_sub(self.frozen(reasons)) + } + /// The amount that this account's free balance may not be reduced beyond for the given + /// `reasons`. + fn frozen(&self, reasons: Reasons) -> Balance { + match reasons { + Reasons::All => self.misc_frozen.max(self.fee_frozen), + Reasons::Misc => self.misc_frozen, + Reasons::Fee => self.fee_frozen, + } + } + /// The total balance in this account including any that is reserved and ignoring any frozen. + fn total(&self) -> Balance { + self.free.saturating_add(self.reserved) + } +} + +// A value placed in storage that represents the current version of the Balances storage. +// This value is used by the `on_runtime_upgrade` logic to determine whether we run +// storage migration logic. This should match directly with the semantic versions of the Rust crate. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)] +enum Releases { + V1_0_0, + V2_0_0, +} + +impl Default for Releases { + fn default() -> Self { + Releases::V1_0_0 + } +} + +decl_storage! { + trait Store for Module, I: Instance=DefaultInstance> as Balances { + /// The total units issued in the system. + pub TotalIssuance get(fn total_issuance) build(|config: &GenesisConfig| { + config.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n) + }): T::Balance; + + /// The balance of an account. + /// + /// NOTE: This is only used in the case that this module is used to store balances. + pub Account: map hasher(blake2_128_concat) T::AccountId => AccountData; + + /// Any liquidity locks on some account balances. + /// NOTE: Should only be accessed when setting, changing and freeing a lock. + pub Locks get(fn locks): map hasher(blake2_128_concat) T::AccountId => Vec>; + + /// Storage version of the pallet. + /// + /// This is set to v2.0.0 for new networks. + StorageVersion build(|_: &GenesisConfig| Releases::V2_0_0): Releases; + } + add_extra_genesis { + config(balances): Vec<(T::AccountId, T::Balance)>; + // ^^ begin, length, amount liquid at genesis + build(|config: &GenesisConfig| { + for (_, balance) in &config.balances { + assert!( + *balance >= >::ExistentialDeposit::get(), + "the balance of any account should always be more than existential deposit.", + ) + } + for &(ref who, free) in config.balances.iter() { + T::AccountStore::insert(who, AccountData { free, .. Default::default() }); + } + }); + } +} + +decl_module! { + pub struct Module, I: Instance = DefaultInstance> for enum Call where origin: T::Origin { + type Error = Error; + + /// The minimum amount required to keep an account open. + const ExistentialDeposit: T::Balance = T::ExistentialDeposit::get(); + + fn deposit_event() = default; + + /// Transfer some liquid free balance to another account. + /// + /// `transfer` will set the `FreeBalance` of the sender and receiver. + /// It will decrease the total issuance of the system by the `TransferFee`. + /// 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. + /// + /// # + /// - Dependent on arguments but not critical, given proper implementations for + /// input config types. See related functions below. + /// - It contains a limited number of reads and writes internally and no complex computation. + /// + /// Related functions: + /// + /// - `ensure_can_withdraw` is always called internally but has a bounded complexity. + /// - Transferring balances to accounts that did not exist before will cause + /// `T::OnNewAccount::on_new_account` to be called. + /// - Removing enough funds from an account will trigger `T::DustRemoval::on_unbalanced`. + /// - `transfer_keep_alive` works the same way as `transfer`, but has an additional + /// check that the transfer will not kill the origin account. + /// --------------------------------- + /// - Base Weight: 73.64 µs, worst case scenario (account created, account removed) + /// - DB Weight: 1 Read and 1 Write to destination account + /// - Origin account is already in memory, so no DB operations for them. + /// # + #[weight = T::DbWeight::get().reads_writes(1, 1) + 70_000_000] + pub fn transfer( + origin, + dest: ::Source, + #[compact] value: T::Balance + ) { + let transactor = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::transfer(&transactor, &dest, value, ExistenceRequirement::AllowDeath)?; + } + + /// Set the balances of a given account. + /// + /// This will alter `FreeBalance` and `ReservedBalance` in storage. it will + /// also decrease the total issuance of the system (`TotalIssuance`). + /// If the new free or reserved balance is below the existential deposit, + /// it will reset the account nonce (`frame_system::AccountNonce`). + /// + /// The dispatch origin for this call is `root`. + /// + /// # + /// - Independent of the arguments. + /// - Contains a limited number of reads and writes. + /// --------------------- + /// - Base Weight: + /// - Creating: 27.56 µs + /// - Killing: 35.11 µs + /// - DB Weight: 1 Read, 1 Write to `who` + /// # + #[weight = T::DbWeight::get().reads_writes(1, 1) + 35_000_000] + fn set_balance( + origin, + who: ::Source, + #[compact] new_free: T::Balance, + #[compact] new_reserved: T::Balance + ) { + ensure_root(origin)?; + let who = T::Lookup::lookup(who)?; + let existential_deposit = T::ExistentialDeposit::get(); + + let wipeout = new_free + new_reserved < existential_deposit; + let new_free = if wipeout { Zero::zero() } else { new_free }; + let new_reserved = if wipeout { Zero::zero() } else { new_reserved }; + + let (free, reserved) = Self::mutate_account(&who, |account| { + if new_free > account.free { + mem::drop(PositiveImbalance::::new(new_free - account.free)); + } else if new_free < account.free { + mem::drop(NegativeImbalance::::new(account.free - new_free)); + } + + if new_reserved > account.reserved { + mem::drop(PositiveImbalance::::new(new_reserved - account.reserved)); + } else if new_reserved < account.reserved { + mem::drop(NegativeImbalance::::new(account.reserved - new_reserved)); + } + + account.free = new_free; + account.reserved = new_reserved; + + (account.free, account.reserved) + }); + Self::deposit_event(RawEvent::BalanceSet(who, free, reserved)); + } + + /// Exactly as `transfer`, except the origin must be root and the source account may be + /// specified. + /// # + /// - Same as transfer, but additional read and write because the source account is + /// not assumed to be in the overlay. + /// # + #[weight = T::DbWeight::get().reads_writes(2, 2) + 70_000_000] + pub fn force_transfer( + origin, + source: ::Source, + dest: ::Source, + #[compact] value: T::Balance + ) { + ensure_root(origin)?; + let source = T::Lookup::lookup(source)?; + let dest = T::Lookup::lookup(dest)?; + >::transfer(&source, &dest, value, ExistenceRequirement::AllowDeath)?; + } + + /// Same as the [`transfer`] call, but with a check that the transfer will not kill the + /// origin account. + /// + /// 99% of the time you want [`transfer`] instead. + /// + /// [`transfer`]: struct.Module.html#method.transfer + /// # + /// - Cheaper than transfer because account cannot be killed. + /// - Base Weight: 51.4 µs + /// - DB Weight: 1 Read and 1 Write to dest (sender is in overlay already) + /// # + #[weight = T::DbWeight::get().reads_writes(1, 1) + 50_000_000] + pub fn transfer_keep_alive( + origin, + dest: ::Source, + #[compact] value: T::Balance + ) { + let transactor = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::transfer(&transactor, &dest, value, KeepAlive)?; + } + + #[weight = T::DbWeight::get().reads_writes(1, 1) + 70_000_000] + pub fn transfer_with_memo( + origin, + dest: ::Source, + #[compact] value: T::Balance, + memo: Memo, + ) { + let transactor = ensure_signed(origin)?; + memo.check_validity()?; + Self::transfer(frame_system::RawOrigin::Signed(transactor).into(), dest, value.into())?; + } + } +} + +impl, I: Instance> Module { + // PRIVATE MUTABLES + + /// Get the free balance of an account. + pub fn free_balance(who: impl sp_std::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 sp_std::borrow::Borrow) -> T::Balance { + Self::account(who.borrow()).usable(Reasons::Misc) + } + + /// 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`. + pub fn usable_balance_for_fees(who: impl sp_std::borrow::Borrow) -> T::Balance { + Self::account(who.borrow()).usable(Reasons::Fee) + } + + /// Get the reserved balance of an account. + pub fn reserved_balance(who: impl sp_std::borrow::Borrow) -> T::Balance { + Self::account(who.borrow()).reserved + } + + /// Get both the free and reserved balances of an account. + fn account(who: &T::AccountId) -> AccountData { + T::AccountStore::get(&who) + } + + /// Places the `free` and `reserved` parts of `new` into `account`. Also does 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. + /// + /// Returns the final free balance, iff the account was previously of total balance zero, known + /// as its "endowment". + fn post_mutation( + who: &T::AccountId, + new: AccountData, + ) -> Option> { + let total = new.total(); + if total < T::ExistentialDeposit::get() { + if !total.is_zero() { + T::DustRemoval::on_unbalanced(NegativeImbalance::new(total)); + Self::deposit_event(RawEvent::DustLost(who.clone(), total)); + } + None + } else { + Some(new) + } + } + + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// 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 fn mutate_account( + who: &T::AccountId, + f: impl FnOnce(&mut AccountData) -> R, + ) -> R { + Self::try_mutate_account(who, |a, _| -> Result { Ok(f(a)) }) + .expect("Error is infallible; qed") + } + + /// 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`. + /// + /// 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. + fn try_mutate_account( + who: &T::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> 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(); + f(&mut account, is_new).map(move |result| { + let maybe_endowed = if is_new { Some(account.free) } else { None }; + *maybe_account = Self::post_mutation(who, account); + (maybe_endowed, result) + }) + }) + .map(|(maybe_endowed, result)| { + if let Some(endowed) = maybe_endowed { + Self::deposit_event(RawEvent::Endowed(who.clone(), endowed)); + } + result + }) + } + + /// Update the account entry for `who`, given the locks. + fn update_locks(who: &T::AccountId, locks: &[BalanceLock]) { + Self::mutate_account(who, |b| { + b.misc_frozen = Zero::zero(); + b.fee_frozen = Zero::zero(); + for l in locks.iter() { + if l.reasons == Reasons::All || l.reasons == Reasons::Misc { + b.misc_frozen = b.misc_frozen.max(l.amount); + } + if l.reasons == Reasons::All || l.reasons == Reasons::Fee { + b.fee_frozen = b.fee_frozen.max(l.amount); + } + } + }); + + let existed = Locks::::contains_key(who); + if locks.is_empty() { + Locks::::remove(who); + if existed { + // TODO: use Locks::::hashed_key + // https://github.com/paritytech/substrate/issues/4969 + system::Module::::dec_ref(who); + } + } else { + Locks::::insert(who, locks); + if !existed { + system::Module::::inc_ref(who); + } + } + } +} + +// wrapping these imbalances in a private module is necessary to ensure absolute privacy +// of the inner member. +mod imbalances { + use super::{ + result, DefaultInstance, Imbalance, Instance, Saturating, StorageValue, Subtrait, Trait, + TryDrop, Zero, + }; + use sp_std::mem; + + /// 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] + pub struct PositiveImbalance, I: Instance = DefaultInstance>(T::Balance); + + impl, I: Instance> 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] + pub struct NegativeImbalance, I: Instance = DefaultInstance>(T::Balance); + + impl, I: Instance> NegativeImbalance { + /// Create a new negative imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + NegativeImbalance(amount) + } + } + + impl, I: Instance> TryDrop for PositiveImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } + } + + impl, I: Instance> 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 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) -> result::Result { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + if a >= b { + Ok(Self(a - b)) + } else { + Err(NegativeImbalance::new(b - a)) + } + } + fn peek(&self) -> T::Balance { + self.0.clone() + } + } + + impl, I: Instance> TryDrop for NegativeImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } + } + + impl, I: Instance> 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 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) -> result::Result { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + if a >= b { + Ok(Self(a - b)) + } else { + Err(PositiveImbalance::new(b - a)) + } + } + fn peek(&self) -> T::Balance { + self.0.clone() + } + } + + impl, I: Instance> Drop for PositiveImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + , I>>::mutate(|v| { + *v = v.saturating_add(self.0) + }); + } + } + + impl, I: Instance> Drop for NegativeImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + , I>>::mutate(|v| { + *v = v.saturating_sub(self.0) + }); + } + } +} + +// TODO: #2052 +// Somewhat ugly hack in order to gain access to module's `increase_total_issuance_by` +// using only the Subtrait (which defines only the types that are not dependent +// on Positive/NegativeImbalance). Subtrait must be used otherwise we end up with a +// circular dependency with Trait having some types be dependent on PositiveImbalance +// and PositiveImbalance itself depending back on Trait for its Drop impl (and thus +// its type declaration). +// This works as long as `increase_total_issuance_by` doesn't use the Imbalance +// types (basically for charging fees). +// This should eventually be refactored so that the type item that +// depends on the Imbalance type (DustRemoval) is placed in its own pallet. +struct ElevatedTrait, I: Instance>(T, I); +impl, I: Instance> Clone for ElevatedTrait { + fn clone(&self) -> Self { + unimplemented!() + } +} +impl, I: Instance> PartialEq for ElevatedTrait { + fn eq(&self, _: &Self) -> bool { + unimplemented!() + } +} +impl, I: Instance> Eq for ElevatedTrait {} +impl, I: Instance> frame_system::Trait for ElevatedTrait { + type BaseCallFilter = T::BaseCallFilter; + type Origin = T::Origin; + type Call = T::Call; + type Index = T::Index; + type BlockNumber = T::BlockNumber; + type Hash = T::Hash; + type Hashing = T::Hashing; + type AccountId = T::AccountId; + type Lookup = T::Lookup; + type Header = T::Header; + type Event = (); + type BlockHashCount = T::BlockHashCount; + type MaximumBlockWeight = T::MaximumBlockWeight; + type DbWeight = T::DbWeight; + type BlockExecutionWeight = T::BlockExecutionWeight; + type ExtrinsicBaseWeight = T::ExtrinsicBaseWeight; + type MaximumExtrinsicWeight = T::MaximumBlockWeight; + type MaximumBlockLength = T::MaximumBlockLength; + type AvailableBlockRatio = T::AvailableBlockRatio; + type Version = T::Version; + type ModuleToIndex = T::ModuleToIndex; + type OnNewAccount = T::OnNewAccount; + type OnKilledAccount = T::OnKilledAccount; + type AccountData = T::AccountData; +} +impl, I: Instance> Trait for ElevatedTrait { + type Balance = T::Balance; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = T::ExistentialDeposit; + type AccountStore = T::AccountStore; +} + +impl, I: Instance> Currency for Module +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + 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 { + >::get() + } + + 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() + }); + }); + 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() + }) + }); + 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. + // + // # + // Despite iterating over a list of locks, they are limited by the number of + // lock IDs, which means the number of runtime modules that intend to use and create locks. + // # + fn ensure_can_withdraw( + who: &T::AccountId, + amount: T::Balance, + reasons: WithdrawReasons, + new_balance: T::Balance, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()); + } + let min_balance = Self::account(who).frozen(reasons.into()); + ensure!( + new_balance >= min_balance, + 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(()); + } + + Self::try_mutate_account(dest, |to_account, _| -> DispatchResult { + Self::try_mutate_account(transactor, |from_account, _| -> DispatchResult { + from_account.free = from_account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + + // NOTE: total stake being stored in the same type means that this could never overflow + // but better to be safe than sorry. + to_account.free = to_account + .free + .checked_add(&value) + .ok_or(Error::::Overflow)?; + + let ed = T::ExistentialDeposit::get(); + ensure!(to_account.total() >= ed, Error::::ExistentialDeposit); + + Self::ensure_can_withdraw( + transactor, + value, + WithdrawReason::Transfer.into(), + from_account.free, + )?; + + let allow_death = existence_requirement == ExistenceRequirement::AllowDeath; + let allow_death = allow_death && system::Module::::allow_death(transactor); + ensure!( + allow_death || from_account.free >= ed, + Error::::KeepAlive + ); + + Ok(()) + }) + })?; + + // Emit transfer event. + Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value)); + + 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. + /// + /// 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()); + } + + Self::mutate_account(who, |account| { + let free_slash = cmp::min(account.free, value); + account.free -= free_slash; + + let remaining_slash = value - free_slash; + if !remaining_slash.is_zero() { + let reserved_slash = cmp::min(account.reserved, remaining_slash); + account.reserved -= reserved_slash; + ( + NegativeImbalance::new(free_slash + reserved_slash), + remaining_slash - reserved_slash, + ) + } else { + (NegativeImbalance::new(value), Zero::zero()) + } + }) + } + + /// 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( + who, + |account, is_new| -> Result { + ensure!(!is_new, Error::::DeadAccount); + account.free = account + .free + .checked_add(&value) + .ok_or(Error::::Overflow)?; + 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 + /// - if the `value` to be deposited is less than the ED and the account does not yet exist; 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( + who, + |account, is_new| -> Result { + // bail if not yet created and this operation wouldn't be enough to create it. + let ed = T::ExistentialDeposit::get(); + ensure!(value >= ed || !is_new, Self::PositiveImbalance::zero()); + + // defensive only: overflow should never happen, however in case it does, then this + // operation is a no-op. + account.free = account + .free + .checked_add(&value) + .ok_or(Self::PositiveImbalance::zero())?; + + Ok(PositiveImbalance::new(value)) + }, + ) + .unwrap_or_else(|x| x) + } + + /// 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( + 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 + account.reserved < ed; + let would_kill = would_be_dead && account.free + account.reserved >= ed; + ensure!( + liveness == AllowDeath || !would_kill, + Error::::KeepAlive + ); + + Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; + + account.free = new_free_account; + + 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( + who, + |account, + is_new| + -> Result, ()> { + 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.saturating_add(account.reserved) >= ed || !is_new, ()); + + let imbalance = if account.free <= value { + SignedImbalance::Positive(PositiveImbalance::new(value - account.free)) + } else { + SignedImbalance::Negative(NegativeImbalance::new(account.free - value)) + }; + account.free = value; + Ok(imbalance) + }, + ) + .unwrap_or(SignedImbalance::Positive(Self::PositiveImbalance::zero())) + } +} + +impl, I: Instance> ReservableCurrency for Module +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + /// 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) + .map_or(false, |new_balance| { + Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve.into(), 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(who, |account, _| -> DispatchResult { + account.free = account + .free + .checked_sub(&value) + .ok_or(Error::::InsufficientBalance)?; + account.reserved = account + .reserved + .checked_add(&value) + .ok_or(Error::::Overflow)?; + Self::ensure_can_withdraw( + &who, + value.clone(), + WithdrawReason::Reserve.into(), + account.free, + ) + })?; + + Self::deposit_event(RawEvent::Reserved(who.clone(), 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(who: &T::AccountId, value: Self::Balance) -> Self::Balance { + if value.is_zero() { + return Zero::zero(); + } + + let actual = Self::mutate_account(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.saturating_add(actual); + actual + }); + + Self::deposit_event(RawEvent::Unreserved(who.clone(), actual.clone())); + 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. + fn slash_reserved( + who: &T::AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()); + } + + Self::mutate_account(who, |account| { + // underflow should never happen, but it if does, there's nothing to be done here. + let actual = cmp::min(account.reserved, value); + account.reserved -= actual; + (NegativeImbalance::new(actual), value - actual) + }) + } + + /// 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`. + fn repatriate_reserved( + 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(slashed, value)), + Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))), + }; + } + + let actual = Self::try_mutate_account( + beneficiary, + |to_account, is_new| -> Result { + ensure!(!is_new, Error::::DeadAccount); + Self::try_mutate_account( + slashed, + |from_account, _| -> Result { + let actual = cmp::min(from_account.reserved, value); + match status { + Status::Free => { + to_account.free = to_account + .free + .checked_add(&actual) + .ok_or(Error::::Overflow)? + } + Status::Reserved => { + to_account.reserved = to_account + .reserved + .checked_add(&actual) + .ok_or(Error::::Overflow)? + } + } + from_account.reserved -= actual; + Ok(actual) + }, + ) + }, + )?; + + Self::deposit_event(RawEvent::ReserveRepatriated( + slashed.clone(), + beneficiary.clone(), + actual, + status, + )); + Ok(value - actual) + } +} + +/// Implement `OnKilledAccount` to remove the local account, if using local account storage. +/// +/// NOTE: You probably won't need to use this! This only needs to be "wired in" to System module +/// if you're using the local balance storage. **If you're using the composite system account +/// storage (which is the default in most examples and tests) then there's no need.** +impl, I: Instance> OnKilledAccount for Module { + fn on_killed_account(who: &T::AccountId) { + Account::::mutate_exists(who, |account| { + let total = account.as_ref().map(|acc| acc.total()).unwrap_or_default(); + if !total.is_zero() { + T::DustRemoval::on_unbalanced(NegativeImbalance::new(total)); + Self::deposit_event(RawEvent::DustLost(who.clone(), total)); + } + *account = None; + }); + } +} + +impl, I: Instance> LockableCurrency for Module +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + type Moment = T::BlockNumber; + + // Set a lock on the balance of `who`. + // Is a no-op if lock amount is zero or `reasons` `is_none()`. + fn set_lock( + id: LockIdentifier, + who: &T::AccountId, + amount: T::Balance, + reasons: WithdrawReasons, + ) { + if amount.is_zero() || reasons.is_none() { + 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_none() { + 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: Instance> IsDeadAccount for Module +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + fn is_dead_account(who: &T::AccountId) -> bool { + // this should always be exactly equivalent to `Self::account(who).total().is_zero()` if ExistentialDeposit > 0 + !T::AccountStore::is_explicit(who) + } +} diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs new file mode 100644 index 0000000000000..62e3dc95788da --- /dev/null +++ b/frame/balances/src/tests.rs @@ -0,0 +1,790 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2020 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. + +//! Macro for creating the tests for the module. + +#![cfg(test)] + +#[derive(Debug)] +pub struct CallWithDispatchInfo; +impl sp_runtime::traits::Dispatchable for CallWithDispatchInfo { + type Origin = (); + type Trait = (); + type Info = frame_support::weights::DispatchInfo; + type PostInfo = frame_support::weights::PostDispatchInfo; + + fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo { + panic!("Do not use dummy implementation for dispatch."); + } +} + +#[macro_export] +macro_rules! decl_tests { + ($test:ty, $ext_builder:ty, $existential_deposit:expr) => { + + use crate::*; + use sp_runtime::{FixedPointNumber, traits::{SignedExtension, BadOrigin}}; + use frame_support::{ + assert_noop, assert_ok, assert_err, + traits::{ + LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons, + Currency, ReservableCurrency, ExistenceRequirement::AllowDeath, StoredMap + } + }; + use pallet_transaction_payment::{ChargeTransactionPayment, Multiplier}; + use frame_system::RawOrigin; + + const ID_1: LockIdentifier = *b"1 "; + const ID_2: LockIdentifier = *b"2 "; + + pub type System = frame_system::Module<$test>; + pub type Balances = Module<$test>; + + pub const CALL: &<$test as frame_system::Trait>::Call = &$crate::tests::CallWithDispatchInfo; + + /// create a transaction info struct from weight. Handy to avoid building the whole struct. + pub fn info_from_weight(w: Weight) -> DispatchInfo { + DispatchInfo { weight: w, ..Default::default() } + } + + fn events() -> Vec { + let evt = System::events().into_iter().map(|evt| evt.event).collect::>(); + + System::reset_events(); + + evt + } + + fn last_event() -> Event { + system::Module::::events().pop().expect("Event expected").event + } + + #[test] + fn basic_locking_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + Balances::set_lock(ID_1, &1, 9, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 5, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + }); + } + + #[test] + fn account_should_be_reaped() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); + assert!(!<::AccountStore as StoredMap>>::is_explicit(&1)); + }); + } + + #[test] + fn partial_locking_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); + } + + #[test] + fn lock_removal_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), WithdrawReasons::all()); + Balances::remove_lock(ID_1, &1); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); + } + + #[test] + fn lock_replacement_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); + } + + #[test] + fn double_locking_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + Balances::set_lock(ID_2, &1, 5, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); + } + + #[test] + fn combination_locking_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), WithdrawReasons::none()); + Balances::set_lock(ID_2, &1, 0, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); + } + + #[test] + fn lock_value_extension_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + Balances::extend_lock(ID_1, &1, 2, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + Balances::extend_lock(ID_1, &1, 8, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + }); + } + + #[test] + fn lock_reasons_should_work() { + <$ext_builder>::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + pallet_transaction_payment::NextFeeMultiplier::put(Multiplier::saturating_from_integer(1)); + Balances::set_lock(ID_1, &1, 10, WithdrawReason::Reserve.into()); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + assert_noop!( + >::reserve(&1, 1), + Error::<$test, _>::LiquidityRestrictions, + ); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(1), + &1, + CALL, + &info_from_weight(1), + 1, + ).is_err()); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(0), + &1, + CALL, + &info_from_weight(1), + 1, + ).is_ok()); + + Balances::set_lock(ID_1, &1, 10, WithdrawReason::TransactionPayment.into()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + assert_ok!(>::reserve(&1, 1)); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(1), + &1, + CALL, + &info_from_weight(1), + 1, + ).is_err()); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(0), + &1, + CALL, + &info_from_weight(1), + 1, + ).is_err()); + }); + } + + #[test] + fn lock_block_number_extension_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + System::set_block_number(2); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + }); + } + + #[test] + fn lock_reasons_extension_should_work() { + <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReason::Transfer.into()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::none()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReason::Reserve.into()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + Error::<$test, _>::LiquidityRestrictions + ); + }); + } + + #[test] + fn default_indexing_on_new_accounts_should_not_work2() { + <$ext_builder>::default() + .existential_deposit(10) + .monied(true) + .build() + .execute_with(|| { + assert_eq!(Balances::is_dead_account(&5), true); + // account 5 should not exist + // ext_deposit is 10, value is 9, not satisfies for ext_deposit + assert_noop!( + Balances::transfer(Some(1).into(), 5, 9), + Error::<$test, _>::ExistentialDeposit, + ); + assert_eq!(Balances::is_dead_account(&5), true); // account 5 should not exist + assert_eq!(Balances::free_balance(1), 100); + }); + } + + #[test] + fn reserved_balance_should_prevent_reclaim_count() { + <$ext_builder>::default() + .existential_deposit(256 * 1) + .monied(true) + .build() + .execute_with(|| { + System::inc_account_nonce(&2); + assert_eq!(Balances::is_dead_account(&2), false); + assert_eq!(Balances::is_dead_account(&5), true); + assert_eq!(Balances::total_balance(&2), 256 * 20); + + assert_ok!(Balances::reserve(&2, 256 * 19 + 1)); // account 2 becomes mostly reserved + assert_eq!(Balances::free_balance(2), 255); // "free" account deleted." + assert_eq!(Balances::total_balance(&2), 256 * 20); // reserve still exists. + assert_eq!(Balances::is_dead_account(&2), false); + assert_eq!(System::account_nonce(&2), 1); + + // account 4 tries to take index 1 for account 5. + assert_ok!(Balances::transfer(Some(4).into(), 5, 256 * 1 + 0x69)); + assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69); + assert_eq!(Balances::is_dead_account(&5), false); + + assert!(Balances::slash(&2, 256 * 19 + 2).1.is_zero()); // account 2 gets slashed + // "reserve" account reduced to 255 (below ED) so account deleted + assert_eq!(Balances::total_balance(&2), 0); + assert_eq!(System::account_nonce(&2), 0); // nonce zero + assert_eq!(Balances::is_dead_account(&2), true); + + // account 4 tries to take index 1 again for account 6. + assert_ok!(Balances::transfer(Some(4).into(), 6, 256 * 1 + 0x69)); + assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69); + assert_eq!(Balances::is_dead_account(&6), false); + }); + } + + #[test] + fn reward_should_work() { + <$ext_builder>::default().monied(true).build().execute_with(|| { + assert_eq!(Balances::total_balance(&1), 10); + assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop)); + assert_eq!(Balances::total_balance(&1), 20); + assert_eq!(>::get(), 120); + }); + } + + #[test] + fn dust_account_removal_should_work() { + <$ext_builder>::default() + .existential_deposit(100) + .monied(true) + .build() + .execute_with(|| { + System::inc_account_nonce(&2); + assert_eq!(System::account_nonce(&2), 1); + assert_eq!(Balances::total_balance(&2), 2000); + // index 1 (account 2) becomes zombie + assert_ok!(Balances::transfer(Some(2).into(), 5, 1901)); + assert_eq!(Balances::total_balance(&2), 0); + assert_eq!(Balances::total_balance(&5), 1901); + assert_eq!(System::account_nonce(&2), 0); + }); + } + + #[test] + fn balance_works() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + assert_eq!(Balances::free_balance(1), 42); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::total_balance(&2), 0); + }); + } + + #[test] + fn balance_transfer_works() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::transfer(Some(1).into(), 2, 69)); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::total_balance(&2), 69); + }); + } + + #[test] + fn force_transfer_works() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_noop!( + Balances::force_transfer(Some(2).into(), 1, 2, 69), + BadOrigin, + ); + assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69)); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::total_balance(&2), 69); + }); + } + + #[test] + fn reserving_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 111); + assert_eq!(Balances::reserved_balance(1), 0); + + assert_ok!(Balances::reserve(&1, 69)); + + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 42); + assert_eq!(Balances::reserved_balance(1), 69); + }); + } + + #[test] + fn balance_transfer_when_reserved_should_not_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 69)); + assert_noop!( + Balances::transfer(Some(1).into(), 2, 69), + Error::<$test, _>::InsufficientBalance, + ); + }); + } + + #[test] + fn deducting_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 69)); + assert_eq!(Balances::free_balance(1), 42); + }); + } + + #[test] + fn refunding_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + Balances::mutate_account(&1, |a| a.reserved = 69); + Balances::unreserve(&1, 69); + assert_eq!(Balances::free_balance(1), 111); + assert_eq!(Balances::reserved_balance(1), 0); + }); + } + + #[test] + fn slashing_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 69)); + assert!(Balances::slash(&1, 69).1.is_zero()); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::reserved_balance(1), 42); + assert_eq!(>::get(), 42); + }); + } + + #[test] + fn slashing_incomplete_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + assert_ok!(Balances::reserve(&1, 21)); + assert_eq!(Balances::slash(&1, 69).1, 27); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(>::get(), 0); + }); + } + + #[test] + fn unreserving_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 111)); + Balances::unreserve(&1, 42); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 42); + }); + } + + #[test] + fn slashing_reserved_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 111)); + assert_eq!(Balances::slash_reserved(&1, 42).1, 0); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(>::get(), 69); + }); + } + + #[test] + fn slashing_incomplete_reserved_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 42)); + assert_eq!(Balances::slash_reserved(&1, 69).1, 27); + assert_eq!(Balances::free_balance(1), 69); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(>::get(), 69); + }); + } + + #[test] + fn repatriating_reserved_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 110)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Status::Free), 0); + assert_eq!( + last_event(), + Event::balances(RawEvent::ReserveRepatriated(1, 2, 41, Status::Free)), + ); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 42); + }); + } + + #[test] + fn transferring_reserved_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 110)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Status::Reserved), 0); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::reserved_balance(2), 41); + assert_eq!(Balances::free_balance(2), 1); + }); + } + + #[test] + fn transferring_reserved_balance_to_nonexistent_should_fail() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 111)); + assert_noop!(Balances::repatriate_reserved(&1, &2, 42, Status::Free), Error::<$test, _>::DeadAccount); + }); + } + + #[test] + fn transferring_incomplete_reserved_balance_should_work() { + <$ext_builder>::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 41)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 69, Status::Free), 28); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 69); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 42); + }); + } + + #[test] + fn transferring_too_high_value_should_not_panic() { + <$ext_builder>::default().build().execute_with(|| { + Balances::make_free_balance_be(&1, u64::max_value()); + Balances::make_free_balance_be(&2, 1); + + assert_err!( + Balances::transfer(Some(1).into(), 2, u64::max_value()), + Error::<$test, _>::Overflow, + ); + + assert_eq!(Balances::free_balance(1), u64::max_value()); + assert_eq!(Balances::free_balance(2), 1); + }); + } + + #[test] + fn account_create_on_free_too_low_with_other() { + <$ext_builder>::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + assert_eq!(>::get(), 100); + + // No-op. + let _ = Balances::deposit_creating(&2, 50); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(>::get(), 100); + }) + } + + #[test] + fn account_create_on_free_too_low() { + <$ext_builder>::default().existential_deposit(100).build().execute_with(|| { + // No-op. + let _ = Balances::deposit_creating(&2, 50); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(>::get(), 0); + }) + } + + #[test] + fn account_removal_on_free_too_low() { + <$ext_builder>::default().existential_deposit(100).build().execute_with(|| { + assert_eq!(>::get(), 0); + + // Setup two accounts with free balance above the existential threshold. + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 110); + + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::free_balance(2), 110); + assert_eq!(>::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(Some(1).into(), 2, 20)); + + // Verify free balance removal of account 1. + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 130); + + // Verify that TotalIssuance tracks balance removal when free balance is too low. + assert_eq!(>::get(), 130); + }); + } + + #[test] + fn burn_must_work() { + <$ext_builder>::default().monied(true).build().execute_with(|| { + let init_total_issuance = Balances::total_issuance(); + let imbalance = Balances::burn(10); + assert_eq!(Balances::total_issuance(), init_total_issuance - 10); + drop(imbalance); + assert_eq!(Balances::total_issuance(), init_total_issuance); + }); + } + + #[test] + fn transfer_keep_alive_works() { + <$ext_builder>::default().existential_deposit(1).build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + assert_noop!( + Balances::transfer_keep_alive(Some(1).into(), 2, 100), + Error::<$test, _>::KeepAlive + ); + assert_eq!(Balances::is_dead_account(&1), false); + assert_eq!(Balances::total_balance(&1), 100); + assert_eq!(Balances::total_balance(&2), 0); + }); + } + + #[test] + #[should_panic = "the balance of any account should always be more than 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::<$test>().unwrap(); + let _ = GenesisConfig::<$test> { + balances: vec![(1, 10)], + }.assimilate_storage(&mut t).unwrap(); + } + + #[test] + fn dust_moves_between_free_and_reserved() { + <$ext_builder>::default() + .existential_deposit(100) + .build() + .execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); + // Check balance + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(Balances::reserved_balance(1), 0); + + // Reserve some free balance + assert_ok!(Balances::reserve(&1, 50)); + // Check balance, the account should be ok. + assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Balances::reserved_balance(1), 50); + + // Reserve the rest of the free balance + assert_ok!(Balances::reserve(&1, 50)); + // Check balance, the account should be ok. + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::reserved_balance(1), 100); + + // Unreserve everything + Balances::unreserve(&1, 100); + // Check balance, all 100 should move to free_balance + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(Balances::reserved_balance(1), 0); + }); + } + + #[test] + fn account_deleted_when_just_dust() { + <$ext_builder>::default() + .existential_deposit(100) + .build() + .execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 50, 50)); + // Check balance + assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Balances::reserved_balance(1), 50); + + // Reserve some free balance + let _ = Balances::slash(&1, 1); + // The account should be dead. + assert!(Balances::is_dead_account(&1)); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::reserved_balance(1), 0); + }); + } + + #[test] + fn emit_events_with_reserve_and_unreserve() { + <$ext_builder>::default() + .build() + .execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + + System::set_block_number(2); + let _ = Balances::reserve(&1, 10); + + assert_eq!( + last_event(), + Event::balances(RawEvent::Reserved(1, 10)), + ); + + System::set_block_number(3); + let _ = Balances::unreserve(&1, 5); + + assert_eq!( + last_event(), + Event::balances(RawEvent::Unreserved(1, 5)), + ); + + System::set_block_number(4); + let _ = Balances::unreserve(&1, 6); + + // should only unreserve 5 + assert_eq!( + last_event(), + Event::balances(RawEvent::Unreserved(1, 5)), + ); + }); + } + + #[test] + fn emit_events_with_existential_deposit() { + <$ext_builder>::default() + .existential_deposit(100) + .build() + .execute_with(|| { + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); + + assert_eq!( + events(), + [ + Event::system(system::RawEvent::NewAccount(1)), + Event::balances(RawEvent::Endowed(1, 100)), + Event::balances(RawEvent::BalanceSet(1, 100, 0)), + ] + ); + + let _ = Balances::slash(&1, 1); + + assert_eq!( + events(), + [ + Event::balances(RawEvent::DustLost(1, 99)), + Event::system(system::RawEvent::KilledAccount(1)) + ] + ); + }); + } + + #[test] + fn emit_events_with_no_existential_deposit_suicide() { + <$ext_builder>::default() + .existential_deposit(0) + .build() + .execute_with(|| { + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); + + assert_eq!( + events(), + [ + Event::system(system::RawEvent::NewAccount(1)), + Event::balances(RawEvent::Endowed(1, 100)), + Event::balances(RawEvent::BalanceSet(1, 100, 0)), + ] + ); + + let _ = Balances::slash(&1, 100); + + // no events + assert_eq!(events(), []); + + assert_ok!(System::suicide(Origin::signed(1))); + + assert_eq!( + events(), + [ + Event::system(system::RawEvent::KilledAccount(1)) + ] + ); + }); + } + } +} diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs new file mode 100644 index 0000000000000..82de7ed6bf4d1 --- /dev/null +++ b/frame/balances/src/tests_composite.rs @@ -0,0 +1,162 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test utilities + +#![cfg(test)] + +use crate::{decl_tests, tests::CallWithDispatchInfo, GenesisConfig, Module, Trait}; +use frame_support::traits::Get; +use frame_support::weights::{DispatchInfo, IdentityFee, Weight}; +use frame_support::{impl_outer_event, impl_outer_origin, parameter_types}; +use sp_core::H256; +use sp_io; +use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; +use std::cell::RefCell; + +use frame_system as system; +impl_outer_origin! { + pub enum Origin for Test {} +} + +mod balances { + pub use crate::Event; +} + +impl_outer_event! { + pub enum Event for Test { + system, + balances, + } +} + +thread_local! { + static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); +} + +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> u64 { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) + } +} + +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Test; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl frame_system::Trait for Test { + type BaseCallFilter = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = CallWithDispatchInfo; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = super::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +parameter_types! { + pub const TransactionByteFee: u64 = 1; +} +impl pallet_transaction_payment::Trait for Test { + type Currency = Module; + type OnTransactionPayment = (); + type TransactionByteFee = TransactionByteFee; + type WeightToFee = IdentityFee; + type FeeMultiplierUpdate = (); +} +impl Trait for Test { + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = system::Module; +} + +pub struct ExtBuilder { + existential_deposit: u64, + monied: bool, +} +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: 1, + monied: false, + } + } +} +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn monied(mut self, monied: bool) -> Self { + self.monied = monied; + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + GenesisConfig:: { + balances: if self.monied { + vec![ + (1, 10 * self.existential_deposit), + (2, 20 * self.existential_deposit), + (3, 30 * self.existential_deposit), + (4, 40 * self.existential_deposit), + (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 + } +} + +decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT } diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs new file mode 100644 index 0000000000000..3901216085417 --- /dev/null +++ b/frame/balances/src/tests_local.rs @@ -0,0 +1,205 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test utilities + +#![cfg(test)] + +use crate::{decl_tests, tests::CallWithDispatchInfo, GenesisConfig, Module, Trait}; +use frame_support::traits::{Get, StorageMapShim}; +use frame_support::weights::{DispatchInfo, IdentityFee, Weight}; +use frame_support::{impl_outer_event, impl_outer_origin, parameter_types}; +use sp_core::H256; +use sp_io; +use sp_runtime::{testing::Header, traits::IdentityLookup, Perbill}; +use std::cell::RefCell; + +use frame_system as system; +impl_outer_origin! { + pub enum Origin for Test {} +} + +mod balances { + pub use crate::Event; +} + +impl_outer_event! { + pub enum Event for Test { + system, + balances, + } +} + +thread_local! { + static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); +} + +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> u64 { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) + } +} + +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Test; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl frame_system::Trait for Test { + type BaseCallFilter = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = CallWithDispatchInfo; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = super::AccountData; + type OnNewAccount = (); + type OnKilledAccount = Module; +} +parameter_types! { + pub const TransactionByteFee: u64 = 1; +} +impl pallet_transaction_payment::Trait for Test { + type Currency = Module; + type OnTransactionPayment = (); + type TransactionByteFee = TransactionByteFee; + type WeightToFee = IdentityFee; + type FeeMultiplierUpdate = (); +} +impl Trait for Test { + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = StorageMapShim< + super::Account, + system::CallOnCreatedAccount, + system::CallKillAccount, + u64, + super::AccountData, + >; +} + +pub struct ExtBuilder { + existential_deposit: u64, + monied: bool, +} +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: 1, + monied: false, + } + } +} +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> 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 set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + GenesisConfig:: { + balances: if self.monied { + vec![ + (1, 10 * self.existential_deposit), + (2, 20 * self.existential_deposit), + (3, 30 * self.existential_deposit), + (4, 40 * self.existential_deposit), + (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 + } +} + +decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT } + +#[test] +fn emit_events_with_no_existential_deposit_suicide_with_dust() { + ::default() + .existential_deposit(0) + .build() + .execute_with(|| { + assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); + + assert_eq!( + events(), + [ + Event::system(system::RawEvent::NewAccount(1)), + Event::balances(RawEvent::Endowed(1, 100)), + Event::balances(RawEvent::BalanceSet(1, 100, 0)), + ] + ); + + let _ = Balances::slash(&1, 99); + + // no events + assert_eq!(events(), []); + + assert_ok!(System::suicide(Origin::signed(1))); + + assert_eq!( + events(), + [ + Event::balances(RawEvent::DustLost(1, 1)), + Event::system(system::RawEvent::KilledAccount(1)) + ] + ); + }); +} diff --git a/xpallets/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml similarity index 64% rename from xpallets/transaction-payment/Cargo.toml rename to frame/transaction-payment/Cargo.toml index c2df114eec084..c99ae79fdb8cd 100644 --- a/xpallets/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -1,7 +1,7 @@ [package] -name = "xpallet-transaction-payment" +name = "pallet-transaction-payment" version = "2.0.0-rc4" -authors = ["Parity Technologies and ChainX community "] +authors = ["Parity Technologies "] edition = "2018" license = "Apache-2.0" homepage = "https://substrate.dev" @@ -12,41 +12,34 @@ description = "FRAME pallet to manage transaction payments" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -smallvec = "1.4.0" codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false, features = ["derive"] } -serde = { version = "1.0", optional = true } +serde = { version = "1.0.101", optional = true } +smallvec = "1.4.0" -# Substrate primitives sp-std = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } - -# Substrate pallets frame-support = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "./rpc/runtime-api", default-features = false } -# ChainX pallets -xpallet-transaction-payment-rpc-runtime-api = { path = "./rpc/runtime-api", default-features = false } -xpallet-support = { path = "../support", default-features = false } -xpallet-assets = { path = "../assets", default-features = false } +xpallet-support = { path = "../../xpallets/support", default-features = false } [dev-dependencies] -sp-io = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } -sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } -sp-storage = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } - -pallet-balances = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } +sp-io = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +sp-storage = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +pallet-balances = { path = "../balances" } [features] default = ["std"] std = [ - "codec/std", "serde", + "codec/std", "sp-std/std", "sp-runtime/std", "frame-support/std", "frame-system/std", + "pallet-transaction-payment-rpc-runtime-api/std", - "xpallet-transaction-payment-rpc-runtime-api/std", "xpallet-support/std", - "xpallet-assets/std", ] diff --git a/xpallets/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml similarity index 71% rename from xpallets/transaction-payment/rpc/Cargo.toml rename to frame/transaction-payment/rpc/Cargo.toml index b5c3031afbed7..66279be313884 100644 --- a/xpallets/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "xpallet-transaction-payment-rpc" +name = "pallet-transaction-payment-rpc" version = "2.0.0-rc4" authors = ["Parity Technologies "] edition = "2018" @@ -12,17 +12,17 @@ description = "RPC interface for the transaction payment module." targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.101", features = ["derive"] } codec = { package = "parity-scale-codec", version = "1.3.1" } +serde = { version = "1.0.101", features = ["derive"] } jsonrpc-core = "14.2.0" jsonrpc-core-client = "14.2.0" jsonrpc-derive = "14.2.1" -sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } -sp-rpc = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } -sp-runtime = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } -sp-api = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } -sp-blockchain = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } +sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +sp-rpc = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } -xpallet-transaction-payment-rpc-runtime-api = { path = "./runtime-api" } +sp-runtime = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +sp-api = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +sp-blockchain = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "./runtime-api" } diff --git a/xpallets/transaction-payment/rpc/runtime-api/Cargo.toml b/frame/transaction-payment/rpc/runtime-api/Cargo.toml similarity index 95% rename from xpallets/transaction-payment/rpc/runtime-api/Cargo.toml rename to frame/transaction-payment/rpc/runtime-api/Cargo.toml index 97e324b85962c..287f0cb2e96ea 100644 --- a/xpallets/transaction-payment/rpc/runtime-api/Cargo.toml +++ b/frame/transaction-payment/rpc/runtime-api/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "xpallet-transaction-payment-rpc-runtime-api" +name = "pallet-transaction-payment-rpc-runtime-api" version = "2.0.0-rc4" authors = ["Parity Technologies "] edition = "2018" @@ -29,8 +29,10 @@ default = ["std"] std = [ "serde", "codec/std", + "sp-api/std", "sp-std/std", "sp-runtime/std", + "frame-support/std", ] diff --git a/xpallets/transaction-payment/rpc/runtime-api/src/lib.rs b/frame/transaction-payment/rpc/runtime-api/src/lib.rs similarity index 100% rename from xpallets/transaction-payment/rpc/runtime-api/src/lib.rs rename to frame/transaction-payment/rpc/runtime-api/src/lib.rs diff --git a/xpallets/transaction-payment/rpc/src/lib.rs b/frame/transaction-payment/rpc/src/lib.rs similarity index 92% rename from xpallets/transaction-payment/rpc/src/lib.rs rename to frame/transaction-payment/rpc/src/lib.rs index 72f7f57a6175e..cd5043f886425 100644 --- a/xpallets/transaction-payment/rpc/src/lib.rs +++ b/frame/transaction-payment/rpc/src/lib.rs @@ -21,6 +21,8 @@ pub use self::gen_client::Client as TransactionPaymentClient; use codec::{Codec, Decode}; use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; use jsonrpc_derive::rpc; +use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; +pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::Bytes; @@ -29,8 +31,6 @@ use sp_runtime::{ traits::{Block as BlockT, MaybeDisplay, MaybeFromStr}, }; use std::sync::Arc; -use xpallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; -pub use xpallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi; #[rpc] pub trait TransactionPaymentApi { @@ -88,8 +88,8 @@ where ) -> Result> { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); + // If the block hash is not supplied assume the best block. + self.client.info().best_hash)); let encoded_len = encoded_xt.len() as u32; diff --git a/xpallets/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs similarity index 91% rename from xpallets/transaction-payment/src/lib.rs rename to frame/transaction-payment/src/lib.rs index 22cfb998bc6b9..a8be5e507c05f 100644 --- a/xpallets/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -36,12 +36,13 @@ use codec::{Decode, Encode}; use frame_support::{ decl_module, decl_storage, dispatch::DispatchResult, - traits::Get, + traits::{Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, WithdrawReason}, weights::{ DispatchInfo, GetDispatchInfo, Pays, PostDispatchInfo, Weight, WeightToFeeCoefficient, WeightToFeePolynomial, }, }; +use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use sp_runtime::{ traits::{ Convert, DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SaturatedConversion, Saturating, @@ -51,20 +52,19 @@ use sp_runtime::{ InvalidTransaction, TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransaction, }, - FixedPointNumber, FixedPointOperand, FixedU128, Perquintill, + FixedPointNumber, FixedPointOperand, FixedU128, Perquintill, RuntimeDebug, }; use sp_std::prelude::*; + use xpallet_support::debug; -use xpallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; + /// Fee multiplier. pub type Multiplier = FixedU128; -type BalanceOf = ::Balance; -// <::Currency as Currency<::AccountId>>::Balance; -// type BalanceOf = -// <::Currency as Currency<::AccountId>>::Balance; -// type NegativeImbalanceOf = -// <::Currency as Currency<::AccountId>>::NegativeImbalance; +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = + <::Currency as Currency<::AccountId>>::NegativeImbalance; /// A struct to update the weight multiplier per block. It implements `Convert`, meaning that it can convert the previous multiplier to the next one. This should @@ -169,14 +169,29 @@ where } } -pub trait Trait: frame_system::Trait + xpallet_assets::Trait { - // /// The currency type in which fees will be paid. - // type Currency: Currency + Send + Sync; +/// Storage releases of the module. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)] +enum Releases { + /// Original version of the module. + V1Ancient, + /// One that bumps the usage to FixedU128 from FixedI128. + V2, +} + +impl Default for Releases { + fn default() -> Self { + Releases::V1Ancient + } +} + +pub trait Trait: frame_system::Trait { + /// The currency type in which fees will be paid. + type Currency: Currency + Send + Sync; - // /// Handler for the unbalanced reduction when taking transaction fees. This is either one or - // /// two separate imbalances, the first is the transaction fee paid, the second is the tip paid, - // /// if any. - // type OnTransactionPayment: OnUnbalanced>; + /// Handler for the unbalanced reduction when taking transaction fees. This is either one or + /// two separate imbalances, the first is the transaction fee paid, the second is the tip paid, + /// if any. + type OnTransactionPayment: OnUnbalanced>; /// The fee to be paid for making a transaction; the per-byte portion. type TransactionByteFee: Get>; @@ -191,6 +206,8 @@ pub trait Trait: frame_system::Trait + xpallet_assets::Trait { decl_storage! { trait Store for Module as TransactionPayment { pub NextFeeMultiplier get(fn next_fee_multiplier): Multiplier = Multiplier::saturating_from_integer(1); + + StorageVersion build(|_: &GenesisConfig| Releases::V2): Releases; } } @@ -330,10 +347,10 @@ where .saturating_add(fixed_len_fee) .saturating_add(adjusted_weight_fee) .saturating_add(tip); - debug!("[fee]|total:{:?}|base_fee:{:?}|fixed_len_fee:{:?}|adjusted_weight_fee:{:?}|tip:{:?}", total, base_fee, fixed_len_fee, adjusted_weight_fee, tip); + debug!("[compute_fee]|total:{:?}|base_fee:{:?}|fixed_len_fee:{:?}|adjusted_weight_fee:{:?}|tip:{:?}", total, base_fee, fixed_len_fee, adjusted_weight_fee, tip); total } else { - debug!("[fee]|just tip|tip:{:?}", tip); + debug!("[compute_fee]|only tip:{:?}", tip); tip } } @@ -381,25 +398,28 @@ where who: &T::AccountId, info: &DispatchInfoOf, len: usize, - ) -> Result, TransactionValidityError> { + ) -> Result<(BalanceOf, Option>), TransactionValidityError> { let tip = self.0; let fee = Module::::compute_fee(len as u32, info, tip); // Only mess with balances if fee is not zero. if fee.is_zero() { - return Ok(Zero::zero()); + return Ok((fee, None)); } - let _ = xpallet_assets::Module::::move_balance( - & as xpallet_assets::ChainT<_>>::ASSET_ID, - who, - xpallet_assets::AssetType::Free, + + match T::Currency::withdraw( who, - xpallet_assets::AssetType::LockedFee, fee, - false, - ) - .map_err(|_| InvalidTransaction::Payment)?; - Ok(fee) + if tip.is_zero() { + WithdrawReason::TransactionPayment.into() + } else { + WithdrawReason::TransactionPayment | WithdrawReason::Tip + }, + ExistenceRequirement::KeepAlive, + ) { + Ok(imbalance) => Ok((fee, Some(imbalance))), + Err(_) => Err(InvalidTransaction::Payment.into()), + } } } @@ -423,7 +443,12 @@ where type AccountId = T::AccountId; type Call = T::Call; type AdditionalSigned = (); - type Pre = (BalanceOf, Self::AccountId, BalanceOf); + type Pre = ( + BalanceOf, + Self::AccountId, + Option>, + BalanceOf, + ); fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { Ok(()) } @@ -435,7 +460,7 @@ where info: &DispatchInfoOf, len: usize, ) -> TransactionValidity { - let fee = self.withdraw_fee(who, info, len)?; + let (fee, _) = self.withdraw_fee(who, info, len)?; let mut r = ValidTransaction::default(); // NOTE: we probably want to maximize the _fee (of any type) per weight unit_ here, which @@ -451,8 +476,8 @@ where info: &DispatchInfoOf, len: usize, ) -> Result { - let fee = self.withdraw_fee(who, info, len)?; - Ok((self.0, who.clone(), fee)) + let (fee, imbalance) = self.withdraw_fee(who, info, len)?; + Ok((self.0, who.clone(), imbalance, fee)) } fn post_dispatch( @@ -462,38 +487,28 @@ where len: usize, _result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - let (tip, who, fee) = pre; - if !fee.is_zero() { + let (tip, who, imbalance, fee) = pre; + if let Some(payed) = imbalance { let actual_fee = Module::::compute_actual_fee(len as u32, info, post_info, tip); - // refund locked fee to free - let _ = xpallet_assets::Module::::move_balance( - & as xpallet_assets::ChainT<_>>::ASSET_ID, - &who, - xpallet_assets::AssetType::LockedFee, - &who, - xpallet_assets::AssetType::Free, - fee, - false, - ) - .map_err(|_| InvalidTransaction::Payment)?; - // real do deduct fee from free balance - let _ = xpallet_assets::Module::::move_balance( - & as xpallet_assets::ChainT<_>>::ASSET_ID, - &who, - xpallet_assets::AssetType::Free, - &who, // TODO to blockproducer in future - xpallet_assets::AssetType::Free, - actual_fee, - true, // and log event - ) - .expect("at now, move fee must success"); + let refund = fee.saturating_sub(actual_fee); + let actual_payment = match T::Currency::deposit_into_existing(&who, refund) { + Ok(refund_imbalance) => { + // The refund cannot be larger than the up front payed max weight. + // `PostDispatchInfo::calc_unspent` guards against such a case. + match payed.offset(refund_imbalance) { + Ok(actual_payment) => actual_payment, + Err(_) => return Err(InvalidTransaction::Payment.into()), + } + } + // We do not recreate the account using the refund. The up front payment + // is gone in that case. + Err(_) => payed, + }; + let imbalances = actual_payment.split(tip); + T::OnTransactionPayment::on_unbalanceds( + Some(imbalances.0).into_iter().chain(Some(imbalances.1)), + ); } - assert!(xpallet_assets::Module::::asset_balance_of( - &who, - & as xpallet_assets::ChainT<_>>::ASSET_ID, - xpallet_assets::AssetType::LockedFee - ) - .is_zero()); Ok(()) } } @@ -510,6 +525,7 @@ mod tests { }, }; use pallet_balances::Call as BalancesCall; + use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use smallvec::smallvec; use sp_core::H256; use sp_runtime::{ @@ -518,7 +534,6 @@ mod tests { Perbill, }; use std::cell::RefCell; - use xpallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; const CALL: &::Call = &Call::Balances(BalancesCall::transfer(2, 69)); @@ -789,7 +804,7 @@ mod tests { &Ok(()) ) .is_ok()); - // 75 (3/2 of the returned 50 units of weight ) is refunded + // 75 (3/2 of the returned 50 units of weight) is refunded assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 75 - 5); }); } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 474b2f8225e0c..40200b27803ac 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -29,6 +29,7 @@ sp-transaction-pool = { git = "https://github.com/paritytech/substrate.git", tag # Substrate pallets frame-support = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } substrate-frame-rpc-system = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } +pallet-transaction-payment-rpc = { path = "../frame/transaction-payment/rpc" } chainx-runtime = { path = "../runtime" } @@ -42,7 +43,6 @@ xpallet-mining-staking-rpc = { path = "../xpallets/mining/staking/rpc" } xpallet-mining-staking-rpc-runtime-api = { path = "../xpallets/mining/staking/rpc/runtime-api" } xpallet-contracts-rpc = { path = "../xpallets/contracts/rpc" } xpallet-contracts-rpc-runtime-api = { path = "../xpallets/contracts/rpc/runtime-api" } -xpallet-transaction-payment-rpc = { path = "../xpallets/transaction-payment/rpc" } xpallet-gateway-records-rpc = { path = "../xpallets/gateway/records/rpc" } xpallet-gateway-records-rpc-runtime-api = { path = "../xpallets/gateway/records/rpc/runtime-api" } xpallet-gateway-common-rpc = { path = "../xpallets/gateway/common/rpc" } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 9691ba4b93886..c3bceb7515b7f 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -65,7 +65,7 @@ where as ProvideRuntimeApi>::Api: substrate_frame_rpc_system::AccountNonceApi, as ProvideRuntimeApi>::Api: - xpallet_transaction_payment_rpc::TransactionPaymentRuntimeApi< + pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi< Block, Balance, UncheckedExtrinsic, @@ -90,13 +90,13 @@ where P: TransactionPool + 'static, M: jsonrpc_core::Metadata + Default, { + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; use substrate_frame_rpc_system::{FullSystem, SystemApi}; use xpallet_assets_rpc::{Assets, AssetsApi}; use xpallet_contracts_rpc::{Contracts, ContractsApi}; use xpallet_gateway_common_rpc::{XGatewayCommon, XGatewayCommonApi}; use xpallet_gateway_records_rpc::{XGatewayRecords, XGatewayRecordsApi}; use xpallet_mining_staking_rpc::{XStaking, XStakingApi}; - use xpallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; let mut io = jsonrpc_core::IoHandler::default(); let FullDeps { diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 665653bc3e579..aeffa1af7fa9f 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -43,6 +43,10 @@ pallet-timestamp = { git = "https://github.com/paritytech/substrate.git", tag = pallet-utility = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } pallet-multisig = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +pallet-balances = { path = "../frame/balances", default-features = false } +pallet-transaction-payment = { path = "../frame/transaction-payment", default-features = false } +pallet-transaction-payment-rpc-runtime-api = { path = "../frame/transaction-payment/rpc/runtime-api", default-features = false } + # ChainX primitives chainx-primitives = { path = "../primitives", default-features = false } xp-mining-staking = { path = "../primitives/mining/staking", default-features = false } @@ -65,8 +69,6 @@ xpallet-dex-spot = { path = "../xpallets/dex/spot", default-features = false } xpallet-mining-staking = { path = "../xpallets/mining/staking", default-features = false } xpallet-mining-staking-rpc-runtime-api = { path = "../xpallets/mining/staking/rpc/runtime-api", default-features = false } xpallet-mining-asset = { path = "../xpallets/mining/asset", default-features = false } -xpallet-transaction-payment = { path = "../xpallets/transaction-payment", default-features = false } -xpallet-transaction-payment-rpc-runtime-api = { path = "../xpallets/transaction-payment/rpc/runtime-api", default-features = false } [build-dependencies] wasm-builder-runner = { package = "substrate-wasm-builder-runner", git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } @@ -107,6 +109,10 @@ std = [ "pallet-utility/std", "pallet-multisig/std", + "pallet-balances/std", + "pallet-transaction-payment/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "chainx-primitives/std", "xp-mining-staking/std", @@ -127,6 +133,4 @@ std = [ "xpallet-mining-staking/std", "xpallet-mining-staking-rpc-runtime-api/std", "xpallet-mining-asset/std", - "xpallet-transaction-payment/std", - "xpallet-transaction-payment-rpc-runtime-api/std", ] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 0d3884012364a..344fd20482c40 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -43,7 +43,7 @@ pub use sp_runtime::BuildStorage; // A few exports that help ease life for downstream crates. pub use frame_support::{ construct_runtime, parameter_types, - traits::{Filter, InstanceFilter, KeyOwnerProofSystem, Randomness}, + traits::{Currency, Filter, InstanceFilter, KeyOwnerProofSystem, OnUnbalanced, Randomness}, weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, IdentityFee, Weight, @@ -56,10 +56,11 @@ pub use chainx_primitives::{ AccountId, AccountIndex, AddrStr, AssetId, Balance, BlockNumber, Hash, Index, Memo, Moment, Name, Signature, Token, }; +use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use xpallet_contracts_rpc_runtime_api::ContractExecResult; -use xpallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; // xpallet re-exports +pub use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; pub use xpallet_assets::{ AssetInfo, AssetRestriction, AssetRestrictions, AssetType, Chain, TotalAssetInfo, WithdrawalLimit, @@ -77,7 +78,6 @@ pub use xpallet_gateway_common::{ }; pub use xpallet_gateway_records::Withdrawal; pub use xpallet_protocol::*; -pub use xpallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; /// Constant values used within the runtime. pub mod constants; @@ -234,13 +234,35 @@ impl frame_system::Trait for Runtime { /// This type is being generated by `construct_runtime!`. type ModuleToIndex = ModuleToIndex; /// The data to be stored in an account. - type AccountData = (); + type AccountData = pallet_balances::AccountData; /// What to do if a new account is created. type OnNewAccount = (); /// What to do if an account is fully reaped from the system. type OnKilledAccount = (); } +parameter_types! { + pub const MinimumPeriod: u64 = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Trait for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = MinimumPeriod; +} + +parameter_types! { + pub const UncleGenerations: BlockNumber = 5; +} + +impl pallet_authorship::Trait for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type UncleGenerations = UncleGenerations; + type FilterUncle = (); + type EventHandler = (ImOnline); +} + impl pallet_aura::Trait for Runtime { type AuthorityId = AuraId; } @@ -263,14 +285,122 @@ impl pallet_grandpa::Trait for Runtime { } parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; + pub const Offset: BlockNumber = 0; + pub const Period: BlockNumber = 50; + pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); } -impl pallet_timestamp::Trait for Runtime { - /// A timestamp: milliseconds since the unix epoch. - type Moment = u64; - type OnTimestampSet = Aura; - type MinimumPeriod = MinimumPeriod; +/// Substrate has the controller/stash concept, the according `Convert` implementation +/// is used to find the stash of the given controller account. +/// There is no such concepts in the context of ChainX, the stash account is also the controller account. +pub struct SimpleValidatorIdConverter; + +impl Convert> for SimpleValidatorIdConverter { + fn convert(controller: AccountId) -> Option { + Some(controller) + } +} + +impl pallet_session::Trait for Runtime { + type Event = Event; + type ValidatorId = ::AccountId; + type ValidatorIdOf = SimpleValidatorIdConverter; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = XStaking; + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type DisabledValidatorsThreshold = DisabledValidatorsThreshold; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1 * DOLLARS; +} + +impl pallet_balances::Trait for Runtime { + type Balance = Balance; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = frame_system::Module; +} + +parameter_types! { + pub const TransactionByteFee: Balance = 1; // TODO change in future + pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); + pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000); + pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128); +} +type NegativeImbalance = >::NegativeImbalance; + +pub struct DealWithFees; +impl OnUnbalanced for DealWithFees { + fn on_unbalanceds(mut fees_then_tips: impl Iterator) { + if let Some(fees) = fees_then_tips.next() { + // for fees, 80% to treasury, 20% to author + // let mut split = fees.ration(80, 20); + // if let Some(tips) = fees_then_tips.next() { + // // for tips, if any, 80% to treasury, 20% to author (though this can be anything) + // tips.ration_merge_into(80, 20, &mut split); + // } + // Treasury::on_unbalanced(split.0); + // Author::on_unbalanced(split.1); + // TODO impl fees dispatch + } + } +} + +impl pallet_transaction_payment::Trait for Runtime { + type Currency = Balances; + type OnTransactionPayment = DealWithFees; + type TransactionByteFee = TransactionByteFee; + type WeightToFee = IdentityFee; + type FeeMultiplierUpdate = + TargetedFeeAdjustment; +} + +parameter_types! { + /// Babe use EPOCH_DURATION_IN_SLOTS here, we use Aura. + pub const SessionDuration: BlockNumber = Period::get(); + pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); + /// We prioritize im-online heartbeats over election solution submission. + pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; +} + +impl pallet_im_online::Trait for Runtime { + type AuthorityId = ImOnlineId; + type Event = Event; + type SessionDuration = SessionDuration; + type ReportUnresponsiveness = Offences; + type UnsignedPriority = ImOnlineUnsignedPriority; +} + +/// Dummy implementation for the trait bound of pallet_im_online. +/// We actually make no use of the historical feature of pallet_session. +impl pallet_session::historical::Trait for Runtime { + type FullIdentification = AccountId; + /// Substrate: given the stash account ID, find the active exposure of nominators on that account. + /// ChainX: we don't need such info due to the reward pot. + type FullIdentificationOf = (); +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = Call; +} + +parameter_types! { + pub OffencesWeightSoftLimit: Weight = Perbill::from_percent(60) * MaximumBlockWeight::get(); +} + +impl pallet_offences::Trait for Runtime { + type Event = Event; + type IdentificationTuple = xpallet_mining_staking::IdentificationTuple; + type OnOffenceHandler = XStaking; + type WeightSoftLimit = OffencesWeightSoftLimit; } impl pallet_utility::Trait for Runtime { @@ -289,7 +419,7 @@ parameter_types! { impl pallet_multisig::Trait for Runtime { type Event = Event; type Call = Call; - type Currency = XAssets; + type Currency = Balances; type DepositBase = DepositBase; type DepositFactor = DepositFactor; type MaxSignatories = MaxSignatories; @@ -300,12 +430,14 @@ impl pallet_sudo::Trait for Runtime { type Call = Call; } +/////////////// chainx impl + impl xpallet_system::Trait for Runtime { type Event = Event; } impl xpallet_assets::Trait for Runtime { - type Balance = Balance; + type Currency = Balances; type Event = Event; type OnAssetChanged = XMiningAsset; type OnAssetRegisterOrRevoke = XMiningAsset; @@ -322,13 +454,6 @@ impl xpallet_gateway_common::Trait for Runtime { type BitcoinTrustee = XGatewayBitcoin; } -// pub struct Extractor; -// impl xpallet_gateway_common::traits::Extractable for Extractor { -// fn account_info(data: &[u8]) -> Option<(AccountId, Option)> { -// xpallet_gateway_common::extractor::Extractor::account_info(data) -// } -// } - impl xpallet_gateway_bitcoin::Trait for Runtime { type Event = Event; type AccountExtractor = xpallet_gateway_common::extractor::Extractor; @@ -352,7 +477,7 @@ impl xpallet_contracts::Trait for Runtime { type StorageSizeOffset = xpallet_contracts::DefaultStorageSizeOffset; type MaxDepth = xpallet_contracts::DefaultMaxDepth; type MaxValueSize = xpallet_contracts::DefaultMaxValueSize; - type WeightPrice = xpallet_transaction_payment::Module; + type WeightPrice = pallet_transaction_payment::Module; } parameter_types! { @@ -368,6 +493,7 @@ impl xp_mining_staking::TreasuryAccount for SimpleTreasuryAccount { impl xpallet_mining_staking::Trait for Runtime { type Event = Event; + type Currency = Balances; type SessionDuration = SessionDuration; type SessionInterface = Self; type TreasuryAccount = SimpleTreasuryAccount; @@ -378,109 +504,12 @@ impl xpallet_mining_staking::Trait for Runtime { impl xpallet_mining_asset::Trait for Runtime { type Event = Event; + type StakingInterface = Self; type TreasuryAccount = SimpleTreasuryAccount; type DetermineRewardPotAccount = xpallet_mining_asset::SimpleAssetRewardPotAccountDeterminer; } -parameter_types! { - pub const TransactionByteFee: Balance = 1; // TODO change in future - pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25); - pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000); - pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128); -} - -impl xpallet_transaction_payment::Trait for Runtime { - type TransactionByteFee = TransactionByteFee; - type WeightToFee = IdentityFee; - type FeeMultiplierUpdate = - TargetedFeeAdjustment; -} - -parameter_types! { - pub const Offset: BlockNumber = 0; - pub const Period: BlockNumber = 50; - pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); -} - -/// Substrate has the controller/stash concept, the according `Convert` implementation -/// is used to find the stash of the given controller account. -/// There is no such concepts in the context of ChainX, the stash account is also the controller account. -pub struct SimpleValidatorIdConverter; - -impl Convert> for SimpleValidatorIdConverter { - fn convert(controller: AccountId) -> Option { - Some(controller) - } -} - -impl pallet_session::Trait for Runtime { - type Event = Event; - type ValidatorId = ::AccountId; - type ValidatorIdOf = SimpleValidatorIdConverter; - type ShouldEndSession = pallet_session::PeriodicSessions; - type NextSessionRotation = pallet_session::PeriodicSessions; - type SessionManager = XStaking; - type SessionHandler = ::KeyTypeIdProviders; - type Keys = SessionKeys; - type DisabledValidatorsThreshold = DisabledValidatorsThreshold; -} - -parameter_types! { - /// Babe use EPOCH_DURATION_IN_SLOTS here, we use Aura. - pub const SessionDuration: BlockNumber = Period::get(); - pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); - /// We prioritize im-online heartbeats over election solution submission. - pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; -} - -impl pallet_im_online::Trait for Runtime { - type AuthorityId = ImOnlineId; - type Event = Event; - type SessionDuration = SessionDuration; - type ReportUnresponsiveness = Offences; - type UnsignedPriority = ImOnlineUnsignedPriority; -} - -/// Dummy implementation for the trait bound of pallet_im_online. -/// We actually make no use of the historical feature of pallet_session. -impl pallet_session::historical::Trait for Runtime { - type FullIdentification = AccountId; - /// Substrate: given the stash account ID, find the active exposure of nominators on that account. - /// ChainX: we don't need such info due to the reward pot. - type FullIdentificationOf = (); -} - -impl frame_system::offchain::SendTransactionTypes for Runtime -where - Call: From, -{ - type Extrinsic = UncheckedExtrinsic; - type OverarchingCall = Call; -} - -parameter_types! { - pub OffencesWeightSoftLimit: Weight = Perbill::from_percent(60) * MaximumBlockWeight::get(); -} - -impl pallet_offences::Trait for Runtime { - type Event = Event; - type IdentificationTuple = xpallet_mining_staking::IdentificationTuple; - type OnOffenceHandler = XStaking; - type WeightSoftLimit = OffencesWeightSoftLimit; -} - -parameter_types! { - pub const UncleGenerations: BlockNumber = 5; -} - -impl pallet_authorship::Trait for Runtime { - type FindAuthor = pallet_session::FindAccountFromAuthorIndex; - type UncleGenerations = UncleGenerations; - type FilterUncle = (); - type EventHandler = (ImOnline); -} - construct_runtime!( pub enum Runtime where Block = Block, @@ -493,10 +522,12 @@ construct_runtime!( Authorship: pallet_authorship::{Module, Call, Storage, Inherent}, Aura: pallet_aura::{Module, Config, Inherent(Timestamp)}, Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event}, - Utility: pallet_utility::{Module, Call, Event}, Session: pallet_session::{Module, Call, Storage, Event, Config}, + Balances: pallet_balances::{Module, Call, Storage, Config, Event}, + TransactionPayment: pallet_transaction_payment::{Module, Storage}, ImOnline: pallet_im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, Offences: pallet_offences::{Module, Call, Storage, Event}, + Utility: pallet_utility::{Module, Call, Event}, Multisig: pallet_multisig::{Module, Call, Storage, Event}, Sudo: pallet_sudo::{Module, Call, Config, Storage, Event}, @@ -508,7 +539,7 @@ construct_runtime!( XContracts: xpallet_contracts::{Module, Call, Config, Storage, Event}, XStaking: xpallet_mining_staking::{Module, Call, Storage, Event, Config}, XMiningAsset: xpallet_mining_asset::{Module, Call, Storage, Event, Config}, - XTransactionPayment: xpallet_transaction_payment::{Module, Storage}, + XSpot: xpallet_dex_spot::{Module, Call, Storage, Event, Config}, } ); @@ -531,7 +562,7 @@ pub type SignedExtra = ( frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, - xpallet_transaction_payment::ChargeTransactionPayment, + pallet_transaction_payment::ChargeTransactionPayment, BaseFilter, ); /// Unchecked extrinsic type as expected by this runtime. @@ -662,13 +693,13 @@ impl_runtime_apis! { } } - impl xpallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< Block, Balance, UncheckedExtrinsic, > for Runtime { fn query_info(uxt: UncheckedExtrinsic, len: u32) -> RuntimeDispatchInfo { - XTransactionPayment::query_info(uxt, len) + TransactionPayment::query_info(uxt, len) } } diff --git a/scripts/chainx-js/res/chainx_rpc.json b/scripts/chainx-js/res/chainx_rpc.json index 101615f525cc4..8dc3f8345ad1f 100644 --- a/scripts/chainx-js/res/chainx_rpc.json +++ b/scripts/chainx-js/res/chainx_rpc.json @@ -2,7 +2,7 @@ "rpc": { "xassets": { "getAssetsByAccount": { - "description": "Some description", + "description": "Return all assets with AssetTypes for an account (exclude native token(PCX)). The returned map would not contains the assets which is not existed for this account but existed in valid assets list.", "params": [ { "name": "who", @@ -30,7 +30,7 @@ }, "xgatewaycommon": { "withdrawalLimit": { - "description": "Some description", + "description": "Get withdrawal limit(minimal_withdrawal&fee) for an AssetId", "params": [ { "name": "asset_id", @@ -45,7 +45,7 @@ "type": "WithdrawalLimit" }, "verifyWithdrawal": { - "description": "Some description", + "description": "Use the params to verify whether the withdrawal apply is valid. Notice those params is same as the params for call `XGatewayCommon::withdraw(...)`, including checking address is valid or something else. Front-end should use this rpc to check params first, than could create the extrinsic.", "params": [ { "name": "asset_id", @@ -72,7 +72,7 @@ "type": "()" }, "trusteeMultisigs": { - "description": "Some description", + "description": "Return the trustee multisig address for all chain.", "params": [ { "name": "at", @@ -82,7 +82,7 @@ "type": "BTreeMap" }, "bitcoinTrusteeProperties": { - "description": "Some description", + "description": "Return bitcoin trustee registered property info for an account(e.g. registered hot/cold address)", "params": [ { "name": "who", @@ -97,7 +97,7 @@ "type": "BtcTrusteeIntentionProps" }, "bitcoinTrusteeSessionInfo": { - "description": "Some description", + "description": "Return bitcoin trustee for current session(e.g. trustee hot/cold address and else)", "params": [ { "name": "at", @@ -108,7 +108,7 @@ "type": "BtcTrusteeSessionInfo" }, "bitcoinGenerateTrusteeSessionInfo": { - "description": "Some description", + "description": "Try to generate bitcoin trustee info for a list of candidates. (this api is used to check the trustee info which would be generated by those candidates)", "params": [ { "name": "candidates", @@ -125,7 +125,7 @@ }, "xgatewayrecords": { "withdrawalList": { - "description": "Some description", + "description": "Return current withdraw list(include Applying and Processing withdraw state)", "params": [ { "name": "at", @@ -136,7 +136,7 @@ "type": "BTreeMap>" }, "withdrawalListByChain": { - "description": "Some description", + "description": "Return current withdraw list for a chain(include Applying and Processing withdraw state)", "params": [ { "name": "chain", @@ -151,7 +151,7 @@ "type": "BTreeMap>" }, "pendingWithdrawalList": { - "description": "Some description", + "description": "Return current pending withdraw list for a chain", "params": [ { "name": "chain", diff --git a/xpallets/assets/Cargo.toml b/xpallets/assets/Cargo.toml index 1b0d77e1e4921..35f57fba166e8 100644 --- a/xpallets/assets/Cargo.toml +++ b/xpallets/assets/Cargo.toml @@ -11,7 +11,6 @@ serde = { version = "1.0", optional = true } # Substrate primitives sp-std = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } sp-runtime = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } # Substrate pallets @@ -25,6 +24,14 @@ chainx-primitives = { path = "../../primitives", default-features = false } xpallet-protocol = { path = "../protocol", default-features = false } xpallet-support = { path = "../support", default-features = false } +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } +sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } + +pallet-balances = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } + +env_logger = "0.7.1" + [features] default = ["std"] std = [ @@ -33,7 +40,6 @@ std = [ "serde", "sp-std/std", - "sp-core/std", "sp-runtime/std", "frame-support/std", diff --git a/xpallets/assets/rpc/src/lib.rs b/xpallets/assets/rpc/src/lib.rs index 2cfee4087b544..a2587415e47c7 100644 --- a/xpallets/assets/rpc/src/lib.rs +++ b/xpallets/assets/rpc/src/lib.rs @@ -31,6 +31,7 @@ impl Assets { #[rpc] pub trait AssetsApi { + /// Return all assets with AssetTypes for an account (exclude native token(PCX)). The returned map would not contains the assets which is not existed for this account but existed in valid assets list. #[rpc(name = "xassets_getAssetsByAccount")] fn assets_by_account( &self, @@ -38,6 +39,7 @@ pub trait AssetsApi { at: Option, ) -> Result>>; + /// Return all valid assets balance with AssetTypes. (exclude native token(PCX)) #[rpc(name = "xassets_getAssets")] fn assets(&self, at: Option) -> Result>; } diff --git a/xpallets/assets/src/lib.rs b/xpallets/assets/src/lib.rs index 7e10e5dcb8e03..9292e9aeb3d3b 100644 --- a/xpallets/assets/src/lib.rs +++ b/xpallets/assets/src/lib.rs @@ -4,24 +4,25 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -mod pcx; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + pub mod traits; mod trigger; pub mod types; -use codec::Codec; // Substrate -use sp_runtime::traits::{ - AtLeast32BitUnsigned, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Zero, -}; -use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, prelude::*, result}; +use sp_runtime::traits::{CheckedAdd, CheckedSub, Saturating, Zero}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*, result}; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, dispatch::{DispatchError, DispatchResult}, ensure, - traits::{Imbalance, OnNewAccount, SignedImbalance}, - Parameter, + traits::{Currency, LockableCurrency, ReservableCurrency}, + StorageDoubleMap, }; use frame_system::{self as system, ensure_root, ensure_signed}; @@ -34,23 +35,26 @@ use self::trigger::AssetChangedTrigger; pub use self::traits::{ChainT, OnAssetChanged, OnAssetRegisterOrRevoke}; pub use self::types::{ is_valid_desc, is_valid_token, AssetErr, AssetInfo, AssetRestriction, AssetRestrictions, - AssetType, Chain, NegativeImbalance, PositiveImbalance, SignedBalance, SignedImbalanceT, - TotalAssetInfo, WithdrawalLimit, + AssetType, Chain, SignedBalance, TotalAssetInfo, WithdrawalLimit, }; +use frame_support::traits::IsDeadAccount; + +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +// pub type PositiveImbalanceOf = +// <::Currency as Currency<::AccountId>>::NegativeImbalance; +// pub type NegativeImbalanceOf = +// <::Currency as Currency<::AccountId>>::NegativeImbalance; +// pub type SignedImbalance = frame_support::traits::SignedImbalance, PositiveImbalance>; pub trait Trait: system::Trait { - type Balance: Parameter - + Member - + AtLeast32BitUnsigned - + Codec - + Default - + Copy - + MaybeSerializeDeserialize - + Debug; /// Event type Event: From> + Into<::Event>; - type OnAssetChanged: OnAssetChanged; + type Currency: ReservableCurrency + + LockableCurrency; + + type OnAssetChanged: OnAssetChanged>; type OnAssetRegisterOrRevoke: OnAssetRegisterOrRevoke; } @@ -97,7 +101,7 @@ decl_error! { decl_event!( pub enum Event where ::AccountId, - ::Balance, + Balance = BalanceOf, SignedBalance = SignedBalance, { Register(AssetId, bool), @@ -125,7 +129,7 @@ decl_module! { asset: AssetInfo, restrictions: AssetRestrictions, is_online: bool, - has_mining_rights: bool + has_mining_rights: bool, ) -> DispatchResult { ensure_root(origin)?; asset.is_valid::()?; @@ -147,16 +151,30 @@ decl_module! { pub fn revoke_asset(origin, #[compact] id: AssetId) -> DispatchResult { ensure_root(origin)?; ensure!(Self::asset_online(id).is_some(), Error::::InvalidAsset); - Self::remove_asset(&id)?; + Self::remove_asset(&id); T::OnAssetRegisterOrRevoke::on_revoke(&id)?; Self::deposit_event(RawEvent::Revoke(id)); Ok(()) } + /// recover an offline asset, + #[weight = 0] + pub fn recover_asset(origin, #[compact] id: AssetId, has_mining_rights: bool) -> DispatchResult { + ensure_root(origin)?; + ensure!(Self::asset_info_of(id).is_some(), Error::::NotExistedAsset); + ensure!(Self::asset_online(id).is_none(), Error::::InvalidAsset); + + Self::re_add_asset(&id); + + T::OnAssetRegisterOrRevoke::on_register(&id, has_mining_rights)?; + Self::deposit_event(RawEvent::Register(id, has_mining_rights)); + Ok(()) + } + /// set free token for an account #[weight = 0] - pub fn set_balance(origin, who: T::AccountId, #[compact] id: AssetId, balances: BTreeMap) -> DispatchResult { + pub fn set_balance(origin, who: T::AccountId, #[compact] id: AssetId, balances: BTreeMap>) -> DispatchResult { ensure_root(origin)?; info!("[set_balance]|set balances by root|who:{:?}|id:{:}|balances_map:{:?}", who, id, balances); Self::set_balance_by_root(&who, &id, balances)?; @@ -165,7 +183,7 @@ decl_module! { /// transfer between account #[weight = 0] - pub fn transfer(origin, dest: T::AccountId, #[compact] id: AssetId, #[compact] value: T::Balance, memo: Memo) -> DispatchResult { + pub fn transfer(origin, dest: T::AccountId, #[compact] id: AssetId, #[compact] value: BalanceOf, memo: Memo) -> DispatchResult { let transactor = ensure_signed(origin)?; debug!("[transfer]|from:{:?}|to:{:?}|id:{:}|value:{:?}|memo:{}", transactor, dest, id, value, memo); memo.check_validity()?; @@ -178,7 +196,7 @@ decl_module! { /// for transfer by root #[weight = 0] - pub fn force_transfer(origin, transactor: T::AccountId, dest: T::AccountId, #[compact] id: AssetId, #[compact] value: T::Balance, memo: Memo) -> DispatchResult { + pub fn force_transfer(origin, transactor: T::AccountId, dest: T::AccountId, #[compact] id: AssetId, #[compact] value: BalanceOf, memo: Memo) -> DispatchResult { ensure_root(origin)?; debug!("[force_transfer]|from:{:?}|to:{:?}|id:{:}|value:{:?}|memo:{}", transactor, dest, id, value, memo); memo.check_validity()?; @@ -235,36 +253,26 @@ decl_storage! { /// asset balance for user&asset_id, use btree_map to accept different asset type pub AssetBalance get(fn asset_balance): - double_map hasher(blake2_128_concat) T::AccountId, hasher(twox_64_concat) AssetId => BTreeMap; + double_map hasher(blake2_128_concat) T::AccountId, hasher(twox_64_concat) AssetId => BTreeMap>; /// asset balance for an asset_id, use btree_map to accept different asset type - pub TotalAssetBalance get(fn total_asset_balance): map hasher(twox_64_concat) AssetId => BTreeMap; + pub TotalAssetBalance get(fn total_asset_balance): map hasher(twox_64_concat) AssetId => BTreeMap>; /// memo len pub MemoLen get(fn memo_len) config(): u32; } add_extra_genesis { config(assets): Vec<(AssetId, AssetInfo, AssetRestrictions, bool, bool)>; - config(endowed): BTreeMap>; + config(endowed): BTreeMap)>>; build(|config| { Module::::initialize_assets(&config.assets, &config.endowed); }) } } -impl ChainT for Module { - const ASSET_ID: AssetId = xpallet_protocol::PCX; - fn chain() -> Chain { - Chain::ChainX - } - fn withdrawal_limit(_: &AssetId) -> result::Result, DispatchError> { - Err(Error::::ActionNotAllowed)? - } -} - impl Module { fn initialize_assets( assets: &Vec<(AssetId, AssetInfo, AssetRestrictions, bool, bool)>, - endowed_accounts: &BTreeMap>, + endowed_accounts: &BTreeMap)>>, ) { for (id, asset, restrictions, is_online, has_mining_rights) in assets { Self::register_asset( @@ -292,13 +300,6 @@ impl Module { } Ok(()) } - - pub fn should_not_chainx(id: &AssetId) -> DispatchResult { - if *id == >::ASSET_ID { - Err(Error::::PcxNotAllowed)?; - } - Ok(()) - } } // asset related @@ -324,9 +325,12 @@ impl Module { Ok(()) } - fn remove_asset(id: &AssetId) -> DispatchResult { + fn remove_asset(id: &AssetId) { AssetOnline::remove(id); - Ok(()) + } + + fn re_add_asset(id: &AssetId) { + AssetOnline::insert(id, ()); } pub fn asset_ids() -> Vec { @@ -337,7 +341,7 @@ impl Module { v } - pub fn total_asset_infos() -> BTreeMap> { + pub fn total_asset_infos() -> BTreeMap>> { use frame_support::IterableStorageMap; AssetInfoOf::iter() .map(|(id, info)| { @@ -363,7 +367,7 @@ impl Module { pub fn valid_assets_of( who: &T::AccountId, - ) -> BTreeMap> { + ) -> BTreeMap>> { use frame_support::IterableStorageDoubleMap; AssetBalance::::iter_prefix(who) .filter_map(|(id, map)| Self::asset_online(id).map(|_| (id, map))) @@ -438,25 +442,32 @@ impl Module { /// token issue destroy reserve/unreserve, it's core function impl Module { - pub fn all_type_total_asset_balance(id: &AssetId) -> T::Balance { + pub fn all_type_total_asset_balance(id: &AssetId) -> BalanceOf { let map = Self::total_asset_balance(id); map.values().fold(Zero::zero(), |acc, &x| acc + x) } - pub fn all_type_asset_balance(who: &T::AccountId, id: &AssetId) -> T::Balance { + pub fn total_asset_balance_of(id: &AssetId, type_: AssetType) -> BalanceOf { + Self::total_asset_balance(id) + .get(&type_) + .map(|b| *b) + .unwrap_or_default() + } + + pub fn all_type_asset_balance(who: &T::AccountId, id: &AssetId) -> BalanceOf { let map = Self::asset_balance(who, id); map.values().fold(Zero::zero(), |acc, &x| acc + x) } - pub fn asset_balance_of(who: &T::AccountId, id: &AssetId, type_: AssetType) -> T::Balance { + pub fn asset_balance_of(who: &T::AccountId, id: &AssetId, type_: AssetType) -> BalanceOf { Self::asset_type_balance(who, id, type_) } - pub fn free_balance_of(who: &T::AccountId, id: &AssetId) -> T::Balance { + pub fn free_balance_of(who: &T::AccountId, id: &AssetId) -> BalanceOf { Self::asset_type_balance(&who, &id, AssetType::Free) } - fn asset_type_balance(who: &T::AccountId, id: &AssetId, type_: AssetType) -> T::Balance { + fn asset_type_balance(who: &T::AccountId, id: &AssetId, type_: AssetType) -> BalanceOf { let balance_map = Self::asset_balance(who, id); match balance_map.get(&type_) { Some(b) => *b, @@ -464,17 +475,17 @@ impl Module { } } - pub fn issue(id: &AssetId, who: &T::AccountId, value: T::Balance) -> DispatchResult { + pub fn issue(id: &AssetId, who: &T::AccountId, value: BalanceOf) -> DispatchResult { ensure!(Self::asset_online(id).is_some(), Error::::InvalidAsset); - // may set storage inner - Self::try_new_account(&who, id); + // // may set storage inner + // Self::try_new_account(&who); let _imbalance = Self::inner_issue(id, who, AssetType::Free, value)?; Ok(()) } - pub fn destroy(id: &AssetId, who: &T::AccountId, value: T::Balance) -> DispatchResult { + pub fn destroy(id: &AssetId, who: &T::AccountId, value: BalanceOf) -> DispatchResult { ensure!(Self::asset_online(id).is_some(), Error::::InvalidAsset); Self::can_destroy_withdrawal(id)?; @@ -482,7 +493,7 @@ impl Module { Ok(()) } - pub fn destroy_free(id: &AssetId, who: &T::AccountId, value: T::Balance) -> DispatchResult { + pub fn destroy_free(id: &AssetId, who: &T::AccountId, value: BalanceOf) -> DispatchResult { ensure!(Self::asset_online(id).is_some(), Error::::InvalidAsset); Self::can_destroy_free(id)?; @@ -491,31 +502,13 @@ impl Module { } fn new_account(who: &T::AccountId) { - T::OnNewAccount::on_new_account(&who); - // set empty balance for pcx - assert!( - !AssetBalance::::contains_key(&who, Self::ASSET_ID), - "when new account, the pcx must not exist for this account!" - ); info!("[new_account]|create new account|who:{:?}", who); - AssetBalance::::insert( - &who, - Self::ASSET_ID, - BTreeMap::::new(), - ); - // Self::deposit_event(RawEvent::NewAccount(who.clone())); + system::Module::::on_created_account(who.clone()); } - fn try_new_account(who: &T::AccountId, id: &AssetId) { + fn try_new_account(who: &T::AccountId) { // lookup chainx balance - let existed = if *id == Self::ASSET_ID { - AssetBalance::::contains_key(who, id) - } else { - AssetBalance::::contains_key(who, Self::ASSET_ID) - }; - - if !existed { - // init account + if system::Module::::is_dead_account(who) { Self::new_account(who); } } @@ -524,44 +517,57 @@ impl Module { who: &T::AccountId, id: &AssetId, type_: AssetType, - new_balance: T::Balance, - ) -> SignedImbalanceT { - let mut original: T::Balance = Zero::zero(); - AssetBalance::::mutate(who, id, |balance_map| { - if new_balance == Zero::zero() { - // remove Zero balance to save space - if let Some(old) = balance_map.remove(&type_) { - original = old; + new_balance: BalanceOf, + ) { + let mut original: BalanceOf = Zero::zero(); + let existed = AssetBalance::::contains_key(who, id); + let exists = AssetBalance::::mutate( + who, + id, + |balance_map: &mut BTreeMap>| { + if new_balance == Zero::zero() { + // remove Zero balance to save space + if let Some(old) = balance_map.remove(&type_) { + original = old; + } + // if is_empty(), means not exists + !balance_map.is_empty() + } else { + let balance = balance_map.entry(type_).or_default(); + original = *balance; + // modify to new balance + *balance = new_balance; + true } + }, + ); + if !existed && exists { + Self::try_new_account(who); + frame_system::Module::::inc_ref(who); + } else if existed && !exists { + frame_system::Module::::dec_ref(who); + AssetBalance::::remove(who, id); + } + + TotalAssetBalance::::mutate(id, |total: &mut BTreeMap>| { + let balance = total.entry(type_).or_default(); + if original <= new_balance { + *balance = balance.saturating_add(new_balance - original); } else { - let balance = balance_map.entry(type_).or_default(); - original = *balance; - // modify to new balance - *balance = new_balance; + *balance = balance.saturating_sub(original - new_balance); + }; + if *balance == Zero::zero() { + total.remove(&type_); } }); - let imbalance = if original <= new_balance { - SignedImbalance::Positive(PositiveImbalance::::new( - new_balance - original, - *id, - type_, - )) - } else { - SignedImbalance::Negative(NegativeImbalance::::new( - original - new_balance, - *id, - type_, - )) - }; - imbalance } fn inner_issue( id: &AssetId, who: &T::AccountId, type_: AssetType, - value: T::Balance, - ) -> result::Result, DispatchError> { + value: BalanceOf, + ) -> result::Result<(), DispatchError> { let current = Self::asset_type_balance(&who, id, type_); debug!( @@ -574,24 +580,18 @@ impl Module { AssetChangedTrigger::::on_issue_pre(id, who); // set to storage - let imbalance = Self::make_type_balance_be(who, id, type_, new); - let positive = if let SignedImbalance::Positive(p) = imbalance { - p - } else { - // Impossible, but be defensive. - PositiveImbalance::::new(Zero::zero(), *id, type_) - }; + Self::make_type_balance_be(who, id, type_, new); AssetChangedTrigger::::on_issue_post(id, who, value)?; - Ok(positive) + Ok(()) } fn inner_destroy( id: &AssetId, who: &T::AccountId, type_: AssetType, - value: T::Balance, - ) -> result::Result, DispatchError> { + value: BalanceOf, + ) -> result::Result<(), DispatchError> { let current = Self::asset_type_balance(&who, id, type_); debug!("[destroy_directly]|destroy asset for account|id:{:}|who:{:?}|type:{:?}|current:{:?}|destroy:{:?}", @@ -603,16 +603,10 @@ impl Module { AssetChangedTrigger::::on_destroy_pre(id, who); - let imbalance = Self::make_type_balance_be(who, id, type_, new); - let negative = if let SignedImbalance::Negative(n) = imbalance { - n - } else { - // Impossible, but be defensive. - NegativeImbalance::::new(Zero::zero(), *id, type_) - }; + Self::make_type_balance_be(who, id, type_, new); AssetChangedTrigger::::on_destroy_post(id, who, value)?; - Ok(negative) + Ok(()) } pub fn move_balance( @@ -621,19 +615,15 @@ impl Module { from_type: AssetType, to: &T::AccountId, to_type: AssetType, - value: T::Balance, - do_trigger: bool, - ) -> result::Result<(SignedImbalanceT, SignedImbalanceT), AssetErr> { + value: BalanceOf, + ) -> result::Result<(), AssetErr> { // check ensure!(Self::asset_online(id).is_some(), AssetErr::InvalidAsset); Self::can_move(id).map_err(|_| AssetErr::NotAllow)?; if value == Zero::zero() { // value is zero, do not read storage, no event - return Ok(( - SignedImbalance::Positive(PositiveImbalance::::zero()), - SignedImbalance::Positive(PositiveImbalance::::zero()), - )); + return Ok(()); } let from_balance = Self::asset_type_balance(from, id, from_type); @@ -652,48 +642,37 @@ impl Module { if from == to && from_type == to_type { // same account, same type, return directly // same account also do trigger - if do_trigger { - AssetChangedTrigger::::on_move_pre(id, from, from_type, to, to_type, value); - AssetChangedTrigger::::on_move_post(id, from, from_type, to, to_type, value)?; - } - return Ok(( - SignedImbalance::Positive(PositiveImbalance::::zero()), - SignedImbalance::Positive(PositiveImbalance::::zero()), - )); + AssetChangedTrigger::::on_move_pre(id, from, from_type, to, to_type, value); + AssetChangedTrigger::::on_move_post(id, from, from_type, to, to_type, value)?; + return Ok(()); } // !!! all check pass, start set storage - // for account to set storage - if to_type == AssetType::Free { - Self::try_new_account(to, id); - } + // // for account to set storage + // Self::try_new_account(to)?; - if do_trigger { - AssetChangedTrigger::::on_move_pre(id, from, from_type, to, to_type, value); - } + AssetChangedTrigger::::on_move_pre(id, from, from_type, to, to_type, value); - let from_imbalance = Self::make_type_balance_be(from, id, from_type, new_from_balance); - let to_imbalance = Self::make_type_balance_be(to, id, to_type, new_to_balance); + Self::make_type_balance_be(from, id, from_type, new_from_balance); + Self::make_type_balance_be(to, id, to_type, new_to_balance); - if do_trigger { - AssetChangedTrigger::::on_move_post(id, from, from_type, to, to_type, value)?; - } - Ok((from_imbalance, to_imbalance)) + AssetChangedTrigger::::on_move_post(id, from, from_type, to, to_type, value)?; + Ok(()) } pub fn move_free_balance( id: &AssetId, from: &T::AccountId, to: &T::AccountId, - value: T::Balance, - ) -> result::Result<(SignedImbalanceT, SignedImbalanceT), AssetErr> { - Self::move_balance(id, from, AssetType::Free, to, AssetType::Free, value, true) + value: BalanceOf, + ) -> result::Result<(), AssetErr> { + Self::move_balance(id, from, AssetType::Free, to, AssetType::Free, value) } pub fn set_balance_by_root( who: &T::AccountId, id: &AssetId, - balances: BTreeMap, + balances: BTreeMap>, ) -> DispatchResult { for (type_, val) in balances.into_iter() { let old_val = Self::asset_type_balance(who, id, type_); @@ -701,76 +680,10 @@ impl Module { continue; } - let _imbalance = Self::make_type_balance_be(who, id, type_, val); + Self::make_type_balance_be(who, id, type_, val); AssetChangedTrigger::::on_set_balance(id, who, type_, val)?; } Ok(()) } } - -// wrapper for balances module -impl Module { - pub fn pcx_free_balance(who: &T::AccountId) -> T::Balance { - Self::asset_balance_of(who, &Self::ASSET_ID, AssetType::Free) - } - - pub fn pcx_type_balance(who: &T::AccountId, type_: AssetType) -> T::Balance { - Self::asset_balance_of(who, &Self::ASSET_ID, type_) - } - - pub fn pcx_all_type_balance(who: &T::AccountId) -> T::Balance { - Self::all_type_asset_balance(who, &Self::ASSET_ID) - } - - pub fn pcx_total_balance() -> T::Balance { - Self::all_type_total_asset_balance(&Self::ASSET_ID) - } - - pub fn pcx_issue(who: &T::AccountId, value: T::Balance) -> DispatchResult { - Self::issue(&Self::ASSET_ID, who, value) - } - - pub fn pcx_move_balance( - from: &T::AccountId, - from_type: AssetType, - to: &T::AccountId, - to_type: AssetType, - value: T::Balance, - ) -> result::Result<(), AssetErr> { - Self::move_balance( - &>::ASSET_ID, - from, - from_type, - to, - to_type, - value, - true, - )?; - Ok(()) - } - - pub fn pcx_move_free_balance( - from: &T::AccountId, - to: &T::AccountId, - value: T::Balance, - ) -> result::Result<(), AssetErr> { - Self::pcx_move_balance(from, AssetType::Free, to, AssetType::Free, value) - } - - pub fn pcx_make_free_balance_be(who: &T::AccountId, value: T::Balance) -> SignedImbalanceT { - Self::try_new_account(who, &Self::ASSET_ID); - let imbalance = Self::make_type_balance_be(who, &Self::ASSET_ID, AssetType::Free, value); - let b = match imbalance { - SignedImbalance::Positive(ref p) => SignedBalance::Positive(p.peek()), - SignedImbalance::Negative(ref n) => SignedBalance::Negative(n.peek()), - }; - Self::deposit_event(RawEvent::Change( - Self::ASSET_ID, - who.clone(), - AssetType::Free, - b, - )); - imbalance - } -} diff --git a/xpallets/assets/src/mock.rs b/xpallets/assets/src/mock.rs new file mode 100644 index 0000000000000..3334ea16fa5a3 --- /dev/null +++ b/xpallets/assets/src/mock.rs @@ -0,0 +1,160 @@ +use crate::*; +use crate::{Module, Trait}; +use chainx_primitives::AssetId; +use frame_support::{impl_outer_event, impl_outer_origin, parameter_types, sp_io, weights::Weight}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; +use std::collections::BTreeMap; + +/// The AccountId alias in this test module. +pub(crate) type AccountId = u64; +pub(crate) type BlockNumber = u64; +pub(crate) type Balance = u128; + +impl_outer_origin! { + pub enum Origin for Test {} +} + +use frame_system as system; +mod assets { + // Re-export needed for `impl_outer_event!`. + pub use super::super::*; +} + +impl_outer_event! { + pub enum MetaEvent for Test { + system, + pallet_balances, + assets, + } +} + +// For testing the pallet, we construct most of a mock runtime. This means +// first constructing a configuration type (`Test`) which `impl`s each of the +// configuration traits of pallets we want to use. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct Test; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: Weight = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); +} + +impl system::Trait for Test { + type BaseCallFilter = (); + type Origin = Origin; + type Call = (); + type Index = u64; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = MetaEvent; + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type DbWeight = (); + type BlockExecutionWeight = (); + type ExtrinsicBaseWeight = (); + type MaximumExtrinsicWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); + type ModuleToIndex = (); + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); +} +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} +impl pallet_balances::Trait for Test { + type Balance = Balance; + type DustRemoval = (); + type Event = MetaEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +impl Trait for Test { + type Event = MetaEvent; + type Currency = Balances; + type OnAssetChanged = (); + type OnAssetRegisterOrRevoke = (); +} + +pub struct ExtBuilder; +impl Default for ExtBuilder { + fn default() -> Self { + Self + } +} + +pub(crate) fn btc() -> (AssetId, AssetInfo, AssetRestrictions) { + ( + xpallet_protocol::X_BTC, + AssetInfo::new::( + b"X-BTC".to_vec(), + b"X-BTC".to_vec(), + Chain::Bitcoin, + 8, + b"ChainX's cross-chain Bitcoin".to_vec(), + ) + .unwrap(), + AssetRestriction::DestroyFree.into(), + ) +} + +impl ExtBuilder { + pub fn build( + self, + assets: Vec<(AssetId, AssetInfo, AssetRestrictions, bool, bool)>, + endowed: BTreeMap>, + ) -> sp_io::TestExternalities { + let _ = env_logger::try_init(); + let mut storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + let _ = GenesisConfig:: { + assets, + endowed, + memo_len: 128, + } + .assimilate_storage(&mut storage); + + let ext = sp_io::TestExternalities::new(storage); + ext + } + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + let btc_assets = btc(); + let assets = vec![(btc_assets.0, btc_assets.1, btc_assets.2, true, true)]; + let mut endowed = BTreeMap::new(); + let endowed_info = vec![(1, 100), (2, 200), (3, 300), (4, 400)]; + endowed.insert(btc_assets.0, endowed_info); + + let mut ext = self.build(assets, endowed); + ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(test); + } + + pub fn build_no_endowed_and_execute(self, test: impl FnOnce() -> ()) { + let btc_assets = btc(); + let assets = vec![(btc_assets.0, btc_assets.1, btc_assets.2, true, true)]; + let mut ext = self.build(assets, Default::default()); + ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(test); + } +} + +pub type System = frame_system::Module; +pub type Balances = pallet_balances::Module; +pub type XAssets = Module; +pub type XAssetsErr = Error; diff --git a/xpallets/assets/src/pcx.rs b/xpallets/assets/src/pcx.rs deleted file mode 100644 index b752d0007c249..0000000000000 --- a/xpallets/assets/src/pcx.rs +++ /dev/null @@ -1,183 +0,0 @@ -use sp_runtime::traits::{CheckedSub, Zero}; -use sp_std::{cmp, result}; - -use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - ensure, - traits::{ - BalanceStatus, Currency, ExistenceRequirement, Imbalance, ReservableCurrency, - SignedImbalance, WithdrawReason, WithdrawReasons, - }, -}; - -use crate::traits::ChainT; -use crate::types::{AssetType, NegativeImbalance, PositiveImbalance}; -use crate::{Error, Module, Trait}; - -impl Currency for Module { - type Balance = T::Balance; - type PositiveImbalance = PositiveImbalance; - type NegativeImbalance = NegativeImbalance; - - fn total_balance(who: &T::AccountId) -> Self::Balance { - Self::pcx_all_type_balance(who) - } - - fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { - Self::free_balance(who) >= value - } - - fn total_issuance() -> Self::Balance { - Self::pcx_total_balance() - } - - fn minimum_balance() -> Self::Balance { - Zero::zero() - } - - fn burn(_amount: Self::Balance) -> Self::PositiveImbalance { - PositiveImbalance::zero() - } - - fn issue(_amount: Self::Balance) -> Self::NegativeImbalance { - NegativeImbalance::zero() - } - - fn free_balance(who: &T::AccountId) -> Self::Balance { - Self::pcx_free_balance(who) - } - - fn ensure_can_withdraw( - who: &T::AccountId, - amount: Self::Balance, - _reason: WithdrawReasons, - _new_balance: Self::Balance, - ) -> DispatchResult { - if amount.is_zero() { - return Ok(()); - } - let balance = Self::free_balance(who); - ensure!(balance >= amount, Error::::InsufficientBalance); - Ok(()) - } - - fn transfer( - source: &T::AccountId, - dest: &T::AccountId, - value: Self::Balance, - _: ExistenceRequirement, - ) -> DispatchResult { - Self::pcx_move_free_balance(&source, &dest, value).map_err::, _>(Into::into)?; - Ok(()) - } - - fn slash( - _who: &T::AccountId, - _value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - unimplemented!() - } - - fn deposit_into_existing( - who: &T::AccountId, - value: Self::Balance, - ) -> result::Result { - Self::inner_issue(&Self::ASSET_ID, who, AssetType::Free, value) - } - - fn deposit_creating(_who: &T::AccountId, _value: Self::Balance) -> Self::PositiveImbalance { - unimplemented!() - } - - fn withdraw( - _who: &T::AccountId, - _value: Self::Balance, - _reason: WithdrawReasons, - _liveness: ExistenceRequirement, - ) -> result::Result { - unimplemented!() - } - - fn make_free_balance_be( - who: &T::AccountId, - balance: Self::Balance, - ) -> SignedImbalance { - Self::pcx_make_free_balance_be(who, balance) - } -} - -impl ReservableCurrency for Module { - fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { - Self::free_balance(who) - .checked_sub(&value) - .map_or(false, |new_balance| { - Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve.into(), new_balance) - .is_ok() - }) - } - - fn slash_reserved( - _who: &T::AccountId, - _value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - unimplemented!() - } - - fn reserved_balance(who: &T::AccountId) -> Self::Balance { - Self::pcx_type_balance(&who, AssetType::ReservedCurrency) - } - - fn reserve(who: &T::AccountId, value: Self::Balance) -> result::Result<(), DispatchError> { - let b = Self::free_balance(who); - if b < value { - Err(Error::::InsufficientBalance)?; - } - let new_balance = b - value; - Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve.into(), new_balance)?; - Self::pcx_move_balance( - who, - AssetType::Free, - who, - AssetType::ReservedCurrency, - value, - ) - .map_err::, _>(Into::into)?; - Ok(()) - } - - fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { - let b = Self::reserved_balance(who); - let actual = cmp::min(b, value); - match Self::pcx_move_balance( - who, - AssetType::ReservedCurrency, - who, - AssetType::Free, - actual, - ) { - Ok(()) => value - actual, - Err(_) => Zero::zero(), - } - } - - fn repatriate_reserved( - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - _status: BalanceStatus, - ) -> result::Result { - let b = Self::reserved_balance(slashed); - let slash = cmp::min(b, value); - - let _ = Self::pcx_move_balance( - slashed, - AssetType::ReservedCurrency, - beneficiary, - AssetType::Free, - slash, - ) - .map_err::, _>(Into::into)?; - - Ok(value - slash) - } -} diff --git a/xpallets/assets/src/tests.rs b/xpallets/assets/src/tests.rs new file mode 100644 index 0000000000000..711d34e10e350 --- /dev/null +++ b/xpallets/assets/src/tests.rs @@ -0,0 +1,660 @@ +use crate::mock::*; +use crate::*; + +use frame_support::{assert_noop, assert_ok}; +use frame_system::{EventRecord, Phase}; + +use xpallet_protocol::X_BTC; + +#[test] +fn test_genesis() { + let abc_id = 100; + let efd_id = 101; + let abc_assets = ( + abc_id, + AssetInfo::new::( + b"ABC".to_vec(), + b"ABC".to_vec(), + Chain::Bitcoin, + 8, + b"abc".to_vec(), + ) + .unwrap(), + AssetRestriction::DestroyFree.into(), + ); + + let efd_assets = ( + efd_id, + AssetInfo::new::( + b"EFD".to_vec(), + b"EFD Token".to_vec(), + Chain::Bitcoin, + 8, + b"efd".to_vec(), + ) + .unwrap(), + AssetRestriction::Transfer | AssetRestriction::DestroyFree, + ); + + let mut endowed = BTreeMap::new(); + let endowed_info = vec![(1, 100), (2, 200), (3, 300), (4, 400)]; + endowed.insert(abc_assets.0, endowed_info); + + let endowed_info = vec![(999, 1000)]; + endowed.insert(efd_assets.0, endowed_info); + + let assets = vec![ + (abc_assets.0, abc_assets.1, abc_assets.2, true, true), + (efd_assets.0, efd_assets.1, efd_assets.2, true, false), + ]; + + ExtBuilder::default() + .build(assets, endowed) + .execute_with(|| { + assert_eq!( + XAssets::all_type_total_asset_balance(&abc_id), + 100 + 200 + 300 + 400 + ); + assert_eq!(XAssets::all_type_total_asset_balance(&efd_id), 1000); + assert_eq!(XAssets::free_balance_of(&1, &abc_id), 100); + assert_eq!(XAssets::free_balance_of(&4, &abc_id), 400); + assert_eq!(XAssets::free_balance_of(&999, &efd_id), 1000); + + assert_noop!( + XAssets::destroy_free(&abc_id, &1, 10), + XAssetsErr::ActionNotAllowed + ); + assert_ok!(XAssets::transfer( + Origin::signed(1), + 999, + abc_id.into(), + 50_u128.into(), + b"".to_vec().into() + )); + assert_noop!( + XAssets::transfer( + Origin::signed(999), + 1, + efd_id.into(), + 50_u128.into(), + b"".to_vec().into() + ), + XAssetsErr::ActionNotAllowed + ); + }); +} + +#[test] +fn test_register() { + ExtBuilder::default().build_and_execute(|| { + let abc_id = 100; + let abc_assets = ( + abc_id, + AssetInfo::new::( + b"ABC".to_vec(), + b"ABC".to_vec(), + Chain::Bitcoin, + 8, + b"abc".to_vec(), + ) + .unwrap(), + AssetRestriction::DestroyFree.into(), + ); + assert_ok!(XAssets::register_asset( + Origin::root(), + abc_assets.0, + abc_assets.1.clone(), + abc_assets.2, + false, + false + )); + assert_noop!( + XAssets::register_asset( + Origin::root(), + abc_assets.0, + abc_assets.1, + abc_assets.2, + false, + false + ), + XAssetsErr::AlreadyExistentToken + ); + + assert_noop!(XAssets::get_asset(&abc_id), XAssetsErr::InvalidAsset); + + assert_ok!(XAssets::recover_asset(Origin::root(), abc_id, true)); + assert!(XAssets::get_asset(&abc_id).is_ok()); + + assert_noop!( + XAssets::revoke_asset(Origin::root(), 10000), + XAssetsErr::InvalidAsset + ); + assert_noop!( + XAssets::recover_asset(Origin::root(), X_BTC, true), + XAssetsErr::InvalidAsset + ); + + assert_ok!(XAssets::revoke_asset(Origin::root(), X_BTC)); + assert_noop!(XAssets::get_asset(&X_BTC), XAssetsErr::InvalidAsset); + }) +} + +#[test] +fn test_normal_case() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!( + XAssets::all_type_total_asset_balance(&X_BTC), + 100 + 200 + 300 + 400 + ); + + assert_ok!(XAssets::transfer( + Origin::signed(1), + 999, + X_BTC.into(), + 50_u128.into(), + b"".to_vec().into() + )); + assert_eq!(XAssets::free_balance_of(&1, &X_BTC), 50); + assert_eq!(XAssets::free_balance_of(&999, &X_BTC), 50); + + assert_eq!( + XAssets::all_type_total_asset_balance(&X_BTC), + 100 + 200 + 300 + 400 + ); + + assert_ok!(XAssets::move_balance( + &X_BTC, + &1, + AssetType::Free, + &999, + AssetType::ReservedWithdrawal, + 25 + )); + assert_eq!( + XAssets::total_asset_balance_of(&X_BTC, AssetType::Free), + 1000 - 25 + ); + assert_eq!( + XAssets::total_asset_balance_of(&X_BTC, AssetType::ReservedWithdrawal), + 25 + ); + + assert_ok!(XAssets::destroy(&X_BTC, &999, 15)); + assert_eq!( + XAssets::asset_type_balance(&999, &X_BTC, AssetType::ReservedWithdrawal), + 10 + ); + assert_eq!( + XAssets::total_asset_balance_of(&X_BTC, AssetType::ReservedWithdrawal), + 10 + ); + assert_eq!( + XAssets::all_type_total_asset_balance(&X_BTC), + 100 + 200 + 300 + 400 - 15 + ); + + assert_ok!(XAssets::destroy(&X_BTC, &999, 10)); + assert_eq!( + XAssets::total_asset_balance_of(&X_BTC, AssetType::ReservedWithdrawal), + 0 + ); + // make sure the item is removed in btree-map + assert!(XAssets::total_asset_balance(&X_BTC) + .get(&AssetType::ReservedWithdrawal) + .is_none()); + assert!(XAssets::asset_balance(&999, &X_BTC) + .get(&AssetType::ReservedWithdrawal) + .is_none()); + assert_eq!( + XAssets::all_type_total_asset_balance(&X_BTC), + 100 + 200 + 300 + 400 - 25 + ); + }) +} + +#[test] +fn test_normal_issue_and_destroy() { + ExtBuilder::default().build_and_execute(|| { + let a: u64 = 1; // accountid + let btc_id = X_BTC; + + // issue + XAssets::issue(&btc_id, &a, 50).unwrap(); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 150); + assert_eq!(XAssets::all_type_total_asset_balance(&btc_id), 1050); + + // reserve + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + 25, + ) + .unwrap(); + + assert_eq!( + XAssets::asset_balance_of(&a, &btc_id, AssetType::ReservedWithdrawal), + 25 + ); + assert_eq!(XAssets::free_balance_of(&a, &btc_id), 125); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 150); + + // destroy + XAssets::destroy(&btc_id, &a, 25).unwrap(); + assert_eq!( + XAssets::asset_balance_of(&a, &btc_id, AssetType::ReservedWithdrawal), + 0 + ); + assert_eq!(XAssets::free_balance_of(&a, &btc_id), 125); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 125); + assert_eq!(XAssets::all_type_total_asset_balance(&btc_id), 1025); + }) +} + +#[test] +fn test_unlock_issue_and_destroy2() { + ExtBuilder::default().build_no_endowed_and_execute(|| { + let a: u64 = 1; // accountid + let btc_id = X_BTC; + + // issue + XAssets::issue(&btc_id, &a, 50).unwrap(); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 50); + assert_eq!(XAssets::all_type_total_asset_balance(&btc_id), 50); + + // reserve + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + 25, + ) + .unwrap(); + + assert_eq!( + XAssets::asset_balance_of(&a, &btc_id, AssetType::ReservedWithdrawal), + 25 + ); + assert_eq!(XAssets::free_balance_of(&a, &btc_id), 25); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 50); + + // unreserve + XAssets::move_balance( + &btc_id, + &a, + AssetType::ReservedWithdrawal, + &a, + AssetType::Free, + 10, + ) + .unwrap(); + + assert_eq!( + XAssets::asset_balance_of(&a, &btc_id, AssetType::ReservedWithdrawal), + 15 + ); + assert_eq!(XAssets::free_balance_of(&a, &btc_id), 35); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 50); + }) +} + +#[test] +fn test_error_issue_and_destroy1() { + ExtBuilder::default().build_no_endowed_and_execute(|| { + let a: u64 = 1; // accountid + let btc_id = X_BTC; + // issue + XAssets::issue(&btc_id, &a, 50).unwrap(); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 50); + assert_eq!(XAssets::all_type_total_asset_balance(&btc_id), 50); + // destroy first + // destroy + assert_noop!( + XAssets::destroy(&btc_id, &a, 25), + XAssetsErr::InsufficientBalance, + ); + + assert_noop!( + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + 100 + ), + AssetErr::NotEnough + ); + + // lock first + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + 25, + ) + .unwrap(); + // destroy + assert_ok!(XAssets::destroy(&btc_id, &a, 25)); + }) +} + +#[test] +fn test_error_issue_and_destroy2() { + ExtBuilder::default().build_no_endowed_and_execute(|| { + let a: u64 = 1; // accountid + let btc_id = X_BTC; + // issue + XAssets::issue(&btc_id, &a, 50).unwrap(); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 50); + assert_eq!(XAssets::all_type_total_asset_balance(&btc_id), 50); + // overflow + let i: i32 = -1; + + assert_noop!( + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + i as Balance, + ), + AssetErr::NotEnough + ); + + assert_noop!( + XAssets::issue(&btc_id, &a, i as Balance), + XAssetsErr::Overflow + ); + }) +} + +#[test] +fn test_error_issue_and_destroy3() { + ExtBuilder::default().build_no_endowed_and_execute(|| { + let a: u64 = 1; // accountid + let btc_id = X_BTC; + // lock or destroy without init + assert_noop!( + XAssets::destroy(&btc_id, &a, 25), + XAssetsErr::InsufficientBalance + ); + + assert_noop!( + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + 25 + ), + AssetErr::NotEnough + ); + + XAssets::issue(&btc_id, &a, 0).unwrap(); + assert_noop!( + XAssets::destroy(&btc_id, &a, 25), + XAssetsErr::InsufficientBalance + ); + + assert_noop!( + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + 25 + ), + AssetErr::NotEnough + ); + + XAssets::issue(&btc_id, &a, 100).unwrap(); + + XAssets::move_balance( + &btc_id, + &a, + AssetType::Free, + &a, + AssetType::ReservedWithdrawal, + 25, + ) + .unwrap(); + + assert_ok!(XAssets::destroy(&btc_id, &a, 25)); + }) +} + +#[test] +fn test_balance_btree_map() { + ExtBuilder::default().build_and_execute(|| { + let a: u64 = 100; // accountid + let b: u64 = 200; + let btc_id = X_BTC; + assert_eq!(XAssets::all_type_total_asset_balance(&btc_id), 1000); + + let _ = XAssets::issue(&X_BTC, &a, 100); + let _ = XAssets::move_balance( + &X_BTC, + &a, + AssetType::Free, + &a, + AssetType::ReservedXRC20, + 30, + ); + assert_eq!(AssetBalance::::get(&a, &btc_id).len(), 2); + assert_eq!(TotalAssetBalance::::get(&btc_id).len(), 2); + + let _ = XAssets::move_balance( + &X_BTC, + &a, + AssetType::ReservedXRC20, + &a, + AssetType::Free, + 10, + ); + let _ = XAssets::move_balance( + &X_BTC, + &a, + AssetType::ReservedXRC20, + &b, + AssetType::Free, + 20, + ); + assert_eq!(AssetBalance::::get(&a, &btc_id).len(), 1); + assert_eq!(TotalAssetBalance::::get(&btc_id).len(), 1); + assert_eq!(XAssets::free_balance_of(&a, &X_BTC,), 80); + assert_eq!(XAssets::free_balance_of(&b, &X_BTC,), 20); + assert_eq!(XAssets::all_type_total_asset_balance(&X_BTC), 1100); // 1000 + 100 + }) +} + +#[test] +fn test_account_init() { + ExtBuilder::default().build_and_execute(|| { + let a: u64 = 999; // accountid + let id1: u64 = 1000; + let btc_id = X_BTC; + assert_eq!(XAssets::all_type_total_asset_balance(&btc_id), 1000); + + // issue init + let _ = XAssets::issue(&X_BTC, &a, 100); + assert!(System::events().contains(&EventRecord { + phase: Phase::Initialization, + event: MetaEvent::system(frame_system::RawEvent::NewAccount(a)), + topics: vec![], + })); + + // transfer token init + assert_ok!(XAssets::transfer( + Origin::signed(a), + id1.into(), + btc_id.into(), + 25, + b"".to_vec().into() + )); + assert!(System::events().contains(&EventRecord { + phase: Phase::Initialization, + event: MetaEvent::system(frame_system::RawEvent::NewAccount(id1)), + topics: vec![], + })); + }) +} + +#[test] +fn test_transfer_not_init() { + ExtBuilder::default().build_and_execute(|| { + fn check_only_one_new_account(new_id: u64) { + let count = System::events() + .iter() + .filter(|e| { + **e == EventRecord { + phase: Phase::Initialization, + event: MetaEvent::system(frame_system::RawEvent::NewAccount(new_id)), + topics: vec![], + } + }) + .count(); + assert_eq!(count, 1); + } + + let a: u64 = 1; // accountid + let new_id: u64 = 1000; + let btc_id = X_BTC; + XAssets::issue(&btc_id, &a, 50).unwrap(); + assert_ok!(XAssets::transfer( + Origin::signed(a), + new_id.into(), + btc_id.into(), + 25, + b"".to_vec().into() + )); + check_only_one_new_account(new_id); + + assert_ok!(XAssets::transfer( + Origin::signed(a), + new_id.into(), + btc_id.into(), + 25, + b"".to_vec().into() + )); + check_only_one_new_account(new_id); + + { + let _ = ::Currency::deposit_creating(&a, 1000); + let _ = ::Currency::transfer(Origin::signed(a), new_id, 10); + } + check_only_one_new_account(new_id); + + assert_eq!(System::refs(&new_id), 1); + assert_ok!(XAssets::transfer( + Origin::signed(new_id), + a.into(), + btc_id.into(), + 50, + b"".to_vec().into() + )); + assert_eq!(System::refs(&new_id), 0); + assert_ok!(XAssets::transfer( + Origin::signed(a), + new_id.into(), + btc_id.into(), + 50, + b"".to_vec().into() + )); + check_only_one_new_account(new_id); + }) +} + +#[test] +fn test_transfer_token() { + ExtBuilder::default().build_no_endowed_and_execute(|| { + let a: u64 = 1; // accountid + let b: u64 = 2; // accountid + let btc_id = X_BTC; + // issue 50 to account 1 + XAssets::issue(&btc_id, &a, 50).unwrap(); + // transfer + XAssets::transfer( + Origin::signed(a), + b.into(), + btc_id.into(), + 25, + b"".to_vec().into(), + ) + .unwrap(); + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 25); + assert_eq!(XAssets::free_balance_of(&b, &btc_id), 25); + + assert_noop!( + XAssets::transfer( + Origin::signed(a), + b.into(), + btc_id.into(), + 50, + b"".to_vec().into() + ), + XAssetsErr::InsufficientBalance + ); + }) +} + +#[test] +fn test_transfer_to_self() { + ExtBuilder::default().build_no_endowed_and_execute(|| { + let a: u64 = 1; // accountid + let btc_id = X_BTC; + // issue 50 to account 1 + XAssets::issue(&btc_id, &a, 50).unwrap(); + // transfer + assert_ok!(XAssets::transfer( + Origin::signed(a), + a.into(), + btc_id.into(), + 25, + b"".to_vec().into() + )); + + assert_eq!(XAssets::all_type_asset_balance(&a, &btc_id), 50); + }) +} + +#[test] +fn test_move() { + ExtBuilder::default().build_and_execute(|| { + let a: u64 = 1; // accountid + let b: u64 = 2; // accountid + let btc_id = X_BTC; + XAssets::move_free_balance(&btc_id, &a, &b, 100).unwrap(); + assert_noop!( + XAssets::move_free_balance(&btc_id, &a, &b, 1000), + AssetErr::NotEnough + ); + assert_eq!(XAssets::free_balance_of(&a, &btc_id), 0); + assert_eq!(XAssets::free_balance_of(&b, &btc_id), 200 + 100); + + let token = X_BTC; + assert_noop!( + XAssets::move_free_balance(&token, &a, &b, 100), + AssetErr::NotEnough + ); + + XAssets::issue(&token, &a, 100).unwrap(); + XAssets::move_free_balance(&token, &a, &b, 100).unwrap(); + assert_noop!( + XAssets::move_free_balance(&token, &a, &b, 1000), + AssetErr::NotEnough + ); + + assert_eq!(XAssets::free_balance_of(&a, &token), 0); + assert_eq!(XAssets::free_balance_of(&b, &token), 200 + 100 + 100); + }) +} diff --git a/xpallets/assets/src/trigger.rs b/xpallets/assets/src/trigger.rs index 88264c4f9a935..d92e540649fb6 100644 --- a/xpallets/assets/src/trigger.rs +++ b/xpallets/assets/src/trigger.rs @@ -5,7 +5,7 @@ use chainx_primitives::AssetId; use crate::traits::{OnAssetChanged, OnAssetRegisterOrRevoke}; use crate::types::{AssetErr, AssetType}; -use crate::{Module, RawEvent, Trait}; +use crate::{BalanceOf, Module, RawEvent, Trait}; impl OnAssetChanged for () {} @@ -44,7 +44,7 @@ impl AssetChangedTrigger { from_type: AssetType, to: &T::AccountId, to_type: AssetType, - value: T::Balance, + value: BalanceOf, ) { T::OnAssetChanged::on_move_pre(id, from, from_type, to, to_type, value); } @@ -55,7 +55,7 @@ impl AssetChangedTrigger { from_type: AssetType, to: &T::AccountId, to_type: AssetType, - value: T::Balance, + value: BalanceOf, ) -> result::Result<(), AssetErr> { Module::::deposit_event(RawEvent::Move( id.clone(), @@ -73,7 +73,7 @@ impl AssetChangedTrigger { T::OnAssetChanged::on_issue_pre(id, who); } - pub fn on_issue_post(id: &AssetId, who: &T::AccountId, value: T::Balance) -> DispatchResult { + pub fn on_issue_post(id: &AssetId, who: &T::AccountId, value: BalanceOf) -> DispatchResult { Module::::deposit_event(RawEvent::Issue(id.clone(), who.clone(), value)); T::OnAssetChanged::on_issue_post(id, who, value)?; Ok(()) @@ -83,7 +83,11 @@ impl AssetChangedTrigger { T::OnAssetChanged::on_destroy_pre(id, who); } - pub fn on_destroy_post(id: &AssetId, who: &T::AccountId, value: T::Balance) -> DispatchResult { + pub fn on_destroy_post( + id: &AssetId, + who: &T::AccountId, + value: BalanceOf, + ) -> DispatchResult { Module::::deposit_event(RawEvent::Destory(id.clone(), who.clone(), value)); T::OnAssetChanged::on_destroy_post(id, who, value)?; Ok(()) @@ -93,7 +97,7 @@ impl AssetChangedTrigger { id: &AssetId, who: &T::AccountId, type_: AssetType, - value: T::Balance, + value: BalanceOf, ) -> DispatchResult { Module::::deposit_event(RawEvent::Set(id.clone(), who.clone(), type_, value)); T::OnAssetChanged::on_set_balance(id, who, type_, value)?; diff --git a/xpallets/assets/src/types.rs b/xpallets/assets/src/types.rs index c1ee7897c5af1..4eca9c4398641 100644 --- a/xpallets/assets/src/types.rs +++ b/xpallets/assets/src/types.rs @@ -6,35 +6,24 @@ use codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; // Substrate -use sp_runtime::{ - traits::{Saturating, Zero}, - RuntimeDebug, -}; +use sp_runtime::RuntimeDebug; use sp_std::{collections::btree_map::BTreeMap, prelude::*, result, slice::Iter}; -use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - traits::{Imbalance, SignedImbalance}, -}; +use frame_support::dispatch::{DispatchError, DispatchResult}; // ChainX pub use chainx_primitives::{Desc, Memo, Precision, Token}; -use super::traits::ChainT; -use super::{Error, Trait}; - -pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; +use super::{BalanceOf, Error, Trait}; const MAX_TOKEN_LEN: usize = 32; const MAX_DESC_LEN: usize = 128; -pub type SignedImbalanceT = SignedImbalance<::Balance, PositiveImbalance>; - #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub enum SignedBalance { /// A positive imbalance (funds have been created but none destroyed). - Positive(T::Balance), + Positive(BalanceOf), /// A negative imbalance (funds have been destroyed but none created). - Negative(T::Balance), + Negative(BalanceOf), } macro_rules! define_enum { @@ -303,190 +292,3 @@ pub fn is_valid_desc(desc: &[u8]) -> DispatchResult { } Ok(()) } - -mod imbalances { - use frame_support::{traits::TryDrop, StorageMap}; - use sp_std::mem; - - use chainx_primitives::AssetId; - - use super::{result, AssetType, ChainT, Imbalance, Saturating, Zero}; - use crate::{Module, TotalAssetBalance, Trait}; - - /// 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] - #[cfg_attr(feature = "std", derive(Debug, PartialEq))] - pub struct PositiveImbalance(T::Balance, AssetId, AssetType); - impl PositiveImbalance { - /// Create a new positive imbalance from a balance. - pub fn new(amount: T::Balance, id: AssetId, type_: AssetType) -> Self { - PositiveImbalance(amount, id, type_) - } - } - - /// 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] - #[cfg_attr(feature = "std", derive(Debug, PartialEq))] - pub struct NegativeImbalance(T::Balance, AssetId, AssetType); - impl NegativeImbalance { - /// Create a new negative imbalance from a balance. - pub fn new(amount: T::Balance, id: AssetId, type_: AssetType) -> Self { - NegativeImbalance(amount, id, type_) - } - } - - impl TryDrop for PositiveImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl Imbalance for PositiveImbalance { - type Opposite = NegativeImbalance; - - fn zero() -> Self { - PositiveImbalance::new(Zero::zero(), Module::::ASSET_ID, AssetType::Free) - } - - 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; - // create new object pair - let r = ( - Self(first, self.1.clone(), self.2), - Self(second, self.1.clone(), self.2), - ); - // drop self object - mem::forget(self); - r - } - - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - // drop other object - mem::forget(other); - self - } - - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - // drop other object - mem::forget(other); - } - - fn offset(self, other: Self::Opposite) -> result::Result { - let (a, b) = (self.0, other.0); - let r = if a >= b { - Ok(Self::new(a - b, self.1.clone(), self.2)) - } else { - Err(NegativeImbalance::new(b - a, self.1.clone(), self.2)) - }; - // drop tuple object - mem::forget((self, other)); - r - } - - fn peek(&self) -> T::Balance { - self.0.clone() - } - } - - impl TryDrop for NegativeImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl Imbalance for NegativeImbalance { - type Opposite = PositiveImbalance; - - fn zero() -> Self { - NegativeImbalance::new(Zero::zero(), Module::::ASSET_ID, AssetType::Free) - } - - 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; - // create object pair - let r = ( - Self(first, self.1.clone(), self.2), - Self(second, self.1.clone(), self.2), - ); - // drop self - mem::forget(self); - r - } - - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - // drop other - mem::forget(other); - self - } - - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - // drop other - mem::forget(other); - } - - fn offset(self, other: Self::Opposite) -> result::Result { - let (a, b) = (self.0, other.0); - let r = if a >= b { - Ok(Self::new(a - b, self.1.clone(), self.2)) - } else { - Err(PositiveImbalance::new(b - a, self.1.clone(), self.2)) - }; - mem::forget((self, other)); - r - } - - fn peek(&self) -> T::Balance { - self.0.clone() - } - } - - impl Drop for PositiveImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - TotalAssetBalance::::mutate(&self.1, |map| { - let balance = map.entry(self.2).or_default(); - *balance = balance.saturating_add(self.0) - }) - } - } - - impl Drop for NegativeImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - TotalAssetBalance::::mutate(&self.1, |map| { - let balance = map.entry(self.2).or_default(); - let new_balance = balance.saturating_sub(self.0); - if new_balance == Zero::zero() { - // remove Zero balance to save space - map.remove(&self.2); - } else { - *balance = new_balance; - } - }) - } - } -} diff --git a/xpallets/contracts/src/account_db.rs b/xpallets/contracts/src/account_db.rs index b4f70d5180ba5..402073f35473b 100644 --- a/xpallets/contracts/src/account_db.rs +++ b/xpallets/contracts/src/account_db.rs @@ -144,16 +144,13 @@ impl AccountDb for DirectAccountDb { >::contains_key(account) } fn get_balance(&self, account: &T::AccountId) -> BalanceOf { - as Currency>::free_balance(account) + T::Currency::free_balance(account) } fn commit(&mut self, s: ChangeSet) { let mut total_imbalance = SignedImbalance::zero(); for (address, changed) in s.into_iter() { if let Some(balance) = changed.balance() { - let imbalance = - as Currency>::make_free_balance_be( - &address, balance, - ); + let imbalance = T::Currency::make_free_balance_be(&address, balance); total_imbalance = total_imbalance.merge(imbalance); } diff --git a/xpallets/contracts/src/exec.rs b/xpallets/contracts/src/exec.rs index e9d5014f41e17..211c8d1dd948f 100644 --- a/xpallets/contracts/src/exec.rs +++ b/xpallets/contracts/src/exec.rs @@ -707,7 +707,7 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( _ => ctx.config.existential_deposit, }; - as Currency>::ensure_can_withdraw( + T::Currency::ensure_can_withdraw( transactor, value, WithdrawReason::Transfer.into(), diff --git a/xpallets/contracts/src/lib.rs b/xpallets/contracts/src/lib.rs index 575a7b58f8231..4e38fd5bda9eb 100644 --- a/xpallets/contracts/src/lib.rs +++ b/xpallets/contracts/src/lib.rs @@ -98,12 +98,13 @@ pub use crate::exec::{ExecError, ExecResult, ExecReturnValue, StatusCode}; pub use crate::gas::{Gas, GasMeter}; use codec::{Decode, Encode}; +use frame_support::dispatch::{ + DispatchResult, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo, +}; +use frame_support::traits::{Currency, Get, Randomness, Time}; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, - dispatch::{DispatchResult, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo}, - parameter_types, + decl_error, decl_event, decl_module, decl_storage, parameter_types, storage::child::ChildInfo, - traits::{Get, Randomness, Time}, weights::{GetDispatchInfo, Weight}, IsSubType, Parameter, }; @@ -112,7 +113,7 @@ use frame_system::{self as system, ensure_root, ensure_signed, RawOrigin}; use serde::{Deserialize, Serialize}; use sp_core::crypto::UncheckedFrom; use sp_runtime::{ - traits::{Convert, Hash, Zero}, + traits::{Convert, Hash, StaticLookup, Zero}, RuntimeDebug, }; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*}; @@ -292,14 +293,12 @@ where } } -// pub type BalanceOf = <::Currency as Currency< -// ::AccountId, -// >>::Balance; -// pub type NegativeImbalanceOf = -// <::Currency as Currency< -// ::AccountId, -// >>::NegativeImbalance; -pub type BalanceOf = ::Balance; +pub type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; +pub type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; parameter_types! { // /// A reasonable default value for [`Trait::SignedClaimedHandicap`]. @@ -537,12 +536,13 @@ decl_module! { #[weight = *gas_limit] pub fn call( origin, - dest: T::AccountId, + dest: ::Source, #[compact] value: BalanceOf, #[compact] gas_limit: Gas, data: Vec ) -> DispatchResultWithPostInfo { let origin = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; let mut gas_meter = GasMeter::new(gas_limit); let result = Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| { @@ -593,7 +593,7 @@ decl_module! { /// Convert asset balance to xrc20 asset. This function would call xrc20 `issue` interface. /// The gas cast would deduct the caller. #[weight = 0] - pub fn convert_to_xrc20(origin, #[compact] id: AssetId, #[compact] value: T::Balance) -> DispatchResult { + pub fn convert_to_xrc20(origin, #[compact] id: AssetId, #[compact] value: BalanceOf) -> DispatchResult { let origin = ensure_signed(origin)?; Self::issue_to_xrc20(id, origin, value) } @@ -601,7 +601,7 @@ decl_module! { /// Convert xrc20 asset to asset balance. This function could not be called from an extrinsic, /// just could be called inside the xrc20, XRC777 and etc contract instance. #[weight = 0] - pub fn convert_to_asset(origin, to: T::AccountId, #[compact] value: T::Balance) -> DispatchResult { + pub fn convert_to_asset(origin, to: T::AccountId, #[compact] value: BalanceOf) -> DispatchResult { let origin = ensure_signed(origin)?; // check asset xrc20 is exist Self::refund_to_asset(origin, to, value) @@ -640,7 +640,7 @@ decl_module! { /// Force issue xrc20 token. #[weight = 0] - pub fn force_issue_xrc20(origin, #[compact] id: AssetId, issues: Vec<(T::AccountId, T::Balance)>, gas_limit: Gas) -> DispatchResult { + pub fn force_issue_xrc20(origin, #[compact] id: AssetId, issues: Vec<(T::AccountId, BalanceOf)>, gas_limit: Gas) -> DispatchResult { ensure_root(origin)?; for (origin, value) in issues { let params = (origin.clone(), value).encode(); @@ -699,12 +699,6 @@ impl Module { gas_limit: Gas, input_data: Vec, ) -> ExecResult { - if >::get(&dest).is_none() { - return Err(ExecError { - reason: Error::::InvalidDestinationContract.into(), - buffer: input_data, - }); - } let mut gas_meter = GasMeter::new(gas_limit); Self::execute_wasm(origin, &mut gas_meter, |ctx, gas_meter| { ctx.call(dest, value, gas_meter, input_data) @@ -755,7 +749,7 @@ impl Module { Self::call_for_xrc20(id, selector, data) } - fn issue_to_xrc20(id: AssetId, origin: T::AccountId, value: T::Balance) -> DispatchResult { + fn issue_to_xrc20(id: AssetId, origin: T::AccountId, value: BalanceOf) -> DispatchResult { // check ensure_with_errorlog!( xpallet_assets::Module::::free_balance_of(&origin, &id) >= value, @@ -812,7 +806,6 @@ impl Module { &xrc20_addr, AssetType::ReservedXRC20, value, - true, ) .map_err::, _>(Into::into)?; Ok(()) @@ -860,7 +853,7 @@ impl Module { fn refund_to_asset( contract_addr: T::AccountId, to: T::AccountId, - value: T::Balance, + value: BalanceOf, ) -> DispatchResult { let id: AssetId = Self::asset_id_of_addr(&contract_addr).ok_or_else(|| { error!( @@ -892,7 +885,6 @@ impl Module { &to, AssetType::Free, value, - true, ) .map_err::, _>(Into::into)?; Ok(()) @@ -1100,7 +1092,7 @@ decl_event! { } decl_storage! { - trait Store for Module as XContracts { + trait Store for Module as RioContracts { /// Current cost schedule for contracts. CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default(); /// A mapping from an original code hash to the original code, untouched by instrumentation. @@ -1145,7 +1137,7 @@ impl Config { fn preload() -> Config { Config { schedule: >::current_schedule(), - existential_deposit: Zero::zero(), // T::Currency::minimum_balance(), + existential_deposit: T::Currency::minimum_balance(), // tombstone_deposit: T::TombstoneDeposit::get(), max_depth: T::MaxDepth::get(), max_value_size: T::MaxValueSize::get(), diff --git a/xpallets/contracts/src/rent.rs b/xpallets/contracts/src/rent.rs index 213d14ea3ccb7..f48683a648c92 100644 --- a/xpallets/contracts/src/rent.rs +++ b/xpallets/contracts/src/rent.rs @@ -23,8 +23,8 @@ use crate::{ use frame_support::storage::child; use frame_support::traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReason}; use frame_support::StorageMap; +use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult}; use sp_runtime::traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero}; -use xpallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult}; /// The amount to charge. /// @@ -227,7 +227,9 @@ fn enact_verdict( Verdict::Exempt => return Some(ContractInfo::Alive(alive_contract_info)), Verdict::Kill => { >::remove(account); - child::kill_storage(&alive_contract_info.child_trie_info()); + child::kill_storage( + &alive_contract_info.child_trie_info(), + ); >::deposit_event(RawEvent::Evicted(account.clone(), false)); None } @@ -237,7 +239,9 @@ fn enact_verdict( } // Note: this operation is heavy. - let child_storage_root = child::root(&alive_contract_info.child_trie_info()); + let child_storage_root = child::root( + &alive_contract_info.child_trie_info(), + ); let tombstone = >::new( &child_storage_root[..], @@ -246,7 +250,9 @@ fn enact_verdict( let tombstone_info = ContractInfo::Tombstone(tombstone); >::insert(account, &tombstone_info); - child::kill_storage(&alive_contract_info.child_trie_info()); + child::kill_storage( + &alive_contract_info.child_trie_info(), + ); >::deposit_event(RawEvent::Evicted(account.clone(), true)); Some(tombstone_info) diff --git a/xpallets/contracts/src/tests.rs b/xpallets/contracts/src/tests.rs index 457c0a9848dc3..5f25450c07a41 100644 --- a/xpallets/contracts/src/tests.rs +++ b/xpallets/contracts/src/tests.rs @@ -146,7 +146,7 @@ impl Convert> for Test { } } -impl xpallet_transaction_payment::Trait for Test { +impl rio_payment::Trait for Test { type Currency = Balances; type OnTransactionPayment = (); type TransactionByteFee = TransactionByteFee; diff --git a/xpallets/dex/spot/Cargo.toml b/xpallets/dex/spot/Cargo.toml index 32f3eab03ad3a..8e3cd8b2bf26c 100644 --- a/xpallets/dex/spot/Cargo.toml +++ b/xpallets/dex/spot/Cargo.toml @@ -17,12 +17,14 @@ sp-arithmetic = { git = "https://github.com/paritytech/substrate.git", tag = "v2 # Substrate pallets frame-support = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } # ChainX primitives chainx-primitives = { path = "../../../primitives", default-features = false } # ChainX pallets xpallet-assets = { path = "../../assets", default-features = false } +xpallet-protocol = { path = "../../protocol", default-features = false } xpallet-support = { path = "../../support", default-features = false } [dev-dependencies] @@ -44,9 +46,11 @@ std = [ "frame-support/std", "frame-system/std", + "pallet-balances/std", "chainx-primitives/std", "xpallet-assets/std", + "xpallet-protocol/std", "xpallet-support/std", ] diff --git a/xpallets/dex/spot/src/execution/asset.rs b/xpallets/dex/spot/src/execution/asset.rs index 7c93195bdb6d3..88e599da35f66 100644 --- a/xpallets/dex/spot/src/execution/asset.rs +++ b/xpallets/dex/spot/src/execution/asset.rs @@ -10,11 +10,11 @@ impl Module { pub(super) fn delivery_asset_to_each_other( maker_order_side: Side, pair: &TradingPairProfile, - turnover: T::Balance, + turnover: BalanceOf, price: T::Price, maker_order: &mut OrderInfo, taker_order: &mut OrderInfo, - ) -> result::Result<(T::Balance, T::Balance), Error> { + ) -> result::Result<(BalanceOf, BalanceOf), DispatchError> { let maker = &maker_order.submitter(); let taker = &taker_order.submitter(); @@ -31,8 +31,8 @@ impl Module { let maker_turnover_amount = turnover; let taker_turnover_amount = turnover_in_quote; - Self::apply_delivery(&base, maker_turnover_amount, maker, taker)?; - Self::apply_delivery("e, taker_turnover_amount, taker, maker)?; + Self::apply_delivery(base, maker_turnover_amount, maker, taker)?; + Self::apply_delivery(quote, taker_turnover_amount, taker, maker)?; Ok((maker_turnover_amount, taker_turnover_amount)) } @@ -42,8 +42,8 @@ impl Module { let maker_turnover_amount = turnover_in_quote; let taker_turnover_amount = turnover; - Self::apply_delivery(&base, taker_turnover_amount, taker, maker)?; - Self::apply_delivery("e, maker_turnover_amount, maker, taker)?; + Self::apply_delivery(base, taker_turnover_amount, taker, maker)?; + Self::apply_delivery(quote, maker_turnover_amount, maker, taker)?; Ok((maker_turnover_amount, taker_turnover_amount)) } @@ -53,59 +53,90 @@ impl Module { /// Actually move someone's ReservedDexSpot asset_id to another one's Free. #[inline] fn apply_delivery( - asset_id: &AssetId, - value: T::Balance, + asset_id: AssetId, + value: BalanceOf, from: &T::AccountId, to: &T::AccountId, - ) -> Result { - Self::move_balance(asset_id, from, ReservedDexSpot, to, Free, value) + ) -> DispatchResult { + if asset_id == xpallet_protocol::PCX { + ::Currency::unreserve(from, value); + ::Currency::transfer( + from, + to, + value, + ExistenceRequirement::KeepAlive, + )?; + NativeReserves::::mutate(from, |reserved| *reserved -= value); + } else { + Self::move_asset(asset_id, from, ReservedDexSpot, to, Free, value)?; + } + Ok(()) } /// Actually reserve the asset locked by putting order. pub(crate) fn put_order_reserve( who: &T::AccountId, - asset_id: &AssetId, - value: T::Balance, - ) -> Result { - if >::free_balance_of(who, asset_id) < value { - return Err(Error::::InsufficientBalance); + asset_id: AssetId, + value: BalanceOf, + ) -> DispatchResult { + if asset_id == xpallet_protocol::PCX { + ::Currency::reserve(who, value)?; + NativeReserves::::mutate(who, |reserved| *reserved += value); + } else { + ensure!( + >::free_balance_of(who, &asset_id) >= value, + Error::::InsufficientBalance + ); + Self::move_asset(asset_id, who, Free, who, ReservedDexSpot, value)?; } + Ok(()) + } - Self::move_balance(asset_id, who, Free, who, ReservedDexSpot, value) + /// Unreserve the locked balances in Spot in general. + fn generic_unreserve( + who: &T::AccountId, + asset_id: AssetId, + value: BalanceOf, + ) -> DispatchResult { + if asset_id == xpallet_protocol::PCX { + ::Currency::unreserve(who, value); + NativeReserves::::mutate(who, |reserved| *reserved -= value); + } else { + Self::move_asset(asset_id, who, ReservedDexSpot, who, Free, value)?; + } + Ok(()) } /// Unreserve the locked asset when the order is canceled. #[inline] pub(crate) fn cancel_order_unreserve( who: &T::AccountId, - asset_id: &AssetId, - value: T::Balance, - ) -> Result { - Self::move_balance(asset_id, who, ReservedDexSpot, who, Free, value) + asset_id: AssetId, + value: BalanceOf, + ) -> DispatchResult { + Self::generic_unreserve(who, asset_id, value) } /// Refund the remaining reserved asset when the order is fulfilled. #[inline] pub(crate) fn refund_reserved_dex_spot( who: &T::AccountId, - asset_id: &AssetId, - remaining: T::Balance, + asset_id: AssetId, + remaining: BalanceOf, ) { - let _ = Self::move_balance(asset_id, who, ReservedDexSpot, who, Free, remaining); + let _ = Self::generic_unreserve(who, asset_id, remaining); } /// Wrap the move_balance function in xassets module. - fn move_balance( - asset_id: &AssetId, + fn move_asset( + asset_id: AssetId, from: &T::AccountId, - from_type: AssetType, + from_ty: AssetType, to: &T::AccountId, - to_type: AssetType, - value: T::Balance, - ) -> Result { - >::move_balance( - asset_id, from, from_type, to, to_type, value, true, - )?; - Ok(()) + to_ty: AssetType, + value: BalanceOf, + ) -> DispatchResult { + >::move_balance(&asset_id, from, from_ty, to, to_ty, value) + .map_err(|_| DispatchError::Other("Unexpected asset error")) } } diff --git a/xpallets/dex/spot/src/execution/mod.rs b/xpallets/dex/spot/src/execution/mod.rs index fba0426a6e533..267d19bc4c2d1 100644 --- a/xpallets/dex/spot/src/execution/mod.rs +++ b/xpallets/dex/spot/src/execution/mod.rs @@ -86,6 +86,10 @@ impl Module { Ok(()) } + fn currency_precision_of(asset_id: AssetId) -> Option { + >::asset_info_of(asset_id).map(|x| x.precision()) + } + /// Converts the base currency to the quote currency given the trading pair. /// /// NOTE: There is possibly a loss of accuracy here. @@ -98,19 +102,16 @@ impl Module { /// = amount * price * 10^(quote.precision) / 10^(base.precision) * 10^(price.precision) /// = amount * price * 10^(quote.precision - base.precision - price.precision) pub(crate) fn convert_base_to_quote( - amount: T::Balance, + amount: BalanceOf, price: T::Price, pair: &TradingPairProfile, - ) -> result::Result> { - if let (Some(base), Some(quote)) = ( - >::asset_info_of(pair.base()), - >::asset_info_of(pair.quote()), + ) -> result::Result, Error> { + if let (Some(base_p), Some(quote_p)) = ( + Self::currency_precision_of(pair.base()), + Self::currency_precision_of(pair.quote()), ) { - let (base_p, quote_p, pair_p) = ( - u32::from(base.precision()), - u32::from(quote.precision()), - pair.pip_precision, - ); + let (base_p, quote_p, pair_p) = + (u32::from(base_p), u32::from(quote_p), pair.pip_precision); let (mul, s) = if quote_p >= (base_p + pair_p) { (true, 10_u128.pow(quote_p - base_p - pair_p)) diff --git a/xpallets/dex/spot/src/execution/order.rs b/xpallets/dex/spot/src/execution/order.rs index 6ed1c64e9a577..c2685a41ff5bd 100644 --- a/xpallets/dex/spot/src/execution/order.rs +++ b/xpallets/dex/spot/src/execution/order.rs @@ -59,9 +59,9 @@ impl Module { price: T::Price, order_type: OrderType, side: Side, - amount: T::Balance, - remaining: T::Balance, - ) -> Order { + amount: BalanceOf, + remaining: BalanceOf, + ) -> Order, T::Price, T::BlockNumber> { let order_id = Self::order_count_of(&who); let submitter = who.clone(); @@ -89,9 +89,9 @@ impl Module { submitter: T::AccountId, class: OrderType, side: Side, - amount: T::Balance, - remaining: T::Balance, - ) -> Order { + amount: BalanceOf, + remaining: BalanceOf, + ) -> Order, T::Price, T::BlockNumber> { let current_block = >::block_number(); let props = OrderProperty { pair_id, @@ -178,7 +178,7 @@ impl Module { ); // Execute the order at the opponent price when they match. - let _execution_result = Self::execute_order( + let execution_result = Self::execute_order( pair.id, &mut maker_order, taker_order, @@ -186,6 +186,8 @@ impl Module { turnover, ); + assert!(execution_result.is_ok(), "Match order execution paniced"); + // Remove maker_order if it has been full filled. if maker_order.is_fulfilled() { fulfilled_orders.push((maker_order.submitter(), maker_order.id())); @@ -197,7 +199,9 @@ impl Module { } // Remove the fulfilled orders as well as the quotations. - Self::remove_orders_and_quotations(pair.id, counterparty_price, fulfilled_orders); + if !fulfilled_orders.is_empty() { + Self::remove_orders_and_quotations(pair.id, counterparty_price, fulfilled_orders); + } } fn match_taker_order_buy( @@ -292,7 +296,7 @@ impl Module { /// Update the status of order after the turnover is calculated. fn update_order_on_execute( order: &mut OrderInfo, - turnover: &T::Balance, + turnover: &BalanceOf, trade_history_index: TradingHistoryIndex, ) { order.executed_indices.push(trade_history_index); @@ -321,7 +325,7 @@ impl Module { /// Due to the loss of precision in Self::convert_base_to_quote(), /// the remaining could still be non-zero when the order is full filled, which must be refunded. - fn try_refund_remaining(order: &mut OrderInfo, asset_id: &AssetId) { + fn try_refund_remaining(order: &mut OrderInfo, asset_id: AssetId) { if order.is_fulfilled() && !order.remaining.is_zero() { Self::refund_reserved_dex_spot(&order.submitter(), asset_id, order.remaining); order.remaining = Zero::zero(); @@ -337,8 +341,8 @@ impl Module { maker_order: &mut OrderInfo, taker_order: &mut OrderInfo, price: T::Price, - turnover: T::Balance, - ) -> Result { + turnover: BalanceOf, + ) -> DispatchResult { let pair = Self::trading_pair(pair_id)?; let trading_history_idx = Self::trading_history_index_of(pair_id); @@ -364,8 +368,8 @@ impl Module { Side::Sell => pair.base(), }; - Self::try_refund_remaining(maker_order, &refund_remaining_asset(maker_order)); - Self::try_refund_remaining(taker_order, &refund_remaining_asset(taker_order)); + Self::try_refund_remaining(maker_order, refund_remaining_asset(maker_order)); + Self::try_refund_remaining(taker_order, refund_remaining_asset(taker_order)); Self::insert_executed_order(maker_order); Self::insert_executed_order(taker_order); @@ -389,14 +393,14 @@ impl Module { order: &mut OrderInfo, pair: &TradingPairProfile, who: &T::AccountId, - ) -> Result { + ) -> DispatchResult { // Unreserve the remaining asset. let (refund_token, refund_amount) = match order.side() { Side::Sell => (pair.base(), order.remaining_in_base()), Side::Buy => (pair.quote(), order.remaining), }; - Self::cancel_order_unreserve(who, &refund_token, refund_amount)?; + Self::cancel_order_unreserve(who, refund_token, refund_amount)?; order.update_status_on_cancel(); order.decrease_remaining_on_cancel(refund_amount); diff --git a/xpallets/dex/spot/src/lib.rs b/xpallets/dex/spot/src/lib.rs index 6500cbf76e657..198ecb936b215 100644 --- a/xpallets/dex/spot/src/lib.rs +++ b/xpallets/dex/spot/src/lib.rs @@ -19,7 +19,11 @@ use sp_std::prelude::*; use sp_std::{cmp, fmt::Debug, result}; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure, Parameter, + decl_error, decl_event, decl_module, decl_storage, + dispatch::{DispatchError, DispatchResult}, + ensure, + traits::{Currency, ExistenceRequirement, ReservableCurrency}, + Parameter, }; use frame_system::{self as system, ensure_root, ensure_signed}; @@ -44,7 +48,11 @@ const MAX_BACKLOG_ORDER: usize = 1000; /// more time than the Block time to finish. const DEFAULT_FLUCTUATION: u32 = 100; -pub trait Trait: frame_system::Trait + xpallet_assets::Trait { +pub type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +pub trait Trait: xpallet_assets::Trait { /// The overarching event type. type Event: From> + Into<::Event>; @@ -64,7 +72,7 @@ type Result = result::Result<(), Error>; pub type OrderInfo = Order< TradingPairId, ::AccountId, - ::Balance, + BalanceOf, ::Price, ::BlockNumber, >; @@ -76,6 +84,10 @@ decl_storage! { /// How many trading pairs so far. pub TradingPairCount get(fn trading_pair_count): TradingPairId; + /// Record the exact balance of reserved native coins in Spot. + pub NativeReserves get(fn native_reserves): + map hasher(twox_64_concat) T::AccountId => BalanceOf; + /// The map from trading pair id to its static profile. pub TradingPairOf get(fn trading_pair_of): map hasher(twox_64_concat) TradingPairId => Option; @@ -130,9 +142,9 @@ decl_storage! { decl_event!( pub enum Event where + Balance = BalanceOf, ::AccountId, ::BlockNumber, - ::Balance, ::Price, { /// A new order is created. @@ -209,7 +221,7 @@ decl_module! { pair_id: TradingPairId, order_type: OrderType, side: Side, - amount: T::Balance, + amount: BalanceOf, price: T::Price ) { let who = ensure_signed(origin)?; @@ -232,7 +244,7 @@ decl_module! { Side::Sell => (pair.base(), amount) }; - Self::put_order_reserve(&who, &reserve_asset, reserve_amount)?; + Self::put_order_reserve(&who, reserve_asset, reserve_amount)?; Self::apply_put_order(who, pair_id, order_type, side, amount, price, reserve_amount)?; } @@ -362,9 +374,9 @@ impl Module { pair_id: TradingPairId, order_type: OrderType, side: Side, - amount: T::Balance, + amount: BalanceOf, price: T::Price, - reserve_amount: T::Balance, + reserve_amount: BalanceOf, ) -> Result { info!( "transactor:{:?}, pair_id:{:}, type:{:?}, side:{:?}, amount:{:?}, price:{:?}", @@ -392,7 +404,11 @@ impl Module { Self::order_info_of(who, order_id).ok_or(Error::::InvalidOrderId) } - fn do_cancel_order(who: &T::AccountId, pair_id: TradingPairId, order_id: OrderId) -> Result { + fn do_cancel_order( + who: &T::AccountId, + pair_id: TradingPairId, + order_id: OrderId, + ) -> DispatchResult { let pair = Self::trading_pair(pair_id)?; ensure!(pair.online, Error::::TradingPairOffline); @@ -411,7 +427,7 @@ impl Module { who: &T::AccountId, pair_id: TradingPairId, order_id: OrderId, - ) -> Result { + ) -> DispatchResult { info!( "[apply_cancel_order]who:{:?}, pair_id:{}, order_id:{}", who, pair_id, order_id diff --git a/xpallets/dex/spot/src/mock.rs b/xpallets/dex/spot/src/mock.rs index dee5bf13bf532..f82b52f3ff0ca 100644 --- a/xpallets/dex/spot/src/mock.rs +++ b/xpallets/dex/spot/src/mock.rs @@ -5,7 +5,7 @@ use std::{ collections::{BTreeMap, HashSet}, }; -use frame_support::{impl_outer_origin, parameter_types, weights::Weight}; +use frame_support::{impl_outer_origin, parameter_types, traits::Get, weights::Weight}; use frame_system as system; use sp_core::H256; use sp_runtime::{ @@ -19,8 +19,9 @@ use xpallet_assets::{AssetInfo, AssetRestriction, AssetRestrictions, Chain}; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; +pub(crate) type AccountIndex = u64; pub(crate) type Balance = u128; -pub(crate) type Price = u64; +pub(crate) type Price = u128; impl_outer_origin! { pub enum Origin for Test {} @@ -43,11 +44,11 @@ impl system::Trait for Test { type BaseCallFilter = (); type Origin = Origin; type Call = (); - type Index = u64; + type Index = AccountIndex; type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type Event = (); @@ -61,18 +62,33 @@ impl system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); - type AccountData = (); + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); } +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> Balance { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) + } +} + +impl pallet_balances::Trait for Test { + type Balance = Balance; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + impl Trait for Test { type Event = (); type Price = Price; } impl xpallet_assets::Trait for Test { - type Balance = Balance; + type Currency = Balances; type Event = (); type OnAssetChanged = (); type OnAssetRegisterOrRevoke = XSpot; @@ -141,10 +157,7 @@ impl ExtBuilder { (btc_asset.0, btc_asset.1, pcx_asset.2, true, true), ]; - let mut endowed = BTreeMap::new(); - let pcx_id = pcx().0; - let endowed_info = vec![]; - endowed.insert(pcx_id, endowed_info); + let endowed = BTreeMap::new(); let _ = xpallet_assets::GenesisConfig:: { assets, endowed, @@ -195,5 +208,6 @@ impl ExtBuilder { } pub type System = frame_system::Module; +pub type Balances = pallet_balances::Module; pub type XAssets = xpallet_assets::Module; pub type XSpot = Module; diff --git a/xpallets/dex/spot/src/tests.rs b/xpallets/dex/spot/src/tests.rs index ac2df426c7092..df2b2aef1db28 100644 --- a/xpallets/dex/spot/src/tests.rs +++ b/xpallets/dex/spot/src/tests.rs @@ -12,6 +12,26 @@ use xpallet_assets::AssetType; const EOS: AssetId = 8888; const ETH: AssetId = 9999; +fn t_issue_pcx(to: AccountId, value: Balance) { + let _ = Balances::deposit_creating(&to, value); +} + +fn t_generic_issue(asset_id: AssetId, to: AccountId, value: Balance) { + if asset_id == xpallet_protocol::PCX { + t_issue_pcx(to, value); + } else { + assert_ok!(XAssets::issue(&asset_id, &to, value)); + } +} + +fn t_generic_free_balance(who: AccountId, asset_id: AssetId) -> Balance { + if asset_id == xpallet_protocol::PCX { + Balances::free_balance(who) + } else { + XAssets::free_balance_of(&who, &asset_id) + } +} + fn t_trading_pair_of(idx: TradingPairId) -> TradingPairProfile { XSpot::trading_pair_of(idx).unwrap() } @@ -48,6 +68,10 @@ fn t_put_order_sell( ) } +fn t_cancel_order(who: AccountId, pair_id: TradingPairId, order_id: OrderId) -> DispatchResult { + XSpot::cancel_order(Origin::signed(who), pair_id, order_id) +} + fn t_set_handicap(pair_idx: TradingPairId, highest_bid: Price, lowest_offer: Price) { assert_ok!(XSpot::set_handicap( Origin::root(), @@ -100,7 +124,7 @@ fn convert_base_to_quote_should_work() { let trading_pair = XSpot::trading_pair_of(0).unwrap(); let amount = 1_000u128; - let price = 1_210_000u64; + let price = 1_210_000u128; assert_eq!(t_convert_base_to_quote(amount, price, &trading_pair), 1); }) @@ -109,16 +133,57 @@ fn convert_base_to_quote_should_work() { #[test] fn put_order_reserve_should_work() { ExtBuilder::default().build_and_execute(|| { - let trading_pair = XSpot::trading_pair_of(0).unwrap(); + let pair_id = 0; + let who = 1; + let trading_pair = XSpot::trading_pair_of(pair_id).unwrap(); - t_set_handicap(0, 1_000_000, 1_100_000); + t_set_handicap(pair_id, 1_000_000, 1_100_000); - assert_ok!(XAssets::issue(&trading_pair.quote(), &1, 10)); - assert_eq!(XAssets::free_balance_of(&1, &trading_pair.quote()), 10); + // Reserve asset. + t_generic_issue(trading_pair.quote(), who, 10); + assert_eq!(t_generic_free_balance(who, trading_pair.quote()), 10); + assert_ok!(t_put_order_buy(who, pair_id, 1000, 1_000_200)); + assert_eq!(t_generic_free_balance(who, trading_pair.quote()), 9); - assert_ok!(t_put_order_buy(1, 0, 1000, 1_000_200)); + // Reserve native coin, 100 native coins should be reserved. + t_issue_pcx(who, 1000); + assert_ok!(t_put_order_sell(who, pair_id, 100, 1_210_000)); + assert_eq!( + frame_system::Account::::get(&who).data, + pallet_balances::AccountData { + free: 900, + reserved: 100, + misc_frozen: 0, + fee_frozen: 0 + } + ); + assert_eq!(XSpot::native_reserves(&who), 100); + + // Reserve native coin, 200 more native coins should be reserved. + assert_ok!(t_put_order_sell(who, pair_id, 200, 1_210_000)); + assert_eq!( + frame_system::Account::::get(&who).data, + pallet_balances::AccountData { + free: 700, + reserved: 300, + misc_frozen: 0, + fee_frozen: 0 + } + ); + assert_eq!(XSpot::native_reserves(&who), 300); - assert_eq!(XAssets::free_balance_of(&1, &trading_pair.quote()), 9); + // 100 native coins should be unreserved. + assert_ok!(t_cancel_order(who, pair_id, 2)); + assert_eq!( + frame_system::Account::::get(&1).data, + pallet_balances::AccountData { + free: 900, + reserved: 100, + misc_frozen: 0, + fee_frozen: 0 + } + ); + assert_eq!(XSpot::native_reserves(&1), 100); }) } @@ -167,7 +232,7 @@ fn price_too_high_or_too_low_should_not_work() { ); t_set_handicap(0, 1_000_000, 1_100_000); - assert_ok!(XAssets::pcx_issue(&1, 1000)); + t_issue_pcx(1, 1000); assert_ok!(t_put_order_sell(1, 0, 1000, 1_210_000)); @@ -187,9 +252,9 @@ fn update_handicap_should_work() { ExtBuilder::default().build_and_execute(|| { let trading_pair = XSpot::trading_pair_of(0).unwrap(); - assert_ok!(XAssets::issue(&trading_pair.quote(), &1, 10)); - assert_ok!(XAssets::pcx_issue(&2, 2000)); - assert_ok!(XAssets::pcx_issue(&3, 2000)); + t_generic_issue(trading_pair.quote(), 1, 10); + t_issue_pcx(2, 2000); + t_issue_pcx(3, 2000); assert_ok!(t_put_order_buy(1, 0, 1000, 1_210_000,)); @@ -216,9 +281,9 @@ fn match_order_should_work() { t_set_handicap(0, 1_000_000, 1_100_000); - assert_ok!(XAssets::issue(&trading_pair.quote(), &1, 10)); - assert_ok!(XAssets::pcx_issue(&2, 2000)); - assert_ok!(XAssets::pcx_issue(&3, 2000)); + t_generic_issue(trading_pair.quote(), 1, 10); + t_issue_pcx(2, 2000); + t_issue_pcx(3, 2000); assert_ok!(t_put_order_buy(1, 0, 1000, 1_000_000)); @@ -253,9 +318,9 @@ fn cancel_order_should_work() { t_set_handicap(0, 1_000_000, 1_100_000); assert_ok!(XAssets::issue(&trading_pair.quote(), &1, 10)); - assert_ok!(XAssets::pcx_issue(&1, 2000)); - assert_ok!(XAssets::pcx_issue(&2, 2000)); - assert_ok!(XAssets::pcx_issue(&3, 2000)); + t_issue_pcx(1, 2000); + t_issue_pcx(2, 2000); + t_issue_pcx(3, 2000); assert_ok!(t_put_order_buy(1, 0, 1000, 1_000_000)); assert_ok!(t_put_order_buy(1, 0, 1000, 1_000_100)); @@ -275,14 +340,14 @@ fn reap_orders_should_work() { ExtBuilder::default().build_and_execute(|| { let trading_pair = XSpot::trading_pair_of(0).unwrap(); - assert_ok!(XAssets::issue(&trading_pair.quote(), &1, 10)); - assert_ok!(XAssets::issue(&trading_pair.quote(), &2, 10)); - assert_ok!(XAssets::issue(&trading_pair.quote(), &3, 10)); - assert_ok!(XAssets::pcx_issue(&2, 20000)); - assert_ok!(XAssets::pcx_issue(&3, 20000)); - assert_ok!(XAssets::pcx_issue(&4, 20000)); + t_generic_issue(trading_pair.quote(), 1, 10); + t_generic_issue(trading_pair.quote(), 2, 10); + t_generic_issue(trading_pair.quote(), 3, 10); + t_issue_pcx(2, 20000); + t_issue_pcx(3, 20000); + t_issue_pcx(4, 20000); - assert_eq!(XAssets::free_balance_of(&1, &trading_pair.base()), 0); + assert_eq!(t_generic_free_balance(1, trading_pair.base()), 0); assert_ok!(t_put_order_buy(1, 0, 1000, 1_000_000)); assert_ok!(t_put_order_buy(1, 0, 5000, 1_200_000)); @@ -292,10 +357,10 @@ fn reap_orders_should_work() { assert_ok!(t_put_order_sell(4, 0, 20_000, 2_100_000 - 100)); - assert_eq!(XAssets::free_balance_of(&1, &trading_pair.quote()), 3); - assert_eq!(XAssets::free_balance_of(&1, &trading_pair.base()), 0); - assert_eq!(XAssets::free_balance_of(&2, &trading_pair.quote()), 6); - assert_eq!(XAssets::free_balance_of(&3, &trading_pair.quote()), 6); + assert_eq!(t_generic_free_balance(1, trading_pair.quote()), 3); + assert_eq!(t_generic_free_balance(1, trading_pair.base()), 0); + assert_eq!(t_generic_free_balance(2, trading_pair.quote()), 6); + assert_eq!(t_generic_free_balance(3, trading_pair.quote()), 6); assert_eq!(XSpot::order_info_of(4, 0).unwrap().already_filled, 1_000); }) } @@ -308,10 +373,10 @@ fn refund_remaining_of_taker_order_should_work() { let base = trading_pair.base(); let quote = trading_pair.quote(); - assert_ok!(XAssets::pcx_issue(&1, 1000000)); - assert_ok!(XAssets::pcx_issue(&2, 237000000)); + t_issue_pcx(1, 1000000); + t_issue_pcx(2, 237000000); - assert_ok!(XAssets::issue(&trading_pair.quote(), &3, 489994)); + t_generic_issue(trading_pair.quote(), 3, 489994); assert_ok!(t_put_order_sell(1, 0, 1000000, 2058800,)); // 2058 @@ -329,27 +394,21 @@ fn refund_remaining_of_taker_order_should_work() { // remaining is 1 let remaining = btc_reserved_for_buyer - btc_for_seller1 - btc_for_seller2; - let bmap = BTreeMap::new(); - assert_eq!(XAssets::asset_balance(1, base.clone()), bmap); - let mut bmap = BTreeMap::new(); bmap.insert(AssetType::Free, btc_for_seller1); assert_eq!(XAssets::asset_balance(1, quote.clone()), bmap); - let bmap = BTreeMap::new(); - assert_eq!(XAssets::asset_balance(2, base.clone()), bmap); - let mut bmap = BTreeMap::new(); bmap.insert(AssetType::Free, btc_for_seller2); assert_eq!(XAssets::asset_balance(2, quote.clone()), bmap); - let mut bmap = BTreeMap::new(); - bmap.insert(AssetType::Free, 238000000); - assert_eq!(XAssets::asset_balance(3, base.clone()), bmap); - let mut bmap = BTreeMap::new(); bmap.insert(AssetType::Free, remaining); assert_eq!(XAssets::asset_balance(3, quote.clone()), bmap); + + assert_eq!(t_generic_free_balance(1, base.clone()), 0); + assert_eq!(t_generic_free_balance(2, base.clone()), 0); + assert_eq!(t_generic_free_balance(3, base.clone()), 238000000); }) } @@ -357,23 +416,23 @@ fn refund_remaining_of_taker_order_should_work() { fn refund_remaining_of_maker_order_should_work() { ExtBuilder::default().build_and_execute(|| { let trading_pair = XSpot::trading_pair_of(0).unwrap(); - + // PCX let base = trading_pair.base(); + // BTC let quote = trading_pair.quote(); - assert_ok!(XAssets::pcx_issue(&1, 1000000)); - assert_ok!(XAssets::pcx_issue(&2, 237000000)); + t_issue_pcx(1, 1_000_000); + t_issue_pcx(2, 237000000); + t_generic_issue(quote, 3, 489994); - assert_ok!(XAssets::issue(&trading_pair.quote(), &3, 489994)); - - assert_ok!(t_put_order_buy(3, 0, 238000000, 2058800)); + assert_ok!(t_put_order_buy(3, 0, 238_000_000, 2_058_800)); // 489994 - let btc_reserved_for_buyer = t_convert_base_to_quote(238000000, 2058800, &trading_pair); + let btc_reserved_for_buyer = t_convert_base_to_quote(238_000_000, 2_058_800, &trading_pair); - assert_ok!(t_put_order_sell(1, 0, 1000000, 2058800)); + assert_ok!(t_put_order_sell(1, 0, 1_000_000, 2058800)); // 2058 - let btc_for_seller1 = t_convert_base_to_quote(1_000_000, 2058800, &trading_pair); + let btc_for_seller1 = t_convert_base_to_quote(1_000_000, 2_058_800, &trading_pair); assert_ok!(t_put_order_sell(2, 0, 237_000_000, 2_058_800)); // 487935 @@ -382,27 +441,21 @@ fn refund_remaining_of_maker_order_should_work() { // remaining is 1 let remaining = btc_reserved_for_buyer - btc_for_seller1 - btc_for_seller2; - let bmap = BTreeMap::new(); - assert_eq!(XAssets::asset_balance(1, base.clone()), bmap); - let mut bmap = BTreeMap::new(); bmap.insert(AssetType::Free, btc_for_seller1); - assert_eq!(XAssets::asset_balance(1, quote.clone()), bmap); - - let bmap = BTreeMap::new(); - assert_eq!(XAssets::asset_balance(2, base.clone()), bmap); + assert_eq!(XAssets::asset_balance(1, quote), bmap); let mut bmap = BTreeMap::new(); bmap.insert(AssetType::Free, btc_for_seller2); - assert_eq!(XAssets::asset_balance(2, quote.clone()), bmap); - - let mut bmap = BTreeMap::new(); - bmap.insert(AssetType::Free, 238_000_000); - assert_eq!(XAssets::asset_balance(3, base.clone()), bmap); + assert_eq!(XAssets::asset_balance(2, quote), bmap); let mut bmap = BTreeMap::new(); bmap.insert(AssetType::Free, remaining); - assert_eq!(XAssets::asset_balance(3, quote.clone()), bmap); + assert_eq!(XAssets::asset_balance(3, quote), bmap); + + assert_eq!(t_generic_free_balance(1, base), 0); + assert_eq!(t_generic_free_balance(2, base), 0); + assert_eq!(t_generic_free_balance(3, base), 238_000_000); }) } @@ -411,17 +464,17 @@ fn quotations_order_should_be_preserved_when_removing_orders_and_quotations() { ExtBuilder::default().build_and_execute(|| { let trading_pair = XSpot::trading_pair_of(0).unwrap(); - assert_ok!(XAssets::issue(&trading_pair.quote(), &1, 100)); - assert_ok!(XAssets::issue(&trading_pair.quote(), &2, 100)); - assert_ok!(XAssets::issue(&trading_pair.quote(), &3, 100)); + t_generic_issue(trading_pair.quote(), 1, 100); + t_generic_issue(trading_pair.quote(), 2, 100); + t_generic_issue(trading_pair.quote(), 3, 100); - assert_ok!(XAssets::pcx_issue(&2, 20000)); - assert_ok!(XAssets::pcx_issue(&3, 20000)); - assert_ok!(XAssets::pcx_issue(&4, 20000)); - assert_ok!(XAssets::pcx_issue(&5, 20000)); - assert_ok!(XAssets::pcx_issue(&6, 20000)); + t_issue_pcx(2, 20000); + t_issue_pcx(3, 20000); + t_issue_pcx(4, 20000); + t_issue_pcx(5, 20000); + t_issue_pcx(6, 20000); - assert_eq!(XAssets::free_balance_of(&1, &trading_pair.base()), 0); + assert_eq!(t_generic_free_balance(1, trading_pair.base()), 0); assert_ok!(t_put_order_buy(1, 0, 1_000, 1_000_000)); assert_ok!(t_put_order_buy(2, 0, 5_000, 1_100_000)); diff --git a/xpallets/gateway/bitcoin/src/lib.rs b/xpallets/gateway/bitcoin/src/lib.rs index b711acb8b32e9..10872312ed01d 100644 --- a/xpallets/gateway/bitcoin/src/lib.rs +++ b/xpallets/gateway/bitcoin/src/lib.rs @@ -21,6 +21,7 @@ use frame_support::{ decl_error, decl_event, decl_module, decl_storage, dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, PostDispatchInfo}, ensure, + traits::Currency, }; use frame_system::{self as system, ensure_root, ensure_signed}; @@ -54,6 +55,10 @@ pub use self::types::{BtcParams, BtcTxVerifier, BtcWithdrawalProposal}; use crate::trustee::get_trustee_address_pair; use crate::tx::{remove_pending_deposit, utils::addr2vecu8}; +pub type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + pub trait Trait: frame_system::Trait + pallet_timestamp::Trait @@ -146,7 +151,7 @@ decl_error! { decl_event!( pub enum Event where ::AccountId, - ::Balance + Balance = BalanceOf { /// block hash InsertHeader(H256), @@ -403,7 +408,7 @@ decl_module! { } } -impl ChainT for Module { +impl ChainT> for Module { const ASSET_ID: AssetId = xpallet_protocol::X_BTC; fn chain() -> Chain { @@ -432,12 +437,12 @@ impl ChainT for Module { fn withdrawal_limit( asset_id: &AssetId, - ) -> result::Result, DispatchError> { + ) -> result::Result>, DispatchError> { if *asset_id != Self::ASSET_ID { Err(xpallet_assets::Error::::ActionNotAllowed)? } let fee = Self::btc_withdrawal_fee().saturated_into(); - let limit = WithdrawalLimit:: { + let limit = WithdrawalLimit::> { minimal_withdrawal: fee * 3.saturated_into() / 2.saturated_into(), fee, }; diff --git a/xpallets/gateway/bitcoin/src/tx/mod.rs b/xpallets/gateway/bitcoin/src/tx/mod.rs index e8b08c2cc6faf..3cb2c0d979171 100644 --- a/xpallets/gateway/bitcoin/src/tx/mod.rs +++ b/xpallets/gateway/bitcoin/src/tx/mod.rs @@ -38,7 +38,8 @@ use crate::types::{ AccountInfo, BtcDepositCache, BtcTxResult, BtcTxState, DepositInfo, MetaTxType, }; use crate::{ - AddressBinding, BoundAddressOf, Module, PendingDeposits, RawEvent, Trait, WithdrawalProposal, + AddressBinding, BalanceOf, BoundAddressOf, Module, PendingDeposits, RawEvent, Trait, + WithdrawalProposal, }; pub fn process_tx( @@ -350,7 +351,7 @@ fn deposit(hash: H256, deposit_info: DepositInfo) -> Btc fn deposit_token(who: &T::AccountId, balance: u64) -> DispatchResult { let id: AssetId = as ChainT<_>>::ASSET_ID; - let b: T::Balance = balance.saturated_into(); + let b: BalanceOf = balance.saturated_into(); let _ = >::deposit(&who, &id, b).map_err(|e| { error!( "deposit error!, must use root to fix this error. reason:{:?}", diff --git a/xpallets/gateway/common/rpc/src/lib.rs b/xpallets/gateway/common/rpc/src/lib.rs index cf0ca2c8b5d14..892c92d70ee43 100644 --- a/xpallets/gateway/common/rpc/src/lib.rs +++ b/xpallets/gateway/common/rpc/src/lib.rs @@ -24,6 +24,7 @@ use xpallet_support::RpcBalance; /// XGatewayCommon RPC methods. #[rpc] pub trait XGatewayCommonApi { + /// Get withdrawal limit(minimal_withdrawal&fee) for an AssetId #[rpc(name = "xgatewaycommon_withdrawalLimit")] fn withdrawal_limit( &self, @@ -31,6 +32,7 @@ pub trait XGatewayCommonApi { at: Option, ) -> Result>; + /// Use the params to verify whether the withdrawal apply is valid. Notice those params is same as the params for call `XGatewayCommon::withdraw(...)`, including checking address is valid or something else. Front-end should use this rpc to check params first, than could create the extrinsic. #[rpc(name = "xgatewaycommon_verifyWithdrawal")] fn verify_withdrawal( &self, @@ -41,9 +43,11 @@ pub trait XGatewayCommonApi { at: Option, ) -> Result<()>; + /// Return the trustee multisig address for all chain. #[rpc(name = "xgatewaycommon_trusteeMultisigs")] fn multisigs(&self, at: Option) -> Result>; + /// Return bitcoin trustee registered property info for an account(e.g. registered hot/cold address) #[rpc(name = "xgatewaycommon_bitcoinTrusteeProperties")] fn btc_trustee_properties( &self, @@ -51,12 +55,14 @@ pub trait XGatewayCommonApi { at: Option, ) -> Result; + /// Return bitcoin trustee for current session(e.g. trustee hot/cold address and else) #[rpc(name = "xgatewaycommon_bitcoinTrusteeSessionInfo")] fn btc_trustee_session_info( &self, at: Option, ) -> Result>; + /// Try to generate bitcoin trustee info for a list of candidates. (this api is used to check the trustee info which would be generated by those candidates) #[rpc(name = "xgatewaycommon_bitcoinGenerateTrusteeSessionInfo")] fn btc_generate_trustee_session_info( &self, diff --git a/xpallets/gateway/common/src/lib.rs b/xpallets/gateway/common/src/lib.rs index c8f69525bd499..29d347743bb03 100644 --- a/xpallets/gateway/common/src/lib.rs +++ b/xpallets/gateway/common/src/lib.rs @@ -16,7 +16,9 @@ use sp_std::{collections::btree_map::BTreeMap, convert::TryFrom, prelude::*, res use frame_support::{ decl_error, decl_event, decl_module, decl_storage, dispatch::{DispatchError, DispatchResult}, - ensure, IterableStorageMap, + ensure, + traits::Currency, + IterableStorageMap, }; use frame_system::{self as system, ensure_root, ensure_signed}; @@ -35,12 +37,16 @@ use crate::types::{ TrusteeIntentionProps, }; +pub type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + pub trait Trait: system::Trait + pallet_multisig::Trait + xpallet_gateway_records::Trait { type Event: From> + Into<::Event>; type Validator: Validator; // for chain - type Bitcoin: ChainT; + type Bitcoin: ChainT>; type BitcoinTrustee: TrusteeForChain< Self::AccountId, trustees::bitcoin::BtcTrusteeType, @@ -87,7 +93,7 @@ decl_module! { fn deposit_event() = default; #[weight = 0] - fn withdraw(origin, #[compact] asset_id: AssetId, #[compact] value: T::Balance, addr: AddrStr, ext: Memo) -> DispatchResult { + fn withdraw(origin, #[compact] asset_id: AssetId, #[compact] value: BalanceOf, addr: AddrStr, ext: Memo) -> DispatchResult { let who = ensure_signed(origin)?; Self::apply_withdraw(who, asset_id, value, addr, ext) } @@ -193,7 +199,7 @@ impl Module { fn apply_withdraw( who: T::AccountId, asset_id: AssetId, - value: T::Balance, + value: BalanceOf, addr: AddrStr, ext: Memo, ) -> DispatchResult { @@ -210,7 +216,7 @@ impl Module { pub fn withdrawal_limit( asset_id: &AssetId, - ) -> result::Result, DispatchError> { + ) -> result::Result>, DispatchError> { let info = xpallet_assets::Module::::asset_info_of(&asset_id) .ok_or(xpallet_assets::Error::::InvalidAsset)?; let chain = info.chain(); @@ -222,7 +228,7 @@ impl Module { pub fn verify_withdrawal( asset_id: AssetId, - value: T::Balance, + value: BalanceOf, addr: &[u8], ext: &Memo, ) -> DispatchResult { diff --git a/xpallets/gateway/records/rpc/src/lib.rs b/xpallets/gateway/records/rpc/src/lib.rs index 8bd35e87a41d0..a0c18872b3a2f 100644 --- a/xpallets/gateway/records/rpc/src/lib.rs +++ b/xpallets/gateway/records/rpc/src/lib.rs @@ -33,12 +33,14 @@ impl XGatewayRecords { #[rpc] pub trait XGatewayRecordsApi { + /// Return current withdraw list(include Applying and Processing withdraw state) #[rpc(name = "xgatewayrecords_withdrawalList")] fn withdrawal_list( &self, at: Option, ) -> Result>>; + /// Return current withdraw list for a chain(include Applying and Processing withdraw state) #[rpc(name = "xgatewayrecords_withdrawalListByChain")] fn withdrawal_list_by_chain( &self, @@ -46,6 +48,7 @@ pub trait XGatewayRecordsApi { at: Option, ) -> Result>>; + /// Return current pending withdraw list for a chain #[rpc(name = "xgatewayrecords_pendingWithdrawalList")] fn pending_withdrawal_list( &self, diff --git a/xpallets/gateway/records/src/lib.rs b/xpallets/gateway/records/src/lib.rs index f7545eea9d05e..670f55e444601 100644 --- a/xpallets/gateway/records/src/lib.rs +++ b/xpallets/gateway/records/src/lib.rs @@ -8,7 +8,8 @@ pub mod types; // Substrate use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, IterableStorageMap, + decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, traits::Currency, + IterableStorageMap, }; use frame_system::{self as system, ensure_root}; use sp_std::prelude::*; @@ -16,13 +17,17 @@ use sp_std::prelude::*; // ChainX use chainx_primitives::{AddrStr, AssetId, Memo}; -use xpallet_assets::{AssetType, Chain, ChainT}; +use xpallet_assets::{AssetType, Chain}; use xpallet_support::{error, info, try_addr, warn}; pub use self::types::{Withdrawal, WithdrawalRecord, WithdrawalState}; use sp_std::collections::btree_map::BTreeMap; +pub type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + pub trait Trait: system::Trait + xpallet_assets::Trait { /// The overarching event type. type Event: From> + Into<::Event>; @@ -53,13 +58,13 @@ decl_module! { fn deposit_event() = default; // only for root #[weight = 0] - fn root_deposit(origin, who: T::AccountId, #[compact] asset_id: AssetId, #[compact] balance: T::Balance) -> DispatchResult { + fn root_deposit(origin, who: T::AccountId, #[compact] asset_id: AssetId, #[compact] balance: BalanceOf) -> DispatchResult { ensure_root(origin)?; Self::deposit(&who, &asset_id, balance) } #[weight = 0] - fn root_withdrawal(origin, who: T::AccountId, #[compact] asset_id: AssetId, #[compact] balance: T::Balance) -> DispatchResult { + fn root_withdrawal(origin, who: T::AccountId, #[compact] asset_id: AssetId, #[compact] balance: BalanceOf) -> DispatchResult { ensure_root(origin)?; Self::withdrawal(&who, &asset_id, balance, Default::default(), Default::default()) } @@ -93,8 +98,8 @@ decl_module! { decl_event!( pub enum Event where ::AccountId, - ::Balance, - WithdrawalRecord = WithdrawalRecord<::AccountId, ::Balance, ::BlockNumber> { + Balance = BalanceOf, + WithdrawalRecord = WithdrawalRecord<::AccountId, BalanceOf, ::BlockNumber> { Deposit(AccountId, AssetId, Balance), ApplyWithdrawal(u32, WithdrawalRecord), FinishWithdrawal(u32, WithdrawalState), @@ -105,7 +110,7 @@ decl_storage! { trait Store for Module as XGatewayRecords { /// withdrawal applications collection, use serial number to mark them, and has prev and next to link them pub PendingWithdrawals get(fn pending_withdrawals):map hasher(twox_64_concat) u32 - => Option>; + => Option, T::BlockNumber>>; pub WithdrawalStateOf get(fn state_of): map hasher(twox_64_concat) u32 => Option; /// withdrawal WithdrawalRecord serial number @@ -115,10 +120,7 @@ decl_storage! { impl Module { /// deposit/withdrawal pre-process - fn check_asset(_: &T::AccountId, asset_id: &AssetId) -> DispatchResult { - if *asset_id == as ChainT<_>>::ASSET_ID { - Err(Error::::DenyNativeAsset)?; - } + fn check_asset(_: &T::AccountId, _: &AssetId) -> DispatchResult { // other check Ok(()) } @@ -126,7 +128,7 @@ impl Module { fn check_withdrawal( who: &T::AccountId, asset_id: &AssetId, - value: T::Balance, + value: BalanceOf, ) -> DispatchResult { Self::check_asset(who, asset_id)?; @@ -156,7 +158,11 @@ impl Module { impl Module { /// deposit, notice this func has include deposit_init and deposit_finish (not wait for block confirm process) - pub fn deposit(who: &T::AccountId, asset_id: &AssetId, balance: T::Balance) -> DispatchResult { + pub fn deposit( + who: &T::AccountId, + asset_id: &AssetId, + balance: BalanceOf, + ) -> DispatchResult { Self::check_asset(who, asset_id)?; info!( @@ -173,7 +179,7 @@ impl Module { pub fn withdrawal( who: &T::AccountId, asset_id: &AssetId, - balance: T::Balance, + balance: BalanceOf, addr: AddrStr, ext: Memo, ) -> DispatchResult { @@ -191,7 +197,7 @@ impl Module { ext ); - let appl = WithdrawalRecord::::new( + let appl = WithdrawalRecord::, T::BlockNumber>::new( who.clone(), *asset_id, balance, @@ -360,7 +366,7 @@ impl Module { Ok(()) } - fn lock(who: &T::AccountId, asset_id: &AssetId, value: T::Balance) -> DispatchResult { + fn lock(who: &T::AccountId, asset_id: &AssetId, value: BalanceOf) -> DispatchResult { let _ = xpallet_assets::Module::::move_balance( asset_id, who, @@ -368,13 +374,12 @@ impl Module { who, AssetType::ReservedWithdrawal, value, - true, ) .map_err::, _>(Into::into)?; Ok(()) } - fn unlock(who: &T::AccountId, asset_id: &AssetId, value: T::Balance) -> DispatchResult { + fn unlock(who: &T::AccountId, asset_id: &AssetId, value: BalanceOf) -> DispatchResult { let _ = xpallet_assets::Module::::move_balance( asset_id, who, @@ -382,18 +387,17 @@ impl Module { who, AssetType::Free, value, - true, ) .map_err::, _>(Into::into)?; Ok(()) } - fn destroy(who: &T::AccountId, asset_id: &AssetId, value: T::Balance) -> DispatchResult { + fn destroy(who: &T::AccountId, asset_id: &AssetId, value: BalanceOf) -> DispatchResult { let _ = xpallet_assets::Module::::destroy(&asset_id, &who, value)?; Ok(()) } - pub fn withdrawal_list() -> BTreeMap> + pub fn withdrawal_list() -> BTreeMap, T::BlockNumber>> { PendingWithdrawals::::iter() .map(|(id, record)| { @@ -407,7 +411,7 @@ impl Module { pub fn withdrawals_list_by_chain( chain: Chain, - ) -> BTreeMap> { + ) -> BTreeMap, T::BlockNumber>> { Self::withdrawal_list() .into_iter() .filter(|(_, withdrawal)| { diff --git a/xpallets/mining/asset/Cargo.toml b/xpallets/mining/asset/Cargo.toml index f9fb43254dc07..483119b32494e 100644 --- a/xpallets/mining/asset/Cargo.toml +++ b/xpallets/mining/asset/Cargo.toml @@ -34,6 +34,7 @@ sp-core = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-r sp-io = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } pallet-session = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } pallet-timestamp = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } +pallet-balances = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4" } env_logger = "0.7.1" [features] diff --git a/xpallets/mining/asset/src/impls.rs b/xpallets/mining/asset/src/impls.rs index 36054b464c1a1..2596d03cc6f80 100644 --- a/xpallets/mining/asset/src/impls.rs +++ b/xpallets/mining/asset/src/impls.rs @@ -8,8 +8,8 @@ use xp_mining_common::{ }; use xp_mining_staking::MiningPower; -impl<'a, T: Trait> BaseMiningWeight for AssetLedgerWrapper<'a, T> { - fn amount(&self) -> T::Balance { +impl<'a, T: Trait> BaseMiningWeight, T::BlockNumber> for AssetLedgerWrapper<'a, T> { + fn amount(&self) -> BalanceOf { xpallet_assets::Module::::all_type_total_asset_balance(&self.asset_id) } @@ -30,8 +30,8 @@ impl<'a, T: Trait> BaseMiningWeight for AssetLedgerW } } -impl<'a, T: Trait> BaseMiningWeight for MinerLedgerWrapper<'a, T> { - fn amount(&self) -> T::Balance { +impl<'a, T: Trait> BaseMiningWeight, T::BlockNumber> for MinerLedgerWrapper<'a, T> { + fn amount(&self) -> BalanceOf { xpallet_assets::Module::::all_type_asset_balance(&self.miner, &self.asset_id) } @@ -63,7 +63,7 @@ impl ComputeMiningWeight for Module { ) -> WeightFactors { let mut inner = MinerLedgers::::get(who, target); let wrapper = MinerLedgerWrapper::::new(who, target, &mut inner); - generic_weight_factors::(wrapper, current_block) + generic_weight_factors::, T::BlockNumber, _>(wrapper, current_block) } fn claimee_weight_factors( @@ -72,11 +72,11 @@ impl ComputeMiningWeight for Module { ) -> WeightFactors { let mut inner = AssetLedgers::::get(target); let wrapper = AssetLedgerWrapper::::new(target, &mut inner); - generic_weight_factors::(wrapper, current_block) + generic_weight_factors::, T::BlockNumber, _>(wrapper, current_block) } } -impl xpallet_assets::OnAssetChanged for Module { +impl xpallet_assets::OnAssetChanged> for Module { fn on_issue_pre(target: &AssetId, source: &T::AccountId) { if xpallet_protocol::PCX == *target { return; @@ -90,7 +90,7 @@ impl xpallet_assets::OnAssetChanged for Modu fn on_issue_post( target: &AssetId, source: &T::AccountId, - _value: T::Balance, + _value: BalanceOf, ) -> DispatchResult { if xpallet_protocol::PCX == *target { return Ok(()); @@ -104,7 +104,7 @@ impl xpallet_assets::OnAssetChanged for Modu _: AssetType, to: &T::AccountId, _: AssetType, - _: T::Balance, + _: BalanceOf, ) { if xpallet_protocol::PCX == *asset_id || from == to { return; @@ -127,10 +127,11 @@ impl Module { claimer: &T::AccountId, _claimee: &AssetId, _claimee_reward_pot: &T::AccountId, - dividend: T::Balance, + dividend: BalanceOf, ) -> Result<(), Error> { // todo!("referral_or_treasury 10%, claimer 90%") - let _ = xpallet_assets::Module::::pcx_issue(claimer, dividend); + // FIXME + // let _ = xpallet_assets::Module::::pcx_issue(claimer, dividend); Ok(()) } } @@ -150,7 +151,8 @@ impl Claim for Module { Self::passed_enough_interval(claimer, claimee, frequency_limit, current_block)?; let claimee_reward_pot = T::DetermineRewardPotAccount::reward_pot_account_for(claimee); - let reward_pot_balance = xpallet_assets::Module::::pcx_free_balance(&claimee_reward_pot); + let reward_pot_balance = + ::Currency::free_balance(&claimee_reward_pot); let (dividend, source_weight, target_weight) = >::compute_dividend( @@ -230,7 +232,7 @@ where } } -impl xp_mining_staking::AssetMining for Module { +impl xp_mining_staking::AssetMining> for Module { /// Collects the mining power of all mining assets. fn asset_mining_power() -> Vec<(AssetId, MiningPower)> { // Currently only X-BTC asset. @@ -249,8 +251,8 @@ impl xp_mining_staking::AssetMining for Module { } /// Issues reward to the reward pot of an Asset. - fn reward(asset_id: AssetId, value: T::Balance) { + fn reward(asset_id: AssetId, value: BalanceOf) { let reward_pot = T::DetermineRewardPotAccount::reward_pot_account_for(&asset_id); - let _ = xpallet_assets::Module::::pcx_issue(&reward_pot, value); + ::Currency::deposit_creating(&reward_pot, value); } } diff --git a/xpallets/mining/asset/src/lib.rs b/xpallets/mining/asset/src/lib.rs index e337c2c0107d1..f5ab3428551e1 100644 --- a/xpallets/mining/asset/src/lib.rs +++ b/xpallets/mining/asset/src/lib.rs @@ -11,8 +11,11 @@ mod mock; mod tests; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure, + decl_error, decl_event, decl_module, decl_storage, + dispatch::DispatchResult, + ensure, storage::IterableStorageMap, + traits::{Currency, ExistenceRequirement}, }; use frame_system::{self as system, ensure_root, ensure_signed}; use sp_runtime::traits::{SaturatedConversion, Zero}; @@ -31,10 +34,17 @@ use types::*; pub use impls::SimpleAssetRewardPotAccountDeterminer; -pub trait Trait: frame_system::Trait + xpallet_assets::Trait { +pub type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +pub trait Trait: xpallet_assets::Trait { /// The overarching event type. type Event: From> + Into<::Event>; + /// + type StakingInterface: StakingInterface; + /// type TreasuryAccount: TreasuryAccount; @@ -42,10 +52,23 @@ pub trait Trait: frame_system::Trait + xpallet_assets::Trait { type DetermineRewardPotAccount: RewardPotAccountFor; } +pub trait StakingInterface { + fn staked_of(who: &AccountId) -> Balance; +} + +impl StakingInterface<::AccountId, u128> for T +where + T: xpallet_mining_staking::Trait, +{ + fn staked_of(who: &::AccountId) -> u128 { + xpallet_mining_staking::Module::::staked_of(who).saturated_into() + } +} + decl_storage! { - trait Store for Module as XStaking { + trait Store for Module as XMiningAsset { /// - pub DepositReward get(fn deposit_reward): T::Balance = 100_000.into(); + pub DepositReward get(fn deposit_reward): BalanceOf = 100_000.into(); /// pub ClaimRestrictionOf get(fn claim_restriction_of): @@ -87,8 +110,8 @@ decl_storage! { decl_event!( pub enum Event where + Balance = BalanceOf, ::AccountId, - ::Balance, { /// Claim(AccountId, AccountId, Balance), @@ -187,19 +210,19 @@ impl Module { /// This rule doesn't take effect if the staking requirement is zero. fn has_enough_staking( who: &T::AccountId, - total_dividend: T::Balance, + total_dividend: BalanceOf, staking_requirement: StakingRequirement, ) -> Result<(), Error> { if !staking_requirement.is_zero() { - let staking_locked = - >::pcx_type_balance(who, AssetType::ReservedStaking); - if staking_locked < staking_requirement.saturated_into::() * total_dividend + let staking_locked = T::StakingInterface::staked_of(who); + if staking_locked.saturated_into::>() + < staking_requirement.saturated_into::>() * total_dividend { warn!( "cannot claim due to the insufficient staking, total dividend: {:?}, staking locked: {:?}, required staking: {:?}", total_dividend, staking_locked, - staking_requirement.saturated_into::() * total_dividend + staking_requirement.saturated_into::>() * total_dividend ); return Err(Error::::InsufficientStaking); } @@ -282,14 +305,14 @@ impl Module { fn issue_deposit_reward(depositor: &T::AccountId, target: &AssetId) -> DispatchResult { let deposit_reward = Self::deposit_reward(); let reward_pot = T::DetermineRewardPotAccount::reward_pot_account_for(target); - let reward_pot_balance = xpallet_assets::Module::::pcx_free_balance(&reward_pot); + let reward_pot_balance = ::Currency::free_balance(&reward_pot); if reward_pot_balance >= deposit_reward { - xpallet_assets::Module::::pcx_move_free_balance( + ::Currency::transfer( &reward_pot, depositor, deposit_reward, - ) - .map_err(|_| Error::::AssetError)?; + ExistenceRequirement::KeepAlive, + )?; } else { warn!("asset {}'s reward pot has only {:?}, skipped issuing deposit reward for depositor {:?}", target, reward_pot_balance, depositor); } diff --git a/xpallets/mining/asset/src/mock.rs b/xpallets/mining/asset/src/mock.rs index b1aa3cb7a84cb..c12d435d55e16 100644 --- a/xpallets/mining/asset/src/mock.rs +++ b/xpallets/mining/asset/src/mock.rs @@ -38,15 +38,15 @@ use pallet_session as session; use xpallet_assets as assets; use xpallet_mining_staking as staking; -impl_outer_event! { - pub enum MetaEvent for Test { - system, - assets, - session, - staking, - mining_asset, - } -} +// impl_outer_event! { +// pub enum MetaEvent for Test { +// system, +// assets, +// session, +// staking, +// mining_asset, +// } +// } // For testing the pallet, we construct most of a mock runtime. This means // first constructing a configuration type (`Test`) which `impl`s each of the @@ -61,6 +61,8 @@ parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::from_percent(75); } +type MetaEvent = (); + impl system::Trait for Test { type BaseCallFilter = (); type Origin = Origin; @@ -83,7 +85,7 @@ impl system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); - type AccountData = (); + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); } @@ -98,8 +100,23 @@ impl pallet_timestamp::Trait for Test { type MinimumPeriod = MinimumPeriod; } -impl xpallet_assets::Trait for Test { +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> Balance { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) + } +} + +impl pallet_balances::Trait for Test { type Balance = Balance; + type Event = (); + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +impl xpallet_assets::Trait for Test { + type Currency = Balances; type Event = MetaEvent; type OnAssetChanged = XMiningAsset; type OnAssetRegisterOrRevoke = XMiningAsset; @@ -212,6 +229,7 @@ impl xp_mining_common::RewardPotAccountFor } impl xpallet_mining_staking::Trait for Test { + type Currency = Balances; type Event = MetaEvent; type AssetMining = XMiningAsset; type SessionDuration = SessionDuration; @@ -231,6 +249,7 @@ impl xp_mining_common::RewardPotAccountFor } impl Trait for Test { + type StakingInterface = Self; type Event = MetaEvent; type TreasuryAccount = (); type DetermineRewardPotAccount = DummyAssetRewardPotAccountDeterminer; @@ -293,16 +312,14 @@ impl ExtBuilder { .build_storage::() .unwrap(); - let pcx_asset = pcx(); - let assets = vec![(pcx_asset.0, pcx_asset.1, pcx_asset.2, true, false)]; + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(1, 100), (2, 200), (3, 300), (4, 400)], + } + .assimilate_storage(&mut storage); - let mut endowed = BTreeMap::new(); - let pcx_id = pcx().0; - let endowed_info = vec![(1, 100), (2, 200), (3, 300), (4, 400)]; - endowed.insert(pcx_id, endowed_info.clone()); let _ = xpallet_assets::GenesisConfig:: { - assets, - endowed, + assets: vec![], + endowed: BTreeMap::new(), memo_len: 128, } .assimilate_storage(&mut storage); @@ -366,6 +383,7 @@ impl ExtBuilder { } pub type System = frame_system::Module; +pub type Balances = pallet_balances::Module; pub type XAssets = xpallet_assets::Module; pub type Session = pallet_session::Module; pub type Timestamp = pallet_timestamp::Module; diff --git a/xpallets/mining/asset/src/tests.rs b/xpallets/mining/asset/src/tests.rs index a16b29e5038ea..7904f26c48742 100644 --- a/xpallets/mining/asset/src/tests.rs +++ b/xpallets/mining/asset/src/tests.rs @@ -15,6 +15,10 @@ fn t_bond(who: AccountId, target: AccountId, value: Balance) -> DispatchResult { XStaking::bond(Origin::signed(who), target, value, b"memo".as_ref().into()) } +fn t_issue_pcx(who: AccountId, value: Balance) { + let _ = Balances::deposit_creating(&who, value); +} + fn t_issue_xbtc(to: AccountId, value: Balance) -> DispatchResult { XAssets::issue(&X_BTC, &to, value) } @@ -43,16 +47,14 @@ fn t_xbtc_latest_total_weights() -> WeightType { } fn t_xbtc_move(from: AccountId, to: AccountId, value: Balance) { - XAssets::move_balance( + assert_ok!(XAssets::move_balance( &X_BTC, &from, AssetType::Free, &to, AssetType::Free, - value, - true, - ) - .unwrap(); + value + )); } fn t_xbtc_latest_weight_of(who: AccountId) -> WeightType { @@ -309,8 +311,8 @@ fn claim_restriction_should_work() { // Block 7 t_start_session(7); - assert_ok!(XAssets::pcx_issue(&1, 1_000_000_000_000u128)); - assert_ok!(XAssets::pcx_issue(&t_1, 1_000_000_000_000u128)); + t_issue_pcx(1, 1_000_000_000_000u128); + t_issue_pcx(t_1, 1_000_000_000_000u128); assert_ok!(t_bond(1, 1, 100_000_000_000)); // total dividend: 2464000000 let total_mining_dividend = 2_464_000_000; @@ -342,7 +344,7 @@ fn total_issuance_should_work() { all.push(TREASURY_ACCOUNT); all.push(DummyAssetRewardPotAccountDeterminer::reward_pot_account_for(&X_BTC)); - let total_issuance = || all.iter().map(XAssets::pcx_all_type_balance).sum::(); + let total_issuance = || all.iter().map(Balances::total_balance).sum::(); let initial = 100 + 200 + 300 + 400; t_start_session(1); diff --git a/xpallets/mining/staking/Cargo.toml b/xpallets/mining/staking/Cargo.toml index c09e41ccc0c91..18be7c75d8a03 100644 --- a/xpallets/mining/staking/Cargo.toml +++ b/xpallets/mining/staking/Cargo.toml @@ -19,6 +19,7 @@ sp-std = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc frame-support = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } pallet-session = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate.git", tag = "v2.0.0-rc4", default-features = false } # ChainX primitives chainx-primitives = { path = "../../../primitives", default-features = false } @@ -26,7 +27,6 @@ xp-mining-common = { path = "../../../primitives/mining/common", default-featur xp-mining-staking = { path = "../../../primitives/mining/staking", default-features = false } # ChainX pallets -xpallet-assets = { path = "../../assets", default-features = false } xpallet-support = { path = "../../support", default-features = false } [dev-dependencies] @@ -51,11 +51,11 @@ std = [ "frame-support/std", "frame-system/std", "pallet-session/std", + "pallet-balances/std", "chainx-primitives/std", "xp-mining-common/std", "xp-mining-staking/std", - "xpallet-assets/std", "xpallet-support/std", ] diff --git a/xpallets/mining/staking/src/election.rs b/xpallets/mining/staking/src/election.rs index 0710efbc7281c..2ad302ff46407 100644 --- a/xpallets/mining/staking/src/election.rs +++ b/xpallets/mining/staking/src/election.rs @@ -45,7 +45,7 @@ impl Module { } /// Filters out all the qualified validator candidates, sorted by the total nominations. - fn filter_out_candidates() -> Vec<(T::Balance, T::AccountId)> { + fn filter_out_candidates() -> Vec<(BalanceOf, T::AccountId)> { let mut candidates = Self::validator_set() .filter(Self::is_qualified_candidate) .map(|v| (Self::total_votes_of(&v), v)) diff --git a/xpallets/mining/staking/src/impls.rs b/xpallets/mining/staking/src/impls.rs index 65be6e48c97f6..6936c22a35a53 100644 --- a/xpallets/mining/staking/src/impls.rs +++ b/xpallets/mining/staking/src/impls.rs @@ -81,7 +81,7 @@ impl ComputeMiningWeight for Module { current_block: T::BlockNumber, ) -> WeightFactors { let claimer_ledger = Nominations::::get(who, target); - generic_weight_factors::(claimer_ledger, current_block) + generic_weight_factors::, T::BlockNumber, _>(claimer_ledger, current_block) } fn claimee_weight_factors( @@ -89,7 +89,7 @@ impl ComputeMiningWeight for Module { current_block: T::BlockNumber, ) -> WeightFactors { let claimee_ledger = ValidatorLedgers::::get(target); - generic_weight_factors::(claimee_ledger, current_block) + generic_weight_factors::, T::BlockNumber, _>(claimee_ledger, current_block) } } @@ -99,9 +99,9 @@ impl Module { nominator: &T::AccountId, validator: &T::AccountId, block_number: T::BlockNumber, - ) -> Result<(T::Balance, WeightType, WeightType, T::AccountId), Error> { + ) -> Result<(BalanceOf, WeightType, WeightType, T::AccountId), Error> { let validator_pot = T::DetermineRewardPotAccount::reward_pot_account_for(validator); - let reward_pot_balance = xpallet_assets::Module::::pcx_free_balance(&validator_pot); + let reward_pot_balance = Self::free_balance_of(&validator_pot); let (dividend, source_weight, target_weight) = >::compute_dividend( @@ -119,7 +119,7 @@ impl Module { nominator: &T::AccountId, validator: &T::AccountId, block_number: T::BlockNumber, - ) -> Result> { + ) -> Result, Error> { Self::calculate_dividend_on_claim(nominator, validator, block_number) .map(|(dividend, _, _, _)| dividend) } @@ -127,9 +127,10 @@ impl Module { fn allocate_dividend( claimer: &T::AccountId, pot_account: &T::AccountId, - dividend: T::Balance, - ) -> Result<(), AssetErr> { - xpallet_assets::Module::::pcx_move_free_balance(pot_account, claimer, dividend) + dividend: BalanceOf, + ) -> Result<(), Error> { + Self::move_balance(pot_account, claimer, dividend); + Ok(()) } /// Actually update the nominator vote weight given the new vote weight, block number and amount delta. @@ -138,7 +139,7 @@ impl Module { validator: &T::AccountId, new_weight: WeightType, current_block: T::BlockNumber, - delta: Delta, + delta: Delta>, ) { Nominations::::mutate(nominator, validator, |claimer_ledger| { claimer_ledger.nomination = delta.calculate(claimer_ledger.nomination); @@ -152,7 +153,7 @@ impl Module { validator: &T::AccountId, new_weight: WeightType, current_block: T::BlockNumber, - delta: Delta, + delta: Delta>, ) { ValidatorLedgers::::mutate(validator, |validator_ledger| { validator_ledger.total = delta.calculate(validator_ledger.total); diff --git a/xpallets/mining/staking/src/lib.rs b/xpallets/mining/staking/src/lib.rs index ada069c5355a7..8826d29653ac8 100644 --- a/xpallets/mining/staking/src/lib.rs +++ b/xpallets/mining/staking/src/lib.rs @@ -15,11 +15,16 @@ mod mock; mod tests; use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, ensure, storage::IterableStorageMap, - traits::Get, + decl_error, decl_event, decl_module, decl_storage, ensure, + storage::IterableStorageMap, + traits::{ + Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, WithdrawReasons, + }, }; use frame_system::{self as system, ensure_signed}; -use sp_runtime::traits::{Convert, SaturatedConversion, Saturating, Zero}; +use sp_runtime::traits::{CheckedSub, Convert, SaturatedConversion, Saturating, Zero}; +use sp_runtime::DispatchResult; +use sp_std::collections::btree_map::BTreeMap; use sp_std::prelude::*; use chainx_primitives::Memo; @@ -27,13 +32,17 @@ use xp_mining_common::{ Claim, ComputeMiningWeight, Delta, RewardPotAccountFor, ZeroMiningWeightError, }; use xp_mining_staking::{AssetMining, SessionIndex, TreasuryAccount, UnbondedIndex}; -use xpallet_assets::{AssetErr, AssetType}; use xpallet_support::{debug, RpcBalance}; pub use impls::{IdentificationTuple, SimpleValidatorRewardPotAccountDeterminer}; pub use rpc::*; pub use types::*; +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +const STAKING_ID: LockIdentifier = *b"staking "; + /// Session reward of the first 210_000 sessions. const INITIAL_REWARD: u64 = 5_000_000_000; /// Every 210_000 sessions, the session reward is cut in half. @@ -54,15 +63,18 @@ const DEFAULT_VALIDATOR_BONDING_DURATION: u64 = DEFAULT_BONDING_DURATION * 10; /// Counter for the number of eras that have passed. pub type EraIndex = u32; -pub trait Trait: xpallet_assets::Trait { +pub trait Trait: frame_system::Trait { /// The overarching event type. type Event: From> + Into<::Event>; + /// + type Currency: LockableCurrency; + /// type TreasuryAccount: TreasuryAccount; /// - type AssetMining: AssetMining; + type AssetMining: AssetMining>; /// type DetermineRewardPotAccount: RewardPotAccountFor; @@ -94,7 +106,7 @@ decl_storage! { /// Minimum value (self_bonded, total_bonded) to be a candidate of validator election. pub ValidatorCandidateRequirement get(fn validator_bond_requirement): - BondRequirement; + BondRequirement>; /// The length of a session in blocks. pub BlocksPerSession get(fn blocks_per_session) config(): @@ -135,16 +147,20 @@ decl_storage! { /// The map from nominator key to the set of keys of all validators to nominate. pub Nominators get(fn nominators): - map hasher(twox_64_concat) T::AccountId => NominatorProfile; + map hasher(twox_64_concat) T::AccountId => NominatorProfile, T::BlockNumber>; /// The map from validator key to the vote weight ledger of that validator. pub ValidatorLedgers get(fn validator_ledgers): - map hasher(twox_64_concat) T::AccountId => ValidatorLedger; + map hasher(twox_64_concat) T::AccountId => ValidatorLedger, T::BlockNumber>; /// The map from nominator to the vote weight ledger of all nominees. pub Nominations get(fn nominations): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId - => NominatorLedger; + => NominatorLedger, T::BlockNumber>; + + /// All kinds of locked balances of an account in Staking. + pub Locks get(fn locks): + map hasher(blake2_128_concat) T::AccountId => BTreeMap>; /// Mode of era forcing. pub ForceEra get(fn force_era) config(): Forcing; @@ -173,7 +189,7 @@ decl_storage! { OffendersInSession get(fn offenders_in_session): Vec; /// Minimum penalty for each slash. - pub MinimumPenalty get(fn minimum_penalty) config(): T::Balance; + pub MinimumPenalty get(fn minimum_penalty) config(): BalanceOf; /// The higher the severity, the more slash for the offences. pub OffenceSeverity get(fn offence_severity) config(): u32; @@ -181,13 +197,13 @@ decl_storage! { add_extra_genesis { config(validators): - Vec<(T::AccountId, T::Balance)>; + Vec<(T::AccountId, BalanceOf)>; config(glob_dist_ratio): (u32, u32); config(mining_ratio): (u32, u32); build(|config: &GenesisConfig| { for &(ref v, balance) in &config.validators { assert!( - >::pcx_free_balance(v) >= balance, + Module::::free_balance_of(v) >= balance, "Validator does not have enough balance to bond." ); Module::::apply_register(v); @@ -210,8 +226,8 @@ decl_storage! { decl_event!( pub enum Event where - ::AccountId, - ::Balance, + Balance = BalanceOf, + ::AccountId { /// The staker has been rewarded by this amount. `AccountId` is the stash account. Reward(AccountId, Balance), @@ -225,7 +241,7 @@ decl_event!( Claim(AccountId, AccountId, Balance), /// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance` /// from the unlocking queue. - WithdrawUnbonded(AccountId, Balance), + UnlockUnbondedWithdrawal(AccountId, Balance), /// Offenders are forcibly to be chilled due to insufficient reward pot balance. ForceChilled(SessionIndex, Vec), } @@ -248,6 +264,8 @@ decl_error! { InsufficientValue, /// Invalid rebondable value. InvalidRebondValue, + /// LockedType::Bond is missing unexpectedly. + LockedTypeMissingBond, /// InvalidUnbondValue, /// Can not schedule more unbond chunks. @@ -277,12 +295,6 @@ decl_error! { } } -impl From for Error { - fn from(_: AssetErr) -> Self { - Self::AssetError - } -} - impl From for Error { fn from(_: ZeroMiningWeightError) -> Self { Self::ZeroVoteWeight @@ -301,7 +313,7 @@ decl_module! { /// Nominates the `target` with `value` of the origin account's balance locked. #[weight = 10] - pub fn bond(origin, target: T::AccountId, value: T::Balance, memo: Memo) { + pub fn bond(origin, target: T::AccountId, value: BalanceOf, memo: Memo) { let sender = ensure_signed(origin)?; memo.check_validity()?; @@ -317,7 +329,7 @@ decl_module! { /// Switchs the nomination of `value` from one validator to another. #[weight = 10] - fn rebond(origin, from: T::AccountId, to: T::AccountId, value: T::Balance, memo: Memo) { + fn rebond(origin, from: T::AccountId, to: T::AccountId, value: BalanceOf, memo: Memo) { let sender = ensure_signed(origin)?; memo.check_validity()?; @@ -344,7 +356,7 @@ decl_module! { /// Unnominates balance `value` from validator `target`. #[weight = 10] - fn unbond(origin, target: T::AccountId, value: T::Balance, memo: Memo) { + fn unbond(origin, target: T::AccountId, value: BalanceOf, memo: Memo) { let sender = ensure_signed(origin)?; memo.check_validity()?; @@ -361,7 +373,7 @@ decl_module! { /// Frees the unbonded balances that are due. #[weight = 10] - fn withdraw_unbonded(origin, unbonded_index: UnbondedIndex) { + fn unlock_unbonded_withdrawal(origin, unbonded_index: UnbondedIndex) { let sender = ensure_signed(origin)?; let mut unbonded_chunks = Self::unbonded_chunks_of(&sender); @@ -374,14 +386,14 @@ decl_module! { ensure!(current_block > locked_until, Error::::UnbondRequestNotYetDue); // apply withdraw_unbonded - Self::unlock_unbonded_reservation(&sender, value).map_err(|_| Error::::AssetError)?; - unbonded_chunks.swap_remove(unbonded_index as usize); + Self::apply_unlock_unbonded_withdrawal(&sender, value); + unbonded_chunks.swap_remove(unbonded_index as usize); Nominators::::mutate(&sender, |nominator_profile| { nominator_profile.unbonded_chunks = unbonded_chunks; }); - Self::deposit_event(RawEvent::WithdrawUnbonded(sender, value)); + Self::deposit_event(RawEvent::UnlockUnbondedWithdrawal(sender, value)); } /// Claims the staking reward given the `target` validator. @@ -496,7 +508,7 @@ impl Module { Self::validator_set().filter(Self::is_active) } - pub fn active_validator_votes() -> impl Iterator { + pub fn active_validator_votes() -> impl Iterator)> { Self::active_validator_set().map(|v| { let total_votes = Self::total_votes_of(&v); (v, total_votes) @@ -507,8 +519,13 @@ impl Module { /// /// One (indivisible) PCX one power, only the votes of active validators are counted. #[inline] - pub fn total_staked() -> T::Balance { - Self::active_validator_votes().fold(Zero::zero(), |acc: T::Balance, (_, x)| acc + x) + pub fn total_staked() -> BalanceOf { + Self::active_validator_votes().fold(Zero::zero(), |acc: BalanceOf, (_, x)| acc + x) + } + + /// Returns the bonded balance of Staking for the given account. + pub fn staked_of(who: &T::AccountId) -> BalanceOf { + *Self::locks(who).entry(LockedType::Bonded).or_default() } #[inline] @@ -517,7 +534,7 @@ impl Module { } #[inline] - fn unbonded_chunks_of(nominator: &T::AccountId) -> Vec> { + fn unbonded_chunks_of(nominator: &T::AccountId) -> Vec, T::BlockNumber>> { Nominators::::get(nominator).unbonded_chunks } @@ -527,8 +544,8 @@ impl Module { } #[inline] - fn free_balance_of(who: &T::AccountId) -> T::Balance { - >::pcx_free_balance(who) + fn free_balance_of(who: &T::AccountId) -> BalanceOf { + T::Currency::free_balance(who) } fn is_validator_self_bonding(nominator: &T::AccountId, nominee: &T::AccountId) -> bool { @@ -577,26 +594,26 @@ impl Module { }); } - fn total_votes_of(validator: &T::AccountId) -> T::Balance { + fn total_votes_of(validator: &T::AccountId) -> BalanceOf { ValidatorLedgers::::get(validator).total } - fn validator_self_bonded(validator: &T::AccountId) -> T::Balance { + fn validator_self_bonded(validator: &T::AccountId) -> BalanceOf { Self::bonded_to(validator, validator) } #[inline] - fn bonded_to(nominator: &T::AccountId, nominee: &T::AccountId) -> T::Balance { + fn bonded_to(nominator: &T::AccountId, nominee: &T::AccountId) -> BalanceOf { Nominations::::get(nominator, nominee).nomination } - fn acceptable_votes_limit_of(validator: &T::AccountId) -> T::Balance { - Self::validator_self_bonded(validator) * T::Balance::from(Self::upper_bound_factor()) + fn acceptable_votes_limit_of(validator: &T::AccountId) -> BalanceOf { + Self::validator_self_bonded(validator) * BalanceOf::::from(Self::upper_bound_factor()) } fn check_validator_acceptable_votes_limit( validator: &T::AccountId, - value: T::Balance, + value: BalanceOf, ) -> Result<(), Error> { let cur_total = Self::total_votes_of(validator); let upper_limit = Self::acceptable_votes_limit_of(validator); @@ -607,38 +624,69 @@ impl Module { } } - fn bond_reserve(who: &T::AccountId, value: T::Balance) -> Result<(), AssetErr> { - >::pcx_move_balance( - who, - AssetType::Free, - who, - AssetType::ReservedStaking, - value, - ) + fn move_balance(from: &T::AccountId, to: &T::AccountId, value: BalanceOf) { + let _ = T::Currency::transfer(from, to, value, ExistenceRequirement::KeepAlive); } - fn unbond_reserve(who: &T::AccountId, value: T::Balance) -> Result<(), AssetErr> { - >::pcx_move_balance( - who, - AssetType::ReservedStaking, - who, - AssetType::ReservedStakingRevocation, - value, - ) + /// Set a lock on `value` of free balance of an account. + pub(crate) fn bond_reserve(who: &T::AccountId, value: BalanceOf) -> DispatchResult { + let mut new_locks = Self::locks(who); + let old_bonded = *new_locks.entry(LockedType::Bonded).or_default(); + let new_bonded = old_bonded + value; + + Self::free_balance_of(who) + .checked_sub(&new_bonded) + .ok_or(Error::::InsufficientBalance)?; + + T::Currency::set_lock(STAKING_ID, who, new_bonded, WithdrawReasons::all()); + + new_locks.insert(LockedType::Bonded, new_bonded); + + Locks::::insert(who, new_locks); + + Ok(()) } - fn unlock_unbonded_reservation(who: &T::AccountId, value: T::Balance) -> Result<(), AssetErr> { - >::pcx_move_balance( - who, - AssetType::ReservedStakingRevocation, - who, - AssetType::Free, - value, - ) + /// `unbond` only triggers the internal change of Staking locked type. + fn unbond_reserve(who: &T::AccountId, value: BalanceOf) -> Result<(), Error> { + Locks::::mutate(who, |locks| { + *locks.entry(LockedType::Bonded).or_default() -= value; + *locks.entry(LockedType::BondedWithdrawal).or_default() += value; + }); + Ok(()) + } + + /// Returns the total locked balances in Staking. + fn total_locked_of(who: &T::AccountId) -> BalanceOf { + Self::locks(who) + .values() + .fold(Zero::zero(), |acc: BalanceOf, x| acc + *x) + } + + fn apply_unlock_unbonded_withdrawal(who: &T::AccountId, value: BalanceOf) { + let new_bonded = Self::total_locked_of(who) - value; + + T::Currency::set_lock(STAKING_ID, who, new_bonded, WithdrawReasons::all()); + + Locks::::mutate(who, |locks| { + let old_value = *locks.entry(LockedType::BondedWithdrawal).or_default(); + if old_value == value { + locks.remove(&LockedType::BondedWithdrawal); + } else { + locks.insert( + LockedType::BondedWithdrawal, + old_value.saturating_sub(value), + ); + } + }); } /// Settles and update the vote weight state of the nominator `source` and validator `target` given the delta amount. - fn update_vote_weight(source: &T::AccountId, target: &T::AccountId, delta: Delta) { + fn update_vote_weight( + source: &T::AccountId, + target: &T::AccountId, + delta: Delta>, + ) { let current_block = >::block_number(); let source_weight = @@ -672,8 +720,8 @@ impl Module { fn apply_bond( nominator: &T::AccountId, nominee: &T::AccountId, - value: T::Balance, - ) -> Result<(), Error> { + value: BalanceOf, + ) -> DispatchResult { Self::bond_reserve(nominator, value)?; Self::update_vote_weight(nominator, nominee, Delta::Add(value)); Self::deposit_event(RawEvent::Bond(nominator.clone(), nominee.clone(), value)); @@ -684,7 +732,7 @@ impl Module { who: &T::AccountId, from: &T::AccountId, to: &T::AccountId, - value: T::Balance, + value: BalanceOf, current_block: T::BlockNumber, ) { // TODO: reduce one block_number read? @@ -698,7 +746,7 @@ impl Module { fn apply_unbond( who: &T::AccountId, target: &T::AccountId, - value: T::Balance, + value: BalanceOf, ) -> Result<(), Error> { debug!( "[apply_unbond] who:{:?}, target: {:?}, value: {:?}", diff --git a/xpallets/mining/staking/src/mock.rs b/xpallets/mining/staking/src/mock.rs index f9c0e2c0414c1..912234a68e06f 100644 --- a/xpallets/mining/staking/src/mock.rs +++ b/xpallets/mining/staking/src/mock.rs @@ -1,6 +1,5 @@ use crate::*; use crate::{Module, Trait}; -use chainx_primitives::AssetId; use frame_support::{impl_outer_event, impl_outer_origin, parameter_types, weights::Weight}; use sp_core::H256; use sp_runtime::{ @@ -8,12 +7,8 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, Perbill, }; -use std::{ - cell::RefCell, - collections::{BTreeMap, HashSet}, -}; +use std::{cell::RefCell, collections::HashSet}; use xp_mining_staking::SessionIndex; -use xpallet_assets::{AssetInfo, AssetRestriction, AssetRestrictions, Chain}; pub const INIT_TIMESTAMP: u64 = 30_000; @@ -22,6 +17,7 @@ pub(crate) const TREASURY_ACCOUNT: AccountId = 100_000; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; +pub(crate) type AccountIndex = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -35,13 +31,13 @@ mod staking { } use frame_system as system; +use pallet_balances as balances; use pallet_session as session; -use xpallet_assets as assets; impl_outer_event! { pub enum MetaEvent for Test { system, - assets, + balances, session, staking, } @@ -64,11 +60,11 @@ impl system::Trait for Test { type BaseCallFilter = (); type Origin = Origin; type Call = (); - type Index = u64; + type Index = AccountIndex; type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type Event = MetaEvent; @@ -82,11 +78,26 @@ impl system::Trait for Test { type AvailableBlockRatio = AvailableBlockRatio; type Version = (); type ModuleToIndex = (); - type AccountData = (); + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); } +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> Balance { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) + } +} + +impl pallet_balances::Trait for Test { + type Balance = Balance; + type Event = MetaEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + parameter_types! { pub const MinimumPeriod: u64 = 5; } @@ -97,13 +108,6 @@ impl pallet_timestamp::Trait for Test { type MinimumPeriod = MinimumPeriod; } -impl xpallet_assets::Trait for Test { - type Balance = Balance; - type Event = MetaEvent; - type OnAssetChanged = (); - type OnAssetRegisterOrRevoke = (); -} - /// Another session handler struct to test on_disabled. pub struct OtherSessionHandler; impl pallet_session::OneSessionHandler for OtherSessionHandler { @@ -193,6 +197,7 @@ parameter_types! { } impl Trait for Test { + type Currency = Balances; type Event = MetaEvent; type AssetMining = (); type SessionDuration = SessionDuration; @@ -226,25 +231,6 @@ impl Default for ExtBuilder { } } -const PCX_PRECISION: u8 = 8; -fn pcx() -> (AssetId, AssetInfo, AssetRestrictions) { - ( - xpallet_protocol::PCX, - AssetInfo::new::( - b"PCX".to_vec(), - b"Polkadot ChainX".to_vec(), - Chain::ChainX, - PCX_PRECISION, - b"ChainX's crypto currency in Polkadot ecology".to_vec(), - ) - .unwrap(), - AssetRestriction::Deposit - | AssetRestriction::Withdraw - | AssetRestriction::DestroyWithdrawal - | AssetRestriction::DestroyFree, - ) -} - impl ExtBuilder { pub fn set_associated_constants(&self) { SESSION_PER_ERA.with(|v| *v.borrow_mut() = self.session_per_era); @@ -258,17 +244,8 @@ impl ExtBuilder { .build_storage::() .unwrap(); - let pcx_asset = pcx(); - let assets = vec![(pcx_asset.0, pcx_asset.1, pcx_asset.2, true, false)]; - - let mut endowed = BTreeMap::new(); - let pcx_id = pcx().0; - let endowed_info = vec![(1, 100), (2, 200), (3, 300), (4, 400)]; - endowed.insert(pcx_id, endowed_info.clone()); - let _ = xpallet_assets::GenesisConfig:: { - assets, - endowed, - memo_len: 128, + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(1, 100), (2, 200), (3, 300), (4, 400)], } .assimilate_storage(&mut storage); @@ -325,7 +302,8 @@ impl ExtBuilder { } pub type System = frame_system::Module; -pub type XAssets = xpallet_assets::Module; +pub type Balances = pallet_balances::Module; +// pub type XAssets = xpallet_assets::Module; pub type Session = pallet_session::Module; pub type Timestamp = pallet_timestamp::Module; pub type XStaking = Module; diff --git a/xpallets/mining/staking/src/reward/mod.rs b/xpallets/mining/staking/src/reward/mod.rs index b76c4b8d080e9..e86a34a75b056 100644 --- a/xpallets/mining/staking/src/reward/mod.rs +++ b/xpallets/mining/staking/src/reward/mod.rs @@ -7,30 +7,30 @@ mod proposal09; impl Module { /// Simple u32 power of 2 function - simply uses a bit shift #[inline] - fn pow2(n: u32) -> T::Balance { + fn pow2(n: u32) -> BalanceOf { (1_u32 << n).saturated_into() } /// Returns the total reward for the session, assuming it ends with this block. /// /// Due to the migration of ChainX 1.0 to ChainX 2.0, - fn this_session_reward(current_index: SessionIndex) -> T::Balance { + fn this_session_reward(current_index: SessionIndex) -> BalanceOf { let halving_epoch = current_index / SESSIONS_PER_ROUND; // FIXME: migration_offset - let reward = INITIAL_REWARD.saturated_into::() / Self::pow2(halving_epoch); + let reward = INITIAL_REWARD.saturated_into::>() / Self::pow2(halving_epoch); reward } /// Issue new fresh PCX. #[inline] - fn mint(receiver: &T::AccountId, value: T::Balance) { - let _ = >::pcx_issue(receiver, value); + pub(crate) fn mint(receiver: &T::AccountId, value: BalanceOf) { + T::Currency::deposit_creating(receiver, value); } /// Reward a (potential) validator by a specific amount. /// /// Add the reward to their balance, and their reward pot, pro-rata. - fn apply_reward_validator(who: &T::AccountId, reward: T::Balance) { + fn apply_reward_validator(who: &T::AccountId, reward: BalanceOf) { // Validator themselves can only directly gain 10%, the rest 90% is for the reward pot. let off_the_table = (reward.saturated_into() / 10).saturated_into(); Self::mint(who, off_the_table); @@ -51,7 +51,7 @@ impl Module { /// If the slashed validator can't afford that penalty, it will be /// removed from the validator list. #[inline] - fn reward_active_validator(validator: &T::AccountId, reward: T::Balance) { + fn reward_active_validator(validator: &T::AccountId, reward: BalanceOf) { Self::apply_reward_validator(validator, reward); // FIXME: slash? // It the intention was an offline validator, we should enforce a slash. @@ -64,7 +64,7 @@ impl Module { /// /// Only these active validators are able to be rewarded on each new session, /// the inactive ones earn nothing. - fn get_active_validator_set() -> impl Iterator { + fn get_active_validator_set() -> impl Iterator)> { Self::validator_set().filter(Self::is_active).map(|who| { let total_votes = Self::total_votes_of(&who); (who, total_votes) @@ -72,7 +72,7 @@ impl Module { } /// 20% reward of each session is for the vesting schedule in the first halving epoch. - fn try_vesting(current_index: SessionIndex, this_session_reward: T::Balance) -> T::Balance { + fn try_vesting(current_index: SessionIndex, this_session_reward: BalanceOf) -> BalanceOf { // FIXME: consider the offset due to the migration. // SESSIONS_PER_ROUND --> offset if current_index < SESSIONS_PER_ROUND { @@ -90,7 +90,7 @@ impl Module { } /// Distribute the session reward to all the receivers, returns the total reward for validators. - pub(crate) fn distribute_session_reward(session_index: SessionIndex) -> T::Balance { + pub(crate) fn distribute_session_reward(session_index: SessionIndex) -> BalanceOf { let this_session_reward = Self::this_session_reward(session_index); let session_reward = Self::try_vesting(session_index, this_session_reward); diff --git a/xpallets/mining/staking/src/reward/proposal09.rs b/xpallets/mining/staking/src/reward/proposal09.rs index ae9d2b5f210ea..26685bfe404e1 100644 --- a/xpallets/mining/staking/src/reward/proposal09.rs +++ b/xpallets/mining/staking/src/reward/proposal09.rs @@ -8,10 +8,10 @@ use xpallet_support::debug; impl Module { #[inline] fn generic_calculate_by_proportion>( - total_reward: T::Balance, + total_reward: BalanceOf, mine: S, total: S, - ) -> T::Balance { + ) -> BalanceOf { let mine: u128 = mine.saturated_into(); let total: u128 = total.saturated_into(); @@ -22,7 +22,7 @@ impl Module { r < u128::from(u64::max_value()), "reward of per validator definitely less than u64::max_value()" ); - r.saturated_into::() + r.saturated_into::>() } None => panic!("stake * session_reward overflow!"), } @@ -30,25 +30,25 @@ impl Module { /// Calculates the individual reward according to the proportion and total reward. fn calc_individual_staking_reward( - total_reward: T::Balance, - my_stake: T::Balance, - total_stake: T::Balance, - ) -> T::Balance { + total_reward: BalanceOf, + my_stake: BalanceOf, + total_stake: BalanceOf, + ) -> BalanceOf { let mine = my_stake.saturated_into::(); let total = total_stake.saturated_into::(); Self::generic_calculate_by_proportion(total_reward, mine, total) } fn calc_invididual_asset_mining_reward( - total_reward: T::Balance, + total_reward: BalanceOf, my_power: u128, total_power: u128, - ) -> T::Balance { + ) -> BalanceOf { Self::generic_calculate_by_proportion(total_reward, my_power, total_power) } /// Distributes the invididual asset mining reward, returns the unpaid asset mining rewards. - fn distribute_to_mining_assets(total_reward: T::Balance) -> T::Balance { + fn distribute_to_mining_assets(total_reward: BalanceOf) -> BalanceOf { let asset_mining_info = T::AssetMining::asset_mining_power(); // [PASS*] No risk of sum overflow practically. @@ -74,11 +74,11 @@ impl Module { } /// Reward to all the active validators pro rata. - fn distribute_to_active_validators(session_reward: T::Balance) { + fn distribute_to_active_validators(session_reward: BalanceOf) { let active_validators = Self::get_active_validator_set().collect::>(); let mut total_stake = active_validators .iter() - .fold(Zero::zero(), |acc: T::Balance, (_, x)| acc + *x); + .fold(Zero::zero(), |acc: BalanceOf, (_, x)| acc + *x); let mut total_reward = session_reward; for (validator, stake) in active_validators.into_iter() { // May become zero after meeting the last one. @@ -93,7 +93,10 @@ impl Module { /// Issue new PCX to the action intentions and cross mining asset entities /// accroding to DistributionRatio. - fn distribute_mining_rewards(total: T::Balance, treasury_account: &T::AccountId) -> T::Balance { + fn distribute_mining_rewards( + total: BalanceOf, + treasury_account: &T::AccountId, + ) -> BalanceOf { let mining_distribution = Self::mining_distribution_ratio(); let staking_reward = mining_distribution.calc_staking_reward::(total); let max_asset_mining_reward = total - staking_reward; @@ -122,7 +125,7 @@ impl Module { staking_reward } - pub(super) fn distribute_session_reward_impl_09(session_reward: T::Balance) -> T::Balance { + pub(super) fn distribute_session_reward_impl_09(session_reward: BalanceOf) -> BalanceOf { let global_distribution = Self::global_distribution_ratio(); let (treasury_reward, mining_reward) = global_distribution.calc_rewards::(session_reward); diff --git a/xpallets/mining/staking/src/rpc.rs b/xpallets/mining/staking/src/rpc.rs index 6f82f0d9319fe..63d45b4addfd3 100644 --- a/xpallets/mining/staking/src/rpc.rs +++ b/xpallets/mining/staking/src/rpc.rs @@ -84,22 +84,22 @@ pub struct ValidatorInfo { impl Module { pub fn validators_info( - ) -> Vec, T::BlockNumber>> { + ) -> Vec>, T::BlockNumber>> { Self::validator_set().map(Self::validator_info_of).collect() } pub fn validator_info_of( who: T::AccountId, - ) -> ValidatorInfo, T::BlockNumber> { + ) -> ValidatorInfo>, T::BlockNumber> { let profile = Validators::::get(&who); - let ledger: RpcValidatorLedger, T::BlockNumber> = + let ledger: RpcValidatorLedger>, T::BlockNumber> = ValidatorLedgers::::get(&who).into(); - let self_bonded: RpcBalance = + let self_bonded: RpcBalance> = Nominations::::get(&who, &who).nomination.into(); let is_validating = T::SessionInterface::validators().contains(&who); let reward_pot_account = T::DetermineRewardPotAccount::reward_pot_account_for(&who); - let reward_pot_balance: RpcBalance = - xpallet_assets::Module::::pcx_free_balance(&reward_pot_account).into(); + let reward_pot_balance: RpcBalance> = + Self::free_balance_of(&reward_pot_account).into(); ValidatorInfo { account: who, profile, @@ -113,7 +113,7 @@ impl Module { pub fn staking_dividend_of( who: T::AccountId, - ) -> BTreeMap> { + ) -> BTreeMap>> { let current_block = >::block_number(); Nominations::::iter_prefix(&who) .filter_map(|(validator, _)| { @@ -127,7 +127,7 @@ impl Module { pub fn nomination_details_of( who: T::AccountId, - ) -> BTreeMap, T::BlockNumber>> { + ) -> BTreeMap>, T::BlockNumber>> { Nominations::::iter_prefix(&who) .map(|(validator, ledger)| (validator, ledger.into())) .collect() diff --git a/xpallets/mining/staking/src/slashing.rs b/xpallets/mining/staking/src/slashing.rs index 707422a3fb0e4..046c73ff0504a 100644 --- a/xpallets/mining/staking/src/slashing.rs +++ b/xpallets/mining/staking/src/slashing.rs @@ -2,7 +2,7 @@ use super::*; impl Module { /// Average reward for validator per block. - fn reward_per_block(staking_reward: T::Balance, validator_count: usize) -> u128 { + fn reward_per_block(staking_reward: BalanceOf, validator_count: usize) -> u128 { let session_length = T::SessionDuration::get(); let per_reward = staking_reward.saturated_into::() * validator_count.saturated_into::() @@ -11,14 +11,14 @@ impl Module { } /// TODO: flexiable slash according to slash fraction? - fn expected_slash_of(reward_per_block: u128) -> T::Balance { + fn expected_slash_of(reward_per_block: u128) -> BalanceOf { let ideal_slash = reward_per_block * u128::from(Self::offence_severity()); let min_slash = Self::minimum_penalty().saturated_into::(); let expected_slash = sp_std::cmp::max(ideal_slash, min_slash); expected_slash.saturated_into() } - pub(crate) fn slash_offenders_in_session(staking_reward: T::Balance) -> Vec { + pub(crate) fn slash_offenders_in_session(staking_reward: BalanceOf) -> Vec { // Find the offenders that are in the current validator set. let validators = T::SessionInterface::validators(); let valid_offenders = Self::offenders_in_session() diff --git a/xpallets/mining/staking/src/tests.rs b/xpallets/mining/staking/src/tests.rs index 2b4361292a227..33c899edd9e5a 100644 --- a/xpallets/mining/staking/src/tests.rs +++ b/xpallets/mining/staking/src/tests.rs @@ -1,10 +1,9 @@ use super::*; use crate::mock::*; use frame_support::{assert_err, assert_ok, traits::OnInitialize}; -use sp_runtime::DispatchResult; -fn t_issue_pcx(to: AccountId, value: Balance) -> DispatchResult { - XAssets::pcx_issue(&to, value) +fn t_issue_pcx(to: AccountId, value: Balance) { + XStaking::mint(&to, value); } fn t_register(who: AccountId) -> DispatchResult { @@ -30,13 +29,19 @@ fn t_unbond(who: AccountId, target: AccountId, value: Balance) -> DispatchResult } fn t_withdraw_unbonded(who: AccountId, unbonded_index: UnbondedIndex) -> DispatchResult { - XStaking::withdraw_unbonded(Origin::signed(who), unbonded_index) + XStaking::unlock_unbonded_withdrawal(Origin::signed(who), unbonded_index) } fn t_system_block_number_inc(number: BlockNumber) { System::set_block_number((System::block_number() + number).into()); } +fn t_make_a_validator_candidate(who: AccountId, self_bonded: Balance) { + t_issue_pcx(who, self_bonded); + assert_ok!(t_register(who)); + assert_ok!(t_bond(who, who, self_bonded)); +} + fn t_start_session(session_index: SessionIndex) { assert_eq!( >::get(), @@ -84,10 +89,10 @@ fn bond_should_work() { t_system_block_number_inc(1); - let before_bond = XAssets::pcx_free_balance(&1); + let before_bond = Balances::usable_balance(&1); assert_ok!(t_bond(1, 2, 10)); - assert_eq!(XAssets::pcx_free_balance(&1), before_bond - 10); + assert_eq!(Balances::usable_balance(&1), before_bond - 10); assert_eq!( >::get(2), ValidatorLedger { @@ -220,15 +225,15 @@ fn withdraw_unbond_should_work() { ExtBuilder::default().build_and_execute(|| { t_system_block_number_inc(1); - let before_bond = XAssets::pcx_free_balance(&1); + let before_bond = Balances::usable_balance(&1); assert_ok!(t_bond(1, 2, 10)); - assert_eq!(XAssets::pcx_free_balance(&1), before_bond - 10); + assert_eq!(Balances::usable_balance(&1), before_bond - 10); t_system_block_number_inc(1); assert_ok!(t_unbond(1, 2, 5)); - let before_unbond = XAssets::pcx_free_balance(&1); - assert_eq!(XAssets::pcx_free_balance(&1), before_unbond); + let before_unbond = Balances::usable_balance(&1); + assert_eq!(Balances::usable_balance(&1), before_unbond); assert_eq!( >::get(1), @@ -249,18 +254,12 @@ fn withdraw_unbond_should_work() { t_system_block_number_inc(1); - let before_withdraw_unbonded = XAssets::pcx_free_balance(&1); + let before_withdraw_unbonded = Balances::usable_balance(&1); assert_ok!(t_withdraw_unbonded(1, 0),); - assert_eq!(XAssets::pcx_free_balance(&1), before_withdraw_unbonded + 5); + assert_eq!(Balances::usable_balance(&1), before_withdraw_unbonded + 5); }); } -fn t_make_a_validator_candidate(who: AccountId, self_bonded: Balance) { - assert_ok!(t_issue_pcx(who, self_bonded)); - assert_ok!(t_register(who)); - assert_ok!(t_bond(who, who, self_bonded)); -} - #[test] fn regular_staking_should_work() { ExtBuilder::default().build_and_execute(|| { @@ -330,9 +329,9 @@ fn staking_reward_should_work() { let t_2 = 68; let t_3 = 69; - assert_ok!(t_issue_pcx(t_1, 100)); - assert_ok!(t_issue_pcx(t_2, 100)); - assert_ok!(t_issue_pcx(t_3, 100)); + t_issue_pcx(t_1, 100); + t_issue_pcx(t_2, 100); + t_issue_pcx(t_3, 100); // 5_000_000_000 // @@ -370,24 +369,24 @@ fn staking_reward_should_work() { // 10% -> validator // 90% -> validator's reward pot assert_eq!( - XAssets::pcx_free_balance(&validator), + Balances::free_balance(&validator), initial_free + val_total_reward * session_index as u128 / 10 ); assert_eq!( - XAssets::pcx_free_balance( + Balances::free_balance( &DummyStakingRewardPotAccountDeterminer::reward_pot_account_for(&validator) ), 0 + (val_total_reward - val_total_reward / 10) * session_index as u128 ); }; - test_validator_reward(1, 100 - 10, 10, 1); - test_validator_reward(2, 200 - 20, 20, 1); - test_validator_reward(3, 300 - 30, 30, 1); - test_validator_reward(4, 400 - 40, 40, 1); + test_validator_reward(1, 100, 10, 1); + test_validator_reward(2, 200, 20, 1); + test_validator_reward(3, 300, 30, 1); + test_validator_reward(4, 400, 40, 1); assert_eq!( - XAssets::pcx_free_balance(&TREASURY_ACCOUNT), + Balances::free_balance(&TREASURY_ACCOUNT), (treasury_reward + asset_mining_reward) * 1 ); @@ -399,7 +398,7 @@ fn staking_reward_should_work() { let issued_manually = 100 * 3; let endowed = 100 + 200 + 300 + 400; assert_eq!( - XAssets::pcx_total_balance(), + Balances::total_issuance(), 5_000_000_000u128 + issued_manually + endowed ); @@ -410,17 +409,13 @@ fn staking_reward_should_work() { all.extend_from_slice(&validators); all.extend_from_slice(&validators_reward_pot); - let total_issuance = || { - all.iter() - .map(|x| XAssets::all_type_asset_balance(x, &xpallet_protocol::PCX)) - .sum::() - }; + let total_issuance = || all.iter().map(|x| Balances::free_balance(x)).sum::(); - assert_eq!(XAssets::pcx_total_balance(), total_issuance()); + assert_eq!(Balances::total_issuance(), total_issuance()); t_start_session(2); assert_eq!( - XAssets::pcx_total_balance(), + Balances::total_issuance(), 5_000_000_000u128 * 2 + issued_manually + endowed ); }); @@ -439,3 +434,110 @@ fn slash_should_work() { // todo!("force_new_era_test"); }); } + +#[test] +fn mint_should_work() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Balances::total_issuance(), 1000); + let to_mint = 666; + XStaking::mint(&7777, to_mint); + assert_eq!(Balances::total_issuance(), 1000 + to_mint); + assert_eq!(Balances::free_balance(&7777), to_mint); + }); +} + +#[test] +fn bond_reserve_should_work() { + ExtBuilder::default().build_and_execute(|| { + let who = 7777; + let to_mint = 10; + XStaking::mint(&who, to_mint); + assert_eq!(Balances::free_balance(&who), 10); + + // Bond 6 + assert_ok!(XStaking::bond_reserve(&who, 6)); + assert_eq!(Balances::usable_balance(&who), 4); + let mut locks = BTreeMap::new(); + locks.insert(LockedType::Bonded, 6); + assert_eq!(XStaking::locks(&who), locks); + assert_eq!( + frame_system::Account::::get(&who).data, + pallet_balances::AccountData { + free: 10, + reserved: 0, + misc_frozen: 6, + fee_frozen: 6 // fee_frozen is also 6 now? + } + ); + assert_err!( + Balances::transfer(Some(who).into(), 6, 1000), + pallet_balances::Error::::InsufficientBalance + ); + + // Bond 2 extra + assert_ok!(XStaking::bond_reserve(&who, 2)); + let mut locks = BTreeMap::new(); + locks.insert(LockedType::Bonded, 8); + assert_eq!( + frame_system::Account::::get(&who).data, + pallet_balances::AccountData { + free: 10, + reserved: 0, + misc_frozen: 8, + fee_frozen: 8 + } + ); + assert_err!( + XStaking::bond_reserve(&who, 3), + >::InsufficientBalance + ); + + // Unbond 5 now, the frozen balances stay the same, + // only internal Staking locked state changes. + assert_ok!(XStaking::unbond_reserve(&who, 5)); + let mut locks = BTreeMap::new(); + locks.insert(LockedType::Bonded, 3); + locks.insert(LockedType::BondedWithdrawal, 5); + assert_eq!(XStaking::locks(&who), locks); + assert_eq!( + frame_system::Account::::get(&who).data, + pallet_balances::AccountData { + free: 10, + reserved: 0, + misc_frozen: 8, + fee_frozen: 8 + } + ); + + // Unlock unbonded withdrawal 4. + XStaking::apply_unlock_unbonded_withdrawal(&who, 4); + let mut locks = BTreeMap::new(); + locks.insert(LockedType::Bonded, 3); + locks.insert(LockedType::BondedWithdrawal, 1); + assert_eq!(XStaking::locks(&who), locks); + assert_eq!( + frame_system::Account::::get(&who).data, + pallet_balances::AccountData { + free: 10, + reserved: 0, + misc_frozen: 4, + fee_frozen: 4 + } + ); + + // Unlock unbonded withdrawal 1. + XStaking::apply_unlock_unbonded_withdrawal(&who, 1); + let mut locks = BTreeMap::new(); + locks.insert(LockedType::Bonded, 3); + assert_eq!(XStaking::locks(&who), locks); + assert_eq!( + frame_system::Account::::get(&who).data, + pallet_balances::AccountData { + free: 10, + reserved: 0, + misc_frozen: 3, + fee_frozen: 3 + } + ); + }); +} diff --git a/xpallets/mining/staking/src/types.rs b/xpallets/mining/staking/src/types.rs index 953719c55ce90..2cf283b5a0e8e 100644 --- a/xpallets/mining/staking/src/types.rs +++ b/xpallets/mining/staking/src/types.rs @@ -8,6 +8,17 @@ use xp_mining_common::WeightType; use xp_mining_staking::MiningPower; use xpallet_support::RpcWeightType; +/// Detailed types of reserved balances in Staking. +#[derive(PartialEq, PartialOrd, Ord, Eq, Clone, Copy, Encode, Decode, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum LockedType { + /// Locked balances when nominator calls `bond`. + Bonded, + /// The locked balances transition from `Bonded` into `BondedWithdrawal` state + /// when nominator calls `withdraw_bonded`. + BondedWithdrawal, +} + /// Destination for minted fresh PCX on each new session. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub enum MintedDestination { @@ -192,7 +203,7 @@ pub struct GlobalDistribution { impl GlobalDistribution { /// Calculates the rewards for treasury and mining accordingly. - pub fn calc_rewards(&self, reward: T::Balance) -> (T::Balance, T::Balance) { + pub fn calc_rewards(&self, reward: BalanceOf) -> (BalanceOf, BalanceOf) { assert!(self.treasury + self.mining > 0); let treasury_reward = reward * self.treasury.saturated_into() / (self.treasury + self.mining).saturated_into(); @@ -209,7 +220,7 @@ pub struct MiningDistribution { impl MiningDistribution { /// Returns the reward for Staking given the total reward according to the Staking proportion. - pub fn calc_staking_reward(&self, reward: T::Balance) -> T::Balance { + pub fn calc_staking_reward(&self, reward: BalanceOf) -> BalanceOf { reward.saturating_mul(self.staking.saturated_into()) / (self.asset + self.staking).saturated_into() } @@ -241,8 +252,8 @@ impl MiningDistribution { pub fn has_treasury_extra( &self, - asset_mining_reward_cap: T::Balance, - ) -> Option { + asset_mining_reward_cap: BalanceOf, + ) -> Option> { let (m1, m2) = self.asset_mining_vs_staking::(); if m1 >= m2 { debug!( @@ -257,7 +268,7 @@ impl MiningDistribution { ); // There could be some computation loss here, but it's ok. let treasury_extra = (m2 - m1) * asset_mining_reward_cap.saturated_into::() / m2; - Some(treasury_extra.saturated_into::()) + Some(treasury_extra.saturated_into::>()) } } } @@ -279,10 +290,10 @@ impl Slasher { pub fn try_slash( &self, offender: &T::AccountId, - expected_slash: T::Balance, - ) -> Result<(), T::Balance> { + expected_slash: BalanceOf, + ) -> Result<(), BalanceOf> { let reward_pot = T::DetermineRewardPotAccount::reward_pot_account_for(offender); - let reward_pot_balance = >::pcx_free_balance(&reward_pot); + let reward_pot_balance = Module::::free_balance_of(&reward_pot); if expected_slash <= reward_pot_balance { self.apply_slash(&reward_pot, expected_slash); @@ -294,7 +305,7 @@ impl Slasher { } /// Actually slash the account being punished, all slashed balance will go to the treasury. - fn apply_slash(&self, reward_pot: &T::AccountId, value: T::Balance) { - let _ = >::pcx_move_free_balance(reward_pot, &self.0, value); + fn apply_slash(&self, reward_pot: &T::AccountId, value: BalanceOf) { + let _ = Module::::move_balance(reward_pot, &self.0, value); } } diff --git a/xpallets/support/src/traits.rs b/xpallets/support/src/traits.rs index ad2b4e0f33142..5c3081df358e3 100644 --- a/xpallets/support/src/traits.rs +++ b/xpallets/support/src/traits.rs @@ -18,3 +18,9 @@ pub trait MultiSig { pub trait Validator { fn is_validator(who: &AccountId) -> bool; } + +impl Validator for () { + fn is_validator(_: &AccountId) -> bool { + false + } +}