mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 03:18:01 +00:00
Lazy reaping (#4895)
* Squash and rebase from gav-lazy-reaping * Bump version * Bump runtime again * Docs. * Remove old functions * Update frame/balances/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/contracts/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Warnings * Bump runtime version * Update frame/democracy/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/system/src/lib.rs * Clean up OnReapAccount * Use frame_support debug * Bump spec * Renames and fix * Fix * Fix rename * Fix * Increase time for test Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> Co-authored-by: Benjamin Kampmann <ben.kampmann@googlemail.com>
This commit is contained in:
@@ -41,7 +41,7 @@ impl system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Event = ();
|
||||
|
||||
@@ -164,7 +164,7 @@ impl system::Trait for Runtime {
|
||||
/// What to do if a new account is created.
|
||||
type OnNewAccount = ();
|
||||
/// What to do if an account is fully reaped from the system.
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
/// The data to be stored in an account.
|
||||
type AccountData = balances::AccountData<Balance>;
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ pub fn testnet_genesis(
|
||||
}),
|
||||
pallet_session: Some(SessionConfig {
|
||||
keys: initial_authorities.iter().map(|x| {
|
||||
(x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()))
|
||||
(x.0.clone(), x.0.clone(), session_keys(x.2.clone(), x.3.clone(), x.4.clone(), x.5.clone()))
|
||||
}).collect::<Vec<_>>(),
|
||||
}),
|
||||
pallet_staking: Some(StakingConfig {
|
||||
|
||||
@@ -60,5 +60,5 @@ pub fn run_command_for_a_while(base_path: &Path, dev: bool) {
|
||||
|
||||
// Stop the process
|
||||
kill(Pid::from_raw(cmd.id().try_into().unwrap()), SIGINT).unwrap();
|
||||
assert!(wait_for(&mut cmd, 20).map(|x| x.success()).unwrap_or_default());
|
||||
assert!(wait_for(&mut cmd, 40).map(|x| x.success()).unwrap_or_default());
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ fn panic_execution_with_foreign_code_gives_error() {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(BLOATY_CODE, Storage {
|
||||
top: map![
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()) => {
|
||||
(69u128, 0u128, 0u128, 0u128).encode()
|
||||
(69u128, 0u8, 0u128, 0u128, 0u128).encode()
|
||||
},
|
||||
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => {
|
||||
69_u128.encode()
|
||||
@@ -200,7 +200,7 @@ fn bad_extrinsic_with_native_equivalent_code_gives_error() {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(COMPACT_CODE, Storage {
|
||||
top: map![
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()) => {
|
||||
(0u32, 69u128, 0u128, 0u128, 0u128).encode()
|
||||
(0u32, 0u8, 69u128, 0u128, 0u128, 0u128).encode()
|
||||
},
|
||||
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => {
|
||||
69_u128.encode()
|
||||
@@ -236,7 +236,7 @@ fn successful_execution_with_native_equivalent_code_gives_ok() {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(COMPACT_CODE, Storage {
|
||||
top: map![
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()) => {
|
||||
(0u32, 111 * DOLLARS, 0u128, 0u128, 0u128).encode()
|
||||
(0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode()
|
||||
},
|
||||
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => {
|
||||
(111 * DOLLARS).encode()
|
||||
@@ -278,7 +278,7 @@ fn successful_execution_with_foreign_code_gives_ok() {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(BLOATY_CODE, Storage {
|
||||
top: map![
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()) => {
|
||||
(0u32, 111 * DOLLARS, 0u128, 0u128, 0u128).encode()
|
||||
(0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode()
|
||||
},
|
||||
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => {
|
||||
(111 * DOLLARS).encode()
|
||||
@@ -734,7 +734,7 @@ fn successful_execution_gives_ok() {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(COMPACT_CODE, Storage {
|
||||
top: map![
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()) => {
|
||||
(0u32, 111 * DOLLARS, 0u128, 0u128, 0u128).encode()
|
||||
(0u32, 0u8, 111 * DOLLARS, 0u128, 0u128, 0u128).encode()
|
||||
},
|
||||
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => {
|
||||
(111 * DOLLARS).encode()
|
||||
|
||||
@@ -139,10 +139,10 @@ fn transaction_fee_is_correct_ultimate() {
|
||||
let mut t = TestExternalities::<Blake2Hasher>::new_with_code(COMPACT_CODE, Storage {
|
||||
top: map![
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(alice()) => {
|
||||
(0u32, 100 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode()
|
||||
(0u32, 0u8, 100 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode()
|
||||
},
|
||||
<frame_system::Account<Runtime>>::hashed_key_for(bob()) => {
|
||||
(0u32, 10 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode()
|
||||
(0u32, 0u8, 10 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS).encode()
|
||||
},
|
||||
<pallet_balances::TotalIssuance<Runtime>>::hashed_key().to_vec() => {
|
||||
(110 * DOLLARS).encode()
|
||||
|
||||
@@ -167,8 +167,9 @@ fn submitted_transaction_should_be_valid() {
|
||||
// add balance to the account
|
||||
let author = extrinsic.signature.clone().unwrap().0;
|
||||
let address = Indices::lookup(author).unwrap();
|
||||
let account = pallet_balances::AccountData { free: 5_000_000_000_000, ..Default::default() };
|
||||
<frame_system::Account<Runtime>>::insert(&address, (0u32, account));
|
||||
let data = pallet_balances::AccountData { free: 5_000_000_000_000, ..Default::default() };
|
||||
let account = frame_system::AccountInfo { nonce: 0u32, refcount: 0u8, data };
|
||||
<frame_system::Account<Runtime>>::insert(&address, account);
|
||||
|
||||
// check validity
|
||||
let res = Executive::validate_transaction(extrinsic);
|
||||
|
||||
@@ -81,8 +81,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
||||
// and set impl_version to 0. If only runtime
|
||||
// implementation changes and behavior does not, then leave spec_version as
|
||||
// is and increment impl_version.
|
||||
spec_version: 224,
|
||||
impl_version: 2,
|
||||
spec_version: 225,
|
||||
impl_version: 0,
|
||||
apis: RUNTIME_API_VERSIONS,
|
||||
};
|
||||
|
||||
@@ -131,7 +131,7 @@ impl frame_system::Trait for Runtime {
|
||||
type ModuleToIndex = ModuleToIndex;
|
||||
type AccountData = pallet_balances::AccountData<Balance>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = (Balances, Staking, Contracts, Session, Recovery);
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -69,15 +69,15 @@ pub fn config_endowed(
|
||||
}),
|
||||
pallet_session: Some(SessionConfig {
|
||||
keys: vec![
|
||||
(alice(), to_session_keys(
|
||||
(dave(), alice(), to_session_keys(
|
||||
&Ed25519Keyring::Alice,
|
||||
&Sr25519Keyring::Alice,
|
||||
)),
|
||||
(bob(), to_session_keys(
|
||||
(eve(), bob(), to_session_keys(
|
||||
&Ed25519Keyring::Bob,
|
||||
&Sr25519Keyring::Bob,
|
||||
)),
|
||||
(charlie(), to_session_keys(
|
||||
(ferdie(), charlie(), to_session_keys(
|
||||
&Ed25519Keyring::Charlie,
|
||||
&Sr25519Keyring::Charlie,
|
||||
)),
|
||||
|
||||
@@ -295,7 +295,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
impl Trait for Test {
|
||||
type Event = ();
|
||||
|
||||
@@ -63,7 +63,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Trait for Test {
|
||||
|
||||
@@ -159,7 +159,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl_outer_origin! {
|
||||
|
||||
@@ -433,7 +433,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -66,7 +66,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
|
||||
@@ -18,7 +18,6 @@ frame-support = { version = "2.0.0-dev", default-features = false, path = "../su
|
||||
frame-system = { version = "2.0.0-dev", default-features = false, path = "../system" }
|
||||
|
||||
[dev-dependencies]
|
||||
sp-io = { version = "2.0.0-dev", path = "../../primitives/io" }
|
||||
sp-core = { version = "2.0.0-dev", path = "../../primitives/core" }
|
||||
pallet-transaction-payment = { version = "2.0.0-dev", path = "../transaction-payment" }
|
||||
|
||||
|
||||
@@ -159,11 +159,12 @@ mod benchmarking;
|
||||
|
||||
use sp_std::prelude::*;
|
||||
use sp_std::{cmp, result, mem, fmt::Debug, ops::BitOr, convert::Infallible};
|
||||
use sp_io::hashing::twox_64;
|
||||
use codec::{Codec, Encode, Decode};
|
||||
use frame_support::{
|
||||
StorageValue, Parameter, decl_event, decl_storage, decl_module, decl_error, ensure,
|
||||
weights::SimpleDispatchInfo, traits::{
|
||||
Currency, OnReapAccount, OnUnbalanced, TryDrop, StoredMap,
|
||||
Currency, OnKilledAccount, OnUnbalanced, TryDrop, StoredMap,
|
||||
WithdrawReason, WithdrawReasons, LockIdentifier, LockableCurrency, ExistenceRequirement,
|
||||
Imbalance, SignedImbalance, ReservableCurrency, Get, ExistenceRequirement::KeepAlive,
|
||||
ExistenceRequirement::AllowDeath, IsDeadAccount, BalanceStatus as Status
|
||||
@@ -178,7 +179,7 @@ use sp_runtime::{
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed, ensure_root};
|
||||
use frame_support::storage::migration::{
|
||||
get_storage_value, take_storage_value, put_storage_value, StorageIterator
|
||||
get_storage_value, take_storage_value, put_storage_value, StorageIterator, have_storage_value
|
||||
};
|
||||
|
||||
pub use self::imbalances::{PositiveImbalance, NegativeImbalance};
|
||||
@@ -609,7 +610,16 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
|
||||
|
||||
for (hash, balances) in StorageIterator::<AccountData<T::Balance>>::new(b"Balances", b"Account").drain() {
|
||||
let nonce = take_storage_value::<T::Index>(b"System", b"AccountNonce", &hash).unwrap_or_default();
|
||||
put_storage_value(b"System", b"Account", &hash, (nonce, balances));
|
||||
let mut refs: system::RefCount = 0;
|
||||
// The items in Kusama that would result in a ref count being incremented.
|
||||
if have_storage_value(b"Democracy", b"Proxy", &hash) { refs += 1 }
|
||||
// We skip Recovered since it's being replaced anyway.
|
||||
let mut prefixed_hash = twox_64(&b":session:keys"[..]).to_vec();
|
||||
prefixed_hash.extend(&b":session:keys"[..]);
|
||||
prefixed_hash.extend(&hash[..]);
|
||||
if have_storage_value(b"Session", b"NextKeys", &prefixed_hash) { refs += 1 }
|
||||
if have_storage_value(b"Staking", b"Bonded", &hash) { refs += 1 }
|
||||
put_storage_value(b"System", b"Account", &hash, (nonce, refs, &balances));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -721,14 +731,21 @@ impl<T: Trait<I>, I: Instance> Module<T, I> {
|
||||
}
|
||||
}
|
||||
});
|
||||
Locks::<T, I>::insert(who, locks);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance> OnReapAccount<T::AccountId> for Module<T, I> {
|
||||
fn on_reap_account(who: &T::AccountId) {
|
||||
Locks::<T, I>::remove(who);
|
||||
Account::<T, I>::remove(who);
|
||||
let existed = Locks::<T, I>::contains_key(who);
|
||||
if locks.is_empty() {
|
||||
Locks::<T, I>::remove(who);
|
||||
if existed {
|
||||
// TODO: use Locks::<T, I>::hashed_key
|
||||
// https://github.com/paritytech/substrate/issues/4969
|
||||
system::Module::<T>::dec_ref(who);
|
||||
}
|
||||
} else {
|
||||
Locks::<T, I>::insert(who, locks);
|
||||
if !existed {
|
||||
system::Module::<T>::inc_ref(who);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -923,7 +940,7 @@ impl<T: Subtrait<I>, I: Instance> frame_system::Trait for ElevatedTrait<T, I> {
|
||||
type Version = T::Version;
|
||||
type ModuleToIndex = T::ModuleToIndex;
|
||||
type OnNewAccount = T::OnNewAccount;
|
||||
type OnReapAccount = T::OnReapAccount;
|
||||
type OnKilledAccount = T::OnKilledAccount;
|
||||
type AccountData = T::AccountData;
|
||||
}
|
||||
impl<T: Subtrait<I>, I: Instance> Trait<I> for ElevatedTrait<T, I> {
|
||||
@@ -1040,6 +1057,7 @@ impl<T: Trait<I>, I: Instance> Currency<T::AccountId> for Module<T, I> where
|
||||
)?;
|
||||
|
||||
let allow_death = existence_requirement == ExistenceRequirement::AllowDeath;
|
||||
let allow_death = allow_death && system::Module::<T>::allow_death(transactor);
|
||||
ensure!(allow_death || from_account.free >= ed, Error::<T, I>::KeepAlive);
|
||||
|
||||
Ok(())
|
||||
@@ -1283,6 +1301,17 @@ impl<T: Trait<I>, I: Instance> ReservableCurrency<T::AccountId> for Module<T, I>
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement `OnKilledAccount` to remove the local account, if using local account storage.
|
||||
///
|
||||
/// NOTE: You probably won't need to use this! This only needs to be "wired in" to System module
|
||||
/// if you're using the local balance storage. **If you're using the composite system account
|
||||
/// storage (which is the default in most examples and tests) then there's no need.**
|
||||
impl<T: Trait<I>, I: Instance> OnKilledAccount<T::AccountId> for Module<T, I> {
|
||||
fn on_killed_account(who: &T::AccountId) {
|
||||
Account::<T, I>::remove(who);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait<I>, I: Instance> LockableCurrency<T::AccountId> for Module<T, I>
|
||||
where
|
||||
T::Balance: MaybeSerializeDeserialize + Debug
|
||||
|
||||
@@ -24,8 +24,10 @@ macro_rules! decl_tests {
|
||||
use sp_runtime::{Fixed64, traits::{SignedExtension, BadOrigin}};
|
||||
use frame_support::{
|
||||
assert_noop, assert_ok, assert_err,
|
||||
traits::{LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons,
|
||||
Currency, ReservableCurrency, ExistenceRequirement::AllowDeath}
|
||||
traits::{
|
||||
LockableCurrency, LockIdentifier, WithdrawReason, WithdrawReasons,
|
||||
Currency, ReservableCurrency, ExistenceRequirement::AllowDeath, StoredMap
|
||||
}
|
||||
};
|
||||
use pallet_transaction_payment::ChargeTransactionPayment;
|
||||
use frame_system::RawOrigin;
|
||||
@@ -55,6 +57,15 @@ macro_rules! decl_tests {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn account_should_be_reaped() {
|
||||
<$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| {
|
||||
assert_eq!(Balances::free_balance(1), 10);
|
||||
assert_ok!(<Balances as Currency<_>>::transfer(&1, &2, 10, AllowDeath));
|
||||
assert!(!<<Test as Trait>::AccountStore as StoredMap<u64, AccountData<u64>>>::is_explicit(&1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn partial_locking_should_work() {
|
||||
<$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| {
|
||||
|
||||
@@ -67,7 +67,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = super::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Module<Test>;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const TransactionBaseFee: u64 = 0;
|
||||
|
||||
@@ -67,7 +67,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = super::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Module<Test>;
|
||||
type OnKilledAccount = Module<Test>;
|
||||
}
|
||||
parameter_types! {
|
||||
pub const TransactionBaseFee: u64 = 0;
|
||||
|
||||
@@ -435,7 +435,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
impl Trait<Instance1> for Test {
|
||||
type Origin = Origin;
|
||||
|
||||
@@ -151,7 +151,7 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
||||
let exists = !T::Currency::total_balance(&address).is_zero();
|
||||
total_imbalance = total_imbalance.merge(imbalance);
|
||||
if existed && !exists {
|
||||
// Account killed. This will ultimately lead to calling `OnReapAccount` callback
|
||||
// Account killed. This will ultimately lead to calling `OnKilledAccount` callback
|
||||
// which will make removal of CodeHashOf and AccountStorage for this account.
|
||||
// In order to avoid writing over the deleted properties we `continue` here.
|
||||
continue;
|
||||
|
||||
@@ -125,7 +125,7 @@ use frame_support::{
|
||||
parameter_types, IsSubType,
|
||||
weights::DispatchInfo,
|
||||
};
|
||||
use frame_support::traits::{OnReapAccount, OnUnbalanced, Currency, Get, Time, Randomness};
|
||||
use frame_support::traits::{OnKilledAccount, OnUnbalanced, Currency, Get, Time, Randomness};
|
||||
use frame_system::{self as system, ensure_signed, RawOrigin, ensure_root};
|
||||
use sp_core::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX;
|
||||
use pallet_contracts_primitives::{RentProjection, ContractAccessError};
|
||||
@@ -941,8 +941,12 @@ decl_storage! {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnReapAccount<T::AccountId> for Module<T> {
|
||||
fn on_reap_account(who: &T::AccountId) {
|
||||
// TODO: this should be removed in favour of a self-destruct contract host function allowing the
|
||||
// contract to delete all storage and the `ContractInfoOf` key and transfer remaining balance to
|
||||
// some other account. As it stands, it's an economic insecurity on any smart-contract chain.
|
||||
// https://github.com/paritytech/substrate/issues/4952
|
||||
impl<T: Trait> OnKilledAccount<T::AccountId> for Module<T> {
|
||||
fn on_killed_account(who: &T::AccountId) {
|
||||
if let Some(ContractInfo::Alive(info)) = <ContractInfoOf<T>>::take(who) {
|
||||
child::kill_storage(&info.trie_id, info.child_trie_unique_id());
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = (Balances, Contracts);
|
||||
type OnKilledAccount = Contracts;
|
||||
}
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
@@ -1606,7 +1606,7 @@ fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage:
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::system(system::RawEvent::ReapedAccount(DJANGO)),
|
||||
event: MetaEvent::system(system::RawEvent::KilledAccount(DJANGO)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
|
||||
@@ -30,7 +30,7 @@ use frame_support::{
|
||||
weights::SimpleDispatchInfo,
|
||||
traits::{
|
||||
Currency, ReservableCurrency, LockableCurrency, WithdrawReason, LockIdentifier, Get,
|
||||
OnReapAccount, OnUnbalanced, BalanceStatus
|
||||
OnUnbalanced, BalanceStatus
|
||||
}
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed, ensure_root};
|
||||
@@ -260,6 +260,24 @@ impl<BlockNumber: Parameter, Hash: Parameter> ReferendumInfo<BlockNumber, Hash>
|
||||
}
|
||||
}
|
||||
|
||||
/// State of a proxy voting account.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug)]
|
||||
pub enum ProxyState<AccountId> {
|
||||
/// Account is open to becoming a proxy but is not yet assigned.
|
||||
Open(AccountId),
|
||||
/// Account is actively being a proxy.
|
||||
Active(AccountId),
|
||||
}
|
||||
|
||||
impl<AccountId> ProxyState<AccountId> {
|
||||
fn as_active(self) -> Option<AccountId> {
|
||||
match self {
|
||||
ProxyState::Active(a) => Some(a),
|
||||
ProxyState::Open(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as Democracy {
|
||||
/// The number of (public) proposals that have been made so far.
|
||||
@@ -299,7 +317,7 @@ decl_storage! {
|
||||
|
||||
/// Who is able to vote for whom. Value is the fund-holding account, key is the
|
||||
/// vote-transaction-sending account.
|
||||
pub Proxy get(fn proxy): map hasher(blake2_256) T::AccountId => Option<T::AccountId>;
|
||||
pub Proxy get(fn proxy): map hasher(blake2_256) T::AccountId => Option<ProxyState<T::AccountId>>;
|
||||
|
||||
/// Get the account (and lock periods) to which another account is delegating vote.
|
||||
pub Delegations get(fn delegations):
|
||||
@@ -423,6 +441,12 @@ decl_error! {
|
||||
NotLocked,
|
||||
/// The lock on the account to be unlocked has not yet expired.
|
||||
NotExpired,
|
||||
/// A proxy-pairing was attempted to an account that was not open.
|
||||
NotOpen,
|
||||
/// A proxy-pairing was attempted to an account that was open to another account.
|
||||
WrongOpen,
|
||||
/// A proxy-de-pairing was attempted to an account that was not active.
|
||||
NotActive
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,8 +549,9 @@ decl_module! {
|
||||
#[compact] ref_index: ReferendumIndex,
|
||||
vote: Vote
|
||||
) -> DispatchResult {
|
||||
let who = Self::proxy(ensure_signed(origin)?).ok_or(Error::<T>::NotProxy)?;
|
||||
Self::do_vote(who, ref_index, vote)
|
||||
let who = ensure_signed(origin)?;
|
||||
let voter = Self::proxy(who).and_then(|a| a.as_active()).ok_or(Error::<T>::NotProxy)?;
|
||||
Self::do_vote(voter, ref_index, vote)
|
||||
}
|
||||
|
||||
/// Schedule an emergency cancellation of a referendum. Cannot happen twice to the same
|
||||
@@ -659,42 +684,65 @@ decl_module! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Specify a proxy. Called by the stash.
|
||||
/// Specify a proxy that is already open to us. Called by the stash.
|
||||
///
|
||||
/// NOTE: Used to be called `set_proxy`.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - One extra DB entry.
|
||||
/// # </weight>
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(100_000)]
|
||||
fn set_proxy(origin, proxy: T::AccountId) {
|
||||
fn activate_proxy(origin, proxy: T::AccountId) {
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(!<Proxy<T>>::contains_key(&proxy), Error::<T>::AlreadyProxy);
|
||||
<Proxy<T>>::insert(proxy, who)
|
||||
Proxy::<T>::try_mutate(&proxy, |a| match a.take() {
|
||||
None => Err(Error::<T>::NotOpen),
|
||||
Some(ProxyState::Active(_)) => Err(Error::<T>::AlreadyProxy),
|
||||
Some(ProxyState::Open(x)) if &x == &who => {
|
||||
*a = Some(ProxyState::Active(who));
|
||||
Ok(())
|
||||
}
|
||||
Some(ProxyState::Open(_)) => Err(Error::<T>::WrongOpen),
|
||||
})?;
|
||||
}
|
||||
|
||||
/// Clear the proxy. Called by the proxy.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - One DB clear.
|
||||
/// # </weight>
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(100_000)]
|
||||
fn resign_proxy(origin) {
|
||||
let who = ensure_signed(origin)?;
|
||||
<Proxy<T>>::remove(who);
|
||||
}
|
||||
|
||||
/// Clear the proxy. Called by the stash.
|
||||
/// NOTE: Used to be called `resign_proxy`.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - One DB clear.
|
||||
/// # </weight>
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(100_000)]
|
||||
fn remove_proxy(origin, proxy: T::AccountId) {
|
||||
fn close_proxy(origin) {
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(
|
||||
&Self::proxy(&proxy).ok_or(Error::<T>::NotProxy)? == &who,
|
||||
Error::<T>::WrongProxy,
|
||||
);
|
||||
<Proxy<T>>::remove(proxy);
|
||||
Proxy::<T>::mutate(&who, |a| {
|
||||
if a.is_some() {
|
||||
system::Module::<T>::dec_ref(&who);
|
||||
}
|
||||
*a = None;
|
||||
});
|
||||
}
|
||||
|
||||
/// Deactivate the proxy, but leave open to this account. Called by the stash.
|
||||
///
|
||||
/// The proxy must already be active.
|
||||
///
|
||||
/// NOTE: Used to be called `remove_proxy`.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - One DB clear.
|
||||
/// # </weight>
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(100_000)]
|
||||
fn deactivate_proxy(origin, proxy: T::AccountId) {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxy::<T>::try_mutate(&proxy, |a| match a.take() {
|
||||
None | Some(ProxyState::Open(_)) => Err(Error::<T>::NotActive),
|
||||
Some(ProxyState::Active(x)) if &x == &who => {
|
||||
*a = Some(ProxyState::Open(who));
|
||||
Ok(())
|
||||
}
|
||||
Some(ProxyState::Active(_)) => Err(Error::<T>::WrongProxy),
|
||||
})?;
|
||||
}
|
||||
|
||||
/// Delegate vote.
|
||||
@@ -818,6 +866,30 @@ decl_module! {
|
||||
Locks::<T>::remove(&target);
|
||||
Self::deposit_event(RawEvent::Unlocked(target));
|
||||
}
|
||||
|
||||
/// Become a proxy.
|
||||
///
|
||||
/// This must be called prior to a later `activate_proxy`.
|
||||
///
|
||||
/// Origin must be a Signed.
|
||||
///
|
||||
/// - `target`: The account whose votes will later be proxied.
|
||||
///
|
||||
/// `close_proxy` must be called before the account can be destroyed.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - One extra DB entry.
|
||||
/// # </weight>
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(100_000)]
|
||||
fn open_proxy(origin, target: T::AccountId) {
|
||||
let who = ensure_signed(origin)?;
|
||||
Proxy::<T>::mutate(&who, |a| {
|
||||
if a.is_none() {
|
||||
system::Module::<T>::inc_ref(&who);
|
||||
}
|
||||
*a = Some(ProxyState::Open(target));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -935,7 +1007,12 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn force_proxy(stash: T::AccountId, proxy: T::AccountId) {
|
||||
<Proxy<T>>::insert(proxy, stash)
|
||||
Proxy::<T>::mutate(&proxy, |o| {
|
||||
if o.is_none() {
|
||||
system::Module::<T>::inc_ref(&proxy);
|
||||
}
|
||||
*o = Some(ProxyState::Active(stash))
|
||||
})
|
||||
}
|
||||
|
||||
/// Start a referendum.
|
||||
@@ -1162,12 +1239,6 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnReapAccount<T::AccountId> for Module<T> {
|
||||
fn on_reap_account(who: &T::AccountId) {
|
||||
<Proxy<T>>::remove(who)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1229,7 +1300,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
@@ -1968,29 +2039,45 @@ mod tests {
|
||||
fn proxy_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(Democracy::proxy(10), None);
|
||||
assert_ok!(Democracy::set_proxy(Origin::signed(1), 10));
|
||||
assert_eq!(Democracy::proxy(10), Some(1));
|
||||
assert!(System::allow_death(&10));
|
||||
|
||||
assert_noop!(Democracy::activate_proxy(Origin::signed(1), 10), Error::<Test>::NotOpen);
|
||||
|
||||
assert_ok!(Democracy::open_proxy(Origin::signed(10), 1));
|
||||
assert!(!System::allow_death(&10));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1)));
|
||||
|
||||
assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::<Test>::WrongOpen);
|
||||
assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1)));
|
||||
|
||||
// Can't set when already set.
|
||||
assert_noop!(Democracy::set_proxy(Origin::signed(2), 10), Error::<Test>::AlreadyProxy);
|
||||
assert_noop!(Democracy::activate_proxy(Origin::signed(2), 10), Error::<Test>::AlreadyProxy);
|
||||
|
||||
// But this works because 11 isn't proxying.
|
||||
assert_ok!(Democracy::set_proxy(Origin::signed(2), 11));
|
||||
assert_eq!(Democracy::proxy(10), Some(1));
|
||||
assert_eq!(Democracy::proxy(11), Some(2));
|
||||
assert_ok!(Democracy::open_proxy(Origin::signed(11), 2));
|
||||
assert_ok!(Democracy::activate_proxy(Origin::signed(2), 11));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Active(1)));
|
||||
assert_eq!(Democracy::proxy(11), Some(ProxyState::Active(2)));
|
||||
|
||||
// 2 cannot fire 1's proxy:
|
||||
assert_noop!(Democracy::remove_proxy(Origin::signed(2), 10), Error::<Test>::WrongProxy);
|
||||
assert_noop!(Democracy::deactivate_proxy(Origin::signed(2), 10), Error::<Test>::WrongProxy);
|
||||
|
||||
// 1 fires his proxy:
|
||||
assert_ok!(Democracy::remove_proxy(Origin::signed(1), 10));
|
||||
assert_eq!(Democracy::proxy(10), None);
|
||||
assert_eq!(Democracy::proxy(11), Some(2));
|
||||
// 1 deactivates their proxy:
|
||||
assert_ok!(Democracy::deactivate_proxy(Origin::signed(1), 10));
|
||||
assert_eq!(Democracy::proxy(10), Some(ProxyState::Open(1)));
|
||||
// but the proxy account cannot be killed until the proxy is closed.
|
||||
assert!(!System::allow_death(&10));
|
||||
|
||||
// 11 resigns:
|
||||
assert_ok!(Democracy::resign_proxy(Origin::signed(11)));
|
||||
// and then 10 closes it completely:
|
||||
assert_ok!(Democracy::close_proxy(Origin::signed(10)));
|
||||
assert_eq!(Democracy::proxy(10), None);
|
||||
assert!(System::allow_death(&10));
|
||||
|
||||
// 11 just closes without 2's "permission".
|
||||
assert_ok!(Democracy::close_proxy(Origin::signed(11)));
|
||||
assert_eq!(Democracy::proxy(11), None);
|
||||
assert!(System::allow_death(&11));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2002,7 +2089,8 @@ mod tests {
|
||||
|
||||
fast_forward_to(2);
|
||||
let r = 0;
|
||||
assert_ok!(Democracy::set_proxy(Origin::signed(1), 10));
|
||||
assert_ok!(Democracy::open_proxy(Origin::signed(10), 1));
|
||||
assert_ok!(Democracy::activate_proxy(Origin::signed(1), 10));
|
||||
assert_ok!(Democracy::proxy_vote(Origin::signed(10), r, AYE));
|
||||
|
||||
assert_eq!(Democracy::voters_for(r), vec![1]);
|
||||
|
||||
@@ -816,7 +816,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -56,7 +56,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -50,10 +50,10 @@ parameter_types! {
|
||||
}
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Call = ();
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Call = ();
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = sp_core::sr25519::Public;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
@@ -65,9 +65,9 @@ impl frame_system::Trait for Test {
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type Version = ();
|
||||
type ModuleToIndex = ();
|
||||
type OnReapAccount = ();
|
||||
type OnNewAccount = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
type Extrinsic = TestXt<Call<Test>, ()>;
|
||||
|
||||
@@ -690,7 +690,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = pallet_balances::Module<Test>;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
|
||||
@@ -469,7 +469,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
@@ -573,7 +573,7 @@ mod tests {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("96797237079b6d6ffab7a47f90ee257a439a0e8268bdab3fe2f1e52572b101de").into(),
|
||||
state_root: hex!("17caebd966d10cc6dc9659edf7fa3196511593f6c39f80f9b97cdbc3b0855cf3").into(),
|
||||
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
|
||||
@@ -263,7 +263,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const WindowSize: u64 = 11;
|
||||
|
||||
@@ -1124,7 +1124,7 @@ impl<T: Subtrait> frame_system::Trait for ElevatedTrait<T> {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
impl<T: Subtrait> Trait for ElevatedTrait<T> {
|
||||
type Balance = T::Balance;
|
||||
|
||||
@@ -64,7 +64,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
|
||||
@@ -67,7 +67,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
mod grandpa {
|
||||
|
||||
@@ -936,7 +936,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
|
||||
@@ -117,7 +117,7 @@ impl frame_system::Trait for Runtime {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -67,7 +67,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -271,7 +271,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
ord_parameter_types! {
|
||||
pub const One: u64 = 1;
|
||||
|
||||
@@ -287,7 +287,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
|
||||
@@ -91,7 +91,7 @@ impl frame_system::Trait for Runtime {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl Trait for Runtime {
|
||||
|
||||
@@ -193,7 +193,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
type System = frame_system::Module<Test>;
|
||||
|
||||
@@ -159,9 +159,8 @@ use codec::{Encode, Decode};
|
||||
|
||||
use frame_support::{
|
||||
decl_module, decl_event, decl_storage, decl_error, ensure,
|
||||
Parameter, RuntimeDebug,
|
||||
weights::{GetDispatchInfo, SimpleDispatchInfo, FunctionOf},
|
||||
traits::{Currency, ReservableCurrency, Get, OnReapAccount, BalanceStatus},
|
||||
Parameter, RuntimeDebug, weights::{GetDispatchInfo, SimpleDispatchInfo, FunctionOf},
|
||||
traits::{Currency, ReservableCurrency, Get, BalanceStatus},
|
||||
};
|
||||
use frame_system::{self as system, ensure_signed, ensure_root};
|
||||
|
||||
@@ -241,6 +240,7 @@ decl_storage! {
|
||||
pub Recoverable get(fn recovery_config):
|
||||
map hasher(blake2_256) T::AccountId
|
||||
=> Option<RecoveryConfig<T::BlockNumber, BalanceOf<T>, T::AccountId>>;
|
||||
|
||||
/// Active recovery attempts.
|
||||
///
|
||||
/// First account is the account to be recovered, and the second account
|
||||
@@ -248,10 +248,11 @@ decl_storage! {
|
||||
pub ActiveRecoveries get(fn active_recovery):
|
||||
double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId =>
|
||||
Option<ActiveRecovery<T::BlockNumber, BalanceOf<T>, T::AccountId>>;
|
||||
/// The final list of recovered accounts.
|
||||
|
||||
/// The list of allowed proxy accounts.
|
||||
///
|
||||
/// Map from the recovered account to the user who can access it.
|
||||
pub Recovered get(fn recovered_account):
|
||||
/// Map from the user who can access it to the recovered account.
|
||||
pub Proxy get(fn proxy):
|
||||
map hasher(blake2_256) T::AccountId => Option<T::AccountId>;
|
||||
}
|
||||
}
|
||||
@@ -308,6 +309,8 @@ decl_error! {
|
||||
StillActive,
|
||||
/// There was an overflow in a calculation
|
||||
Overflow,
|
||||
/// This account is already set up for recovery
|
||||
AlreadyProxy,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +335,7 @@ decl_module! {
|
||||
/// - One storage lookup to check account is recovered by `who`. O(1)
|
||||
/// # </weight>
|
||||
#[weight = FunctionOf(
|
||||
|args: (&T::AccountId, &Box<<T as Trait>::Call>)| args.1.get_dispatch_info().weight + 10_000,
|
||||
|args: (&T::AccountId, &Box<<T as Trait>::Call>)| args.1.get_dispatch_info().weight + 10_000,
|
||||
|args: (&T::AccountId, &Box<<T as Trait>::Call>)| args.1.get_dispatch_info().class,
|
||||
true
|
||||
)]
|
||||
@@ -342,7 +345,8 @@ decl_module! {
|
||||
) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
// Check `who` is allowed to make a call on behalf of `account`
|
||||
ensure!(Self::recovered_account(&account) == Some(who), Error::<T>::NotAllowed);
|
||||
let target = Self::proxy(&who).ok_or(Error::<T>::NotAllowed)?;
|
||||
ensure!(&target == &account, Error::<T>::NotAllowed);
|
||||
call.dispatch(frame_system::RawOrigin::Signed(account).into())
|
||||
}
|
||||
|
||||
@@ -363,7 +367,7 @@ decl_module! {
|
||||
fn set_recovered(origin, lost: T::AccountId, rescuer: T::AccountId) {
|
||||
ensure_root(origin)?;
|
||||
// Create the recovery storage item.
|
||||
<Recovered<T>>::insert(&lost, &rescuer);
|
||||
<Proxy<T>>::insert(&rescuer, &lost);
|
||||
Self::deposit_event(RawEvent::AccountRecovered(lost, rescuer));
|
||||
}
|
||||
|
||||
@@ -428,6 +432,7 @@ decl_module! {
|
||||
};
|
||||
// Create the recovery configuration storage item
|
||||
<Recoverable<T>>::insert(&who, recovery_config);
|
||||
|
||||
Self::deposit_event(RawEvent::RecoveryCreated(who));
|
||||
}
|
||||
|
||||
@@ -545,6 +550,7 @@ decl_module! {
|
||||
let recovery_config = Self::recovery_config(&account).ok_or(Error::<T>::NotRecoverable)?;
|
||||
// Get the active recovery process for the rescuer
|
||||
let active_recovery = Self::active_recovery(&account, &who).ok_or(Error::<T>::NotStarted)?;
|
||||
ensure!(!Proxy::<T>::contains_key(&who), Error::<T>::AlreadyProxy);
|
||||
// Make sure the delay period has passed
|
||||
let current_block_number = <system::Module<T>>::block_number();
|
||||
let recoverable_block_number = active_recovery.created
|
||||
@@ -557,7 +563,8 @@ decl_module! {
|
||||
Error::<T>::Threshold
|
||||
);
|
||||
// Create the recovery storage item
|
||||
<Recovered<T>>::insert(&account, &who);
|
||||
Proxy::<T>::insert(&who, &account);
|
||||
system::Module::<T>::inc_ref(&who);
|
||||
Self::deposit_event(RawEvent::AccountRecovered(account, who));
|
||||
}
|
||||
|
||||
@@ -592,7 +599,7 @@ decl_module! {
|
||||
Self::deposit_event(RawEvent::RecoveryClosed(who, rescuer));
|
||||
}
|
||||
|
||||
/// Remove the recovery process for your account.
|
||||
/// Remove the recovery process for your account. Recovered accounts are still accessible.
|
||||
///
|
||||
/// NOTE: The user must make sure to call `close_recovery` on all active
|
||||
/// recovery attempts before calling this function else it will fail.
|
||||
@@ -621,10 +628,30 @@ decl_module! {
|
||||
ensure!(active_recoveries.next().is_none(), Error::<T>::StillActive);
|
||||
// Take the recovery configuration for this account.
|
||||
let recovery_config = <Recoverable<T>>::take(&who).ok_or(Error::<T>::NotRecoverable)?;
|
||||
|
||||
// Unreserve the initial deposit for the recovery configuration.
|
||||
T::Currency::unreserve(&who, recovery_config.deposit);
|
||||
Self::deposit_event(RawEvent::RecoveryRemoved(who));
|
||||
}
|
||||
|
||||
/// Cancel the ability to use `as_recovered` for `account`.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_ and registered to
|
||||
/// be able to make calls on behalf of the recovered account.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `account`: The recovered account you are able to call on-behalf-of.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - One storage mutation to check account is recovered by `who`. O(1)
|
||||
/// # </weight>
|
||||
fn cancel_recovered(origin, account: T::AccountId) {
|
||||
let who = ensure_signed(origin)?;
|
||||
// Check `who` is allowed to make a call on behalf of `account`
|
||||
ensure!(Self::proxy(&who) == Some(account), Error::<T>::NotAllowed);
|
||||
Proxy::<T>::remove(&who);
|
||||
system::Module::<T>::dec_ref(&who);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,11 +666,3 @@ impl<T: Trait> Module<T> {
|
||||
friends.binary_search(&friend).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnReapAccount<T::AccountId> for Module<T> {
|
||||
/// Remove any existing access another account might have when the account is reaped.
|
||||
/// This removes the final storage item managed by this module for any given account.
|
||||
fn on_reap_account(who: &T::AccountId) {
|
||||
<Recovered<T>>::remove(who);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u128>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = (Balances, Recovery);
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -31,7 +31,7 @@ use frame_support::{
|
||||
fn basic_setup_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Nothing in storage to start
|
||||
assert_eq!(Recovery::recovered_account(&1), None);
|
||||
assert_eq!(Recovery::proxy(&2), None);
|
||||
assert_eq!(Recovery::active_recovery(&1, &2), None);
|
||||
assert_eq!(Recovery::recovery_config(&1), None);
|
||||
// Everyone should have starting balance of 100
|
||||
@@ -91,10 +91,13 @@ fn recovery_life_cycle_works() {
|
||||
// All funds have been fully recovered!
|
||||
assert_eq!(Balances::free_balance(1), 200);
|
||||
assert_eq!(Balances::free_balance(5), 0);
|
||||
// Remove the proxy link.
|
||||
assert_ok!(Recovery::cancel_recovered(Origin::signed(1), 5));
|
||||
|
||||
// All storage items are removed from the module
|
||||
assert!(!<ActiveRecoveries<Test>>::contains_key(&5, &1));
|
||||
assert!(!<Recoverable<Test>>::contains_key(&5));
|
||||
assert!(!<Recovered<Test>>::contains_key(&5));
|
||||
assert!(!<Proxy<Test>>::contains_key(&1));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -335,7 +338,7 @@ fn claim_recovery_works() {
|
||||
// Account can be recovered.
|
||||
assert_ok!(Recovery::claim_recovery(Origin::signed(1), 5));
|
||||
// Recovered storage item is correctly created
|
||||
assert_eq!(<Recovered<Test>>::get(&5), Some(1));
|
||||
assert_eq!(<Proxy<Test>>::get(&1), Some(5));
|
||||
// Account could be re-recovered in the case that the recoverer account also gets lost.
|
||||
assert_ok!(Recovery::initiate_recovery(Origin::signed(4), 5));
|
||||
assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 4));
|
||||
@@ -347,7 +350,7 @@ fn claim_recovery_works() {
|
||||
// Account is re-recovered.
|
||||
assert_ok!(Recovery::claim_recovery(Origin::signed(4), 5));
|
||||
// Recovered storage item is correctly updated
|
||||
assert_eq!(<Recovered<Test>>::get(&5), Some(4));
|
||||
assert_eq!(<Proxy<Test>>::get(&4), Some(5));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl pallet_balances::Trait for Test {
|
||||
|
||||
@@ -318,7 +318,7 @@ mod tests {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
crate::GenesisConfig::<Test> {
|
||||
keys: NEXT_VALIDATORS.with(|l|
|
||||
l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i).into())).collect()
|
||||
l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect()
|
||||
),
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
sp_io::TestExternalities::new(t)
|
||||
|
||||
@@ -105,8 +105,9 @@ use sp_runtime::{KeyTypeId, Perbill, RuntimeAppPublic, BoundToRuntimeAppPublic};
|
||||
use frame_support::weights::SimpleDispatchInfo;
|
||||
use sp_runtime::traits::{Convert, Zero, Member, OpaqueKeys};
|
||||
use sp_staking::SessionIndex;
|
||||
use frame_support::{dispatch, ConsensusEngineId, decl_module, decl_event, decl_storage, decl_error};
|
||||
use frame_support::{ensure, traits::{OnReapAccount, Get, FindAuthor, ValidatorRegistration}, Parameter};
|
||||
use frame_support::{ensure, decl_module, decl_event, decl_storage, decl_error, ConsensusEngineId};
|
||||
use frame_support::{traits::{Get, FindAuthor, ValidatorRegistration}, Parameter};
|
||||
use frame_support::dispatch::{self, DispatchResult, DispatchError};
|
||||
use frame_system::{self as system, ensure_signed};
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -361,6 +362,7 @@ decl_storage! {
|
||||
///
|
||||
/// The first key is always `DEDUP_KEY_PREFIX` to have all the data in the same branch of
|
||||
/// the trie. Having all data in the same branch should prevent slowing down other queries.
|
||||
// TODO: Migrate to a normal map now https://github.com/paritytech/substrate/issues/4917
|
||||
NextKeys: double_map hasher(twox_64_concat) Vec<u8>, hasher(blake2_256) T::ValidatorId
|
||||
=> Option<T::Keys>;
|
||||
|
||||
@@ -368,11 +370,12 @@ decl_storage! {
|
||||
///
|
||||
/// The first key is always `DEDUP_KEY_PREFIX` to have all the data in the same branch of
|
||||
/// the trie. Having all data in the same branch should prevent slowing down other queries.
|
||||
// TODO: Migrate to a normal map now https://github.com/paritytech/substrate/issues/4917
|
||||
KeyOwner: double_map hasher(twox_64_concat) Vec<u8>, hasher(blake2_256) (KeyTypeId, Vec<u8>)
|
||||
=> Option<T::ValidatorId>;
|
||||
}
|
||||
add_extra_genesis {
|
||||
config(keys): Vec<(T::ValidatorId, T::Keys)>;
|
||||
config(keys): Vec<(T::AccountId, T::ValidatorId, T::Keys)>;
|
||||
build(|config: &GenesisConfig<T>| {
|
||||
if T::SessionHandler::KEY_TYPE_IDS.len() != T::Keys::key_ids().len() {
|
||||
panic!("Number of keys in session handler and session keys does not match");
|
||||
@@ -388,21 +391,17 @@ decl_storage! {
|
||||
}
|
||||
});
|
||||
|
||||
for (who, keys) in config.keys.iter().cloned() {
|
||||
assert!(
|
||||
<Module<T>>::load_keys(&who).is_none(),
|
||||
"genesis config contained duplicate validator {:?}", who,
|
||||
);
|
||||
|
||||
<Module<T>>::do_set_keys(&who, keys)
|
||||
for (account, val, keys) in config.keys.iter().cloned() {
|
||||
<Module<T>>::inner_set_keys(&val, keys)
|
||||
.expect("genesis config must not contain duplicates; qed");
|
||||
system::Module::<T>::inc_ref(&account);
|
||||
}
|
||||
|
||||
let initial_validators_0 = T::SessionManager::new_session(0)
|
||||
.unwrap_or_else(|| {
|
||||
frame_support::print("No initial validator provided by `SessionManager`, use \
|
||||
session config keys to generate initial validator set.");
|
||||
config.keys.iter().map(|(ref v, _)| v.clone()).collect()
|
||||
config.keys.iter().map(|x| x.1.clone()).collect()
|
||||
});
|
||||
assert!(!initial_validators_0.is_empty(), "Empty validator set for session 0 in genesis block!");
|
||||
|
||||
@@ -445,6 +444,8 @@ decl_error! {
|
||||
NoAssociatedValidatorId,
|
||||
/// Registered duplicate key.
|
||||
DuplicatedKey,
|
||||
/// No keys are associated with this account.
|
||||
NoKeys,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,6 +468,8 @@ decl_module! {
|
||||
/// # <weight>
|
||||
/// - O(log n) in number of accounts.
|
||||
/// - One extra DB entry.
|
||||
/// - Increases system account refs by one on success iff there were previously no keys set.
|
||||
/// In this case, purge_keys will need to be called before the account can be removed.
|
||||
/// # </weight>
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(150_000)]
|
||||
fn set_keys(origin, keys: T::Keys, proof: Vec<u8>) -> dispatch::DispatchResult {
|
||||
@@ -474,13 +477,27 @@ decl_module! {
|
||||
|
||||
ensure!(keys.ownership_proof_is_valid(&proof), Error::<T>::InvalidProof);
|
||||
|
||||
let who = T::ValidatorIdOf::convert(who).ok_or(Error::<T>::NoAssociatedValidatorId)?;
|
||||
|
||||
Self::do_set_keys(&who, keys)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Removes any session key(s) of the function caller.
|
||||
/// This doesn't take effect until the next session.
|
||||
///
|
||||
/// The dispatch origin of this function must be signed.
|
||||
///
|
||||
/// # <weight>
|
||||
/// - O(N) in number of key types.
|
||||
/// - Removes N + 1 DB entries.
|
||||
/// - Reduces system account refs by one on success.
|
||||
/// # </weight>
|
||||
#[weight = SimpleDispatchInfo::FixedNormal(150_000)]
|
||||
fn purge_keys(origin) {
|
||||
let who = ensure_signed(origin)?;
|
||||
Self::do_purge_keys(&who)?;
|
||||
}
|
||||
|
||||
/// Called when a block is initialized. Will rotate session if it is the last
|
||||
/// block of the current session.
|
||||
fn on_initialize(n: T::BlockNumber) {
|
||||
@@ -612,10 +629,30 @@ impl<T: Trait> Module<T> {
|
||||
Self::validators().iter().position(|i| i == c).map(Self::disable_index).ok_or(())
|
||||
}
|
||||
|
||||
// perform the set_key operation, checking for duplicates.
|
||||
// does not set `Changed`.
|
||||
fn do_set_keys(who: &T::ValidatorId, keys: T::Keys) -> dispatch::DispatchResult {
|
||||
let old_keys = Self::load_keys(&who);
|
||||
/// Perform the set_key operation, checking for duplicates. Does not set `Changed`.
|
||||
///
|
||||
/// This ensures that the reference counter in system is incremented appropriately and as such
|
||||
/// must accept an account ID, rather than a validator ID.
|
||||
fn do_set_keys(account: &T::AccountId, keys: T::Keys) -> dispatch::DispatchResult {
|
||||
let who = T::ValidatorIdOf::convert(account.clone())
|
||||
.ok_or(Error::<T>::NoAssociatedValidatorId)?;
|
||||
|
||||
let old_keys = Self::inner_set_keys(&who, keys)?;
|
||||
if old_keys.is_none() {
|
||||
system::Module::<T>::inc_ref(&account);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform the set_key operation, checking for duplicates. Does not set `Changed`.
|
||||
///
|
||||
/// The old keys for this validator are returned, or `None` if there were none.
|
||||
///
|
||||
/// This does not ensure that the reference counter in system is incremented appropriately, it
|
||||
/// must be done by the caller or the keys will be leaked in storage.
|
||||
fn inner_set_keys(who: &T::ValidatorId, keys: T::Keys) -> Result<Option<T::Keys>, DispatchError> {
|
||||
let old_keys = Self::load_keys(who);
|
||||
|
||||
for id in T::Keys::key_ids() {
|
||||
let key = keys.get_raw(*id);
|
||||
@@ -634,21 +671,25 @@ impl<T: Trait> Module<T> {
|
||||
Self::clear_key_owner(*id, old);
|
||||
}
|
||||
|
||||
Self::put_key_owner(*id, key, &who);
|
||||
Self::put_key_owner(*id, key, who);
|
||||
}
|
||||
|
||||
Self::put_keys(&who, &keys);
|
||||
|
||||
Ok(())
|
||||
Self::put_keys(who, &keys);
|
||||
Ok(old_keys)
|
||||
}
|
||||
|
||||
fn prune_dead_keys(who: &T::ValidatorId) {
|
||||
if let Some(old_keys) = Self::take_keys(who) {
|
||||
for id in T::Keys::key_ids() {
|
||||
let key_data = old_keys.get_raw(*id);
|
||||
Self::clear_key_owner(*id, key_data);
|
||||
}
|
||||
fn do_purge_keys(account: &T::AccountId) -> DispatchResult {
|
||||
let who = T::ValidatorIdOf::convert(account.clone())
|
||||
.ok_or(Error::<T>::NoAssociatedValidatorId)?;
|
||||
|
||||
let old_keys = Self::take_keys(&who).ok_or(Error::<T>::NoKeys)?;
|
||||
for id in T::Keys::key_ids() {
|
||||
let key_data = old_keys.get_raw(*id);
|
||||
Self::clear_key_owner(*id, key_data);
|
||||
}
|
||||
system::Module::<T>::dec_ref(&account);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_keys(v: &T::ValidatorId) -> Option<T::Keys> {
|
||||
@@ -676,12 +717,6 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnReapAccount<T::ValidatorId> for Module<T> {
|
||||
fn on_reap_account(who: &T::ValidatorId) {
|
||||
Self::prune_dead_keys(who);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps the author-scraping logic for consensus engines that can recover
|
||||
/// the canonical index of an author. This then transforms it into the
|
||||
/// registering account-ID of that session key index.
|
||||
@@ -716,7 +751,7 @@ mod tests {
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
GenesisConfig::<Test> {
|
||||
keys: NEXT_VALIDATORS.with(|l|
|
||||
l.borrow().iter().cloned().map(|i| (i, UintAuthorityId(i).into())).collect()
|
||||
l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect()
|
||||
),
|
||||
}.assimilate_storage(&mut t).unwrap();
|
||||
sp_io::TestExternalities::new(t)
|
||||
@@ -754,7 +789,10 @@ mod tests {
|
||||
let id = DUMMY;
|
||||
assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1));
|
||||
|
||||
Session::on_reap_account(&1);
|
||||
assert!(!System::allow_death(&1));
|
||||
assert_ok!(Session::purge_keys(Origin::signed(1)));
|
||||
assert!(System::allow_death(&1));
|
||||
|
||||
assert_eq!(Session::load_keys(&1), None);
|
||||
assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), None);
|
||||
})
|
||||
|
||||
@@ -178,7 +178,7 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Session;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Trait for Test {
|
||||
|
||||
@@ -78,7 +78,7 @@ impl frame_system::Trait for Test {
|
||||
type Version = ();
|
||||
type ModuleToIndex = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
|
||||
@@ -259,6 +259,7 @@ use codec::{HasCompact, Encode, Decode};
|
||||
use frame_support::{
|
||||
decl_module, decl_event, decl_storage, ensure, decl_error,
|
||||
weights::SimpleDispatchInfo,
|
||||
dispatch::DispatchResult,
|
||||
traits::{
|
||||
Currency, LockIdentifier, LockableCurrency,
|
||||
WithdrawReasons, OnUnbalanced, Imbalance, Get, Time
|
||||
@@ -282,7 +283,6 @@ use sp_runtime::{Serialize, Deserialize};
|
||||
use frame_system::{self as system, ensure_signed, ensure_root};
|
||||
|
||||
use sp_phragmen::ExtendedBalance;
|
||||
use frame_support::traits::OnReapAccount;
|
||||
|
||||
const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4;
|
||||
const MAX_NOMINATIONS: usize = 16;
|
||||
@@ -831,6 +831,8 @@ decl_error! {
|
||||
NoMoreChunks,
|
||||
/// Can not rebond without unlocking chunks.
|
||||
NoUnlockChunk,
|
||||
/// Attempting to target a stash that still has funds.
|
||||
FundedTarget,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,6 +902,8 @@ decl_module! {
|
||||
<Bonded<T>>::insert(&stash, &controller);
|
||||
<Payee<T>>::insert(&stash, payee);
|
||||
|
||||
system::Module::<T>::inc_ref(&stash);
|
||||
|
||||
let stash_balance = T::Currency::free_balance(&stash);
|
||||
let value = value.min(stash_balance);
|
||||
let item = StakingLedger { stash, total: value, active: value, unlocking: vec![] };
|
||||
@@ -1013,10 +1017,10 @@ decl_module! {
|
||||
// portion to fall below existential deposit + will have no more unlocking chunks
|
||||
// left. We can now safely remove this.
|
||||
let stash = ledger.stash;
|
||||
// remove all staking-related information.
|
||||
Self::kill_stash(&stash)?;
|
||||
// remove the lock.
|
||||
T::Currency::remove_lock(STAKING_ID, &stash);
|
||||
// remove all staking-related information.
|
||||
Self::kill_stash(&stash);
|
||||
} else {
|
||||
// This was the consequence of a partial unbond. just update the ledger and move on.
|
||||
Self::update_ledger(&controller, &ledger);
|
||||
@@ -1187,10 +1191,11 @@ decl_module! {
|
||||
fn force_unstake(origin, stash: T::AccountId) {
|
||||
ensure_root(origin)?;
|
||||
|
||||
// remove all staking-related information.
|
||||
Self::kill_stash(&stash)?;
|
||||
|
||||
// remove the lock.
|
||||
T::Currency::remove_lock(STAKING_ID, &stash);
|
||||
// remove all staking-related information.
|
||||
Self::kill_stash(&stash);
|
||||
}
|
||||
|
||||
/// Force there to be a new era at the end of sessions indefinitely.
|
||||
@@ -1254,9 +1259,22 @@ decl_module! {
|
||||
);
|
||||
|
||||
let ledger = ledger.rebond(value);
|
||||
|
||||
Self::update_ledger(&controller, &ledger);
|
||||
}
|
||||
|
||||
/// Remove all data structure concerning a staker/stash once its balance is zero.
|
||||
/// This is essentially equivalent to `withdraw_unbonded` except it can be called by anyone
|
||||
/// and the target `stash` must have no funds left.
|
||||
///
|
||||
/// This can be called from any origin.
|
||||
///
|
||||
/// - `stash`: The stash account to reap. Its balance must be zero.
|
||||
fn reap_stash(_origin, stash: T::AccountId) {
|
||||
Self::ensure_storage_upgraded();
|
||||
ensure!(T::Currency::total_balance(&stash).is_zero(), Error::<T>::FundedTarget);
|
||||
Self::kill_stash(&stash)?;
|
||||
T::Currency::remove_lock(STAKING_ID, &stash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1585,18 +1603,22 @@ impl<T: Trait> Module<T> {
|
||||
///
|
||||
/// Assumes storage is upgraded before calling.
|
||||
///
|
||||
/// This is called :
|
||||
/// - Immediately when an account's balance falls below existential deposit.
|
||||
/// This is called:
|
||||
/// - after a `withdraw_unbond()` call that frees all of a stash's bonded balance.
|
||||
fn kill_stash(stash: &T::AccountId) {
|
||||
if let Some(controller) = <Bonded<T>>::take(stash) {
|
||||
<Ledger<T>>::remove(&controller);
|
||||
}
|
||||
/// - through `reap_stash()` if the balance has fallen to zero (through slashing).
|
||||
fn kill_stash(stash: &T::AccountId) -> DispatchResult {
|
||||
let controller = Bonded::<T>::take(stash).ok_or(Error::<T>::NotStash)?;
|
||||
<Ledger<T>>::remove(&controller);
|
||||
|
||||
<Payee<T>>::remove(stash);
|
||||
<Validators<T>>::remove(stash);
|
||||
<Nominators<T>>::remove(stash);
|
||||
|
||||
slashing::clear_stash_metadata::<T>(stash);
|
||||
|
||||
system::Module::<T>::dec_ref(stash);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add reward points to validators using their stash account ID.
|
||||
@@ -1676,13 +1698,6 @@ impl<T: Trait> SessionManager<T::AccountId, Exposure<T::AccountId, BalanceOf<T>>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> OnReapAccount<T::AccountId> for Module<T> {
|
||||
fn on_reap_account(stash: &T::AccountId) {
|
||||
Self::ensure_storage_upgraded();
|
||||
Self::kill_stash(stash);
|
||||
}
|
||||
}
|
||||
|
||||
/// Add reward points to block authors:
|
||||
/// * 20 points to the block producer for producing a (non-uncle) block in the relay chain,
|
||||
/// * 2 points to the block producer for each reference to a previously unreferenced uncle, and
|
||||
|
||||
@@ -140,12 +140,12 @@ impl frame_system::Trait for Test {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = (Balances, Staking, Session);
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = Balance;
|
||||
type Event = ();
|
||||
type DustRemoval = ();
|
||||
type Event = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
}
|
||||
@@ -156,13 +156,13 @@ parameter_types! {
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25);
|
||||
}
|
||||
impl pallet_session::Trait for Test {
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Test, Staking>;
|
||||
type Keys = UintAuthorityId;
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Event = ();
|
||||
type ValidatorId = AccountId;
|
||||
type ValidatorIdOf = crate::StashOf<Test>;
|
||||
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Test, Staking>;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type Keys = UintAuthorityId;
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
}
|
||||
|
||||
@@ -300,22 +300,22 @@ impl ExtBuilder {
|
||||
|
||||
let _ = pallet_balances::GenesisConfig::<Test>{
|
||||
balances: vec![
|
||||
(1, 10 * balance_factor),
|
||||
(2, 20 * balance_factor),
|
||||
(3, 300 * balance_factor),
|
||||
(4, 400 * balance_factor),
|
||||
(10, balance_factor),
|
||||
(11, balance_factor * 1000),
|
||||
(20, balance_factor),
|
||||
(21, balance_factor * 2000),
|
||||
(30, balance_factor),
|
||||
(31, balance_factor * 2000),
|
||||
(40, balance_factor),
|
||||
(41, balance_factor * 2000),
|
||||
(100, 2000 * balance_factor),
|
||||
(101, 2000 * balance_factor),
|
||||
// This allow us to have a total_payout different from 0.
|
||||
(999, 1_000_000_000_000),
|
||||
(1, 10 * balance_factor),
|
||||
(2, 20 * balance_factor),
|
||||
(3, 300 * balance_factor),
|
||||
(4, 400 * balance_factor),
|
||||
(10, balance_factor),
|
||||
(11, balance_factor * 1000),
|
||||
(20, balance_factor),
|
||||
(21, balance_factor * 2000),
|
||||
(30, balance_factor),
|
||||
(31, balance_factor * 2000),
|
||||
(40, balance_factor),
|
||||
(41, balance_factor * 2000),
|
||||
(100, 2000 * balance_factor),
|
||||
(101, 2000 * balance_factor),
|
||||
// This allow us to have a total_payout different from 0.
|
||||
(999, 1_000_000_000_000),
|
||||
],
|
||||
}.assimilate_storage(&mut storage);
|
||||
|
||||
@@ -346,7 +346,7 @@ impl ExtBuilder {
|
||||
}.assimilate_storage(&mut storage);
|
||||
|
||||
let _ = pallet_session::GenesisConfig::<Test> {
|
||||
keys: validators.iter().map(|x| (*x, UintAuthorityId(*x))).collect(),
|
||||
keys: validators.iter().map(|x| (*x, *x, UintAuthorityId(*x))).collect(),
|
||||
}.assimilate_storage(&mut storage);
|
||||
|
||||
let mut ext = sp_io::TestExternalities::from(storage);
|
||||
|
||||
@@ -1456,6 +1456,9 @@ fn on_free_balance_zero_stash_removes_validator() {
|
||||
// Check total balance of stash
|
||||
assert_eq!(Balances::total_balance(&11), 0);
|
||||
|
||||
// Reap the stash
|
||||
assert_ok!(Staking::reap_stash(Origin::NONE, 11));
|
||||
|
||||
// Check storage items do not exist
|
||||
assert!(!<Ledger<Test>>::contains_key(&10));
|
||||
assert!(!<Bonded<Test>>::contains_key(&11));
|
||||
@@ -1509,6 +1512,9 @@ fn on_free_balance_zero_stash_removes_nominator() {
|
||||
// Check total balance of stash
|
||||
assert_eq!(Balances::total_balance(&11), 0);
|
||||
|
||||
// Reap the stash
|
||||
assert_ok!(Staking::reap_stash(Origin::NONE, 11));
|
||||
|
||||
// Check storage items do not exist
|
||||
assert!(!<Ledger<Test>>::contains_key(&10));
|
||||
assert!(!<Bonded<Test>>::contains_key(&11));
|
||||
@@ -2303,6 +2309,10 @@ fn garbage_collection_after_slashing() {
|
||||
// so we don't test those here.
|
||||
|
||||
assert_eq!(Balances::free_balance(11), 0);
|
||||
assert_eq!(Balances::total_balance(&11), 0);
|
||||
|
||||
assert_ok!(Staking::reap_stash(Origin::NONE, 11));
|
||||
|
||||
assert!(<Staking as crate::Store>::SlashingSpans::get(&11).is_none());
|
||||
assert_eq!(<Staking as crate::Store>::SpanSlash::get(&(11, 0)).amount_slashed(), &0);
|
||||
})
|
||||
|
||||
@@ -70,6 +70,11 @@ impl<T: Decode + Sized> Iterator for StorageIterator<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`.
|
||||
pub fn have_storage_value(module: &[u8], item: &[u8], hash: &[u8]) -> bool {
|
||||
get_storage_value::<()>(module, item, hash).is_some()
|
||||
}
|
||||
|
||||
/// Get a particular value in storage by the `module`, the map's `item` name and the key `hash`.
|
||||
pub fn get_storage_value<T: Decode + Sized>(module: &[u8], item: &[u8], hash: &[u8]) -> Option<T> {
|
||||
let mut key = vec![0u8; 32 + hash.len()];
|
||||
|
||||
@@ -195,9 +195,9 @@ pub trait OnNewAccount<AccountId> {
|
||||
|
||||
/// The account with the given id was reaped.
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
pub trait OnReapAccount<AccountId> {
|
||||
pub trait OnKilledAccount<AccountId> {
|
||||
/// The account with the given id was reaped.
|
||||
fn on_reap_account(who: &AccountId);
|
||||
fn on_killed_account(who: &AccountId);
|
||||
}
|
||||
|
||||
/// A trait for finding the author of a block header based on the `PreRuntime` digests contained
|
||||
|
||||
@@ -78,7 +78,7 @@ impl system::Trait for Runtime {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl module::Trait for Runtime {
|
||||
|
||||
@@ -93,6 +93,7 @@ use serde::Serialize;
|
||||
use sp_std::prelude::*;
|
||||
#[cfg(any(feature = "std", test))]
|
||||
use sp_std::map;
|
||||
use sp_std::convert::Infallible;
|
||||
use sp_std::marker::PhantomData;
|
||||
use sp_std::fmt::Debug;
|
||||
use sp_version::RuntimeVersion;
|
||||
@@ -112,9 +113,9 @@ use sp_runtime::{
|
||||
|
||||
use sp_core::{ChangesTrieConfiguration, storage::well_known_keys};
|
||||
use frame_support::{
|
||||
decl_module, decl_event, decl_storage, decl_error, storage, Parameter,
|
||||
decl_module, decl_event, decl_storage, decl_error, storage, Parameter, ensure, debug,
|
||||
traits::{
|
||||
Contains, Get, ModuleToIndex, OnNewAccount, OnReapAccount, IsDeadAccount, Happened,
|
||||
Contains, Get, ModuleToIndex, OnNewAccount, OnKilledAccount, IsDeadAccount, Happened,
|
||||
StoredMap
|
||||
},
|
||||
weights::{Weight, DispatchInfo, DispatchClass, SimpleDispatchInfo, FunctionOf},
|
||||
@@ -219,7 +220,7 @@ pub trait Trait: 'static + Eq + Clone {
|
||||
/// A function that is invoked when an account has been determined to be dead.
|
||||
///
|
||||
/// All resources should be cleaned up associated with the given account.
|
||||
type OnReapAccount: OnReapAccount<Self::AccountId>;
|
||||
type OnKilledAccount: OnKilledAccount<Self::AccountId>;
|
||||
}
|
||||
|
||||
pub type DigestOf<T> = generic::Digest<<T as Trait>::Hash>;
|
||||
@@ -290,13 +291,29 @@ fn hash69<T: AsMut<[u8]> + Default>() -> T {
|
||||
/// which can't contain more than `u32::max_value()` items.
|
||||
type EventIndex = u32;
|
||||
|
||||
/// Type used to encode the number of references an account has.
|
||||
pub type RefCount = u8;
|
||||
|
||||
/// Information of an account.
|
||||
#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode)]
|
||||
pub struct AccountInfo<Index, AccountData> {
|
||||
/// The number of transactions this account has sent.
|
||||
pub nonce: Index,
|
||||
/// The number of other modules that currently depend on this account's existence. The account
|
||||
/// cannot be reaped until this is zero.
|
||||
pub refcount: RefCount,
|
||||
/// The additional data that belongs to this account. Used to store the balance(s) in a lot of
|
||||
/// chains.
|
||||
pub data: AccountData,
|
||||
}
|
||||
|
||||
decl_storage! {
|
||||
trait Store for Module<T: Trait> as System {
|
||||
/// The full account information for a particular account ID.
|
||||
// TODO: should be hasher(twox64_concat) - will need staged migration
|
||||
// TODO: should not including T::Index (the nonce)
|
||||
// https://github.com/paritytech/substrate/issues/4917
|
||||
pub Account get(fn account): map hasher(blake2_256) T::AccountId => (T::Index, T::AccountData);
|
||||
pub Account get(fn account):
|
||||
map hasher(blake2_256) T::AccountId => AccountInfo<T::Index, T::AccountData>;
|
||||
|
||||
/// Total extrinsics count for the current block.
|
||||
ExtrinsicCount: Option<u32>;
|
||||
@@ -384,7 +401,7 @@ decl_event!(
|
||||
/// A new account was created.
|
||||
NewAccount(AccountId),
|
||||
/// An account was reaped.
|
||||
ReapedAccount(AccountId),
|
||||
KilledAccount(AccountId),
|
||||
}
|
||||
);
|
||||
|
||||
@@ -407,6 +424,11 @@ decl_error! {
|
||||
///
|
||||
/// Either calling `Core_version` or decoding `RuntimeVersion` failed.
|
||||
FailedToExtractRuntimeVersion,
|
||||
|
||||
/// Suicide called when the account has non-default composite data.
|
||||
NonDefaultComposite,
|
||||
/// There is a non-zero reference count preventing the account from being purged.
|
||||
NonZeroRefCount
|
||||
}
|
||||
}
|
||||
|
||||
@@ -517,6 +539,17 @@ decl_module! {
|
||||
ensure_root(origin)?;
|
||||
storage::unhashed::kill_prefix(&prefix);
|
||||
}
|
||||
|
||||
/// Kill the sending account, assuming there are no references outstanding and the composite
|
||||
/// data is equal to its default value.
|
||||
#[weight = SimpleDispatchInfo::FixedOperational(25_000)]
|
||||
fn suicide(origin) {
|
||||
let who = ensure_signed(origin)?;
|
||||
let account = Account::<T>::get(&who);
|
||||
ensure!(account.refcount == 0, Error::<T>::NonZeroRefCount);
|
||||
ensure!(account.data == T::AccountData::default(), Error::<T>::NonDefaultComposite);
|
||||
Account::<T>::remove(who);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -637,13 +670,40 @@ impl Default for InitKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Reference status; can be either referenced or unreferenced.
|
||||
pub enum RefStatus {
|
||||
Referenced,
|
||||
Unreferenced,
|
||||
}
|
||||
|
||||
impl<T: Trait> Module<T> {
|
||||
/// Deposits an event into this block's event record.
|
||||
pub fn deposit_event(event: impl Into<T::Event>) {
|
||||
Self::deposit_event_indexed(&[], event.into());
|
||||
}
|
||||
|
||||
/// Deposits an event into this block's event record adding this event
|
||||
/// Increment the reference counter on an account.
|
||||
pub fn inc_ref(who: &T::AccountId) {
|
||||
Account::<T>::mutate(who, |a| a.refcount = a.refcount.saturating_add(1));
|
||||
}
|
||||
|
||||
/// Decrement the reference counter on an account. This *MUST* only be done once for every time
|
||||
/// you called `inc_ref` on `who`.
|
||||
pub fn dec_ref(who: &T::AccountId) {
|
||||
Account::<T>::mutate(who, |a| a.refcount = a.refcount.saturating_sub(1));
|
||||
}
|
||||
|
||||
/// The number of outstanding references for the account `who`.
|
||||
pub fn refs(who: &T::AccountId) -> RefCount {
|
||||
Account::<T>::get(who).refcount
|
||||
}
|
||||
|
||||
/// True if the account has no outstanding references.
|
||||
pub fn allow_death(who: &T::AccountId) -> bool {
|
||||
Account::<T>::get(who).refcount == 0
|
||||
}
|
||||
|
||||
/// Deposits an event into this block's event record adding this event
|
||||
/// to the corresponding topic indexes.
|
||||
///
|
||||
/// This will update storage entries that correspond to the specified topics.
|
||||
@@ -855,12 +915,12 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
/// Retrieve the account transaction counter from storage.
|
||||
pub fn account_nonce(who: impl EncodeLike<T::AccountId>) -> T::Index {
|
||||
Account::<T>::get(who).0
|
||||
Account::<T>::get(who).nonce
|
||||
}
|
||||
|
||||
/// Increment a particular account's nonce by 1.
|
||||
pub fn inc_account_nonce(who: impl EncodeLike<T::AccountId>) {
|
||||
Account::<T>::mutate(who, |a| a.0 += T::Index::one());
|
||||
Account::<T>::mutate(who, |a| a.nonce += T::Index::one());
|
||||
}
|
||||
|
||||
/// Note what the extrinsic data of the current extrinsic index is. If this
|
||||
@@ -912,18 +972,28 @@ impl<T: Trait> Module<T> {
|
||||
Self::deposit_event(RawEvent::NewAccount(who));
|
||||
}
|
||||
|
||||
/// Kill the account and reap any related information.
|
||||
pub fn kill_account(who: T::AccountId) {
|
||||
if Account::<T>::contains_key(&who) {
|
||||
Account::<T>::remove(&who);
|
||||
Self::on_killed_account(who);
|
||||
}
|
||||
}
|
||||
|
||||
/// Do anything that needs to be done after an account has been killed.
|
||||
fn on_killed_account(who: T::AccountId) {
|
||||
T::OnReapAccount::on_reap_account(&who);
|
||||
Self::deposit_event(RawEvent::ReapedAccount(who));
|
||||
T::OnKilledAccount::on_killed_account(&who);
|
||||
Self::deposit_event(RawEvent::KilledAccount(who));
|
||||
}
|
||||
|
||||
/// Remove an account from storage. This should only be done when its refs are zero or you'll
|
||||
/// get storage leaks in other modules. Nonetheless we assume that the calling logic knows best.
|
||||
///
|
||||
/// This is a no-op if the account doesn't already exist. If it does then it will ensure
|
||||
/// cleanups (those in `on_killed_account`) take place.
|
||||
fn kill_account(who: &T::AccountId) {
|
||||
if Account::<T>::contains_key(who) {
|
||||
let account = Account::<T>::take(who);
|
||||
if account.refcount > 0 {
|
||||
debug::debug!(
|
||||
target: "system",
|
||||
"WARNING: Referenced account deleted. This is probably a bug."
|
||||
);
|
||||
}
|
||||
Module::<T>::on_killed_account(who.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,7 +1009,7 @@ impl<T: Trait> Happened<T::AccountId> for CallOnCreatedAccount<T> {
|
||||
pub struct CallKillAccount<T>(PhantomData<T>);
|
||||
impl<T: Trait> Happened<T::AccountId> for CallKillAccount<T> {
|
||||
fn happened(who: &T::AccountId) {
|
||||
Module::<T>::kill_account(who.clone());
|
||||
Module::<T>::kill_account(who)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -948,53 +1018,45 @@ impl<T: Trait> Happened<T::AccountId> for CallKillAccount<T> {
|
||||
// Anything more complex will need more sophisticated logic.
|
||||
impl<T: Trait> StoredMap<T::AccountId, T::AccountData> for Module<T> {
|
||||
fn get(k: &T::AccountId) -> T::AccountData {
|
||||
Account::<T>::get(k).1
|
||||
Account::<T>::get(k).data
|
||||
}
|
||||
fn is_explicit(k: &T::AccountId) -> bool {
|
||||
Account::<T>::contains_key(k)
|
||||
}
|
||||
fn insert(k: &T::AccountId, t: T::AccountData) {
|
||||
fn insert(k: &T::AccountId, data: T::AccountData) {
|
||||
let existed = Account::<T>::contains_key(k);
|
||||
Account::<T>::insert(k, (T::Index::default(), t));
|
||||
Account::<T>::mutate(k, |a| a.data = data);
|
||||
if !existed {
|
||||
Self::on_created_account(k.clone());
|
||||
}
|
||||
}
|
||||
fn remove(k: &T::AccountId) {
|
||||
if Account::<T>::contains_key(&k) {
|
||||
Self::kill_account(k.clone());
|
||||
}
|
||||
Self::kill_account(k)
|
||||
}
|
||||
fn mutate<R>(k: &T::AccountId, f: impl FnOnce(&mut T::AccountData) -> R) -> R {
|
||||
let existed = Account::<T>::contains_key(k);
|
||||
let r = Account::<T>::mutate(k, |a| f(&mut a.1));
|
||||
let r = Account::<T>::mutate(k, |a| f(&mut a.data));
|
||||
if !existed {
|
||||
Self::on_created_account(k.clone());
|
||||
}
|
||||
r
|
||||
}
|
||||
fn mutate_exists<R>(k: &T::AccountId, f: impl FnOnce(&mut Option<T::AccountData>) -> R) -> R {
|
||||
let (existed, exists, r) = Account::<T>::mutate_exists(k, |maybe_value| {
|
||||
let existed = maybe_value.is_some();
|
||||
let (maybe_nonce, mut maybe_extra) = split_inner(maybe_value.take(), |v| v);
|
||||
let r = f(&mut maybe_extra);
|
||||
*maybe_value = maybe_extra.map(|extra| (maybe_nonce.unwrap_or_default(), extra));
|
||||
(existed, maybe_value.is_some(), r)
|
||||
});
|
||||
if !existed && exists {
|
||||
Self::on_created_account(k.clone());
|
||||
} else if existed && !exists {
|
||||
Self::on_killed_account(k.clone());
|
||||
}
|
||||
r
|
||||
Self::try_mutate_exists(k, |x| -> Result<R, Infallible> { Ok(f(x)) }).expect("Infallible; qed")
|
||||
}
|
||||
fn try_mutate_exists<R, E>(k: &T::AccountId, f: impl FnOnce(&mut Option<T::AccountData>) -> Result<R, E>) -> Result<R, E> {
|
||||
Account::<T>::try_mutate_exists(k, |maybe_value| {
|
||||
let existed = maybe_value.is_some();
|
||||
let (maybe_nonce, mut maybe_extra) = split_inner(maybe_value.take(), |v| v);
|
||||
f(&mut maybe_extra).map(|v| {
|
||||
*maybe_value = maybe_extra.map(|extra| (maybe_nonce.unwrap_or_default(), extra));
|
||||
(existed, maybe_value.is_some(), v)
|
||||
let (maybe_prefix, mut maybe_data) = split_inner(
|
||||
maybe_value.take(),
|
||||
|account| ((account.nonce, account.refcount), account.data)
|
||||
);
|
||||
f(&mut maybe_data).map(|result| {
|
||||
*maybe_value = maybe_data.map(|data| {
|
||||
let (nonce, refcount) = maybe_prefix.unwrap_or_default();
|
||||
AccountInfo { nonce, refcount, data }
|
||||
});
|
||||
(existed, maybe_value.is_some(), result)
|
||||
})
|
||||
}).map(|(existed, exists, v)| {
|
||||
if !existed && exists {
|
||||
@@ -1213,17 +1275,18 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
|
||||
_info: Self::DispatchInfo,
|
||||
_len: usize,
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
let (expected, extra) = Account::<T>::get(who);
|
||||
if self.0 != expected {
|
||||
let mut account = Account::<T>::get(who);
|
||||
if self.0 != account.nonce {
|
||||
return Err(
|
||||
if self.0 < expected {
|
||||
if self.0 < account.nonce {
|
||||
InvalidTransaction::Stale
|
||||
} else {
|
||||
InvalidTransaction::Future
|
||||
}.into()
|
||||
)
|
||||
}
|
||||
Account::<T>::insert(who, (expected + T::Index::one(), extra));
|
||||
account.nonce += T::Index::one();
|
||||
Account::<T>::insert(who, account);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1235,13 +1298,13 @@ impl<T: Trait> SignedExtension for CheckNonce<T> {
|
||||
_len: usize,
|
||||
) -> TransactionValidity {
|
||||
// check index
|
||||
let (expected, _extra) = Account::<T>::get(who);
|
||||
if self.0 < expected {
|
||||
let account = Account::<T>::get(who);
|
||||
if self.0 < account.nonce {
|
||||
return InvalidTransaction::Stale.into()
|
||||
}
|
||||
|
||||
let provides = vec![Encode::encode(&(who, self.0))];
|
||||
let requires = if expected < self.0 {
|
||||
let requires = if account.nonce < self.0 {
|
||||
vec![Encode::encode(&(who, self.0 - One::one()))]
|
||||
} else {
|
||||
vec![]
|
||||
@@ -1411,6 +1474,7 @@ impl<T: Trait> Lookup for ChainContext<T> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use sp_std::cell::RefCell;
|
||||
use sp_core::H256;
|
||||
use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header, DispatchError};
|
||||
use frame_support::{impl_outer_origin, parameter_types};
|
||||
@@ -1437,6 +1501,15 @@ mod tests {
|
||||
};
|
||||
}
|
||||
|
||||
thread_local!{
|
||||
pub static KILLED: RefCell<Vec<u64>> = RefCell::new(vec![]);
|
||||
}
|
||||
|
||||
pub struct RecordKilled;
|
||||
impl OnKilledAccount<u64> for RecordKilled {
|
||||
fn on_killed_account(who: &u64) { KILLED.with(|r| r.borrow_mut().push(*who)) }
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Call = ();
|
||||
@@ -1454,9 +1527,9 @@ mod tests {
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type Version = Version;
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type AccountData = u32;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = RecordKilled;
|
||||
}
|
||||
|
||||
impl From<Event<Test>> for u16 {
|
||||
@@ -1493,6 +1566,27 @@ mod tests {
|
||||
assert_eq!(x, Ok(RawOrigin::<u64>::Signed(1u64)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stored_map_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::insert(&0, 42);
|
||||
assert!(System::allow_death(&0));
|
||||
|
||||
System::inc_ref(&0);
|
||||
assert!(!System::allow_death(&0));
|
||||
|
||||
System::insert(&0, 69);
|
||||
assert!(!System::allow_death(&0));
|
||||
|
||||
System::dec_ref(&0);
|
||||
assert!(System::allow_death(&0));
|
||||
|
||||
assert!(KILLED.with(|r| r.borrow().is_empty()));
|
||||
System::kill_account(&0);
|
||||
assert_eq!(KILLED.with(|r| r.borrow().clone()), vec![0u64]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deposit_event_should_work() {
|
||||
new_test_ext().execute_with(|| {
|
||||
@@ -1645,7 +1739,7 @@ mod tests {
|
||||
#[test]
|
||||
fn signed_ext_check_nonce_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
Account::<Test>::insert(1, (1, ()));
|
||||
Account::<Test>::insert(1, AccountInfo { nonce: 1, refcount: 0, data: 0 });
|
||||
let info = DispatchInfo::default();
|
||||
let len = 0_usize;
|
||||
// stale
|
||||
|
||||
@@ -128,19 +128,20 @@ pub trait SignAndSubmitTransaction<T: crate::Trait, Call> {
|
||||
fn sign_and_submit(call: impl Into<Call>, public: PublicOf<T, Call, Self>) -> Result<(), ()> {
|
||||
let call = call.into();
|
||||
let id = public.clone().into_account();
|
||||
let (expected_nonce, extra) = super::Account::<T>::get(&id);
|
||||
let mut account = super::Account::<T>::get(&id);
|
||||
debug::native::debug!(
|
||||
target: "offchain",
|
||||
"Creating signed transaction from account: {:?} (nonce: {:?})",
|
||||
id,
|
||||
expected_nonce,
|
||||
account.nonce,
|
||||
);
|
||||
let (call, signature_data) = Self::CreateTransaction
|
||||
::create_transaction::<Self::Signer>(call, public, id.clone(), expected_nonce)
|
||||
::create_transaction::<Self::Signer>(call, public, id.clone(), account.nonce)
|
||||
.ok_or(())?;
|
||||
// increment the nonce. This is fine, since the code should always
|
||||
// be running in off-chain context, so we NEVER persists data.
|
||||
super::Account::<T>::insert(&id, (expected_nonce + One::one(), extra));
|
||||
account.nonce += One::one();
|
||||
super::Account::<T>::insert(&id, account);
|
||||
|
||||
let xt = Self::Extrinsic::new(call, Some(signature_data)).ok_or(())?;
|
||||
sp_io::offchain::submit_transaction(xt.encode())
|
||||
|
||||
@@ -278,7 +278,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const MinimumPeriod: u64 = 5;
|
||||
|
||||
@@ -304,7 +304,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -760,7 +760,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
|
||||
@@ -258,7 +258,7 @@ decl_module! {
|
||||
/// - The weight of the `call` + 10,000.
|
||||
/// # </weight>
|
||||
#[weight = FunctionOf(
|
||||
|args: (&u16, &Box<<T as Trait>::Call>)| args.1.get_dispatch_info().weight + 10_000,
|
||||
|args: (&u16, &Box<<T as Trait>::Call>)| args.1.get_dispatch_info().weight + 10_000,
|
||||
|args: (&u16, &Box<<T as Trait>::Call>)| args.1.get_dispatch_info().class,
|
||||
true
|
||||
)]
|
||||
@@ -410,7 +410,7 @@ decl_module! {
|
||||
#[weight = FunctionOf(
|
||||
|args: (&u16, &Vec<T::AccountId>, &Option<Timepoint<T::BlockNumber>>, &[u8; 32])| {
|
||||
10_000 * (args.1.len() as u32 + 1)
|
||||
},
|
||||
},
|
||||
DispatchClass::Normal,
|
||||
true
|
||||
)]
|
||||
@@ -485,7 +485,7 @@ decl_module! {
|
||||
#[weight = FunctionOf(
|
||||
|args: (&u16, &Vec<T::AccountId>, &Timepoint<T::BlockNumber>, &[u8; 32])| {
|
||||
10_000 * (args.1.len() as u32 + 1)
|
||||
},
|
||||
},
|
||||
DispatchClass::Normal,
|
||||
true
|
||||
)]
|
||||
@@ -624,7 +624,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u64 = 1;
|
||||
|
||||
@@ -339,7 +339,7 @@ mod tests {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = pallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = Balances;
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = u64;
|
||||
|
||||
@@ -374,7 +374,7 @@ impl frame_system::Trait for Runtime {
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnReapAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Trait for Runtime {
|
||||
|
||||
Reference in New Issue
Block a user