From f3b316c7e49300e6232d07c3649cb3a681e062c4 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 14 Nov 2019 12:53:06 +0800 Subject: [PATCH 01/30] update: change `LockableCurrency::Moment` from `BlockNumber` to `TimeStamp(u64)` for Ring/Kton --- Cargo.lock | 26 +- Cargo.toml | 7 +- node/cli/Cargo.toml | 3 +- node/executor/Cargo.toml | 4 +- node/runtime/Cargo.toml | 2 +- node/runtime/src/lib.rs | 58 +- srml/balances/Cargo.toml | 39 + srml/balances/src/lib.rs | 1210 ++++++++++++++++++++ srml/balances/src/mock.rs | 190 +++ srml/balances/src/tests.rs | 762 ++++++++++++ srml/chainrelay/bridge/ethereum/Cargo.toml | 4 +- srml/chainrelay/bridge/ethereum/src/lib.rs | 3 +- srml/chainrelay/construct.md | 29 - srml/kton/Cargo.toml | 5 +- srml/kton/src/lib.rs | 45 +- srml/staking/Cargo.toml | 13 +- srml/staking/src/lib.rs | 11 +- srml/support/src/lib.rs | 1 + srml/support/src/types.rs | 1 + 19 files changed, 2300 insertions(+), 113 deletions(-) create mode 100644 srml/balances/Cargo.toml create mode 100644 srml/balances/src/lib.rs create mode 100644 srml/balances/src/mock.rs create mode 100644 srml/balances/src/tests.rs delete mode 100644 srml/chainrelay/construct.md create mode 100644 srml/support/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index cb4c2f567..d28150ab6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -681,6 +681,25 @@ dependencies = [ "vergen 3.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "darwinia-balances" +version = "2.0.0" +dependencies = [ + "darwinia-support 0.1.0", + "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", + "sr-io 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "sr-primitives 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "sr-std 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "srml-support 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "srml-system 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "srml-timestamp 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "srml-transaction-payment 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "substrate-keyring 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", + "substrate-primitives 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", +] + [[package]] name = "darwinia-eos-bridge" version = "0.1.0" @@ -696,6 +715,7 @@ dependencies = [ name = "darwinia-ethereum-bridge" version = "0.1.0" dependencies = [ + "darwinia-support 0.1.0", "merkle-mountain-range 0.1.0", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", @@ -726,6 +746,7 @@ dependencies = [ name = "darwinia-staking" version = "0.1.0" dependencies = [ + "darwinia-balances 2.0.0", "darwinia-kton 0.1.0", "darwinia-support 0.1.0", "node-runtime 0.1.0", @@ -739,7 +760,6 @@ dependencies = [ "sr-staking-primitives 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "sr-std 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-authorship 0.1.0 (git+https://github.com/darwinia-network/substrate.git)", - "srml-balances 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-session 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-support 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-system 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", @@ -2577,13 +2597,13 @@ dependencies = [ name = "node-executor" version = "2.0.0" dependencies = [ + "darwinia-balances 2.0.0", "darwinia-staking 0.1.0", "node-primitives 2.0.0", "node-runtime 0.1.0", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "sr-io 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "sr-primitives 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", - "srml-balances 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-contracts 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-grandpa 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-indices 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", @@ -2647,6 +2667,7 @@ dependencies = [ name = "node-runtime" version = "0.1.0" dependencies = [ + "darwinia-balances 2.0.0", "darwinia-eos-bridge 0.1.0", "darwinia-ethereum-bridge 0.1.0", "darwinia-kton 0.1.0", @@ -2666,7 +2687,6 @@ dependencies = [ "srml-authority-discovery 0.1.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-authorship 0.1.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-babe 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", - "srml-balances 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-collective 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-contracts 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", "srml-contracts-rpc-runtime-api 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", diff --git a/Cargo.toml b/Cargo.toml index c84023f5a..27bcc7abe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -75,11 +75,12 @@ members = [ "srml/support", - "srml/chainrelay/bridge/eos", - "srml/chainrelay/bridge/ethereum", - + "srml/balances", "srml/kton", "srml/staking", + + "srml/chainrelay/bridge/eos", + "srml/chainrelay/bridge/ethereum", ] exclude = ["node/runtime/wasm"] diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index 96ad47ae0..78e594d27 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -18,7 +18,6 @@ runtime-io = { package = "sr-io", git = 'https://github.com/darwinia-network/sub client = { package = "substrate-client", git = 'https://github.com/darwinia-network/substrate.git' } primitives = { package = "substrate-primitives", git = 'https://github.com/darwinia-network/substrate.git' } inherents = { package = "substrate-inherents", git = 'https://github.com/darwinia-network/substrate.git' } -node-runtime = { path = "../runtime" } node-primitives = { path = "../primitives" } hex-literal = "0.2.1" substrate-basic-authorship = { git = 'https://github.com/darwinia-network/substrate.git' } @@ -60,6 +59,8 @@ sr-authority-discovery = { package = "srml-authority-discovery", git = 'https:// authority-discovery = { package = "substrate-authority-discovery", git = 'https://github.com/darwinia-network/substrate.git' } offchain = { package = "substrate-offchain", git = 'https://github.com/darwinia-network/substrate.git' } +node-runtime = { path = "../runtime" } + [dev-dependencies] consensus-common = { package = "substrate-consensus-common", git = 'https://github.com/darwinia-network/substrate.git' } service-test = { package = "substrate-service-test", git = 'https://github.com/darwinia-network/substrate.git' } diff --git a/node/executor/Cargo.toml b/node/executor/Cargo.toml index 5ce9afbcd..9b4813902 100644 --- a/node/executor/Cargo.toml +++ b/node/executor/Cargo.toml @@ -13,6 +13,7 @@ state_machine = { package = "substrate-state-machine", git = 'https://github.com substrate-executor = { git = 'https://github.com/darwinia-network/substrate.git' } primitives = { package = "substrate-primitives", git = 'https://github.com/darwinia-network/substrate.git' } trie = { package = "substrate-trie", git = 'https://github.com/darwinia-network/substrate.git' } + node-primitives = { path = "../primitives" } node-runtime = { path = "../runtime" } @@ -21,7 +22,6 @@ test-client = { package = "substrate-test-client", git = 'https://github.com/dar keyring = { package = "substrate-keyring", git = 'https://github.com/darwinia-network/substrate.git' } runtime_primitives = { package = "sr-primitives", git = 'https://github.com/darwinia-network/substrate.git' } runtime_support = { package = "srml-support", git = 'https://github.com/darwinia-network/substrate.git' } -balances = { package = "srml-balances",git = 'https://github.com/darwinia-network/substrate.git' } session = { package = "srml-session", git = 'https://github.com/darwinia-network/substrate.git' } staking = { package = "darwinia-staking", path = '../../srml/staking' } system = { package = "srml-system", git = 'https://github.com/darwinia-network/substrate.git' } @@ -32,5 +32,7 @@ grandpa = { package = "srml-grandpa", git = 'https://github.com/darwinia-network indices = { package = "srml-indices", git = 'https://github.com/darwinia-network/substrate.git' } wabt = "0.9.2" +balances = { package = "darwinia-balances", path = '../../srml/balances', default-features = false } + [features] benchmarks = [] diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index c580d7eb3..211461995 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -18,7 +18,6 @@ sr-primitives = { git = 'https://github.com/darwinia-network/substrate.git', def offchain-primitives = { package = "substrate-offchain-primitives", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } version = { package = "sr-version", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } support = { package = "srml-support", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } -balances = { package = "srml-balances", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } executive = { package = "srml-executive", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } finality-tracker = { package = "srml-finality-tracker", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } grandpa = { package = "srml-grandpa", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } @@ -61,6 +60,7 @@ inherents = { package = "substrate-inherents", git = 'https://github.com/darwini transaction-payment-rpc-runtime-api = { package = "srml-transaction-payment-rpc-runtime-api", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } +balances = { package = "darwinia-balances", path = '../../srml/balances', default-features = false } kton = { package = "darwinia-kton", path = '../../srml/kton', default-features = false } staking = { package = "darwinia-staking", path = "../../srml/staking", default-features = false } diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 8ea07a288..3551d7375 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -22,9 +22,9 @@ use authority_discovery_primitives::{AuthorityId as EncodedAuthorityId, Signature as EncodedSignature}; use babe_primitives::{AuthorityId as BabeId, AuthoritySignature as BabeSignature}; pub use balances::Call as BalancesCall; -use sr_api::impl_runtime_apis; use codec::{Decode, Encode}; pub use contracts::Gas; +use sr_api::impl_runtime_apis; //use grandpa::fg_primitives; //use grandpa::{AuthorityId as GrandpaId, AuthorityWeight as GrandpaWeight}; @@ -49,24 +49,23 @@ use support::{ }; pub use timestamp::Call as TimestampCall; -use version::RuntimeVersion; #[cfg(any(feature = "std", test))] - use version::NativeVersion; +use version::RuntimeVersion; -use substrate_primitives::OpaqueMetadata; -use grandpa::AuthorityList as GrandpaAuthorityList; use grandpa::fg_primitives; -use im_online::sr25519::{AuthorityId as ImOnlineId}; -use transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; +use grandpa::AuthorityList as GrandpaAuthorityList; +use im_online::sr25519::AuthorityId as ImOnlineId; +use substrate_primitives::OpaqueMetadata; use system::offchain::TransactionSubmitter; +use transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; use staking::EraIndex; pub use staking::StakerStatus; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::{CurrencyToVoteHandler, Author, LinearWeightToFee, TargetedFeeAdjustment}; +use impls::{Author, CurrencyToVoteHandler, LinearWeightToFee, TargetedFeeAdjustment}; /// Constant values used within the runtime. pub mod constants; @@ -137,7 +136,6 @@ parameter_types! { pub const MaximumBlockLength: u32 = 5 * 1024 * 1024; pub const Version: RuntimeVersion = VERSION; } - impl system::Trait for Runtime { type Origin = Origin; type Call = Call; @@ -173,7 +171,6 @@ parameter_types! { pub const TransferFee: Balance = 1 * MILLI; pub const CreationFee: Balance = 1 * MILLI; } - impl balances::Trait for Runtime { type Balance = Balance; type OnFreeBalanceZero = (Staking, Session); @@ -194,7 +191,6 @@ parameter_types! { // for a sane configuration, this should always be less than `AvailableBlockRatio`. pub const TargetBlockFullness: Perbill = Perbill::from_percent(25); } - impl transaction_payment::Trait for Runtime { type Currency = Balances; type OnTransactionPayment = DealWithFees; @@ -208,7 +204,6 @@ parameter_types! { pub const EpochDuration: u64 = EPOCH_DURATION_IN_SLOTS; pub const ExpectedBlockTime: Moment = MILLISECS_PER_BLOCK; } - impl babe::Trait for Runtime { type EpochDuration = EpochDuration; type ExpectedBlockTime = ExpectedBlockTime; @@ -218,17 +213,12 @@ impl babe::Trait for Runtime { parameter_types! { pub const MinimumPeriod: Moment = SLOT_DURATION / 2; } - impl timestamp::Trait for Runtime { type Moment = u64; type OnTimestampSet = Babe; type MinimumPeriod = MinimumPeriod; } -parameter_types! { - pub const UncleGenerations: BlockNumber = 5; -} - impl_opaque_keys! { pub struct SessionKeys { pub grandpa: Grandpa, @@ -237,6 +227,9 @@ impl_opaque_keys! { } } +parameter_types! { + pub const UncleGenerations: BlockNumber = 5; +} impl authorship::Trait for Runtime { type FindAuthor = session::FindAccountFromAuthorIndex; type UncleGenerations = UncleGenerations; @@ -250,17 +243,10 @@ impl authorship::Trait for Runtime { // TODO: Introduce some structure to tie these together to make it a bit less of a footgun. This // should be easy, since OneSessionHandler trait provides the `Key` as an associated type. #2858 -parameter_types! { - pub const Period: BlockNumber = 1 * MINUTES; - pub const Offset: BlockNumber = 0; -} - type SessionHandlers = (Grandpa, Babe, ImOnline, AuthorityDiscovery); - parameter_types! { pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(17); } - impl session::Trait for Runtime { type Event = Event; type ValidatorId = ::AccountId; @@ -278,14 +264,6 @@ impl session::historical::Trait for Runtime { type FullIdentificationOf = staking::ExposureOf; } -parameter_types! { - pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 5; - // about 14 days - pub const BondingDuration: staking::EraIndex = 4032; - // 365 days * 24 hours * 60 minutes / 5 minutes - pub const ErasPerEpoch: EraIndex = 105120; -} - impl sudo::Trait for Runtime { type Event = Event; type Proposal = Call; @@ -302,18 +280,16 @@ impl offences::Trait for Runtime { } type SubmitTransaction = TransactionSubmitter; - parameter_types! { pub const SessionDuration: BlockNumber = EPOCH_DURATION_IN_SLOTS as _; } - impl im_online::Trait for Runtime { type AuthorityId = ImOnlineId; type Event = Event; type Call = Call; type SubmitTransaction = SubmitTransaction; - type ReportUnresponsiveness = Offences; type SessionDuration = SessionDuration; + type ReportUnresponsiveness = Offences; } impl authority_discovery::Trait for Runtime { @@ -324,7 +300,6 @@ parameter_types! { pub const WindowSize: BlockNumber = 101; pub const ReportLatency: BlockNumber = 1000; } - impl finality_tracker::Trait for Runtime { type OnFinalizationStalled = Grandpa; type WindowSize = WindowSize; @@ -342,7 +317,6 @@ parameter_types! { pub const RentDepositOffset: Balance = 1000 * COIN; pub const SurchargeReward: Balance = 150 * COIN; } - impl contracts::Trait for Runtime { type Currency = Balances; type Time = Timestamp; @@ -410,10 +384,16 @@ impl kton::Trait for Runtime { } parameter_types! { + pub const Period: BlockNumber = 1 * MINUTES; +// pub const Offset: BlockNumber = 0; + pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 5; + // about 14 days + pub const BondingDuration: staking::EraIndex = 4032; + // 365 days * 24 hours * 60 minutes / 5 minutes + pub const ErasPerEpoch: EraIndex = 105120; // decimal 9 pub const CAP: Balance = 10_000_000_000 * COIN; } - impl staking::Trait for Runtime { type Ring = Balances; type Kton = Kton; @@ -449,7 +429,6 @@ construct_runtime!( AuthorityDiscovery: authority_discovery::{Module, Call, Config}, Authorship: authorship::{Module, Call, Storage}, Babe: babe::{Module, Call, Storage, Config, Inherent(Timestamp)}, - Balances: balances::{default, Error}, Contracts: contracts, FinalityTracker: finality_tracker::{Module, Call, Inherent}, Grandpa: grandpa::{Module, Call, Storage, Config, Event}, @@ -464,6 +443,7 @@ construct_runtime!( TransactionPayment: transaction_payment::{Module, Storage}, Utility: utility::{Module, Call, Event}, + Balances: balances::{default, Error}, Kton: kton, Staking: staking::{default, OfflineWorker}, EOSBridge: eos_bridge::{Storage, Module, Event, Call}, diff --git a/srml/balances/Cargo.toml b/srml/balances/Cargo.toml new file mode 100644 index 000000000..7955fffa6 --- /dev/null +++ b/srml/balances/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "darwinia-balances" +version = "2.0.0" +authors = ["Xavier Lau "] +edition = "2018" + +[dependencies] +serde = { version = "1.0.101", optional = true } +safe-mix = { version = "1.0.0", default-features = false } +codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } +substrate-keyring = { git = "https://github.com/darwinia-network/substrate.git", optional = true } +rstd = { package = "sr-std", git = "https://github.com/darwinia-network/substrate.git", default-features = false } +sr-primitives = { git = "https://github.com/darwinia-network/substrate.git", default-features = false } +support = { package = "srml-support", git = "https://github.com/darwinia-network/substrate.git", default-features = false } +system = { package = "srml-system", git = "https://github.com/darwinia-network/substrate.git", default-features = false } +timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } + +darwinia-support = { path = "../support", default-features = false } + +[dev-dependencies] +runtime-io = { package = "sr-io", git = "https://github.com/darwinia-network/substrate.git" } +primitives = { package = "substrate-primitives", git = "https://github.com/darwinia-network/substrate.git" } +transaction-payment = { package = "srml-transaction-payment", git = "https://github.com/darwinia-network/substrate.git" } + +[features] +default = ["std"] +std = [ + "serde", + "safe-mix/std", + "substrate-keyring", + "codec/std", + "rstd/std", + "support/std", + "sr-primitives/std", + "system/std", + "timestamp/std", + + "darwinia-support/std" +] diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs new file mode 100644 index 000000000..f7ad548da --- /dev/null +++ b/srml/balances/src/lib.rs @@ -0,0 +1,1210 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! # 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. +//! - **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 balance is set +//! to zero. +//! - **Free Balance:** The portion of a balance that is not reserved. The free balance is the only balance that matters +//! for most operations. When this balance falls below the existential deposit, most functionality of the account is +//! removed. When both it and the reserved balance are deleted, then the account is said to be dead. +//! - **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. If the reserved balance falls below the +//! existential deposit then it and any related functionality will be deleted. When both it and the free balance are +//! deleted, then the account is said to be dead. +//! - **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". +//! - **Vesting:** Similar to a lock, this is another, but independent, liquidity restriction that reduces linearly +//! over time. +//! +//! ### 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`](../srml_support/traits/trait.Currency.html): Functions for dealing with a +//! fungible assets system. +//! - [`ReservableCurrency`](../srml_support/traits/trait.ReservableCurrency.html): +//! Functions for dealing with assets that can be reserved from an account. +//! - [`LockableCurrency`](../srml_support/traits/trait.LockableCurrency.html): Functions for +//! dealing with accounts that allow liquidity restrictions. +//! - [`Imbalance`](../srml_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`](../srml_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. +//! +//! ### Public Functions +//! +//! - `vesting_balance` - Get the amount that is currently being vested and cannot be transferred out of this account. +//! +//! ## Usage +//! +//! The following examples show how to use the Balances module in your custom module. +//! +//! ### Examples from the SRML +//! +//! The Contract module uses the `Currency` trait to handle gas payment, and its types inherit from `Currency`: +//! +//! ``` +//! use support::traits::Currency; +//! # pub trait Trait: 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 support::traits::{WithdrawReasons, LockableCurrency}; +//! use sr_primitives::traits::Bounded; +//! pub trait Trait: system::Trait { +//! type Currency: LockableCurrency; +//! } +//! # struct StakingLedger { +//! # stash: ::AccountId, +//! # total: <::Currency as 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, +//! T::BlockNumber::max_value(), +//! 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)] + +use codec::{Codec, Decode, Encode}; +use rstd::prelude::*; +use rstd::{cmp, fmt::Debug, mem, result}; +use sr_primitives::{ + traits::{ + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SaturatedConversion, Saturating, + SimpleArithmetic, StaticLookup, Zero, + }, + weights::SimpleDispatchInfo, + RuntimeDebug, +}; +use support::{ + decl_event, decl_module, decl_storage, + dispatch::Result, + traits::{ + Currency, ExistenceRequirement, Get, Imbalance, LockIdentifier, LockableCurrency, OnFreeBalanceZero, + OnUnbalanced, ReservableCurrency, SignedImbalance, UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, + }, + Parameter, StorageValue, +}; +use system::{ensure_root, ensure_signed, IsDeadAccount, OnNewAccount}; + +use darwinia_support::types::TimeStamp; + +mod mock; +mod tests; + +pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; + +pub trait Subtrait: system::Trait + timestamp::Trait { + /// The balance of an account. + type Balance: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + MaybeSerializeDeserialize + + Debug + + From; + + /// A function that is invoked when the free-balance has fallen below the existential deposit and + /// has been reduced to zero. + /// + /// Gives a chance to clean up resources associated with the given account. + type OnFreeBalanceZero: OnFreeBalanceZero; + + /// Handler for when a new account is created. + type OnNewAccount: OnNewAccount; + + /// The minimum amount required to keep an account open. + type ExistentialDeposit: Get; + + /// The fee required to make a transfer. + type TransferFee: Get; + + /// The fee required to create an account. + type CreationFee: Get; +} + +pub trait Trait: system::Trait + timestamp::Trait { + /// The balance of an account. + type Balance: Parameter + + Member + + SimpleArithmetic + + Codec + + Default + + Copy + + MaybeSerializeDeserialize + + Debug + + From; + + /// A function that is invoked when the free-balance has fallen below the existential deposit and + /// has been reduced to zero. + /// + /// Gives a chance to clean up resources associated with the given account. + type OnFreeBalanceZero: OnFreeBalanceZero; + + /// Handler for when a new account is created. + type OnNewAccount: OnNewAccount; + + /// Handler for the unbalanced reduction when taking fees associated with balance + /// transfer (which may also include account creation). + type TransferPayment: OnUnbalanced>; + + /// 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 fee required to make a transfer. + type TransferFee: Get; + + /// The fee required to create an account. + type CreationFee: Get; +} + +impl, I: Instance> Subtrait for T { + type Balance = T::Balance; + type OnFreeBalanceZero = T::OnFreeBalanceZero; + type OnNewAccount = T::OnNewAccount; + type ExistentialDeposit = T::ExistentialDeposit; + type TransferFee = T::TransferFee; + type CreationFee = T::CreationFee; +} + +decl_event!( + pub enum Event where + ::AccountId, + >::Balance + { + /// A new account was created. + NewAccount(AccountId, Balance), + /// An account was reaped. + ReapedAccount(AccountId), + /// Transfer succeeded (from, to, value, fees). + Transfer(AccountId, AccountId, Balance, Balance), + } +); + +/// Struct to encode the vesting schedule of an individual account. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct VestingSchedule { + /// Locked amount at genesis. + pub locked: Balance, + /// Amount that gets unlocked every block after `starting_block`. + pub per_block: Balance, + /// Starting block for unlocking(vesting). + pub starting_block: BlockNumber, +} + +impl VestingSchedule { + /// Amount locked at block `n`. + pub fn locked_at(&self, n: BlockNumber) -> Balance + where + Balance: From, + { + // Number of blocks that count toward vesting + // Saturating to 0 when n < starting_block + let vested_block_count = n.saturating_sub(self.starting_block); + // Return amount that is still locked in vesting + if let Some(x) = Balance::from(vested_block_count).checked_mul(&self.per_block) { + self.locked.max(x) - x + } else { + Zero::zero() + } + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] +pub struct BalanceLock { + pub id: LockIdentifier, + pub amount: Balance, + pub until: TimeStamp, + pub reasons: WithdrawReasons, +} + +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; + + /// Information regarding the vesting of a given account. + pub Vesting get(fn vesting) build(|config: &GenesisConfig| { + // Generate initial vesting configuration + // * who - Account which we are generating vesting configuration for + // * begin - Block when the account will start to vest + // * length - Number of blocks from `begin` until fully vested + // * liquid - Number of units which can be spent before vesting begins + config.vesting.iter().filter_map(|&(ref who, begin, length, liquid)| { + let length = >::from(length); + + config.balances.iter() + .find(|&&(ref w, _)| w == who) + .map(|&(_, balance)| { + // Total genesis `balance` minus `liquid` equals funds locked for vesting + let locked = balance.saturating_sub(liquid); + // Number of units unlocked per block after `begin` + let per_block = locked / length.max(sr_primitives::traits::One::one()); + + (who.clone(), VestingSchedule { + locked: locked, + per_block: per_block, + starting_block: begin + }) + }) + }).collect::>() + }): map T::AccountId => Option>; + + /// The 'free' balance of a given account. + /// + /// 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. When this + /// balance falls below the value of `ExistentialDeposit`, then the 'current account' is + /// deleted: specifically `FreeBalance`. Further, the `OnFreeBalanceZero` callback + /// is invoked, giving a chance to external modules to clean up data associated with + /// the deleted account. + /// + /// `system::AccountNonce` is also deleted if `ReservedBalance` is also zero (it also gets + /// collapsed to zero if it ever becomes less than `ExistentialDeposit`. + pub FreeBalance get(fn free_balance) + build(|config: &GenesisConfig| config.balances.clone()): + map T::AccountId => T::Balance; + + /// The amount of the balance of a given account that is externally reserved; 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. + /// + /// When this balance falls below the value of `ExistentialDeposit`, then this 'reserve account' + /// is deleted: specifically, `ReservedBalance`. + /// + /// `system::AccountNonce` is also deleted if `FreeBalance` is also zero (it also gets + /// collapsed to zero if it ever becomes less than `ExistentialDeposit`.) + pub ReservedBalance get(fn reserved_balance): map T::AccountId => T::Balance; + + /// Any liquidity locks on some account balances. + pub Locks get(fn locks): map T::AccountId => Vec>; + } + add_extra_genesis { + config(balances): Vec<(T::AccountId, T::Balance)>; + config(vesting): Vec<(T::AccountId, T::BlockNumber, T::BlockNumber, T::Balance)>; + // ^^ begin, length, amount liquid at genesis + } +} + +decl_module! { + pub struct Module, I: Instance = DefaultInstance> for enum Call where origin: T::Origin { + /// The minimum amount required to keep an account open. + const ExistentialDeposit: T::Balance = T::ExistentialDeposit::get(); + + /// The fee required to make a transfer. + const TransferFee: T::Balance = T::TransferFee::get(); + + /// The fee required to create an account. + const CreationFee: T::Balance = T::CreationFee::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` and `T::OnFreeBalanceZero::on_free_balance_zero`. + /// - `transfer_keep_alive` works the same way as `transfer`, but has an additional + /// check that the transfer will not kill the origin account. + /// + /// # + #[weight = SimpleDispatchInfo::FixedNormal(1_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 (`system::AccountNonce`). + /// + /// The dispatch origin for this call is `root`. + /// + /// # + /// - Independent of the arguments. + /// - Contains a limited number of reads and writes. + /// # + #[weight = SimpleDispatchInfo::FixedOperational(50_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 current_free = >::get(&who); + if new_free > current_free { + mem::drop(PositiveImbalance::::new(new_free - current_free)); + } else if new_free < current_free { + mem::drop(NegativeImbalance::::new(current_free - new_free)); + } + Self::set_free_balance(&who, new_free); + + let current_reserved = >::get(&who); + if new_reserved > current_reserved { + mem::drop(PositiveImbalance::::new(new_reserved - current_reserved)); + } else if new_reserved < current_reserved { + mem::drop(NegativeImbalance::::new(current_reserved - new_reserved)); + } + Self::set_reserved_balance(&who, new_reserved); + } + + /// Exactly as `transfer`, except the origin must be root and the source account may be + /// specified. + #[weight = SimpleDispatchInfo::FixedNormal(1_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 + #[weight = SimpleDispatchInfo::FixedNormal(1_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, ExistenceRequirement::KeepAlive)?; + } + + } +} + +impl, I: Instance> Module { + // PUBLIC IMMUTABLES + + /// Get the amount that is currently being vested and cannot be transferred out of this account. + pub fn vesting_balance(who: &T::AccountId) -> T::Balance { + if let Some(v) = Self::vesting(who) { + Self::free_balance(who).min(v.locked_at(>::block_number())) + } else { + Zero::zero() + } + } + + // PRIVATE MUTABLES + + /// Set the reserved balance of an account to some new value. Will enforce `ExistentialDeposit` + /// law, annulling the account as needed. + /// + /// 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 set_reserved_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome { + if balance < T::ExistentialDeposit::get() { + >::insert(who, balance); + Self::on_reserved_too_low(who); + UpdateBalanceOutcome::AccountKilled + } else { + >::insert(who, balance); + UpdateBalanceOutcome::Updated + } + } + + /// Set the free balance of an account to some new value. Will enforce `ExistentialDeposit` + /// law, annulling the account as needed. + /// + /// 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 set_free_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome { + // Commented out for now - but consider it instructive. + // assert!(!Self::total_balance(who).is_zero()); + // assert!(Self::free_balance(who) > T::ExistentialDeposit::get()); + if balance < T::ExistentialDeposit::get() { + >::insert(who, balance); + Self::on_free_too_low(who); + UpdateBalanceOutcome::AccountKilled + } else { + >::insert(who, balance); + UpdateBalanceOutcome::Updated + } + } + + /// Register a new account (with existential balance). + /// + /// This just calls appropriate hooks. It doesn't (necessarily) make any state changes. + fn new_account(who: &T::AccountId, balance: T::Balance) { + T::OnNewAccount::on_new_account(&who); + Self::deposit_event(RawEvent::NewAccount(who.clone(), balance.clone())); + } + + /// Unregister an account. + /// + /// This just removes the nonce and leaves an event. + fn reap_account(who: &T::AccountId) { + >::remove(who); + Self::deposit_event(RawEvent::ReapedAccount(who.clone())); + } + + /// Account's free balance has dropped below existential deposit. Kill its + /// free side and the account completely if its reserved size is already dead. + /// + /// Will maintain total issuance. + fn on_free_too_low(who: &T::AccountId) { + let dust = >::take(who); + >::remove(who); + + // underflow should never happen, but if it does, there's not much we can do about it. + if !dust.is_zero() { + T::DustRemoval::on_unbalanced(NegativeImbalance::new(dust)); + } + + T::OnFreeBalanceZero::on_free_balance_zero(who); + + if Self::reserved_balance(who).is_zero() { + Self::reap_account(who); + } + } + + /// Account's reserved balance has dropped below existential deposit. Kill its + /// reserved side and the account completely if its free size is already dead. + /// + /// Will maintain total issuance. + fn on_reserved_too_low(who: &T::AccountId) { + let dust = >::take(who); + + // underflow should never happen, but it if does, there's nothing to be done here. + if !dust.is_zero() { + T::DustRemoval::on_unbalanced(NegativeImbalance::new(dust)); + } + + if Self::free_balance(who).is_zero() { + Self::reap_account(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, Zero}; + use rstd::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> 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> 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 three type items that do +// depend on the Imbalance type (TransferPayment, DustRemoval) +// are placed in their own SRML module. +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> system::Trait for ElevatedTrait { + 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 MaximumBlockLength = T::MaximumBlockLength; + type AvailableBlockRatio = T::AvailableBlockRatio; + type Version = T::Version; +} +impl, I: Instance> timestamp::Trait for ElevatedTrait { + type Moment = T::Moment; + type OnTimestampSet = (); + type MinimumPeriod = T::MinimumPeriod; +} +impl, I: Instance> Trait for ElevatedTrait { + type Balance = T::Balance; + type OnFreeBalanceZero = T::OnFreeBalanceZero; + type OnNewAccount = T::OnNewAccount; + type TransferPayment = (); + type DustRemoval = (); + type Event = (); + type ExistentialDeposit = T::ExistentialDeposit; + type TransferFee = T::TransferFee; + type CreationFee = T::CreationFee; +} + +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::free_balance(who) + Self::reserved_balance(who) + } + + fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { + Self::free_balance(who) >= value + } + + fn total_issuance() -> Self::Balance { + >::get() + } + + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { + >::mutate(|issued| { + *issued = issued.checked_sub(&amount).unwrap_or_else(|| { + amount = *issued; + Zero::zero() + }); + }); + PositiveImbalance::new(amount) + } + + fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { + >::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 { + >::get(who) + } + + // # + // 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, + ) -> Result { + if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) + && Self::vesting_balance(who) > new_balance + { + return Err("vesting balance too high to send value"); + } + let locks = Self::locks(who); + if locks.is_empty() { + return Ok(()); + } + + let now = >::now().saturated_into::(); + if locks + .into_iter() + .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) + { + Ok(()) + } else { + Err("account liquidity restrictions prevent withdrawal") + } + } + + fn transfer( + transactor: &T::AccountId, + dest: &T::AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> Result { + let from_balance = Self::free_balance(transactor); + let to_balance = Self::free_balance(dest); + let would_create = to_balance.is_zero(); + let fee = if would_create { + T::CreationFee::get() + } else { + T::TransferFee::get() + }; + let liability = match value.checked_add(&fee) { + Some(l) => l, + None => return Err("got overflow after adding a fee to value"), + }; + + let new_from_balance = match from_balance.checked_sub(&liability) { + None => return Err("balance too low to send value"), + Some(b) => b, + }; + if would_create && value < T::ExistentialDeposit::get() { + return Err("value too low to create account"); + } + Self::ensure_can_withdraw(transactor, value, WithdrawReason::Transfer.into(), new_from_balance)?; + + // NOTE: total stake being stored in the same type means that this could never overflow + // but better to be safe than sorry. + let new_to_balance = match to_balance.checked_add(&value) { + Some(b) => b, + None => return Err("destination balance too high to receive value"), + }; + + if transactor != dest { + if existence_requirement == ExistenceRequirement::KeepAlive { + if new_from_balance < Self::minimum_balance() { + return Err("transfer would kill account"); + } + } + + Self::set_free_balance(transactor, new_from_balance); + if !>::exists(dest) { + Self::new_account(dest, new_to_balance); + } + Self::set_free_balance(dest, new_to_balance); + T::TransferPayment::on_unbalanced(NegativeImbalance::new(fee)); + Self::deposit_event(RawEvent::Transfer(transactor.clone(), dest.clone(), value, fee)); + } + + Ok(()) + } + + fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { + let free_balance = Self::free_balance(who); + let free_slash = cmp::min(free_balance, value); + Self::set_free_balance(who, free_balance - free_slash); + let remaining_slash = value - free_slash; + // 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. + if !remaining_slash.is_zero() { + let reserved_balance = Self::reserved_balance(who); + let reserved_slash = cmp::min(reserved_balance, remaining_slash); + Self::set_reserved_balance(who, reserved_balance - reserved_slash); + ( + NegativeImbalance::new(free_slash + reserved_slash), + remaining_slash - reserved_slash, + ) + } else { + (NegativeImbalance::new(value), Zero::zero()) + } + } + + fn deposit_into_existing( + who: &T::AccountId, + value: Self::Balance, + ) -> result::Result { + if Self::total_balance(who).is_zero() { + return Err("beneficiary account must pre-exist"); + } + Self::set_free_balance(who, Self::free_balance(who) + value); + Ok(PositiveImbalance::new(value)) + } + + fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { + let (imbalance, _) = Self::make_free_balance_be(who, Self::free_balance(who) + value); + if let SignedImbalance::Positive(p) = imbalance { + p + } else { + // Impossible, but be defensive. + Self::PositiveImbalance::zero() + } + } + + fn withdraw( + who: &T::AccountId, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> result::Result { + let old_balance = Self::free_balance(who); + if let Some(new_balance) = old_balance.checked_sub(&value) { + // if we need to keep the account alive... + if liveness == ExistenceRequirement::KeepAlive + // ...and it would be dead afterwards... + && new_balance < T::ExistentialDeposit::get() + // ...yet is was alive before + && old_balance >= T::ExistentialDeposit::get() + { + return Err("payment would kill account"); + } + Self::ensure_can_withdraw(who, value, reasons, new_balance)?; + Self::set_free_balance(who, new_balance); + Ok(NegativeImbalance::new(value)) + } else { + Err("too few free funds in account") + } + } + + fn make_free_balance_be( + who: &T::AccountId, + balance: Self::Balance, + ) -> ( + SignedImbalance, + UpdateBalanceOutcome, + ) { + let original = Self::free_balance(who); + if balance < T::ExistentialDeposit::get() && original.is_zero() { + // 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_free_balance) 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. + return ( + SignedImbalance::Positive(Self::PositiveImbalance::zero()), + UpdateBalanceOutcome::AccountKilled, + ); + } + let imbalance = if original <= balance { + SignedImbalance::Positive(PositiveImbalance::new(balance - original)) + } else { + SignedImbalance::Negative(NegativeImbalance::new(original - balance)) + }; + // If the balance is too low, then the account is reaped. + // NOTE: There are two balances for every account: `reserved_balance` and + // `free_balance`. This contract subsystem only cares about the latter: whenever + // the term "balance" is used *here* it should be assumed to mean "free balance" + // in the rest of the module. + // Free balance can never be less than ED. If that happens, it gets reduced to zero + // and the account information relevant to this subsystem is deleted (i.e. the + // account is reaped). + let outcome = if balance < T::ExistentialDeposit::get() { + Self::set_free_balance(who, balance); + UpdateBalanceOutcome::AccountKilled + } else { + if !>::exists(who) { + Self::new_account(&who, balance); + } + Self::set_free_balance(who, balance); + UpdateBalanceOutcome::Updated + }; + (imbalance, outcome) + } +} + +impl, I: Instance> ReservableCurrency for Module +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + 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) { + let b = Self::reserved_balance(who); + let slash = cmp::min(b, value); + // underflow should never happen, but it if does, there's nothing to be done here. + Self::set_reserved_balance(who, b - slash); + (NegativeImbalance::new(slash), value - slash) + } + + fn reserved_balance(who: &T::AccountId) -> Self::Balance { + >::get(who) + } + + fn reserve(who: &T::AccountId, value: Self::Balance) -> result::Result<(), &'static str> { + let b = Self::free_balance(who); + if b < value { + return Err("not enough free funds"); + } + let new_balance = b - value; + Self::ensure_can_withdraw(who, value, WithdrawReason::Reserve.into(), new_balance)?; + Self::set_reserved_balance(who, Self::reserved_balance(who) + value); + Self::set_free_balance(who, new_balance); + Ok(()) + } + + fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { + let b = Self::reserved_balance(who); + let actual = cmp::min(b, value); + Self::set_free_balance(who, Self::free_balance(who) + actual); + Self::set_reserved_balance(who, b - actual); + value - actual + } + + fn repatriate_reserved( + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + ) -> result::Result { + if Self::total_balance(beneficiary).is_zero() { + return Err("beneficiary account must pre-exist"); + } + let b = Self::reserved_balance(slashed); + let slash = cmp::min(b, value); + Self::set_free_balance(beneficiary, Self::free_balance(beneficiary) + slash); + Self::set_reserved_balance(slashed, b - slash); + Ok(value - slash) + } +} + +impl LockableCurrency for Module +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + type Moment = TimeStamp; + + // `amount` > `free_balance` is allowed + fn set_lock( + id: LockIdentifier, + who: &T::AccountId, + amount: T::Balance, + until: TimeStamp, + reasons: WithdrawReasons, + ) { + let now = >::now().saturated_into::(); + let mut new_lock = Some(BalanceLock { + id, + amount, + until, + reasons, + }); + let mut locks = Self::locks(who) + .into_iter() + .filter_map(|l| { + if l.id == id { + new_lock.take() + } else if l.until > now { + Some(l) + } else { + None + } + }) + .collect::>(); + if let Some(lock) = new_lock { + locks.push(lock); + } + >::insert(who, locks); + } + + fn extend_lock( + id: LockIdentifier, + who: &T::AccountId, + amount: T::Balance, + until: TimeStamp, + reasons: WithdrawReasons, + ) { + let now = >::now().saturated_into::(); + let mut new_lock = Some(BalanceLock { + id, + amount, + until, + reasons, + }); + 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), + until: l.until.max(nl.until), + reasons: l.reasons | nl.reasons, + }) + } else if l.until > now { + Some(l) + } else { + None + } + }) + .collect::>(); + if let Some(lock) = new_lock { + locks.push(lock); + } + >::insert(who, locks); + } + + fn remove_lock(id: LockIdentifier, who: &T::AccountId) { + let now = >::now().saturated_into::(); + >::mutate(who, |locks| { + // unexpired and mismatched id -> keep + locks.retain(|lock| (lock.until > now) && (lock.id != id)); + }); + } +} + +impl, I: Instance> IsDeadAccount for Module +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + fn is_dead_account(who: &T::AccountId) -> bool { + Self::total_balance(who).is_zero() + } +} diff --git a/srml/balances/src/mock.rs b/srml/balances/src/mock.rs new file mode 100644 index 000000000..600d0e6fb --- /dev/null +++ b/srml/balances/src/mock.rs @@ -0,0 +1,190 @@ +// Copyright 2018-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Test utilities + +#![cfg(test)] + +use sr_primitives::{Perbill, traits::{ConvertInto, IdentityLookup}, testing::Header, + weights::{DispatchInfo, Weight}}; +use primitives::H256; +use runtime_io; +use support::{impl_outer_origin, parameter_types}; +use support::traits::Get; +use std::cell::RefCell; +use crate::{GenesisConfig, Module, Trait}; + +impl_outer_origin!{ + pub enum Origin for Runtime {} +} + +thread_local! { + static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); + static TRANSFER_FEE: RefCell = RefCell::new(0); + static CREATION_FEE: RefCell = RefCell::new(0); +} + +pub struct ExistentialDeposit; +impl Get for ExistentialDeposit { + fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) } +} + +pub struct TransferFee; +impl Get for TransferFee { + fn get() -> u64 { TRANSFER_FEE.with(|v| *v.borrow()) } +} + +pub struct CreationFee; +impl Get for CreationFee { + fn get() -> u64 { CREATION_FEE.with(|v| *v.borrow()) } +} + +// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Runtime; +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const MaximumBlockWeight: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} +impl system::Trait for Runtime { + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = (); + type Hash = H256; + type Hashing = ::sr_primitives::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); +} +parameter_types! { + pub const TransactionBaseFee: u64 = 0; + pub const TransactionByteFee: u64 = 1; +} +impl transaction_payment::Trait for Runtime { + type Currency = Module; + type OnTransactionPayment = (); + type TransactionBaseFee = TransactionBaseFee; + type TransactionByteFee = TransactionByteFee; + type WeightToFee = ConvertInto; + type FeeMultiplierUpdate = (); +} +impl Trait for Runtime { + type Balance = u64; + type OnFreeBalanceZero = (); + type OnNewAccount = (); + type Event = (); + type DustRemoval = (); + type TransferPayment = (); + type ExistentialDeposit = ExistentialDeposit; + type TransferFee = TransferFee; + type CreationFee = CreationFee; +} + +pub struct ExtBuilder { + existential_deposit: u64, + transfer_fee: u64, + creation_fee: u64, + monied: bool, + vesting: bool, +} +impl Default for ExtBuilder { + fn default() -> Self { + Self { + existential_deposit: 0, + transfer_fee: 0, + creation_fee: 0, + monied: false, + vesting: false, + } + } +} +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + #[allow(dead_code)] + pub fn transfer_fee(mut self, transfer_fee: u64) -> Self { + self.transfer_fee = transfer_fee; + self + } + pub fn creation_fee(mut self, creation_fee: u64) -> Self { + self.creation_fee = creation_fee; + self + } + pub fn monied(mut self, monied: bool) -> Self { + self.monied = monied; + if self.existential_deposit == 0 { + self.existential_deposit = 1; + } + self + } + pub fn vesting(mut self, vesting: bool) -> Self { + self.vesting = vesting; + self + } + pub fn set_associated_consts(&self) { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); + TRANSFER_FEE.with(|v| *v.borrow_mut() = self.transfer_fee); + CREATION_FEE.with(|v| *v.borrow_mut() = self.creation_fee); + } + pub fn build(self) -> runtime_io::TestExternalities { + self.set_associated_consts(); + let mut t = 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![] + }, + vesting: if self.vesting && self.monied { + vec![ + (1, 0, 10, 5 * self.existential_deposit), + (2, 10, 20, 0), + (12, 10, 20, 5 * self.existential_deposit) + ] + } else { + vec![] + }, + }.assimilate_storage(&mut t).unwrap(); + t.into() + } +} + +pub type System = system::Module; +pub type Balances = Module; + +pub const CALL: &::Call = &(); + +/// 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() } +} diff --git a/srml/balances/src/tests.rs b/srml/balances/src/tests.rs new file mode 100644 index 000000000..8afec6f69 --- /dev/null +++ b/srml/balances/src/tests.rs @@ -0,0 +1,762 @@ +// Copyright 2017-2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Tests for the module. + +#![cfg(test)] + +use super::*; +use mock::{Balances, ExtBuilder, Runtime, System, info_from_weight, CALL}; +use sr_primitives::traits::SignedExtension; +use support::{ + assert_noop, assert_ok, assert_err, + traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons, + Currency, ReservableCurrency, ExistenceRequirement::AllowDeath} +}; +use transaction_payment::ChargeTransactionPayment; +use system::RawOrigin; + +const ID_1: LockIdentifier = *b"1 "; +const ID_2: LockIdentifier = *b"2 "; +const ID_3: LockIdentifier = *b"3 "; + +#[test] +fn basic_locking_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + assert_eq!(Balances::free_balance(&1), 10); + Balances::set_lock(ID_1, &1, 9, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 5, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); +} + +#[test] +fn partial_locking_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn lock_removal_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all()); + Balances::remove_lock(ID_1, &1); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn lock_replacement_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn double_locking_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + Balances::set_lock(ID_2, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn combination_locking_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), 0, WithdrawReasons::none()); + Balances::set_lock(ID_2, &1, 0, u64::max_value(), WithdrawReasons::none()); + Balances::set_lock(ID_3, &1, 0, 0, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn lock_value_extension_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 2, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 8, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); +} + +#[test] +fn lock_reasons_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Transfer.into()); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + assert_ok!(>::reserve(&1, 1)); + // NOTE: this causes a fee payment. + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(1), + &1, + CALL, + info_from_weight(1), + 0, + ).is_ok()); + + Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Reserve.into()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + assert_noop!( + >::reserve(&1, 1), + "account liquidity restrictions prevent withdrawal" + ); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(1), + &1, + CALL, + info_from_weight(1), + 0, + ).is_ok()); + + Balances::set_lock(ID_1, &1, 10, u64::max_value(), 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), + 0, + ).is_err()); + }); +} + +#[test] +fn lock_block_number_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + + System::set_block_number(2); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn lock_block_number_extension_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 10, 1, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + System::set_block_number(2); + Balances::extend_lock(ID_1, &1, 10, 8, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); +} + +#[test] +fn lock_reasons_extension_should_work() { + ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { + Balances::set_lock(ID_1, &1, 10, 10, WithdrawReason::Transfer.into()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReasons::none()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReason::Reserve.into()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); +} + +#[test] +fn default_indexing_on_new_accounts_should_not_work2() { + ExtBuilder::default() + .existential_deposit(10) + .creation_fee(50) + .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), + "value too low to create account", + ); + 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() { + ExtBuilder::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), 0); // "free" account deleted." + assert_eq!(Balances::total_balance(&2), 256 * 19 + 1); // 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 * 18 + 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() { + ExtBuilder::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() { + ExtBuilder::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); + + assert_ok!(Balances::transfer(Some(2).into(), 5, 1901)); // index 1 (account 2) becomes zombie + assert_eq!(Balances::total_balance(&2), 0); + assert_eq!(Balances::total_balance(&5), 1901); + assert_eq!(System::account_nonce(&2), 0); + }); +} + +#[test] +fn dust_account_removal_should_work2() { + ExtBuilder::default() + .existential_deposit(100) + .creation_fee(50) + .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 for 256*10 + 50(fee) < 256 * 10 (ext_deposit) + assert_ok!(Balances::transfer(Some(2).into(), 5, 1851)); + assert_eq!(Balances::total_balance(&2), 0); + assert_eq!(Balances::total_balance(&5), 1851); + assert_eq!(System::account_nonce(&2), 0); + }); +} + +#[test] +fn balance_works() { + ExtBuilder::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() { + ExtBuilder::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() { + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_noop!( + Balances::force_transfer(Some(2).into(), 1, 2, 69), + "RequireRootOrigin", + ); + 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() { + ExtBuilder::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() { + ExtBuilder::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), + "balance too low to send value", + ); + }); +} + +#[test] +fn deducting_balance_should_work() { + ExtBuilder::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() { + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + Balances::set_reserved_balance(&1, 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() { + ExtBuilder::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() { + ExtBuilder::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() { + ExtBuilder::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() { + ExtBuilder::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() { + ExtBuilder::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 transferring_reserved_balance_should_work() { + ExtBuilder::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), 0); + 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_to_nonexistent_should_fail() { + ExtBuilder::default().build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 111)); + assert_noop!(Balances::repatriate_reserved(&1, &2, 42), "beneficiary account must pre-exist"); + }); +} + +#[test] +fn transferring_incomplete_reserved_balance_should_work() { + ExtBuilder::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), 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() { + ExtBuilder::default().build().execute_with(|| { + >::insert(1, u64::max_value()); + >::insert(2, 1); + + assert_err!( + Balances::transfer(Some(1).into(), 2, u64::max_value()), + "destination balance too high to receive value", + ); + + 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() { + ExtBuilder::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() { + ExtBuilder::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() { + ExtBuilder::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 transfer_overflow_isnt_exploitable() { + ExtBuilder::default().creation_fee(50).build().execute_with(|| { + // Craft a value that will overflow if summed with `creation_fee`. + let evil_value = u64::max_value() - 49; + + assert_err!( + Balances::transfer(Some(1).into(), 5, evil_value), + "got overflow after adding a fee to value", + ); + }); +} + +#[test] +fn check_vesting_status() { + ExtBuilder::default() + .existential_deposit(256) + .monied(true) + .vesting(true) + .build() + .execute_with(|| { + assert_eq!(System::block_number(), 1); + let user1_free_balance = Balances::free_balance(&1); + let user2_free_balance = Balances::free_balance(&2); + let user12_free_balance = Balances::free_balance(&12); + assert_eq!(user1_free_balance, 256 * 10); // Account 1 has free balance + assert_eq!(user2_free_balance, 256 * 20); // Account 2 has free balance + assert_eq!(user12_free_balance, 256 * 10); // Account 12 has free balance + let user1_vesting_schedule = VestingSchedule { + locked: 256 * 5, + per_block: 128, // Vesting over 10 blocks + starting_block: 0, + }; + let user2_vesting_schedule = VestingSchedule { + locked: 256 * 20, + per_block: 256, // Vesting over 20 blocks + starting_block: 10, + }; + let user12_vesting_schedule = VestingSchedule { + locked: 256 * 5, + per_block: 64, // Vesting over 20 blocks + starting_block: 10, + }; + assert_eq!(Balances::vesting(&1), Some(user1_vesting_schedule)); // Account 1 has a vesting schedule + assert_eq!(Balances::vesting(&2), Some(user2_vesting_schedule)); // Account 2 has a vesting schedule + assert_eq!(Balances::vesting(&12), Some(user12_vesting_schedule)); // Account 12 has a vesting schedule + + // Account 1 has only 128 units vested from their illiquid 256 * 5 units at block 1 + assert_eq!(Balances::vesting_balance(&1), 128 * 9); + // Account 2 has their full balance locked + assert_eq!(Balances::vesting_balance(&2), user2_free_balance); + // Account 12 has only their illiquid funds locked + assert_eq!(Balances::vesting_balance(&12), user12_free_balance - 256 * 5); + + System::set_block_number(10); + assert_eq!(System::block_number(), 10); + + // Account 1 has fully vested by block 10 + assert_eq!(Balances::vesting_balance(&1), 0); + // Account 2 has started vesting by block 10 + assert_eq!(Balances::vesting_balance(&2), user2_free_balance); + // Account 12 has started vesting by block 10 + assert_eq!(Balances::vesting_balance(&12), user12_free_balance - 256 * 5); + + System::set_block_number(30); + assert_eq!(System::block_number(), 30); + + assert_eq!(Balances::vesting_balance(&1), 0); // Account 1 is still fully vested, and not negative + assert_eq!(Balances::vesting_balance(&2), 0); // Account 2 has fully vested by block 30 + assert_eq!(Balances::vesting_balance(&12), 0); // Account 2 has fully vested by block 30 + + }); +} + +#[test] +fn unvested_balance_should_not_transfer() { + ExtBuilder::default() + .existential_deposit(10) + .monied(true) + .vesting(true) + .build() + .execute_with(|| { + assert_eq!(System::block_number(), 1); + let user1_free_balance = Balances::free_balance(&1); + assert_eq!(user1_free_balance, 100); // Account 1 has free balance + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + assert_eq!(Balances::vesting_balance(&1), 45); + assert_noop!( + Balances::transfer(Some(1).into(), 2, 56), + "vesting balance too high to send value", + ); // Account 1 cannot send more than vested amount + }); +} + +#[test] +fn vested_balance_should_transfer() { + ExtBuilder::default() + .existential_deposit(10) + .monied(true) + .vesting(true) + .build() + .execute_with(|| { + assert_eq!(System::block_number(), 1); + let user1_free_balance = Balances::free_balance(&1); + assert_eq!(user1_free_balance, 100); // Account 1 has free balance + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + assert_eq!(Balances::vesting_balance(&1), 45); + assert_ok!(Balances::transfer(Some(1).into(), 2, 55)); + }); +} + +#[test] +fn extra_balance_should_transfer() { + ExtBuilder::default() + .existential_deposit(10) + .monied(true) + .vesting(true) + .build() + .execute_with(|| { + assert_eq!(System::block_number(), 1); + assert_ok!(Balances::transfer(Some(3).into(), 1, 100)); + assert_ok!(Balances::transfer(Some(3).into(), 2, 100)); + + let user1_free_balance = Balances::free_balance(&1); + assert_eq!(user1_free_balance, 200); // Account 1 has 100 more free balance than normal + + let user2_free_balance = Balances::free_balance(&2); + assert_eq!(user2_free_balance, 300); // Account 2 has 100 more free balance than normal + + // Account 1 has only 5 units vested at block 1 (plus 150 unvested) + assert_eq!(Balances::vesting_balance(&1), 45); + assert_ok!(Balances::transfer(Some(1).into(), 3, 155)); // Account 1 can send extra units gained + + // Account 2 has no units vested at block 1, but gained 100 + assert_eq!(Balances::vesting_balance(&2), 200); + assert_ok!(Balances::transfer(Some(2).into(), 3, 100)); // Account 2 can send extra units gained + }); +} + +#[test] +fn liquid_funds_should_transfer_with_delayed_vesting() { + ExtBuilder::default() + .existential_deposit(256) + .monied(true) + .vesting(true) + .build() + .execute_with(|| { + assert_eq!(System::block_number(), 1); + let user12_free_balance = Balances::free_balance(&12); + + assert_eq!(user12_free_balance, 2560); // Account 12 has free balance + // Account 12 has liquid funds + assert_eq!(Balances::vesting_balance(&12), user12_free_balance - 256 * 5); + + // Account 12 has delayed vesting + let user12_vesting_schedule = VestingSchedule { + locked: 256 * 5, + per_block: 64, // Vesting over 20 blocks + starting_block: 10, + }; + assert_eq!(Balances::vesting(&12), Some(user12_vesting_schedule)); + + // Account 12 can still send liquid funds + assert_ok!(Balances::transfer(Some(12).into(), 3, 256 * 5)); + }); +} + +#[test] +fn burn_must_work() { + ExtBuilder::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() { + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + assert_err!( + Balances::transfer_keep_alive(Some(1).into(), 2, 100), + "transfer would kill account" + ); + assert_eq!(Balances::is_dead_account(&1), false); + assert_eq!(Balances::total_balance(&1), 100); + assert_eq!(Balances::total_balance(&2), 0); + }); +} diff --git a/srml/chainrelay/bridge/ethereum/Cargo.toml b/srml/chainrelay/bridge/ethereum/Cargo.toml index 63c5cf4bb..f03762802 100644 --- a/srml/chainrelay/bridge/ethereum/Cargo.toml +++ b/srml/chainrelay/bridge/ethereum/Cargo.toml @@ -15,6 +15,7 @@ rstd = { package = "sr-std", git = 'https://github.com/darwinia-network/substrat support = { package = "srml-support", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } system = { package = "srml-system", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } +darwinia-support = { path = "../../../support", default-features = false } merkle-mountain-range = { path = "../../../../core/merkle-mountain-range", default-features = false } [features] @@ -28,5 +29,6 @@ std = [ "support/std", "system/std", - "merkle-mountain-range/std", + "darwinia-support/std", + "merkle-mountain-range/std" ] diff --git a/srml/chainrelay/bridge/ethereum/src/lib.rs b/srml/chainrelay/bridge/ethereum/src/lib.rs index ad798f0a6..58f2c618f 100644 --- a/srml/chainrelay/bridge/ethereum/src/lib.rs +++ b/srml/chainrelay/bridge/ethereum/src/lib.rs @@ -13,11 +13,12 @@ use support::{ }; use system::ensure_signed; +use darwinia_support::types::TimeStamp; //use merkle_mountain_range::{MerkleMountainRange, Hash}; pub trait Trait: system::Trait { type Event: From> + Into<::Event>; - type Ring: LockableCurrency; + type Ring: LockableCurrency; } // config() require `serde = { version = "1.0.101", optional = true }` diff --git a/srml/chainrelay/construct.md b/srml/chainrelay/construct.md deleted file mode 100644 index f83a4367e..000000000 --- a/srml/chainrelay/construct.md +++ /dev/null @@ -1,29 +0,0 @@ -- Bridge - - EOS bridge - - Ethereum bridge - - Deposit Pool (shared ?) - - Deposit Value (adjustable) - - Verified Header (Vec\
or MPT\
) - - Unverified (HashMap\ ?) - - ... - - ... -- Relayer - - ... - ---- - -- submit_header(relayer, header) -- lock() -- redeem(account, transaction) -- challenge(relayer) ? -- punish(relayer) ? -- reward(relayer) ? -- verify_submit(header) - 1. if exists? - 2. verify (difficulty + prev_hash + nonce) - 3. challenge -- verify_lock(transaction) - 1. get release value - 2. verify most-worked - 3. ... -- release(account, value) diff --git a/srml/kton/Cargo.toml b/srml/kton/Cargo.toml index 362f8028c..edbfc03c6 100644 --- a/srml/kton/Cargo.toml +++ b/srml/kton/Cargo.toml @@ -15,7 +15,7 @@ srml-support = { git = 'https://github.com/darwinia-network/substrate.git', defa system = { package = "srml-system", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } substrate-primitives = { git = 'https://github.com/darwinia-network/substrate.git', default-features = false } -darwinia-support = { package = "darwinia-support", path = "../support", default-features = false } +darwinia-support = { path = "../support", default-features = false } [dev-dependencies] runtime_io = { package = "sr-io", git = 'https://github.com/darwinia-network/substrate.git' } @@ -34,5 +34,6 @@ std = [ "system/std", "timestamp/std", "substrate-primitives/std", - "darwinia-support/std", + + "darwinia-support/std" ] diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 643e53ced..b7b4542f3 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -1,25 +1,26 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Codec, Decode, Encode}; -use rstd::prelude::*; -use rstd::{cmp, result}; +use rstd::{cmp, fmt::Debug, prelude::*, result}; use sr_primitives::{ traits::{ - Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, One, Saturating, SimpleArithmetic, - StaticLookup, Zero, + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, One, SaturatedConversion, Saturating, + SimpleArithmetic, StaticLookup, Zero, }, RuntimeDebug, }; - -use srml_support::dispatch::Result; -use srml_support::traits::{ - Currency, ExistenceRequirement, Imbalance, LockIdentifier, LockableCurrency, OnUnbalanced, SignedImbalance, - UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, +use srml_support::{ + decl_event, decl_module, decl_storage, + dispatch::Result, + traits::{ + Currency, ExistenceRequirement, Imbalance, LockIdentifier, LockableCurrency, OnUnbalanced, SignedImbalance, + UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, + }, + Parameter, StorageMap, StorageValue, }; -use srml_support::{decl_event, decl_module, decl_storage, Parameter, StorageMap, StorageValue}; use system::ensure_signed; -// customed +use darwinia_support::types::TimeStamp; use imbalance::{NegativeImbalance, PositiveImbalance}; #[cfg(test)] @@ -54,10 +55,10 @@ impl VestingSchedule { } #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub struct BalanceLock { +pub struct BalanceLock { pub id: LockIdentifier, pub amount: Balance, - pub until: BlockNumber, + pub until: TimeStamp, pub reasons: WithdrawReasons, } @@ -105,7 +106,7 @@ decl_storage! { pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance; - pub Locks get(locks): map T::AccountId => Vec>; + pub Locks get(locks): map T::AccountId => Vec>; pub TotalLock get(total_lock): T::Balance; @@ -236,7 +237,7 @@ impl Currency for Module { return Ok(()); } - let now = >::block_number(); + let now = >::now().saturated_into::(); if locks .into_iter() .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) @@ -380,19 +381,19 @@ impl Currency for Module { impl LockableCurrency for Module where - T::Balance: MaybeSerializeDeserialize, + T::Balance: MaybeSerializeDeserialize + Debug, { - type Moment = T::BlockNumber; + type Moment = TimeStamp; // `amount` > `free_balance` is allowed fn set_lock( id: LockIdentifier, who: &T::AccountId, amount: T::Balance, - until: T::BlockNumber, + until: TimeStamp, reasons: WithdrawReasons, ) { - let now = >::block_number(); + let now = >::now().saturated_into::(); let mut new_lock = Some(BalanceLock { id, amount, @@ -421,10 +422,10 @@ where id: LockIdentifier, who: &T::AccountId, amount: T::Balance, - until: T::BlockNumber, + until: TimeStamp, reasons: WithdrawReasons, ) { - let now = >::block_number(); + let now = >::now().saturated_into::(); let mut new_lock = Some(BalanceLock { id, amount, @@ -455,7 +456,7 @@ where } fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let now = >::block_number(); + let now = >::now().saturated_into::(); >::mutate(who, |locks| { // unexpired and mismatched id -> keep locks.retain(|lock| (lock.until > now) && (lock.id != id)); diff --git a/srml/staking/Cargo.toml b/srml/staking/Cargo.toml index 97657bf25..8dfcf8c39 100644 --- a/srml/staking/Cargo.toml +++ b/srml/staking/Cargo.toml @@ -16,19 +16,21 @@ srml-support = { git = 'https://github.com/darwinia-network/substrate.git', defa system = { package = "srml-system", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } session = { package = "srml-session",git = 'https://github.com/darwinia-network/substrate.git', default-features = false, features = ["historical"] } -darwinia-support = { package = "darwinia-support", path = "../support", default-features = false } substrate-primitives = { git = 'https://github.com/darwinia-network/substrate.git', default-features = false } sr-arithmetic = { git = 'https://github.com/darwinia-network/substrate.git', default-features = false } sr-staking-primitives = { git = 'https://github.com/darwinia-network/substrate.git', default-features = false } authorship = { package = "srml-authorship", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } #phragmen = { package = "substrate-phragmen", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } +darwinia-support = { package = "darwinia-support", path = "../support", default-features = false } + [dev-dependencies] substrate-primitives = { git = 'https://github.com/darwinia-network/substrate.git' } timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git' } -balances = { package = "srml-balances", git = 'https://github.com/darwinia-network/substrate.git' } -kton = { package = "darwinia-kton", path = "../kton" } rand = "0.7.2" + +balances = { package = "darwinia-balances", path = '../balances', default-features = false } +kton = { package = "darwinia-kton", path = "../kton" } node-runtime = { path = "../../node/runtime" } [features] @@ -48,8 +50,9 @@ std = [ "session/std", "system/std", "timestamp/std", - "darwinia-support/std", "sr-arithmetic/std", "sr-staking-primitives/std", - "authorship/std" + "authorship/std", + + "darwinia-support/std", ] diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 8d9924ed1..6dc37d730 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -25,7 +25,7 @@ extern crate test; use codec::{CompactAs, Decode, Encode, HasCompact}; use rstd::{collections::btree_map::BTreeMap, prelude::*, result}; use session::{historical::OnSessionEnding, SelectInitialValidators}; -use sr_primitives::traits::{Bounded, CheckedSub, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}; +use sr_primitives::traits::{CheckedSub, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}; #[cfg(feature = "std")] use sr_primitives::{Deserialize, Serialize}; use sr_primitives::{Perbill, RuntimeDebug}; @@ -39,6 +39,7 @@ use srml_support::{ }; use system::{ensure_root, ensure_signed}; +use darwinia_support::types::TimeStamp; use phragmen::{elect, equalize, ExtendedBalance, PhragmenStakedAssignment, Support, SupportMap}; mod utils; @@ -214,8 +215,8 @@ type Assignment = (::AccountId, ExtendedBalance, Extended type ExpoMap = BTreeMap<::AccountId, Exposure<::AccountId, ExtendedBalance>>; pub trait Trait: timestamp::Trait + session::Trait { - type Ring: LockableCurrency; - type Kton: LockableCurrency; + type Ring: LockableCurrency; + type Kton: LockableCurrency; type CurrencyToVote: Convert, u64> + Convert>; @@ -876,7 +877,7 @@ impl Module { STAKING_ID, &ledger.stash, ledger.total_ring, - T::BlockNumber::max_value(), + TimeStamp::max_value(), WithdrawReasons::all(), ), @@ -884,7 +885,7 @@ impl Module { STAKING_ID, &ledger.stash, ledger.total_kton, - T::BlockNumber::max_value(), + TimeStamp::max_value(), WithdrawReasons::all(), ), } diff --git a/srml/support/src/lib.rs b/srml/support/src/lib.rs index 57a707ac6..5d8de4676 100644 --- a/srml/support/src/lib.rs +++ b/srml/support/src/lib.rs @@ -1,3 +1,4 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod traits; +pub mod types; diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs new file mode 100644 index 000000000..d9e522a42 --- /dev/null +++ b/srml/support/src/types.rs @@ -0,0 +1 @@ +pub type TimeStamp = u64; From aad5db0d40cca39b81431ef786d8697775137605 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 14 Nov 2019 13:08:38 +0800 Subject: [PATCH 02/30] update: kton tests --- srml/kton/src/mock.rs | 70 ++++++++++++++--------------- srml/kton/src/tests.rs | 99 ++++++++++++++++++++++-------------------- 2 files changed, 89 insertions(+), 80 deletions(-) diff --git a/srml/kton/src/mock.rs b/srml/kton/src/mock.rs index f5acf607f..60aed4901 100644 --- a/srml/kton/src/mock.rs +++ b/srml/kton/src/mock.rs @@ -1,10 +1,15 @@ +use std::{cell::RefCell, collections::HashSet}; + +use sr_primitives::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + Perbill, +}; +use srml_support::{impl_outer_origin, parameter_types}; +use substrate_primitives::H256; + use super::*; use crate::{GenesisConfig, Module}; -use primitives::testing::Header; -use primitives::traits::IdentityLookup; -use srml_support::impl_outer_origin; -use std::{cell::RefCell, collections::HashSet}; -use substrate_primitives::{Blake2Hasher, H256}; pub const COIN: u64 = 1_000_000_000; @@ -15,30 +20,9 @@ thread_local! { /// The AccountId alias in this test module. pub type AccountId = u64; -// pub type BlockNumber = u64; +pub type BlockNumber = u64; pub type Balance = u64; -#[allow(unused_doc_comments)] -/// Simple structure that exposes how u64 currency can be represented as... u64. -// pub struct CurrencyToVoteHandler; -// impl Convert for CurrencyToVoteHandler { -// fn convert(x: u64) -> u64 { -// x -// } -// } -// impl Convert for CurrencyToVoteHandler { -// fn convert(x: u128) -> u64 { -// x as u64 -// } -// } - -// pub struct ExistentialDeposit; -// impl Get for ExistentialDeposit { -// fn get() -> u64 { -// EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) -// } -// } - impl_outer_origin! { pub enum Origin for Test {} } @@ -46,22 +30,37 @@ impl_outer_origin! { // 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: u32 = 1024; + pub const MaximumBlockLength: u32 = 2 * 1024; + pub const AvailableBlockRatio: Perbill = Perbill::one(); +} impl system::Trait for Test { type Origin = Origin; + type Call = (); type Index = u64; - type BlockNumber = u64; + type BlockNumber = BlockNumber; type Hash = H256; - type Hashing = ::primitives::traits::BlakeTwo256; + type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type Event = (); + type BlockHashCount = BlockHashCount; + type MaximumBlockWeight = MaximumBlockWeight; + type MaximumBlockLength = MaximumBlockLength; + type AvailableBlockRatio = AvailableBlockRatio; + type Version = (); } +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} impl timestamp::Trait for Test { type Moment = u64; type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; } impl Trait for Test { @@ -91,14 +90,15 @@ impl ExtBuilder { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); } - pub fn build(self) -> runtime_io::TestExternalities { + pub fn build(self) -> runtime_io::TestExternalities { self.set_associated_consts(); - let (mut t, mut c) = system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = system::GenesisConfig::default().build_storage::().unwrap(); let balance_factor = if self.existential_deposit > 0 { 1_000 * COIN } else { 1 * COIN }; + let _ = GenesisConfig:: { balances: vec![ (1, 10 * balance_factor), @@ -118,10 +118,12 @@ impl ExtBuilder { ], vesting: vec![(1, 0, 4)], } - .assimilate_storage(&mut t, &mut c); + .assimilate_storage(&mut t); + t.into() } } + +pub type Timestamp = timestamp::Module; pub type System = system::Module; pub type Kton = Module; -// pub type Timestamp = timestamp::Module; diff --git a/srml/kton/src/tests.rs b/srml/kton/src/tests.rs index c42c7ad7f..de1ebabd8 100644 --- a/srml/kton/src/tests.rs +++ b/srml/kton/src/tests.rs @@ -1,8 +1,10 @@ +use srml_support::{ + assert_err, assert_noop, assert_ok, + traits::{Currency, LockIdentifier, WithdrawReason, WithdrawReasons}, +}; + use super::*; -use mock::{ExtBuilder, Kton, Origin, System, Test}; -use runtime_io::with_externalities; -use srml_support::traits::{Currency, LockIdentifier, WithdrawReason, WithdrawReasons}; -use srml_support::{assert_err, assert_noop, assert_ok}; +use crate::mock::*; const ID_1: LockIdentifier = *b"1 "; const ID_2: LockIdentifier = *b"2 "; @@ -10,7 +12,7 @@ const ID_3: LockIdentifier = *b"3 "; #[test] fn transfer_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { let _ = Kton::deposit_creating(&666, 100); assert_ok!(Kton::transfer(Origin::signed(666), 777, 50)); @@ -27,7 +29,7 @@ fn transfer_should_work() { #[test] fn transfer_should_fail() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { let _ = Kton::deposit_creating(&777, 1); assert_err!( Kton::transfer(Origin::signed(666), 777, 50), @@ -55,7 +57,7 @@ fn transfer_should_fail() { #[test] fn set_lock_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { let lock_ids = [[0; 8], [1; 8], [2; 8], [3; 8]]; let balance_per_lock = Kton::free_balance(&1) / (lock_ids.len() as u64); @@ -88,38 +90,36 @@ fn set_lock_should_work() { #[test] fn remove_lock_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + Timestamp::set_timestamp(0); + let ts: u64 = Timestamp::now().into(); + Kton::set_lock(ID_1, &2, u64::max_value(), u64::max_value(), WithdrawReasons::all()); - Kton::set_lock( - ID_2, - &2, - u64::max_value(), - >::block_number() + 1, - WithdrawReasons::all(), - ); - // expired - Kton::set_lock( - ID_3, - &2, - u64::max_value(), - >::block_number(), - WithdrawReasons::all(), + assert_err!( + Kton::transfer(Origin::signed(2), 1, 1), + "account liquidity restrictions prevent withdrawal" ); + // unexpired + Kton::set_lock(ID_2, &2, u64::max_value(), ts + 1, WithdrawReasons::all()); Kton::remove_lock(ID_1, &2); + Timestamp::set_timestamp(ts); assert_err!( Kton::transfer(Origin::signed(2), 1, 1), "account liquidity restrictions prevent withdrawal" ); - Kton::remove_lock(ID_2, &2); assert_ok!(Kton::transfer(Origin::signed(2), 1, 1)); + + // expired + Kton::set_lock(ID_3, &2, u64::max_value(), ts, WithdrawReasons::all()); + assert_ok!(Kton::transfer(Origin::signed(2), 1, 1)); }); } #[test] fn update_lock_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { let mut locks = vec![]; for id in 0..10 { // until > 1 @@ -132,7 +132,7 @@ fn update_lock_should_work() { Kton::set_lock([id; 8], &1, 1, 2, WithdrawReasons::none()); } let update_id = 4; - for amount in 32767..65535 { + for amount in 32767u64..65535 { let until = amount + 1; locks[update_id as usize] = BalanceLock { id: [update_id; 8], @@ -148,18 +148,18 @@ fn update_lock_should_work() { #[test] fn combination_locking_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { Kton::deposit_creating(&1001, 10); Kton::set_lock(ID_1, &1001, u64::max_value(), 0, WithdrawReasons::none()); Kton::set_lock(ID_2, &1001, 0, u64::max_value(), WithdrawReasons::none()); Kton::set_lock(ID_3, &1001, 0, 0, WithdrawReasons::all()); - assert_ok!(>::transfer(&1001, &1002, 1)); + assert_ok!(Kton::transfer(Origin::signed(1001), 1002, 1)); }); } #[test] fn extend_lock_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { let mut locks = vec![]; { let amount = 1; @@ -214,37 +214,44 @@ fn extend_lock_should_work() { #[test] fn lock_block_number_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { Kton::deposit_creating(&1001, 10); - Kton::set_lock(ID_1, &1001, 10, 2, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1001, &1002, 1), - "account liquidity restrictions prevent withdrawal" - ); - System::set_block_number(2); - assert_ok!(>::transfer(&1001, &1002, 1)); + for &until in [1, 2, 3, 4].iter() { + Timestamp::set_timestamp(0); + + Kton::set_lock(ID_1, &1001, 10, until, WithdrawReasons::all()); + assert_noop!( + Kton::transfer(Origin::signed(1001), 1002, 1), + "account liquidity restrictions prevent withdrawal" + ); + + Timestamp::set_timestamp(until); + assert_ok!(Kton::transfer(Origin::signed(1001), 1002, 1)); + } }); } #[test] fn lock_block_number_extension_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { Kton::deposit_creating(&1001, 10); Kton::set_lock(ID_1, &1001, 10, 2, WithdrawReasons::all()); assert_noop!( - >::transfer(&1001, &1002, 6), + Kton::transfer(Origin::signed(1001), 1002, 6), "account liquidity restrictions prevent withdrawal" ); Kton::extend_lock(ID_1, &1001, 10, 1, WithdrawReasons::all()); assert_noop!( - >::transfer(&1001, &1002, 6), + Kton::transfer(Origin::signed(1001), 1002, 6), "account liquidity restrictions prevent withdrawal" ); - System::set_block_number(2); - Kton::extend_lock(ID_1, &1001, 10, 8, WithdrawReasons::all()); + + let until = 8; + Timestamp::set_timestamp(until - 1); + Kton::extend_lock(ID_1, &1001, 10, until, WithdrawReasons::all()); assert_noop!( - >::transfer(&1001, &1002, 3), + Kton::transfer(Origin::signed(1001), 1002, 3), "account liquidity restrictions prevent withdrawal" ); }); @@ -252,21 +259,21 @@ fn lock_block_number_extension_should_work() { #[test] fn lock_reasons_extension_should_work() { - with_externalities(&mut ExtBuilder::default().existential_deposit(0).build(), || { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { Kton::deposit_creating(&1001, 10); Kton::set_lock(ID_1, &1001, 10, 10, WithdrawReason::Transfer.into()); assert_noop!( - >::transfer(&1001, &1002, 6), + Kton::transfer(Origin::signed(1001), 1002, 6), "account liquidity restrictions prevent withdrawal" ); Kton::extend_lock(ID_1, &1001, 10, 10, WithdrawReasons::none()); assert_noop!( - >::transfer(&1001, &1002, 6), + Kton::transfer(Origin::signed(1001), 1002, 6), "account liquidity restrictions prevent withdrawal" ); Kton::extend_lock(ID_1, &1001, 10, 10, WithdrawReason::Reserve.into()); assert_noop!( - >::transfer(&1001, &1002, 6), + Kton::transfer(Origin::signed(1001), 1002, 6), "account liquidity restrictions prevent withdrawal" ); }); From 1de2b6cce8c94e2412d3687da7b81138a34e482c Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 14 Nov 2019 13:32:42 +0800 Subject: [PATCH 03/30] update: `LockableCurrency::Moment` support generic Type --- Cargo.lock | 2 -- srml/balances/Cargo.toml | 4 ---- srml/balances/src/lib.rs | 26 ++++++++++++-------------- srml/kton/Cargo.toml | 3 --- srml/kton/src/lib.rs | 25 ++++++++++++------------- 5 files changed, 24 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d28150ab6..f5062164d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,7 +685,6 @@ dependencies = [ name = "darwinia-balances" version = "2.0.0" dependencies = [ - "darwinia-support 0.1.0", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", @@ -728,7 +727,6 @@ dependencies = [ name = "darwinia-kton" version = "0.1.0" dependencies = [ - "darwinia-support 0.1.0", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/srml/balances/Cargo.toml b/srml/balances/Cargo.toml index 7955fffa6..db5f5a94c 100644 --- a/srml/balances/Cargo.toml +++ b/srml/balances/Cargo.toml @@ -15,8 +15,6 @@ support = { package = "srml-support", git = "https://github.com/darwinia-network system = { package = "srml-system", git = "https://github.com/darwinia-network/substrate.git", default-features = false } timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } -darwinia-support = { path = "../support", default-features = false } - [dev-dependencies] runtime-io = { package = "sr-io", git = "https://github.com/darwinia-network/substrate.git" } primitives = { package = "substrate-primitives", git = "https://github.com/darwinia-network/substrate.git" } @@ -34,6 +32,4 @@ std = [ "sr-primitives/std", "system/std", "timestamp/std", - - "darwinia-support/std" ] diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index f7ad548da..3b1fa13ed 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -152,8 +152,8 @@ use rstd::prelude::*; use rstd::{cmp, fmt::Debug, mem, result}; use sr_primitives::{ traits::{ - Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SaturatedConversion, Saturating, - SimpleArithmetic, StaticLookup, Zero, + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Saturating, SimpleArithmetic, StaticLookup, + Zero, }, weights::SimpleDispatchInfo, RuntimeDebug, @@ -169,8 +169,6 @@ use support::{ }; use system::{ensure_root, ensure_signed, IsDeadAccount, OnNewAccount}; -use darwinia_support::types::TimeStamp; - mod mock; mod tests; @@ -301,10 +299,10 @@ impl Ves } #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] -pub struct BalanceLock { +pub struct BalanceLock { pub id: LockIdentifier, pub amount: Balance, - pub until: TimeStamp, + pub until: Moment, pub reasons: WithdrawReasons, } @@ -371,7 +369,7 @@ decl_storage! { pub ReservedBalance get(fn reserved_balance): map T::AccountId => T::Balance; /// Any liquidity locks on some account balances. - pub Locks get(fn locks): map T::AccountId => Vec>; + pub Locks get(fn locks): map T::AccountId => Vec>; } add_extra_genesis { config(balances): Vec<(T::AccountId, T::Balance)>; @@ -874,7 +872,7 @@ where return Ok(()); } - let now = >::now().saturated_into::(); + let now = >::now(); if locks .into_iter() .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) @@ -1119,17 +1117,17 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Moment = TimeStamp; + type Moment = T::Moment; // `amount` > `free_balance` is allowed fn set_lock( id: LockIdentifier, who: &T::AccountId, amount: T::Balance, - until: TimeStamp, + until: Self::Moment, reasons: WithdrawReasons, ) { - let now = >::now().saturated_into::(); + let now = >::now(); let mut new_lock = Some(BalanceLock { id, amount, @@ -1158,10 +1156,10 @@ where id: LockIdentifier, who: &T::AccountId, amount: T::Balance, - until: TimeStamp, + until: Self::Moment, reasons: WithdrawReasons, ) { - let now = >::now().saturated_into::(); + let now = >::now(); let mut new_lock = Some(BalanceLock { id, amount, @@ -1192,7 +1190,7 @@ where } fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let now = >::now().saturated_into::(); + let now = >::now(); >::mutate(who, |locks| { // unexpired and mismatched id -> keep locks.retain(|lock| (lock.until > now) && (lock.id != id)); diff --git a/srml/kton/Cargo.toml b/srml/kton/Cargo.toml index edbfc03c6..38d726cdf 100644 --- a/srml/kton/Cargo.toml +++ b/srml/kton/Cargo.toml @@ -15,7 +15,6 @@ srml-support = { git = 'https://github.com/darwinia-network/substrate.git', defa system = { package = "srml-system", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } substrate-primitives = { git = 'https://github.com/darwinia-network/substrate.git', default-features = false } -darwinia-support = { path = "../support", default-features = false } [dev-dependencies] runtime_io = { package = "sr-io", git = 'https://github.com/darwinia-network/substrate.git' } @@ -34,6 +33,4 @@ std = [ "system/std", "timestamp/std", "substrate-primitives/std", - - "darwinia-support/std" ] diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index b7b4542f3..ccd466aa8 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -4,8 +4,8 @@ use codec::{Codec, Decode, Encode}; use rstd::{cmp, fmt::Debug, prelude::*, result}; use sr_primitives::{ traits::{ - Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, One, SaturatedConversion, Saturating, - SimpleArithmetic, StaticLookup, Zero, + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, One, Saturating, SimpleArithmetic, + StaticLookup, Zero, }, RuntimeDebug, }; @@ -20,7 +20,6 @@ use srml_support::{ }; use system::ensure_signed; -use darwinia_support::types::TimeStamp; use imbalance::{NegativeImbalance, PositiveImbalance}; #[cfg(test)] @@ -55,10 +54,10 @@ impl VestingSchedule { } #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub struct BalanceLock { +pub struct BalanceLock { pub id: LockIdentifier, pub amount: Balance, - pub until: TimeStamp, + pub until: Moment, pub reasons: WithdrawReasons, } @@ -106,7 +105,7 @@ decl_storage! { pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance; - pub Locks get(locks): map T::AccountId => Vec>; + pub Locks get(locks): map T::AccountId => Vec>; pub TotalLock get(total_lock): T::Balance; @@ -237,7 +236,7 @@ impl Currency for Module { return Ok(()); } - let now = >::now().saturated_into::(); + let now = >::now(); if locks .into_iter() .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) @@ -383,17 +382,17 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Moment = TimeStamp; + type Moment = T::Moment; // `amount` > `free_balance` is allowed fn set_lock( id: LockIdentifier, who: &T::AccountId, amount: T::Balance, - until: TimeStamp, + until: Self::Moment, reasons: WithdrawReasons, ) { - let now = >::now().saturated_into::(); + let now = >::now(); let mut new_lock = Some(BalanceLock { id, amount, @@ -422,10 +421,10 @@ where id: LockIdentifier, who: &T::AccountId, amount: T::Balance, - until: TimeStamp, + until: Self::Moment, reasons: WithdrawReasons, ) { - let now = >::now().saturated_into::(); + let now = >::now(); let mut new_lock = Some(BalanceLock { id, amount, @@ -456,7 +455,7 @@ where } fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let now = >::now().saturated_into::(); + let now = >::now(); >::mutate(who, |locks| { // unexpired and mismatched id -> keep locks.retain(|lock| (lock.until > now) && (lock.id != id)); From 9cf10b68c647e5808c194bceda1e618b279037e0 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Thu, 14 Nov 2019 18:35:42 +0800 Subject: [PATCH 04/30] update: new design for `LockableCurrency` --- Cargo.lock | 15 +- Cargo.toml | 1 - node/runtime/Cargo.toml | 8 +- node/runtime/src/lib.rs | 10 +- srml/balances/Cargo.toml | 4 + srml/balances/src/lib.rs | 281 +-- srml/chainrelay/bridge/eos/Cargo.toml | 26 - srml/chainrelay/bridge/eos/src/lib.rs | 36 - srml/chainrelay/bridge/ethereum/src/lib.rs | 15 +- srml/kton/Cargo.toml | 4 + srml/kton/src/lib.rs | 153 +- srml/staking/src/lib.rs | 336 ++- srml/staking/src/mock.rs | 9 +- srml/staking/src/tests.rs | 2164 ++++++++++---------- srml/support/src/traits.rs | 26 + srml/support/src/types.rs | 9 + 16 files changed, 1421 insertions(+), 1676 deletions(-) delete mode 100644 srml/chainrelay/bridge/eos/Cargo.toml delete mode 100644 srml/chainrelay/bridge/eos/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f5062164d..49cbdfd66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,6 +685,7 @@ dependencies = [ name = "darwinia-balances" version = "2.0.0" dependencies = [ + "darwinia-support 0.1.0", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", @@ -699,17 +700,6 @@ dependencies = [ "substrate-primitives 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", ] -[[package]] -name = "darwinia-eos-bridge" -version = "0.1.0" -dependencies = [ - "merkle-mountain-range 0.1.0", - "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "sr-std 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", - "srml-support 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", - "srml-system 2.0.0 (git+https://github.com/darwinia-network/substrate.git)", -] - [[package]] name = "darwinia-ethereum-bridge" version = "0.1.0" @@ -727,6 +717,7 @@ dependencies = [ name = "darwinia-kton" version = "0.1.0" dependencies = [ + "darwinia-support 0.1.0", "parity-scale-codec 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", "safe-mix 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.102 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2666,10 +2657,10 @@ name = "node-runtime" version = "0.1.0" dependencies = [ "darwinia-balances 2.0.0", - "darwinia-eos-bridge 0.1.0", "darwinia-ethereum-bridge 0.1.0", "darwinia-kton 0.1.0", "darwinia-staking 0.1.0", + "darwinia-support 0.1.0", "integer-sqrt 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "merkle-mountain-range 0.1.0", "node-primitives 2.0.0", diff --git a/Cargo.toml b/Cargo.toml index 27bcc7abe..9b9579510 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,7 +79,6 @@ members = [ "srml/kton", "srml/staking", - "srml/chainrelay/bridge/eos", "srml/chainrelay/bridge/ethereum", ] diff --git a/node/runtime/Cargo.toml b/node/runtime/Cargo.toml index 211461995..aac5e9b76 100644 --- a/node/runtime/Cargo.toml +++ b/node/runtime/Cargo.toml @@ -60,13 +60,13 @@ inherents = { package = "substrate-inherents", git = 'https://github.com/darwini transaction-payment-rpc-runtime-api = { package = "srml-transaction-payment-rpc-runtime-api", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } +darwinia-support = { path = "../../srml/support", default-features = false } balances = { package = "darwinia-balances", path = '../../srml/balances', default-features = false } kton = { package = "darwinia-kton", path = '../../srml/kton', default-features = false } staking = { package = "darwinia-staking", path = "../../srml/staking", default-features = false } merkle-mountain-range = { path = "../../core/merkle-mountain-range", default-features = false } -eos-bridge = { package = "darwinia-eos-bridge", path = "../../srml/chainrelay/bridge/eos", default-features = false } ethereum-bridge = { package = "darwinia-ethereum-bridge", path = "../../srml/chainrelay/bridge/ethereum", default-features = false } [build-dependencies] @@ -124,14 +124,10 @@ std = [ "inherents/std", # custom module + "darwinia-support/std", "kton/std", "staking/std", - # core struct - "merkle-mountain-range/std", - # chainrelay - # bridge - "eos-bridge/std", "ethereum-bridge/std", ] diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 3551d7375..1dd791d4d 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -60,6 +60,7 @@ use substrate_primitives::OpaqueMetadata; use system::offchain::TransactionSubmitter; use transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; +use darwinia_support::types::TimeStamp; use staking::EraIndex; pub use staking::StakerStatus; @@ -387,8 +388,8 @@ parameter_types! { pub const Period: BlockNumber = 1 * MINUTES; // pub const Offset: BlockNumber = 0; pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 5; - // about 14 days - pub const BondingDuration: staking::EraIndex = 4032; + // about 14 days = 14 * 24 * 60 * 60 + pub const BondingDuration: TimeStamp = 1209600; // 365 days * 24 hours * 60 minutes / 5 minutes pub const ErasPerEpoch: EraIndex = 105120; // decimal 9 @@ -411,10 +412,6 @@ impl staking::Trait for Runtime { type SessionInterface = Self; } -impl eos_bridge::Trait for Runtime { - type Event = Event; -} - impl ethereum_bridge::Trait for Runtime { type Event = Event; type Ring = Balances; @@ -446,7 +443,6 @@ construct_runtime!( Balances: balances::{default, Error}, Kton: kton, Staking: staking::{default, OfflineWorker}, - EOSBridge: eos_bridge::{Storage, Module, Event, Call}, EthereumBridge: ethereum_bridge::{Storage, Module, Event, Call}, } ); diff --git a/srml/balances/Cargo.toml b/srml/balances/Cargo.toml index db5f5a94c..22d72ac33 100644 --- a/srml/balances/Cargo.toml +++ b/srml/balances/Cargo.toml @@ -15,6 +15,8 @@ support = { package = "srml-support", git = "https://github.com/darwinia-network system = { package = "srml-system", git = "https://github.com/darwinia-network/substrate.git", default-features = false } timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } +darwinia-support = { path = "../support", default-features = false } + [dev-dependencies] runtime-io = { package = "sr-io", git = "https://github.com/darwinia-network/substrate.git" } primitives = { package = "substrate-primitives", git = "https://github.com/darwinia-network/substrate.git" } @@ -32,4 +34,6 @@ std = [ "sr-primitives/std", "system/std", "timestamp/std", + + "darwinia-support/std", ] diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 3b1fa13ed..3dce1a197 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -14,137 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! # 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. -//! - **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 balance is set -//! to zero. -//! - **Free Balance:** The portion of a balance that is not reserved. The free balance is the only balance that matters -//! for most operations. When this balance falls below the existential deposit, most functionality of the account is -//! removed. When both it and the reserved balance are deleted, then the account is said to be dead. -//! - **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. If the reserved balance falls below the -//! existential deposit then it and any related functionality will be deleted. When both it and the free balance are -//! deleted, then the account is said to be dead. -//! - **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". -//! - **Vesting:** Similar to a lock, this is another, but independent, liquidity restriction that reduces linearly -//! over time. -//! -//! ### 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`](../srml_support/traits/trait.Currency.html): Functions for dealing with a -//! fungible assets system. -//! - [`ReservableCurrency`](../srml_support/traits/trait.ReservableCurrency.html): -//! Functions for dealing with assets that can be reserved from an account. -//! - [`LockableCurrency`](../srml_support/traits/trait.LockableCurrency.html): Functions for -//! dealing with accounts that allow liquidity restrictions. -//! - [`Imbalance`](../srml_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`](../srml_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. -//! -//! ### Public Functions -//! -//! - `vesting_balance` - Get the amount that is currently being vested and cannot be transferred out of this account. -//! -//! ## Usage -//! -//! The following examples show how to use the Balances module in your custom module. -//! -//! ### Examples from the SRML -//! -//! The Contract module uses the `Currency` trait to handle gas payment, and its types inherit from `Currency`: -//! -//! ``` -//! use support::traits::Currency; -//! # pub trait Trait: 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 support::traits::{WithdrawReasons, LockableCurrency}; -//! use sr_primitives::traits::Bounded; -//! pub trait Trait: system::Trait { -//! type Currency: LockableCurrency; -//! } -//! # struct StakingLedger { -//! # stash: ::AccountId, -//! # total: <::Currency as 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, -//! T::BlockNumber::max_value(), -//! 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)] use codec::{Codec, Decode, Encode}; @@ -162,8 +31,8 @@ use support::{ decl_event, decl_module, decl_storage, dispatch::Result, traits::{ - Currency, ExistenceRequirement, Get, Imbalance, LockIdentifier, LockableCurrency, OnFreeBalanceZero, - OnUnbalanced, ReservableCurrency, SignedImbalance, UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, + Currency, ExistenceRequirement, Get, Imbalance, LockIdentifier, OnFreeBalanceZero, OnUnbalanced, + ReservableCurrency, SignedImbalance, UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, }, Parameter, StorageValue, }; @@ -173,6 +42,7 @@ mod mock; mod tests; pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; +use darwinia_support::{traits::LockableCurrency, types::Id}; pub trait Subtrait: system::Trait + timestamp::Trait { /// The balance of an account. @@ -862,25 +732,27 @@ where reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { - if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) - && Self::vesting_balance(who) > new_balance - { - return Err("vesting balance too high to send value"); - } - let locks = Self::locks(who); - if locks.is_empty() { - return Ok(()); - } - - let now = >::now(); - if locks - .into_iter() - .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) - { - Ok(()) - } else { - Err("account liquidity restrictions prevent withdrawal") - } + // if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) + // && Self::vesting_balance(who) > new_balance + // { + // return Err("vesting balance too high to send value"); + // } + // let locks = Self::locks(who); + // if locks.is_empty() { + // return Ok(()); + // } + // + // let now = >::now(); + // if locks + // .into_iter() + // .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) + // { + // Ok(()) + // } else { + // Err("account liquidity restrictions prevent withdrawal") + // } + // TODO + unimplemented!() } fn transfer( @@ -1117,84 +989,45 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Moment = T::Moment; + type Id = Id; // `amount` > `free_balance` is allowed - fn set_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - until: Self::Moment, - reasons: WithdrawReasons, - ) { - let now = >::now(); - let mut new_lock = Some(BalanceLock { - id, - amount, - until, - reasons, - }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| { - if l.id == id { - new_lock.take() - } else if l.until > now { - Some(l) - } else { - None - } - }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock); - } - >::insert(who, locks); + fn set_lock(who: &T::AccountId, amount: Self::Balance, id: Self::Id) { + // let now = >::now(); + // let mut new_lock = Some(BalanceLock { + // id, + // amount, + // until, + // reasons, + // }); + // let mut locks = Self::locks(who) + // .into_iter() + // .filter_map(|l| { + // if l.id == id { + // new_lock.take() + // } else if l.until > now { + // Some(l) + // } else { + // None + // } + // }) + // .collect::>(); + // if let Some(lock) = new_lock { + // locks.push(lock); + // } + // >::insert(who, locks); } - fn extend_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - until: Self::Moment, - reasons: WithdrawReasons, - ) { - let now = >::now(); - let mut new_lock = Some(BalanceLock { - id, - amount, - until, - reasons, - }); - 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), - until: l.until.max(nl.until), - reasons: l.reasons | nl.reasons, - }) - } else if l.until > now { - Some(l) - } else { - None - } - }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock); - } - >::insert(who, locks); + fn remove_lock(id: Self::Id, who: &T::AccountId) { + // let now = >::now(); + // >::mutate(who, |locks| { + // // unexpired and mismatched id -> keep + // locks.retain(|lock| (lock.until > now) && (lock.id != id)); + // }); } - fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let now = >::now(); - >::mutate(who, |locks| { - // unexpired and mismatched id -> keep - locks.retain(|lock| (lock.until > now) && (lock.id != id)); - }); + fn count() -> u32 { + unimplemented!() } } diff --git a/srml/chainrelay/bridge/eos/Cargo.toml b/srml/chainrelay/bridge/eos/Cargo.toml deleted file mode 100644 index b898403d3..000000000 --- a/srml/chainrelay/bridge/eos/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "darwinia-eos-bridge" -version = "0.1.0" -authors = ["Xavier Lau "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"] } -rstd = { package = "sr-std", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } -support = { package = "srml-support", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } -system = { package = "srml-system", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } - -merkle-mountain-range = { path = "../../../../core/merkle-mountain-range", default-features = false } - -[features] -default = ["std"] -std = [ - "codec/std", - "rstd/std", - "support/std", - "system/std", - - "merkle-mountain-range/std", -] diff --git a/srml/chainrelay/bridge/eos/src/lib.rs b/srml/chainrelay/bridge/eos/src/lib.rs deleted file mode 100644 index 3add0e5d4..000000000 --- a/srml/chainrelay/bridge/eos/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! prototype module for bridging in ethereum poa blockcahin - -#![recursion_limit = "128"] -#![cfg_attr(not(feature = "std"), no_std)] - -use support::{decl_event, decl_module, decl_storage}; - -pub trait Trait: system::Trait { - type Event: From> + Into<::Event>; -} - -decl_storage! { - trait Store for Module as Bridge { - - } -} - -decl_module! { - pub struct Module for enum Call - where - origin: T::Origin - { - - } -} - -decl_event! { - pub enum Event - where - ::AccountId - { - TODO(AccountId), - } -} - -impl Module {} diff --git a/srml/chainrelay/bridge/ethereum/src/lib.rs b/srml/chainrelay/bridge/ethereum/src/lib.rs index 58f2c618f..f9ad86b8b 100644 --- a/srml/chainrelay/bridge/ethereum/src/lib.rs +++ b/srml/chainrelay/bridge/ethereum/src/lib.rs @@ -6,19 +6,18 @@ // use blake2::Blake2b; //use codec::{Decode, Encode}; use rstd::vec::Vec; -use support::{ - decl_event, decl_module, decl_storage, - dispatch::Result, - traits::{Currency, LockableCurrency}, -}; +use support::{decl_event, decl_module, decl_storage, dispatch::Result, traits::Currency}; use system::ensure_signed; -use darwinia_support::types::TimeStamp; -//use merkle_mountain_range::{MerkleMountainRange, Hash}; +use darwinia_support::{ + traits::LockableCurrency, + types::{Id, TimeStamp}, +}; +use merkle_mountain_range::{Hash, MerkleMountainRange}; pub trait Trait: system::Trait { type Event: From> + Into<::Event>; - type Ring: LockableCurrency; + type Ring: LockableCurrency>; } // config() require `serde = { version = "1.0.101", optional = true }` diff --git a/srml/kton/Cargo.toml b/srml/kton/Cargo.toml index 38d726cdf..a63398c03 100644 --- a/srml/kton/Cargo.toml +++ b/srml/kton/Cargo.toml @@ -16,6 +16,8 @@ system = { package = "srml-system", git = 'https://github.com/darwinia-network/s timestamp = { package = "srml-timestamp", git = 'https://github.com/darwinia-network/substrate.git', default-features = false } substrate-primitives = { git = 'https://github.com/darwinia-network/substrate.git', default-features = false } +darwinia-support = { path = "../support", default-features = false } + [dev-dependencies] runtime_io = { package = "sr-io", git = 'https://github.com/darwinia-network/substrate.git' } substrate-primitives = { git = 'https://github.com/darwinia-network/substrate.git' } @@ -33,4 +35,6 @@ std = [ "system/std", "timestamp/std", "substrate-primitives/std", + + "darwinia-support/std", ] diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index ccd466aa8..0d2d244de 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -13,13 +13,14 @@ use srml_support::{ decl_event, decl_module, decl_storage, dispatch::Result, traits::{ - Currency, ExistenceRequirement, Imbalance, LockIdentifier, LockableCurrency, OnUnbalanced, SignedImbalance, - UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, + Currency, ExistenceRequirement, Imbalance, LockIdentifier, OnUnbalanced, SignedImbalance, UpdateBalanceOutcome, + WithdrawReason, WithdrawReasons, }, Parameter, StorageMap, StorageValue, }; use system::ensure_signed; +use darwinia_support::{traits::LockableCurrency, types::Id}; use imbalance::{NegativeImbalance, PositiveImbalance}; #[cfg(test)] @@ -55,9 +56,8 @@ impl VestingSchedule { #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] pub struct BalanceLock { - pub id: LockIdentifier, + pub id: Id, pub amount: Balance, - pub until: Moment, pub reasons: WithdrawReasons, } @@ -226,25 +226,27 @@ impl Currency for Module { reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { - if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) - && Self::vesting_balance(who) > new_balance - { - return Err("vesting balance too high to send value"); - } - let locks = Self::locks(who); - if locks.is_empty() { - return Ok(()); - } - - let now = >::now(); - if locks - .into_iter() - .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) - { - Ok(()) - } else { - Err("account liquidity restrictions prevent withdrawal") - } + // if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) + // && Self::vesting_balance(who) > new_balance + // { + // return Err("vesting balance too high to send value"); + // } + // let locks = Self::locks(who); + // if locks.is_empty() { + // return Ok(()); + // } + // + // let now = >::now(); + // if locks + // .into_iter() + // .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) + // { + // Ok(()) + // } else { + // Err("account liquidity restrictions prevent withdrawal") + // } + // TODO + unimplemented!() } // TODO: add fee @@ -382,83 +384,44 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Moment = T::Moment; + type Id = Id; // `amount` > `free_balance` is allowed - fn set_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - until: Self::Moment, - reasons: WithdrawReasons, - ) { - let now = >::now(); - let mut new_lock = Some(BalanceLock { - id, - amount, - until, - reasons, - }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| { - if l.id == id { - new_lock.take() - } else if l.until > now { - Some(l) - } else { - None - } - }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock); - } - >::insert(who, locks); + fn set_lock(who: &T::AccountId, amount: Self::Balance, id: Self::Id) { + // let now = >::now(); + // let mut new_lock = Some(BalanceLock { + // id, + // amount, + // until, + // reasons, + // }); + // let mut locks = Self::locks(who) + // .into_iter() + // .filter_map(|l| { + // if l.id == id { + // new_lock.take() + // } else if l.until > now { + // Some(l) + // } else { + // None + // } + // }) + // .collect::>(); + // if let Some(lock) = new_lock { + // locks.push(lock); + // } + // >::insert(who, locks); } - fn extend_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - until: Self::Moment, - reasons: WithdrawReasons, - ) { - let now = >::now(); - let mut new_lock = Some(BalanceLock { - id, - amount, - until, - reasons, - }); - 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), - until: l.until.max(nl.until), - reasons: l.reasons | nl.reasons, - }) - } else if l.until > now { - Some(l) - } else { - None - } - }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock); - } - >::insert(who, locks); + fn remove_lock(id: Self::Id, who: &T::AccountId) { + // let now = >::now(); + // >::mutate(who, |locks| { + // // unexpired and mismatched id -> keep + // locks.retain(|lock| (lock.until > now) && (lock.id != id)); + // }); } - fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let now = >::now(); - >::mutate(who, |locks| { - // unexpired and mismatched id -> keep - locks.retain(|lock| (lock.until > now) && (lock.id != id)); - }); + fn count() -> u32 { + unimplemented!() } } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 6dc37d730..0511290c7 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -28,18 +28,18 @@ use session::{historical::OnSessionEnding, SelectInitialValidators}; use sr_primitives::traits::{CheckedSub, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}; #[cfg(feature = "std")] use sr_primitives::{Deserialize, Serialize}; -use sr_primitives::{Perbill, RuntimeDebug}; +use sr_primitives::{Perbill, Perquintill, RuntimeDebug}; use sr_staking_primitives::SessionIndex; use srml_support::{ decl_event, decl_module, decl_storage, ensure, - traits::{ - Currency, Get, Imbalance, LockIdentifier, LockableCurrency, OnFreeBalanceZero, OnUnbalanced, WithdrawReason, - WithdrawReasons, - }, + traits::{Currency, Get, Imbalance, OnFreeBalanceZero, OnUnbalanced, WithdrawReason}, }; use system::{ensure_root, ensure_signed}; -use darwinia_support::types::TimeStamp; +use darwinia_support::{ + traits::LockableCurrency, + types::{Id, TimeStamp}, +}; use phragmen::{elect, equalize, ExtendedBalance, PhragmenStakedAssignment, Support, SupportMap}; mod utils; @@ -60,15 +60,12 @@ const RECENT_OFFLINE_COUNT: usize = 32; const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; const MAX_NOMINATIONS: usize = 16; const MAX_UNSTAKE_THRESHOLD: u32 = 10; -const MAX_UNLOCKING_CHUNKS: usize = 32; +const MAX_UNLOCKING_CHUNKS: u32 = 32; const MONTH_IN_SECONDS: u32 = 2_592_000; -const STAKING_ID: LockIdentifier = *b"staking "; /// Counter for the number of eras that have passed. pub type EraIndex = u32; -const ACCURACY: u128 = u32::max_value() as ExtendedBalance + 1; - #[derive(RuntimeDebug)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub enum StakerStatus { @@ -100,12 +97,12 @@ impl Default for ValidatorPrefs { } #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub enum StakingBalance { - Ring(RingBalance), - Kton(KtonBalance), +pub enum StakingBalance { + Ring(Ring), + Kton(Kton), } -impl Default for StakingBalance { +impl Default for StakingBalance { fn default() -> Self { StakingBalance::Ring(Default::default()) } @@ -130,19 +127,9 @@ impl Default for RewardDestination { } #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub struct UnlockChunk { - /// Amount of funds to be unlocked. - value: StakingBalance, - /// Era number at which point it'll be unlocked. +pub struct TimeDepositItem { #[codec(compact)] - era: EraIndex, - is_time_deposit: bool, -} - -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub struct TimeDepositItem { - #[codec(compact)] - value: RingBalance, + value: Ring, #[codec(compact)] start_time: Moment, #[codec(compact)] @@ -150,29 +137,28 @@ pub struct TimeDepositItem { } #[derive(PartialEq, Eq, Default, Clone, Encode, Decode, RuntimeDebug)] -pub struct StakingLedgers { +pub struct StakingLedgers { pub stash: AccountId, // normal pattern: for ring /// total_ring = normal_ring + time_deposit_ring #[codec(compact)] - pub total_ring: RingBalance, + pub total_ring: Ring, #[codec(compact)] - pub total_deposit_ring: RingBalance, + pub total_deposit_ring: Ring, #[codec(compact)] - pub active_ring: RingBalance, + pub active_ring: Ring, // active time-deposit ring #[codec(compact)] - pub active_deposit_ring: RingBalance, + pub active_deposit_ring: Ring, #[codec(compact)] - pub total_kton: KtonBalance, + pub total_kton: Kton, #[codec(compact)] - pub active_kton: KtonBalance, + pub active_kton: Kton, // time-deposit items: // if you deposit ring for a minimum period, // you can get KTON as bonus // which can also be used for staking - pub deposit_items: Vec>, - pub unlocking: Vec>, + pub deposit_items: Vec>, } /// The amount of exposure (to slashing) than an individual nominator has. @@ -215,8 +201,8 @@ type Assignment = (::AccountId, ExtendedBalance, Extended type ExpoMap = BTreeMap<::AccountId, Exposure<::AccountId, ExtendedBalance>>; pub trait Trait: timestamp::Trait + session::Trait { - type Ring: LockableCurrency; - type Kton: LockableCurrency; + type Ring: LockableCurrency>; + type Kton: LockableCurrency>; type CurrencyToVote: Convert, u64> + Convert>; @@ -236,7 +222,7 @@ pub trait Trait: timestamp::Trait + session::Trait { type SessionsPerEra: Get; /// Number of eras that staked funds must remain bonded for. - type BondingDuration: Get; + type BondingDuration: Get; // custom type Cap: Get<>::Balance>; @@ -249,7 +235,6 @@ pub trait Trait: timestamp::Trait + session::Trait { decl_storage! { trait Store for Module as Staking { - pub ValidatorCount get(validator_count) config(): u32; pub MinimumValidatorCount get(minimum_validator_count) config(): @@ -265,9 +250,7 @@ decl_storage! { pub Bonded get(bonded): map T::AccountId => Option; - pub Ledger get(ledger): map T::AccountId => Option, KtonBalanceOf, StakingBalance, KtonBalanceOf>, - T::Moment>>; + pub Ledger get(ledger): map T::AccountId => Option, KtonBalanceOf, T::Moment>>; pub Payee get(payee): map T::AccountId => RewardDestination; @@ -359,8 +342,8 @@ decl_module! { /// Number of sessions per era. const SessionsPerEra: SessionIndex = T::SessionsPerEra::get(); - /// Number of eras that staked funds must remain bonded for. - const BondingDuration: EraIndex = T::BondingDuration::get(); + /// Timestamp diff that staked funds must remain bonded for. + const BondingDuration: TimeStamp = T::BondingDuration::get(); const SessionLength: T::BlockNumber = T::SessionLength::get(); @@ -442,20 +425,21 @@ decl_module! { let controller = ensure_signed(origin)?; let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; let StakingLedgers { + stash, active_ring, active_deposit_ring, active_kton, deposit_items, - unlocking, .. } = &mut ledger; - ensure!( - unlocking.len() < MAX_UNLOCKING_CHUNKS, - "can not schedule more unlock chunks" - ); +// TODO +// ensure!( +// *unlocking_count < MAX_UNLOCKING_CHUNKS, +// "can not schedule more unlock chunks" +// ); - let era = Self::current_era() + T::BondingDuration::get(); + let until = >::now().saturated_into::() + T::BondingDuration::get(); match value { StakingBalance::Ring(r) => { @@ -470,11 +454,12 @@ decl_module! { if !active_normal_value.is_zero() { *active_ring -= active_normal_value; - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(total_value), - era, - is_time_deposit: false - }); +// TODO +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(total_value), +// era, +// is_time_deposit: false +// }); } // no active_normal_ring @@ -512,24 +497,25 @@ decl_module! { }); // update unlocking list - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(total_deposit_changed), - era, - is_time_deposit: true, - }); +// TODO +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(total_deposit_changed), +// era, +// is_time_deposit: true, +// }); >::mutate(|r| *r -= total_deposit_changed); } }, StakingBalance::Kton(k) => { let value = k.min(*active_kton); >::mutate(|k| *k -= value); - *active_kton -= value; - unlocking.push(UnlockChunk { - value: StakingBalance::Kton(value), - era, - is_time_deposit: false, - }); +// TODO +// unlocking.push(UnlockChunk { +// value: StakingBalance::Kton(value), +// era, +// is_time_deposit: false, +// }); }, } @@ -545,7 +531,6 @@ decl_module! { active_ring, active_deposit_ring, deposit_items, - unlocking, .. } = &mut ledger; let now = >::now(); @@ -584,11 +569,12 @@ decl_module! { T::KtonSlash::on_unbalanced(imbalance); // update unlocks - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(value), - era: Self::current_era() + T::BondingDuration::get(), - is_time_deposit: true - }); +// TODO +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(value), +// era: Self::current_era() + T::BondingDuration::get(), +// is_time_deposit: true +// }); >::mutate(|r| *r -= value); if item.value.is_zero() { @@ -655,58 +641,58 @@ decl_module! { } /// may both withdraw ring and kton at the same time - fn withdraw_unbonded(origin) { - let controller = ensure_signed(origin)?; - let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; - let StakingLedgers { - total_ring, - total_deposit_ring, - total_kton, - unlocking, - .. - } = &mut ledger; - let mut balance_kind = 0u8; - let current_era = Self::current_era(); - - unlocking.retain(|UnlockChunk { - value, - era, - is_time_deposit, - }| { - if *era > current_era { - return true; - } - - match value { - StakingBalance::Ring(ring) => { - balance_kind |= 0b01; - *total_ring = total_ring.saturating_sub(*ring); - - // MUST be false if the item is not in deposit - if *is_time_deposit { - *total_deposit_ring = total_deposit_ring.saturating_sub(*ring); - } - } - StakingBalance::Kton(kton) => { - balance_kind |= 0b10; - *total_kton = total_kton.saturating_sub(*kton); - } - } - - false - }); - - match balance_kind { - 0 => (), - 1 => Self::update_ledger(&controller, &ledger, StakingBalance::Ring(0.into())), - 2 => Self::update_ledger(&controller, &ledger, StakingBalance::Kton(0.into())), - 3 => { - Self::update_ledger(&controller, &ledger, StakingBalance::Ring(0.into())); - Self::update_ledger(&controller, &ledger, StakingBalance::Kton(0.into())); - } - _ => unreachable!(), - } - } +// fn withdraw_unbonded(origin) { +// let controller = ensure_signed(origin)?; +// let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; +// let StakingLedgers { +// total_ring, +// total_deposit_ring, +// total_kton, +// unlocking, +// .. +// } = &mut ledger; +// let mut balance_kind = 0u8; +// let current_era = Self::current_era(); +// +// unlocking.retain(|UnlockChunk { +// value, +// era, +// is_time_deposit, +// }| { +// if *era > current_era { +// return true; +// } +// +// match value { +// StakingBalance::Ring(ring) => { +// balance_kind |= 0b01; +// *total_ring = total_ring.saturating_sub(*ring); +// +// // MUST be false if the item is not in deposit +// if *is_time_deposit { +// *total_deposit_ring = total_deposit_ring.saturating_sub(*ring); +// } +// } +// StakingBalance::Kton(kton) => { +// balance_kind |= 0b10; +// *total_kton = total_kton.saturating_sub(*kton); +// } +// } +// +// false +// }); +// +// match balance_kind { +// 0 => (), +// 1 => Self::update_ledger(&controller, &ledger, StakingBalance::Ring(0.into())), +// 2 => Self::update_ledger(&controller, &ledger, StakingBalance::Kton(0.into())), +// 3 => { +// Self::update_ledger(&controller, &ledger, StakingBalance::Ring(0.into())); +// Self::update_ledger(&controller, &ledger, StakingBalance::Kton(0.into())); +// } +// _ => unreachable!(), +// } +// } fn validate(origin, name: Vec, ratio: u32, unstake_threshold: u32) { let controller = ensure_signed(origin)?; @@ -811,13 +797,7 @@ impl Module { controller: &T::AccountId, value: RingBalanceOf, promise_month: u32, - mut ledger: StakingLedgers< - T::AccountId, - RingBalanceOf, - KtonBalanceOf, - StakingBalance, KtonBalanceOf>, - T::Moment, - >, + mut ledger: StakingLedgers, KtonBalanceOf, T::Moment>, ) { // if stash promise to a extra-lock // there will be extra reward, kton, which @@ -847,13 +827,7 @@ impl Module { fn bond_helper_in_kton( controller: &T::AccountId, value: KtonBalanceOf, - mut ledger: StakingLedgers< - T::AccountId, - RingBalanceOf, - KtonBalanceOf, - StakingBalance, KtonBalanceOf>, - T::Moment, - >, + mut ledger: StakingLedgers, KtonBalanceOf, T::Moment>, ) { ledger.total_kton += value; ledger.active_kton += value; @@ -863,32 +837,25 @@ impl Module { fn update_ledger( controller: &T::AccountId, - ledger: &StakingLedgers< - T::AccountId, - RingBalanceOf, - KtonBalanceOf, - StakingBalance, KtonBalanceOf>, - T::Moment, - >, + ledger: &StakingLedgers, KtonBalanceOf, T::Moment>, staking_balance: StakingBalance, KtonBalanceOf>, ) { - match staking_balance { - StakingBalance::Ring(_r) => T::Ring::set_lock( - STAKING_ID, - &ledger.stash, - ledger.total_ring, - TimeStamp::max_value(), - WithdrawReasons::all(), - ), - - StakingBalance::Kton(_k) => T::Kton::set_lock( - STAKING_ID, - &ledger.stash, - ledger.total_kton, - TimeStamp::max_value(), - WithdrawReasons::all(), - ), - } + // match staking_balance { + // StakingBalance::Ring(_r) => T::Ring::set_lock( + // STAKING_ID, + // &ledger.stash, + // ledger.total_ring, + // TimeStamp::max_value(), + // WithdrawReasons::all(), + // ), + // StakingBalance::Kton(_k) => T::Kton::set_lock( + // STAKING_ID, + // &ledger.stash, + // ledger.total_kton, + // TimeStamp::max_value(), + // WithdrawReasons::all(), + // ), + // } >::insert(controller, ledger); } @@ -940,13 +907,7 @@ impl Module { fn slash_helper( controller: &T::AccountId, - ledger: &mut StakingLedgers< - T::AccountId, - RingBalanceOf, - KtonBalanceOf, - StakingBalance, KtonBalanceOf>, - T::Moment, - >, + ledger: &mut StakingLedgers, KtonBalanceOf, T::Moment>, value: StakingBalance, KtonBalanceOf>, ) -> (RingBalanceOf, KtonBalanceOf) { match value { @@ -1117,15 +1078,25 @@ impl Module { } } - // TODO: ready for hacking - // power is a mixture of ring and kton + // TODO: Comment and Refactor fn slashable_balance_of(stash: &T::AccountId) -> ExtendedBalance { + // power is a mixture of ring and kton + // power = ring_ratio * POWER_COUNT / 2 + kton_ratio * POWER_COUNT / 2 + fn calc_power(active: S, pool: S) -> ExtendedBalance + where + S: SaturatedConversion + rstd::convert::TryInto, + { + const HALF_POWER: u128 = 1_000_000_000 / 2; + + Perquintill::from_rational_approximation( + active.saturated_into::(), + pool.saturated_into::().max(1), + ) * HALF_POWER + } + Self::bonded(stash) .and_then(Self::ledger) - .map(|l| { - l.active_ring.saturated_into::() - + l.active_kton.saturated_into::() * Self::kton_vote_weight() / ACCURACY - }) + .map(|l| calc_power(l.active_ring, Self::ring_pool()) + calc_power(l.active_kton, Self::kton_pool())) .unwrap_or_default() } @@ -1319,21 +1290,6 @@ impl Module { Self::deposit_event(event); } } - - // total_kton * kton_vote_weight / ACCURACY = total_ring - // it ensures that when rewarding validators - // reward to ring_pool will be the same with the - // reward to kton_pool - // that means 50% reward is distributed to ring holders, - // another 50% reward is distributed to kton holders - fn kton_vote_weight() -> ExtendedBalance { - let total_ring = Self::ring_pool().saturated_into::(); - // to avoid 'attempt to divide by zero' - let total_kton = Self::kton_pool().saturated_into::().max(1); - // total_ring and total_kton are within the scope of u64 - // so it is safe to multiply ACCURACY when extended to u128 - total_ring * ACCURACY / total_kton - } } impl session::OnSessionEnding for Module { diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 6801a22a5..8f635bd2b 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -3,7 +3,7 @@ use std::{cell::RefCell, collections::HashSet}; use sr_primitives::{ testing::{Header, UintAuthorityId}, traits::{BlakeTwo256, Convert, IdentityLookup, OnInitialize, OpaqueKeys}, - Perbill, + KeyTypeId, Perbill, }; use sr_staking_primitives::SessionIndex; use srml_support::{ @@ -11,12 +11,13 @@ use srml_support::{ traits::{Currency, Get}, StorageLinkedMap, }; -use substrate_primitives::H256; +use substrate_primitives::{crypto::key_types, H256}; use crate::{ phragmen::ExtendedBalance, EraIndex, GenesisConfig, Module, Nominators, RewardDestination, StakerStatus, StakingBalance, Trait, }; +use darwinia_support::types::TimeStamp; /// The AccountId alias in this test module. pub type AccountId = u64; @@ -43,6 +44,8 @@ thread_local! { pub struct TestSessionHandler; impl session::SessionHandler for TestSessionHandler { + const KEY_TYPE_IDS: &'static [KeyTypeId] = &[key_types::DUMMY]; + fn on_genesis_session(_validators: &[(AccountId, Ks)]) {} fn on_new_session( @@ -160,7 +163,7 @@ impl kton::Trait for Test { parameter_types! { pub const SessionsPerEra: SessionIndex = 3; - pub const BondingDuration: EraIndex = 3; + pub const BondingDuration: TimeStamp = 60; pub const ErasPerEpoch: EraIndex = 10; } pub const COIN: u64 = 1_000_000_000; diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index cbad1534d..f6c4a9d5d 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1,8 +1,11 @@ +use srml_support::{ + assert_err, assert_ok, + traits::{Currency, WithdrawReason, WithdrawReasons}, +}; + use super::MONTH_IN_SECONDS; use super::*; use crate::mock::*; -use srml_support::traits::{Currency, WithdrawReason, WithdrawReasons}; -use srml_support::{assert_err, assert_ok}; // gen_paired_account!(a(1), b(2), m(12)); // will create stash `a` and controller `b` @@ -87,7 +90,7 @@ fn test_env_build() { start_time: 0, expire_time: 12 * MONTH_IN_SECONDS as u64 }], - unlocking: vec![] + unlocking_count: 0 }) ); @@ -123,1088 +126,1113 @@ fn test_env_build() { expire_time: 13 * MONTH_IN_SECONDS as u64 } ], - unlocking: vec![] + unlocking_count: 0 }) ); }); } -#[test] -fn normal_kton_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Kton::deposit_creating(&1001, 10 * COIN); - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Kton(10 * COIN), - RewardDestination::Stash, - 0 - )); - assert_eq!( - Staking::ledger(&1000), - Some(StakingLedgers { - stash: 1001, - total_ring: 0, - total_deposit_ring: 0, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 10 * COIN, - active_kton: 10 * COIN, - deposit_items: vec![], - unlocking: vec![] - }) - ); - - assert_eq!( - Kton::locks(&1001), - vec![kton::BalanceLock { - id: STAKING_ID, - amount: 10 * COIN, - until: u64::max_value(), - reasons: WithdrawReasons::all() - }] - ); - - // promise_month should not work for kton - Kton::deposit_creating(&2001, 10 * COIN); - assert_ok!(Staking::bond( - Origin::signed(2001), - 2000, - StakingBalance::Kton(10 * COIN), - RewardDestination::Stash, - 12 - )); - assert_eq!( - Staking::ledger(&2000), - Some(StakingLedgers { - stash: 2001, - total_ring: 0, - total_deposit_ring: 0, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 10 * COIN, - active_kton: 10 * COIN, - deposit_items: vec![], - unlocking: vec![] - }) - ); - }); -} +//#[test] +//fn normal_kton_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Kton::deposit_creating(&1001, 10 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Kton(10 * COIN), +// RewardDestination::Stash, +// 0 +// )); +// assert_eq!( +// Staking::ledger(&1000), +// Some(StakingLedgers { +// stash: 1001, +// total_ring: 0, +// total_deposit_ring: 0, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 10 * COIN, +// active_kton: 10 * COIN, +// deposit_items: vec![], +// unlocking: vec![] +// }) +// ); +// +// assert_eq!( +// Kton::locks(&1001), +// vec![kton::BalanceLock { +// id: STAKING_ID, +// amount: 10 * COIN, +// until: u64::max_value(), +// reasons: WithdrawReasons::all() +// }] +// ); +// +// // promise_month should not work for kton +// Kton::deposit_creating(&2001, 10 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(2001), +// 2000, +// StakingBalance::Kton(10 * COIN), +// RewardDestination::Stash, +// 12 +// )); +// assert_eq!( +// Staking::ledger(&2000), +// Some(StakingLedgers { +// stash: 2001, +// total_ring: 0, +// total_deposit_ring: 0, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 10 * COIN, +// active_kton: 10 * COIN, +// deposit_items: vec![], +// unlocking: vec![] +// }) +// ); +// }); +//} +// +//#[test] +//fn time_deposit_ring_unbond_and_withdraw_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Timestamp::set_timestamp(13 * MONTH_IN_SECONDS as u64); +// +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 100 * COIN, +// total_deposit_ring: 100 * COIN, +// active_deposit_ring: 90 * COIN, +// active_ring: 90 * COIN, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![TimeDepositItem { +// value: 90 * COIN, +// start_time: 0, +// expire_time: 12 * MONTH_IN_SECONDS as u64 +// }], +// unlocking: vec![UnlockChunk { +// value: StakingBalance::Ring(10 * COIN), +// era: 3, +// is_time_deposit: true +// }] +// }) +// ); +// +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(20 * COIN))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 100 * COIN, +// total_deposit_ring: 100 * COIN, +// active_deposit_ring: 70 * COIN, +// active_ring: 70 * COIN, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![TimeDepositItem { +// value: 70 * COIN, +// start_time: 0, +// expire_time: 12 * MONTH_IN_SECONDS as u64 +// }], +// unlocking: vec![ +// UnlockChunk { +// value: StakingBalance::Ring(10 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// UnlockChunk { +// value: StakingBalance::Ring(20 * COIN), +// era: 3, +// is_time_deposit: true +// } +// ] +// }) +// ); +// +// // more than active ring +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(120 * COIN))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 100 * COIN, +// total_deposit_ring: 100 * COIN, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![], // should be cleared +// unlocking: vec![ +// UnlockChunk { +// value: StakingBalance::Ring(10 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// UnlockChunk { +// value: StakingBalance::Ring(20 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// UnlockChunk { +// value: StakingBalance::Ring(70 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// ] +// }) +// ); +// +// start_era(3); +// +// assert_ok!(Staking::withdraw_unbonded(Origin::signed(10))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 0, +// total_deposit_ring: 0, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![], // should be cleared +// unlocking: vec![] +// }) +// ); +// +// let free_balance = Ring::free_balance(&11); +// assert_eq!( +// Ring::locks(&11), +// vec![balances::BalanceLock { +// id: STAKING_ID, +// amount: 0, +// until: u64::max_value(), +// reasons: WithdrawReasons::all() +// }] +// ); +// assert_ok!(Ring::ensure_can_withdraw( +// &11, +// free_balance, +// WithdrawReason::Transfer, +// 0 +// )); +// }); +//} +// +//#[test] +//fn normal_unbond_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 11; +// let controller = 10; +// let value = 200 * COIN; +// let promise_month = 12; +// // unbond normal ring +// let _ = Ring::deposit_creating(&stash, 1000 * COIN); +// +// { +// let kton_free_balance = Kton::free_balance(&stash); +// let mut ledger = Staking::ledger(&controller).unwrap(); +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(value), +// promise_month, +// )); +// assert_eq!( +// Kton::free_balance(&stash), +// kton_free_balance + utils::compute_kton_return::(value, promise_month) +// ); +// ledger.total_ring += value; +// ledger.total_deposit_ring += value; +// ledger.active_ring += value; +// ledger.active_deposit_ring += value; +// ledger.deposit_items.push(TimeDepositItem { +// value: value, +// start_time: 0, +// expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// } +// +// { +// let kton_free_balance = Kton::free_balance(&stash); +// let mut ledger = Staking::ledger(&controller).unwrap(); +// +// // we try to bond 1 kton, but stash only has 0.03 Kton +// // extra = 1.min(0.03) +// // bond += 0.03 +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Kton(COIN), +// 0 +// )); +// ledger.total_kton += kton_free_balance; +// ledger.active_kton += kton_free_balance; +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// +// assert_ok!(Staking::unbond( +// Origin::signed(controller), +// StakingBalance::Kton(kton_free_balance) +// )); +// ledger.active_kton = 0; +// ledger.unlocking = vec![UnlockChunk { +// value: StakingBalance::Kton(kton_free_balance), +// era: 3, +// is_time_deposit: false, +// }]; +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// } +// }); +//} +// +//#[test] +//fn punished_unbond_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 1001; +// let controller = 1000; +// let promise_month = 36; +// +// let _ = Ring::deposit_creating(&stash, 100 * COIN); +// Kton::deposit_creating(&stash, COIN / 100000); +// +// // timestamp now is 0. +// // free balance of kton is too low to work +// assert_ok!(Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(10 * COIN), +// RewardDestination::Stash, +// promise_month +// )); +// assert_eq!( +// Staking::ledger(&controller), +// Some(StakingLedgers { +// stash, +// total_ring: 10 * COIN, +// total_deposit_ring: 10 * COIN, +// active_deposit_ring: 10 * COIN, +// active_ring: 10 * COIN, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![TimeDepositItem { +// value: 10 * COIN, +// start_time: 0, +// expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64 +// }], // should be cleared +// unlocking: vec![] +// }) +// ); +// let mut ledger = Staking::ledger(&controller).unwrap(); +// let kton_free_balance = Kton::free_balance(&stash); +// // kton is 0, skip unbond_with_punish +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 10 * COIN, +// promise_month as u64 * MONTH_IN_SECONDS as u64 +// )); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// assert_eq!(Kton::free_balance(&stash), kton_free_balance); +// +// // set more kton balance to make it work +// Kton::deposit_creating(&stash, 10 * COIN); +// let kton_free_balance = Kton::free_balance(&stash); +// let unbond_value = 5 * COIN; +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// unbond_value, +// promise_month as u64 * MONTH_IN_SECONDS as u64 +// )); +// ledger.active_ring -= unbond_value; +// ledger.active_deposit_ring -= unbond_value; +// ledger.deposit_items[0].value -= unbond_value; +// ledger.unlocking = vec![UnlockChunk { +// value: StakingBalance::Ring(unbond_value), +// era: 3, +// is_time_deposit: true, +// }]; +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// +// let kton_punishment = utils::compute_kton_return::(unbond_value, promise_month); +// assert_eq!(Kton::free_balance(&stash), kton_free_balance - 3 * kton_punishment); +// +// // if deposit_item.value == 0 +// // the whole item should be be dropped +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 5 * COIN, +// promise_month as u64 * MONTH_IN_SECONDS as u64 +// )); +// assert!(Staking::ledger(&controller).unwrap().deposit_items.is_empty()); +// }); +//} +// +//#[test] +//fn transform_to_promised_ring_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Ring::deposit_creating(&1001, 100 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(10 * COIN), +// RewardDestination::Stash, +// 0 +// )); +// let origin_ledger = Staking::ledger(&1000).unwrap(); +// let kton_free_balance = Kton::free_balance(&1001); +// +// assert_ok!(Staking::promise_extra(Origin::signed(1000), 5 * COIN, 12)); +// +// assert_eq!( +// Staking::ledger(&1000), +// Some(StakingLedgers { +// stash: 1001, +// total_ring: origin_ledger.total_ring, +// total_deposit_ring: origin_ledger.total_deposit_ring + 5 * COIN, +// active_deposit_ring: origin_ledger.active_deposit_ring + 5 * COIN, +// active_ring: origin_ledger.active_ring, +// total_kton: origin_ledger.total_kton, +// active_kton: origin_ledger.active_kton, +// deposit_items: vec![TimeDepositItem { +// value: 5 * COIN, +// start_time: 0, +// expire_time: 12 * MONTH_IN_SECONDS as u64 +// }], +// unlocking: vec![] +// }) +// ); +// +// assert_eq!(Kton::free_balance(&1001), kton_free_balance + (5 * COIN / 10000)); +// }); +//} +// +//#[test] +//fn expired_ring_should_capable_to_promise_again() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Ring::deposit_creating(&1001, 100 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(10 * COIN), +// RewardDestination::Stash, +// 12 +// )); +// let mut ledger = Staking::ledger(&1000).unwrap(); +// let ts = 13 * MONTH_IN_SECONDS as u64; +// let promise_extra_value = 5 * COIN; +// Timestamp::set_timestamp(ts); +// assert_ok!(Staking::promise_extra(Origin::signed(1000), promise_extra_value, 13)); +// ledger.total_deposit_ring = promise_extra_value; +// ledger.active_deposit_ring = promise_extra_value; +// // old deposit_item with 12 months promised removed +// ledger.deposit_items = vec![TimeDepositItem { +// value: promise_extra_value, +// start_time: ts, +// expire_time: 2 * ts, +// }]; +// assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); +// }); +//} +// +//#[test] +//fn inflation_should_be_correct() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let initial_issuance = 1_200_000_000 * COIN; +// let surplus_needed = initial_issuance - Ring::total_issuance(); +// let _ = Ring::deposit_into_existing(&11, surplus_needed); +// assert_eq!(Ring::total_issuance(), initial_issuance); +// assert_eq!(Staking::current_era_total_reward(), 80000000 * COIN / 10); +// start_era(11); +// // ErasPerEpoch = 10 +// assert_eq!(Staking::current_era_total_reward(), 88000000 * COIN / 10); +// }); +//} +// +//#[test] +//fn reward_should_work_correctly() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// // create controller account +// let _ = Ring::deposit_creating(&2000, COIN); +// let _ = Ring::deposit_creating(&1000, COIN); +// let _ = Ring::deposit_creating(&200, COIN); +// // new validator +// let _ = Ring::deposit_creating(&2001, 2000 * COIN); +// Kton::deposit_creating(&2001, 10 * COIN); +// // new validator +// let _ = Ring::deposit_creating(&1001, 300 * COIN); +// Kton::deposit_creating(&1001, 1 * COIN); +// // handle some dirty work +// let _ = Ring::deposit_creating(&201, 2000 * COIN); +// Kton::deposit_creating(&201, 10 * COIN); +// assert_eq!(Kton::free_balance(&201), 10 * COIN); +// +// // 2001-2000 +// assert_ok!(Staking::bond( +// Origin::signed(2001), +// 2000, +// StakingBalance::Ring(300 * COIN), +// RewardDestination::Controller, +// 12, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(2001), +// StakingBalance::Kton(1 * COIN), +// 0 +// )); +// // 1001-1000 +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(300 * COIN), +// RewardDestination::Controller, +// 12, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(1001), +// StakingBalance::Kton(1 * COIN), +// 0 +// )); +// let ring_pool = Staking::ring_pool(); +// let kton_pool = Staking::kton_pool(); +// // 201-200 +// assert_ok!(Staking::bond( +// Origin::signed(201), +// 200, +// StakingBalance::Ring(3000 * COIN - ring_pool), +// RewardDestination::Stash, +// 12, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(201), +// StakingBalance::Kton(10 * COIN - kton_pool), +// 0, +// )); +// // ring_pool and kton_pool +// assert_eq!(Staking::ring_pool(), 3000 * COIN); +// assert_eq!(Staking::kton_pool(), 10 * COIN); +// // 1/5 ring_pool and 1/5 kton_pool +// assert_ok!(Staking::validate(Origin::signed(2000), [0; 8].to_vec(), 0, 3)); +// assert_ok!(Staking::nominate(Origin::signed(1000), vec![2001])); +// +// assert_eq!(Staking::ledger(&2000).unwrap().active_kton, 1 * COIN); +// assert_eq!(Staking::ledger(&2000).unwrap().active_ring, 300 * COIN); +// assert_eq!(Staking::slashable_balance_of(&2001), 600 * COIN as u128); +// // 600COIN for rewarding ring bond-er +// // 600COIN for rewarding kton bond-er +// Staking::select_validators(); +// Staking::reward_validator(&2001, 1200 * COIN); +// +// assert_eq!( +// Staking::stakers(2001), +// Exposure { +// total: 1200000000000, +// own: 600000000000, +// others: vec![IndividualExposure { +// who: 1001, +// value: 600000000000 +// }] +// } +// ); +// assert_eq!(Ring::free_balance(&2000), 601 * COIN); +// assert_eq!(Ring::free_balance(&1000), 601 * COIN); +// }); +//} +// +//#[test] +//fn slash_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Ring::deposit_creating(&1001, 100 * COIN); +// Kton::deposit_creating(&1001, 100 * COIN); +// +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(50 * COIN), +// RewardDestination::Controller, +// 0, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(1001), +// StakingBalance::Kton(50 * COIN), +// 0 +// )); +// assert_ok!(Staking::validate(Origin::signed(1000), [0; 8].to_vec(), 0, 3)); +// +// // slash 1% +// let slash_value = 5 * COIN / 10; +// let mut ledger = Staking::ledger(&1000).unwrap(); +// let ring_free_balance = Ring::free_balance(&1001); +// let kton_free_balance = Kton::free_balance(&1001); +// Staking::slash_validator(&1001, 10_000_000); +// ledger.total_ring -= slash_value; +// ledger.active_ring -= slash_value; +// ledger.total_kton -= slash_value; +// ledger.active_kton -= slash_value; +// assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); +// assert_eq!(Ring::free_balance(&1001), ring_free_balance - slash_value); +// assert_eq!(Kton::free_balance(&1001), kton_free_balance - slash_value); +// }); +//} +// +//#[test] +//fn test_inflation() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// assert_eq!(Staking::current_era_total_reward(), 80_000_000 * COIN / 10); +// start_era(20); +// assert_eq!(Staking::epoch_index(), 2); +// assert_eq!(Staking::current_era_total_reward(), 9_999_988_266 * COIN / 1000); +// }); +//} +// +//#[test] +//fn set_controller_should_remove_old_ledger() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 11; +// let old_controller = 10; +// let new_controller = 12; +// +// assert!(Staking::ledger(&old_controller).is_some()); +// assert_eq!(Staking::bonded(&stash), Some(old_controller)); +// +// assert_ok!(Staking::set_controller(Origin::signed(stash), new_controller)); +// assert!(Staking::ledger(&old_controller).is_none()); +// }); +//} +// +//#[test] +//fn set_controller_should_not_change_ledger() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// assert_eq!(Staking::ledger(&10).unwrap().total_ring, 100 * COIN); +// assert_ok!(Staking::set_controller(Origin::signed(11), 12)); +// assert_eq!(Staking::ledger(&12).unwrap().total_ring, 100 * COIN); +// }); +//} +// +//#[test] +//fn slash_should_not_touch_unlockings() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let old_ledger = Staking::ledger(&10).unwrap(); +// // only deposit_ring, no normal_ring +// assert_eq!( +// ( +// old_ledger.total_ring, +// old_ledger.active_ring, +// old_ledger.active_deposit_ring +// ), +// (100 * COIN, 100 * COIN, 100 * COIN) +// ); +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(11), +// StakingBalance::Ring(100 * COIN), +// 0 +// )); +// Kton::deposit_creating(&11, 10 * COIN); +// assert_ok!(Staking::bond_extra( +// Origin::signed(11), +// StakingBalance::Kton(10 * COIN), +// 0 +// )); +// +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); +// let new_ledger = Staking::ledger(&10).unwrap(); +// assert_eq!( +// ( +// new_ledger.total_ring, +// new_ledger.active_ring, +// new_ledger.active_deposit_ring +// ), +// (200 * COIN, 190 * COIN, 100 * COIN) +// ); +// +// // slash 100% +// Staking::slash_validator(&11, 1_000_000_000); +// +// let ledger = Staking::ledger(&10).unwrap(); +// assert_eq!( +// (ledger.total_ring, ledger.active_ring, ledger.active_deposit_ring), +// // 10Ring in unlocking +// (10 * COIN, 0, 0) +// ); +// assert_eq!(ledger.unlocking[0].value, StakingBalance::Ring(10 * COIN)); +// }); +//} +// +//#[test] +//fn bond_over_max_promise_month_should_fail() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(stash(123), controller(456)); +// assert_err!( +// Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(COIN), +// RewardDestination::Stash, +// 37 +// ), +// "months at most is 36." +// ); +// +// gen_paired_account!(stash(123), controller(456), promise_month(12)); +// assert_err!( +// Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(COIN), 37), +// "months at most is 36." +// ); +// }); +//} +// +//#[test] +//fn stash_already_bonded_and_controller_already_paired_should_fail() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(unpaired_stash(123), unpaired_controller(456)); +// assert_err!( +// Staking::bond( +// Origin::signed(11), +// unpaired_controller, +// StakingBalance::Ring(COIN), +// RewardDestination::Stash, +// 0 +// ), +// "stash already bonded" +// ); +// assert_err!( +// Staking::bond( +// Origin::signed(unpaired_stash), +// 10, +// StakingBalance::Ring(COIN), +// RewardDestination::Stash, +// 0 +// ), +// "controller already paired" +// ); +// }); +//} +// +//#[test] +//fn pool_should_be_increased_and_decreased_correctly() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let mut ring_pool = Staking::ring_pool(); +// let mut kton_pool = Staking::kton_pool(); +// +// // bond: 100COIN +// gen_paired_account!(stash_1(111), controller_1(222), 0); +// gen_paired_account!(stash_2(333), controller_2(444), promise_month(12)); +// ring_pool += 100 * COIN; +// kton_pool += 100 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// assert_eq!(Staking::kton_pool(), kton_pool); +// +// // unbond: 50Ring 50Kton +// assert_ok!(Staking::unbond( +// Origin::signed(controller_1), +// StakingBalance::Ring(50 * COIN) +// )); +// assert_ok!(Staking::unbond( +// Origin::signed(controller_1), +// StakingBalance::Kton(25 * COIN) +// )); +// // not yet expired: promise for 12 months +// assert_ok!(Staking::unbond( +// Origin::signed(controller_2), +// StakingBalance::Ring(50 * COIN) +// )); +// assert_ok!(Staking::unbond( +// Origin::signed(controller_2), +// StakingBalance::Kton(25 * COIN) +// )); +// ring_pool -= 50 * COIN; +// kton_pool -= 50 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// assert_eq!(Staking::kton_pool(), kton_pool); +// +// // unbond with punish: 12.5Ring +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller_2), +// 125 * COIN / 10, +// promise_month * MONTH_IN_SECONDS as u64 +// )); +// // unbond deposit items: 12.5Ring +// Timestamp::set_timestamp(promise_month * MONTH_IN_SECONDS as u64); +// assert_ok!(Staking::unbond( +// Origin::signed(controller_2), +// StakingBalance::Ring(125 * COIN / 10) +// )); +// ring_pool -= 25 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// +// // slash: 25Ring 50Kton +// Staking::slash_validator(&stash_1, 1_000_000_000); +// Staking::slash_validator(&stash_2, 1_000_000_000); +// ring_pool -= 25 * COIN; +// kton_pool -= 50 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// assert_eq!(Staking::kton_pool(), kton_pool); +// }); +//} +// +//#[test] +//fn unbond_over_max_unlocking_chunks_should_fail() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(stash(123), controller(456), promise_month(12)); +// let deposit_items_len = MAX_UNLOCKING_CHUNKS + 1; +// +// for _ in 1..deposit_items_len { +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(COIN), +// promise_month +// )); +// } +// { +// let ledger = Staking::ledger(&controller).unwrap(); +// assert_eq!(ledger.deposit_items.len(), deposit_items_len); +// assert_eq!(ledger.unlocking.len(), 0); +// } +// +// Timestamp::set_timestamp(promise_month as u64 * MONTH_IN_SECONDS as u64); +// +// for _ in 1..deposit_items_len { +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); +// } +// { +// let ledger = Staking::ledger(&controller).unwrap(); +// assert_eq!(ledger.deposit_items.len(), 1); +// assert_eq!(ledger.unlocking.len(), deposit_items_len - 1); +// } +// assert_err!( +// Staking::unbond( +// Origin::signed(controller), +// StakingBalance::Ring((deposit_items_len - 1) as u64 * COIN) +// ), +// "can not schedule more unlock chunks" +// ); +// }); +//} +// +//#[test] +//fn unlock_value_should_be_increased_and_decreased_correctly() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// // normal Ring/Kton +// { +// let stash = 444; +// let controller = 555; +// let _ = Ring::deposit_creating(&stash, 100 * COIN); +// Kton::deposit_creating(&stash, 100 * COIN); +// +// assert_ok!(Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(50 * COIN), +// RewardDestination::Stash, +// 0 +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Kton(50 * COIN), +// 0 +// )); +// +// let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; +// +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(COIN), +// era: 3, +// is_time_deposit: false, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(COIN))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Kton(COIN), +// era: 3, +// is_time_deposit: false, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(0))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(0), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(0))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Kton(0), +// era: 3, +// is_time_deposit: false, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// } +// +// // promise Ring +// { +// gen_paired_account!(stash(666), controller(777), promise_month(12)); +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(50 * COIN), +// 36 +// )); +// +// let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; +// +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(0), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// for month in [12, 36].iter() { +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 20 * COIN, +// month * MONTH_IN_SECONDS as u64 +// )); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(20 * COIN), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 29 * COIN, +// month * MONTH_IN_SECONDS as u64 +// )); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(29 * COIN), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 50 * COIN, +// month * MONTH_IN_SECONDS as u64 +// )); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(1 * COIN), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// } +// } +// }); +//} +// +//// #[test] +//// fn total_deposit_should_be_increased_and_decreased_correctly() { +//// with_externalities( +//// &mut ExtBuilder::default().existential_deposit(0).build(), +//// || body, +//// ); +//// } +// +//#[test] +//fn promise_extra_should_not_remove_unexpired_items() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(stash(123), controller(456), promise_month(12)); +// +// let expired_item_len = 3; +// let expiry_date = promise_month as u64 * MONTH_IN_SECONDS as u64; +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(5 * COIN), +// 0 +// )); +// for _ in 0..expired_item_len { +// assert_ok!(Staking::promise_extra(Origin::signed(controller), COIN, promise_month)); +// } +// +// Timestamp::set_timestamp(expiry_date - 1); +// assert_ok!(Staking::promise_extra( +// Origin::signed(controller), +// 2 * COIN, +// promise_month +// )); +// assert_eq!( +// Staking::ledger(&controller).unwrap().deposit_items.len(), +// 2 + expired_item_len +// ); +// +// Timestamp::set_timestamp(expiry_date); +// assert_ok!(Staking::promise_extra( +// Origin::signed(controller), +// 2 * COIN, +// promise_month +// )); +// assert_eq!(Staking::ledger(&controller).unwrap().deposit_items.len(), 2); +// }); +//} +// +//#[test] +//fn unbond_zero_before_expiry() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let expiry_date = 12 * MONTH_IN_SECONDS as u64; +// let unbond_value = StakingBalance::Ring(COIN); +// +// Timestamp::set_timestamp(expiry_date - 1); +// assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); +// assert_eq!( +// Staking::ledger(&10).unwrap().unlocking[0].value, +// StakingBalance::Ring(0) +// ); +// +// Timestamp::set_timestamp(expiry_date); +// assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); +// assert_eq!(Staking::ledger(&10).unwrap().unlocking[1].value, unbond_value); +// }); +//} +// +//// bond 10_000 Ring for 12 months, gain 1 Kton +//// bond extra 10_000 Ring for 36 months, gain 3 Kton +//// bond extra 1 Kton +//// nominate +//// unlock the 12 months deposit item with punish +//// lost 3 Kton and 10_000 Ring's power for nominate +//#[test] +//fn yakio_q1() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 777; +// let controller = 888; +// let _ = Ring::deposit_creating(&stash, 20_000); +// +// assert_ok!(Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(10_000), +// RewardDestination::Stash, +// 12 +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(10_000), +// 36 +// )); +// assert_eq!(Kton::free_balance(&stash), 4); +// +// assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 36)); +// assert_eq!(Staking::ledger(&controller).unwrap().total_kton, 1); +// +// assert_ok!(Staking::nominate(Origin::signed(controller), vec![controller])); +// +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 10_000 * COIN, +// 12 * MONTH_IN_SECONDS as u64 +// )); +// assert_eq!(Kton::free_balance(&stash), 1); +// +// let ledger = StakingLedgers { +// stash: 777, +// total_ring: 10_000, +// total_deposit_ring: 10_000, +// active_ring: 10_000, +// active_deposit_ring: 10_000, +// total_kton: 1, +// active_kton: 1, +// deposit_items: vec![TimeDepositItem { +// value: 10_000, +// start_time: 0, +// expire_time: 36 * MONTH_IN_SECONDS as u64, +// }], +// unlocking: vec![], +// }; +// start_era(3); +// assert_ok!(Staking::withdraw_unbonded(Origin::signed(controller))); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// // not enough Kton to unbond +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 10_000 * COIN, +// 36 * MONTH_IN_SECONDS as u64 +// )); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// }); +//} +// +//// how to balance the power and calculate the reward if some validators have been chilled +//#[test] +//fn yakio_q2() { +// fn run(with_new_era: bool) -> u64 { +// let mut balance = 0; +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(validator_1_stash(123), validator_1_controller(456), 0); +// gen_paired_account!(validator_2_stash(234), validator_2_controller(567), 0); +// gen_paired_account!(nominator_stash(345), nominator_controller(678), 0); +// +// assert_ok!(Staking::validate( +// Origin::signed(validator_1_controller), +// vec![0; 8], +// 0, +// 3 +// )); +// assert_ok!(Staking::validate( +// Origin::signed(validator_2_controller), +// vec![1; 8], +// 0, +// 3 +// )); +// assert_ok!(Staking::nominate( +// Origin::signed(nominator_controller), +// vec![validator_1_stash, validator_2_stash] +// )); +// +// start_era(1); +// assert_ok!(Staking::chill(Origin::signed(validator_1_controller))); +// // assert_ok!(Staking::chill(Origin::signed(validator_2_controller))); +// if with_new_era { +// start_era(2); +// } +// Staking::reward_validator(&validator_1_stash, 1000 * COIN); +// Staking::reward_validator(&validator_2_stash, 1000 * COIN); +// +// balance = Ring::free_balance(&nominator_stash); +// }); +// +// balance +// } +// +// let free_balance = run(false); +// let free_balance_with_new_era = run(true); +// +// assert_ne!(free_balance, 0); +// assert_ne!(free_balance_with_new_era, 0); +// assert!(free_balance > free_balance_with_new_era); +//} #[test] -fn time_deposit_ring_unbond_and_withdraw_should_work() { +fn xavier_q1() { ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Timestamp::set_timestamp(13 * MONTH_IN_SECONDS as u64); - - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 100 * COIN, - total_deposit_ring: 100 * COIN, - active_deposit_ring: 90 * COIN, - active_ring: 90 * COIN, - total_kton: 0, - active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 90 * COIN, - start_time: 0, - expire_time: 12 * MONTH_IN_SECONDS as u64 - }], - unlocking: vec![UnlockChunk { - value: StakingBalance::Ring(10 * COIN), - era: 3, - is_time_deposit: true - }] - }) - ); - - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(20 * COIN))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 100 * COIN, - total_deposit_ring: 100 * COIN, - active_deposit_ring: 70 * COIN, - active_ring: 70 * COIN, - total_kton: 0, - active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 70 * COIN, - start_time: 0, - expire_time: 12 * MONTH_IN_SECONDS as u64 - }], - unlocking: vec![ - UnlockChunk { - value: StakingBalance::Ring(10 * COIN), - era: 3, - is_time_deposit: true - }, - UnlockChunk { - value: StakingBalance::Ring(20 * COIN), - era: 3, - is_time_deposit: true - } - ] - }) - ); - - // more than active ring - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(120 * COIN))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 100 * COIN, - total_deposit_ring: 100 * COIN, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 0, - active_kton: 0, - deposit_items: vec![], // should be cleared - unlocking: vec![ - UnlockChunk { - value: StakingBalance::Ring(10 * COIN), - era: 3, - is_time_deposit: true - }, - UnlockChunk { - value: StakingBalance::Ring(20 * COIN), - era: 3, - is_time_deposit: true - }, - UnlockChunk { - value: StakingBalance::Ring(70 * COIN), - era: 3, - is_time_deposit: true - }, - ] - }) - ); - - start_era(3); - - assert_ok!(Staking::withdraw_unbonded(Origin::signed(10))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 0, - total_deposit_ring: 0, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 0, - active_kton: 0, - deposit_items: vec![], // should be cleared - unlocking: vec![] - }) - ); - - let free_balance = Ring::free_balance(&11); - assert_eq!( - Ring::locks(&11), - vec![balances::BalanceLock { - id: STAKING_ID, - amount: 0, - until: u64::max_value(), - reasons: WithdrawReasons::all() - }] - ); - assert_ok!(Ring::ensure_can_withdraw( - &11, - free_balance, - WithdrawReason::Transfer, - 0 - )); - }); -} - -#[test] -fn normal_unbond_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 11; - let controller = 10; - let value = 200 * COIN; - let promise_month = 12; - // unbond normal ring - let _ = Ring::deposit_creating(&stash, 1000 * COIN); - - { - let kton_free_balance = Kton::free_balance(&stash); - let mut ledger = Staking::ledger(&controller).unwrap(); - - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(value), - promise_month, - )); - assert_eq!( - Kton::free_balance(&stash), - kton_free_balance + utils::compute_kton_return::(value, promise_month) - ); - ledger.total_ring += value; - ledger.total_deposit_ring += value; - ledger.active_ring += value; - ledger.active_deposit_ring += value; - ledger.deposit_items.push(TimeDepositItem { - value: value, - start_time: 0, - expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64, - }); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - } - - { - let kton_free_balance = Kton::free_balance(&stash); - let mut ledger = Staking::ledger(&controller).unwrap(); - - // we try to bond 1 kton, but stash only has 0.03 Kton - // extra = 1.min(0.03) - // bond += 0.03 - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Kton(COIN), - 0 - )); - ledger.total_kton += kton_free_balance; - ledger.active_kton += kton_free_balance; - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - - assert_ok!(Staking::unbond( - Origin::signed(controller), - StakingBalance::Kton(kton_free_balance) - )); - ledger.active_kton = 0; - ledger.unlocking = vec![UnlockChunk { - value: StakingBalance::Kton(kton_free_balance), - era: 3, - is_time_deposit: false, - }]; - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - } - }); -} - -#[test] -fn punished_unbond_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 1001; - let controller = 1000; - let promise_month = 36; - - let _ = Ring::deposit_creating(&stash, 100 * COIN); - Kton::deposit_creating(&stash, COIN / 100000); - - // timestamp now is 0. - // free balance of kton is too low to work - assert_ok!(Staking::bond( + gen_paired_account!(stash(123), controller(456), promise_month(12)); + assert_ok!(Staking::bond_extra( Origin::signed(stash), - controller, - StakingBalance::Ring(10 * COIN), - RewardDestination::Stash, - promise_month - )); - assert_eq!( - Staking::ledger(&controller), - Some(StakingLedgers { - stash, - total_ring: 10 * COIN, - total_deposit_ring: 10 * COIN, - active_deposit_ring: 10 * COIN, - active_ring: 10 * COIN, - total_kton: 0, - active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 10 * COIN, - start_time: 0, - expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64 - }], // should be cleared - unlocking: vec![] - }) - ); - let mut ledger = Staking::ledger(&controller).unwrap(); - let kton_free_balance = Kton::free_balance(&stash); - // kton is 0, skip unbond_with_punish - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 10 * COIN, - promise_month as u64 * MONTH_IN_SECONDS as u64 - )); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - assert_eq!(Kton::free_balance(&stash), kton_free_balance); - - // set more kton balance to make it work - Kton::deposit_creating(&stash, 10 * COIN); - let kton_free_balance = Kton::free_balance(&stash); - let unbond_value = 5 * COIN; - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - unbond_value, - promise_month as u64 * MONTH_IN_SECONDS as u64 - )); - ledger.active_ring -= unbond_value; - ledger.active_deposit_ring -= unbond_value; - ledger.deposit_items[0].value -= unbond_value; - ledger.unlocking = vec![UnlockChunk { - value: StakingBalance::Ring(unbond_value), - era: 3, - is_time_deposit: true, - }]; - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - - let kton_punishment = utils::compute_kton_return::(unbond_value, promise_month); - assert_eq!(Kton::free_balance(&stash), kton_free_balance - 3 * kton_punishment); - - // if deposit_item.value == 0 - // the whole item should be be dropped - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 5 * COIN, - promise_month as u64 * MONTH_IN_SECONDS as u64 - )); - assert!(Staking::ledger(&controller).unwrap().deposit_items.is_empty()); - }); -} - -#[test] -fn transform_to_promised_ring_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Ring::deposit_creating(&1001, 100 * COIN); - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(10 * COIN), - RewardDestination::Stash, - 0 + StakingBalance::Ring(50 * COIN), + 24 )); - let origin_ledger = Staking::ledger(&1000).unwrap(); - let kton_free_balance = Kton::free_balance(&1001); + println!("Ledger: {:#?}", Staking::ledger(controller)); + println!("Stash Free Balance: {:?}", Kton::free_balance(stash)); + println!("Stash Kton Locks: {:?}", Kton::locks(stash)); - assert_ok!(Staking::promise_extra(Origin::signed(1000), 5 * COIN, 12)); - - assert_eq!( - Staking::ledger(&1000), - Some(StakingLedgers { - stash: 1001, - total_ring: origin_ledger.total_ring, - total_deposit_ring: origin_ledger.total_deposit_ring + 5 * COIN, - active_deposit_ring: origin_ledger.active_deposit_ring + 5 * COIN, - active_ring: origin_ledger.active_ring, - total_kton: origin_ledger.total_kton, - active_kton: origin_ledger.active_kton, - deposit_items: vec![TimeDepositItem { - value: 5 * COIN, - start_time: 0, - expire_time: 12 * MONTH_IN_SECONDS as u64 - }], - unlocking: vec![] - }) - ); - - assert_eq!(Kton::free_balance(&1001), kton_free_balance + (5 * COIN / 10000)); - }); -} - -#[test] -fn expired_ring_should_capable_to_promise_again() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Ring::deposit_creating(&1001, 100 * COIN); - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(10 * COIN), - RewardDestination::Stash, - 12 - )); - let mut ledger = Staking::ledger(&1000).unwrap(); let ts = 13 * MONTH_IN_SECONDS as u64; - let promise_extra_value = 5 * COIN; Timestamp::set_timestamp(ts); - assert_ok!(Staking::promise_extra(Origin::signed(1000), promise_extra_value, 13)); - ledger.total_deposit_ring = promise_extra_value; - ledger.active_deposit_ring = promise_extra_value; - // old deposit_item with 12 months promised removed - ledger.deposit_items = vec![TimeDepositItem { - value: promise_extra_value, - start_time: ts, - expire_time: 2 * ts, - }]; - assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); - }); -} - -#[test] -fn inflation_should_be_correct() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let initial_issuance = 1_200_000_000 * COIN; - let surplus_needed = initial_issuance - Ring::total_issuance(); - let _ = Ring::deposit_into_existing(&11, surplus_needed); - assert_eq!(Ring::total_issuance(), initial_issuance); - assert_eq!(Staking::current_era_total_reward(), 80000000 * COIN / 10); - start_era(11); - // ErasPerEpoch = 10 - assert_eq!(Staking::current_era_total_reward(), 88000000 * COIN / 10); - }); -} - -#[test] -fn reward_should_work_correctly() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - // create controller account - let _ = Ring::deposit_creating(&2000, COIN); - let _ = Ring::deposit_creating(&1000, COIN); - let _ = Ring::deposit_creating(&200, COIN); - // new validator - let _ = Ring::deposit_creating(&2001, 2000 * COIN); - Kton::deposit_creating(&2001, 10 * COIN); - // new validator - let _ = Ring::deposit_creating(&1001, 300 * COIN); - Kton::deposit_creating(&1001, 1 * COIN); - // handle some dirty work - let _ = Ring::deposit_creating(&201, 2000 * COIN); - Kton::deposit_creating(&201, 10 * COIN); - assert_eq!(Kton::free_balance(&201), 10 * COIN); - - // 2001-2000 - assert_ok!(Staking::bond( - Origin::signed(2001), - 2000, - StakingBalance::Ring(300 * COIN), - RewardDestination::Controller, - 12, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(2001), - StakingBalance::Kton(1 * COIN), - 0 - )); - // 1001-1000 - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(300 * COIN), - RewardDestination::Controller, - 12, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(1001), - StakingBalance::Kton(1 * COIN), - 0 - )); - let ring_pool = Staking::ring_pool(); - let kton_pool = Staking::kton_pool(); - // 201-200 - assert_ok!(Staking::bond( - Origin::signed(201), - 200, - StakingBalance::Ring(3000 * COIN - ring_pool), - RewardDestination::Stash, - 12, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(201), - StakingBalance::Kton(10 * COIN - kton_pool), - 0, - )); - // ring_pool and kton_pool - assert_eq!(Staking::ring_pool(), 3000 * COIN); - assert_eq!(Staking::kton_pool(), 10 * COIN); - // 1/5 ring_pool and 1/5 kton_pool - assert_ok!(Staking::validate(Origin::signed(2000), [0; 8].to_vec(), 0, 3)); - assert_ok!(Staking::nominate(Origin::signed(1000), vec![2001])); - assert_eq!(Staking::ledger(&2000).unwrap().active_kton, 1 * COIN); - assert_eq!(Staking::ledger(&2000).unwrap().active_ring, 300 * COIN); - assert_eq!(Staking::slashable_balance_of(&2001), 600 * COIN as u128); - // 600COIN for rewarding ring bond-er - // 600COIN for rewarding kton bond-er - Staking::select_validators(); - Staking::reward_validator(&2001, 1200 * COIN); - - assert_eq!( - Staking::stakers(2001), - Exposure { - total: 1200000000000, - own: 600000000000, - others: vec![IndividualExposure { - who: 1001, - value: 600000000000 - }] - } - ); - assert_eq!(Ring::free_balance(&2000), 601 * COIN); - assert_eq!(Ring::free_balance(&1000), 601 * COIN); - }); -} - -#[test] -fn slash_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Ring::deposit_creating(&1001, 100 * COIN); - Kton::deposit_creating(&1001, 100 * COIN); - - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(50 * COIN), - RewardDestination::Controller, - 0, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(1001), - StakingBalance::Kton(50 * COIN), - 0 - )); - assert_ok!(Staking::validate(Origin::signed(1000), [0; 8].to_vec(), 0, 3)); - - // slash 1% - let slash_value = 5 * COIN / 10; - let mut ledger = Staking::ledger(&1000).unwrap(); - let ring_free_balance = Ring::free_balance(&1001); - let kton_free_balance = Kton::free_balance(&1001); - Staking::slash_validator(&1001, 10_000_000); - ledger.total_ring -= slash_value; - ledger.active_ring -= slash_value; - ledger.total_kton -= slash_value; - ledger.active_kton -= slash_value; - assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); - assert_eq!(Ring::free_balance(&1001), ring_free_balance - slash_value); - assert_eq!(Kton::free_balance(&1001), kton_free_balance - slash_value); - }); -} - -#[test] -fn test_inflation() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - assert_eq!(Staking::current_era_total_reward(), 80_000_000 * COIN / 10); - start_era(20); - assert_eq!(Staking::epoch_index(), 2); - assert_eq!(Staking::current_era_total_reward(), 9_999_988_266 * COIN / 1000); - }); -} - -#[test] -fn set_controller_should_remove_old_ledger() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 11; - let old_controller = 10; - let new_controller = 12; - - assert!(Staking::ledger(&old_controller).is_some()); - assert_eq!(Staking::bonded(&stash), Some(old_controller)); - - assert_ok!(Staking::set_controller(Origin::signed(stash), new_controller)); - assert!(Staking::ledger(&old_controller).is_none()); - }); -} - -#[test] -fn set_controller_should_not_change_ledger() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - assert_eq!(Staking::ledger(&10).unwrap().total_ring, 100 * COIN); - assert_ok!(Staking::set_controller(Origin::signed(11), 12)); - assert_eq!(Staking::ledger(&12).unwrap().total_ring, 100 * COIN); - }); -} - -#[test] -fn slash_should_not_touch_unlockings() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let old_ledger = Staking::ledger(&10).unwrap(); - // only deposit_ring, no normal_ring - assert_eq!( - ( - old_ledger.total_ring, - old_ledger.active_ring, - old_ledger.active_deposit_ring - ), - (100 * COIN, 100 * COIN, 100 * COIN) - ); - - assert_ok!(Staking::bond_extra( - Origin::signed(11), - StakingBalance::Ring(100 * COIN), - 0 - )); - Kton::deposit_creating(&11, 10 * COIN); - assert_ok!(Staking::bond_extra( - Origin::signed(11), - StakingBalance::Kton(10 * COIN), - 0 - )); - - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); - let new_ledger = Staking::ledger(&10).unwrap(); - assert_eq!( - ( - new_ledger.total_ring, - new_ledger.active_ring, - new_ledger.active_deposit_ring - ), - (200 * COIN, 190 * COIN, 100 * COIN) - ); - - // slash 100% - Staking::slash_validator(&11, 1_000_000_000); - - let ledger = Staking::ledger(&10).unwrap(); - assert_eq!( - (ledger.total_ring, ledger.active_ring, ledger.active_deposit_ring), - // 10Ring in unlocking - (10 * COIN, 0, 0) - ); - assert_eq!(ledger.unlocking[0].value, StakingBalance::Ring(10 * COIN)); - }); -} - -#[test] -fn bond_over_max_promise_month_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(stash(123), controller(456)); - assert_err!( - Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(COIN), - RewardDestination::Stash, - 37 - ), - "months at most is 36." - ); - - gen_paired_account!(stash(123), controller(456), promise_month(12)); - assert_err!( - Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(COIN), 37), - "months at most is 36." - ); - }); -} - -#[test] -fn stash_already_bonded_and_controller_already_paired_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(unpaired_stash(123), unpaired_controller(456)); - assert_err!( - Staking::bond( - Origin::signed(11), - unpaired_controller, - StakingBalance::Ring(COIN), - RewardDestination::Stash, - 0 - ), - "stash already bonded" - ); - assert_err!( - Staking::bond( - Origin::signed(unpaired_stash), - 10, - StakingBalance::Ring(COIN), - RewardDestination::Stash, - 0 - ), - "controller already paired" - ); - }); -} - -#[test] -fn pool_should_be_increased_and_decreased_correctly() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let mut ring_pool = Staking::ring_pool(); - let mut kton_pool = Staking::kton_pool(); - - // bond: 100COIN - gen_paired_account!(stash_1(111), controller_1(222), 0); - gen_paired_account!(stash_2(333), controller_2(444), promise_month(12)); - ring_pool += 100 * COIN; - kton_pool += 100 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - assert_eq!(Staking::kton_pool(), kton_pool); - - // unbond: 50Ring 50Kton - assert_ok!(Staking::unbond( - Origin::signed(controller_1), - StakingBalance::Ring(50 * COIN) - )); assert_ok!(Staking::unbond( - Origin::signed(controller_1), - StakingBalance::Kton(25 * COIN) - )); - // not yet expired: promise for 12 months - assert_ok!(Staking::unbond( - Origin::signed(controller_2), - StakingBalance::Ring(50 * COIN) - )); - assert_ok!(Staking::unbond( - Origin::signed(controller_2), - StakingBalance::Kton(25 * COIN) - )); - ring_pool -= 50 * COIN; - kton_pool -= 50 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - assert_eq!(Staking::kton_pool(), kton_pool); - - // unbond with punish: 12.5Ring - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller_2), - 125 * COIN / 10, - promise_month * MONTH_IN_SECONDS as u64 - )); - // unbond deposit items: 12.5Ring - Timestamp::set_timestamp(promise_month * MONTH_IN_SECONDS as u64); - assert_ok!(Staking::unbond( - Origin::signed(controller_2), - StakingBalance::Ring(125 * COIN / 10) - )); - ring_pool -= 25 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - - // slash: 25Ring 50Kton - Staking::slash_validator(&stash_1, 1_000_000_000); - Staking::slash_validator(&stash_2, 1_000_000_000); - ring_pool -= 25 * COIN; - kton_pool -= 50 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - assert_eq!(Staking::kton_pool(), kton_pool); - }); -} - -#[test] -fn unbond_over_max_unlocking_chunks_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(stash(123), controller(456), promise_month(12)); - let deposit_items_len = MAX_UNLOCKING_CHUNKS + 1; - - for _ in 1..deposit_items_len { - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(COIN), - promise_month - )); - } - { - let ledger = Staking::ledger(&controller).unwrap(); - assert_eq!(ledger.deposit_items.len(), deposit_items_len); - assert_eq!(ledger.unlocking.len(), 0); - } - - Timestamp::set_timestamp(promise_month as u64 * MONTH_IN_SECONDS as u64); - - for _ in 1..deposit_items_len { - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); - } - { - let ledger = Staking::ledger(&controller).unwrap(); - assert_eq!(ledger.deposit_items.len(), 1); - assert_eq!(ledger.unlocking.len(), deposit_items_len - 1); - } - assert_err!( - Staking::unbond( - Origin::signed(controller), - StakingBalance::Ring((deposit_items_len - 1) as u64 * COIN) - ), - "can not schedule more unlock chunks" - ); - }); -} - -#[test] -fn unlock_value_should_be_increased_and_decreased_correctly() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - // normal Ring/Kton - { - let stash = 444; - let controller = 555; - let _ = Ring::deposit_creating(&stash, 100 * COIN); - Kton::deposit_creating(&stash, 100 * COIN); - - assert_ok!(Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(50 * COIN), - RewardDestination::Stash, - 0 - )); - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Kton(50 * COIN), - 0 - )); - - let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; - - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(COIN), - era: 3, - is_time_deposit: false, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(COIN))); - unlocking.push(UnlockChunk { - value: StakingBalance::Kton(COIN), - era: 3, - is_time_deposit: false, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(0))); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(0), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(0))); - unlocking.push(UnlockChunk { - value: StakingBalance::Kton(0), - era: 3, - is_time_deposit: false, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - } - - // promise Ring - { - gen_paired_account!(stash(666), controller(777), promise_month(12)); - - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(50 * COIN), - 36 - )); - - let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; - - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(0), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - for month in [12, 36].iter() { - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 20 * COIN, - month * MONTH_IN_SECONDS as u64 - )); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(20 * COIN), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 29 * COIN, - month * MONTH_IN_SECONDS as u64 - )); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(29 * COIN), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 50 * COIN, - month * MONTH_IN_SECONDS as u64 - )); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(1 * COIN), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - } - } - }); -} - -// #[test] -// fn total_deposit_should_be_increased_and_decreased_correctly() { -// with_externalities( -// &mut ExtBuilder::default().existential_deposit(0).build(), -// || body, -// ); -// } - -#[test] -fn promise_extra_should_not_remove_unexpired_items() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(stash(123), controller(456), promise_month(12)); - - let expired_item_len = 3; - let expiry_date = promise_month as u64 * MONTH_IN_SECONDS as u64; - - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(5 * COIN), - 0 - )); - for _ in 0..expired_item_len { - assert_ok!(Staking::promise_extra(Origin::signed(controller), COIN, promise_month)); - } - - Timestamp::set_timestamp(expiry_date - 1); - assert_ok!(Staking::promise_extra( - Origin::signed(controller), - 2 * COIN, - promise_month - )); - assert_eq!( - Staking::ledger(&controller).unwrap().deposit_items.len(), - 2 + expired_item_len - ); - - Timestamp::set_timestamp(expiry_date); - assert_ok!(Staking::promise_extra( - Origin::signed(controller), - 2 * COIN, - promise_month - )); - assert_eq!(Staking::ledger(&controller).unwrap().deposit_items.len(), 2); - }); -} - -#[test] -fn unbond_zero_before_expiry() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let expiry_date = 12 * MONTH_IN_SECONDS as u64; - let unbond_value = StakingBalance::Ring(COIN); - - Timestamp::set_timestamp(expiry_date - 1); - assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); - assert_eq!( - Staking::ledger(&10).unwrap().unlocking[0].value, - StakingBalance::Ring(0) - ); - - Timestamp::set_timestamp(expiry_date); - assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); - assert_eq!(Staking::ledger(&10).unwrap().unlocking[1].value, unbond_value); - }); -} - -// bond 10_000 Ring for 12 months, gain 1 Kton -// bond extra 10_000 Ring for 36 months, gain 3 Kton -// bond extra 1 Kton -// nominate -// unlock the 12 months deposit item with punish -// lost 3 Kton and 10_000 Ring's power for nominate -#[test] -fn yakio_q1() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 777; - let controller = 888; - let _ = Ring::deposit_creating(&stash, 20_000); - - assert_ok!(Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(10_000), - RewardDestination::Stash, - 12 - )); - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(10_000), - 36 - )); - assert_eq!(Kton::free_balance(&stash), 4); - - assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 36)); - assert_eq!(Staking::ledger(&controller).unwrap().total_kton, 1); - - assert_ok!(Staking::nominate(Origin::signed(controller), vec![controller])); - - assert_ok!(Staking::unbond_with_punish( Origin::signed(controller), - 10_000 * COIN, - 12 * MONTH_IN_SECONDS as u64 + StakingBalance::Kton(50 * COIN) )); - assert_eq!(Kton::free_balance(&stash), 1); - let ledger = StakingLedgers { - stash: 777, - total_ring: 10_000, - total_deposit_ring: 10_000, - active_ring: 10_000, - active_deposit_ring: 10_000, - total_kton: 1, - active_kton: 1, - deposit_items: vec![TimeDepositItem { - value: 10_000, - start_time: 0, - expire_time: 36 * MONTH_IN_SECONDS as u64, - }], - unlocking: vec![], - }; - start_era(3); - assert_ok!(Staking::withdraw_unbonded(Origin::signed(controller))); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - // not enough Kton to unbond - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 10_000 * COIN, - 36 * MONTH_IN_SECONDS as u64 - )); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); + println!("Stash Kton Locks: {:?}", Kton::locks(stash)); }); } - -// how to balance the power and calculate the reward if some validators have been chilled -#[test] -fn yakio_q2() { - fn run(with_new_era: bool) -> u64 { - let mut balance = 0; - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(validator_1_stash(123), validator_1_controller(456), 0); - gen_paired_account!(validator_2_stash(234), validator_2_controller(567), 0); - gen_paired_account!(nominator_stash(345), nominator_controller(678), 0); - - assert_ok!(Staking::validate( - Origin::signed(validator_1_controller), - vec![0; 8], - 0, - 3 - )); - assert_ok!(Staking::validate( - Origin::signed(validator_2_controller), - vec![1; 8], - 0, - 3 - )); - assert_ok!(Staking::nominate( - Origin::signed(nominator_controller), - vec![validator_1_stash, validator_2_stash] - )); - - start_era(1); - assert_ok!(Staking::chill(Origin::signed(validator_1_controller))); - // assert_ok!(Staking::chill(Origin::signed(validator_2_controller))); - if with_new_era { - start_era(2); - } - Staking::reward_validator(&validator_1_stash, 1000 * COIN); - Staking::reward_validator(&validator_2_stash, 1000 * COIN); - - balance = Ring::free_balance(&nominator_stash); - }); - - balance - } - - let free_balance = run(false); - let free_balance_with_new_era = run(true); - - assert_ne!(free_balance, 0); - assert_ne!(free_balance_with_new_era, 0); - assert!(free_balance > free_balance_with_new_era); -} diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index dbae1a62e..8974dd291 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -11,6 +11,8 @@ // fn on_dilution(treasury_income: Balance); //} +use srml_support::traits::Currency; + pub trait OnMinted { fn on_minted(value: Balance); } @@ -18,3 +20,27 @@ pub trait OnMinted { pub trait OnAccountBalanceChanged { fn on_changed(who: &AccountId, old: Balance, new: Balance); } + +/// A currency whose accounts can have liquidity restrictions. +pub trait LockableCurrency: Currency { + /// The quantity used to denote time; usually just a `BlockNumber`. + /// In Darwinia we prefer using `TimeStamp/u64`. + type Id; + + /// Create a new balance lock on account `who`. + /// + /// If the new lock is valid (i.e. not already expired), it will push the struct to + /// the `Locks` vec in storage. Note that you can lock more funds than a user has. + /// + /// If the lock `id` already exists, this will update it. + fn set_lock(who: &AccountId, amount: Self::Balance, id: Self::Id); + + // TODO: reserve + // fn extend_lock(); + + /// Remove an existing lock. + fn remove_lock(id: Self::Id, who: &AccountId); + + /// The number of locks. + fn count() -> u32; +} diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index d9e522a42..7d1728738 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -1 +1,10 @@ +use codec::{Decode, Encode}; +use sr_primitives::RuntimeDebug; + pub type TimeStamp = u64; + +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +pub enum Id { + Staking, + Unbonding(Moment), +} From d0cc60564ecc4c55d21bddb4fa8c56d9e251039d Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Fri, 15 Nov 2019 13:41:48 +0800 Subject: [PATCH 05/30] update: new design for `LockableCurrency` --- srml/balances/src/lib.rs | 107 +- srml/kton/src/lib.rs | 106 +- srml/kton/src/tests.rs | 541 +++++---- srml/staking/src/lib.rs | 11 +- srml/staking/src/tests.rs | 2159 ++++++++++++++++++------------------ srml/support/src/traits.rs | 23 +- srml/support/src/types.rs | 57 +- 7 files changed, 1511 insertions(+), 1493 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 3dce1a197..6508217dd 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -31,8 +31,8 @@ use support::{ decl_event, decl_module, decl_storage, dispatch::Result, traits::{ - Currency, ExistenceRequirement, Get, Imbalance, LockIdentifier, OnFreeBalanceZero, OnUnbalanced, - ReservableCurrency, SignedImbalance, UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, + Currency, ExistenceRequirement, Get, Imbalance, OnFreeBalanceZero, OnUnbalanced, ReservableCurrency, + SignedImbalance, UpdateBalanceOutcome, WithdrawReason, WithdrawReasons, }, Parameter, StorageValue, }; @@ -42,7 +42,10 @@ mod mock; mod tests; pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; -use darwinia_support::{traits::LockableCurrency, types::Id}; +use darwinia_support::{ + traits::LockableCurrency, + types::{BalanceLock, Id}, +}; pub trait Subtrait: system::Trait + timestamp::Trait { /// The balance of an account. @@ -168,14 +171,6 @@ impl Ves } } -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)] -pub struct BalanceLock { - pub id: LockIdentifier, - pub amount: Balance, - pub until: Moment, - pub reasons: WithdrawReasons, -} - decl_storage! { trait Store for Module, I: Instance=DefaultInstance> as Balances { /// The total units issued in the system. @@ -732,27 +727,26 @@ where reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { - // if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) - // && Self::vesting_balance(who) > new_balance - // { - // return Err("vesting balance too high to send value"); - // } - // let locks = Self::locks(who); - // if locks.is_empty() { - // return Ok(()); - // } - // - // let now = >::now(); - // if locks - // .into_iter() - // .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) - // { - // Ok(()) - // } else { - // Err("account liquidity restrictions prevent withdrawal") - // } - // TODO - unimplemented!() + if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) + && Self::vesting_balance(who) > new_balance + { + return Err("vesting balance too high to send value"); + } + let locks = Self::locks(who); + if locks.is_empty() { + return Ok(()); + } + + let now = >::now(); + // TODO: logic? + if locks + .into_iter() + .all(|lock| !lock.valid(&now) || new_balance >= lock.amount || !lock.reasons.intersects(reasons)) + { + Ok(()) + } else { + Err("account liquidity restrictions prevent withdrawal") + } } fn transfer( @@ -990,44 +984,27 @@ where T::Balance: MaybeSerializeDeserialize + Debug, { type Id = Id; + type WithdrawReasons = WithdrawReasons; + + fn set_lock(who: &T::AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons) { + let now = >::now(); - // `amount` > `free_balance` is allowed - fn set_lock(who: &T::AccountId, amount: Self::Balance, id: Self::Id) { - // let now = >::now(); - // let mut new_lock = Some(BalanceLock { - // id, - // amount, - // until, - // reasons, - // }); - // let mut locks = Self::locks(who) - // .into_iter() - // .filter_map(|l| { - // if l.id == id { - // new_lock.take() - // } else if l.until > now { - // Some(l) - // } else { - // None - // } - // }) - // .collect::>(); - // if let Some(lock) = new_lock { - // locks.push(lock); - // } - // >::insert(who, locks); + >::mutate(who, |locks| { + locks.retain(|lock| lock.id != id || lock.valid(&now)); + locks.push(BalanceLock { id, amount, reasons }); + }); } - fn remove_lock(id: Self::Id, who: &T::AccountId) { - // let now = >::now(); - // >::mutate(who, |locks| { - // // unexpired and mismatched id -> keep - // locks.retain(|lock| (lock.until > now) && (lock.id != id)); - // }); + fn remove_lock(who: &T::AccountId, id: Self::Id) { + let now = >::now(); + >::mutate(who, |locks| { + // unexpired and mismatched id -> keep + locks.retain(|lock| lock.valid(&now) && lock.id != id); + }); } - fn count() -> u32 { - unimplemented!() + fn locks_count(who: &T::AccountId) -> u32 { + >::get(&who).len() as _ } } diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 0d2d244de..c7207bc88 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -13,14 +13,17 @@ use srml_support::{ decl_event, decl_module, decl_storage, dispatch::Result, traits::{ - Currency, ExistenceRequirement, Imbalance, LockIdentifier, OnUnbalanced, SignedImbalance, UpdateBalanceOutcome, - WithdrawReason, WithdrawReasons, + Currency, ExistenceRequirement, Imbalance, OnUnbalanced, SignedImbalance, UpdateBalanceOutcome, WithdrawReason, + WithdrawReasons, }, Parameter, StorageMap, StorageValue, }; use system::ensure_signed; -use darwinia_support::{traits::LockableCurrency, types::Id}; +use darwinia_support::{ + traits::LockableCurrency, + types::{BalanceLock, Id}, +}; use imbalance::{NegativeImbalance, PositiveImbalance}; #[cfg(test)] @@ -54,13 +57,6 @@ impl VestingSchedule { } } -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -pub struct BalanceLock { - pub id: Id, - pub amount: Balance, - pub reasons: WithdrawReasons, -} - pub trait Trait: timestamp::Trait { type Balance: Parameter + Member @@ -226,27 +222,26 @@ impl Currency for Module { reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { - // if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) - // && Self::vesting_balance(who) > new_balance - // { - // return Err("vesting balance too high to send value"); - // } - // let locks = Self::locks(who); - // if locks.is_empty() { - // return Ok(()); - // } - // - // let now = >::now(); - // if locks - // .into_iter() - // .all(|l| now >= l.until || new_balance >= l.amount || !l.reasons.intersects(reasons)) - // { - // Ok(()) - // } else { - // Err("account liquidity restrictions prevent withdrawal") - // } - // TODO - unimplemented!() + if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) + && Self::vesting_balance(who) > new_balance + { + return Err("vesting balance too high to send value"); + } + let locks = Self::locks(who); + if locks.is_empty() { + return Ok(()); + } + + let now = >::now(); + // TODO: logic? + if locks + .into_iter() + .all(|lock| !lock.valid(&now) || new_balance >= lock.amount || !lock.reasons.intersects(reasons)) + { + Ok(()) + } else { + Err("account liquidity restrictions prevent withdrawal") + } } // TODO: add fee @@ -385,43 +380,26 @@ where T::Balance: MaybeSerializeDeserialize + Debug, { type Id = Id; + type WithdrawReasons = WithdrawReasons; + + fn set_lock(who: &T::AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons) { + let now = >::now(); - // `amount` > `free_balance` is allowed - fn set_lock(who: &T::AccountId, amount: Self::Balance, id: Self::Id) { - // let now = >::now(); - // let mut new_lock = Some(BalanceLock { - // id, - // amount, - // until, - // reasons, - // }); - // let mut locks = Self::locks(who) - // .into_iter() - // .filter_map(|l| { - // if l.id == id { - // new_lock.take() - // } else if l.until > now { - // Some(l) - // } else { - // None - // } - // }) - // .collect::>(); - // if let Some(lock) = new_lock { - // locks.push(lock); - // } - // >::insert(who, locks); + >::mutate(who, |locks| { + locks.retain(|lock| lock.id != id || lock.valid(&now)); + locks.push(BalanceLock { id, amount, reasons }); + }); } - fn remove_lock(id: Self::Id, who: &T::AccountId) { - // let now = >::now(); - // >::mutate(who, |locks| { - // // unexpired and mismatched id -> keep - // locks.retain(|lock| (lock.until > now) && (lock.id != id)); - // }); + fn remove_lock(who: &T::AccountId, id: Self::Id) { + let now = >::now(); + >::mutate(who, |locks| { + // unexpired and mismatched id -> keep + locks.retain(|lock| lock.valid(&now) && lock.id != id); + }); } - fn count() -> u32 { - unimplemented!() + fn locks_count(who: &T::AccountId) -> u32 { + >::get(&who).len() as _ } } diff --git a/srml/kton/src/tests.rs b/srml/kton/src/tests.rs index de1ebabd8..bd311c832 100644 --- a/srml/kton/src/tests.rs +++ b/srml/kton/src/tests.rs @@ -6,275 +6,272 @@ use srml_support::{ use super::*; use crate::mock::*; -const ID_1: LockIdentifier = *b"1 "; -const ID_2: LockIdentifier = *b"2 "; -const ID_3: LockIdentifier = *b"3 "; - -#[test] -fn transfer_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Kton::deposit_creating(&666, 100); - - assert_ok!(Kton::transfer(Origin::signed(666), 777, 50)); - assert_eq!(Kton::total_balance(&666), 50); - assert_eq!(Kton::total_balance(&777), 50); - - assert_ok!(Kton::transfer(Origin::signed(666), 777, 50)); - assert_eq!(Kton::total_balance(&666), 0); - assert_eq!(Kton::total_balance(&777), 100); - - assert_ok!(Kton::transfer(Origin::signed(666), 777, 0)); - }); -} - -#[test] -fn transfer_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Kton::deposit_creating(&777, 1); - assert_err!( - Kton::transfer(Origin::signed(666), 777, 50), - "balance too low to send value" - ); - - let _ = Kton::deposit_creating(&666, u64::max_value()); - assert_err!( - Kton::transfer(Origin::signed(777), 666, 1), - "destination balance too high to receive value" - ); - - assert_err!( - Kton::transfer(Origin::signed(1), 777, Kton::vesting_balance(&1)), - "vesting balance too high to send value" - ); - - Kton::set_lock(ID_1, &777, 1, u64::max_value(), WithdrawReasons::all()); - assert_err!( - Kton::transfer(Origin::signed(777), 1, 1), - "account liquidity restrictions prevent withdrawal" - ); - }); -} - -#[test] -fn set_lock_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let lock_ids = [[0; 8], [1; 8], [2; 8], [3; 8]]; - let balance_per_lock = Kton::free_balance(&1) / (lock_ids.len() as u64); - - // account `1`'s vesting length - System::set_block_number(4); - - { - let mut locks = vec![]; - for lock_id in lock_ids.iter() { - Kton::set_lock(*lock_id, &1, balance_per_lock, u64::max_value(), WithdrawReasons::all()); - locks.push(BalanceLock { - id: *lock_id, - amount: balance_per_lock, - until: u64::max_value(), - reasons: WithdrawReasons::all(), - }); - assert_eq!(Kton::locks(&1), locks); - } - } - - for _ in 0..lock_ids.len() - 1 { - assert_ok!(Kton::transfer(Origin::signed(1), 2, balance_per_lock)); - } - assert_err!( - Kton::transfer(Origin::signed(1), 2, balance_per_lock), - "account liquidity restrictions prevent withdrawal" - ); - }); -} - -#[test] -fn remove_lock_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Timestamp::set_timestamp(0); - let ts: u64 = Timestamp::now().into(); - - Kton::set_lock(ID_1, &2, u64::max_value(), u64::max_value(), WithdrawReasons::all()); - assert_err!( - Kton::transfer(Origin::signed(2), 1, 1), - "account liquidity restrictions prevent withdrawal" - ); - - // unexpired - Kton::set_lock(ID_2, &2, u64::max_value(), ts + 1, WithdrawReasons::all()); - Kton::remove_lock(ID_1, &2); - Timestamp::set_timestamp(ts); - assert_err!( - Kton::transfer(Origin::signed(2), 1, 1), - "account liquidity restrictions prevent withdrawal" - ); - Kton::remove_lock(ID_2, &2); - assert_ok!(Kton::transfer(Origin::signed(2), 1, 1)); - - // expired - Kton::set_lock(ID_3, &2, u64::max_value(), ts, WithdrawReasons::all()); - assert_ok!(Kton::transfer(Origin::signed(2), 1, 1)); - }); -} - -#[test] -fn update_lock_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let mut locks = vec![]; - for id in 0..10 { - // until > 1 - locks.push(BalanceLock { - id: [id; 8], - amount: 1, - until: 2, - reasons: WithdrawReasons::none(), - }); - Kton::set_lock([id; 8], &1, 1, 2, WithdrawReasons::none()); - } - let update_id = 4; - for amount in 32767u64..65535 { - let until = amount + 1; - locks[update_id as usize] = BalanceLock { - id: [update_id; 8], - amount, - until, - reasons: WithdrawReasons::all(), - }; - Kton::set_lock([update_id; 8], &1, amount, until, WithdrawReasons::all()); - assert_eq!(Kton::locks(&1), locks); - } - }); -} - -#[test] -fn combination_locking_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Kton::deposit_creating(&1001, 10); - Kton::set_lock(ID_1, &1001, u64::max_value(), 0, WithdrawReasons::none()); - Kton::set_lock(ID_2, &1001, 0, u64::max_value(), WithdrawReasons::none()); - Kton::set_lock(ID_3, &1001, 0, 0, WithdrawReasons::all()); - assert_ok!(Kton::transfer(Origin::signed(1001), 1002, 1)); - }); -} - -#[test] -fn extend_lock_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let mut locks = vec![]; - { - let amount = 1; - let until = 2; - let reasons = WithdrawReasons::none(); - for will_be_extended_id in 0..5 { - locks.push(BalanceLock { - id: [will_be_extended_id; 8], - amount, - until, - reasons, - }); - Kton::set_lock([will_be_extended_id; 8], &1, amount, until, reasons); - } - } - { - let amount = 100; - let until = 100; - let reasons = WithdrawReasons::all(); - for will_not_be_extended_id in 5..10 { - locks.push(BalanceLock { - id: [will_not_be_extended_id; 8], - amount, - until, - reasons, - }); - Kton::set_lock([will_not_be_extended_id; 8], &1, amount, until, reasons); - } - } - { - let new_amount = 50; - let new_until = 50; - let new_reasons = WithdrawReason::Transfer.into(); - for lock in locks.iter_mut() { - let BalanceLock { - id, - amount, - until, - reasons, - } = lock; - if *amount < new_amount { - *amount = new_amount; - *until = new_until; - *reasons = new_reasons; - } - Kton::extend_lock(*id, &1, new_amount, new_until, new_reasons); - } - assert_eq!(Kton::locks(&1), locks); - } - }); -} - -#[test] -fn lock_block_number_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Kton::deposit_creating(&1001, 10); - - for &until in [1, 2, 3, 4].iter() { - Timestamp::set_timestamp(0); - - Kton::set_lock(ID_1, &1001, 10, until, WithdrawReasons::all()); - assert_noop!( - Kton::transfer(Origin::signed(1001), 1002, 1), - "account liquidity restrictions prevent withdrawal" - ); - - Timestamp::set_timestamp(until); - assert_ok!(Kton::transfer(Origin::signed(1001), 1002, 1)); - } - }); -} - -#[test] -fn lock_block_number_extension_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Kton::deposit_creating(&1001, 10); - Kton::set_lock(ID_1, &1001, 10, 2, WithdrawReasons::all()); - assert_noop!( - Kton::transfer(Origin::signed(1001), 1002, 6), - "account liquidity restrictions prevent withdrawal" - ); - Kton::extend_lock(ID_1, &1001, 10, 1, WithdrawReasons::all()); - assert_noop!( - Kton::transfer(Origin::signed(1001), 1002, 6), - "account liquidity restrictions prevent withdrawal" - ); - - let until = 8; - Timestamp::set_timestamp(until - 1); - Kton::extend_lock(ID_1, &1001, 10, until, WithdrawReasons::all()); - assert_noop!( - Kton::transfer(Origin::signed(1001), 1002, 3), - "account liquidity restrictions prevent withdrawal" - ); - }); -} - -#[test] -fn lock_reasons_extension_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Kton::deposit_creating(&1001, 10); - Kton::set_lock(ID_1, &1001, 10, 10, WithdrawReason::Transfer.into()); - assert_noop!( - Kton::transfer(Origin::signed(1001), 1002, 6), - "account liquidity restrictions prevent withdrawal" - ); - Kton::extend_lock(ID_1, &1001, 10, 10, WithdrawReasons::none()); - assert_noop!( - Kton::transfer(Origin::signed(1001), 1002, 6), - "account liquidity restrictions prevent withdrawal" - ); - Kton::extend_lock(ID_1, &1001, 10, 10, WithdrawReason::Reserve.into()); - assert_noop!( - Kton::transfer(Origin::signed(1001), 1002, 6), - "account liquidity restrictions prevent withdrawal" - ); - }); -} +// TODO +//#[test] +//fn transfer_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Kton::deposit_creating(&666, 100); +// +// assert_ok!(Kton::transfer(Origin::signed(666), 777, 50)); +// assert_eq!(Kton::total_balance(&666), 50); +// assert_eq!(Kton::total_balance(&777), 50); +// +// assert_ok!(Kton::transfer(Origin::signed(666), 777, 50)); +// assert_eq!(Kton::total_balance(&666), 0); +// assert_eq!(Kton::total_balance(&777), 100); +// +// assert_ok!(Kton::transfer(Origin::signed(666), 777, 0)); +// }); +//} +// +//#[test] +//fn transfer_should_fail() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Kton::deposit_creating(&777, 1); +// assert_err!( +// Kton::transfer(Origin::signed(666), 777, 50), +// "balance too low to send value" +// ); +// +// let _ = Kton::deposit_creating(&666, u64::max_value()); +// assert_err!( +// Kton::transfer(Origin::signed(777), 666, 1), +// "destination balance too high to receive value" +// ); +// +// assert_err!( +// Kton::transfer(Origin::signed(1), 777, Kton::vesting_balance(&1)), +// "vesting balance too high to send value" +// ); +// +// Kton::set_lock(ID_1, &777, 1, u64::max_value(), WithdrawReasons::all()); +// assert_err!( +// Kton::transfer(Origin::signed(777), 1, 1), +// "account liquidity restrictions prevent withdrawal" +// ); +// }); +//} +// +//#[test] +//fn set_lock_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let lock_ids = [[0; 8], [1; 8], [2; 8], [3; 8]]; +// let balance_per_lock = Kton::free_balance(&1) / (lock_ids.len() as u64); +// +// // account `1`'s vesting length +// System::set_block_number(4); +// +// { +// let mut locks = vec![]; +// for lock_id in lock_ids.iter() { +// Kton::set_lock(*lock_id, &1, balance_per_lock, u64::max_value(), WithdrawReasons::all()); +// locks.push(BalanceLock { +// id: *lock_id, +// amount: balance_per_lock, +// until: u64::max_value(), +// reasons: WithdrawReasons::all(), +// }); +// assert_eq!(Kton::locks(&1), locks); +// } +// } +// +// for _ in 0..lock_ids.len() - 1 { +// assert_ok!(Kton::transfer(Origin::signed(1), 2, balance_per_lock)); +// } +// assert_err!( +// Kton::transfer(Origin::signed(1), 2, balance_per_lock), +// "account liquidity restrictions prevent withdrawal" +// ); +// }); +//} +// +//#[test] +//fn remove_lock_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Timestamp::set_timestamp(0); +// let ts: u64 = Timestamp::now().into(); +// +// Kton::set_lock(ID_1, &2, u64::max_value(), u64::max_value(), WithdrawReasons::all()); +// assert_err!( +// Kton::transfer(Origin::signed(2), 1, 1), +// "account liquidity restrictions prevent withdrawal" +// ); +// +// // unexpired +// Kton::set_lock(ID_2, &2, u64::max_value(), ts + 1, WithdrawReasons::all()); +// Kton::remove_lock(ID_1, &2); +// Timestamp::set_timestamp(ts); +// assert_err!( +// Kton::transfer(Origin::signed(2), 1, 1), +// "account liquidity restrictions prevent withdrawal" +// ); +// Kton::remove_lock(ID_2, &2); +// assert_ok!(Kton::transfer(Origin::signed(2), 1, 1)); +// +// // expired +// Kton::set_lock(ID_3, &2, u64::max_value(), ts, WithdrawReasons::all()); +// assert_ok!(Kton::transfer(Origin::signed(2), 1, 1)); +// }); +//} +// +//#[test] +//fn update_lock_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let mut locks = vec![]; +// for id in 0..10 { +// // until > 1 +// locks.push(BalanceLock { +// id: [id; 8], +// amount: 1, +// until: 2, +// reasons: WithdrawReasons::none(), +// }); +// Kton::set_lock([id; 8], &1, 1, 2, WithdrawReasons::none()); +// } +// let update_id = 4; +// for amount in 32767u64..65535 { +// let until = amount + 1; +// locks[update_id as usize] = BalanceLock { +// id: [update_id; 8], +// amount, +// until, +// reasons: WithdrawReasons::all(), +// }; +// Kton::set_lock([update_id; 8], &1, amount, until, WithdrawReasons::all()); +// assert_eq!(Kton::locks(&1), locks); +// } +// }); +//} +// +//#[test] +//fn combination_locking_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Kton::deposit_creating(&1001, 10); +// Kton::set_lock(ID_1, &1001, u64::max_value(), 0, WithdrawReasons::none()); +// Kton::set_lock(ID_2, &1001, 0, u64::max_value(), WithdrawReasons::none()); +// Kton::set_lock(ID_3, &1001, 0, 0, WithdrawReasons::all()); +// assert_ok!(Kton::transfer(Origin::signed(1001), 1002, 1)); +// }); +//} +// +//#[test] +//fn extend_lock_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let mut locks = vec![]; +// { +// let amount = 1; +// let until = 2; +// let reasons = WithdrawReasons::none(); +// for will_be_extended_id in 0..5 { +// locks.push(BalanceLock { +// id: [will_be_extended_id; 8], +// amount, +// until, +// reasons, +// }); +// Kton::set_lock([will_be_extended_id; 8], &1, amount, until, reasons); +// } +// } +// { +// let amount = 100; +// let until = 100; +// let reasons = WithdrawReasons::all(); +// for will_not_be_extended_id in 5..10 { +// locks.push(BalanceLock { +// id: [will_not_be_extended_id; 8], +// amount, +// until, +// reasons, +// }); +// Kton::set_lock([will_not_be_extended_id; 8], &1, amount, until, reasons); +// } +// } +// { +// let new_amount = 50; +// let new_until = 50; +// let new_reasons = WithdrawReason::Transfer.into(); +// for lock in locks.iter_mut() { +// let BalanceLock { +// id, +// amount, +// until, +// reasons, +// } = lock; +// if *amount < new_amount { +// *amount = new_amount; +// *until = new_until; +// *reasons = new_reasons; +// } +// Kton::extend_lock(*id, &1, new_amount, new_until, new_reasons); +// } +// assert_eq!(Kton::locks(&1), locks); +// } +// }); +//} +// +//#[test] +//fn lock_block_number_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Kton::deposit_creating(&1001, 10); +// +// for &until in [1, 2, 3, 4].iter() { +// Timestamp::set_timestamp(0); +// +// Kton::set_lock(ID_1, &1001, 10, until, WithdrawReasons::all()); +// assert_noop!( +// Kton::transfer(Origin::signed(1001), 1002, 1), +// "account liquidity restrictions prevent withdrawal" +// ); +// +// Timestamp::set_timestamp(until); +// assert_ok!(Kton::transfer(Origin::signed(1001), 1002, 1)); +// } +// }); +//} +// +//#[test] +//fn lock_block_number_extension_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Kton::deposit_creating(&1001, 10); +// Kton::set_lock(ID_1, &1001, 10, 2, WithdrawReasons::all()); +// assert_noop!( +// Kton::transfer(Origin::signed(1001), 1002, 6), +// "account liquidity restrictions prevent withdrawal" +// ); +// Kton::extend_lock(ID_1, &1001, 10, 1, WithdrawReasons::all()); +// assert_noop!( +// Kton::transfer(Origin::signed(1001), 1002, 6), +// "account liquidity restrictions prevent withdrawal" +// ); +// +// let until = 8; +// Timestamp::set_timestamp(until - 1); +// Kton::extend_lock(ID_1, &1001, 10, until, WithdrawReasons::all()); +// assert_noop!( +// Kton::transfer(Origin::signed(1001), 1002, 3), +// "account liquidity restrictions prevent withdrawal" +// ); +// }); +//} +// +//#[test] +//fn lock_reasons_extension_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Kton::deposit_creating(&1001, 10); +// Kton::set_lock(ID_1, &1001, 10, 10, WithdrawReason::Transfer.into()); +// assert_noop!( +// Kton::transfer(Origin::signed(1001), 1002, 6), +// "account liquidity restrictions prevent withdrawal" +// ); +// Kton::extend_lock(ID_1, &1001, 10, 10, WithdrawReasons::none()); +// assert_noop!( +// Kton::transfer(Origin::signed(1001), 1002, 6), +// "account liquidity restrictions prevent withdrawal" +// ); +// Kton::extend_lock(ID_1, &1001, 10, 10, WithdrawReason::Reserve.into()); +// assert_noop!( +// Kton::transfer(Origin::signed(1001), 1002, 6), +// "account liquidity restrictions prevent withdrawal" +// ); +// }); +//} diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 61a1649dd..a399f664c 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -60,7 +60,7 @@ const RECENT_OFFLINE_COUNT: usize = 32; const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; const MAX_NOMINATIONS: usize = 16; const MAX_UNSTAKE_THRESHOLD: u32 = 10; -const MAX_UNLOCKING_CHUNKS: usize = 32; +const MAX_UNLOCKING_CHUNKS: u32 = 32; const MONTH_IN_SECONDS: u32 = 2_592_000; /// Counter for the number of eras that have passed. @@ -431,11 +431,10 @@ decl_module! { .. } = &mut ledger; -// TODO -// ensure!( -// unlocking.len() < MAX_UNLOCKING_CHUNKS, -// "can not schedule more unlock chunks" -// ); + ensure!( + T::Ring::locks_count(stash) + T::Kton::locks_count(stash) < MAX_UNLOCKING_CHUNKS, + "can not schedule more unlock chunks" + ); let until = >::now().saturated_into::() + T::BondingDuration::get(); diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 2f3d922a0..52214f823 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -77,7 +77,6 @@ fn test_env_build() { Some(StakingLedgers { stash: 11, total_ring: 100 * COIN, - total_deposit_ring: 100 * COIN, active_deposit_ring: 100 * COIN, active_ring: 100 * COIN, total_kton: 0, @@ -87,7 +86,6 @@ fn test_env_build() { start_time: 0, expire_time: 12 * MONTH_IN_SECONDS as u64 }], - unlocking: vec![] }) ); @@ -106,7 +104,6 @@ fn test_env_build() { Some(StakingLedgers { stash: 11, total_ring: origin_ledger.total_ring + 20 * COIN, - total_deposit_ring: origin_ledger.total_deposit_ring + 20 * COIN, active_deposit_ring: origin_ledger.active_deposit_ring + 20 * COIN, active_ring: origin_ledger.active_ring + 20 * COIN, total_kton: 0, @@ -123,1088 +120,1094 @@ fn test_env_build() { expire_time: 13 * MONTH_IN_SECONDS as u64 } ], - unlocking: vec![] }) ); }); } -#[test] -fn normal_kton_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Kton::deposit_creating(&1001, 10 * COIN); - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Kton(10 * COIN), - RewardDestination::Stash, - 0 - )); - assert_eq!( - Staking::ledger(&1000), - Some(StakingLedgers { - stash: 1001, - total_ring: 0, - total_deposit_ring: 0, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 10 * COIN, - active_kton: 10 * COIN, - deposit_items: vec![], - unlocking: vec![] - }) - ); - - assert_eq!( - Kton::locks(&1001), - vec![kton::BalanceLock { - id: STAKING_ID, - amount: 10 * COIN, - until: u64::max_value(), - reasons: WithdrawReasons::all() - }] - ); - - // promise_month should not work for kton - Kton::deposit_creating(&2001, 10 * COIN); - assert_ok!(Staking::bond( - Origin::signed(2001), - 2000, - StakingBalance::Kton(10 * COIN), - RewardDestination::Stash, - 12 - )); - assert_eq!( - Staking::ledger(&2000), - Some(StakingLedgers { - stash: 2001, - total_ring: 0, - total_deposit_ring: 0, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 10 * COIN, - active_kton: 10 * COIN, - deposit_items: vec![], - unlocking: vec![] - }) - ); - }); -} - -#[test] -fn time_deposit_ring_unbond_and_withdraw_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - Timestamp::set_timestamp(13 * MONTH_IN_SECONDS as u64); - - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 100 * COIN, - total_deposit_ring: 100 * COIN, - active_deposit_ring: 90 * COIN, - active_ring: 90 * COIN, - total_kton: 0, - active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 90 * COIN, - start_time: 0, - expire_time: 12 * MONTH_IN_SECONDS as u64 - }], - unlocking: vec![UnlockChunk { - value: StakingBalance::Ring(10 * COIN), - era: 3, - is_time_deposit: true - }] - }) - ); - - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(20 * COIN))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 100 * COIN, - total_deposit_ring: 100 * COIN, - active_deposit_ring: 70 * COIN, - active_ring: 70 * COIN, - total_kton: 0, - active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 70 * COIN, - start_time: 0, - expire_time: 12 * MONTH_IN_SECONDS as u64 - }], - unlocking: vec![ - UnlockChunk { - value: StakingBalance::Ring(10 * COIN), - era: 3, - is_time_deposit: true - }, - UnlockChunk { - value: StakingBalance::Ring(20 * COIN), - era: 3, - is_time_deposit: true - } - ] - }) - ); - - // more than active ring - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(120 * COIN))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 100 * COIN, - total_deposit_ring: 100 * COIN, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 0, - active_kton: 0, - deposit_items: vec![], // should be cleared - unlocking: vec![ - UnlockChunk { - value: StakingBalance::Ring(10 * COIN), - era: 3, - is_time_deposit: true - }, - UnlockChunk { - value: StakingBalance::Ring(20 * COIN), - era: 3, - is_time_deposit: true - }, - UnlockChunk { - value: StakingBalance::Ring(70 * COIN), - era: 3, - is_time_deposit: true - }, - ] - }) - ); - - start_era(3); - - assert_ok!(Staking::withdraw_unbonded(Origin::signed(10))); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedgers { - stash: 11, - total_ring: 0, - total_deposit_ring: 0, - active_deposit_ring: 0, - active_ring: 0, - total_kton: 0, - active_kton: 0, - deposit_items: vec![], // should be cleared - unlocking: vec![] - }) - ); - - let free_balance = Ring::free_balance(&11); - assert_eq!( - Ring::locks(&11), - vec![balances::BalanceLock { - id: STAKING_ID, - amount: 0, - until: u64::max_value(), - reasons: WithdrawReasons::all() - }] - ); - assert_ok!(Ring::ensure_can_withdraw( - &11, - free_balance, - WithdrawReason::Transfer, - 0 - )); - }); -} +//#[test] +//fn normal_kton_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Kton::deposit_creating(&1001, 10 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Kton(10 * COIN), +// RewardDestination::Stash, +// 0 +// )); +// assert_eq!( +// Staking::ledger(&1000), +// Some(StakingLedgers { +// stash: 1001, +// total_ring: 0, +// total_deposit_ring: 0, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 10 * COIN, +// active_kton: 10 * COIN, +// deposit_items: vec![], +// unlocking: vec![] +// }) +// ); +// +// assert_eq!( +// Kton::locks(&1001), +// vec![kton::BalanceLock { +// id: STAKING_ID, +// amount: 10 * COIN, +// until: u64::max_value(), +// reasons: WithdrawReasons::all() +// }] +// ); +// +// // promise_month should not work for kton +// Kton::deposit_creating(&2001, 10 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(2001), +// 2000, +// StakingBalance::Kton(10 * COIN), +// RewardDestination::Stash, +// 12 +// )); +// assert_eq!( +// Staking::ledger(&2000), +// Some(StakingLedgers { +// stash: 2001, +// total_ring: 0, +// total_deposit_ring: 0, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 10 * COIN, +// active_kton: 10 * COIN, +// deposit_items: vec![], +// unlocking: vec![] +// }) +// ); +// }); +//} +// +//#[test] +//fn time_deposit_ring_unbond_and_withdraw_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// Timestamp::set_timestamp(13 * MONTH_IN_SECONDS as u64); +// +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 100 * COIN, +// total_deposit_ring: 100 * COIN, +// active_deposit_ring: 90 * COIN, +// active_ring: 90 * COIN, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![TimeDepositItem { +// value: 90 * COIN, +// start_time: 0, +// expire_time: 12 * MONTH_IN_SECONDS as u64 +// }], +// unlocking: vec![UnlockChunk { +// value: StakingBalance::Ring(10 * COIN), +// era: 3, +// is_time_deposit: true +// }] +// }) +// ); +// +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(20 * COIN))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 100 * COIN, +// total_deposit_ring: 100 * COIN, +// active_deposit_ring: 70 * COIN, +// active_ring: 70 * COIN, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![TimeDepositItem { +// value: 70 * COIN, +// start_time: 0, +// expire_time: 12 * MONTH_IN_SECONDS as u64 +// }], +// unlocking: vec![ +// UnlockChunk { +// value: StakingBalance::Ring(10 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// UnlockChunk { +// value: StakingBalance::Ring(20 * COIN), +// era: 3, +// is_time_deposit: true +// } +// ] +// }) +// ); +// +// // more than active ring +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(120 * COIN))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 100 * COIN, +// total_deposit_ring: 100 * COIN, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![], // should be cleared +// unlocking: vec![ +// UnlockChunk { +// value: StakingBalance::Ring(10 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// UnlockChunk { +// value: StakingBalance::Ring(20 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// UnlockChunk { +// value: StakingBalance::Ring(70 * COIN), +// era: 3, +// is_time_deposit: true +// }, +// ] +// }) +// ); +// +// start_era(3); +// +// assert_ok!(Staking::withdraw_unbonded(Origin::signed(10))); +// assert_eq!( +// Staking::ledger(&10), +// Some(StakingLedgers { +// stash: 11, +// total_ring: 0, +// total_deposit_ring: 0, +// active_deposit_ring: 0, +// active_ring: 0, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![], // should be cleared +// unlocking: vec![] +// }) +// ); +// +// let free_balance = Ring::free_balance(&11); +// assert_eq!( +// Ring::locks(&11), +// vec![balances::BalanceLock { +// id: STAKING_ID, +// amount: 0, +// until: u64::max_value(), +// reasons: WithdrawReasons::all() +// }] +// ); +// assert_ok!(Ring::ensure_can_withdraw( +// &11, +// free_balance, +// WithdrawReason::Transfer, +// 0 +// )); +// }); +//} +// +//#[test] +//fn normal_unbond_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 11; +// let controller = 10; +// let value = 200 * COIN; +// let promise_month = 12; +// // unbond normal ring +// let _ = Ring::deposit_creating(&stash, 1000 * COIN); +// +// { +// let kton_free_balance = Kton::free_balance(&stash); +// let mut ledger = Staking::ledger(&controller).unwrap(); +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(value), +// promise_month, +// )); +// assert_eq!( +// Kton::free_balance(&stash), +// kton_free_balance + utils::compute_kton_return::(value, promise_month) +// ); +// ledger.total_ring += value; +// ledger.total_deposit_ring += value; +// ledger.active_ring += value; +// ledger.active_deposit_ring += value; +// ledger.deposit_items.push(TimeDepositItem { +// value: value, +// start_time: 0, +// expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// } +// +// { +// let kton_free_balance = Kton::free_balance(&stash); +// let mut ledger = Staking::ledger(&controller).unwrap(); +// +// // we try to bond 1 kton, but stash only has 0.03 Kton +// // extra = 1.min(0.03) +// // bond += 0.03 +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Kton(COIN), +// 0 +// )); +// ledger.total_kton += kton_free_balance; +// ledger.active_kton += kton_free_balance; +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// +// assert_ok!(Staking::unbond( +// Origin::signed(controller), +// StakingBalance::Kton(kton_free_balance) +// )); +// ledger.active_kton = 0; +// ledger.unlocking = vec![UnlockChunk { +// value: StakingBalance::Kton(kton_free_balance), +// era: 3, +// is_time_deposit: false, +// }]; +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// } +// }); +//} +// +//#[test] +//fn punished_unbond_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 1001; +// let controller = 1000; +// let promise_month = 36; +// +// let _ = Ring::deposit_creating(&stash, 100 * COIN); +// Kton::deposit_creating(&stash, COIN / 100000); +// +// // timestamp now is 0. +// // free balance of kton is too low to work +// assert_ok!(Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(10 * COIN), +// RewardDestination::Stash, +// promise_month +// )); +// assert_eq!( +// Staking::ledger(&controller), +// Some(StakingLedgers { +// stash, +// total_ring: 10 * COIN, +// total_deposit_ring: 10 * COIN, +// active_deposit_ring: 10 * COIN, +// active_ring: 10 * COIN, +// total_kton: 0, +// active_kton: 0, +// deposit_items: vec![TimeDepositItem { +// value: 10 * COIN, +// start_time: 0, +// expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64 +// }], // should be cleared +// unlocking: vec![] +// }) +// ); +// let mut ledger = Staking::ledger(&controller).unwrap(); +// let kton_free_balance = Kton::free_balance(&stash); +// // kton is 0, skip unbond_with_punish +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 10 * COIN, +// promise_month as u64 * MONTH_IN_SECONDS as u64 +// )); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// assert_eq!(Kton::free_balance(&stash), kton_free_balance); +// +// // set more kton balance to make it work +// Kton::deposit_creating(&stash, 10 * COIN); +// let kton_free_balance = Kton::free_balance(&stash); +// let unbond_value = 5 * COIN; +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// unbond_value, +// promise_month as u64 * MONTH_IN_SECONDS as u64 +// )); +// ledger.active_ring -= unbond_value; +// ledger.active_deposit_ring -= unbond_value; +// ledger.deposit_items[0].value -= unbond_value; +// ledger.unlocking = vec![UnlockChunk { +// value: StakingBalance::Ring(unbond_value), +// era: 3, +// is_time_deposit: true, +// }]; +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// +// let kton_punishment = utils::compute_kton_return::(unbond_value, promise_month); +// assert_eq!(Kton::free_balance(&stash), kton_free_balance - 3 * kton_punishment); +// +// // if deposit_item.value == 0 +// // the whole item should be be dropped +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 5 * COIN, +// promise_month as u64 * MONTH_IN_SECONDS as u64 +// )); +// assert!(Staking::ledger(&controller).unwrap().deposit_items.is_empty()); +// }); +//} +// +//#[test] +//fn transform_to_promised_ring_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Ring::deposit_creating(&1001, 100 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(10 * COIN), +// RewardDestination::Stash, +// 0 +// )); +// let origin_ledger = Staking::ledger(&1000).unwrap(); +// let kton_free_balance = Kton::free_balance(&1001); +// +// assert_ok!(Staking::promise_extra(Origin::signed(1000), 5 * COIN, 12)); +// +// assert_eq!( +// Staking::ledger(&1000), +// Some(StakingLedgers { +// stash: 1001, +// total_ring: origin_ledger.total_ring, +// total_deposit_ring: origin_ledger.total_deposit_ring + 5 * COIN, +// active_deposit_ring: origin_ledger.active_deposit_ring + 5 * COIN, +// active_ring: origin_ledger.active_ring, +// total_kton: origin_ledger.total_kton, +// active_kton: origin_ledger.active_kton, +// deposit_items: vec![TimeDepositItem { +// value: 5 * COIN, +// start_time: 0, +// expire_time: 12 * MONTH_IN_SECONDS as u64 +// }], +// unlocking: vec![] +// }) +// ); +// +// assert_eq!(Kton::free_balance(&1001), kton_free_balance + (5 * COIN / 10000)); +// }); +//} +// +//#[test] +//fn expired_ring_should_capable_to_promise_again() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Ring::deposit_creating(&1001, 100 * COIN); +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(10 * COIN), +// RewardDestination::Stash, +// 12 +// )); +// let mut ledger = Staking::ledger(&1000).unwrap(); +// let ts = 13 * MONTH_IN_SECONDS as u64; +// let promise_extra_value = 5 * COIN; +// Timestamp::set_timestamp(ts); +// assert_ok!(Staking::promise_extra(Origin::signed(1000), promise_extra_value, 13)); +// ledger.total_deposit_ring = promise_extra_value; +// ledger.active_deposit_ring = promise_extra_value; +// // old deposit_item with 12 months promised removed +// ledger.deposit_items = vec![TimeDepositItem { +// value: promise_extra_value, +// start_time: ts, +// expire_time: 2 * ts, +// }]; +// assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); +// }); +//} +// +//#[test] +//fn inflation_should_be_correct() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let initial_issuance = 1_200_000_000 * COIN; +// let surplus_needed = initial_issuance - Ring::total_issuance(); +// let _ = Ring::deposit_into_existing(&11, surplus_needed); +// assert_eq!(Ring::total_issuance(), initial_issuance); +// assert_eq!(Staking::current_era_total_reward(), 80000000 * COIN / 10); +// start_era(11); +// // ErasPerEpoch = 10 +// assert_eq!(Staking::current_era_total_reward(), 88000000 * COIN / 10); +// }); +//} +// +//#[test] +//fn reward_should_work_correctly() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// // create controller account +// let _ = Ring::deposit_creating(&2000, COIN); +// let _ = Ring::deposit_creating(&1000, COIN); +// let _ = Ring::deposit_creating(&200, COIN); +// // new validator +// let _ = Ring::deposit_creating(&2001, 2000 * COIN); +// Kton::deposit_creating(&2001, 10 * COIN); +// // new validator +// let _ = Ring::deposit_creating(&1001, 300 * COIN); +// Kton::deposit_creating(&1001, 1 * COIN); +// // handle some dirty work +// let _ = Ring::deposit_creating(&201, 2000 * COIN); +// Kton::deposit_creating(&201, 10 * COIN); +// assert_eq!(Kton::free_balance(&201), 10 * COIN); +// +// // 2001-2000 +// assert_ok!(Staking::bond( +// Origin::signed(2001), +// 2000, +// StakingBalance::Ring(300 * COIN), +// RewardDestination::Controller, +// 12, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(2001), +// StakingBalance::Kton(1 * COIN), +// 0 +// )); +// // 1001-1000 +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(300 * COIN), +// RewardDestination::Controller, +// 12, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(1001), +// StakingBalance::Kton(1 * COIN), +// 0 +// )); +// let ring_pool = Staking::ring_pool(); +// let kton_pool = Staking::kton_pool(); +// // 201-200 +// assert_ok!(Staking::bond( +// Origin::signed(201), +// 200, +// StakingBalance::Ring(3000 * COIN - ring_pool), +// RewardDestination::Stash, +// 12, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(201), +// StakingBalance::Kton(10 * COIN - kton_pool), +// 0, +// )); +// // ring_pool and kton_pool +// assert_eq!(Staking::ring_pool(), 3000 * COIN); +// assert_eq!(Staking::kton_pool(), 10 * COIN); +// // 1/5 ring_pool and 1/5 kton_pool +// assert_ok!(Staking::validate(Origin::signed(2000), [0; 8].to_vec(), 0, 3)); +// assert_ok!(Staking::nominate(Origin::signed(1000), vec![2001])); +// +// assert_eq!(Staking::ledger(&2000).unwrap().active_kton, 1 * COIN); +// assert_eq!(Staking::ledger(&2000).unwrap().active_ring, 300 * COIN); +// assert_eq!(Staking::power_of(&2001), 1_000_000_000 / 10 as u128); +// // 600COIN for rewarding ring bond-er +// // 600COIN for rewarding kton bond-er +// Staking::select_validators(); +// Staking::reward_validator(&2001, 1200 * COIN); +// +// assert_eq!( +// Staking::stakers(2001), +// Exposure { +// total: 1200000000000, +// own: 600000000000, +// others: vec![IndividualExposure { +// who: 1001, +// value: 600000000000 +// }] +// } +// ); +// assert_eq!(Ring::free_balance(&2000), 601 * COIN); +// assert_eq!(Ring::free_balance(&1000), 601 * COIN); +// }); +//} +// +//#[test] +//fn slash_should_work() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let _ = Ring::deposit_creating(&1001, 100 * COIN); +// Kton::deposit_creating(&1001, 100 * COIN); +// +// assert_ok!(Staking::bond( +// Origin::signed(1001), +// 1000, +// StakingBalance::Ring(50 * COIN), +// RewardDestination::Controller, +// 0, +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(1001), +// StakingBalance::Kton(50 * COIN), +// 0 +// )); +// assert_ok!(Staking::validate(Origin::signed(1000), [0; 8].to_vec(), 0, 3)); +// +// // slash 1% +// let slash_value = 5 * COIN / 10; +// let mut ledger = Staking::ledger(&1000).unwrap(); +// let ring_free_balance = Ring::free_balance(&1001); +// let kton_free_balance = Kton::free_balance(&1001); +// Staking::slash_validator(&1001, 10_000_000); +// ledger.total_ring -= slash_value; +// ledger.active_ring -= slash_value; +// ledger.total_kton -= slash_value; +// ledger.active_kton -= slash_value; +// assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); +// assert_eq!(Ring::free_balance(&1001), ring_free_balance - slash_value); +// assert_eq!(Kton::free_balance(&1001), kton_free_balance - slash_value); +// }); +//} +// +//#[test] +//fn test_inflation() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// assert_eq!(Staking::current_era_total_reward(), 80_000_000 * COIN / 10); +// start_era(20); +// assert_eq!(Staking::epoch_index(), 2); +// assert_eq!(Staking::current_era_total_reward(), 9_999_988_266 * COIN / 1000); +// }); +//} +// +//#[test] +//fn set_controller_should_remove_old_ledger() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 11; +// let old_controller = 10; +// let new_controller = 12; +// +// assert!(Staking::ledger(&old_controller).is_some()); +// assert_eq!(Staking::bonded(&stash), Some(old_controller)); +// +// assert_ok!(Staking::set_controller(Origin::signed(stash), new_controller)); +// assert!(Staking::ledger(&old_controller).is_none()); +// }); +//} +// +//#[test] +//fn set_controller_should_not_change_ledger() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// assert_eq!(Staking::ledger(&10).unwrap().total_ring, 100 * COIN); +// assert_ok!(Staking::set_controller(Origin::signed(11), 12)); +// assert_eq!(Staking::ledger(&12).unwrap().total_ring, 100 * COIN); +// }); +//} +// +//#[test] +//fn slash_should_not_touch_unlockings() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let old_ledger = Staking::ledger(&10).unwrap(); +// // only deposit_ring, no normal_ring +// assert_eq!( +// ( +// old_ledger.total_ring, +// old_ledger.active_ring, +// old_ledger.active_deposit_ring +// ), +// (100 * COIN, 100 * COIN, 100 * COIN) +// ); +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(11), +// StakingBalance::Ring(100 * COIN), +// 0 +// )); +// Kton::deposit_creating(&11, 10 * COIN); +// assert_ok!(Staking::bond_extra( +// Origin::signed(11), +// StakingBalance::Kton(10 * COIN), +// 0 +// )); +// +// assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); +// let new_ledger = Staking::ledger(&10).unwrap(); +// assert_eq!( +// ( +// new_ledger.total_ring, +// new_ledger.active_ring, +// new_ledger.active_deposit_ring +// ), +// (200 * COIN, 190 * COIN, 100 * COIN) +// ); +// +// // slash 100% +// Staking::slash_validator(&11, 1_000_000_000); +// +// let ledger = Staking::ledger(&10).unwrap(); +// assert_eq!( +// (ledger.total_ring, ledger.active_ring, ledger.active_deposit_ring), +// // 10Ring in unlocking +// (10 * COIN, 0, 0) +// ); +// assert_eq!(ledger.unlocking[0].value, StakingBalance::Ring(10 * COIN)); +// }); +//} +// +//#[test] +//fn bond_over_max_promise_month_should_fail() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(stash(123), controller(456)); +// assert_err!( +// Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(COIN), +// RewardDestination::Stash, +// 37 +// ), +// "months at most is 36." +// ); +// +// gen_paired_account!(stash(123), controller(456), promise_month(12)); +// assert_err!( +// Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(COIN), 37), +// "months at most is 36." +// ); +// }); +//} +// +//#[test] +//fn stash_already_bonded_and_controller_already_paired_should_fail() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(unpaired_stash(123), unpaired_controller(456)); +// assert_err!( +// Staking::bond( +// Origin::signed(11), +// unpaired_controller, +// StakingBalance::Ring(COIN), +// RewardDestination::Stash, +// 0 +// ), +// "stash already bonded" +// ); +// assert_err!( +// Staking::bond( +// Origin::signed(unpaired_stash), +// 10, +// StakingBalance::Ring(COIN), +// RewardDestination::Stash, +// 0 +// ), +// "controller already paired" +// ); +// }); +//} +// +//#[test] +//fn pool_should_be_increased_and_decreased_correctly() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let mut ring_pool = Staking::ring_pool(); +// let mut kton_pool = Staking::kton_pool(); +// +// // bond: 100COIN +// gen_paired_account!(stash_1(111), controller_1(222), 0); +// gen_paired_account!(stash_2(333), controller_2(444), promise_month(12)); +// ring_pool += 100 * COIN; +// kton_pool += 100 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// assert_eq!(Staking::kton_pool(), kton_pool); +// +// // unbond: 50Ring 50Kton +// assert_ok!(Staking::unbond( +// Origin::signed(controller_1), +// StakingBalance::Ring(50 * COIN) +// )); +// assert_ok!(Staking::unbond( +// Origin::signed(controller_1), +// StakingBalance::Kton(25 * COIN) +// )); +// // not yet expired: promise for 12 months +// assert_ok!(Staking::unbond( +// Origin::signed(controller_2), +// StakingBalance::Ring(50 * COIN) +// )); +// assert_ok!(Staking::unbond( +// Origin::signed(controller_2), +// StakingBalance::Kton(25 * COIN) +// )); +// ring_pool -= 50 * COIN; +// kton_pool -= 50 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// assert_eq!(Staking::kton_pool(), kton_pool); +// +// // unbond with punish: 12.5Ring +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller_2), +// 125 * COIN / 10, +// promise_month * MONTH_IN_SECONDS as u64 +// )); +// // unbond deposit items: 12.5Ring +// Timestamp::set_timestamp(promise_month * MONTH_IN_SECONDS as u64); +// assert_ok!(Staking::unbond( +// Origin::signed(controller_2), +// StakingBalance::Ring(125 * COIN / 10) +// )); +// ring_pool -= 25 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// +// // slash: 25Ring 50Kton +// Staking::slash_validator(&stash_1, 1_000_000_000); +// Staking::slash_validator(&stash_2, 1_000_000_000); +// ring_pool -= 25 * COIN; +// kton_pool -= 50 * COIN; +// assert_eq!(Staking::ring_pool(), ring_pool); +// assert_eq!(Staking::kton_pool(), kton_pool); +// }); +//} +// +//#[test] +//fn unbond_over_max_unlocking_chunks_should_fail() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(stash(123), controller(456), promise_month(12)); +// let deposit_items_len = MAX_UNLOCKING_CHUNKS + 1; +// +// for _ in 1..deposit_items_len { +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(COIN), +// promise_month +// )); +// } +// { +// let ledger = Staking::ledger(&controller).unwrap(); +// assert_eq!(ledger.deposit_items.len(), deposit_items_len); +// assert_eq!(ledger.unlocking.len(), 0); +// } +// +// Timestamp::set_timestamp(promise_month as u64 * MONTH_IN_SECONDS as u64); +// +// for _ in 1..deposit_items_len { +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); +// } +// { +// let ledger = Staking::ledger(&controller).unwrap(); +// assert_eq!(ledger.deposit_items.len(), 1); +// assert_eq!(ledger.unlocking.len(), deposit_items_len - 1); +// } +// assert_err!( +// Staking::unbond( +// Origin::signed(controller), +// StakingBalance::Ring((deposit_items_len - 1) as u64 * COIN) +// ), +// "can not schedule more unlock chunks" +// ); +// }); +//} +// +//#[test] +//fn unlock_value_should_be_increased_and_decreased_correctly() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// // normal Ring/Kton +// { +// let stash = 444; +// let controller = 555; +// let _ = Ring::deposit_creating(&stash, 100 * COIN); +// Kton::deposit_creating(&stash, 100 * COIN); +// +// assert_ok!(Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(50 * COIN), +// RewardDestination::Stash, +// 0 +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Kton(50 * COIN), +// 0 +// )); +// +// let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; +// +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(COIN), +// era: 3, +// is_time_deposit: false, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(COIN))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Kton(COIN), +// era: 3, +// is_time_deposit: false, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(0))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(0), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(0))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Kton(0), +// era: 3, +// is_time_deposit: false, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// } +// +// // promise Ring +// { +// gen_paired_account!(stash(666), controller(777), promise_month(12)); +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(50 * COIN), +// 36 +// )); +// +// let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; +// +// assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(0), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// for month in [12, 36].iter() { +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 20 * COIN, +// month * MONTH_IN_SECONDS as u64 +// )); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(20 * COIN), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 29 * COIN, +// month * MONTH_IN_SECONDS as u64 +// )); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(29 * COIN), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 50 * COIN, +// month * MONTH_IN_SECONDS as u64 +// )); +// unlocking.push(UnlockChunk { +// value: StakingBalance::Ring(1 * COIN), +// era: 3, +// is_time_deposit: true, +// }); +// assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); +// } +// } +// }); +//} +// +//// #[test] +//// fn total_deposit_should_be_increased_and_decreased_correctly() { +//// with_externalities( +//// &mut ExtBuilder::default().existential_deposit(0).build(), +//// || body, +//// ); +//// } +// +//#[test] +//fn promise_extra_should_not_remove_unexpired_items() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(stash(123), controller(456), promise_month(12)); +// +// let expired_item_len = 3; +// let expiry_date = promise_month as u64 * MONTH_IN_SECONDS as u64; +// +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(5 * COIN), +// 0 +// )); +// for _ in 0..expired_item_len { +// assert_ok!(Staking::promise_extra(Origin::signed(controller), COIN, promise_month)); +// } +// +// Timestamp::set_timestamp(expiry_date - 1); +// assert_ok!(Staking::promise_extra( +// Origin::signed(controller), +// 2 * COIN, +// promise_month +// )); +// assert_eq!( +// Staking::ledger(&controller).unwrap().deposit_items.len(), +// 2 + expired_item_len +// ); +// +// Timestamp::set_timestamp(expiry_date); +// assert_ok!(Staking::promise_extra( +// Origin::signed(controller), +// 2 * COIN, +// promise_month +// )); +// assert_eq!(Staking::ledger(&controller).unwrap().deposit_items.len(), 2); +// }); +//} +// +//#[test] +//fn unbond_zero_before_expiry() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let expiry_date = 12 * MONTH_IN_SECONDS as u64; +// let unbond_value = StakingBalance::Ring(COIN); +// +// Timestamp::set_timestamp(expiry_date - 1); +// assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); +// assert_eq!( +// Staking::ledger(&10).unwrap().unlocking[0].value, +// StakingBalance::Ring(0) +// ); +// +// Timestamp::set_timestamp(expiry_date); +// assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); +// assert_eq!(Staking::ledger(&10).unwrap().unlocking[1].value, unbond_value); +// }); +//} +// +//// bond 10_000 Ring for 12 months, gain 1 Kton +//// bond extra 10_000 Ring for 36 months, gain 3 Kton +//// bond extra 1 Kton +//// nominate +//// unlock the 12 months deposit item with punish +//// lost 3 Kton and 10_000 Ring's power for nominate +//#[test] +//fn yakio_q1() { +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// let stash = 777; +// let controller = 888; +// let _ = Ring::deposit_creating(&stash, 20_000); +// +// assert_ok!(Staking::bond( +// Origin::signed(stash), +// controller, +// StakingBalance::Ring(10_000), +// RewardDestination::Stash, +// 12 +// )); +// assert_ok!(Staking::bond_extra( +// Origin::signed(stash), +// StakingBalance::Ring(10_000), +// 36 +// )); +// assert_eq!(Kton::free_balance(&stash), 4); +// +// assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 36)); +// assert_eq!(Staking::ledger(&controller).unwrap().total_kton, 1); +// +// assert_ok!(Staking::nominate(Origin::signed(controller), vec![controller])); +// +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 10_000 * COIN, +// 12 * MONTH_IN_SECONDS as u64 +// )); +// assert_eq!(Kton::free_balance(&stash), 1); +// +// let ledger = StakingLedgers { +// stash: 777, +// total_ring: 10_000, +// total_deposit_ring: 10_000, +// active_ring: 10_000, +// active_deposit_ring: 10_000, +// total_kton: 1, +// active_kton: 1, +// deposit_items: vec![TimeDepositItem { +// value: 10_000, +// start_time: 0, +// expire_time: 36 * MONTH_IN_SECONDS as u64, +// }], +// unlocking: vec![], +// }; +// start_era(3); +// assert_ok!(Staking::withdraw_unbonded(Origin::signed(controller))); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// // not enough Kton to unbond +// assert_ok!(Staking::unbond_with_punish( +// Origin::signed(controller), +// 10_000 * COIN, +// 36 * MONTH_IN_SECONDS as u64 +// )); +// assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); +// }); +//} +// +//// how to balance the power and calculate the reward if some validators have been chilled +//#[test] +//fn yakio_q2() { +// fn run(with_new_era: bool) -> u64 { +// let mut balance = 0; +// ExtBuilder::default().existential_deposit(0).build().execute_with(|| { +// gen_paired_account!(validator_1_stash(123), validator_1_controller(456), 0); +// gen_paired_account!(validator_2_stash(234), validator_2_controller(567), 0); +// gen_paired_account!(nominator_stash(345), nominator_controller(678), 0); +// +// assert_ok!(Staking::validate( +// Origin::signed(validator_1_controller), +// vec![0; 8], +// 0, +// 3 +// )); +// assert_ok!(Staking::validate( +// Origin::signed(validator_2_controller), +// vec![1; 8], +// 0, +// 3 +// )); +// assert_ok!(Staking::nominate( +// Origin::signed(nominator_controller), +// vec![validator_1_stash, validator_2_stash] +// )); +// +// start_era(1); +// assert_ok!(Staking::chill(Origin::signed(validator_1_controller))); +// // assert_ok!(Staking::chill(Origin::signed(validator_2_controller))); +// if with_new_era { +// start_era(2); +// } +// Staking::reward_validator(&validator_1_stash, 1000 * COIN); +// Staking::reward_validator(&validator_2_stash, 1000 * COIN); +// +// balance = Ring::free_balance(&nominator_stash); +// }); +// +// balance +// } +// +// let free_balance = run(false); +// let free_balance_with_new_era = run(true); +// +// assert_ne!(free_balance, 0); +// assert_ne!(free_balance_with_new_era, 0); +// assert!(free_balance > free_balance_with_new_era); +//} #[test] -fn normal_unbond_should_work() { +fn xavier_q1() { ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 11; - let controller = 10; - let value = 200 * COIN; - let promise_month = 12; - // unbond normal ring - let _ = Ring::deposit_creating(&stash, 1000 * COIN); - - { - let kton_free_balance = Kton::free_balance(&stash); - let mut ledger = Staking::ledger(&controller).unwrap(); - - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(value), - promise_month, - )); - assert_eq!( - Kton::free_balance(&stash), - kton_free_balance + utils::compute_kton_return::(value, promise_month) - ); - ledger.total_ring += value; - ledger.total_deposit_ring += value; - ledger.active_ring += value; - ledger.active_deposit_ring += value; - ledger.deposit_items.push(TimeDepositItem { - value: value, - start_time: 0, - expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64, - }); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - } - - { - let kton_free_balance = Kton::free_balance(&stash); - let mut ledger = Staking::ledger(&controller).unwrap(); - - // we try to bond 1 kton, but stash only has 0.03 Kton - // extra = 1.min(0.03) - // bond += 0.03 - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Kton(COIN), - 0 - )); - ledger.total_kton += kton_free_balance; - ledger.active_kton += kton_free_balance; - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - - assert_ok!(Staking::unbond( - Origin::signed(controller), - StakingBalance::Kton(kton_free_balance) - )); - ledger.active_kton = 0; - ledger.unlocking = vec![UnlockChunk { - value: StakingBalance::Kton(kton_free_balance), - era: 3, - is_time_deposit: false, - }]; - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - } - }); -} - -#[test] -fn punished_unbond_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 1001; - let controller = 1000; - let promise_month = 36; - - let _ = Ring::deposit_creating(&stash, 100 * COIN); - Kton::deposit_creating(&stash, COIN / 100000); - - // timestamp now is 0. - // free balance of kton is too low to work - assert_ok!(Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(10 * COIN), - RewardDestination::Stash, - promise_month - )); - assert_eq!( - Staking::ledger(&controller), - Some(StakingLedgers { - stash, - total_ring: 10 * COIN, - total_deposit_ring: 10 * COIN, - active_deposit_ring: 10 * COIN, - active_ring: 10 * COIN, - total_kton: 0, - active_kton: 0, - deposit_items: vec![TimeDepositItem { - value: 10 * COIN, - start_time: 0, - expire_time: promise_month as u64 * MONTH_IN_SECONDS as u64 - }], // should be cleared - unlocking: vec![] - }) - ); - let mut ledger = Staking::ledger(&controller).unwrap(); - let kton_free_balance = Kton::free_balance(&stash); - // kton is 0, skip unbond_with_punish - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 10 * COIN, - promise_month as u64 * MONTH_IN_SECONDS as u64 - )); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - assert_eq!(Kton::free_balance(&stash), kton_free_balance); - - // set more kton balance to make it work - Kton::deposit_creating(&stash, 10 * COIN); - let kton_free_balance = Kton::free_balance(&stash); - let unbond_value = 5 * COIN; - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - unbond_value, - promise_month as u64 * MONTH_IN_SECONDS as u64 - )); - ledger.active_ring -= unbond_value; - ledger.active_deposit_ring -= unbond_value; - ledger.deposit_items[0].value -= unbond_value; - ledger.unlocking = vec![UnlockChunk { - value: StakingBalance::Ring(unbond_value), - era: 3, - is_time_deposit: true, - }]; - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - - let kton_punishment = utils::compute_kton_return::(unbond_value, promise_month); - assert_eq!(Kton::free_balance(&stash), kton_free_balance - 3 * kton_punishment); - - // if deposit_item.value == 0 - // the whole item should be be dropped - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 5 * COIN, - promise_month as u64 * MONTH_IN_SECONDS as u64 - )); - assert!(Staking::ledger(&controller).unwrap().deposit_items.is_empty()); - }); -} - -#[test] -fn transform_to_promised_ring_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Ring::deposit_creating(&1001, 100 * COIN); - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(10 * COIN), - RewardDestination::Stash, - 0 - )); - let origin_ledger = Staking::ledger(&1000).unwrap(); - let kton_free_balance = Kton::free_balance(&1001); - - assert_ok!(Staking::promise_extra(Origin::signed(1000), 5 * COIN, 12)); - - assert_eq!( - Staking::ledger(&1000), - Some(StakingLedgers { - stash: 1001, - total_ring: origin_ledger.total_ring, - total_deposit_ring: origin_ledger.total_deposit_ring + 5 * COIN, - active_deposit_ring: origin_ledger.active_deposit_ring + 5 * COIN, - active_ring: origin_ledger.active_ring, - total_kton: origin_ledger.total_kton, - active_kton: origin_ledger.active_kton, - deposit_items: vec![TimeDepositItem { - value: 5 * COIN, - start_time: 0, - expire_time: 12 * MONTH_IN_SECONDS as u64 - }], - unlocking: vec![] - }) - ); - - assert_eq!(Kton::free_balance(&1001), kton_free_balance + (5 * COIN / 10000)); - }); -} - -#[test] -fn expired_ring_should_capable_to_promise_again() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Ring::deposit_creating(&1001, 100 * COIN); - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(10 * COIN), - RewardDestination::Stash, - 12 - )); - let mut ledger = Staking::ledger(&1000).unwrap(); - let ts = 13 * MONTH_IN_SECONDS as u64; - let promise_extra_value = 5 * COIN; - Timestamp::set_timestamp(ts); - assert_ok!(Staking::promise_extra(Origin::signed(1000), promise_extra_value, 13)); - ledger.total_deposit_ring = promise_extra_value; - ledger.active_deposit_ring = promise_extra_value; - // old deposit_item with 12 months promised removed - ledger.deposit_items = vec![TimeDepositItem { - value: promise_extra_value, - start_time: ts, - expire_time: 2 * ts, - }]; - assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); - }); -} - -#[test] -fn inflation_should_be_correct() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let initial_issuance = 1_200_000_000 * COIN; - let surplus_needed = initial_issuance - Ring::total_issuance(); - let _ = Ring::deposit_into_existing(&11, surplus_needed); - assert_eq!(Ring::total_issuance(), initial_issuance); - assert_eq!(Staking::current_era_total_reward(), 80000000 * COIN / 10); - start_era(11); - // ErasPerEpoch = 10 - assert_eq!(Staking::current_era_total_reward(), 88000000 * COIN / 10); + gen_paired_account!(stash(123), controller(456), 12); }); } - -#[test] -fn reward_should_work_correctly() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - // create controller account - let _ = Ring::deposit_creating(&2000, COIN); - let _ = Ring::deposit_creating(&1000, COIN); - let _ = Ring::deposit_creating(&200, COIN); - // new validator - let _ = Ring::deposit_creating(&2001, 2000 * COIN); - Kton::deposit_creating(&2001, 10 * COIN); - // new validator - let _ = Ring::deposit_creating(&1001, 300 * COIN); - Kton::deposit_creating(&1001, 1 * COIN); - // handle some dirty work - let _ = Ring::deposit_creating(&201, 2000 * COIN); - Kton::deposit_creating(&201, 10 * COIN); - assert_eq!(Kton::free_balance(&201), 10 * COIN); - - // 2001-2000 - assert_ok!(Staking::bond( - Origin::signed(2001), - 2000, - StakingBalance::Ring(300 * COIN), - RewardDestination::Controller, - 12, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(2001), - StakingBalance::Kton(1 * COIN), - 0 - )); - // 1001-1000 - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(300 * COIN), - RewardDestination::Controller, - 12, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(1001), - StakingBalance::Kton(1 * COIN), - 0 - )); - let ring_pool = Staking::ring_pool(); - let kton_pool = Staking::kton_pool(); - // 201-200 - assert_ok!(Staking::bond( - Origin::signed(201), - 200, - StakingBalance::Ring(3000 * COIN - ring_pool), - RewardDestination::Stash, - 12, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(201), - StakingBalance::Kton(10 * COIN - kton_pool), - 0, - )); - // ring_pool and kton_pool - assert_eq!(Staking::ring_pool(), 3000 * COIN); - assert_eq!(Staking::kton_pool(), 10 * COIN); - // 1/5 ring_pool and 1/5 kton_pool - assert_ok!(Staking::validate(Origin::signed(2000), [0; 8].to_vec(), 0, 3)); - assert_ok!(Staking::nominate(Origin::signed(1000), vec![2001])); - - assert_eq!(Staking::ledger(&2000).unwrap().active_kton, 1 * COIN); - assert_eq!(Staking::ledger(&2000).unwrap().active_ring, 300 * COIN); - assert_eq!(Staking::power_of(&2001), 1_000_000_000 / 10 as u128); - // 600COIN for rewarding ring bond-er - // 600COIN for rewarding kton bond-er - Staking::select_validators(); - Staking::reward_validator(&2001, 1200 * COIN); - - assert_eq!( - Staking::stakers(2001), - Exposure { - total: 1200000000000, - own: 600000000000, - others: vec![IndividualExposure { - who: 1001, - value: 600000000000 - }] - } - ); - assert_eq!(Ring::free_balance(&2000), 601 * COIN); - assert_eq!(Ring::free_balance(&1000), 601 * COIN); - }); -} - -#[test] -fn slash_should_work() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let _ = Ring::deposit_creating(&1001, 100 * COIN); - Kton::deposit_creating(&1001, 100 * COIN); - - assert_ok!(Staking::bond( - Origin::signed(1001), - 1000, - StakingBalance::Ring(50 * COIN), - RewardDestination::Controller, - 0, - )); - assert_ok!(Staking::bond_extra( - Origin::signed(1001), - StakingBalance::Kton(50 * COIN), - 0 - )); - assert_ok!(Staking::validate(Origin::signed(1000), [0; 8].to_vec(), 0, 3)); - - // slash 1% - let slash_value = 5 * COIN / 10; - let mut ledger = Staking::ledger(&1000).unwrap(); - let ring_free_balance = Ring::free_balance(&1001); - let kton_free_balance = Kton::free_balance(&1001); - Staking::slash_validator(&1001, 10_000_000); - ledger.total_ring -= slash_value; - ledger.active_ring -= slash_value; - ledger.total_kton -= slash_value; - ledger.active_kton -= slash_value; - assert_eq!(&Staking::ledger(&1000).unwrap(), &ledger); - assert_eq!(Ring::free_balance(&1001), ring_free_balance - slash_value); - assert_eq!(Kton::free_balance(&1001), kton_free_balance - slash_value); - }); -} - -#[test] -fn test_inflation() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - assert_eq!(Staking::current_era_total_reward(), 80_000_000 * COIN / 10); - start_era(20); - assert_eq!(Staking::epoch_index(), 2); - assert_eq!(Staking::current_era_total_reward(), 9_999_988_266 * COIN / 1000); - }); -} - -#[test] -fn set_controller_should_remove_old_ledger() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 11; - let old_controller = 10; - let new_controller = 12; - - assert!(Staking::ledger(&old_controller).is_some()); - assert_eq!(Staking::bonded(&stash), Some(old_controller)); - - assert_ok!(Staking::set_controller(Origin::signed(stash), new_controller)); - assert!(Staking::ledger(&old_controller).is_none()); - }); -} - -#[test] -fn set_controller_should_not_change_ledger() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - assert_eq!(Staking::ledger(&10).unwrap().total_ring, 100 * COIN); - assert_ok!(Staking::set_controller(Origin::signed(11), 12)); - assert_eq!(Staking::ledger(&12).unwrap().total_ring, 100 * COIN); - }); -} - -#[test] -fn slash_should_not_touch_unlockings() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let old_ledger = Staking::ledger(&10).unwrap(); - // only deposit_ring, no normal_ring - assert_eq!( - ( - old_ledger.total_ring, - old_ledger.active_ring, - old_ledger.active_deposit_ring - ), - (100 * COIN, 100 * COIN, 100 * COIN) - ); - - assert_ok!(Staking::bond_extra( - Origin::signed(11), - StakingBalance::Ring(100 * COIN), - 0 - )); - Kton::deposit_creating(&11, 10 * COIN); - assert_ok!(Staking::bond_extra( - Origin::signed(11), - StakingBalance::Kton(10 * COIN), - 0 - )); - - assert_ok!(Staking::unbond(Origin::signed(10), StakingBalance::Ring(10 * COIN))); - let new_ledger = Staking::ledger(&10).unwrap(); - assert_eq!( - ( - new_ledger.total_ring, - new_ledger.active_ring, - new_ledger.active_deposit_ring - ), - (200 * COIN, 190 * COIN, 100 * COIN) - ); - - // slash 100% - Staking::slash_validator(&11, 1_000_000_000); - - let ledger = Staking::ledger(&10).unwrap(); - assert_eq!( - (ledger.total_ring, ledger.active_ring, ledger.active_deposit_ring), - // 10Ring in unlocking - (10 * COIN, 0, 0) - ); - assert_eq!(ledger.unlocking[0].value, StakingBalance::Ring(10 * COIN)); - }); -} - -#[test] -fn bond_over_max_promise_month_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(stash(123), controller(456)); - assert_err!( - Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(COIN), - RewardDestination::Stash, - 37 - ), - "months at most is 36." - ); - - gen_paired_account!(stash(123), controller(456), promise_month(12)); - assert_err!( - Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(COIN), 37), - "months at most is 36." - ); - }); -} - -#[test] -fn stash_already_bonded_and_controller_already_paired_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(unpaired_stash(123), unpaired_controller(456)); - assert_err!( - Staking::bond( - Origin::signed(11), - unpaired_controller, - StakingBalance::Ring(COIN), - RewardDestination::Stash, - 0 - ), - "stash already bonded" - ); - assert_err!( - Staking::bond( - Origin::signed(unpaired_stash), - 10, - StakingBalance::Ring(COIN), - RewardDestination::Stash, - 0 - ), - "controller already paired" - ); - }); -} - -#[test] -fn pool_should_be_increased_and_decreased_correctly() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let mut ring_pool = Staking::ring_pool(); - let mut kton_pool = Staking::kton_pool(); - - // bond: 100COIN - gen_paired_account!(stash_1(111), controller_1(222), 0); - gen_paired_account!(stash_2(333), controller_2(444), promise_month(12)); - ring_pool += 100 * COIN; - kton_pool += 100 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - assert_eq!(Staking::kton_pool(), kton_pool); - - // unbond: 50Ring 50Kton - assert_ok!(Staking::unbond( - Origin::signed(controller_1), - StakingBalance::Ring(50 * COIN) - )); - assert_ok!(Staking::unbond( - Origin::signed(controller_1), - StakingBalance::Kton(25 * COIN) - )); - // not yet expired: promise for 12 months - assert_ok!(Staking::unbond( - Origin::signed(controller_2), - StakingBalance::Ring(50 * COIN) - )); - assert_ok!(Staking::unbond( - Origin::signed(controller_2), - StakingBalance::Kton(25 * COIN) - )); - ring_pool -= 50 * COIN; - kton_pool -= 50 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - assert_eq!(Staking::kton_pool(), kton_pool); - - // unbond with punish: 12.5Ring - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller_2), - 125 * COIN / 10, - promise_month * MONTH_IN_SECONDS as u64 - )); - // unbond deposit items: 12.5Ring - Timestamp::set_timestamp(promise_month * MONTH_IN_SECONDS as u64); - assert_ok!(Staking::unbond( - Origin::signed(controller_2), - StakingBalance::Ring(125 * COIN / 10) - )); - ring_pool -= 25 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - - // slash: 25Ring 50Kton - Staking::slash_validator(&stash_1, 1_000_000_000); - Staking::slash_validator(&stash_2, 1_000_000_000); - ring_pool -= 25 * COIN; - kton_pool -= 50 * COIN; - assert_eq!(Staking::ring_pool(), ring_pool); - assert_eq!(Staking::kton_pool(), kton_pool); - }); -} - -#[test] -fn unbond_over_max_unlocking_chunks_should_fail() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(stash(123), controller(456), promise_month(12)); - let deposit_items_len = MAX_UNLOCKING_CHUNKS + 1; - - for _ in 1..deposit_items_len { - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(COIN), - promise_month - )); - } - { - let ledger = Staking::ledger(&controller).unwrap(); - assert_eq!(ledger.deposit_items.len(), deposit_items_len); - assert_eq!(ledger.unlocking.len(), 0); - } - - Timestamp::set_timestamp(promise_month as u64 * MONTH_IN_SECONDS as u64); - - for _ in 1..deposit_items_len { - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); - } - { - let ledger = Staking::ledger(&controller).unwrap(); - assert_eq!(ledger.deposit_items.len(), 1); - assert_eq!(ledger.unlocking.len(), deposit_items_len - 1); - } - assert_err!( - Staking::unbond( - Origin::signed(controller), - StakingBalance::Ring((deposit_items_len - 1) as u64 * COIN) - ), - "can not schedule more unlock chunks" - ); - }); -} - -#[test] -fn unlock_value_should_be_increased_and_decreased_correctly() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - // normal Ring/Kton - { - let stash = 444; - let controller = 555; - let _ = Ring::deposit_creating(&stash, 100 * COIN); - Kton::deposit_creating(&stash, 100 * COIN); - - assert_ok!(Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(50 * COIN), - RewardDestination::Stash, - 0 - )); - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Kton(50 * COIN), - 0 - )); - - let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; - - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(COIN), - era: 3, - is_time_deposit: false, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(COIN))); - unlocking.push(UnlockChunk { - value: StakingBalance::Kton(COIN), - era: 3, - is_time_deposit: false, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(0))); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(0), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(0))); - unlocking.push(UnlockChunk { - value: StakingBalance::Kton(0), - era: 3, - is_time_deposit: false, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - } - - // promise Ring - { - gen_paired_account!(stash(666), controller(777), promise_month(12)); - - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(50 * COIN), - 36 - )); - - let mut unlocking = Staking::ledger(&controller).unwrap().unlocking; - - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(COIN))); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(0), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - for month in [12, 36].iter() { - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 20 * COIN, - month * MONTH_IN_SECONDS as u64 - )); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(20 * COIN), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 29 * COIN, - month * MONTH_IN_SECONDS as u64 - )); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(29 * COIN), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 50 * COIN, - month * MONTH_IN_SECONDS as u64 - )); - unlocking.push(UnlockChunk { - value: StakingBalance::Ring(1 * COIN), - era: 3, - is_time_deposit: true, - }); - assert_eq!(&Staking::ledger(&controller).unwrap().unlocking, &unlocking); - } - } - }); -} - -// #[test] -// fn total_deposit_should_be_increased_and_decreased_correctly() { -// with_externalities( -// &mut ExtBuilder::default().existential_deposit(0).build(), -// || body, -// ); -// } - -#[test] -fn promise_extra_should_not_remove_unexpired_items() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(stash(123), controller(456), promise_month(12)); - - let expired_item_len = 3; - let expiry_date = promise_month as u64 * MONTH_IN_SECONDS as u64; - - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(5 * COIN), - 0 - )); - for _ in 0..expired_item_len { - assert_ok!(Staking::promise_extra(Origin::signed(controller), COIN, promise_month)); - } - - Timestamp::set_timestamp(expiry_date - 1); - assert_ok!(Staking::promise_extra( - Origin::signed(controller), - 2 * COIN, - promise_month - )); - assert_eq!( - Staking::ledger(&controller).unwrap().deposit_items.len(), - 2 + expired_item_len - ); - - Timestamp::set_timestamp(expiry_date); - assert_ok!(Staking::promise_extra( - Origin::signed(controller), - 2 * COIN, - promise_month - )); - assert_eq!(Staking::ledger(&controller).unwrap().deposit_items.len(), 2); - }); -} - -#[test] -fn unbond_zero_before_expiry() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let expiry_date = 12 * MONTH_IN_SECONDS as u64; - let unbond_value = StakingBalance::Ring(COIN); - - Timestamp::set_timestamp(expiry_date - 1); - assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); - assert_eq!( - Staking::ledger(&10).unwrap().unlocking[0].value, - StakingBalance::Ring(0) - ); - - Timestamp::set_timestamp(expiry_date); - assert_ok!(Staking::unbond(Origin::signed(10), unbond_value.clone())); - assert_eq!(Staking::ledger(&10).unwrap().unlocking[1].value, unbond_value); - }); -} - -// bond 10_000 Ring for 12 months, gain 1 Kton -// bond extra 10_000 Ring for 36 months, gain 3 Kton -// bond extra 1 Kton -// nominate -// unlock the 12 months deposit item with punish -// lost 3 Kton and 10_000 Ring's power for nominate -#[test] -fn yakio_q1() { - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 777; - let controller = 888; - let _ = Ring::deposit_creating(&stash, 20_000); - - assert_ok!(Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(10_000), - RewardDestination::Stash, - 12 - )); - assert_ok!(Staking::bond_extra( - Origin::signed(stash), - StakingBalance::Ring(10_000), - 36 - )); - assert_eq!(Kton::free_balance(&stash), 4); - - assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 36)); - assert_eq!(Staking::ledger(&controller).unwrap().total_kton, 1); - - assert_ok!(Staking::nominate(Origin::signed(controller), vec![controller])); - - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 10_000 * COIN, - 12 * MONTH_IN_SECONDS as u64 - )); - assert_eq!(Kton::free_balance(&stash), 1); - - let ledger = StakingLedgers { - stash: 777, - total_ring: 10_000, - total_deposit_ring: 10_000, - active_ring: 10_000, - active_deposit_ring: 10_000, - total_kton: 1, - active_kton: 1, - deposit_items: vec![TimeDepositItem { - value: 10_000, - start_time: 0, - expire_time: 36 * MONTH_IN_SECONDS as u64, - }], - unlocking: vec![], - }; - start_era(3); - assert_ok!(Staking::withdraw_unbonded(Origin::signed(controller))); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - // not enough Kton to unbond - assert_ok!(Staking::unbond_with_punish( - Origin::signed(controller), - 10_000 * COIN, - 36 * MONTH_IN_SECONDS as u64 - )); - assert_eq!(&Staking::ledger(&controller).unwrap(), &ledger); - }); -} - -// how to balance the power and calculate the reward if some validators have been chilled -#[test] -fn yakio_q2() { - fn run(with_new_era: bool) -> u64 { - let mut balance = 0; - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(validator_1_stash(123), validator_1_controller(456), 0); - gen_paired_account!(validator_2_stash(234), validator_2_controller(567), 0); - gen_paired_account!(nominator_stash(345), nominator_controller(678), 0); - - assert_ok!(Staking::validate( - Origin::signed(validator_1_controller), - vec![0; 8], - 0, - 3 - )); - assert_ok!(Staking::validate( - Origin::signed(validator_2_controller), - vec![1; 8], - 0, - 3 - )); - assert_ok!(Staking::nominate( - Origin::signed(nominator_controller), - vec![validator_1_stash, validator_2_stash] - )); - - start_era(1); - assert_ok!(Staking::chill(Origin::signed(validator_1_controller))); - // assert_ok!(Staking::chill(Origin::signed(validator_2_controller))); - if with_new_era { - start_era(2); - } - Staking::reward_validator(&validator_1_stash, 1000 * COIN); - Staking::reward_validator(&validator_2_stash, 1000 * COIN); - - balance = Ring::free_balance(&nominator_stash); - }); - - balance - } - - let free_balance = run(false); - let free_balance_with_new_era = run(true); - - assert_ne!(free_balance, 0); - assert_ne!(free_balance_with_new_era, 0); - assert!(free_balance > free_balance_with_new_era); -} diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 8974dd291..58ceec233 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -21,11 +21,22 @@ pub trait OnAccountBalanceChanged { fn on_changed(who: &AccountId, old: Balance, new: Balance); } -/// A currency whose accounts can have liquidity restrictions. +/// A more powerful lockable currency. pub trait LockableCurrency: Currency { - /// The quantity used to denote time; usually just a `BlockNumber`. - /// In Darwinia we prefer using `TimeStamp/u64`. + /// Recommend to define `Id` as below and customize `PartialEq` to differentiate the locks: + /// ```rust + /// #[derive(Eq, Clone, Encode, Decode, RuntimeDebug)] + /// pub enum Id { + /// Staking(Moment), + /// Unbonding(Moment), + /// } + /// ``` + /// Moment: + /// - The quantity used to denote time; usually just a `BlockNumber`. + /// - In Darwinia we prefer using `TimeStamp/u64`. type Id; + /// Customize our `WithdrawReasons` + type WithdrawReasons; /// Create a new balance lock on account `who`. /// @@ -33,14 +44,14 @@ pub trait LockableCurrency: Currency { /// the `Locks` vec in storage. Note that you can lock more funds than a user has. /// /// If the lock `id` already exists, this will update it. - fn set_lock(who: &AccountId, amount: Self::Balance, id: Self::Id); + fn set_lock(who: &AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons); // TODO: reserve // fn extend_lock(); /// Remove an existing lock. - fn remove_lock(id: Self::Id, who: &AccountId); + fn remove_lock(who: &AccountId, id: Self::Id); /// The number of locks. - fn count() -> u32; + fn locks_count(who: &AccountId) -> u32; } diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 7d1728738..e2fcb6f82 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -1,10 +1,63 @@ use codec::{Decode, Encode}; use sr_primitives::RuntimeDebug; +use srml_support::traits::WithdrawReasons; pub type TimeStamp = u64; -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] +#[derive(Eq, Clone, Encode, Decode, RuntimeDebug)] +pub struct BalanceLock { + pub id: Id, + pub amount: Balance, + pub reasons: WithdrawReasons, +} + +impl BalanceLock +where + Moment: PartialOrd, +{ + pub fn valid(&self, until: &Moment) -> bool { + self.id.until() > until + } +} + +impl PartialEq for BalanceLock +where + Moment: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +#[derive(Eq, Clone, Encode, Decode, RuntimeDebug)] pub enum Id { - Staking, + Staking(Moment), Unbonding(Moment), } + +impl Id { + fn until(&self) -> &Moment { + match self { + Id::Staking(moment) => moment, + Id::Unbonding(moment) => moment, + } + } +} + +impl PartialEq for Id +where + Moment: PartialEq, +{ + fn eq(&self, other: &Self) -> bool { + match self { + Id::Staking(_) => match other { + Id::Staking(_) => true, + _ => false, + }, + Id::Unbonding(moment) => match other { + Id::Unbonding(moment_) => moment == moment_, + _ => false, + }, + } + } +} From 554fa7d6f6c7001c119305265c99a1804c42379e Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Fri, 15 Nov 2019 13:42:59 +0800 Subject: [PATCH 06/30] fix: typo --- srml/staking/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index a399f664c..95466c924 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -444,15 +444,15 @@ decl_module! { // Only active normal ring can be unbond let active_normal_ring = *active_ring - *active_deposit_ring; // unbond normal ring first - let available_unbund_ring = r.min(active_normal_ring); + let available_unbond_ring = r.min(active_normal_ring); - >::mutate(|r| *r -= available_unbund_ring); + >::mutate(|r| *r -= available_unbond_ring); - if !available_unbund_ring.is_zero() { - *active_ring -= available_unbund_ring; + if !available_unbond_ring.is_zero() { + *active_ring -= available_unbond_ring; // TODO // unlocking.push(UnlockChunk { -// value: StakingBalance::Ring(available_unbund_ring), +// value: StakingBalance::Ring(available_unbond_ring), // era, // }); @@ -460,15 +460,15 @@ decl_module! { } }, StakingBalance::Kton(k) => { - let unbound_kton = k.min(*active_kton); + let unbond_kton = k.min(*active_kton); - if !unbound_kton.is_zero() { - >::mutate(|k| *k -= unbound_kton); + if !unbond_kton.is_zero() { + >::mutate(|k| *k -= unbond_kton); - *active_kton -= unbound_kton; + *active_kton -= unbond_kton; // TODO // unlocking.push(UnlockChunk { -// value: StakingBalance::Kton(unbound_kton), +// value: StakingBalance::Kton(unbond_kton), // era, // }); From 02729983659fabf43bacfe1f8aa6f6c1664f329f Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Fri, 15 Nov 2019 16:32:01 +0800 Subject: [PATCH 07/30] update: new `LockableCurrency` design --- srml/balances/src/lib.rs | 48 +++++++++++++++++++++++++------- srml/kton/src/lib.rs | 48 +++++++++++++++++++++++++------- srml/staking/src/lib.rs | 57 ++++++++++++++++---------------------- srml/staking/src/tests.rs | 35 ++++++++++++++++++++++- srml/support/src/traits.rs | 2 +- srml/support/src/types.rs | 4 +-- 6 files changed, 137 insertions(+), 57 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 6508217dd..5a13c9168 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -737,12 +737,42 @@ where return Ok(()); } + // 1. for Lock: + // currently, we only use `WithdrawReasons::all()` in staking module + // so the `!lock.reasons.intersects(reasons)` always be `false` + // 2. for Staking Lock: + // make sure the free Kton > the Kton which in Staking + // 3. for Unbonding Lock: + // because of the rule 1, only check if is expired let now = >::now(); - // TODO: logic? - if locks - .into_iter() - .all(|lock| !lock.valid(&now) || new_balance >= lock.amount || !lock.reasons.intersects(reasons)) - { + let mut can_withdraw_staking = false; + let mut can_withdraw_unbonding = false; + for lock in locks { + match lock.id { + Id::Staking(_) => { + can_withdraw_staking = !lock.valid_at(&now); + if can_withdraw_staking { + continue; + } + + can_withdraw_staking = new_balance >= lock.amount; + if can_withdraw_staking { + continue; + } + + // due to rule 1 + can_withdraw_staking = false; + // TODO: for complex reasons, in the future + // can_withdraw = !lock.reasons.intersects(reasons); + } + Id::Unbonding(_) => { + // due to rule 3 + can_withdraw_unbonding = !lock.valid_at(&now); + } + } + } + + if can_withdraw_unbonding || can_withdraw_staking { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -987,19 +1017,17 @@ where type WithdrawReasons = WithdrawReasons; fn set_lock(who: &T::AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons) { - let now = >::now(); - + Self::remove_lock(who, &id); >::mutate(who, |locks| { - locks.retain(|lock| lock.id != id || lock.valid(&now)); locks.push(BalanceLock { id, amount, reasons }); }); } - fn remove_lock(who: &T::AccountId, id: Self::Id) { + fn remove_lock(who: &T::AccountId, id: &Self::Id) { let now = >::now(); >::mutate(who, |locks| { // unexpired and mismatched id -> keep - locks.retain(|lock| lock.valid(&now) && lock.id != id); + locks.retain(|lock| lock.valid_at(&now) && &lock.id != id); }); } diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index c7207bc88..9eba75e27 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -232,12 +232,42 @@ impl Currency for Module { return Ok(()); } + // 1. for Lock: + // currently, we only use `WithdrawReasons::all()` in staking module + // so the `!lock.reasons.intersects(reasons)` always be `false` + // 2. for Staking Lock: + // make sure the free Kton > the Kton which in Staking + // 3. for Unbonding Lock: + // because of the rule 1, only check if is expired let now = >::now(); - // TODO: logic? - if locks - .into_iter() - .all(|lock| !lock.valid(&now) || new_balance >= lock.amount || !lock.reasons.intersects(reasons)) - { + let mut can_withdraw_staking = false; + let mut can_withdraw_unbonding = false; + for lock in locks { + match lock.id { + Id::Staking(_) => { + can_withdraw_staking = !lock.valid_at(&now); + if can_withdraw_staking { + continue; + } + + can_withdraw_staking = new_balance >= lock.amount; + if can_withdraw_staking { + continue; + } + + // due to rule 1 + can_withdraw_staking = false; + // TODO: for complex reasons, in the future + // can_withdraw = !lock.reasons.intersects(reasons); + } + Id::Unbonding(_) => { + // due to rule 3 + can_withdraw_unbonding = !lock.valid_at(&now); + } + } + } + + if can_withdraw_unbonding || can_withdraw_staking { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -383,19 +413,17 @@ where type WithdrawReasons = WithdrawReasons; fn set_lock(who: &T::AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons) { - let now = >::now(); - + Self::remove_lock(who, &id); >::mutate(who, |locks| { - locks.retain(|lock| lock.id != id || lock.valid(&now)); locks.push(BalanceLock { id, amount, reasons }); }); } - fn remove_lock(who: &T::AccountId, id: Self::Id) { + fn remove_lock(who: &T::AccountId, id: &Self::Id) { let now = >::now(); >::mutate(who, |locks| { // unexpired and mismatched id -> keep - locks.retain(|lock| lock.valid(&now) && lock.id != id); + locks.retain(|lock| lock.valid_at(&now) && &lock.id != id); }); } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 95466c924..4a33c1e10 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -25,14 +25,14 @@ extern crate test; use codec::{CompactAs, Decode, Encode, HasCompact}; use rstd::{prelude::*, result}; use session::{historical::OnSessionEnding, SelectInitialValidators}; -use sr_primitives::traits::{Bounded, CheckedSub, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}; +use sr_primitives::traits::{CheckedSub, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}; #[cfg(feature = "std")] use sr_primitives::{Deserialize, Serialize}; use sr_primitives::{Perbill, Perquintill, RuntimeDebug}; use sr_staking_primitives::SessionIndex; use srml_support::{ decl_event, decl_module, decl_storage, ensure, - traits::{Currency, Get, Imbalance, OnFreeBalanceZero, OnUnbalanced, WithdrawReason}, + traits::{Currency, Get, Imbalance, OnFreeBalanceZero, OnUnbalanced, WithdrawReason, WithdrawReasons}, }; use system::{ensure_root, ensure_signed}; @@ -201,8 +201,8 @@ type KtonPositiveImbalanceOf = <::Kton as Currency< = <::Kton as Currency<::AccountId>>::NegativeImbalance; pub trait Trait: timestamp::Trait + session::Trait { - type Ring: LockableCurrency>; - type Kton: LockableCurrency>; + type Ring: LockableCurrency, WithdrawReasons = WithdrawReasons>; + type Kton: LockableCurrency, WithdrawReasons = WithdrawReasons>; type CurrencyToVote: Convert, u64> + Convert>; @@ -391,7 +391,7 @@ decl_module! { promise_month: u32 ) { let stash = ensure_signed(origin)?; - ensure!( promise_month <= 36, "months at most is 36."); + ensure!(promise_month <= 36, "months at most is 36."); let controller = Self::bonded(&stash).ok_or("not a stash")?; let ledger = Self::ledger(&controller).ok_or("not a controller")?; match value { @@ -450,11 +450,8 @@ decl_module! { if !available_unbond_ring.is_zero() { *active_ring -= available_unbond_ring; -// TODO -// unlocking.push(UnlockChunk { -// value: StakingBalance::Ring(available_unbond_ring), -// era, -// }); +// TODO: ok? + T::Ring::set_lock(stash, Id::Unbonding(until), available_unbond_ring, WithdrawReasons::all()); Self::update_ledger(&controller, &ledger, value); } @@ -466,11 +463,8 @@ decl_module! { >::mutate(|k| *k -= unbond_kton); *active_kton -= unbond_kton; -// TODO -// unlocking.push(UnlockChunk { -// value: StakingBalance::Kton(unbond_kton), -// era, -// }); +// TODO: ok? + T::Kton::set_lock(stash, Id::Unbonding(until), unbond_kton, WithdrawReasons::all()); Self::update_ledger(&controller, &ledger, value); } @@ -788,23 +782,20 @@ impl Module { ledger: &StakingLedgers, KtonBalanceOf, T::Moment>, staking_balance: StakingBalance, KtonBalanceOf>, ) { - // TODO - // match staking_balance { - // StakingBalance::Ring(_r) => T::Ring::set_lock( - // STAKING_ID, - // &ledger.stash, - // ledger.total_ring, - // T::BlockNumber::max_value(), - // WithdrawReasons::all(), - // ), - // StakingBalance::Kton(_k) => T::Kton::set_lock( - // STAKING_ID, - // &ledger.stash, - // ledger.total_kton, - // T::BlockNumber::max_value(), - // WithdrawReasons::all(), - // ), - // } + match staking_balance { + StakingBalance::Ring(_r) => T::Ring::set_lock( + &ledger.stash, + Id::Staking(TimeStamp::max_value()), + ledger.total_ring, + WithdrawReasons::all(), + ), + StakingBalance::Kton(_k) => T::Kton::set_lock( + &ledger.stash, + Id::Staking(TimeStamp::max_value()), + ledger.total_kton, + WithdrawReasons::all(), + ), + } >::insert(controller, ledger); } @@ -1135,7 +1126,7 @@ impl Module { others: s .others .into_iter() - .map(|(who, value)| IndividualExposure { who, value: value }) + .map(|(who, value)| IndividualExposure { who, value }) .collect::>>(), }; if exposure.total < slot_stake { diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 52214f823..dd31cf2bd 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1208,6 +1208,39 @@ fn test_env_build() { #[test] fn xavier_q1() { ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - gen_paired_account!(stash(123), controller(456), 12); + let stash = 123; + let controller = 456; + Kton::deposit_creating(&stash, 10); + + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Kton(5), + RewardDestination::Stash, + 0, + )); + println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); + println!("Ok Init - Kton Locks: {:?}", Kton::locks(stash)); + + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(5), 0)); + println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); + println!("Ok Bond Extra - Kton Locks: {:?}", Kton::locks(stash)); + + Timestamp::set_timestamp(0); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(10))); + println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); + println!("Ok Unbond - Kton Locks: {:?}", Kton::locks(stash)); + + assert_err!( + Kton::transfer(Origin::signed(stash), controller, 1), + "account liquidity restrictions prevent withdrawal" + ); + println!("Locking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + println!("Locking Transfer - Kton Locks: {:?}", Kton::locks(stash)); + + Timestamp::set_timestamp(BondingDuration::get()); + assert_ok!(Kton::transfer(Origin::signed(stash), controller, 1)); + println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + println!("Unlocking Transfer - Kton Locks: {:?}", Kton::locks(stash)); }); } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 58ceec233..d0f93af74 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -50,7 +50,7 @@ pub trait LockableCurrency: Currency { // fn extend_lock(); /// Remove an existing lock. - fn remove_lock(who: &AccountId, id: Self::Id); + fn remove_lock(who: &AccountId, id: &Self::Id); /// The number of locks. fn locks_count(who: &AccountId) -> u32; diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index e2fcb6f82..906fa4c39 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -15,8 +15,8 @@ impl BalanceLock where Moment: PartialOrd, { - pub fn valid(&self, until: &Moment) -> bool { - self.id.until() > until + pub fn valid_at(&self, at: &Moment) -> bool { + self.id.until() > at } } From 3f5119f08491e2568fe0841b7cbf632686886205 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Mon, 18 Nov 2019 17:55:05 +0800 Subject: [PATCH 08/30] update: withdraw Ring/Kton automatically --- srml/balances/src/lib.rs | 128 ++++++---- srml/chainrelay/bridge/ethereum/src/lib.rs | 9 +- srml/kton/src/lib.rs | 127 ++++++---- srml/staking/src/lib.rs | 117 +++------ srml/staking/src/tests.rs | 276 ++++++++++++++++++++- srml/support/src/traits.rs | 35 +-- srml/support/src/types.rs | 53 ++-- 7 files changed, 494 insertions(+), 251 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 5a13c9168..fe7cc7e46 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -17,12 +17,13 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Codec, Decode, Encode}; -use rstd::prelude::*; -use rstd::{cmp, fmt::Debug, mem, result}; +#[cfg(not(feature = "std"))] +use rstd::borrow::ToOwned; +use rstd::{cmp, fmt::Debug, mem, prelude::*, result}; use sr_primitives::{ traits::{ - Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Saturating, SimpleArithmetic, StaticLookup, - Zero, + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SaturatedConversion, Saturating, + SimpleArithmetic, StaticLookup, Zero, }, weights::SimpleDispatchInfo, RuntimeDebug, @@ -44,7 +45,7 @@ mod tests; pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; use darwinia_support::{ traits::LockableCurrency, - types::{BalanceLock, Id}, + types::{BalanceLock, TimeStamp}, }; pub trait Subtrait: system::Trait + timestamp::Trait { @@ -234,7 +235,8 @@ decl_storage! { pub ReservedBalance get(fn reserved_balance): map T::AccountId => T::Balance; /// Any liquidity locks on some account balances. - pub Locks get(fn locks): map T::AccountId => Vec>; + /// Locks: (StakingBalance, Locks) + pub Locks get(locks): map T::AccountId => (BalanceLock, Vec>); } add_extra_genesis { config(balances): Vec<(T::AccountId, T::Balance)>; @@ -723,7 +725,7 @@ where // # fn ensure_can_withdraw( who: &T::AccountId, - _amount: T::Balance, + amount: T::Balance, reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { @@ -732,47 +734,26 @@ where { return Err("vesting balance too high to send value"); } - let locks = Self::locks(who); - if locks.is_empty() { + let (staking_lock, unbonding_locks) = Self::locks(who); + if unbonding_locks.is_empty() { return Ok(()); } - // 1. for Lock: - // currently, we only use `WithdrawReasons::all()` in staking module - // so the `!lock.reasons.intersects(reasons)` always be `false` - // 2. for Staking Lock: - // make sure the free Kton > the Kton which in Staking - // 3. for Unbonding Lock: - // because of the rule 1, only check if is expired let now = >::now(); - let mut can_withdraw_staking = false; - let mut can_withdraw_unbonding = false; - for lock in locks { - match lock.id { - Id::Staking(_) => { - can_withdraw_staking = !lock.valid_at(&now); - if can_withdraw_staking { - continue; - } - - can_withdraw_staking = new_balance >= lock.amount; - if can_withdraw_staking { - continue; - } - - // due to rule 1 - can_withdraw_staking = false; - // TODO: for complex reasons, in the future - // can_withdraw = !lock.reasons.intersects(reasons); - } - Id::Unbonding(_) => { - // due to rule 3 - can_withdraw_unbonding = !lock.valid_at(&now); + let ok_with_staking_lock = new_balance >= staking_lock.amount || !staking_lock.reasons.intersects(reasons); + let ok_with_unbonding_lock = { + let mut locked_amount = T::Balance::zero(); + for unbonding_lock in unbonding_locks.into_iter() { + if unbonding_lock.valid_at(now) && unbonding_lock.reasons.intersects(reasons) { + // TODO: check overflow? + locked_amount += unbonding_lock.amount; } } - } - if can_withdraw_unbonding || can_withdraw_staking { + // TODO: check underflow? + Self::free_balance(who) - locked_amount >= amount + }; + if ok_with_staking_lock || ok_with_unbonding_lock { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -1013,26 +994,71 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Id = Id; + type Moment = T::Moment; type WithdrawReasons = WithdrawReasons; - fn set_lock(who: &T::AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons) { - Self::remove_lock(who, &id); - >::mutate(who, |locks| { - locks.push(BalanceLock { id, amount, reasons }); + fn set_lock( + who: &T::AccountId, + amount: Self::Balance, + at: Self::Moment, + reasons: Self::WithdrawReasons, + ) -> Self::Balance { + let now = >::now(); + let mut expired_locks_amount = Self::Balance::default(); + + if at.saturated_into::() == 0 { + >::mutate(who, |(staking_lock, unbonding_locks)| { + staking_lock.amount = amount; + staking_lock.reasons = reasons; + + unbonding_locks.retain(|unbonding_lock| { + if unbonding_lock.valid_at(now) { + true + } else { + expired_locks_amount += unbonding_lock.amount; + false + } + }); + }); + + return expired_locks_amount; + } + + let mut new_lock = Some(BalanceLock { amount, at, reasons }); + let mut unbonding_locks = Self::locks(who) + .1 + .into_iter() + .filter_map(|lock| { + if lock.at == at { + new_lock.take() + } else if lock.valid_at(now) { + Some(lock) + } else { + // TODO: check overflow? + expired_locks_amount += lock.amount; + None + } + }) + .collect::>(); + if let Some(lock) = new_lock { + unbonding_locks.push(lock) + } + >::mutate(who, |(_, unbonding_locks_)| { + *unbonding_locks_ = unbonding_locks; }); + + expired_locks_amount } - fn remove_lock(who: &T::AccountId, id: &Self::Id) { + fn remove_lock(who: &T::AccountId, at: Self::Moment) { let now = >::now(); - >::mutate(who, |locks| { - // unexpired and mismatched id -> keep - locks.retain(|lock| lock.valid_at(&now) && &lock.id != id); + >::mutate(who, |(_, locks)| { + locks.retain(|lock| lock.valid_at(now) && lock.at != at); }); } fn locks_count(who: &T::AccountId) -> u32 { - >::get(&who).len() as _ + >::get(&who).1.len() as _ } } diff --git a/srml/chainrelay/bridge/ethereum/src/lib.rs b/srml/chainrelay/bridge/ethereum/src/lib.rs index f9ad86b8b..7f84fb172 100644 --- a/srml/chainrelay/bridge/ethereum/src/lib.rs +++ b/srml/chainrelay/bridge/ethereum/src/lib.rs @@ -9,15 +9,12 @@ use rstd::vec::Vec; use support::{decl_event, decl_module, decl_storage, dispatch::Result, traits::Currency}; use system::ensure_signed; -use darwinia_support::{ - traits::LockableCurrency, - types::{Id, TimeStamp}, -}; -use merkle_mountain_range::{Hash, MerkleMountainRange}; +use darwinia_support::{traits::LockableCurrency, types::TimeStamp}; +//use merkle_mountain_range::{Hash, MerkleMountainRange}; pub trait Trait: system::Trait { type Event: From> + Into<::Event>; - type Ring: LockableCurrency>; + type Ring: LockableCurrency; } // config() require `serde = { version = "1.0.101", optional = true }` diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 9eba75e27..2d57715e4 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -1,11 +1,15 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Codec, Decode, Encode}; +#[cfg(not(feature = "std"))] +use rstd::borrow::ToOwned; use rstd::{cmp, fmt::Debug, prelude::*, result}; +#[cfg(feature = "std")] +use sr_primitives::traits::One; use sr_primitives::{ traits::{ - Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, One, Saturating, SimpleArithmetic, - StaticLookup, Zero, + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SaturatedConversion, Saturating, + SimpleArithmetic, StaticLookup, Zero, }, RuntimeDebug, }; @@ -22,7 +26,7 @@ use system::ensure_signed; use darwinia_support::{ traits::LockableCurrency, - types::{BalanceLock, Id}, + types::{BalanceLock, TimeStamp}, }; use imbalance::{NegativeImbalance, PositiveImbalance}; @@ -101,7 +105,8 @@ decl_storage! { pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance; - pub Locks get(locks): map T::AccountId => Vec>; + /// Locks: (Staking Lock, Unbonding Locks) + pub Locks get(locks): map T::AccountId => (BalanceLock, Vec>); pub TotalLock get(total_lock): T::Balance; @@ -218,7 +223,7 @@ impl Currency for Module { fn ensure_can_withdraw( who: &T::AccountId, - _amount: T::Balance, + amount: T::Balance, reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { @@ -227,47 +232,26 @@ impl Currency for Module { { return Err("vesting balance too high to send value"); } - let locks = Self::locks(who); - if locks.is_empty() { + let (staking_lock, unbonding_locks) = Self::locks(who); + if unbonding_locks.is_empty() { return Ok(()); } - // 1. for Lock: - // currently, we only use `WithdrawReasons::all()` in staking module - // so the `!lock.reasons.intersects(reasons)` always be `false` - // 2. for Staking Lock: - // make sure the free Kton > the Kton which in Staking - // 3. for Unbonding Lock: - // because of the rule 1, only check if is expired let now = >::now(); - let mut can_withdraw_staking = false; - let mut can_withdraw_unbonding = false; - for lock in locks { - match lock.id { - Id::Staking(_) => { - can_withdraw_staking = !lock.valid_at(&now); - if can_withdraw_staking { - continue; - } - - can_withdraw_staking = new_balance >= lock.amount; - if can_withdraw_staking { - continue; - } - - // due to rule 1 - can_withdraw_staking = false; - // TODO: for complex reasons, in the future - // can_withdraw = !lock.reasons.intersects(reasons); - } - Id::Unbonding(_) => { - // due to rule 3 - can_withdraw_unbonding = !lock.valid_at(&now); + let ok_with_staking_lock = new_balance >= staking_lock.amount || !staking_lock.reasons.intersects(reasons); + let ok_with_unbonding_lock = { + let mut locked_amount = T::Balance::zero(); + for unbonding_lock in unbonding_locks.into_iter() { + if unbonding_lock.valid_at(now) && unbonding_lock.reasons.intersects(reasons) { + // TODO: check overflow? + locked_amount += unbonding_lock.amount; } } - } - if can_withdraw_unbonding || can_withdraw_staking { + // TODO: check underflow? + Self::free_balance(who) - locked_amount >= amount + }; + if ok_with_staking_lock || ok_with_unbonding_lock { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -409,25 +393,70 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Id = Id; + type Moment = T::Moment; type WithdrawReasons = WithdrawReasons; - fn set_lock(who: &T::AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons) { - Self::remove_lock(who, &id); - >::mutate(who, |locks| { - locks.push(BalanceLock { id, amount, reasons }); + fn set_lock( + who: &T::AccountId, + amount: Self::Balance, + at: Self::Moment, + reasons: Self::WithdrawReasons, + ) -> Self::Balance { + let now = >::now(); + let mut expired_locks_amount = Self::Balance::default(); + + if at.saturated_into::() == 0 { + >::mutate(who, |(staking_lock, unbonding_locks)| { + staking_lock.amount = amount; + staking_lock.reasons = reasons; + + unbonding_locks.retain(|unbonding_lock| { + if unbonding_lock.valid_at(now) { + true + } else { + expired_locks_amount += unbonding_lock.amount; + false + } + }); + }); + + return expired_locks_amount; + } + + let mut new_lock = Some(BalanceLock { amount, at, reasons }); + let mut unbonding_locks = Self::locks(who) + .1 + .into_iter() + .filter_map(|lock| { + if lock.at == at { + new_lock.take() + } else if lock.valid_at(now) { + Some(lock) + } else { + // TODO: check overflow? + expired_locks_amount += lock.amount; + None + } + }) + .collect::>(); + if let Some(lock) = new_lock { + unbonding_locks.push(lock) + } + >::mutate(who, |(_, unbonding_locks_)| { + *unbonding_locks_ = unbonding_locks; }); + + expired_locks_amount } - fn remove_lock(who: &T::AccountId, id: &Self::Id) { + fn remove_lock(who: &T::AccountId, at: Self::Moment) { let now = >::now(); - >::mutate(who, |locks| { - // unexpired and mismatched id -> keep - locks.retain(|lock| lock.valid_at(&now) && &lock.id != id); + >::mutate(who, |(_, locks)| { + locks.retain(|lock| lock.valid_at(now) && lock.at != at); }); } fn locks_count(who: &T::AccountId) -> u32 { - >::get(&who).len() as _ + >::get(&who).1.len() as _ } } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 4a33c1e10..afaaea2e2 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -36,10 +36,7 @@ use srml_support::{ }; use system::{ensure_root, ensure_signed}; -use darwinia_support::{ - traits::LockableCurrency, - types::{Id, TimeStamp}, -}; +use darwinia_support::{traits::LockableCurrency, types::TimeStamp}; use phragmen::{elect, equalize, ExtendedBalance, PhragmenStakedAssignment, Support, SupportMap}; mod utils; @@ -201,8 +198,8 @@ type KtonPositiveImbalanceOf = <::Kton as Currency< = <::Kton as Currency<::AccountId>>::NegativeImbalance; pub trait Trait: timestamp::Trait + session::Trait { - type Ring: LockableCurrency, WithdrawReasons = WithdrawReasons>; - type Kton: LockableCurrency, WithdrawReasons = WithdrawReasons>; + type Ring: LockableCurrency; + type Kton: LockableCurrency; type CurrencyToVote: Convert, u64> + Convert>; @@ -352,7 +349,7 @@ decl_module! { promise_month: u32 ) { let stash = ensure_signed(origin)?; - ensure!( promise_month <= 36, "months at most is 36."); + ensure!(promise_month <= 36, "months at most is 36."); if >::exists(&stash) { return Err("stash already bonded") @@ -425,8 +422,10 @@ decl_module! { let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; let StakingLedgers { stash, + total_ring, active_ring, active_deposit_ring, + total_kton, active_kton, .. } = &mut ledger; @@ -436,8 +435,7 @@ decl_module! { "can not schedule more unlock chunks" ); - let until = >::now().saturated_into::() + T::BondingDuration::get(); - + let at = >::now().saturated_into::() + T::BondingDuration::get(); match value { StakingBalance::Ring(r) => { // total_active_ring = normal_ring + time_deposit_ring @@ -451,9 +449,11 @@ decl_module! { if !available_unbond_ring.is_zero() { *active_ring -= available_unbond_ring; // TODO: ok? - T::Ring::set_lock(stash, Id::Unbonding(until), available_unbond_ring, WithdrawReasons::all()); + let expired_locks_ring = T::Ring::set_lock(stash, available_unbond_ring, at, WithdrawReasons::all()); + // TODO: check underflow? + *total_ring -= expired_locks_ring; - Self::update_ledger(&controller, &ledger, value); + Self::update_ledger(&controller, &mut ledger, value); } }, StakingBalance::Kton(k) => { @@ -464,9 +464,11 @@ decl_module! { *active_kton -= unbond_kton; // TODO: ok? - T::Kton::set_lock(stash, Id::Unbonding(until), unbond_kton, WithdrawReasons::all()); + let expired_locks_kton = T::Kton::set_lock(stash, unbond_kton, at, WithdrawReasons::all()); + // TODO: check underflow? + *total_kton -= expired_locks_kton; - Self::update_ledger(&controller, &ledger, value); + Self::update_ledger(&controller, &mut ledger, value); } }, } @@ -476,7 +478,7 @@ decl_module! { fn deposit_extra(origin, value: RingBalanceOf, promise_month: u32) { let controller = ensure_signed(origin)?; - ensure!( promise_month <= 36, "months at most is 36."); + ensure!(promise_month <= 36, "months at most is 36."); let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; let StakingLedgers { active_ring, @@ -502,7 +504,7 @@ decl_module! { let kton_positive_imbalance = T::Kton::deposit_creating(stash, kton_return); T::KtonReward::on_unbalanced(kton_positive_imbalance); - let expire_time = now.clone() + (MONTH_IN_SECONDS * promise_month).into(); + let expire_time = now + (MONTH_IN_SECONDS * promise_month).into(); deposit_items.push(TimeDepositItem { value, start_time: now, @@ -535,7 +537,7 @@ decl_module! { if item.expire_time == expire_time { // at least 1 month let month_left = ( - (expire_time.clone() - now.clone()).saturated_into::() + (expire_time - now).saturated_into::() / MONTH_IN_SECONDS ).max(1); let kton_slash = utils::compute_kton_return::(item.value, month_left) * 3.into(); @@ -567,54 +569,6 @@ decl_module! { }); } - /// may both withdraw ring and kton at the same time -// TODO -// fn withdraw_unbonded(origin) { -// let controller = ensure_signed(origin)?; -// let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; -// let StakingLedgers { -// total_ring, -// total_kton, -// unlocking, -// .. -// } = &mut ledger; -// let mut balance_kind = 0u8; -// let current_era = Self::current_era(); -// -// unlocking.retain(|UnlockChunk { -// value, -// era, -// }| { -// if *era > current_era { -// return true; -// } -// -// match value { -// StakingBalance::Ring(ring) => { -// balance_kind |= 0b01; -// *total_ring = total_ring.saturating_sub(*ring); -// } -// StakingBalance::Kton(kton) => { -// balance_kind |= 0b10; -// *total_kton = total_kton.saturating_sub(*kton); -// } -// } -// -// false -// }); -// -// match balance_kind { -// 0 => (), -// 1 => Self::update_ledger(&controller, &ledger, StakingBalance::Ring(0.into())), -// 2 => Self::update_ledger(&controller, &ledger, StakingBalance::Kton(0.into())), -// 3 => { -// Self::update_ledger(&controller, &ledger, StakingBalance::Ring(0.into())); -// Self::update_ledger(&controller, &ledger, StakingBalance::Kton(0.into())); -// } -// _ => unreachable!(), -// } -// } - fn validate(origin, name: Vec, ratio: u32, unstake_threshold: u32) { let controller = ensure_signed(origin)?; let ledger = Self::ledger(&controller).ok_or("not a controller")?; @@ -753,7 +707,7 @@ impl Module { let kton_positive_imbalance = T::Kton::deposit_creating(&stash, kton_return); T::KtonReward::on_unbalanced(kton_positive_imbalance); let now = >::now(); - let expire_time = now.clone() + (MONTH_IN_SECONDS * promise_month).into(); + let expire_time = now + (MONTH_IN_SECONDS * promise_month).into(); ledger.deposit_items.push(TimeDepositItem { value, start_time: now, @@ -763,7 +717,7 @@ impl Module { ledger.active_ring = ledger.active_ring.saturating_add(value); ledger.total_ring = ledger.total_ring.saturating_add(value); - Self::update_ledger(&controller, &ledger, StakingBalance::Ring(value)); + Self::update_ledger(&controller, &mut ledger, StakingBalance::Ring(value)); } fn bond_helper_in_kton( @@ -774,27 +728,25 @@ impl Module { ledger.total_kton += value; ledger.active_kton += value; - Self::update_ledger(&controller, &ledger, StakingBalance::Kton(value)); + Self::update_ledger(&controller, &mut ledger, StakingBalance::Kton(value)); } fn update_ledger( controller: &T::AccountId, - ledger: &StakingLedgers, KtonBalanceOf, T::Moment>, + ledger: &mut StakingLedgers, KtonBalanceOf, T::Moment>, staking_balance: StakingBalance, KtonBalanceOf>, ) { match staking_balance { - StakingBalance::Ring(_r) => T::Ring::set_lock( - &ledger.stash, - Id::Staking(TimeStamp::max_value()), - ledger.total_ring, - WithdrawReasons::all(), - ), - StakingBalance::Kton(_k) => T::Kton::set_lock( - &ledger.stash, - Id::Staking(TimeStamp::max_value()), - ledger.total_kton, - WithdrawReasons::all(), - ), + StakingBalance::Ring(_r) => { + let expired_locks_ring = T::Ring::set_lock(&ledger.stash, ledger.total_ring, 0, WithdrawReasons::all()); + // TODO: check underflow? + ledger.total_ring -= expired_locks_ring; + } + StakingBalance::Kton(_k) => { + let expired_locks_kton = T::Kton::set_lock(&ledger.stash, ledger.total_kton, 0, WithdrawReasons::all()); + // TODO: check underflow? + ledger.total_kton -= expired_locks_kton; + } } >::insert(controller, ledger); @@ -879,9 +831,8 @@ impl Module { // from the nearest expire time if !value_left.is_zero() { // sorted by expire_time from far to near - deposit_items.sort_unstable_by_key(|item| { - u64::max_value() - item.expire_time.clone().saturated_into::() - }); + deposit_items + .sort_unstable_by_key(|item| u64::max_value() - item.expire_time.saturated_into::()); deposit_items.drain_filter(|item| { if value_left.is_zero() { return false; diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index dd31cf2bd..ee311a143 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -4,6 +4,8 @@ use crate::mock::*; use srml_support::traits::{Currency, WithdrawReason, WithdrawReasons}; use srml_support::{assert_err, assert_ok}; +use darwinia_support::types::BalanceLock; + // gen_paired_account!(a(1), b(2), m(12)); // will create stash `a` and controller `b` // `a` has 100 Ring and 100 Kton @@ -1212,6 +1214,7 @@ fn xavier_q1() { let controller = 456; Kton::deposit_creating(&stash, 10); + Timestamp::set_timestamp(0); assert_ok!(Staking::bond( Origin::signed(stash), controller, @@ -1219,28 +1222,277 @@ fn xavier_q1() { RewardDestination::Stash, 0, )); - println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); - println!("Ok Init - Kton Locks: {:?}", Kton::locks(stash)); + assert_eq!(Timestamp::get(), 0); + assert_eq!(Kton::free_balance(stash), 10); + assert_eq!( + Kton::locks(stash), + ( + BalanceLock { + amount: 5, + at: 0, + reasons: WithdrawReasons::all(), + }, + vec![], + ) + ); + // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); + Timestamp::set_timestamp(1); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(5), 0)); - println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); - println!("Ok Bond Extra - Kton Locks: {:?}", Kton::locks(stash)); + assert_eq!(Timestamp::get(), 1); + assert_eq!(Kton::free_balance(stash), 10); + assert_eq!( + Kton::locks(stash), + ( + BalanceLock { + amount: 10, + at: 0, + reasons: WithdrawReasons::all(), + }, + vec![], + ) + ); + // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); - Timestamp::set_timestamp(0); + let unbond_start = 2; + Timestamp::set_timestamp(unbond_start); assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(10))); - println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); - println!("Ok Unbond - Kton Locks: {:?}", Kton::locks(stash)); + assert_eq!(Timestamp::get(), 2); + assert_eq!(Kton::free_balance(stash), 10); + assert_eq!( + Kton::locks(stash), + ( + BalanceLock { + amount: 10, + at: 0, + reasons: WithdrawReasons::all(), + }, + vec![BalanceLock { + amount: 10, + at: BondingDuration::get() + unbond_start, + reasons: WithdrawReasons::all() + }], + ) + ); + // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); assert_err!( Kton::transfer(Origin::signed(stash), controller, 1), "account liquidity restrictions prevent withdrawal" ); - println!("Locking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); - println!("Locking Transfer - Kton Locks: {:?}", Kton::locks(stash)); + // println!("Locking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Locking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); - Timestamp::set_timestamp(BondingDuration::get()); + Timestamp::set_timestamp(BondingDuration::get() + unbond_start); assert_ok!(Kton::transfer(Origin::signed(stash), controller, 1)); - println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); - println!("Unlocking Transfer - Kton Locks: {:?}", Kton::locks(stash)); + // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + // println!( + // "Unlocking Transfer - Kton StakingLedgers: {:#?}", + // Staking::ledger(&controller) + // ); + // println!(); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start); + assert_eq!(Kton::free_balance(stash), 9); + assert_eq!( + Kton::locks(stash), + ( + BalanceLock { + amount: 10, + at: 0, + reasons: WithdrawReasons::all(), + }, + vec![BalanceLock { + amount: 10, + at: BondingDuration::get() + unbond_start, + reasons: WithdrawReasons::all() + }], + ) + ); + + Kton::deposit_creating(&stash, 20); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(19), 0)); + assert_eq!(Kton::free_balance(stash), 29); + assert_eq!( + Kton::locks(stash), + ( + BalanceLock { + amount: 29, + at: 0, + reasons: WithdrawReasons::all(), + }, + vec![], + ) + ); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 0, + active_ring: 0, + active_deposit_ring: 0, + total_kton: 19, + active_kton: 19, + deposit_items: vec![], + } + ); + // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + // println!( + // "Unlocking Transfer - Kton StakingLedgers: {:#?}", + // Staking::ledger(&controller) + // ); + // println!(); + }); +} + +#[test] +fn xavier_q2() { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + let stash = 123; + let controller = 456; + Kton::deposit_creating(&stash, 10); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Kton(5), + RewardDestination::Stash, + 0, + )); + // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(5), 0)); + // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); + + let (unbond_value_1, unbond_start_1) = (2, 2); + Timestamp::set_timestamp(unbond_start_1); + assert_ok!(Staking::unbond( + Origin::signed(controller), + StakingBalance::Kton(unbond_value_1) + )); + // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); + + let (unbond_value_2, unbond_start_2) = (7, 3); + Timestamp::set_timestamp(unbond_start_2); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(7))); + // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); + + assert_err!( + Kton::transfer(Origin::signed(stash), controller, unbond_value_1), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Locking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); + + assert_ok!(Kton::transfer(Origin::signed(stash), controller, 1)); + // println!("Normal Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Normal Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start_1); + assert_err!( + Kton::transfer(Origin::signed(stash), controller, 3), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Locking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + // println!(); + // println!("{}", Timestamp::get()); + assert_ok!(Kton::transfer(Origin::signed(stash), controller, 2)); + // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start_2); + assert_ok!(Kton::transfer(Origin::signed(stash), controller, unbond_value_2)); + // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); + // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + }); +} + +#[test] +fn xavier_q3() { + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + let stash = 123; + let controller = 456; + Kton::deposit_creating(&stash, 10); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Kton(5), + RewardDestination::Stash, + 0, + )); + assert_eq!(Timestamp::get(), 1); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 0, + active_ring: 0, + active_deposit_ring: 0, + total_kton: 5, + active_kton: 5, + deposit_items: vec![], + } + ); + // println!("Locks: {:#?}", Kton::locks(stash)); + // println!("StakingLedgers: {:#?}", Staking::ledger(&controller)); + // println!(); + + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(5))); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 0, + active_ring: 0, + active_deposit_ring: 0, + total_kton: 5, + active_kton: 0, + deposit_items: vec![], + } + ); + // println!("Locks: {:#?}", Kton::locks(stash)); + // println!("StakingLedgers: {:#?}", Staking::ledger(&controller)); + // println!(); + + Timestamp::set_timestamp(61); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 0)); + assert_eq!(Timestamp::get(), 61); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 0, + active_ring: 0, + active_deposit_ring: 0, + total_kton: 1, + active_kton: 1, + deposit_items: vec![], + } + ); + // println!("Locks: {:#?}", Kton::locks(stash)); + // println!("StakingLedgers: {:#?}", Staking::ledger(&controller)); + // println!(); }); } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index d0f93af74..12c9a1b06 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -23,34 +23,41 @@ pub trait OnAccountBalanceChanged { /// A more powerful lockable currency. pub trait LockableCurrency: Currency { + /// The quantity used to denote time; usually just a `BlockNumber`. + /// In Darwinia we prefer using `TimeStamp/u64`. + type Moment; + /// Customize our `WithdrawReasons` + type WithdrawReasons; + /// Recommend to define `Id` as below and customize `PartialEq` to differentiate the locks: /// ```rust - /// #[derive(Eq, Clone, Encode, Decode, RuntimeDebug)] - /// pub enum Id { - /// Staking(Moment), - /// Unbonding(Moment), + /// #[derive(Clone, Default, Eq, Encode, Decode, RuntimeDebug)] + /// pub struct Lock { + /// pub amount: Balance, + /// pub until: Moment, + /// pub reasons: WithdrawReasons, /// } /// ``` - /// Moment: - /// - The quantity used to denote time; usually just a `BlockNumber`. - /// - In Darwinia we prefer using `TimeStamp/u64`. - type Id; - /// Customize our `WithdrawReasons` - type WithdrawReasons; - /// Create a new balance lock on account `who`. /// /// If the new lock is valid (i.e. not already expired), it will push the struct to /// the `Locks` vec in storage. Note that you can lock more funds than a user has. /// - /// If the lock `id` already exists, this will update it. - fn set_lock(who: &AccountId, id: Self::Id, amount: Self::Balance, reasons: Self::WithdrawReasons); + /// If the lock `id/until` already exists, this will update it. + /// + /// The function will return the sum of expired locks' amount + fn set_lock( + who: &AccountId, + amount: Self::Balance, + at: Self::Moment, + reasons: Self::WithdrawReasons, + ) -> Self::Balance; // TODO: reserve // fn extend_lock(); /// Remove an existing lock. - fn remove_lock(who: &AccountId, id: &Self::Id); + fn remove_lock(who: &AccountId, at: Self::Moment); /// The number of locks. fn locks_count(who: &AccountId) -> u32; diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 906fa4c39..a7dafb810 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -4,60 +4,41 @@ use srml_support::traits::WithdrawReasons; pub type TimeStamp = u64; -#[derive(Eq, Clone, Encode, Decode, RuntimeDebug)] +#[derive(Clone, Eq, Encode, Decode, RuntimeDebug)] pub struct BalanceLock { - pub id: Id, pub amount: Balance, + pub at: Moment, pub reasons: WithdrawReasons, } -impl BalanceLock +impl Default for BalanceLock where - Moment: PartialOrd, + Balance: Default, + Moment: Default, { - pub fn valid_at(&self, at: &Moment) -> bool { - self.id.until() > at + fn default() -> Self { + Self { + amount: Balance::default(), + at: Moment::default(), + reasons: WithdrawReasons::all(), + } } } -impl PartialEq for BalanceLock +impl BalanceLock where - Moment: PartialEq, + Moment: PartialOrd, { - fn eq(&self, other: &Self) -> bool { - self.id == other.id + pub fn valid_at(&self, at: Moment) -> bool { + self.at > at } } -#[derive(Eq, Clone, Encode, Decode, RuntimeDebug)] -pub enum Id { - Staking(Moment), - Unbonding(Moment), -} - -impl Id { - fn until(&self) -> &Moment { - match self { - Id::Staking(moment) => moment, - Id::Unbonding(moment) => moment, - } - } -} - -impl PartialEq for Id +impl PartialEq for BalanceLock where Moment: PartialEq, { fn eq(&self, other: &Self) -> bool { - match self { - Id::Staking(_) => match other { - Id::Staking(_) => true, - _ => false, - }, - Id::Unbonding(moment) => match other { - Id::Unbonding(moment_) => moment == moment_, - _ => false, - }, - } + self.at == other.at } } From a7885eb04dfc7e7e3f6ca0a27e873b7f20b2b536 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Mon, 18 Nov 2019 18:04:44 +0800 Subject: [PATCH 09/30] update: ok with `set_lock` --- srml/staking/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index afaaea2e2..4d2f5cf7e 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -448,7 +448,6 @@ decl_module! { if !available_unbond_ring.is_zero() { *active_ring -= available_unbond_ring; -// TODO: ok? let expired_locks_ring = T::Ring::set_lock(stash, available_unbond_ring, at, WithdrawReasons::all()); // TODO: check underflow? *total_ring -= expired_locks_ring; @@ -463,7 +462,6 @@ decl_module! { >::mutate(|k| *k -= unbond_kton); *active_kton -= unbond_kton; -// TODO: ok? let expired_locks_kton = T::Kton::set_lock(stash, unbond_kton, at, WithdrawReasons::all()); // TODO: check underflow? *total_kton -= expired_locks_kton; From 0f26ef0ecc71515e8052b65c2d7c2b1a1cb961fd Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Mon, 18 Nov 2019 18:16:17 +0800 Subject: [PATCH 10/30] update: now, we can update the staking ledger after `remove_lock` --- srml/balances/src/lib.rs | 17 ++++++++++++++--- srml/kton/src/lib.rs | 17 ++++++++++++++--- srml/support/src/traits.rs | 6 ++++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index fe7cc7e46..155f556ec 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -1050,11 +1050,22 @@ where expired_locks_amount } - fn remove_lock(who: &T::AccountId, at: Self::Moment) { + fn remove_lock(who: &T::AccountId, at: Self::Moment) -> Self::Balance { let now = >::now(); - >::mutate(who, |(_, locks)| { - locks.retain(|lock| lock.valid_at(now) && lock.at != at); + let mut expired_locks_amount = Self::Balance::default(); + + >::mutate(who, |(_, unbonding_locks)| { + unbonding_locks.retain(|unbonding_lock| { + if unbonding_lock.valid_at(now) && unbonding_lock.at != at { + true + } else { + expired_locks_amount += unbonding_lock.amount; + false + } + }); }); + + expired_locks_amount } fn locks_count(who: &T::AccountId) -> u32 { diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 2d57715e4..d8da8f8ee 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -449,11 +449,22 @@ where expired_locks_amount } - fn remove_lock(who: &T::AccountId, at: Self::Moment) { + fn remove_lock(who: &T::AccountId, at: Self::Moment) -> Self::Balance { let now = >::now(); - >::mutate(who, |(_, locks)| { - locks.retain(|lock| lock.valid_at(now) && lock.at != at); + let mut expired_locks_amount = Self::Balance::default(); + + >::mutate(who, |(_, unbonding_locks)| { + unbonding_locks.retain(|unbonding_lock| { + if unbonding_lock.valid_at(now) && unbonding_lock.at != at { + true + } else { + expired_locks_amount += unbonding_lock.amount; + false + } + }); }); + + expired_locks_amount } fn locks_count(who: &T::AccountId) -> u32 { diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 12c9a1b06..1efb0c6d5 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -45,7 +45,7 @@ pub trait LockableCurrency: Currency { /// /// If the lock `id/until` already exists, this will update it. /// - /// The function will return the sum of expired locks' amount + /// The function will return the sum of expired locks' amount. fn set_lock( who: &AccountId, amount: Self::Balance, @@ -57,7 +57,9 @@ pub trait LockableCurrency: Currency { // fn extend_lock(); /// Remove an existing lock. - fn remove_lock(who: &AccountId, at: Self::Moment); + /// + /// The function will return the sum of expired locks' amount. + fn remove_lock(who: &AccountId, at: Self::Moment) -> Self::Balance; /// The number of locks. fn locks_count(who: &AccountId) -> u32; From ad5028def6b2229894ba5cc486cf71434e2a6f01 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 12:33:15 +0800 Subject: [PATCH 11/30] update: more powerful lock --- srml/balances/src/lib.rs | 121 ++++++++++++++-------------- srml/kton/src/lib.rs | 123 ++++++++++++++--------------- srml/staking/src/lib.rs | 71 ++++++++++++++--- srml/staking/src/tests.rs | 158 ++++++++++++++++++++++++++----------- srml/support/src/traits.rs | 34 +++----- srml/support/src/types.rs | 53 +++++++++---- 6 files changed, 342 insertions(+), 218 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 155f556ec..aa004d1f7 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -22,8 +22,8 @@ use rstd::borrow::ToOwned; use rstd::{cmp, fmt::Debug, mem, prelude::*, result}; use sr_primitives::{ traits::{ - Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SaturatedConversion, Saturating, - SimpleArithmetic, StaticLookup, Zero, + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Saturating, SimpleArithmetic, StaticLookup, + Zero, }, weights::SimpleDispatchInfo, RuntimeDebug, @@ -45,7 +45,7 @@ mod tests; pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; use darwinia_support::{ traits::LockableCurrency, - types::{BalanceLock, TimeStamp}, + types::{CompositeLock, LockUpdateStrategy}, }; pub trait Subtrait: system::Trait + timestamp::Trait { @@ -235,8 +235,7 @@ decl_storage! { pub ReservedBalance get(fn reserved_balance): map T::AccountId => T::Balance; /// Any liquidity locks on some account balances. - /// Locks: (StakingBalance, Locks) - pub Locks get(locks): map T::AccountId => (BalanceLock, Vec>); + pub Locks get(locks): map T::AccountId => CompositeLock; } add_extra_genesis { config(balances): Vec<(T::AccountId, T::Balance)>; @@ -734,30 +733,34 @@ where { return Err("vesting balance too high to send value"); } - let (staking_lock, unbonding_locks) = Self::locks(who); - if unbonding_locks.is_empty() { + + let composite_lock = Self::locks(who); + + if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { return Ok(()); } - let now = >::now(); - let ok_with_staking_lock = new_balance >= staking_lock.amount || !staking_lock.reasons.intersects(reasons); - let ok_with_unbonding_lock = { + if new_balance >= composite_lock.staking_amount { + return Ok(()); + } + + if { + let now = >::now(); let mut locked_amount = T::Balance::zero(); - for unbonding_lock in unbonding_locks.into_iter() { - if unbonding_lock.valid_at(now) && unbonding_lock.reasons.intersects(reasons) { + for lock in composite_lock.locks.into_iter() { + if lock.valid_at(now) && lock.reasons.intersects(reasons) { // TODO: check overflow? - locked_amount += unbonding_lock.amount; + locked_amount += lock.amount; } } // TODO: check underflow? Self::free_balance(who) - locked_amount >= amount - }; - if ok_with_staking_lock || ok_with_unbonding_lock { - Ok(()) - } else { - Err("account liquidity restrictions prevent withdrawal") + } { + return Ok(()); } + + Err("account liquidity restrictions prevent withdrawal") } fn transfer( @@ -994,57 +997,53 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { + type LockUpdateStrategy = LockUpdateStrategy; type Moment = T::Moment; - type WithdrawReasons = WithdrawReasons; - fn set_lock( - who: &T::AccountId, - amount: Self::Balance, - at: Self::Moment, - reasons: Self::WithdrawReasons, - ) -> Self::Balance { + fn update_lock(who: &T::AccountId, lock_update_strategy: Self::LockUpdateStrategy) -> Self::Balance { let now = >::now(); let mut expired_locks_amount = Self::Balance::default(); + let mut composite_lock = Self::locks(who); - if at.saturated_into::() == 0 { - >::mutate(who, |(staking_lock, unbonding_locks)| { - staking_lock.amount = amount; - staking_lock.reasons = reasons; - - unbonding_locks.retain(|unbonding_lock| { - if unbonding_lock.valid_at(now) { + if let Some(staking_amount) = lock_update_strategy.staking_amount { + composite_lock.staking_amount = staking_amount; + } + if let Some(lock) = lock_update_strategy.lock { + let at = lock.at; + let mut new_lock = Some(lock); + composite_lock.locks = composite_lock + .locks + .into_iter() + .filter_map(|lock| { + if lock.at == at { + new_lock.take() + } else if lock.valid_at(now) { + Some(lock) + } else { + // TODO: check overflow? + expired_locks_amount += lock.amount; + None + } + }) + .collect::>(); + if let Some(lock) = new_lock { + composite_lock.locks.push(lock); + } + } else if lock_update_strategy.check_expired { + >::mutate(who, |composite_lock| { + composite_lock.locks.retain(|lock| { + if lock.valid_at(now) { true } else { - expired_locks_amount += unbonding_lock.amount; + expired_locks_amount += lock.amount; false } }); }); - - return expired_locks_amount; } - let mut new_lock = Some(BalanceLock { amount, at, reasons }); - let mut unbonding_locks = Self::locks(who) - .1 - .into_iter() - .filter_map(|lock| { - if lock.at == at { - new_lock.take() - } else if lock.valid_at(now) { - Some(lock) - } else { - // TODO: check overflow? - expired_locks_amount += lock.amount; - None - } - }) - .collect::>(); - if let Some(lock) = new_lock { - unbonding_locks.push(lock) - } - >::mutate(who, |(_, unbonding_locks_)| { - *unbonding_locks_ = unbonding_locks; + >::mutate(who, |composite_lock_| { + *composite_lock_ = composite_lock; }); expired_locks_amount @@ -1054,12 +1053,12 @@ where let now = >::now(); let mut expired_locks_amount = Self::Balance::default(); - >::mutate(who, |(_, unbonding_locks)| { - unbonding_locks.retain(|unbonding_lock| { - if unbonding_lock.valid_at(now) && unbonding_lock.at != at { + >::mutate(who, |composite_lock| { + composite_lock.locks.retain(|lock| { + if lock.valid_at(now) && lock.at != at { true } else { - expired_locks_amount += unbonding_lock.amount; + expired_locks_amount += lock.amount; false } }); @@ -1069,7 +1068,7 @@ where } fn locks_count(who: &T::AccountId) -> u32 { - >::get(&who).1.len() as _ + >::get(who).locks.len() as _ } } diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index d8da8f8ee..bd4b14958 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -8,8 +8,8 @@ use rstd::{cmp, fmt::Debug, prelude::*, result}; use sr_primitives::traits::One; use sr_primitives::{ traits::{ - Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, SaturatedConversion, Saturating, - SimpleArithmetic, StaticLookup, Zero, + Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Member, Saturating, SimpleArithmetic, StaticLookup, + Zero, }, RuntimeDebug, }; @@ -26,7 +26,7 @@ use system::ensure_signed; use darwinia_support::{ traits::LockableCurrency, - types::{BalanceLock, TimeStamp}, + types::{CompositeLock, LockUpdateStrategy}, }; use imbalance::{NegativeImbalance, PositiveImbalance}; @@ -105,8 +105,7 @@ decl_storage! { pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance; - /// Locks: (Staking Lock, Unbonding Locks) - pub Locks get(locks): map T::AccountId => (BalanceLock, Vec>); + pub Locks get(locks): map T::AccountId => CompositeLock; pub TotalLock get(total_lock): T::Balance; @@ -232,30 +231,34 @@ impl Currency for Module { { return Err("vesting balance too high to send value"); } - let (staking_lock, unbonding_locks) = Self::locks(who); - if unbonding_locks.is_empty() { + + let composite_lock = Self::locks(who); + + if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { return Ok(()); } - let now = >::now(); - let ok_with_staking_lock = new_balance >= staking_lock.amount || !staking_lock.reasons.intersects(reasons); - let ok_with_unbonding_lock = { + if new_balance >= composite_lock.staking_amount { + return Ok(()); + } + + if { + let now = >::now(); let mut locked_amount = T::Balance::zero(); - for unbonding_lock in unbonding_locks.into_iter() { - if unbonding_lock.valid_at(now) && unbonding_lock.reasons.intersects(reasons) { + for lock in composite_lock.locks.into_iter() { + if lock.valid_at(now) && lock.reasons.intersects(reasons) { // TODO: check overflow? - locked_amount += unbonding_lock.amount; + locked_amount += lock.amount; } } // TODO: check underflow? Self::free_balance(who) - locked_amount >= amount - }; - if ok_with_staking_lock || ok_with_unbonding_lock { - Ok(()) - } else { - Err("account liquidity restrictions prevent withdrawal") + } { + return Ok(()); } + + Err("account liquidity restrictions prevent withdrawal") } // TODO: add fee @@ -393,57 +396,51 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { + type LockUpdateStrategy = LockUpdateStrategy; type Moment = T::Moment; - type WithdrawReasons = WithdrawReasons; - fn set_lock( - who: &T::AccountId, - amount: Self::Balance, - at: Self::Moment, - reasons: Self::WithdrawReasons, - ) -> Self::Balance { + fn update_lock(who: &T::AccountId, lock_update_strategy: Self::LockUpdateStrategy) -> Self::Balance { let now = >::now(); let mut expired_locks_amount = Self::Balance::default(); + let mut composite_lock = Self::locks(who); - if at.saturated_into::() == 0 { - >::mutate(who, |(staking_lock, unbonding_locks)| { - staking_lock.amount = amount; - staking_lock.reasons = reasons; - - unbonding_locks.retain(|unbonding_lock| { - if unbonding_lock.valid_at(now) { - true + if let Some(staking_amount) = lock_update_strategy.staking_amount { + composite_lock.staking_amount = staking_amount; + } + if let Some(lock) = lock_update_strategy.lock { + let at = lock.at; + let mut new_lock = Some(lock); + composite_lock.locks = composite_lock + .locks + .into_iter() + .filter_map(|lock| { + if lock.at == at { + new_lock.take() + } else if lock.valid_at(now) { + Some(lock) } else { - expired_locks_amount += unbonding_lock.amount; - false + // TODO: check overflow? + expired_locks_amount += lock.amount; + None } - }); - }); - - return expired_locks_amount; - } - - let mut new_lock = Some(BalanceLock { amount, at, reasons }); - let mut unbonding_locks = Self::locks(who) - .1 - .into_iter() - .filter_map(|lock| { - if lock.at == at { - new_lock.take() - } else if lock.valid_at(now) { - Some(lock) + }) + .collect::>(); + if let Some(lock) = new_lock { + composite_lock.locks.push(lock); + } + } else if lock_update_strategy.check_expired { + composite_lock.locks.retain(|lock| { + if lock.valid_at(now) { + true } else { - // TODO: check overflow? expired_locks_amount += lock.amount; - None + false } - }) - .collect::>(); - if let Some(lock) = new_lock { - unbonding_locks.push(lock) + }); } - >::mutate(who, |(_, unbonding_locks_)| { - *unbonding_locks_ = unbonding_locks; + + >::mutate(who, |composite_lock_| { + *composite_lock_ = composite_lock; }); expired_locks_amount @@ -453,12 +450,12 @@ where let now = >::now(); let mut expired_locks_amount = Self::Balance::default(); - >::mutate(who, |(_, unbonding_locks)| { - unbonding_locks.retain(|unbonding_lock| { - if unbonding_lock.valid_at(now) && unbonding_lock.at != at { + >::mutate(who, |composite_lock| { + composite_lock.locks.retain(|lock| { + if lock.valid_at(now) && lock.at != at { true } else { - expired_locks_amount += unbonding_lock.amount; + expired_locks_amount += lock.amount; false } }); @@ -468,6 +465,6 @@ where } fn locks_count(who: &T::AccountId) -> u32 { - >::get(&who).1.len() as _ + >::get(who).locks.len() as _ } } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 4d2f5cf7e..7089024cf 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -36,7 +36,10 @@ use srml_support::{ }; use system::{ensure_root, ensure_signed}; -use darwinia_support::{traits::LockableCurrency, types::TimeStamp}; +use darwinia_support::{ + traits::LockableCurrency, + types::{BalanceLock, LockUpdateStrategy, TimeStamp}, +}; use phragmen::{elect, equalize, ExtendedBalance, PhragmenStakedAssignment, Support, SupportMap}; mod utils; @@ -198,8 +201,16 @@ type KtonPositiveImbalanceOf = <::Kton as Currency< = <::Kton as Currency<::AccountId>>::NegativeImbalance; pub trait Trait: timestamp::Trait + session::Trait { - type Ring: LockableCurrency; - type Kton: LockableCurrency; + type Ring: LockableCurrency< + Self::AccountId, + Moment = TimeStamp, + LockUpdateStrategy = LockUpdateStrategy, TimeStamp>, + >; + type Kton: LockableCurrency< + Self::AccountId, + Moment = TimeStamp, + LockUpdateStrategy = LockUpdateStrategy, TimeStamp>, + >; type CurrencyToVote: Convert, u64> + Convert>; @@ -448,7 +459,15 @@ decl_module! { if !available_unbond_ring.is_zero() { *active_ring -= available_unbond_ring; - let expired_locks_ring = T::Ring::set_lock(stash, available_unbond_ring, at, WithdrawReasons::all()); + let expired_locks_ring = T::Ring::update_lock( + stash, + LockUpdateStrategy::new() + .with_lock(BalanceLock { + amount: available_unbond_ring, + at, + reasons: WithdrawReasons::all() + }), + ); // TODO: check underflow? *total_ring -= expired_locks_ring; @@ -462,7 +481,15 @@ decl_module! { >::mutate(|k| *k -= unbond_kton); *active_kton -= unbond_kton; - let expired_locks_kton = T::Kton::set_lock(stash, unbond_kton, at, WithdrawReasons::all()); + let expired_locks_kton = T::Kton::update_lock( + stash, + LockUpdateStrategy::new() + .with_lock(BalanceLock { + amount: unbond_kton, + at, + reasons: WithdrawReasons::all(), + }), + ); // TODO: check underflow? *total_kton -= expired_locks_kton; @@ -736,14 +763,36 @@ impl Module { ) { match staking_balance { StakingBalance::Ring(_r) => { - let expired_locks_ring = T::Ring::set_lock(&ledger.stash, ledger.total_ring, 0, WithdrawReasons::all()); - // TODO: check underflow? - ledger.total_ring -= expired_locks_ring; + let expired_locks_ring = T::Ring::update_lock( + &ledger.stash, + LockUpdateStrategy::new() + .with_check_expired(true) + .with_staking_amount(ledger.total_ring), + ); + if !expired_locks_ring.is_zero() { + // TODO: check underflow? + ledger.total_ring -= expired_locks_ring; + T::Ring::update_lock( + &ledger.stash, + LockUpdateStrategy::new().with_staking_amount(ledger.total_ring), + ); + } } StakingBalance::Kton(_k) => { - let expired_locks_kton = T::Kton::set_lock(&ledger.stash, ledger.total_kton, 0, WithdrawReasons::all()); - // TODO: check underflow? - ledger.total_kton -= expired_locks_kton; + let expired_locks_kton = T::Kton::update_lock( + &ledger.stash, + LockUpdateStrategy::new() + .with_check_expired(true) + .with_staking_amount(ledger.total_kton), + ); + if !expired_locks_kton.is_zero() { + // TODO: check underflow? + ledger.total_kton -= expired_locks_kton; + T::Kton::update_lock( + &ledger.stash, + LockUpdateStrategy::new().with_staking_amount(ledger.total_kton), + ); + } } } diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index ee311a143..ea2a88f9a 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -4,7 +4,7 @@ use crate::mock::*; use srml_support::traits::{Currency, WithdrawReason, WithdrawReasons}; use srml_support::{assert_err, assert_ok}; -use darwinia_support::types::BalanceLock; +use darwinia_support::types::{BalanceLock, CompositeLock}; // gen_paired_account!(a(1), b(2), m(12)); // will create stash `a` and controller `b` @@ -1226,14 +1226,10 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - ( - BalanceLock { - amount: 5, - at: 0, - reasons: WithdrawReasons::all(), - }, - vec![], - ) + CompositeLock { + staking_amount: 5, + locks: vec![] + } ); // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1245,14 +1241,10 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - ( - BalanceLock { - amount: 10, - at: 0, - reasons: WithdrawReasons::all(), - }, - vec![], - ) + CompositeLock { + staking_amount: 10, + locks: vec![] + } ); // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1265,18 +1257,14 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - ( - BalanceLock { - amount: 10, - at: 0, - reasons: WithdrawReasons::all(), - }, - vec![BalanceLock { + CompositeLock { + staking_amount: 10, + locks: vec![BalanceLock { amount: 10, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() - }], - ) + }] + } ); // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1303,18 +1291,14 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 9); assert_eq!( Kton::locks(stash), - ( - BalanceLock { - amount: 10, - at: 0, - reasons: WithdrawReasons::all(), - }, - vec![BalanceLock { + CompositeLock { + staking_amount: 10, + locks: vec![BalanceLock { amount: 10, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() - }], - ) + }] + } ); Kton::deposit_creating(&stash, 20); @@ -1322,14 +1306,10 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 29); assert_eq!( Kton::locks(stash), - ( - BalanceLock { - amount: 29, - at: 0, - reasons: WithdrawReasons::all(), - }, - vec![], - ) + CompositeLock { + staking_amount: 19, + locks: vec![] + } ); assert_eq!( Staking::ledger(&controller).unwrap(), @@ -1368,12 +1348,29 @@ fn xavier_q2() { RewardDestination::Stash, 0, )); + assert_eq!(Kton::free_balance(stash), 10); + assert_eq!( + Kton::locks(stash), + CompositeLock { + staking_amount: 5, + locks: vec![] + } + ); // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); Timestamp::set_timestamp(1); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(5), 0)); + assert_eq!(Timestamp::get(), 1); + assert_eq!(Kton::free_balance(stash), 10); + assert_eq!( + Kton::locks(stash), + CompositeLock { + staking_amount: 10, + locks: vec![] + } + ); // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1384,6 +1381,19 @@ fn xavier_q2() { Origin::signed(controller), StakingBalance::Kton(unbond_value_1) )); + assert_eq!(Timestamp::get(), unbond_start_1); + assert_eq!(Kton::free_balance(stash), 10); + assert_eq!( + Kton::locks(stash), + CompositeLock { + staking_amount: 10, + locks: vec![BalanceLock { + amount: 2, + at: 62, + reasons: WithdrawReasons::all(), + }] + } + ); // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1391,6 +1401,26 @@ fn xavier_q2() { let (unbond_value_2, unbond_start_2) = (7, 3); Timestamp::set_timestamp(unbond_start_2); assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(7))); + assert_eq!(Timestamp::get(), unbond_start_2); + assert_eq!(Kton::free_balance(stash), 10); + assert_eq!( + Kton::locks(stash), + CompositeLock { + staking_amount: 10, + locks: vec![ + BalanceLock { + amount: 2, + at: 62, + reasons: WithdrawReasons::all(), + }, + BalanceLock { + amount: 7, + at: 63, + reasons: WithdrawReasons::all(), + }, + ] + } + ); // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1404,6 +1434,7 @@ fn xavier_q2() { // println!(); assert_ok!(Kton::transfer(Origin::signed(stash), controller, 1)); + assert_eq!(Kton::free_balance(stash), 9); // println!("Normal Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Normal Transfer - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1415,13 +1446,52 @@ fn xavier_q2() { // println!("Locking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Locking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); - // println!("{}", Timestamp::get()); assert_ok!(Kton::transfer(Origin::signed(stash), controller, 2)); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_1); + assert_eq!(Kton::free_balance(stash), 7); + assert_eq!( + Kton::locks(stash), + CompositeLock { + staking_amount: 10, + locks: vec![ + BalanceLock { + amount: 2, + at: 62, + reasons: WithdrawReasons::all(), + }, + BalanceLock { + amount: 7, + at: 63, + reasons: WithdrawReasons::all(), + }, + ] + } + ); // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); Timestamp::set_timestamp(BondingDuration::get() + unbond_start_2); assert_ok!(Kton::transfer(Origin::signed(stash), controller, unbond_value_2)); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_2); + assert_eq!(Kton::free_balance(stash), 0); + assert_eq!( + Kton::locks(stash), + CompositeLock { + staking_amount: 10, + locks: vec![ + BalanceLock { + amount: 2, + at: 62, + reasons: WithdrawReasons::all(), + }, + BalanceLock { + amount: 7, + at: 63, + reasons: WithdrawReasons::all(), + }, + ], + } + ); // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); }); diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 1efb0c6d5..491808aa4 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -23,35 +23,19 @@ pub trait OnAccountBalanceChanged { /// A more powerful lockable currency. pub trait LockableCurrency: Currency { + type LockUpdateStrategy; /// The quantity used to denote time; usually just a `BlockNumber`. /// In Darwinia we prefer using `TimeStamp/u64`. type Moment; - /// Customize our `WithdrawReasons` - type WithdrawReasons; - /// Recommend to define `Id` as below and customize `PartialEq` to differentiate the locks: - /// ```rust - /// #[derive(Clone, Default, Eq, Encode, Decode, RuntimeDebug)] - /// pub struct Lock { - /// pub amount: Balance, - /// pub until: Moment, - /// pub reasons: WithdrawReasons, - /// } - /// ``` - /// Create a new balance lock on account `who`. - /// - /// If the new lock is valid (i.e. not already expired), it will push the struct to - /// the `Locks` vec in storage. Note that you can lock more funds than a user has. - /// - /// If the lock `id/until` already exists, this will update it. - /// - /// The function will return the sum of expired locks' amount. - fn set_lock( - who: &AccountId, - amount: Self::Balance, - at: Self::Moment, - reasons: Self::WithdrawReasons, - ) -> Self::Balance; + /// - Create a new balance lock on account `who`. + /// - If the new lock is valid (i.e. not already expired), it will push the struct to + /// the `Locks` vec in storage. Note that you can lock more funds than a user has. + /// - If the lock `id/until` already exists, this will update it. + /// - Remove the expired locks on account `who`. + /// - Update the global staking amount. + /// - The function will return the sum of expired locks' amount. + fn update_lock(who: &AccountId, strategy: Self::LockUpdateStrategy) -> Self::Balance; // TODO: reserve // fn extend_lock(); diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index a7dafb810..5b07fc986 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -1,28 +1,53 @@ use codec::{Decode, Encode}; +use rstd::vec::Vec; use sr_primitives::RuntimeDebug; use srml_support::traits::WithdrawReasons; pub type TimeStamp = u64; -#[derive(Clone, Eq, Encode, Decode, RuntimeDebug)] -pub struct BalanceLock { - pub amount: Balance, - pub at: Moment, - pub reasons: WithdrawReasons, +#[derive(Clone, PartialEq, Default, Encode, Decode, RuntimeDebug)] +pub struct CompositeLock { + pub staking_amount: Balance, + pub locks: Vec>, } -impl Default for BalanceLock -where - Balance: Default, - Moment: Default, -{ - fn default() -> Self { +pub struct LockUpdateStrategy { + /// if `lock` is set, `check_expired` will be ignored + pub check_expired: bool, + pub staking_amount: Option, + pub lock: Option>, +} + +impl LockUpdateStrategy { + pub fn new() -> Self { Self { - amount: Balance::default(), - at: Moment::default(), - reasons: WithdrawReasons::all(), + check_expired: false, + staking_amount: None, + lock: None, } } + + pub fn with_check_expired(mut self, check_expired: bool) -> Self { + self.check_expired = check_expired; + self + } + + pub fn with_staking_amount(mut self, staking_amount: Balance) -> Self { + self.staking_amount = Some(staking_amount); + self + } + + pub fn with_lock(mut self, lock: BalanceLock) -> Self { + self.lock = Some(lock); + self + } +} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub struct BalanceLock { + pub amount: Balance, + pub at: Moment, + pub reasons: WithdrawReasons, } impl BalanceLock From 4fdd232869ce3ad03fb1d791dfd8286960fb35bf Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 15:51:33 +0800 Subject: [PATCH 12/30] update: move `ensure_can_withdraw` logic to `LockableCurrency`'s `can_withdraw` fix: adjust `total_ring`/`total_kton` in `bond_extra` --- srml/balances/src/lib.rs | 91 ++++++++++++++++++++------------------ srml/kton/src/lib.rs | 67 ++++++++++++++-------------- srml/staking/src/lib.rs | 41 ++++++++--------- srml/staking/src/tests.rs | 64 ++++++++++++++++----------- srml/support/src/traits.rs | 3 ++ srml/support/src/types.rs | 11 +---- 6 files changed, 147 insertions(+), 130 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index aa004d1f7..ed6a59017 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -724,43 +724,21 @@ where // # fn ensure_can_withdraw( who: &T::AccountId, - amount: T::Balance, + _amount: T::Balance, reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) && Self::vesting_balance(who) > new_balance { - return Err("vesting balance too high to send value"); - } - - let composite_lock = Self::locks(who); - - if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { - return Ok(()); - } - - if new_balance >= composite_lock.staking_amount { - return Ok(()); - } - - if { - let now = >::now(); - let mut locked_amount = T::Balance::zero(); - for lock in composite_lock.locks.into_iter() { - if lock.valid_at(now) && lock.reasons.intersects(reasons) { - // TODO: check overflow? - locked_amount += lock.amount; - } + Err("vesting balance too high to send value") + } else { + if Self::can_withdraw(who, reasons, new_balance) { + Ok(()) + } else { + Err("account liquidity restrictions prevent withdrawal") } - - // TODO: check underflow? - Self::free_balance(who) - locked_amount >= amount - } { - return Ok(()); } - - Err("account liquidity restrictions prevent withdrawal") } fn transfer( @@ -993,12 +971,13 @@ where } } -impl LockableCurrency for Module +impl, I: Instance> LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { type LockUpdateStrategy = LockUpdateStrategy; type Moment = T::Moment; + type WithdrawReasons = WithdrawReasons; fn update_lock(who: &T::AccountId, lock_update_strategy: Self::LockUpdateStrategy) -> Self::Balance { let now = >::now(); @@ -1030,19 +1009,17 @@ where composite_lock.locks.push(lock); } } else if lock_update_strategy.check_expired { - >::mutate(who, |composite_lock| { - composite_lock.locks.retain(|lock| { - if lock.valid_at(now) { - true - } else { - expired_locks_amount += lock.amount; - false - } - }); + composite_lock.locks.retain(|lock| { + if lock.valid_at(now) { + true + } else { + expired_locks_amount += lock.amount; + false + } }); } - >::mutate(who, |composite_lock_| { + >::mutate(who, |composite_lock_| { *composite_lock_ = composite_lock; }); @@ -1053,7 +1030,7 @@ where let now = >::now(); let mut expired_locks_amount = Self::Balance::default(); - >::mutate(who, |composite_lock| { + >::mutate(who, |composite_lock| { composite_lock.locks.retain(|lock| { if lock.valid_at(now) && lock.at != at { true @@ -1067,8 +1044,38 @@ where expired_locks_amount } + fn can_withdraw(who: &T::AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { + let composite_lock = Self::locks(who); + + if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { + return true; + } + + if new_balance >= composite_lock.staking_amount { + return true; + } + + if { + let now = >::now(); + let mut locked_amount = T::Balance::zero(); + for lock in composite_lock.locks.into_iter() { + if lock.valid_at(now) && lock.reasons.intersects(reasons) { + // TODO: check overflow? + locked_amount += lock.amount; + } + } + + // TODO: check underflow? + new_balance >= locked_amount + } { + return true; + } + + false + } + fn locks_count(who: &T::AccountId) -> u32 { - >::get(who).locks.len() as _ + >::get(who).locks.len() as _ } } diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index bd4b14958..98c09a0a7 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -222,43 +222,21 @@ impl Currency for Module { fn ensure_can_withdraw( who: &T::AccountId, - amount: T::Balance, + _amount: T::Balance, reasons: WithdrawReasons, new_balance: T::Balance, ) -> Result { if reasons.intersects(WithdrawReason::Reserve | WithdrawReason::Transfer) && Self::vesting_balance(who) > new_balance { - return Err("vesting balance too high to send value"); - } - - let composite_lock = Self::locks(who); - - if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { - return Ok(()); - } - - if new_balance >= composite_lock.staking_amount { - return Ok(()); - } - - if { - let now = >::now(); - let mut locked_amount = T::Balance::zero(); - for lock in composite_lock.locks.into_iter() { - if lock.valid_at(now) && lock.reasons.intersects(reasons) { - // TODO: check overflow? - locked_amount += lock.amount; - } + Err("vesting balance too high to send value") + } else { + if Self::can_withdraw(who, reasons, new_balance) { + Ok(()) + } else { + Err("account liquidity restrictions prevent withdrawal") } - - // TODO: check underflow? - Self::free_balance(who) - locked_amount >= amount - } { - return Ok(()); } - - Err("account liquidity restrictions prevent withdrawal") } // TODO: add fee @@ -398,6 +376,7 @@ where { type LockUpdateStrategy = LockUpdateStrategy; type Moment = T::Moment; + type WithdrawReasons = WithdrawReasons; fn update_lock(who: &T::AccountId, lock_update_strategy: Self::LockUpdateStrategy) -> Self::Balance { let now = >::now(); @@ -439,9 +418,7 @@ where }); } - >::mutate(who, |composite_lock_| { - *composite_lock_ = composite_lock; - }); + >::insert(who, composite_lock); expired_locks_amount } @@ -464,6 +441,32 @@ where expired_locks_amount } + fn can_withdraw(who: &T::AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { + let composite_lock = Self::locks(who); + + if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { + return true; + } + + if { + let now = >::now(); + let mut locked_amount = composite_lock.staking_amount; + for lock in composite_lock.locks.into_iter() { + if lock.valid_at(now) && lock.reasons.intersects(reasons) { + // TODO: check overflow? + locked_amount += lock.amount; + } + } + + // TODO: check underflow? + new_balance >= locked_amount + } { + return true; + } + + false + } + fn locks_count(who: &T::AccountId) -> u32 { >::get(who).locks.len() as _ } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 7089024cf..79eeb5aaf 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -203,13 +203,15 @@ type KtonNegativeImbalanceOf = <::Kton as Currency<, TimeStamp>, + Moment = TimeStamp, + WithdrawReasons = WithdrawReasons, >; type Kton: LockableCurrency< Self::AccountId, Moment = TimeStamp, LockUpdateStrategy = LockUpdateStrategy, TimeStamp>, + WithdrawReasons = WithdrawReasons, >; type CurrencyToVote: Convert, u64> + Convert>; @@ -401,10 +403,16 @@ decl_module! { let stash = ensure_signed(origin)?; ensure!(promise_month <= 36, "months at most is 36."); let controller = Self::bonded(&stash).ok_or("not a stash")?; - let ledger = Self::ledger(&controller).ok_or("not a controller")?; + let mut ledger = Self::ledger(&controller).ok_or("not a controller")?; match value { StakingBalance::Ring(r) => { let stash_balance = T::Ring::free_balance(&stash); + let expired_locks_ring = T::Ring::update_lock( + &stash, + LockUpdateStrategy::new().with_check_expired(true), + ); + // TODO: check underflow? + ledger.total_ring -= expired_locks_ring; if let Some(extra) = stash_balance.checked_sub(&ledger.total_ring) { let extra = extra.min(r); >::mutate(|r| *r += extra); @@ -413,6 +421,11 @@ decl_module! { }, StakingBalance::Kton(k) => { let stash_balance = T::Kton::free_balance(&stash); + let expired_locks_kton = T::Kton::update_lock( + &stash, + LockUpdateStrategy::new().with_check_expired(true), + ); + ledger.total_kton -= expired_locks_kton; if let Some(extra) = stash_balance.checked_sub(&ledger.total_kton) { let extra = extra.min(k); >::mutate(|k| *k += extra); @@ -767,32 +780,20 @@ impl Module { &ledger.stash, LockUpdateStrategy::new() .with_check_expired(true) - .with_staking_amount(ledger.total_ring), + .with_staking_amount(ledger.active_ring), ); - if !expired_locks_ring.is_zero() { - // TODO: check underflow? - ledger.total_ring -= expired_locks_ring; - T::Ring::update_lock( - &ledger.stash, - LockUpdateStrategy::new().with_staking_amount(ledger.total_ring), - ); - } + // TODO: check underflow? + ledger.total_ring -= expired_locks_ring; } StakingBalance::Kton(_k) => { let expired_locks_kton = T::Kton::update_lock( &ledger.stash, LockUpdateStrategy::new() .with_check_expired(true) - .with_staking_amount(ledger.total_kton), + .with_staking_amount(ledger.active_kton), ); - if !expired_locks_kton.is_zero() { - // TODO: check underflow? - ledger.total_kton -= expired_locks_kton; - T::Kton::update_lock( - &ledger.stash, - LockUpdateStrategy::new().with_staking_amount(ledger.total_kton), - ); - } + // TODO: check underflow? + ledger.total_kton -= expired_locks_kton; } } diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index ea2a88f9a..332fb246c 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1252,15 +1252,15 @@ fn xavier_q1() { let unbond_start = 2; Timestamp::set_timestamp(unbond_start); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(10))); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(9))); assert_eq!(Timestamp::get(), 2); assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 10, + staking_amount: 1, locks: vec![BalanceLock { - amount: 10, + amount: 9, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() }] @@ -1292,9 +1292,9 @@ fn xavier_q1() { assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 10, + staking_amount: 1, locks: vec![BalanceLock { - amount: 10, + amount: 9, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() }] @@ -1307,7 +1307,7 @@ fn xavier_q1() { assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 19, + staking_amount: 20, locks: vec![] } ); @@ -1318,8 +1318,8 @@ fn xavier_q1() { total_ring: 0, active_ring: 0, active_deposit_ring: 0, - total_kton: 19, - active_kton: 19, + total_kton: 20, + active_kton: 20, deposit_items: vec![], } ); @@ -1361,13 +1361,13 @@ fn xavier_q2() { // println!(); Timestamp::set_timestamp(1); - assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(5), 0)); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(4), 0)); assert_eq!(Timestamp::get(), 1); assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 10, + staking_amount: 9, locks: vec![] } ); @@ -1386,10 +1386,10 @@ fn xavier_q2() { assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 10, + staking_amount: 7, locks: vec![BalanceLock { amount: 2, - at: 62, + at: BondingDuration::get() + unbond_start_1, reasons: WithdrawReasons::all(), }] } @@ -1398,24 +1398,24 @@ fn xavier_q2() { // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); - let (unbond_value_2, unbond_start_2) = (7, 3); + let (unbond_value_2, unbond_start_2) = (6, 3); Timestamp::set_timestamp(unbond_start_2); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(7))); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Kton(6))); assert_eq!(Timestamp::get(), unbond_start_2); assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 10, + staking_amount: 1, locks: vec![ BalanceLock { amount: 2, - at: 62, + at: BondingDuration::get() + unbond_start_1, reasons: WithdrawReasons::all(), }, BalanceLock { - amount: 7, - at: 63, + amount: 6, + at: BondingDuration::get() + unbond_start_2, reasons: WithdrawReasons::all(), }, ] @@ -1433,26 +1433,26 @@ fn xavier_q2() { // println!("Locking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); - assert_ok!(Kton::transfer(Origin::signed(stash), controller, 1)); + assert_ok!(Kton::transfer(Origin::signed(stash), controller, unbond_value_1 - 1)); assert_eq!(Kton::free_balance(stash), 9); // println!("Normal Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Normal Transfer - Kton Locks: {:#?}", Kton::locks(stash)); Timestamp::set_timestamp(BondingDuration::get() + unbond_start_1); assert_err!( - Kton::transfer(Origin::signed(stash), controller, 3), + Kton::transfer(Origin::signed(stash), controller, unbond_value_1 + 1), "account liquidity restrictions prevent withdrawal" ); // println!("Locking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Locking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); - assert_ok!(Kton::transfer(Origin::signed(stash), controller, 2)); + assert_ok!(Kton::transfer(Origin::signed(stash), controller, unbond_value_1)); assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_1); assert_eq!(Kton::free_balance(stash), 7); assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 10, + staking_amount: 1, locks: vec![ BalanceLock { amount: 2, @@ -1460,7 +1460,7 @@ fn xavier_q2() { reasons: WithdrawReasons::all(), }, BalanceLock { - amount: 7, + amount: 6, at: 63, reasons: WithdrawReasons::all(), }, @@ -1473,11 +1473,11 @@ fn xavier_q2() { Timestamp::set_timestamp(BondingDuration::get() + unbond_start_2); assert_ok!(Kton::transfer(Origin::signed(stash), controller, unbond_value_2)); assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_2); - assert_eq!(Kton::free_balance(stash), 0); + assert_eq!(Kton::free_balance(stash), 1); assert_eq!( Kton::locks(stash), CompositeLock { - staking_amount: 10, + staking_amount: 1, locks: vec![ BalanceLock { amount: 2, @@ -1485,7 +1485,7 @@ fn xavier_q2() { reasons: WithdrawReasons::all(), }, BalanceLock { - amount: 7, + amount: 6, at: 63, reasons: WithdrawReasons::all(), }, @@ -1494,6 +1494,18 @@ fn xavier_q2() { ); // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); + + Kton::deposit_creating(&stash, 1); + // println!("Staking Ledger: {:#?}", Staking::ledger(controller).unwrap()); + assert_eq!(Kton::free_balance(stash), 2); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 0)); + assert_eq!( + Kton::locks(stash), + CompositeLock { + staking_amount: 2, + locks: vec![], + } + ); }); } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 491808aa4..258c096e5 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -27,6 +27,7 @@ pub trait LockableCurrency: Currency { /// The quantity used to denote time; usually just a `BlockNumber`. /// In Darwinia we prefer using `TimeStamp/u64`. type Moment; + type WithdrawReasons; /// - Create a new balance lock on account `who`. /// - If the new lock is valid (i.e. not already expired), it will push the struct to @@ -45,6 +46,8 @@ pub trait LockableCurrency: Currency { /// The function will return the sum of expired locks' amount. fn remove_lock(who: &AccountId, at: Self::Moment) -> Self::Balance; + fn can_withdraw(who: &AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool; + /// The number of locks. fn locks_count(who: &AccountId) -> u32; } diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 5b07fc986..80f5a6b9a 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -43,7 +43,7 @@ impl LockUpdateStrategy { } } -#[derive(Clone, Encode, Decode, RuntimeDebug)] +#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug)] pub struct BalanceLock { pub amount: Balance, pub at: Moment, @@ -58,12 +58,3 @@ where self.at > at } } - -impl PartialEq for BalanceLock -where - Moment: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.at == other.at - } -} From 9554a520375f7e0fd1546780e3322739b86a6734 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 15:56:39 +0800 Subject: [PATCH 13/30] update: move `ensure_can_withdraw` logic to `LockableCurrency`'s `can_withdraw` fix: adjust `total_ring`/`total_kton` in `bond_extra` --- srml/balances/src/lib.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index ed6a59017..3427c90fd 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -1019,9 +1019,7 @@ where }); } - >::mutate(who, |composite_lock_| { - *composite_lock_ = composite_lock; - }); + >::insert(who, composite_lock); expired_locks_amount } @@ -1051,13 +1049,9 @@ where return true; } - if new_balance >= composite_lock.staking_amount { - return true; - } - if { let now = >::now(); - let mut locked_amount = T::Balance::zero(); + let mut locked_amount = composite_lock.staking_amount; for lock in composite_lock.locks.into_iter() { if lock.valid_at(now) && lock.reasons.intersects(reasons) { // TODO: check overflow? From 7f0ed10f2a0a462119fdb696bf4c2de2abef18bd Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 16:10:56 +0800 Subject: [PATCH 14/30] fix: doc --- srml/support/src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 258c096e5..9cb782eff 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -33,7 +33,7 @@ pub trait LockableCurrency: Currency { /// - If the new lock is valid (i.e. not already expired), it will push the struct to /// the `Locks` vec in storage. Note that you can lock more funds than a user has. /// - If the lock `id/until` already exists, this will update it. - /// - Remove the expired locks on account `who`. + /// - Remove the expired locks on account `who`. /// - Update the global staking amount. /// - The function will return the sum of expired locks' amount. fn update_lock(who: &AccountId, strategy: Self::LockUpdateStrategy) -> Self::Balance; From dc95b8ab2d109bab83f4ecf0f45fb3df55a2c6a2 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 16:14:10 +0800 Subject: [PATCH 15/30] update: remove check overflow/underflow --- srml/balances/src/lib.rs | 1 - srml/kton/src/lib.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 3427c90fd..61d579407 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -1059,7 +1059,6 @@ where } } - // TODO: check underflow? new_balance >= locked_amount } { return true; diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 98c09a0a7..eeed2d22d 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -458,7 +458,6 @@ where } } - // TODO: check underflow? new_balance >= locked_amount } { return true; From 6a51c51b68a0800ea846cc7d3751ef546c372b49 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 16:33:14 +0800 Subject: [PATCH 16/30] add: Ring test --- srml/staking/src/tests.rs | 363 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 363 insertions(+) diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 332fb246c..f03ba36cc 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1331,6 +1331,129 @@ fn xavier_q1() { // ); // println!(); }); + + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + let stash = 123; + let controller = 456; + let _ = Ring::deposit_creating(&stash, 10); + + Timestamp::set_timestamp(0); + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Ring(5), + RewardDestination::Stash, + 0, + )); + assert_eq!(Timestamp::get(), 0); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 5, + locks: vec![] + } + ); + // println!("Ok Init - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Init - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(5), 0)); + assert_eq!(Timestamp::get(), 1); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 10, + locks: vec![] + } + ); + // println!("Ok Bond Extra - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Bond Extra - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + let unbond_start = 2; + Timestamp::set_timestamp(unbond_start); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(9))); + assert_eq!(Timestamp::get(), 2); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 1, + locks: vec![BalanceLock { + amount: 9, + at: BondingDuration::get() + unbond_start, + reasons: WithdrawReasons::all() + }] + } + ); + // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + assert_err!( + Ring::transfer(Origin::signed(stash), controller, 1), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start); + assert_ok!(Ring::transfer(Origin::signed(stash), controller, 1)); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!( + // "Unlocking Transfer - Ring StakingLedgers: {:#?}", + // Staking::ledger(&controller) + // ); + // println!(); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start); + assert_eq!(Ring::free_balance(stash), 9); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 1, + locks: vec![BalanceLock { + amount: 9, + at: BondingDuration::get() + unbond_start, + reasons: WithdrawReasons::all() + }] + } + ); + + let _ = Ring::deposit_creating(&stash, 20); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(19), 0)); + assert_eq!(Ring::free_balance(stash), 29); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 20, + locks: vec![] + } + ); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 20, + active_ring: 20, + active_deposit_ring: 0, + total_kton: 0, + active_kton: 0, + deposit_items: vec![], + } + ); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!( + // "Unlocking Transfer - Ring StakingLedgers: {:#?}", + // Staking::ledger(&controller) + // ); + // println!(); + }); } #[test] @@ -1507,6 +1630,179 @@ fn xavier_q2() { } ); }); + + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + let stash = 123; + let controller = 456; + let _ = Ring::deposit_creating(&stash, 10); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Ring(5), + RewardDestination::Stash, + 0, + )); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 5, + locks: vec![] + } + ); + // println!("Ok Init - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Init - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(4), 0)); + assert_eq!(Timestamp::get(), 1); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 9, + locks: vec![] + } + ); + // println!("Ok Bond Extra - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Bond Extra - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + let (unbond_value_1, unbond_start_1) = (2, 2); + Timestamp::set_timestamp(unbond_start_1); + assert_ok!(Staking::unbond( + Origin::signed(controller), + StakingBalance::Ring(unbond_value_1) + )); + assert_eq!(Timestamp::get(), unbond_start_1); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 7, + locks: vec![BalanceLock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all(), + }] + } + ); + // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + let (unbond_value_2, unbond_start_2) = (6, 3); + Timestamp::set_timestamp(unbond_start_2); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(6))); + assert_eq!(Timestamp::get(), unbond_start_2); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 1, + locks: vec![ + BalanceLock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all(), + }, + BalanceLock { + amount: 6, + at: BondingDuration::get() + unbond_start_2, + reasons: WithdrawReasons::all(), + }, + ] + } + ); + // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + assert_err!( + Ring::transfer(Origin::signed(stash), controller, unbond_value_1), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_1 - 1)); + assert_eq!(Ring::free_balance(stash), 9); + // println!("Normal Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Normal Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start_1); + assert_err!( + Ring::transfer(Origin::signed(stash), controller, unbond_value_1 + 1), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_1)); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_1); + assert_eq!(Ring::free_balance(stash), 7); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 1, + locks: vec![ + BalanceLock { + amount: 2, + at: 62, + reasons: WithdrawReasons::all(), + }, + BalanceLock { + amount: 6, + at: 63, + reasons: WithdrawReasons::all(), + }, + ] + } + ); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start_2); + assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_2)); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_2); + assert_eq!(Ring::free_balance(stash), 1); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 1, + locks: vec![ + BalanceLock { + amount: 2, + at: 62, + reasons: WithdrawReasons::all(), + }, + BalanceLock { + amount: 6, + at: 63, + reasons: WithdrawReasons::all(), + }, + ], + } + ); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + + let _ = Ring::deposit_creating(&stash, 1); + // println!("Staking Ledger: {:#?}", Staking::ledger(controller).unwrap()); + assert_eq!(Ring::free_balance(stash), 2); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(1), 0)); + assert_eq!( + Ring::locks(stash), + CompositeLock { + staking_amount: 2, + locks: vec![], + } + ); + }); } #[test] @@ -1577,4 +1873,71 @@ fn xavier_q3() { // println!("StakingLedgers: {:#?}", Staking::ledger(&controller)); // println!(); }); + + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + let stash = 123; + let controller = 456; + let _ = Ring::deposit_creating(&stash, 10); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Ring(5), + RewardDestination::Stash, + 0, + )); + assert_eq!(Timestamp::get(), 1); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 5, + active_ring: 5, + active_deposit_ring: 0, + total_kton: 0, + active_kton: 0, + deposit_items: vec![], + } + ); + // println!("Locks: {:#?}", Ring::locks(stash)); + // println!("StakingLedgers: {:#?}", Staking::ledger(&controller)); + // println!(); + + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(5))); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 5, + active_ring: 0, + active_deposit_ring: 0, + total_kton: 0, + active_kton: 0, + deposit_items: vec![], + } + ); + // println!("Locks: {:#?}", Ring::locks(stash)); + // println!("StakingLedgers: {:#?}", Staking::ledger(&controller)); + // println!(); + + Timestamp::set_timestamp(61); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(1), 0)); + assert_eq!(Timestamp::get(), 61); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedgers { + stash: 123, + total_ring: 1, + active_ring: 1, + active_deposit_ring: 0, + total_kton: 0, + active_kton: 0, + deposit_items: vec![], + } + ); + // println!("Locks: {:#?}", Ring::locks(stash)); + // println!("StakingLedgers: {:#?}", Staking::ledger(&controller)); + // println!(); + }); } From 171c023fbc7b4ae91517f3ba3e03fcc5b88965fe Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 16:33:53 +0800 Subject: [PATCH 17/30] add: `is_empty` for `CompositeLock` --- srml/balances/src/lib.rs | 2 +- srml/kton/src/lib.rs | 2 +- srml/support/src/types.rs | 11 ++++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 61d579407..82f618648 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -1045,7 +1045,7 @@ where fn can_withdraw(who: &T::AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { let composite_lock = Self::locks(who); - if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { + if composite_lock.is_empty() { return true; } diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index eeed2d22d..62ad32b30 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -444,7 +444,7 @@ where fn can_withdraw(who: &T::AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { let composite_lock = Self::locks(who); - if composite_lock.staking_amount.is_zero() && composite_lock.locks.is_empty() { + if composite_lock.is_empty() { return true; } diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 80f5a6b9a..2ff153c7e 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -1,6 +1,6 @@ use codec::{Decode, Encode}; use rstd::vec::Vec; -use sr_primitives::RuntimeDebug; +use sr_primitives::{traits::Zero, RuntimeDebug}; use srml_support::traits::WithdrawReasons; pub type TimeStamp = u64; @@ -11,6 +11,15 @@ pub struct CompositeLock { pub locks: Vec>, } +impl CompositeLock +where + Balance: Zero, +{ + pub fn is_empty(&self) -> bool { + self.staking_amount.is_zero() && self.locks.is_empty() + } +} + pub struct LockUpdateStrategy { /// if `lock` is set, `check_expired` will be ignored pub check_expired: bool, From b9f12a72703f058dd9095ac80e9e856b542ce97e Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 19 Nov 2019 18:23:37 +0800 Subject: [PATCH 18/30] hanging: bakcup --- srml/kton/src/lib.rs | 75 ++++++-------------------- srml/support/src/traits.rs | 6 +-- srml/support/src/types.rs | 104 ++++++++++++++++++++++--------------- 3 files changed, 80 insertions(+), 105 deletions(-) diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 62ad32b30..87c7b762e 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -26,7 +26,7 @@ use system::ensure_signed; use darwinia_support::{ traits::LockableCurrency, - types::{CompositeLock, LockUpdateStrategy}, + types::{CompositeLock, Lock}, }; use imbalance::{NegativeImbalance, PositiveImbalance}; @@ -105,7 +105,7 @@ decl_storage! { pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance; - pub Locks get(locks): map T::AccountId => CompositeLock; + pub Locks get(locks): map T::AccountId => BalanceLocks; pub TotalLock get(total_lock): T::Balance; @@ -374,71 +374,26 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type LockUpdateStrategy = LockUpdateStrategy; + type Lock = Lock; type Moment = T::Moment; type WithdrawReasons = WithdrawReasons; - fn update_lock(who: &T::AccountId, lock_update_strategy: Self::LockUpdateStrategy) -> Self::Balance { - let now = >::now(); - let mut expired_locks_amount = Self::Balance::default(); - let mut composite_lock = Self::locks(who); - - if let Some(staking_amount) = lock_update_strategy.staking_amount { - composite_lock.staking_amount = staking_amount; - } - if let Some(lock) = lock_update_strategy.lock { - let at = lock.at; - let mut new_lock = Some(lock); - composite_lock.locks = composite_lock - .locks - .into_iter() - .filter_map(|lock| { - if lock.at == at { - new_lock.take() - } else if lock.valid_at(now) { - Some(lock) - } else { - // TODO: check overflow? - expired_locks_amount += lock.amount; - None - } - }) - .collect::>(); - if let Some(lock) = new_lock { - composite_lock.locks.push(lock); - } - } else if lock_update_strategy.check_expired { - composite_lock.locks.retain(|lock| { - if lock.valid_at(now) { - true - } else { - expired_locks_amount += lock.amount; - false - } - }); - } - - >::insert(who, composite_lock); + fn update_lock(who: &T::AccountId, lock: Option) -> Self::Balance { + let at = >::now(); + let mut locks = Self::locks(who); + let expired_locks_amount = if let Some(lock) = lock { + locks.update_lock(lock, at) + } else { + locks.remove_expired_lock(at) + }; + >::insert(who, locks); expired_locks_amount } - fn remove_lock(who: &T::AccountId, at: Self::Moment) -> Self::Balance { - let now = >::now(); - let mut expired_locks_amount = Self::Balance::default(); - - >::mutate(who, |composite_lock| { - composite_lock.locks.retain(|lock| { - if lock.valid_at(now) && lock.at != at { - true - } else { - expired_locks_amount += lock.amount; - false - } - }); - }); - - expired_locks_amount + fn remove_locks(who: &T::AccountId, lock: Self::Lock) -> Self::Balance { + let at = >::now(); + locks.remove_locks(at, lock) } fn can_withdraw(who: &T::AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 9cb782eff..5bf53cdfd 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -23,7 +23,7 @@ pub trait OnAccountBalanceChanged { /// A more powerful lockable currency. pub trait LockableCurrency: Currency { - type LockUpdateStrategy; + type Lock; /// The quantity used to denote time; usually just a `BlockNumber`. /// In Darwinia we prefer using `TimeStamp/u64`. type Moment; @@ -36,7 +36,7 @@ pub trait LockableCurrency: Currency { /// - Remove the expired locks on account `who`. /// - Update the global staking amount. /// - The function will return the sum of expired locks' amount. - fn update_lock(who: &AccountId, strategy: Self::LockUpdateStrategy) -> Self::Balance; + fn update_lock(who: &AccountId, strategy: Self::Lock) -> Self::Balance; // TODO: reserve // fn extend_lock(); @@ -44,7 +44,7 @@ pub trait LockableCurrency: Currency { /// Remove an existing lock. /// /// The function will return the sum of expired locks' amount. - fn remove_lock(who: &AccountId, at: Self::Moment) -> Self::Balance; + fn remove_locks(who: &AccountId, lock: Self::Lock) -> Self::Balance; fn can_withdraw(who: &AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool; diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 2ff153c7e..13a08c63f 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -1,54 +1,83 @@ use codec::{Decode, Encode}; use rstd::vec::Vec; -use sr_primitives::{traits::Zero, RuntimeDebug}; +use sr_primitives::{traits::SimpleArithmetic, RuntimeDebug}; use srml_support::traits::WithdrawReasons; pub type TimeStamp = u64; -#[derive(Clone, PartialEq, Default, Encode, Decode, RuntimeDebug)] -pub struct CompositeLock { - pub staking_amount: Balance, - pub locks: Vec>, -} +#[derive(Clone, Default, Encode, Decode, RuntimeDebug)] +pub struct BalanceLocks(Vec>); -impl CompositeLock +impl BalanceLocks where - Balance: Zero, + Balance: Clone + Copy + Default + SimpleArithmetic, + Moment: Clone + Copy + PartialOrd, { - pub fn is_empty(&self) -> bool { - self.staking_amount.is_zero() && self.locks.is_empty() + #[inline] + fn update_lock(&mut self, lock: Lock, at: Moment) -> Balance { + let expired_locks_amount = self.remove_expired_locks(at); + self.0.push(lock); + + expired_locks_amount } -} -pub struct LockUpdateStrategy { - /// if `lock` is set, `check_expired` will be ignored - pub check_expired: bool, - pub staking_amount: Option, - pub lock: Option>, -} + fn remove_locks(&mut self, at: Moment, lock: Lock) -> Balance { + let mut expired_locks_amount = Self::Balance::zero(); -impl LockUpdateStrategy { - pub fn new() -> Self { - Self { - check_expired: false, - staking_amount: None, - lock: None, - } + >::mutate(who, |locks| { + locks.retain(|lock_| { + if lock_.valid_at(now) && lock == lock { + true + } else { + expired_locks_amount += lock.amount; + false + } + }); + }); + + expired_locks_amount } - pub fn with_check_expired(mut self, check_expired: bool) -> Self { - self.check_expired = check_expired; - self + fn remove_expired_locks(&mut self, at: Moment) -> Balance { + let mut expired_locks_amount = Balance::default(); + self.0.retain(|lock| { + if lock.valid_at(at) { + true + } else { + expired_locks_amount += lock.amount(); + false + } + }); + + expired_locks_amount } +} - pub fn with_staking_amount(mut self, staking_amount: Balance) -> Self { - self.staking_amount = Some(staking_amount); - self +#[derive(Clone, RuntimeDebug)] +pub enum Lock { + Staking(Balance), + Unbonding(BalanceLock), +} + +impl Lock +where + Balance: Copy, + Moment: PartialOrd, +{ + #[inline] + fn valid_at(&self, at: Moment) -> bool { + match self { + Lock::Staking(_) => true, + Lock::Unbonding(balance_lock) => balance_lock.at > at, + } } - pub fn with_lock(mut self, lock: BalanceLock) -> Self { - self.lock = Some(lock); - self + #[inline] + fn amount(&self) -> Balance { + match self { + Lock::Staking(balance) => *balance, + Lock::Unbonding(balance_lock) => balance_lock.amount, + } } } @@ -58,12 +87,3 @@ pub struct BalanceLock { pub at: Moment, pub reasons: WithdrawReasons, } - -impl BalanceLock -where - Moment: PartialOrd, -{ - pub fn valid_at(&self, at: Moment) -> bool { - self.at > at - } -} From c69838e9d148363d091b44e47a3bdb9b13b79b6d Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 01:52:24 +0800 Subject: [PATCH 19/30] hanging: hanging --- node/cli/src/factory_impl.rs | 91 ++++--- node/cli/src/lib.rs | 56 ++--- node/cli/src/service.rs | 290 ++++++++++------------ node/rpc/src/lib.rs | 14 +- node/runtime/src/impls.rs | 2 +- srml/balances/src/lib.rs | 103 ++------ srml/balances/src/mock.rs | 41 +++- srml/balances/src/tests.rs | 282 ++++++++++++--------- srml/kton/src/lib.rs | 40 +-- srml/staking/src/lib.rs | 52 ++-- srml/staking/src/tests.rs | 462 +++++------------------------------ srml/support/src/traits.rs | 6 +- srml/support/src/types.rs | 62 +++-- 13 files changed, 554 insertions(+), 947 deletions(-) diff --git a/node/cli/src/factory_impl.rs b/node/cli/src/factory_impl.rs index 66a4ee89f..b69373c4d 100644 --- a/node/cli/src/factory_impl.rs +++ b/node/cli/src/factory_impl.rs @@ -18,25 +18,25 @@ //! using the cli to manufacture transactions and distribute them //! to accounts. -use rand::{Rng, SeedableRng}; use rand::rngs::StdRng; +use rand::{Rng, SeedableRng}; -use codec::{Encode, Decode}; +use codec::{Decode, Encode}; +use finality_tracker; +use inherents::InherentData; use keyring::sr25519::Keyring; +use node_primitives::Signature; use node_runtime::{ - Call, CheckedExtrinsic, UncheckedExtrinsic, SignedExtra, BalancesCall, ExistentialDeposit, - MinimumPeriod + BalancesCall, Call, CheckedExtrinsic, ExistentialDeposit, MinimumPeriod, SignedExtra, UncheckedExtrinsic, }; -use node_primitives::Signature; -use primitives::{sr25519, crypto::Pair}; +use primitives::{crypto::Pair, sr25519}; use sr_primitives::{ - generic::Era, traits::{Block as BlockT, Header as HeaderT, SignedExtension, Verify, IdentifyAccount} + generic::Era, + traits::{Block as BlockT, Header as HeaderT, IdentifyAccount, SignedExtension, Verify}, }; -use transaction_factory::RuntimeAdapter; -use transaction_factory::modes::Mode; -use inherents::InherentData; use timestamp; -use finality_tracker; +use transaction_factory::modes::Mode; +use transaction_factory::RuntimeAdapter; type AccountPublic = ::Signer; @@ -77,11 +77,7 @@ impl RuntimeAdapter for FactoryState { type Number = Number; - fn new( - mode: Mode, - num: u64, - rounds: u64, - ) -> FactoryState { + fn new(mode: Mode, num: u64, rounds: u64) -> FactoryState { FactoryState { mode, num: num as u32, @@ -145,24 +141,28 @@ impl RuntimeAdapter for FactoryState { ) -> ::Extrinsic { let index = self.extract_index(&sender, prior_block_hash); let phase = self.extract_phase(*prior_block_hash); - sign::(CheckedExtrinsic { - signed: Some((sender.clone(), Self::build_extra(index, phase))), - function: Call::Balances( - BalancesCall::transfer( + sign::( + CheckedExtrinsic { + signed: Some((sender.clone(), Self::build_extra(index, phase))), + function: Call::Balances(BalancesCall::transfer( indices::address::Address::Id(destination.clone().into()), - (*amount).into() - ) - ) - }, key, (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ())) + (*amount).into(), + )), + }, + key, + (version, genesis_hash.clone(), prior_block_hash.clone(), (), (), (), ()), + ) } fn inherent_extrinsics(&self) -> InherentData { let timestamp = (self.block_no as u64 + 1) * MinimumPeriod::get(); let mut inherent = InherentData::new(); - inherent.put_data(timestamp::INHERENT_IDENTIFIER, ×tamp) + inherent + .put_data(timestamp::INHERENT_IDENTIFIER, ×tamp) .expect("Failed putting timestamp inherent"); - inherent.put_data(finality_tracker::INHERENT_IDENTIFIER, &self.block_no) + inherent + .put_data(finality_tracker::INHERENT_IDENTIFIER, &self.block_no) .expect("Failed putting finalized number inherent"); inherent } @@ -191,11 +191,7 @@ impl RuntimeAdapter for FactoryState { pair } - fn extract_index( - &self, - _account_id: &Self::AccountId, - _block_hash: &::Hash, - ) -> Self::Index { + fn extract_index(&self, _account_id: &Self::AccountId, _block_hash: &::Hash) -> Self::Index { // TODO get correct index for account via api. See #2587. // This currently prevents the factory from being used // without a preceding purge of the database. @@ -204,20 +200,21 @@ impl RuntimeAdapter for FactoryState { } else { match self.round() { 0 => - // if round is 0 all transactions will be done with master as a sender - self.block_no() as Self::Index, + // if round is 0 all transactions will be done with master as a sender + { + self.block_no() as Self::Index + } _ => - // if round is e.g. 1 every sender account will be new and not yet have - // any transactions done + // if round is e.g. 1 every sender account will be new and not yet have + // any transactions done + { 0 + } } } } - fn extract_phase( - &self, - _block_hash: ::Hash - ) -> Self::Phase { + fn extract_phase(&self, _block_hash: ::Hash) -> Self::Phase { // TODO get correct phase via api. See #2587. // This currently prevents the factory from being used // without a preceding purge of the database. @@ -245,13 +242,15 @@ fn sign( let s = match xt.signed { Some((signed, extra)) => { let payload = (xt.function, extra.clone(), additional_signed); - let signature = payload.using_encoded(|b| { - if b.len() > 256 { - key.sign(&runtime_io::hashing::blake2_256(b)) - } else { - key.sign(b) - } - }).into(); + let signature = payload + .using_encoded(|b| { + if b.len() > 256 { + key.sign(&runtime_io::hashing::blake2_256(b)) + } else { + key.sign(b) + } + }) + .into(); UncheckedExtrinsic { signature: Some((indices::address::Address::Id(signed), signature, extra)), function: payload.0, diff --git a/node/cli/src/lib.rs b/node/cli/src/lib.rs index 7aa10c6e8..db7962f78 100644 --- a/node/cli/src/lib.rs +++ b/node/cli/src/lib.rs @@ -168,7 +168,7 @@ where ParseAndPrepare::Run(cmd) => cmd.run( load_spec, exit, - |exit, _cli_args, _custom_args, config: Config<_,_>| { + |exit, _cli_args, _custom_args, config: Config<_, _>| { info!("{}", version.name); info!(" version {}", config.full_version()); info!(" _____ _ _ "); @@ -180,37 +180,31 @@ where info!("Chain specification: {}", config.chain_spec.name()); info!("Node name: {}", config.name); info!("Roles: {:?}", cli::display_role(&config)); - let runtime = RuntimeBuilder::new().name_prefix("main-tokio-").build() + let runtime = RuntimeBuilder::new() + .name_prefix("main-tokio-") + .build() .map_err(|e| format!("{:?}", e))?; match config.roles { - ServiceRoles::LIGHT => run_until_exit( - runtime, - service::new_light(config)?, - exit - ), - _ => run_until_exit( - runtime, - service::new_full(config)?, - exit - ), + ServiceRoles::LIGHT => run_until_exit(runtime, service::new_light(config)?, exit), + _ => run_until_exit(runtime, service::new_full(config)?, exit), } }, ), ParseAndPrepare::BuildSpec(cmd) => cmd.run::(load_spec), - ParseAndPrepare::ExportBlocks(cmd) => cmd.run_with_builder(|config: Config<_,_>| - Ok(new_full_start!(config).0), load_spec, exit), - ParseAndPrepare::ImportBlocks(cmd) => cmd.run_with_builder(|config: Config<_,_>| - Ok(new_full_start!(config).0), load_spec, exit), + ParseAndPrepare::ExportBlocks(cmd) => { + cmd.run_with_builder(|config: Config<_, _>| Ok(new_full_start!(config).0), load_spec, exit) + } + ParseAndPrepare::ImportBlocks(cmd) => { + cmd.run_with_builder(|config: Config<_, _>| Ok(new_full_start!(config).0), load_spec, exit) + } ParseAndPrepare::PurgeChain(cmd) => cmd.run(load_spec), - ParseAndPrepare::RevertChain(cmd) => cmd.run_with_builder(|config: Config<_,_>| - Ok(new_full_start!(config).0), load_spec), + ParseAndPrepare::RevertChain(cmd) => { + cmd.run_with_builder(|config: Config<_, _>| Ok(new_full_start!(config).0), load_spec) + } ParseAndPrepare::CustomCommand(CustomSubcommands::Factory(cli_args)) => { - let mut config: Config<_, _> = cli::create_config_with_db_path( - load_spec, - &cli_args.shared_params, - &version, - )?; + let mut config: Config<_, _> = + cli::create_config_with_db_path(load_spec, &cli_args.shared_params, &version)?; config.execution_strategies = ExecutionStrategies { importing: cli_args.execution.into(), block_construction: cli_args.execution.into(), @@ -219,23 +213,21 @@ where }; match ChainSpec::from(config.chain_spec.id()) { - Some(ref c) if c == &ChainSpec::Development || c == &ChainSpec::LocalTestnet => {}, + Some(ref c) if c == &ChainSpec::Development || c == &ChainSpec::LocalTestnet => {} _ => panic!("Factory is only supported for development and local testnet."), } - let factory_state = FactoryState::new( - cli_args.mode.clone(), - cli_args.num, - cli_args.rounds, - ); + let factory_state = FactoryState::new(cli_args.mode.clone(), cli_args.num, cli_args.rounds); let service_builder = new_full_start!(config).0; transaction_factory::factory::, _, _, _, _, _>( factory_state, service_builder.client(), - service_builder.select_chain() - .expect("The select_chain is always initialized by new_full_start!; QED") - ).map_err(|e| format!("Error in transaction factory: {}", e))?; + service_builder + .select_chain() + .expect("The select_chain is always initialized by new_full_start!; QED"), + ) + .map_err(|e| format!("Error in transaction factory: {}", e))?; Ok(()) } diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index 52ad35fe7..895462edf 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -23,24 +23,22 @@ use std::sync::Arc; use babe; use client::{self, LongestChain}; use grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider}; +use inherents::InherentDataProviders; +use network::construct_simple_protocol; use node_executor; use node_primitives::Block; use node_runtime::{GenesisConfig, RuntimeApi}; -use substrate_service::{ - AbstractService, ServiceBuilder, config::Configuration, error::{Error as ServiceError}, -}; -use transaction_pool::{self, txpool::{Pool as TransactionPool}}; -use inherents::InherentDataProviders; -use network::construct_simple_protocol; +use substrate_service::{config::Configuration, error::Error as ServiceError, AbstractService, ServiceBuilder}; +use transaction_pool::{self, txpool::Pool as TransactionPool}; -use substrate_service::{Service, NetworkStatus}; use client::{Client, LocalCallExecutor}; use client_db::Backend; -use sr_primitives::traits::Block as BlockT; -use node_executor::NativeExecutor; use network::NetworkService; +use node_executor::NativeExecutor; use offchain::OffchainWorkers; use primitives::Blake2Hasher; +use sr_primitives::traits::Block as BlockT; +use substrate_service::{NetworkStatus, Service}; construct_simple_protocol! { /// Demo protocol attachment for substrate. @@ -58,50 +56,48 @@ macro_rules! new_full_start { let inherent_data_providers = inherents::InherentDataProviders::new(); let builder = substrate_service::ServiceBuilder::new_full::< - node_primitives::Block, node_runtime::RuntimeApi, node_executor::Executor + node_primitives::Block, + node_runtime::RuntimeApi, + node_executor::Executor, >($config)? - .with_select_chain(|_config, backend| { - Ok(client::LongestChain::new(backend.clone())) - })? - .with_transaction_pool(|config, client| - Ok(transaction_pool::txpool::Pool::new(config, transaction_pool::FullChainApi::new(client))) - )? - .with_import_queue(|_config, client, mut select_chain, _transaction_pool| { - let select_chain = select_chain.take() - .ok_or_else(|| substrate_service::Error::SelectChainRequired)?; - let (grandpa_block_import, grandpa_link) = grandpa::block_import( - client.clone(), - &*client, - select_chain, - )?; - let justification_import = grandpa_block_import.clone(); - - let (block_import, babe_link) = babe::block_import( - babe::Config::get_or_compute(&*client)?, - grandpa_block_import, - client.clone(), - client.clone(), - )?; - - let import_queue = babe::import_queue( - babe_link.clone(), - block_import.clone(), - Some(Box::new(justification_import)), - None, - client.clone(), - client, - inherent_data_providers.clone(), - )?; - - import_setup = Some((block_import, grandpa_link, babe_link)); - Ok(import_queue) - })? - .with_rpc_extensions(|client, pool, _backend| -> RpcExtension { - node_rpc::create(client, pool) - })?; + .with_select_chain(|_config, backend| Ok(client::LongestChain::new(backend.clone())))? + .with_transaction_pool(|config, client| { + Ok(transaction_pool::txpool::Pool::new( + config, + transaction_pool::FullChainApi::new(client), + )) + })? + .with_import_queue(|_config, client, mut select_chain, _transaction_pool| { + let select_chain = select_chain + .take() + .ok_or_else(|| substrate_service::Error::SelectChainRequired)?; + let (grandpa_block_import, grandpa_link) = grandpa::block_import(client.clone(), &*client, select_chain)?; + let justification_import = grandpa_block_import.clone(); + + let (block_import, babe_link) = babe::block_import( + babe::Config::get_or_compute(&*client)?, + grandpa_block_import, + client.clone(), + client.clone(), + )?; + + let import_queue = babe::import_queue( + babe_link.clone(), + block_import.clone(), + Some(Box::new(justification_import)), + None, + client.clone(), + client, + inherent_data_providers.clone(), + )?; + + import_setup = Some((block_import, grandpa_link, babe_link)); + Ok(import_queue) + })? + .with_rpc_extensions(|client, pool, _backend| -> RpcExtension { node_rpc::create(client, pool) })?; (builder, import_setup, inherent_data_providers) - }} + }}; } /// Creates a full service from the configuration. @@ -113,17 +109,12 @@ macro_rules! new_full { use futures::sync::mpsc; use network::DhtEvent; - let ( - is_authority, - force_authoring, - name, - disable_grandpa - ) = ( + let (is_authority, force_authoring, name, disable_grandpa) = ( $config.roles.is_authority(), $config.force_authoring, $config.name.clone(), - $config.disable_grandpa - ); + $config.disable_grandpa, + ); // sentry nodes announce themselves as authorities to the network // and should run the same protocols authorities do, but it should @@ -136,18 +127,19 @@ macro_rules! new_full { // back-pressure. Authority discovery is triggering one event per authority within the current authority set. // This estimates the authority set size to be somewhere below 10 000 thereby setting the channel buffer size to // 10 000. - let (dht_event_tx, _dht_event_rx) = - mpsc::channel::(10_000); + let (dht_event_tx, _dht_event_rx) = mpsc::channel::(10_000); - let service = builder.with_network_protocol(|_| Ok(crate::service::NodeProtocol::new()))? - .with_finality_proof_provider(|client, backend| + let service = builder + .with_network_protocol(|_| Ok(crate::service::NodeProtocol::new()))? + .with_finality_proof_provider(|client, backend| { Ok(Arc::new(grandpa::FinalityProofProvider::new(backend, client)) as _) - )? + })? .with_dht_event_tx(dht_event_tx)? .build()?; - let (block_import, grandpa_link, babe_link) = import_setup.take() - .expect("Link Half and Block Import are present for Full Services or setup failed before. qed"); + let (block_import, grandpa_link, babe_link) = import_setup + .take() + .expect("Link Half and Block Import are present for Full Services or setup failed before. qed"); ($with_startup_data)(&block_import, &babe_link); @@ -158,7 +150,8 @@ macro_rules! new_full { }; let client = service.client(); - let select_chain = service.select_chain() + let select_chain = service + .select_chain() .ok_or(substrate_service::Error::SelectChainRequired)?; let babe_config = babe::BabeParams { @@ -175,7 +168,7 @@ macro_rules! new_full { let babe = babe::start_babe(babe_config)?; service.spawn_essential_task(babe); - } + } // if the node isn't actively participating in consensus then it doesn't // need a keystore, regardless of which protocol we use below. @@ -183,7 +176,7 @@ macro_rules! new_full { Some(service.keystore()) } else { None - }; + }; let config = grandpa::Config { // FIXME #1578 make this available through chainspec @@ -193,7 +186,7 @@ macro_rules! new_full { observer_enabled: true, keystore, is_authority, - }; + }; match (is_authority, disable_grandpa) { (false, false) => { @@ -204,7 +197,7 @@ macro_rules! new_full { service.network(), service.on_exit(), )?); - }, + } (true, false) => { // start the full GRANDPA voter let grandpa_config = grandpa::GrandpaParams { @@ -219,34 +212,28 @@ macro_rules! new_full { // the GRANDPA voter task is considered infallible, i.e. // if it fails we take down the service with it. service.spawn_essential_task(grandpa::run_grandpa_voter(grandpa_config)?); - }, + } (_, true) => { - grandpa::setup_disabled_grandpa( - service.client(), - &inherent_data_providers, - service.network(), - )?; - }, - } + grandpa::setup_disabled_grandpa(service.client(), &inherent_data_providers, service.network())?; + } + } Ok((service, inherent_data_providers)) - }}; + }}; ($config:expr) => {{ new_full!($config, |_, _| {}) - }} + }}; } #[allow(dead_code)] type ConcreteBlock = node_primitives::Block; #[allow(dead_code)] -type ConcreteClient = - Client< - Backend, - LocalCallExecutor, - NativeExecutor>, - ConcreteBlock, - node_runtime::RuntimeApi - >; +type ConcreteClient = Client< + Backend, + LocalCallExecutor, NativeExecutor>, + ConcreteBlock, + node_runtime::RuntimeApi, +>; #[allow(dead_code)] type ConcreteBackend = Backend; @@ -254,8 +241,9 @@ type ConcreteBackend = Backend; pub type NodeConfiguration = Configuration; /// Builds a new service for a full client. -pub fn new_full(config: NodeConfiguration) --> Result< +pub fn new_full( + config: NodeConfiguration, +) -> Result< Service< ConcreteBlock, ConcreteClient, @@ -267,27 +255,28 @@ pub fn new_full(config: NodeConfiguration) ConcreteClient, >::OffchainStorage, ConcreteBlock, - > + >, >, ServiceError, -> -{ +> { new_full!(config).map(|(service, _)| service) } /// Builds a new service for a light client. -pub fn new_light(config: NodeConfiguration) --> Result { +pub fn new_light( + config: NodeConfiguration, +) -> Result { type RpcExtension = jsonrpc_core::IoHandler; let inherent_data_providers = InherentDataProviders::new(); let service = ServiceBuilder::new_light::(config)? - .with_select_chain(|_config, backend| { - Ok(LongestChain::new(backend.clone())) + .with_select_chain(|_config, backend| Ok(LongestChain::new(backend.clone())))? + .with_transaction_pool(|config, client| { + Ok(TransactionPool::new( + config, + transaction_pool::FullChainApi::new(client), + )) })? - .with_transaction_pool(|config, client| - Ok(TransactionPool::new(config, transaction_pool::FullChainApi::new(client))) - )? .with_import_queue_and_fprb(|_config, client, backend, fetcher, _select_chain, _tx_pool| { let fetch_checker = fetcher .map(|fetcher| fetcher.checker().clone()) @@ -300,8 +289,7 @@ pub fn new_light(config: NodeConfiguration) )?; let finality_proof_import = grandpa_block_import.clone(); - let finality_proof_request_builder = - finality_proof_import.create_finality_proof_request_builder(); + let finality_proof_request_builder = finality_proof_import.create_finality_proof_request_builder(); let (babe_block_import, babe_link) = babe::block_import( babe::Config::get_or_compute(&*client)?, @@ -323,12 +311,10 @@ pub fn new_light(config: NodeConfiguration) Ok((import_queue, finality_proof_request_builder)) })? .with_network_protocol(|_| Ok(NodeProtocol::new()))? - .with_finality_proof_provider(|client, backend| + .with_finality_proof_provider(|client, backend| { Ok(Arc::new(GrandpaFinalityProofProvider::new(backend, client)) as _) - )? - .with_rpc_extensions(|client, pool, _backend| -> RpcExtension { - node_rpc::create(client, pool) })? + .with_rpc_extensions(|client, pool, _backend| -> RpcExtension { node_rpc::create(client, pool) })? .build()?; Ok(service) @@ -336,28 +322,26 @@ pub fn new_light(config: NodeConfiguration) #[cfg(test)] mod tests { - use std::sync::Arc; + use crate::service::new_full; use babe::CompatibleDigestItem; - use consensus_common::{ - Environment, Proposer, BlockImportParams, BlockOrigin, ForkChoiceStrategy, BlockImport, - }; + use codec::{Decode, Encode}; + use consensus_common::{BlockImport, BlockImportParams, BlockOrigin, Environment, ForkChoiceStrategy, Proposer}; + use finality_tracker; + use keyring::AccountKeyring; use node_primitives::{Block, DigestItem, Signature}; - use node_runtime::{BalancesCall, Call, UncheckedExtrinsic, Address}; use node_runtime::constants::{currency::CENTS, time::SLOT_DURATION}; - use codec::{Encode, Decode}; + use node_runtime::{Address, BalancesCall, Call, UncheckedExtrinsic}; use primitives::{crypto::Pair as CryptoPair, H256}; + use sr_primitives::traits::IdentifyAccount; use sr_primitives::{ - generic::{BlockId, Era, Digest, SignedPayload}, + generic::{BlockId, Digest, Era, SignedPayload}, traits::Block as BlockT, traits::Verify, OpaqueExtrinsic, }; - use timestamp; - use finality_tracker; - use keyring::AccountKeyring; + use std::sync::Arc; use substrate_service::{AbstractService, Roles}; - use crate::service::new_full; - use sr_primitives::traits::IdentifyAccount; + use timestamp; type AccountPublic = ::Signer; @@ -365,8 +349,8 @@ mod tests { fn test_sync() { use primitives::ed25519::Pair; - use {service_test, Factory}; use client::{BlockImportParams, BlockOrigin}; + use {service_test, Factory}; let alice: Arc = Arc::new(Keyring::Alice.into()); let bob: Arc = Arc::new(Keyring::Bob.into()); @@ -384,7 +368,9 @@ mod tests { force_delay: 0, handle: dummy_runtime.executor(), }; - let (proposer, _, _) = proposer_factory.init(&parent_header, &validators, alice.clone()).unwrap(); + let (proposer, _, _) = proposer_factory + .init(&parent_header, &validators, alice.clone()) + .unwrap(); let block = proposer.propose().expect("Error making test block"); BlockImportParams { origin: BlockOrigin::File, @@ -396,21 +382,20 @@ mod tests { auxiliary: Vec::new(), } }; - let extrinsic_factory = - |service: &SyncService<::FullService>| - { + let extrinsic_factory = |service: &SyncService<::FullService>| { let payload = ( 0, Call::Balances(BalancesCall::transfer(RawAddress::Id(bob.public().0.into()), 69.into())), Era::immortal(), - service.client().genesis_hash() + service.client().genesis_hash(), ); let signature = alice.sign(&payload.encode()).into(); let id = alice.public().0.into(); let xt = UncheckedExtrinsic { signature: Some((RawAddress::Id(id), signature, payload.0, Era::immortal())), function: payload.1, - }.encode(); + } + .encode(); let v: Vec = Decode::decode(&mut xt.as_slice()).unwrap(); OpaqueExtrinsic(v) }; @@ -431,9 +416,10 @@ mod tests { #[ignore] fn test_sync() { let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore = keystore::Store::open(keystore_path.path(), None) - .expect("Creates keystore"); - let alice = keystore.write().insert_ephemeral_from_seed::("//Alice") + let keystore = keystore::Store::open(keystore_path.path(), None).expect("Creates keystore"); + let alice = keystore + .write() + .insert_ephemeral_from_seed::("//Alice") .expect("Creates authority pair"); let chain_spec = crate::chain_spec::tests::integration_test_config_with_single_authority(); @@ -450,12 +436,11 @@ mod tests { chain_spec, |config| { let mut setup_handles = None; - new_full!(config, | - block_import: &babe::BabeBlockImport<_, _, Block, _, _, _>, - babe_link: &babe::BabeLink, - | { + new_full!(config, |block_import: &babe::BabeBlockImport<_, _, Block, _, _, _>, + babe_link: &babe::BabeLink| { setup_handles = Some((block_import.clone(), babe_link.clone())); - }).map(move |(node, x)| (node, (x, setup_handles.unwrap()))) + }) + .map(move |(node, x)| (node, (x, setup_handles.unwrap()))) }, |mut config| { // light nodes are unsupported @@ -501,7 +486,8 @@ mod tests { inherent_data, digest, std::time::Duration::from_secs(1), - )).expect("Error making test block"); + )) + .expect("Error making test block"); let (new_header, new_body) = new_block.deconstruct(); let pre_hash = new_header.hash(); @@ -509,9 +495,7 @@ mod tests { // add it to a digest item. let to_sign = pre_hash.encode(); let signature = alice.sign(&to_sign[..]); - let item = ::babe_seal( - signature.into(), - ); + let item = ::babe_seal(signature.into()); slot_num += 1; let params = BlockImportParams { @@ -526,7 +510,8 @@ mod tests { allow_missing_state: false, }; - block_import.import_block(params, Default::default()) + block_import + .import_block(params, Default::default()) .expect("error importing test block"); }, |service, _| { @@ -535,7 +520,11 @@ mod tests { let from: Address = AccountPublic::from(charlie.public()).into_account().into(); let genesis_hash = service.client().block_hash(0).unwrap().unwrap(); let best_block_id = BlockId::number(service.client().info().chain.best_number); - let version = service.client().runtime_version_at(&best_block_id).unwrap().spec_version; + let version = service + .client() + .runtime_version_at(&best_block_id) + .unwrap() + .spec_version; let signer = charlie.clone(); let function = Call::Balances(BalancesCall::transfer(to.into(), amount)); @@ -555,21 +544,11 @@ mod tests { payment, Default::default(), ); - let raw_payload = SignedPayload::from_raw( - function, - extra, - (version, genesis_hash, genesis_hash, (), (), (), ()) - ); - let signature = raw_payload.using_encoded(|payload| { - signer.sign(payload) - }); + let raw_payload = + SignedPayload::from_raw(function, extra, (version, genesis_hash, genesis_hash, (), (), (), ())); + let signature = raw_payload.using_encoded(|payload| signer.sign(payload)); let (function, extra, _) = raw_payload.deconstruct(); - let xt = UncheckedExtrinsic::new_signed( - function, - from.into(), - signature.into(), - extra, - ).encode(); + let xt = UncheckedExtrinsic::new_signed(function, from.into(), signature.into(), extra).encode(); let v: Vec = Decode::decode(&mut xt.as_slice()).unwrap(); index += 1; @@ -589,10 +568,7 @@ mod tests { config.roles = Roles::FULL; new_full(config) }, - vec![ - "//Alice".into(), - "//Bob".into(), - ], + vec!["//Alice".into(), "//Bob".into()], ) } } diff --git a/node/rpc/src/lib.rs b/node/rpc/src/lib.rs index 0e0147468..814e8f134 100644 --- a/node/rpc/src/lib.rs +++ b/node/rpc/src/lib.rs @@ -49,19 +49,13 @@ where P: ChainApi + Sync + Send + 'static, M: jsonrpc_core::Metadata + Default, { - use srml_system_rpc::{System, SystemApi}; use srml_contracts_rpc::{Contracts, ContractsApi}; + use srml_system_rpc::{System, SystemApi}; use srml_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; let mut io = jsonrpc_core::IoHandler::default(); - io.extend_with( - SystemApi::to_delegate(System::new(client.clone(), pool)) - ); - io.extend_with( - ContractsApi::to_delegate(Contracts::new(client.clone())) - ); - io.extend_with( - TransactionPaymentApi::to_delegate(TransactionPayment::new(client)) - ); + io.extend_with(SystemApi::to_delegate(System::new(client.clone(), pool))); + io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone()))); + io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client))); io } diff --git a/node/runtime/src/impls.rs b/node/runtime/src/impls.rs index fb367c294..efaf37c7f 100644 --- a/node/runtime/src/impls.rs +++ b/node/runtime/src/impls.rs @@ -22,7 +22,7 @@ use sr_primitives::traits::{Convert, Saturating}; use sr_primitives::weights::Weight; use sr_primitives::{Fixed64, Perbill}; -use support::traits::{OnUnbalanced, Currency, Get}; +use support::traits::{Currency, Get, OnUnbalanced}; pub struct Author; impl OnUnbalanced for Author { diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 82f618648..8e873446a 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -45,7 +45,7 @@ mod tests; pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; use darwinia_support::{ traits::LockableCurrency, - types::{CompositeLock, LockUpdateStrategy}, + types::{BalanceLocks, Lock}, }; pub trait Subtrait: system::Trait + timestamp::Trait { @@ -235,7 +235,7 @@ decl_storage! { pub ReservedBalance get(fn reserved_balance): map T::AccountId => T::Balance; /// Any liquidity locks on some account balances. - pub Locks get(locks): map T::AccountId => CompositeLock; + pub Locks get(locks): map T::AccountId => BalanceLocks; } add_extra_genesis { config(balances): Vec<(T::AccountId, T::Balance)>; @@ -733,7 +733,7 @@ where { Err("vesting balance too high to send value") } else { - if Self::can_withdraw(who, reasons, new_balance) { + if Self::locks(who).can_withdraw(>::now(), reasons, new_balance) { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -975,100 +975,35 @@ impl, I: Instance> LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type LockUpdateStrategy = LockUpdateStrategy; + type Lock = Lock; type Moment = T::Moment; type WithdrawReasons = WithdrawReasons; - fn update_lock(who: &T::AccountId, lock_update_strategy: Self::LockUpdateStrategy) -> Self::Balance { - let now = >::now(); - let mut expired_locks_amount = Self::Balance::default(); - let mut composite_lock = Self::locks(who); - - if let Some(staking_amount) = lock_update_strategy.staking_amount { - composite_lock.staking_amount = staking_amount; - } - if let Some(lock) = lock_update_strategy.lock { - let at = lock.at; - let mut new_lock = Some(lock); - composite_lock.locks = composite_lock - .locks - .into_iter() - .filter_map(|lock| { - if lock.at == at { - new_lock.take() - } else if lock.valid_at(now) { - Some(lock) - } else { - // TODO: check overflow? - expired_locks_amount += lock.amount; - None - } - }) - .collect::>(); - if let Some(lock) = new_lock { - composite_lock.locks.push(lock); - } - } else if lock_update_strategy.check_expired { - composite_lock.locks.retain(|lock| { - if lock.valid_at(now) { - true - } else { - expired_locks_amount += lock.amount; - false - } - }); - } - - >::insert(who, composite_lock); + fn update_lock(who: &T::AccountId, lock: Option) -> Self::Balance { + let at = >::now(); + let mut locks = Self::locks(who); + let expired_locks_amount = if let Some(lock) = lock { + locks.update_lock(lock, at) + } else { + locks.remove_expired_locks(at) + }; + >::insert(who, locks); expired_locks_amount } - fn remove_lock(who: &T::AccountId, at: Self::Moment) -> Self::Balance { - let now = >::now(); - let mut expired_locks_amount = Self::Balance::default(); - - >::mutate(who, |composite_lock| { - composite_lock.locks.retain(|lock| { - if lock.valid_at(now) && lock.at != at { - true - } else { - expired_locks_amount += lock.amount; - false - } - }); + fn remove_locks(who: &T::AccountId, lock: &Self::Lock) -> Self::Balance { + let at = >::now(); + let mut expired_locks_amount = Self::Balance::zero(); + >::mutate(who, |locks| { + expired_locks_amount = locks.remove_locks(at, lock); }); expired_locks_amount } - fn can_withdraw(who: &T::AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { - let composite_lock = Self::locks(who); - - if composite_lock.is_empty() { - return true; - } - - if { - let now = >::now(); - let mut locked_amount = composite_lock.staking_amount; - for lock in composite_lock.locks.into_iter() { - if lock.valid_at(now) && lock.reasons.intersects(reasons) { - // TODO: check overflow? - locked_amount += lock.amount; - } - } - - new_balance >= locked_amount - } { - return true; - } - - false - } - fn locks_count(who: &T::AccountId) -> u32 { - >::get(who).locks.len() as _ + >::get(who).len() } } diff --git a/srml/balances/src/mock.rs b/srml/balances/src/mock.rs index 600d0e6fb..c11e03402 100644 --- a/srml/balances/src/mock.rs +++ b/srml/balances/src/mock.rs @@ -18,16 +18,20 @@ #![cfg(test)] -use sr_primitives::{Perbill, traits::{ConvertInto, IdentityLookup}, testing::Header, - weights::{DispatchInfo, Weight}}; +use crate::{GenesisConfig, Module, Trait}; use primitives::H256; use runtime_io; -use support::{impl_outer_origin, parameter_types}; -use support::traits::Get; +use sr_primitives::{ + testing::Header, + traits::{ConvertInto, IdentityLookup}, + weights::{DispatchInfo, Weight}, + Perbill, +}; use std::cell::RefCell; -use crate::{GenesisConfig, Module, Trait}; +use support::traits::Get; +use support::{impl_outer_origin, parameter_types}; -impl_outer_origin!{ +impl_outer_origin! { pub enum Origin for Runtime {} } @@ -39,17 +43,23 @@ thread_local! { pub struct ExistentialDeposit; impl Get for ExistentialDeposit { - fn get() -> u64 { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) } + fn get() -> u64 { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow()) + } } pub struct TransferFee; impl Get for TransferFee { - fn get() -> u64 { TRANSFER_FEE.with(|v| *v.borrow()) } + fn get() -> u64 { + TRANSFER_FEE.with(|v| *v.borrow()) + } } pub struct CreationFee; impl Get for CreationFee { - fn get() -> u64 { CREATION_FEE.with(|v| *v.borrow()) } + fn get() -> u64 { + CREATION_FEE.with(|v| *v.borrow()) + } } // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. @@ -160,7 +170,7 @@ impl ExtBuilder { (2, 20 * self.existential_deposit), (3, 30 * self.existential_deposit), (4, 40 * self.existential_deposit), - (12, 10 * self.existential_deposit) + (12, 10 * self.existential_deposit), ] } else { vec![] @@ -169,12 +179,14 @@ impl ExtBuilder { vec![ (1, 0, 10, 5 * self.existential_deposit), (2, 10, 20, 0), - (12, 10, 20, 5 * self.existential_deposit) + (12, 10, 20, 5 * self.existential_deposit), ] } else { vec![] }, - }.assimilate_storage(&mut t).unwrap(); + } + .assimilate_storage(&mut t) + .unwrap(); t.into() } } @@ -186,5 +198,8 @@ pub const CALL: &::Call = &(); /// 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() } + DispatchInfo { + weight: w, + ..Default::default() + } } diff --git a/srml/balances/src/tests.rs b/srml/balances/src/tests.rs index 8afec6f69..b57898e5f 100644 --- a/srml/balances/src/tests.rs +++ b/srml/balances/src/tests.rs @@ -19,15 +19,17 @@ #![cfg(test)] use super::*; -use mock::{Balances, ExtBuilder, Runtime, System, info_from_weight, CALL}; +use mock::{info_from_weight, Balances, ExtBuilder, Runtime, System, CALL}; use sr_primitives::traits::SignedExtension; use support::{ - assert_noop, assert_ok, assert_err, - traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons, - Currency, ReservableCurrency, ExistenceRequirement::AllowDeath} + assert_err, assert_noop, assert_ok, + traits::{ + Currency, ExistenceRequirement::AllowDeath, LockIdentifier, LockableCurrency, ReservableCurrency, + WithdrawReason, WithdrawReasons, + }, }; -use transaction_payment::ChargeTransactionPayment; use system::RawOrigin; +use transaction_payment::ChargeTransactionPayment; const ID_1: LockIdentifier = *b"1 "; const ID_2: LockIdentifier = *b"2 "; @@ -35,80 +37,108 @@ const ID_3: LockIdentifier = *b"3 "; #[test] fn basic_locking_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - assert_eq!(Balances::free_balance(&1), 10); - Balances::set_lock(ID_1, &1, 9, u64::max_value(), WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 5, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + assert_eq!(Balances::free_balance(&1), 10); + Balances::set_lock(ID_1, &1, 9, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 5, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); } #[test] fn partial_locking_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn lock_removal_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all()); - Balances::remove_lock(ID_1, &1); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all()); + Balances::remove_lock(ID_1, &1); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn lock_replacement_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all()); - Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), u64::max_value(), WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn double_locking_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); - Balances::set_lock(ID_2, &1, 5, u64::max_value(), WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + Balances::set_lock(ID_2, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn combination_locking_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, u64::max_value(), 0, WithdrawReasons::none()); - Balances::set_lock(ID_2, &1, 0, u64::max_value(), WithdrawReasons::none()); - Balances::set_lock(ID_3, &1, 0, 0, WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, u64::max_value(), 0, WithdrawReasons::none()); + Balances::set_lock(ID_2, &1, 0, u64::max_value(), WithdrawReasons::none()); + Balances::set_lock(ID_3, &1, 0, 0, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn lock_value_extension_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - Balances::extend_lock(ID_1, &1, 2, u64::max_value(), WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - Balances::extend_lock(ID_1, &1, 8, u64::max_value(), WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 3, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, 5, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 2, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 8, u64::max_value(), WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); } #[test] @@ -131,7 +161,8 @@ fn lock_reasons_should_work() { CALL, info_from_weight(1), 0, - ).is_ok()); + ) + .is_ok()); Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::Reserve.into()); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); @@ -145,9 +176,16 @@ fn lock_reasons_should_work() { CALL, info_from_weight(1), 0, - ).is_ok()); + ) + .is_ok()); - Balances::set_lock(ID_1, &1, 10, u64::max_value(), WithdrawReason::TransactionPayment.into()); + Balances::set_lock( + ID_1, + &1, + 10, + u64::max_value(), + WithdrawReason::TransactionPayment.into(), + ); assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); assert_ok!(>::reserve(&1, 1)); assert!( as SignedExtension>::pre_dispatch( @@ -156,65 +194,78 @@ fn lock_reasons_should_work() { CALL, info_from_weight(1), 0, - ).is_err()); + ) + .is_err()); }); } #[test] fn lock_block_number_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 1, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); - System::set_block_number(2); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); + System::set_block_number(2); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); } #[test] fn lock_block_number_extension_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - Balances::extend_lock(ID_1, &1, 10, 1, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - System::set_block_number(2); - Balances::extend_lock(ID_1, &1, 10, 8, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 3, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, 10, 2, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 10, 1, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + System::set_block_number(2); + Balances::extend_lock(ID_1, &1, 10, 8, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); } #[test] fn lock_reasons_extension_should_work() { - ExtBuilder::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 10, 10, WithdrawReason::Transfer.into()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReasons::none()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReason::Reserve.into()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - "account liquidity restrictions prevent withdrawal" - ); - }); + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build() + .execute_with(|| { + Balances::set_lock(ID_1, &1, 10, 10, WithdrawReason::Transfer.into()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReasons::none()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + Balances::extend_lock(ID_1, &1, 10, 10, WithdrawReason::Reserve.into()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + "account liquidity restrictions prevent withdrawal" + ); + }); } #[test] @@ -226,7 +277,7 @@ fn default_indexing_on_new_accounts_should_not_work2() { .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 + // ext_deposit is 10, value is 9, not satisfies for ext_deposit assert_noop!( Balances::transfer(Some(1).into(), 5, 9), "value too low to create account", @@ -260,9 +311,9 @@ fn reserved_balance_should_prevent_reclaim_count() { assert_eq!(Balances::is_dead_account(&5), false); assert!(Balances::slash(&2, 256 * 18 + 2).1.is_zero()); // account 2 gets slashed - // "reserve" account reduced to 255 (below ED) so account deleted + // "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!(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. @@ -272,7 +323,6 @@ fn reserved_balance_should_prevent_reclaim_count() { }); } - #[test] fn reward_should_work() { ExtBuilder::default().monied(true).build().execute_with(|| { @@ -347,10 +397,7 @@ fn balance_transfer_works() { fn force_transfer_works() { ExtBuilder::default().build().execute_with(|| { let _ = Balances::deposit_creating(&1, 111); - assert_noop!( - Balances::force_transfer(Some(2).into(), 1, 2, 69), - "RequireRootOrigin", - ); + assert_noop!(Balances::force_transfer(Some(2).into(), 1, 2, 69), "RequireRootOrigin",); 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); @@ -484,7 +531,10 @@ fn transferring_reserved_balance_to_nonexistent_should_fail() { ExtBuilder::default().build().execute_with(|| { let _ = Balances::deposit_creating(&1, 111); assert_ok!(Balances::reserve(&1, 111)); - assert_noop!(Balances::repatriate_reserved(&1, &2, 42), "beneficiary account must pre-exist"); + assert_noop!( + Balances::repatriate_reserved(&1, &2, 42), + "beneficiary account must pre-exist" + ); }); } @@ -531,7 +581,6 @@ fn account_create_on_free_too_low_with_other() { }) } - #[test] fn account_create_on_free_too_low() { ExtBuilder::default().existential_deposit(100).build().execute_with(|| { @@ -639,7 +688,6 @@ fn check_vesting_status() { assert_eq!(Balances::vesting_balance(&1), 0); // Account 1 is still fully vested, and not negative assert_eq!(Balances::vesting_balance(&2), 0); // Account 2 has fully vested by block 30 assert_eq!(Balances::vesting_balance(&12), 0); // Account 2 has fully vested by block 30 - }); } @@ -654,7 +702,7 @@ fn unvested_balance_should_not_transfer() { assert_eq!(System::block_number(), 1); let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Balances::vesting_balance(&1), 45); assert_noop!( Balances::transfer(Some(1).into(), 2, 56), @@ -674,7 +722,7 @@ fn vested_balance_should_transfer() { assert_eq!(System::block_number(), 1); let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 100); // Account 1 has free balance - // Account 1 has only 5 units vested at block 1 (plus 50 unvested) + // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Balances::vesting_balance(&1), 45); assert_ok!(Balances::transfer(Some(1).into(), 2, 55)); }); @@ -720,7 +768,7 @@ fn liquid_funds_should_transfer_with_delayed_vesting() { let user12_free_balance = Balances::free_balance(&12); assert_eq!(user12_free_balance, 2560); // Account 12 has free balance - // Account 12 has liquid funds + // Account 12 has liquid funds assert_eq!(Balances::vesting_balance(&12), user12_free_balance - 256 * 5); // Account 12 has delayed vesting diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 87c7b762e..ea86b74fc 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -26,7 +26,7 @@ use system::ensure_signed; use darwinia_support::{ traits::LockableCurrency, - types::{CompositeLock, Lock}, + types::{BalanceLocks, Lock}, }; use imbalance::{NegativeImbalance, PositiveImbalance}; @@ -231,7 +231,7 @@ impl Currency for Module { { Err("vesting balance too high to send value") } else { - if Self::can_withdraw(who, reasons, new_balance) { + if Self::locks(who).can_withdraw(>::now(), reasons, new_balance) { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -384,44 +384,24 @@ where let expired_locks_amount = if let Some(lock) = lock { locks.update_lock(lock, at) } else { - locks.remove_expired_lock(at) + locks.remove_expired_locks(at) }; >::insert(who, locks); expired_locks_amount } - fn remove_locks(who: &T::AccountId, lock: Self::Lock) -> Self::Balance { + fn remove_locks(who: &T::AccountId, lock: &Self::Lock) -> Self::Balance { let at = >::now(); - locks.remove_locks(at, lock) - } - - fn can_withdraw(who: &T::AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { - let composite_lock = Self::locks(who); - - if composite_lock.is_empty() { - return true; - } - - if { - let now = >::now(); - let mut locked_amount = composite_lock.staking_amount; - for lock in composite_lock.locks.into_iter() { - if lock.valid_at(now) && lock.reasons.intersects(reasons) { - // TODO: check overflow? - locked_amount += lock.amount; - } - } - - new_balance >= locked_amount - } { - return true; - } + let mut expired_locks_amount = Self::Balance::zero(); + >::mutate(who, |locks| { + expired_locks_amount = locks.remove_locks(at, lock); + }); - false + expired_locks_amount } fn locks_count(who: &T::AccountId) -> u32 { - >::get(who).locks.len() as _ + >::get(who).len() } } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 79eeb5aaf..3d1e4e516 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -38,7 +38,7 @@ use system::{ensure_root, ensure_signed}; use darwinia_support::{ traits::LockableCurrency, - types::{BalanceLock, LockUpdateStrategy, TimeStamp}, + types::{BalanceLock, Lock, TimeStamp}, }; use phragmen::{elect, equalize, ExtendedBalance, PhragmenStakedAssignment, Support, SupportMap}; @@ -203,14 +203,14 @@ type KtonNegativeImbalanceOf = <::Kton as Currency<, TimeStamp>, + Lock = Lock, TimeStamp>, Moment = TimeStamp, WithdrawReasons = WithdrawReasons, >; type Kton: LockableCurrency< Self::AccountId, + Lock = Lock, TimeStamp>, Moment = TimeStamp, - LockUpdateStrategy = LockUpdateStrategy, TimeStamp>, WithdrawReasons = WithdrawReasons, >; @@ -407,10 +407,7 @@ decl_module! { match value { StakingBalance::Ring(r) => { let stash_balance = T::Ring::free_balance(&stash); - let expired_locks_ring = T::Ring::update_lock( - &stash, - LockUpdateStrategy::new().with_check_expired(true), - ); + let expired_locks_ring = T::Ring::update_lock(&stash, None); // TODO: check underflow? ledger.total_ring -= expired_locks_ring; if let Some(extra) = stash_balance.checked_sub(&ledger.total_ring) { @@ -421,10 +418,7 @@ decl_module! { }, StakingBalance::Kton(k) => { let stash_balance = T::Kton::free_balance(&stash); - let expired_locks_kton = T::Kton::update_lock( - &stash, - LockUpdateStrategy::new().with_check_expired(true), - ); + let expired_locks_kton = T::Kton::update_lock(&stash, None); ledger.total_kton -= expired_locks_kton; if let Some(extra) = stash_balance.checked_sub(&ledger.total_kton) { let extra = extra.min(k); @@ -474,12 +468,11 @@ decl_module! { *active_ring -= available_unbond_ring; let expired_locks_ring = T::Ring::update_lock( stash, - LockUpdateStrategy::new() - .with_lock(BalanceLock { - amount: available_unbond_ring, - at, - reasons: WithdrawReasons::all() - }), + Some(Lock::Unbonding(BalanceLock { + amount: available_unbond_ring, + at, + reasons: WithdrawReasons::all() + })), ); // TODO: check underflow? *total_ring -= expired_locks_ring; @@ -496,12 +489,11 @@ decl_module! { *active_kton -= unbond_kton; let expired_locks_kton = T::Kton::update_lock( stash, - LockUpdateStrategy::new() - .with_lock(BalanceLock { - amount: unbond_kton, - at, - reasons: WithdrawReasons::all(), - }), + Some(Lock::Unbonding(BalanceLock { + amount: unbond_kton, + at, + reasons: WithdrawReasons::all(), + })), ); // TODO: check underflow? *total_kton -= expired_locks_kton; @@ -776,22 +768,12 @@ impl Module { ) { match staking_balance { StakingBalance::Ring(_r) => { - let expired_locks_ring = T::Ring::update_lock( - &ledger.stash, - LockUpdateStrategy::new() - .with_check_expired(true) - .with_staking_amount(ledger.active_ring), - ); + let expired_locks_ring = T::Ring::update_lock(&ledger.stash, Some(Lock::Staking(ledger.active_ring))); // TODO: check underflow? ledger.total_ring -= expired_locks_ring; } StakingBalance::Kton(_k) => { - let expired_locks_kton = T::Kton::update_lock( - &ledger.stash, - LockUpdateStrategy::new() - .with_check_expired(true) - .with_staking_amount(ledger.active_kton), - ); + let expired_locks_kton = T::Kton::update_lock(&ledger.stash, Some(Lock::Staking(ledger.active_kton))); // TODO: check underflow? ledger.total_kton -= expired_locks_kton; } diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index f03ba36cc..83bb275ee 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -4,7 +4,7 @@ use crate::mock::*; use srml_support::traits::{Currency, WithdrawReason, WithdrawReasons}; use srml_support::{assert_err, assert_ok}; -use darwinia_support::types::{BalanceLock, CompositeLock}; +use darwinia_support::types::{BalanceLock, BalanceLocks, Lock}; // gen_paired_account!(a(1), b(2), m(12)); // will create stash `a` and controller `b` @@ -1224,13 +1224,7 @@ fn xavier_q1() { )); assert_eq!(Timestamp::get(), 0); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!( - Kton::locks(stash), - CompositeLock { - staking_amount: 5, - locks: vec![] - } - ); + assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(5)])); // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1239,13 +1233,7 @@ fn xavier_q1() { assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(5), 0)); assert_eq!(Timestamp::get(), 1); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!( - Kton::locks(stash), - CompositeLock { - staking_amount: 10, - locks: vec![] - } - ); + assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(10)])); // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1257,14 +1245,14 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![BalanceLock { + BalanceLocks(vec![ + Lock::Staking(1), + Lock::Unbonding(BalanceLock { amount: 9, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() - }] - } + }) + ]) ); // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1291,26 +1279,20 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 9); assert_eq!( Kton::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![BalanceLock { + BalanceLocks(vec![ + Lock::Staking(1), + Lock::Unbonding(BalanceLock { amount: 9, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() - }] - } + }) + ]) ); Kton::deposit_creating(&stash, 20); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(19), 0)); assert_eq!(Kton::free_balance(stash), 29); - assert_eq!( - Kton::locks(stash), - CompositeLock { - staking_amount: 20, - locks: vec![] - } - ); + assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(20),])); assert_eq!( Staking::ledger(&controller).unwrap(), StakingLedgers { @@ -1331,129 +1313,6 @@ fn xavier_q1() { // ); // println!(); }); - - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 123; - let controller = 456; - let _ = Ring::deposit_creating(&stash, 10); - - Timestamp::set_timestamp(0); - assert_ok!(Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(5), - RewardDestination::Stash, - 0, - )); - assert_eq!(Timestamp::get(), 0); - assert_eq!(Ring::free_balance(stash), 10); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 5, - locks: vec![] - } - ); - // println!("Ok Init - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Ok Init - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - Timestamp::set_timestamp(1); - assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(5), 0)); - assert_eq!(Timestamp::get(), 1); - assert_eq!(Ring::free_balance(stash), 10); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 10, - locks: vec![] - } - ); - // println!("Ok Bond Extra - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Ok Bond Extra - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - let unbond_start = 2; - Timestamp::set_timestamp(unbond_start); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(9))); - assert_eq!(Timestamp::get(), 2); - assert_eq!(Ring::free_balance(stash), 10); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![BalanceLock { - amount: 9, - at: BondingDuration::get() + unbond_start, - reasons: WithdrawReasons::all() - }] - } - ); - // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - assert_err!( - Ring::transfer(Origin::signed(stash), controller, 1), - "account liquidity restrictions prevent withdrawal" - ); - // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - Timestamp::set_timestamp(BondingDuration::get() + unbond_start); - assert_ok!(Ring::transfer(Origin::signed(stash), controller, 1)); - // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - // println!( - // "Unlocking Transfer - Ring StakingLedgers: {:#?}", - // Staking::ledger(&controller) - // ); - // println!(); - assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start); - assert_eq!(Ring::free_balance(stash), 9); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![BalanceLock { - amount: 9, - at: BondingDuration::get() + unbond_start, - reasons: WithdrawReasons::all() - }] - } - ); - - let _ = Ring::deposit_creating(&stash, 20); - assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(19), 0)); - assert_eq!(Ring::free_balance(stash), 29); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 20, - locks: vec![] - } - ); - assert_eq!( - Staking::ledger(&controller).unwrap(), - StakingLedgers { - stash: 123, - total_ring: 20, - active_ring: 20, - active_deposit_ring: 0, - total_kton: 0, - active_kton: 0, - deposit_items: vec![], - } - ); - // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - // println!( - // "Unlocking Transfer - Ring StakingLedgers: {:#?}", - // Staking::ledger(&controller) - // ); - // println!(); - }); } #[test] @@ -1472,13 +1331,7 @@ fn xavier_q2() { 0, )); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!( - Kton::locks(stash), - CompositeLock { - staking_amount: 5, - locks: vec![] - } - ); + assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(5),])); // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1487,13 +1340,7 @@ fn xavier_q2() { assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(4), 0)); assert_eq!(Timestamp::get(), 1); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!( - Kton::locks(stash), - CompositeLock { - staking_amount: 9, - locks: vec![] - } - ); + assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(9),])); // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1508,14 +1355,14 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - CompositeLock { - staking_amount: 7, - locks: vec![BalanceLock { + BalanceLocks(vec![ + Lock::Staking(7), + Lock::Unbonding(BalanceLock { amount: 2, at: BondingDuration::get() + unbond_start_1, - reasons: WithdrawReasons::all(), - }] - } + reasons: WithdrawReasons::all() + }) + ]) ); // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1528,21 +1375,19 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![ - BalanceLock { - amount: 2, - at: BondingDuration::get() + unbond_start_1, - reasons: WithdrawReasons::all(), - }, - BalanceLock { - amount: 6, - at: BondingDuration::get() + unbond_start_2, - reasons: WithdrawReasons::all(), - }, - ] - } + BalanceLocks(vec![ + Lock::Staking(1), + Lock::Unbonding(BalanceLock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all() + }), + Lock::Unbonding(BalanceLock { + amount: 6, + at: BondingDuration::get() + unbond_start_2, + reasons: WithdrawReasons::all(), + }), + ]) ); // println!("Ok Unbond - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Unbond - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1574,21 +1419,19 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 7); assert_eq!( Kton::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![ - BalanceLock { - amount: 2, - at: 62, - reasons: WithdrawReasons::all(), - }, - BalanceLock { - amount: 6, - at: 63, - reasons: WithdrawReasons::all(), - }, - ] - } + BalanceLocks(vec![ + Lock::Staking(1), + Lock::Unbonding(BalanceLock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all() + }), + Lock::Unbonding(BalanceLock { + amount: 6, + at: BondingDuration::get() + unbond_start_2, + reasons: WithdrawReasons::all(), + }), + ]) ); // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1599,21 +1442,19 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 1); assert_eq!( Kton::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![ - BalanceLock { - amount: 2, - at: 62, - reasons: WithdrawReasons::all(), - }, - BalanceLock { - amount: 6, - at: 63, - reasons: WithdrawReasons::all(), - }, - ], - } + BalanceLocks(vec![ + Lock::Staking(1), + Lock::Unbonding(BalanceLock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all() + }), + Lock::Unbonding(BalanceLock { + amount: 6, + at: BondingDuration::get() + unbond_start_2, + reasons: WithdrawReasons::all(), + }), + ]) ); // println!("Unlocking Transfer - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Unlocking Transfer - Kton Locks: {:#?}", Kton::locks(stash)); @@ -1622,186 +1463,7 @@ fn xavier_q2() { // println!("Staking Ledger: {:#?}", Staking::ledger(controller).unwrap()); assert_eq!(Kton::free_balance(stash), 2); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 0)); - assert_eq!( - Kton::locks(stash), - CompositeLock { - staking_amount: 2, - locks: vec![], - } - ); - }); - - ExtBuilder::default().existential_deposit(0).build().execute_with(|| { - let stash = 123; - let controller = 456; - let _ = Ring::deposit_creating(&stash, 10); - - Timestamp::set_timestamp(1); - assert_ok!(Staking::bond( - Origin::signed(stash), - controller, - StakingBalance::Ring(5), - RewardDestination::Stash, - 0, - )); - assert_eq!(Ring::free_balance(stash), 10); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 5, - locks: vec![] - } - ); - // println!("Ok Init - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Ok Init - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - Timestamp::set_timestamp(1); - assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(4), 0)); - assert_eq!(Timestamp::get(), 1); - assert_eq!(Ring::free_balance(stash), 10); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 9, - locks: vec![] - } - ); - // println!("Ok Bond Extra - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Ok Bond Extra - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - let (unbond_value_1, unbond_start_1) = (2, 2); - Timestamp::set_timestamp(unbond_start_1); - assert_ok!(Staking::unbond( - Origin::signed(controller), - StakingBalance::Ring(unbond_value_1) - )); - assert_eq!(Timestamp::get(), unbond_start_1); - assert_eq!(Ring::free_balance(stash), 10); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 7, - locks: vec![BalanceLock { - amount: 2, - at: BondingDuration::get() + unbond_start_1, - reasons: WithdrawReasons::all(), - }] - } - ); - // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - let (unbond_value_2, unbond_start_2) = (6, 3); - Timestamp::set_timestamp(unbond_start_2); - assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(6))); - assert_eq!(Timestamp::get(), unbond_start_2); - assert_eq!(Ring::free_balance(stash), 10); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![ - BalanceLock { - amount: 2, - at: BondingDuration::get() + unbond_start_1, - reasons: WithdrawReasons::all(), - }, - BalanceLock { - amount: 6, - at: BondingDuration::get() + unbond_start_2, - reasons: WithdrawReasons::all(), - }, - ] - } - ); - // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - assert_err!( - Ring::transfer(Origin::signed(stash), controller, unbond_value_1), - "account liquidity restrictions prevent withdrawal" - ); - // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - - assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_1 - 1)); - assert_eq!(Ring::free_balance(stash), 9); - // println!("Normal Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Normal Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - - Timestamp::set_timestamp(BondingDuration::get() + unbond_start_1); - assert_err!( - Ring::transfer(Origin::signed(stash), controller, unbond_value_1 + 1), - "account liquidity restrictions prevent withdrawal" - ); - // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - // println!(); - assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_1)); - assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_1); - assert_eq!(Ring::free_balance(stash), 7); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![ - BalanceLock { - amount: 2, - at: 62, - reasons: WithdrawReasons::all(), - }, - BalanceLock { - amount: 6, - at: 63, - reasons: WithdrawReasons::all(), - }, - ] - } - ); - // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - - Timestamp::set_timestamp(BondingDuration::get() + unbond_start_2); - assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_2)); - assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_2); - assert_eq!(Ring::free_balance(stash), 1); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 1, - locks: vec![ - BalanceLock { - amount: 2, - at: 62, - reasons: WithdrawReasons::all(), - }, - BalanceLock { - amount: 6, - at: 63, - reasons: WithdrawReasons::all(), - }, - ], - } - ); - // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); - // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); - - let _ = Ring::deposit_creating(&stash, 1); - // println!("Staking Ledger: {:#?}", Staking::ledger(controller).unwrap()); - assert_eq!(Ring::free_balance(stash), 2); - assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(1), 0)); - assert_eq!( - Ring::locks(stash), - CompositeLock { - staking_amount: 2, - locks: vec![], - } - ); + assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(2),])); }); } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 5bf53cdfd..6f926d7e9 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -36,7 +36,7 @@ pub trait LockableCurrency: Currency { /// - Remove the expired locks on account `who`. /// - Update the global staking amount. /// - The function will return the sum of expired locks' amount. - fn update_lock(who: &AccountId, strategy: Self::Lock) -> Self::Balance; + fn update_lock(who: &AccountId, lock: Option) -> Self::Balance; // TODO: reserve // fn extend_lock(); @@ -44,9 +44,7 @@ pub trait LockableCurrency: Currency { /// Remove an existing lock. /// /// The function will return the sum of expired locks' amount. - fn remove_locks(who: &AccountId, lock: Self::Lock) -> Self::Balance; - - fn can_withdraw(who: &AccountId, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool; + fn remove_locks(who: &AccountId, lock: &Self::Lock) -> Self::Balance; /// The number of locks. fn locks_count(who: &AccountId) -> u32; diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 13a08c63f..66f4e0942 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -5,8 +5,8 @@ use srml_support::traits::WithdrawReasons; pub type TimeStamp = u64; -#[derive(Clone, Default, Encode, Decode, RuntimeDebug)] -pub struct BalanceLocks(Vec>); +#[derive(Clone, Default, PartialEq, Encode, Decode, RuntimeDebug)] +pub struct BalanceLocks(pub Vec>); impl BalanceLocks where @@ -14,31 +14,33 @@ where Moment: Clone + Copy + PartialOrd, { #[inline] - fn update_lock(&mut self, lock: Lock, at: Moment) -> Balance { - let expired_locks_amount = self.remove_expired_locks(at); + pub fn len(&self) -> u32 { + self.0.len() as _ + } + + #[inline] + pub fn update_lock(&mut self, lock: Lock, at: Moment) -> Balance { + let expired_locks_amount = self.remove_locks(at, &lock); self.0.push(lock); expired_locks_amount } - fn remove_locks(&mut self, at: Moment, lock: Lock) -> Balance { - let mut expired_locks_amount = Self::Balance::zero(); - - >::mutate(who, |locks| { - locks.retain(|lock_| { - if lock_.valid_at(now) && lock == lock { - true - } else { - expired_locks_amount += lock.amount; - false - } - }); + pub fn remove_locks(&mut self, at: Moment, lock: &Lock) -> Balance { + let mut expired_locks_amount = Balance::zero(); + self.0.retain(|lock_| { + if lock_.valid_at(at) && lock_ == lock { + true + } else { + expired_locks_amount += lock.amount(); + false + } }); expired_locks_amount } - fn remove_expired_locks(&mut self, at: Moment) -> Balance { + pub fn remove_expired_locks(&mut self, at: Moment) -> Balance { let mut expired_locks_amount = Balance::default(); self.0.retain(|lock| { if lock.valid_at(at) { @@ -51,9 +53,25 @@ where expired_locks_amount } + + pub fn can_withdraw(&self, at: Moment, reasons: WithdrawReasons, new_balance: Balance) -> bool { + if self.0.is_empty() { + return true; + } + + let mut locked_amount = Balance::default(); + for lock in self.0.iter() { + if lock.valid_at(at) && lock.check_reasons_intersects(reasons) { + // TODO: check overflow? + locked_amount += lock.amount(); + } + } + + new_balance >= locked_amount + } } -#[derive(Clone, RuntimeDebug)] +#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug)] pub enum Lock { Staking(Balance), Unbonding(BalanceLock), @@ -79,6 +97,14 @@ where Lock::Unbonding(balance_lock) => balance_lock.amount, } } + + #[inline] + fn check_reasons_intersects(&self, reasons: WithdrawReasons) -> bool { + match self { + Lock::Staking(_) => true, + Lock::Unbonding(balance_lock) => balance_lock.reasons.intersects(reasons), + } + } } #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug)] From 4f39b0d394d2925b69af63b402b37ade4908ae4e Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 10:44:16 +0800 Subject: [PATCH 20/30] fix: pass `cargo fmt` --- core/sr-rlp/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/sr-rlp/src/lib.rs b/core/sr-rlp/src/lib.rs index 860598826..212535ce7 100644 --- a/core/sr-rlp/src/lib.rs +++ b/core/sr-rlp/src/lib.rs @@ -1,5 +1,4 @@ +// RLP lib for eth transaction encoding -/// RLP lib for eth transaction encoding - -#![recursion_limit = "128"] -#![cfg_attr(not(feature = "std"), no_std)] +//#![recursion_limit = "128"] +//#![cfg_attr(not(feature = "std"), no_std)] From c2642a5bfec0e0f4d7059425326fb511dccd470c Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 10:44:36 +0800 Subject: [PATCH 21/30] remove: unused macro --- srml/staking/src/phragmen.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs index 5ec9dcccf..189aeeff3 100644 --- a/srml/staking/src/phragmen.rs +++ b/srml/staking/src/phragmen.rs @@ -31,8 +31,6 @@ //! Further details: //! https://research.web3.foundation/en/latest/polkadot/NPoS/4.%20Sequential%20Phragm%C3%A9n%E2%80%99s%20method/ -#![cfg_attr(not(feature = "std"), no_std)] - use rstd::{collections::btree_map::BTreeMap, prelude::*}; use sr_primitives::traits::{Bounded, Member, Saturating, Zero}; use sr_primitives::{helpers_128bit::multiply_by_rational, Perbill, Rational128}; From 425099bb49ac1e5697588f8dbf58ab2e05d3521c Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 10:45:05 +0800 Subject: [PATCH 22/30] update: new `LockableCurrency` design --- srml/balances/src/lib.rs | 21 +++--- srml/kton/src/lib.rs | 21 +++--- srml/staking/src/lib.rs | 16 ++-- srml/staking/src/tests.rs | 60 +++++++-------- srml/support/src/lib.rs | 1 + srml/support/src/traits.rs | 30 +++++++- srml/support/src/types.rs | 151 ++++++++++++++++++++++++++++++------- 7 files changed, 214 insertions(+), 86 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 8e873446a..6823f686f 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -44,8 +44,8 @@ mod tests; pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; use darwinia_support::{ - traits::LockableCurrency, - types::{BalanceLocks, Lock}, + traits::{LockableCurrency, Locks as LocksTrait}, + types::{CompositeLock, Locks as LocksStruct}, }; pub trait Subtrait: system::Trait + timestamp::Trait { @@ -235,7 +235,7 @@ decl_storage! { pub ReservedBalance get(fn reserved_balance): map T::AccountId => T::Balance; /// Any liquidity locks on some account balances. - pub Locks get(locks): map T::AccountId => BalanceLocks; + pub Locks get(locks): map T::AccountId => LocksStruct; } add_extra_genesis { config(balances): Vec<(T::AccountId, T::Balance)>; @@ -733,7 +733,7 @@ where { Err("vesting balance too high to send value") } else { - if Self::locks(who).can_withdraw(>::now(), reasons, new_balance) { + if Self::locks(who).ensure_can_withdraw(>::now(), reasons, new_balance) { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -975,10 +975,15 @@ impl, I: Instance> LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Lock = Lock; + type Lock = CompositeLock; type Moment = T::Moment; type WithdrawReasons = WithdrawReasons; + #[inline] + fn locks_count(who: &T::AccountId) -> u32 { + >::get(who).locks_count() + } + fn update_lock(who: &T::AccountId, lock: Option) -> Self::Balance { let at = >::now(); let mut locks = Self::locks(who); @@ -996,15 +1001,11 @@ where let at = >::now(); let mut expired_locks_amount = Self::Balance::zero(); >::mutate(who, |locks| { - expired_locks_amount = locks.remove_locks(at, lock); + expired_locks_amount = locks.remove_locks(lock, at); }); expired_locks_amount } - - fn locks_count(who: &T::AccountId) -> u32 { - >::get(who).len() - } } impl, I: Instance> IsDeadAccount for Module diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index ea86b74fc..17f86c3d8 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -25,8 +25,8 @@ use srml_support::{ use system::ensure_signed; use darwinia_support::{ - traits::LockableCurrency, - types::{BalanceLocks, Lock}, + traits::{LockableCurrency, Locks as LocksTrait}, + types::{CompositeLock, Locks as LocksStruct}, }; use imbalance::{NegativeImbalance, PositiveImbalance}; @@ -105,7 +105,7 @@ decl_storage! { pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance; - pub Locks get(locks): map T::AccountId => BalanceLocks; + pub Locks get(locks): map T::AccountId => LocksStruct; pub TotalLock get(total_lock): T::Balance; @@ -231,7 +231,7 @@ impl Currency for Module { { Err("vesting balance too high to send value") } else { - if Self::locks(who).can_withdraw(>::now(), reasons, new_balance) { + if Self::locks(who).ensure_can_withdraw(>::now(), reasons, new_balance) { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") @@ -374,10 +374,15 @@ impl LockableCurrency for Module where T::Balance: MaybeSerializeDeserialize + Debug, { - type Lock = Lock; + type Lock = CompositeLock; type Moment = T::Moment; type WithdrawReasons = WithdrawReasons; + #[inline] + fn locks_count(who: &T::AccountId) -> u32 { + >::get(who).locks_count() + } + fn update_lock(who: &T::AccountId, lock: Option) -> Self::Balance { let at = >::now(); let mut locks = Self::locks(who); @@ -395,13 +400,9 @@ where let at = >::now(); let mut expired_locks_amount = Self::Balance::zero(); >::mutate(who, |locks| { - expired_locks_amount = locks.remove_locks(at, lock); + expired_locks_amount = locks.remove_locks(lock, at); }); expired_locks_amount } - - fn locks_count(who: &T::AccountId) -> u32 { - >::get(who).len() - } } diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 3d1e4e516..96eda7ffb 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -38,7 +38,7 @@ use system::{ensure_root, ensure_signed}; use darwinia_support::{ traits::LockableCurrency, - types::{BalanceLock, Lock, TimeStamp}, + types::{CompositeLock, Lock, TimeStamp}, }; use phragmen::{elect, equalize, ExtendedBalance, PhragmenStakedAssignment, Support, SupportMap}; @@ -203,13 +203,13 @@ type KtonNegativeImbalanceOf = <::Kton as Currency<, TimeStamp>, + Lock = CompositeLock, TimeStamp>, Moment = TimeStamp, WithdrawReasons = WithdrawReasons, >; type Kton: LockableCurrency< Self::AccountId, - Lock = Lock, TimeStamp>, + Lock = CompositeLock, TimeStamp>, Moment = TimeStamp, WithdrawReasons = WithdrawReasons, >; @@ -468,7 +468,7 @@ decl_module! { *active_ring -= available_unbond_ring; let expired_locks_ring = T::Ring::update_lock( stash, - Some(Lock::Unbonding(BalanceLock { + Some(CompositeLock::Unbonding(Lock { amount: available_unbond_ring, at, reasons: WithdrawReasons::all() @@ -489,7 +489,7 @@ decl_module! { *active_kton -= unbond_kton; let expired_locks_kton = T::Kton::update_lock( stash, - Some(Lock::Unbonding(BalanceLock { + Some(CompositeLock::Unbonding(Lock { amount: unbond_kton, at, reasons: WithdrawReasons::all(), @@ -768,12 +768,14 @@ impl Module { ) { match staking_balance { StakingBalance::Ring(_r) => { - let expired_locks_ring = T::Ring::update_lock(&ledger.stash, Some(Lock::Staking(ledger.active_ring))); + let expired_locks_ring = + T::Ring::update_lock(&ledger.stash, Some(CompositeLock::Staking(ledger.active_ring))); // TODO: check underflow? ledger.total_ring -= expired_locks_ring; } StakingBalance::Kton(_k) => { - let expired_locks_kton = T::Kton::update_lock(&ledger.stash, Some(Lock::Staking(ledger.active_kton))); + let expired_locks_kton = + T::Kton::update_lock(&ledger.stash, Some(CompositeLock::Staking(ledger.active_kton))); // TODO: check underflow? ledger.total_kton -= expired_locks_kton; } diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 83bb275ee..cfba6cea9 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -4,7 +4,7 @@ use crate::mock::*; use srml_support::traits::{Currency, WithdrawReason, WithdrawReasons}; use srml_support::{assert_err, assert_ok}; -use darwinia_support::types::{BalanceLock, BalanceLocks, Lock}; +use darwinia_support::types::{CompositeLock, Lock, Locks}; // gen_paired_account!(a(1), b(2), m(12)); // will create stash `a` and controller `b` @@ -155,7 +155,7 @@ fn test_env_build() { // // assert_eq!( // Kton::locks(&1001), -// vec![kton::BalanceLock { +// vec![kton::Lock { // id: STAKING_ID, // amount: 10 * COIN, // until: u64::max_value(), @@ -303,7 +303,7 @@ fn test_env_build() { // let free_balance = Ring::free_balance(&11); // assert_eq!( // Ring::locks(&11), -// vec![balances::BalanceLock { +// vec![balances::Lock { // id: STAKING_ID, // amount: 0, // until: u64::max_value(), @@ -1224,7 +1224,7 @@ fn xavier_q1() { )); assert_eq!(Timestamp::get(), 0); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(5)])); + assert_eq!(Kton::locks(stash), Locks(vec![CompositeLock::Staking(5)])); // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1233,7 +1233,7 @@ fn xavier_q1() { assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(5), 0)); assert_eq!(Timestamp::get(), 1); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(10)])); + assert_eq!(Kton::locks(stash), Locks(vec![CompositeLock::Staking(10)])); // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1245,9 +1245,9 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - BalanceLocks(vec![ - Lock::Staking(1), - Lock::Unbonding(BalanceLock { + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { amount: 9, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() @@ -1279,9 +1279,9 @@ fn xavier_q1() { assert_eq!(Kton::free_balance(stash), 9); assert_eq!( Kton::locks(stash), - BalanceLocks(vec![ - Lock::Staking(1), - Lock::Unbonding(BalanceLock { + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { amount: 9, at: BondingDuration::get() + unbond_start, reasons: WithdrawReasons::all() @@ -1292,7 +1292,7 @@ fn xavier_q1() { Kton::deposit_creating(&stash, 20); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(19), 0)); assert_eq!(Kton::free_balance(stash), 29); - assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(20),])); + assert_eq!(Kton::locks(stash), Locks(vec![CompositeLock::Staking(20)])); assert_eq!( Staking::ledger(&controller).unwrap(), StakingLedgers { @@ -1331,7 +1331,7 @@ fn xavier_q2() { 0, )); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(5),])); + assert_eq!(Kton::locks(stash), Locks(vec![CompositeLock::Staking(5)])); // println!("Ok Init - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Init - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1340,7 +1340,7 @@ fn xavier_q2() { assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(4), 0)); assert_eq!(Timestamp::get(), 1); assert_eq!(Kton::free_balance(stash), 10); - assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(9),])); + assert_eq!(Kton::locks(stash), Locks(vec![CompositeLock::Staking(9)])); // println!("Ok Bond Extra - Kton Balance: {:?}", Kton::free_balance(stash)); // println!("Ok Bond Extra - Kton Locks: {:#?}", Kton::locks(stash)); // println!(); @@ -1355,9 +1355,9 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - BalanceLocks(vec![ - Lock::Staking(7), - Lock::Unbonding(BalanceLock { + Locks(vec![ + CompositeLock::Staking(7), + CompositeLock::Unbonding(Lock { amount: 2, at: BondingDuration::get() + unbond_start_1, reasons: WithdrawReasons::all() @@ -1375,14 +1375,14 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 10); assert_eq!( Kton::locks(stash), - BalanceLocks(vec![ - Lock::Staking(1), - Lock::Unbonding(BalanceLock { + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { amount: 2, at: BondingDuration::get() + unbond_start_1, reasons: WithdrawReasons::all() }), - Lock::Unbonding(BalanceLock { + CompositeLock::Unbonding(Lock { amount: 6, at: BondingDuration::get() + unbond_start_2, reasons: WithdrawReasons::all(), @@ -1419,14 +1419,14 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 7); assert_eq!( Kton::locks(stash), - BalanceLocks(vec![ - Lock::Staking(1), - Lock::Unbonding(BalanceLock { + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { amount: 2, at: BondingDuration::get() + unbond_start_1, reasons: WithdrawReasons::all() }), - Lock::Unbonding(BalanceLock { + CompositeLock::Unbonding(Lock { amount: 6, at: BondingDuration::get() + unbond_start_2, reasons: WithdrawReasons::all(), @@ -1442,14 +1442,14 @@ fn xavier_q2() { assert_eq!(Kton::free_balance(stash), 1); assert_eq!( Kton::locks(stash), - BalanceLocks(vec![ - Lock::Staking(1), - Lock::Unbonding(BalanceLock { + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { amount: 2, at: BondingDuration::get() + unbond_start_1, reasons: WithdrawReasons::all() }), - Lock::Unbonding(BalanceLock { + CompositeLock::Unbonding(Lock { amount: 6, at: BondingDuration::get() + unbond_start_2, reasons: WithdrawReasons::all(), @@ -1463,7 +1463,7 @@ fn xavier_q2() { // println!("Staking Ledger: {:#?}", Staking::ledger(controller).unwrap()); assert_eq!(Kton::free_balance(stash), 2); assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 0)); - assert_eq!(Kton::locks(stash), BalanceLocks(vec![Lock::Staking(2),])); + assert_eq!(Kton::locks(stash), Locks(vec![CompositeLock::Staking(2)])); }); } diff --git a/srml/support/src/lib.rs b/srml/support/src/lib.rs index 5d8de4676..a7205d2f7 100644 --- a/srml/support/src/lib.rs +++ b/srml/support/src/lib.rs @@ -1,4 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![feature(drain_filter)] pub mod traits; pub mod types; diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 6f926d7e9..52f8a3b1c 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -29,6 +29,9 @@ pub trait LockableCurrency: Currency { type Moment; type WithdrawReasons; + /// The number of locks. + fn locks_count(who: &AccountId) -> u32; + /// - Create a new balance lock on account `who`. /// - If the new lock is valid (i.e. not already expired), it will push the struct to /// the `Locks` vec in storage. Note that you can lock more funds than a user has. @@ -45,7 +48,32 @@ pub trait LockableCurrency: Currency { /// /// The function will return the sum of expired locks' amount. fn remove_locks(who: &AccountId, lock: &Self::Lock) -> Self::Balance; +} + +pub trait Locks { + type Balance; + type Lock; + type Moment; + type WithdrawReasons; /// The number of locks. - fn locks_count(who: &AccountId) -> u32; + fn locks_count(&self) -> u32; + + /// - Create a new balance lock on account `who`. + /// - If the new lock is valid (i.e. not already expired), it will push the struct to + /// the `Locks` vec in storage. Note that you can lock more funds than a user has. + /// - If the lock `id/until` already exists, this will update it. + /// - Remove the expired locks on account `who`. + /// - Update the global staking amount. + /// - The function will return the sum of expired locks' amount. + fn update_lock(&mut self, lock: Self::Lock, at: Self::Moment) -> Self::Balance; + + /// Remove expired locks + fn remove_expired_locks(&mut self, at: Self::Moment) -> Self::Balance; + + /// Remove specify locks and expired locks + fn remove_locks(&mut self, lock: &Self::Lock, at: Self::Moment) -> Self::Balance; + + fn ensure_can_withdraw(&self, at: Self::Moment, reasons: Self::WithdrawReasons, new_balance: Self::Balance) + -> bool; } diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 66f4e0942..59b9ac2e1 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -3,33 +3,43 @@ use rstd::vec::Vec; use sr_primitives::{traits::SimpleArithmetic, RuntimeDebug}; use srml_support::traits::WithdrawReasons; +use super::traits::Locks as LocksTrait; + pub type TimeStamp = u64; #[derive(Clone, Default, PartialEq, Encode, Decode, RuntimeDebug)] -pub struct BalanceLocks(pub Vec>); +pub struct Locks(pub Vec>); -impl BalanceLocks +impl LocksTrait for Locks where Balance: Clone + Copy + Default + SimpleArithmetic, Moment: Clone + Copy + PartialOrd, { + type Balance = Balance; + type Lock = CompositeLock; + type Moment = Moment; + type WithdrawReasons = WithdrawReasons; + #[inline] - pub fn len(&self) -> u32 { + fn locks_count(&self) -> u32 { self.0.len() as _ } - #[inline] - pub fn update_lock(&mut self, lock: Lock, at: Moment) -> Balance { - let expired_locks_amount = self.remove_locks(at, &lock); - self.0.push(lock); + fn update_lock(&mut self, lock: Self::Lock, at: Self::Moment) -> Self::Balance { + let expired_locks_amount = self.remove_expired_locks(at); + if let Some(i) = self.0.iter().position(|lock_| lock_ == &lock) { + self.0[i] = lock; + } else { + self.0.push(lock); + } expired_locks_amount } - pub fn remove_locks(&mut self, at: Moment, lock: &Lock) -> Balance { - let mut expired_locks_amount = Balance::zero(); - self.0.retain(|lock_| { - if lock_.valid_at(at) && lock_ == lock { + fn remove_expired_locks(&mut self, at: Self::Moment) -> Self::Balance { + let mut expired_locks_amount = Balance::default(); + self.0.retain(|lock| { + if lock.valid_at(at) { true } else { expired_locks_amount += lock.amount(); @@ -40,13 +50,13 @@ where expired_locks_amount } - pub fn remove_expired_locks(&mut self, at: Moment) -> Balance { - let mut expired_locks_amount = Balance::default(); - self.0.retain(|lock| { - if lock.valid_at(at) { + fn remove_locks(&mut self, lock: &Self::Lock, at: Self::Moment) -> Self::Balance { + let mut expired_locks_amount = Balance::zero(); + self.0.retain(|lock_| { + if lock_.valid_at(at) && lock_ != lock { true } else { - expired_locks_amount += lock.amount(); + expired_locks_amount += lock_.amount(); false } }); @@ -54,7 +64,12 @@ where expired_locks_amount } - pub fn can_withdraw(&self, at: Moment, reasons: WithdrawReasons, new_balance: Balance) -> bool { + fn ensure_can_withdraw( + &self, + at: Self::Moment, + reasons: Self::WithdrawReasons, + new_balance: Self::Balance, + ) -> bool { if self.0.is_empty() { return true; } @@ -71,13 +86,79 @@ where } } -#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug)] -pub enum Lock { +//impl BalanceLocks +//where +// Balance: Clone + Copy + Default + SimpleArithmetic, +// Moment: Clone + Copy + PartialOrd, +//{ +// #[inline] +// pub fn len(&self) -> u32 { +// self.0.len() as _ +// } +// +// pub fn update_lock(&mut self, lock: Lock, at: Moment) -> Balance { +// let expired_locks_amount = self.remove_expired_locks(at); +// if let Some(i) = self.0.iter().position(|lock_| lock_ == &lock) { +// self.0[i] = lock; +// } else { +// self.0.push(lock); +// } +// +// expired_locks_amount +// } +// +// pub fn remove_expired_locks(&mut self, at: Moment) -> Balance { +// let mut expired_locks_amount = Balance::default(); +// self.0.retain(|lock| { +// if lock.valid_at(at) { +// true +// } else { +// expired_locks_amount += lock.amount(); +// false +// } +// }); +// +// expired_locks_amount +// } +// +// pub fn remove_locks(&mut self, lock: &Lock, at: Moment) -> Balance { +// let mut expired_locks_amount = Balance::zero(); +// self.0.retain(|lock_| { +// if lock_.valid_at(at) && lock_ != lock { +// true +// } else { +// expired_locks_amount += lock_.amount(); +// false +// } +// }); +// +// expired_locks_amount +// } +// +// pub fn ensure_can_withdraw(&self, at: Moment, reasons: WithdrawReasons, new_balance: Balance) -> bool { +// if self.0.is_empty() { +// return true; +// } +// +// let mut locked_amount = Balance::default(); +// for lock in self.0.iter() { +// if lock.valid_at(at) && lock.check_reasons_intersects(reasons) { +// // TODO: check overflow? +// locked_amount += lock.amount(); +// } +// } +// +// new_balance >= locked_amount +// } +//} + +#[derive(Clone, Encode, Decode, RuntimeDebug)] +pub enum CompositeLock { Staking(Balance), - Unbonding(BalanceLock), + Unbonding(Lock), } -impl Lock +impl CompositeLock where Balance: Copy, Moment: PartialOrd, @@ -85,30 +166,44 @@ where #[inline] fn valid_at(&self, at: Moment) -> bool { match self { - Lock::Staking(_) => true, - Lock::Unbonding(balance_lock) => balance_lock.at > at, + CompositeLock::Staking(_) => true, + CompositeLock::Unbonding(balance_lock) => balance_lock.at > at, } } #[inline] fn amount(&self) -> Balance { match self { - Lock::Staking(balance) => *balance, - Lock::Unbonding(balance_lock) => balance_lock.amount, + CompositeLock::Staking(balance) => *balance, + CompositeLock::Unbonding(balance_lock) => balance_lock.amount, } } #[inline] fn check_reasons_intersects(&self, reasons: WithdrawReasons) -> bool { match self { - Lock::Staking(_) => true, - Lock::Unbonding(balance_lock) => balance_lock.reasons.intersects(reasons), + CompositeLock::Staking(_) => true, + CompositeLock::Unbonding(balance_lock) => balance_lock.reasons.intersects(reasons), + } + } +} + +impl PartialEq for CompositeLock +where + Moment: PartialEq, +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (CompositeLock::Staking(_), CompositeLock::Staking(_)) => true, + (CompositeLock::Unbonding(a), CompositeLock::Unbonding(b)) => a.at == b.at, + _ => false, } } } #[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug)] -pub struct BalanceLock { +pub struct Lock { pub amount: Balance, pub at: Moment, pub reasons: WithdrawReasons, From 85966d42a0a983a528db9ce9664806423f4338e6 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 11:27:19 +0800 Subject: [PATCH 23/30] add: tests for Ring --- srml/staking/src/tests.rs | 254 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 254 insertions(+) diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index e005ba453..5d103da2b 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -1313,6 +1313,111 @@ fn xavier_q1() { // ); // println!(); }); + + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + let stash = 123; + let controller = 456; + let _ = Ring::deposit_creating(&stash, 10); + + Timestamp::set_timestamp(0); + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Ring(5), + RewardDestination::Stash, + 0, + )); + assert_eq!(Timestamp::get(), 0); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!(Ring::locks(stash), Locks(vec![CompositeLock::Staking(5)])); + // println!("Ok Init - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Init - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(5), 0)); + assert_eq!(Timestamp::get(), 1); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!(Ring::locks(stash), Locks(vec![CompositeLock::Staking(10)])); + // println!("Ok Bond Extra - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Bond Extra - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + let unbond_start = 2; + Timestamp::set_timestamp(unbond_start); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(9))); + assert_eq!(Timestamp::get(), 2); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { + amount: 9, + at: BondingDuration::get() + unbond_start, + reasons: WithdrawReasons::all() + }) + ]) + ); + // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + assert_err!( + Ring::transfer(Origin::signed(stash), controller, 1), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start); + assert_ok!(Ring::transfer(Origin::signed(stash), controller, 1)); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!( + // "Unlocking Transfer - Ring StakingLedger: {:#?}", + // Staking::ledger(&controller) + // ); + // println!(); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start); + assert_eq!(Ring::free_balance(stash), 9); + assert_eq!( + Ring::locks(stash), + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { + amount: 9, + at: BondingDuration::get() + unbond_start, + reasons: WithdrawReasons::all() + }) + ]) + ); + + let _ = Ring::deposit_creating(&stash, 20); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(19), 0)); + assert_eq!(Ring::free_balance(stash), 29); + assert_eq!(Ring::locks(stash), Locks(vec![CompositeLock::Staking(20)])); + assert_eq!( + Staking::ledger(&controller).unwrap(), + StakingLedger { + stash: 123, + total_ring: 20, + active_ring: 20, + active_deposit_ring: 0, + total_kton: 0, + active_kton: 0, + deposit_items: vec![], + } + ); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!( + // "Unlocking Transfer - Ring StakingLedger: {:#?}", + // Staking::ledger(&controller) + // ); + // println!(); + }); } #[test] @@ -1465,6 +1570,155 @@ fn xavier_q2() { assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Kton(1), 0)); assert_eq!(Kton::locks(stash), Locks(vec![CompositeLock::Staking(2)])); }); + + ExtBuilder::default().existential_deposit(0).build().execute_with(|| { + let stash = 123; + let controller = 456; + let _ = Ring::deposit_creating(&stash, 10); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond( + Origin::signed(stash), + controller, + StakingBalance::Ring(5), + RewardDestination::Stash, + 0, + )); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!(Ring::locks(stash), Locks(vec![CompositeLock::Staking(5)])); + // println!("Ok Init - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Init - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + Timestamp::set_timestamp(1); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(4), 0)); + assert_eq!(Timestamp::get(), 1); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!(Ring::locks(stash), Locks(vec![CompositeLock::Staking(9)])); + // println!("Ok Bond Extra - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Bond Extra - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + let (unbond_value_1, unbond_start_1) = (2, 2); + Timestamp::set_timestamp(unbond_start_1); + assert_ok!(Staking::unbond( + Origin::signed(controller), + StakingBalance::Ring(unbond_value_1) + )); + assert_eq!(Timestamp::get(), unbond_start_1); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + Locks(vec![ + CompositeLock::Staking(7), + CompositeLock::Unbonding(Lock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all() + }) + ]) + ); + // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + let (unbond_value_2, unbond_start_2) = (6, 3); + Timestamp::set_timestamp(unbond_start_2); + assert_ok!(Staking::unbond(Origin::signed(controller), StakingBalance::Ring(6))); + assert_eq!(Timestamp::get(), unbond_start_2); + assert_eq!(Ring::free_balance(stash), 10); + assert_eq!( + Ring::locks(stash), + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all() + }), + CompositeLock::Unbonding(Lock { + amount: 6, + at: BondingDuration::get() + unbond_start_2, + reasons: WithdrawReasons::all(), + }), + ]) + ); + // println!("Ok Unbond - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Ok Unbond - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + assert_err!( + Ring::transfer(Origin::signed(stash), controller, unbond_value_1), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + + assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_1 - 1)); + assert_eq!(Ring::free_balance(stash), 9); + // println!("Normal Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Normal Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start_1); + assert_err!( + Ring::transfer(Origin::signed(stash), controller, unbond_value_1 + 1), + "account liquidity restrictions prevent withdrawal" + ); + // println!("Locking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Locking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + // println!(); + assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_1)); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_1); + assert_eq!(Ring::free_balance(stash), 7); + assert_eq!( + Ring::locks(stash), + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all() + }), + CompositeLock::Unbonding(Lock { + amount: 6, + at: BondingDuration::get() + unbond_start_2, + reasons: WithdrawReasons::all(), + }), + ]) + ); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + + Timestamp::set_timestamp(BondingDuration::get() + unbond_start_2); + assert_ok!(Ring::transfer(Origin::signed(stash), controller, unbond_value_2)); + assert_eq!(Timestamp::get(), BondingDuration::get() + unbond_start_2); + assert_eq!(Ring::free_balance(stash), 1); + assert_eq!( + Ring::locks(stash), + Locks(vec![ + CompositeLock::Staking(1), + CompositeLock::Unbonding(Lock { + amount: 2, + at: BondingDuration::get() + unbond_start_1, + reasons: WithdrawReasons::all() + }), + CompositeLock::Unbonding(Lock { + amount: 6, + at: BondingDuration::get() + unbond_start_2, + reasons: WithdrawReasons::all(), + }), + ]) + ); + // println!("Unlocking Transfer - Ring Balance: {:?}", Ring::free_balance(stash)); + // println!("Unlocking Transfer - Ring Locks: {:#?}", Ring::locks(stash)); + + let _ = Ring::deposit_creating(&stash, 1); + // println!("Staking Ledger: {:#?}", Staking::ledger(controller).unwrap()); + assert_eq!(Ring::free_balance(stash), 2); + assert_ok!(Staking::bond_extra(Origin::signed(stash), StakingBalance::Ring(1), 0)); + assert_eq!(Ring::locks(stash), Locks(vec![CompositeLock::Staking(2)])); + }); } #[test] From 1698c2c114be5081f523a4a715095cafbc44b8df Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 11:40:39 +0800 Subject: [PATCH 24/30] update: compare condition --- srml/support/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 59b9ac2e1..4e9024421 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -196,7 +196,7 @@ where fn eq(&self, other: &Self) -> bool { match (self, other) { (CompositeLock::Staking(_), CompositeLock::Staking(_)) => true, - (CompositeLock::Unbonding(a), CompositeLock::Unbonding(b)) => a.at == b.at, + (CompositeLock::Unbonding(a), CompositeLock::Unbonding(b)) => a == b, _ => false, } } From 912c9d501baf23430cf967e809e00e12a9b04952 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 12:00:14 +0800 Subject: [PATCH 25/30] fix: missing `PartialEq` --- srml/support/src/types.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 4e9024421..57a80bad3 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -190,6 +190,7 @@ where impl PartialEq for CompositeLock where + Balance: PartialEq, Moment: PartialEq, { #[inline] From f9cdfc9f74d9f665c58ea33da789880c30ba4d1b Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 12:23:17 +0800 Subject: [PATCH 26/30] update: `Locks` storage accept a `Locks` struct which implement `Locks` trait --- node/runtime/src/lib.rs | 4 +++- srml/balances/src/lib.rs | 26 +++++++++++++++++++++++--- srml/kton/src/lib.rs | 16 ++++++++++++---- srml/staking/src/mock.rs | 6 ++++-- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index e16a070a6..8c116ed6e 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -60,7 +60,7 @@ use substrate_primitives::OpaqueMetadata; use system::offchain::TransactionSubmitter; use transaction_payment_rpc_runtime_api::RuntimeDispatchInfo; -use darwinia_support::types::TimeStamp; +use darwinia_support::types::{Locks, TimeStamp}; use staking::EraIndex; pub use staking::StakerStatus; @@ -182,6 +182,7 @@ impl balances::Trait for Runtime { type ExistentialDeposit = ExistentialDeposit; type TransferFee = TransferFee; type CreationFee = CreationFee; + type Locks = Locks; } parameter_types! { @@ -382,6 +383,7 @@ impl kton::Trait for Runtime { type Event = Event; type OnMinted = (); type OnRemoval = (); + type Locks = Locks; } parameter_types! { diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 6823f686f..810ffeab8 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -16,7 +16,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Codec, Decode, Encode}; +use codec::{Codec, Decode, Encode, EncodeLike}; #[cfg(not(feature = "std"))] use rstd::borrow::ToOwned; use rstd::{cmp, fmt::Debug, mem, prelude::*, result}; @@ -45,7 +45,7 @@ mod tests; pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; use darwinia_support::{ traits::{LockableCurrency, Locks as LocksTrait}, - types::{CompositeLock, Locks as LocksStruct}, + types::CompositeLock, }; pub trait Subtrait: system::Trait + timestamp::Trait { @@ -77,6 +77,15 @@ pub trait Subtrait: system::Trait + timestamp::Tr /// The fee required to create an account. type CreationFee: Get; + + type Locks: LocksTrait< + Balance = Self::Balance, + Lock = CompositeLock, + Moment = Self::Moment, + WithdrawReasons = WithdrawReasons, + > + Default + + EncodeLike + + Decode; } pub trait Trait: system::Trait + timestamp::Trait { @@ -118,6 +127,15 @@ pub trait Trait: system::Trait + timestamp::Trait /// The fee required to create an account. type CreationFee: Get; + + type Locks: LocksTrait< + Balance = Self::Balance, + Lock = CompositeLock, + Moment = Self::Moment, + WithdrawReasons = WithdrawReasons, + > + Default + + EncodeLike + + Decode; } impl, I: Instance> Subtrait for T { @@ -127,6 +145,7 @@ impl, I: Instance> Subtrait for T { type ExistentialDeposit = T::ExistentialDeposit; type TransferFee = T::TransferFee; type CreationFee = T::CreationFee; + type Locks = T::Locks; } decl_event!( @@ -235,7 +254,7 @@ decl_storage! { pub ReservedBalance get(fn reserved_balance): map T::AccountId => T::Balance; /// Any liquidity locks on some account balances. - pub Locks get(locks): map T::AccountId => LocksStruct; + pub Locks get(locks): map T::AccountId => T::Locks; } add_extra_genesis { config(balances): Vec<(T::AccountId, T::Balance)>; @@ -668,6 +687,7 @@ impl, I: Instance> Trait for ElevatedTrait { type ExistentialDeposit = T::ExistentialDeposit; type TransferFee = T::TransferFee; type CreationFee = T::CreationFee; + type Locks = T::Locks; } impl, I: Instance> Currency for Module diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 17f86c3d8..e5f2d5d08 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -1,6 +1,6 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Codec, Decode, Encode}; +use codec::{Codec, Decode, Encode, EncodeLike}; #[cfg(not(feature = "std"))] use rstd::borrow::ToOwned; use rstd::{cmp, fmt::Debug, prelude::*, result}; @@ -26,7 +26,7 @@ use system::ensure_signed; use darwinia_support::{ traits::{LockableCurrency, Locks as LocksTrait}, - types::{CompositeLock, Locks as LocksStruct}, + types::CompositeLock, }; use imbalance::{NegativeImbalance, PositiveImbalance}; @@ -73,9 +73,17 @@ pub trait Trait: timestamp::Trait { type Event: From> + Into<::Event>; - // kton type OnMinted: OnUnbalanced>; type OnRemoval: OnUnbalanced>; + + type Locks: LocksTrait< + Balance = Self::Balance, + Lock = CompositeLock, + Moment = Self::Moment, + WithdrawReasons = WithdrawReasons, + > + Default + + EncodeLike + + Decode; } decl_event!( @@ -105,7 +113,7 @@ decl_storage! { pub ReservedBalance get(reserved_balance): map T::AccountId => T::Balance; - pub Locks get(locks): map T::AccountId => LocksStruct; + pub Locks get(locks): map T::AccountId => T::Locks; pub TotalLock get(total_lock): T::Balance; diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index 341b7fac9..6679330ee 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -14,7 +14,7 @@ use srml_support::{ use substrate_primitives::{crypto::key_types, H256}; use crate::{EraIndex, GenesisConfig, Module, Nominators, RewardDestination, StakerStatus, StakingBalance, Trait}; -use darwinia_support::types::TimeStamp; +use darwinia_support::types::{CompositeLock, Locks, TimeStamp}; use phragmen::ExtendedBalance; /// The AccountId alias in this test module. @@ -124,7 +124,7 @@ parameter_types! { pub const TransactionByteFee: u64 = 0; } impl balances::Trait for Test { - type Balance = u64; + type Balance = Balance; type OnFreeBalanceZero = Staking; type OnNewAccount = (); type TransferPayment = (); @@ -133,6 +133,7 @@ impl balances::Trait for Test { type ExistentialDeposit = ExistentialDeposit; type TransferFee = TransferFee; type CreationFee = CreationFee; + type Locks = Locks; } parameter_types! { pub const Period: BlockNumber = 1; @@ -170,6 +171,7 @@ impl kton::Trait for Test { type Event = (); type OnMinted = (); type OnRemoval = (); + type Locks = Locks; } parameter_types! { From e13138b12dcb1dd1cb9064fbb8f66ffa4c5c39b3 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 12:28:17 +0800 Subject: [PATCH 27/30] rename: `ensure_can_withdraw` to `can_withdraw` --- srml/balances/src/lib.rs | 2 +- srml/kton/src/lib.rs | 2 +- srml/support/src/traits.rs | 3 +-- srml/support/src/types.rs | 7 +------ 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 810ffeab8..9bd5e80cd 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -753,7 +753,7 @@ where { Err("vesting balance too high to send value") } else { - if Self::locks(who).ensure_can_withdraw(>::now(), reasons, new_balance) { + if Self::locks(who).can_withdraw(>::now(), reasons, new_balance) { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index e5f2d5d08..964e8c3eb 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -239,7 +239,7 @@ impl Currency for Module { { Err("vesting balance too high to send value") } else { - if Self::locks(who).ensure_can_withdraw(>::now(), reasons, new_balance) { + if Self::locks(who).can_withdraw(>::now(), reasons, new_balance) { Ok(()) } else { Err("account liquidity restrictions prevent withdrawal") diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 52f8a3b1c..2e34618f3 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -74,6 +74,5 @@ pub trait Locks { /// Remove specify locks and expired locks fn remove_locks(&mut self, lock: &Self::Lock, at: Self::Moment) -> Self::Balance; - fn ensure_can_withdraw(&self, at: Self::Moment, reasons: Self::WithdrawReasons, new_balance: Self::Balance) - -> bool; + fn can_withdraw(&self, at: Self::Moment, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool; } diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 57a80bad3..6a59feb8b 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -64,12 +64,7 @@ where expired_locks_amount } - fn ensure_can_withdraw( - &self, - at: Self::Moment, - reasons: Self::WithdrawReasons, - new_balance: Self::Balance, - ) -> bool { + fn can_withdraw(&self, at: Self::Moment, reasons: Self::WithdrawReasons, new_balance: Self::Balance) -> bool { if self.0.is_empty() { return true; } From be172174eaa60b21af243daf8d9551c96ccb9bd0 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 13:44:37 +0800 Subject: [PATCH 28/30] remove: phragmen --- srml/staking/src/phragmen.rs | 505 ----------------------------------- 1 file changed, 505 deletions(-) delete mode 100644 srml/staking/src/phragmen.rs diff --git a/srml/staking/src/phragmen.rs b/srml/staking/src/phragmen.rs deleted file mode 100644 index 189aeeff3..000000000 --- a/srml/staking/src/phragmen.rs +++ /dev/null @@ -1,505 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// This file is part of Substrate. - -// Substrate is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Substrate is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Substrate. If not, see . - -//! Rust implementation of the Phragmén election algorithm. This is used in several SRML modules to -//! optimally distribute the weight of a set of voters among an elected set of candidates. In the -//! context of staking this is mapped to validators and nominators. -//! -//! The algorithm has two phases: -//! - Sequential phragmen: performed in [`elect`] function which is first pass of the distribution -//! The results are not optimal but the execution time is less. -//! - Equalize post-processing: tries to further distribute the weight fairly among candidates. -//! Incurs more execution time. -//! -//! The main objective of the assignments done by phragmen is to maximize the minimum backed -//! candidate in the elected set. -//! -//! Reference implementation: https://github.com/w3f/consensus -//! Further details: -//! https://research.web3.foundation/en/latest/polkadot/NPoS/4.%20Sequential%20Phragm%C3%A9n%E2%80%99s%20method/ - -use rstd::{collections::btree_map::BTreeMap, prelude::*}; -use sr_primitives::traits::{Bounded, Member, Saturating, Zero}; -use sr_primitives::{helpers_128bit::multiply_by_rational, Perbill, Rational128}; - -/// A type in which performing operations on balances and stakes of candidates and voters are safe. -/// -/// This module's functions expect a `Convert` type to convert all balances to u64. Hence, u128 is -/// a safe type for arithmetic operations over them. -/// -/// Balance types converted to `ExtendedBalance` are referred to as `Votes`. -pub type ExtendedBalance = u128; - -/// The denominator used for loads. Since votes are collected as u64, the smallest ratio that we -/// might collect is `1/approval_stake` where approval stake is the sum of votes. Hence, some number -/// bigger than u64::max_value() is needed. For maximum accuracy we simply use u128; -const DEN: u128 = u128::max_value(); - -/// A candidate entity for phragmen election. -#[derive(Clone, Default)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct Candidate { - /// Identifier. - pub who: AccountId, - /// Intermediary value used to sort candidates. - pub score: Rational128, - /// Sum of the stake of this candidate based on received votes. - approval_stake: ExtendedBalance, - /// Flag for being elected. - elected: bool, -} - -/// A voter entity. -#[derive(Clone, Default)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct Voter { - /// Identifier. - who: AccountId, - /// List of candidates proposed by this voter. - edges: Vec>, - /// The stake of this voter. - budget: ExtendedBalance, - /// Incremented each time a candidate that this voter voted for has been elected. - load: Rational128, -} - -/// A candidate being backed by a voter. -#[derive(Clone, Default)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct Edge { - /// Identifier. - who: AccountId, - /// Load of this vote. - load: Rational128, - /// Index of the candidate stored in the 'candidates' vector. - candidate_index: usize, -} - -/// Means a particular `AccountId` was backed by `Perbill`th of a nominator's stake. -pub type PhragmenAssignment = (AccountId, Perbill); - -/// Means a particular `AccountId` was backed by `ExtendedBalance` of a nominator's stake. -pub type PhragmenStakedAssignment = (AccountId, ExtendedBalance); - -/// Final result of the phragmen election. -#[cfg_attr(feature = "std", derive(Debug))] -pub struct PhragmenResult { - /// Just winners zipped with their approval stake. Note that the approval stake is merely the - /// sub of their received stake and could be used for very basic sorting and approval voting. - pub winners: Vec<(AccountId, ExtendedBalance)>, - /// Individual assignments. for each tuple, the first elements is a voter and the second - /// is the list of candidates that it supports. - pub assignments: Vec<(AccountId, Vec>)>, -} - -/// A structure to demonstrate the phragmen result from the perspective of the candidate, i.e. how -/// much support each candidate is receiving. -/// -/// This complements the [`PhragmenResult`] and is needed to run the equalize post-processing. -/// -/// This, at the current version, resembles the `Exposure` defined in the staking SRML module, yet -/// they do not necessarily have to be the same. -#[derive(Default)] -#[cfg_attr(feature = "std", derive(Debug))] -pub struct Support { - /// The amount of support as the effect of self-vote. - pub own: ExtendedBalance, - /// Total support. - pub total: ExtendedBalance, - /// Support from voters. - pub others: Vec>, -} - -/// A linkage from a candidate and its [`Support`]. -pub type SupportMap = BTreeMap>; - -/// Perform election based on Phragmén algorithm. -/// -/// Returns an `Option` the set of winners and their detailed support ratio from each voter if -/// enough candidates are provided. Returns `None` otherwise. -/// -/// * `candidate_count`: number of candidates to elect. -/// * `minimum_candidate_count`: minimum number of candidates to elect. If less candidates exist, -/// `None` is returned. -/// * `initial_candidates`: candidates list to be elected from. -/// * `initial_voters`: voters list. -/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. -/// * `self_vote`. If true, then each candidate will automatically vote for themselves with the a -/// weight indicated by their stake. Note that when this is `true` candidates are filtered by -/// having at least some backed stake from themselves. -pub fn elect( - candidate_count: usize, - minimum_candidate_count: usize, - initial_candidates: Vec, - initial_voters: Vec<(AccountId, Vec)>, - stake_of: FS, - self_vote: bool, -) -> Option> -where - AccountId: Default + Ord + Member, - for<'r> FS: Fn(&'r AccountId) -> ExtendedBalance, -{ - // return structures - let mut elected_candidates: Vec<(AccountId, ExtendedBalance)>; - let mut assigned: Vec<(AccountId, Vec>)>; - - // used to cache and access candidates index. - let mut c_idx_cache = BTreeMap::::new(); - - // voters list. - let num_voters = initial_candidates.len() + initial_voters.len(); - let mut voters: Vec> = Vec::with_capacity(num_voters); - - // collect candidates. self vote or filter might apply - let mut candidates = if self_vote { - // self vote. filter. - initial_candidates - .into_iter() - .map(|who| { - let stake = stake_of(&who); - Candidate { - who, - approval_stake: stake, - ..Default::default() - } - }) - .filter(|c| !c.approval_stake.is_zero()) - .enumerate() - .map(|(i, c)| { - voters.push(Voter { - who: c.who.clone(), - edges: vec![Edge { - who: c.who.clone(), - candidate_index: i, - ..Default::default() - }], - budget: c.approval_stake, - load: Rational128::zero(), - }); - c_idx_cache.insert(c.who.clone(), i); - c - }) - .collect::>>() - } else { - // no self vote. just collect. - initial_candidates - .into_iter() - .enumerate() - .map(|(idx, who)| { - c_idx_cache.insert(who.clone(), idx); - Candidate { - who, - ..Default::default() - } - }) - .collect::>>() - }; - - // early return if we don't have enough candidates - if candidates.len() < minimum_candidate_count { - return None; - } - - // collect voters. use `c_idx_cache` for fast access and aggregate `approval_stake` of - // candidates. - voters.extend(initial_voters.into_iter().map(|(who, votes)| { - let voter_stake = stake_of(&who); - let mut edges: Vec> = Vec::with_capacity(votes.len()); - for v in votes { - if let Some(idx) = c_idx_cache.get(&v) { - // This candidate is valid + already cached. - candidates[*idx].approval_stake = candidates[*idx].approval_stake.saturating_add(voter_stake); - edges.push(Edge { - who: v.clone(), - candidate_index: *idx, - ..Default::default() - }); - } // else {} would be wrong votes. We don't really care about it. - } - Voter { - who, - edges: edges, - budget: voter_stake, - load: Rational128::zero(), - } - })); - - // we have already checked that we have more candidates than minimum_candidate_count. - // run phragmen. - let to_elect = candidate_count.min(candidates.len()); - elected_candidates = Vec::with_capacity(candidate_count); - assigned = Vec::with_capacity(candidate_count); - - // main election loop - for _round in 0..to_elect { - // loop 1: initialize score - for c in &mut candidates { - if !c.elected { - // 1 / approval_stake == (DEN / approval_stake) / DEN. If approval_stake is zero, - // then the ratio should be as large as possible, essentially `infinity`. - if c.approval_stake.is_zero() { - c.score = Rational128::from_unchecked(DEN, 0); - } else { - c.score = Rational128::from(DEN / c.approval_stake, DEN); - } - } - } - - // loop 2: increment score - for n in &voters { - for e in &n.edges { - let c = &mut candidates[e.candidate_index]; - if !c.elected && !c.approval_stake.is_zero() { - let temp_n = - multiply_by_rational(n.load.n(), n.budget, c.approval_stake).unwrap_or(Bounded::max_value()); - let temp_d = n.load.d(); - let temp = Rational128::from(temp_n, temp_d); - c.score = c.score.lazy_saturating_add(temp); - } - } - } - - // loop 3: find the best - if let Some(winner) = candidates.iter_mut().filter(|c| !c.elected).min_by_key(|c| c.score) { - // loop 3: update voter and edge load - winner.elected = true; - for n in &mut voters { - for e in &mut n.edges { - if e.who == winner.who { - e.load = winner.score.lazy_saturating_sub(n.load); - n.load = winner.score; - } - } - } - - elected_candidates.push((winner.who.clone(), winner.approval_stake)); - } else { - break; - } - } // end of all rounds - - // update backing stake of candidates and voters - for n in &mut voters { - let mut assignment = (n.who.clone(), vec![]); - for e in &mut n.edges { - if let Some(c) = elected_candidates.iter().cloned().find(|(c, _)| *c == e.who) { - if c.0 != n.who { - let per_bill_parts = { - if n.load == e.load { - // Full support. No need to calculate. - Perbill::accuracy().into() - } else { - if e.load.d() == n.load.d() { - // return e.load / n.load. - let desired_scale: u128 = Perbill::accuracy().into(); - multiply_by_rational(desired_scale, e.load.n(), n.load.n()) - .unwrap_or(Bounded::max_value()) - } else { - // defensive only. Both edge and nominator loads are built from - // scores, hence MUST have the same denominator. - Zero::zero() - } - } - }; - // safer to .min() inside as well to argue as u32 is safe. - let per_thing = Perbill::from_parts(per_bill_parts.min(Perbill::accuracy().into()) as u32); - assignment.1.push((e.who.clone(), per_thing)); - } - } - } - - if assignment.1.len() > 0 { - // To ensure an assertion indicating: no stake from the nominator going to waste, - // we add a minimal post-processing to equally assign all of the leftover stake ratios. - let vote_count = assignment.1.len() as u32; - let len = assignment.1.len(); - let sum = assignment.1.iter().map(|a| a.1.deconstruct()).sum::(); - let accuracy = Perbill::accuracy(); - let diff = accuracy.checked_sub(sum).unwrap_or(0); - let diff_per_vote = (diff / vote_count).min(accuracy); - - if diff_per_vote > 0 { - for i in 0..len { - let current_ratio = assignment.1[i % len].1; - let next_ratio = current_ratio.saturating_add(Perbill::from_parts(diff_per_vote)); - assignment.1[i % len].1 = next_ratio; - } - } - - // `remainder` is set to be less than maximum votes of a nominator (currently 16). - // safe to cast it to usize. - let remainder = diff - diff_per_vote * vote_count; - for i in 0..remainder as usize { - let current_ratio = assignment.1[i % len].1; - let next_ratio = current_ratio.saturating_add(Perbill::from_parts(1)); - assignment.1[i % len].1 = next_ratio; - } - assigned.push(assignment); - } - } - - Some(PhragmenResult { - winners: elected_candidates, - assignments: assigned, - }) -} - -/// Performs equalize post-processing to the output of the election algorithm. This happens in -/// rounds. The number of rounds and the maximum diff-per-round tolerance can be tuned through input -/// parameters. -/// -/// No value is returned from the function and the `supports` parameter is updated. -/// -/// * `assignments`: exactly the same is the output of phragmen. -/// * `supports`: mutable reference to s `SupportMap`. This parameter is updated. -/// * `tolerance`: maximum difference that can occur before an early quite happens. -/// * `iterations`: maximum number of iterations that will be processed. -/// * `stake_of`: something that can return the stake stake of a particular candidate or voter. -pub fn equalize( - mut assignments: Vec<(AccountId, Vec>)>, - supports: &mut SupportMap, - tolerance: ExtendedBalance, - iterations: usize, - stake_of: FS, -) where - for<'r> FS: Fn(&'r AccountId) -> ExtendedBalance, - AccountId: Ord + Clone, -{ - // prepare the data for equalise - for _i in 0..iterations { - let mut max_diff = 0; - - for (voter, assignment) in assignments.iter_mut() { - let voter_budget = stake_of(&voter); - - let diff = do_equalize::<_>(voter, voter_budget, assignment, supports, tolerance); - if diff > max_diff { - max_diff = diff; - } - } - - if max_diff < tolerance { - break; - } - } -} - -/// actually perform equalize. same interface is `equalize`. Just called in loops with a check for -/// maximum difference. -fn do_equalize( - voter: &AccountId, - budget_balance: ExtendedBalance, - elected_edges: &mut Vec>, - support_map: &mut SupportMap, - tolerance: ExtendedBalance, -) -> ExtendedBalance -where - AccountId: Ord + Clone, -{ - let budget = budget_balance; - - // Nothing to do. This voter had nothing useful. - // Defensive only. Assignment list should always be populated. - if elected_edges.is_empty() { - return 0; - } - - let stake_used = elected_edges - .iter() - .fold(0 as ExtendedBalance, |s, e| s.saturating_add(e.1)); - - let backed_stakes_iter = elected_edges - .iter() - .filter_map(|e| support_map.get(&e.0)) - .map(|e| e.total); - - let backing_backed_stake = elected_edges - .iter() - .filter(|e| e.1 > 0) - .filter_map(|e| support_map.get(&e.0)) - .map(|e| e.total) - .collect::>(); - - let mut difference: u128; - if backing_backed_stake.len() > 0 { - let max_stake = backing_backed_stake - .iter() - .max() - .expect("vector with positive length will have a max; qed"); - let min_stake = backed_stakes_iter - .min() - .expect("iterator with positive length will have a min; qed"); - - difference = max_stake.saturating_sub(min_stake); - difference = difference.saturating_add(budget.saturating_sub(stake_used)); - if difference < tolerance { - return difference; - } - } else { - difference = budget; - } - - // Undo updates to support - elected_edges.iter_mut().for_each(|e| { - if let Some(support) = support_map.get_mut(&e.0) { - support.total = support.total.saturating_sub(e.1); - support.others.retain(|i_support| i_support.0 != *voter); - } - e.1 = 0; - }); - - elected_edges.sort_unstable_by_key(|e| { - if let Some(e) = support_map.get(&e.0) { - e.total - } else { - Zero::zero() - } - }); - - let mut cumulative_stake: ExtendedBalance = 0; - let mut last_index = elected_edges.len() - 1; - let mut idx = 0usize; - for e in &mut elected_edges[..] { - if let Some(support) = support_map.get_mut(&e.0) { - let stake = support.total; - let stake_mul = stake.saturating_mul(idx as ExtendedBalance); - let stake_sub = stake_mul.saturating_sub(cumulative_stake); - if stake_sub > budget { - last_index = idx.checked_sub(1).unwrap_or(0); - break; - } - cumulative_stake = cumulative_stake.saturating_add(stake); - } - idx += 1; - } - - let last_stake = elected_edges[last_index].1; - let split_ways = last_index + 1; - let excess = budget - .saturating_add(cumulative_stake) - .saturating_sub(last_stake.saturating_mul(split_ways as ExtendedBalance)); - elected_edges.iter_mut().take(split_ways).for_each(|e| { - if let Some(support) = support_map.get_mut(&e.0) { - e.1 = (excess / split_ways as ExtendedBalance) - .saturating_add(last_stake) - .saturating_sub(support.total); - support.total = support.total.saturating_add(e.1); - support.others.push((voter.clone(), e.1)); - } - }); - - difference -} From 695d7407e6f87d863d408f3f0789a2353b922227 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 14:05:09 +0800 Subject: [PATCH 29/30] add: BONDING_DURATION_ERA_TO_SECS_RATIO --- srml/staking/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 7cd2a85c1..988446516 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -63,7 +63,6 @@ const MAX_NOMINATIONS: usize = 16; const MAX_UNSTAKE_THRESHOLD: u32 = 10; const MAX_UNLOCKING_CHUNKS: u32 = 32; const MONTH_IN_SECONDS: u32 = 2_592_000; -const ERA_IN_SECONDS: TimeStamp = 300; /// Counter for the number of eras that have passed. pub type EraIndex = u32; @@ -1142,7 +1141,10 @@ impl Module { CurrentEraStartSessionIndex::mutate(|v| { *v = start_session_index; }); - let bonding_duration = (T::BondingDuration::get() / ERA_IN_SECONDS) as _; + let bonding_duration = { + const BONDING_DURATION_ERA_TO_SECS_RATIO: TimeStamp = 300; + (T::BondingDuration::get() / BONDING_DURATION_ERA_TO_SECS_RATIO) as _ + }; if current_era > bonding_duration { let first_kept = current_era - bonding_duration; From 3b9c1a1b47daa962b6ee0d1a7ddcb285f848c970 Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Wed, 20 Nov 2019 14:23:21 +0800 Subject: [PATCH 30/30] rename: `update_lock` to `update_locks` --- srml/balances/src/lib.rs | 4 +-- srml/kton/src/lib.rs | 4 +-- srml/staking/src/lib.rs | 12 +++---- srml/support/src/traits.rs | 4 +-- srml/support/src/types.rs | 68 +------------------------------------- 5 files changed, 13 insertions(+), 79 deletions(-) diff --git a/srml/balances/src/lib.rs b/srml/balances/src/lib.rs index 9bd5e80cd..855cf5f60 100644 --- a/srml/balances/src/lib.rs +++ b/srml/balances/src/lib.rs @@ -1004,11 +1004,11 @@ where >::get(who).locks_count() } - fn update_lock(who: &T::AccountId, lock: Option) -> Self::Balance { + fn update_locks(who: &T::AccountId, lock: Option) -> Self::Balance { let at = >::now(); let mut locks = Self::locks(who); let expired_locks_amount = if let Some(lock) = lock { - locks.update_lock(lock, at) + locks.update_locks(lock, at) } else { locks.remove_expired_locks(at) }; diff --git a/srml/kton/src/lib.rs b/srml/kton/src/lib.rs index 964e8c3eb..5f94cf744 100644 --- a/srml/kton/src/lib.rs +++ b/srml/kton/src/lib.rs @@ -391,11 +391,11 @@ where >::get(who).locks_count() } - fn update_lock(who: &T::AccountId, lock: Option) -> Self::Balance { + fn update_locks(who: &T::AccountId, lock: Option) -> Self::Balance { let at = >::now(); let mut locks = Self::locks(who); let expired_locks_amount = if let Some(lock) = lock { - locks.update_lock(lock, at) + locks.update_locks(lock, at) } else { locks.remove_expired_locks(at) }; diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index 988446516..6bcfe86de 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -519,7 +519,7 @@ decl_module! { match value { StakingBalance::Ring(r) => { let stash_balance = T::Ring::free_balance(&stash); - let expired_locks_ring = T::Ring::update_lock(&stash, None); + let expired_locks_ring = T::Ring::update_locks(&stash, None); // TODO: check underflow? ledger.total_ring -= expired_locks_ring; if let Some(extra) = stash_balance.checked_sub(&ledger.total_ring) { @@ -530,7 +530,7 @@ decl_module! { }, StakingBalance::Kton(k) => { let stash_balance = T::Kton::free_balance(&stash); - let expired_locks_kton = T::Kton::update_lock(&stash, None); + let expired_locks_kton = T::Kton::update_locks(&stash, None); ledger.total_kton -= expired_locks_kton; if let Some(extra) = stash_balance.checked_sub(&ledger.total_kton) { let extra = extra.min(k); @@ -578,7 +578,7 @@ decl_module! { if !available_unbond_ring.is_zero() { *active_ring -= available_unbond_ring; - let expired_locks_ring = T::Ring::update_lock( + let expired_locks_ring = T::Ring::update_locks( stash, Some(CompositeLock::Unbonding(Lock { amount: available_unbond_ring, @@ -599,7 +599,7 @@ decl_module! { >::mutate(|k| *k -= unbond_kton); *active_kton -= unbond_kton; - let expired_locks_kton = T::Kton::update_lock( + let expired_locks_kton = T::Kton::update_locks( stash, Some(CompositeLock::Unbonding(Lock { amount: unbond_kton, @@ -879,13 +879,13 @@ impl Module { match staking_balance { StakingBalance::Ring(_r) => { let expired_locks_ring = - T::Ring::update_lock(&ledger.stash, Some(CompositeLock::Staking(ledger.active_ring))); + T::Ring::update_locks(&ledger.stash, Some(CompositeLock::Staking(ledger.active_ring))); // TODO: check underflow? ledger.total_ring -= expired_locks_ring; } StakingBalance::Kton(_k) => { let expired_locks_kton = - T::Kton::update_lock(&ledger.stash, Some(CompositeLock::Staking(ledger.active_kton))); + T::Kton::update_locks(&ledger.stash, Some(CompositeLock::Staking(ledger.active_kton))); // TODO: check underflow? ledger.total_kton -= expired_locks_kton; } diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 2e34618f3..ed928f37a 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -39,7 +39,7 @@ pub trait LockableCurrency: Currency { /// - Remove the expired locks on account `who`. /// - Update the global staking amount. /// - The function will return the sum of expired locks' amount. - fn update_lock(who: &AccountId, lock: Option) -> Self::Balance; + fn update_locks(who: &AccountId, lock: Option) -> Self::Balance; // TODO: reserve // fn extend_lock(); @@ -66,7 +66,7 @@ pub trait Locks { /// - Remove the expired locks on account `who`. /// - Update the global staking amount. /// - The function will return the sum of expired locks' amount. - fn update_lock(&mut self, lock: Self::Lock, at: Self::Moment) -> Self::Balance; + fn update_locks(&mut self, lock: Self::Lock, at: Self::Moment) -> Self::Balance; /// Remove expired locks fn remove_expired_locks(&mut self, at: Self::Moment) -> Self::Balance; diff --git a/srml/support/src/types.rs b/srml/support/src/types.rs index 6a59feb8b..1275694eb 100644 --- a/srml/support/src/types.rs +++ b/srml/support/src/types.rs @@ -25,7 +25,7 @@ where self.0.len() as _ } - fn update_lock(&mut self, lock: Self::Lock, at: Self::Moment) -> Self::Balance { + fn update_locks(&mut self, lock: Self::Lock, at: Self::Moment) -> Self::Balance { let expired_locks_amount = self.remove_expired_locks(at); if let Some(i) = self.0.iter().position(|lock_| lock_ == &lock) { self.0[i] = lock; @@ -81,72 +81,6 @@ where } } -//impl BalanceLocks -//where -// Balance: Clone + Copy + Default + SimpleArithmetic, -// Moment: Clone + Copy + PartialOrd, -//{ -// #[inline] -// pub fn len(&self) -> u32 { -// self.0.len() as _ -// } -// -// pub fn update_lock(&mut self, lock: Lock, at: Moment) -> Balance { -// let expired_locks_amount = self.remove_expired_locks(at); -// if let Some(i) = self.0.iter().position(|lock_| lock_ == &lock) { -// self.0[i] = lock; -// } else { -// self.0.push(lock); -// } -// -// expired_locks_amount -// } -// -// pub fn remove_expired_locks(&mut self, at: Moment) -> Balance { -// let mut expired_locks_amount = Balance::default(); -// self.0.retain(|lock| { -// if lock.valid_at(at) { -// true -// } else { -// expired_locks_amount += lock.amount(); -// false -// } -// }); -// -// expired_locks_amount -// } -// -// pub fn remove_locks(&mut self, lock: &Lock, at: Moment) -> Balance { -// let mut expired_locks_amount = Balance::zero(); -// self.0.retain(|lock_| { -// if lock_.valid_at(at) && lock_ != lock { -// true -// } else { -// expired_locks_amount += lock_.amount(); -// false -// } -// }); -// -// expired_locks_amount -// } -// -// pub fn ensure_can_withdraw(&self, at: Moment, reasons: WithdrawReasons, new_balance: Balance) -> bool { -// if self.0.is_empty() { -// return true; -// } -// -// let mut locked_amount = Balance::default(); -// for lock in self.0.iter() { -// if lock.valid_at(at) && lock.check_reasons_intersects(reasons) { -// // TODO: check overflow? -// locked_amount += lock.amount(); -// } -// } -// -// new_balance >= locked_amount -// } -//} - #[derive(Clone, Encode, Decode, RuntimeDebug)] pub enum CompositeLock { Staking(Balance),