feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+33
View File
@@ -0,0 +1,33 @@
[package]
name = "pezpallet-recovery"
version = "28.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "FRAME account recovery pallet"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
frame = { workspace = true, features = ["runtime"] }
scale-info = { features = ["derive"], workspace = true }
[dev-dependencies]
pezpallet-balances = { workspace = true, default-features = true }
[features]
default = ["std"]
runtime-benchmarks = [
"frame/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
]
std = ["codec/std", "frame/std", "pezpallet-balances/std", "scale-info/std"]
try-runtime = ["frame/try-runtime", "pezpallet-balances/try-runtime"]
+134
View File
@@ -0,0 +1,134 @@
# Recovery Pallet
- [`recovery::Config`](https://docs.rs/pezpallet-recovery/latest/pallet_recovery/trait.Config.html)
- [`Call`](https://docs.rs/pezpallet-recovery/latest/pallet_recovery/enum.Call.html)
## Overview
The Recovery pallet is an M-of-N social recovery tool for users to gain
access to their accounts if the private key or other authentication mechanism
is lost. Through this pallet, a user is able to make calls on-behalf-of another
account which they have recovered. The recovery process is protected by trusted
"friends" whom the original account owner chooses. A threshold (M) out of N
friends are needed to give another account access to the recoverable account.
### Recovery Configuration
The recovery process for each recoverable account can be configured by the account owner.
They are able to choose:
- `friends` - The list of friends that the account owner trusts to protect the
recovery process for their account.
- `threshold` - The number of friends that need to approve a recovery process for
the account to be successfully recovered.
- `delay_period` - The minimum number of blocks after the beginning of the recovery
process that need to pass before the account can be successfully recovered.
There is a configurable deposit that all users need to pay to create a recovery
configuration. This deposit is composed of a base deposit plus a multiplier for
the number of friends chosen. This deposit is returned in full when the account
owner removes their recovery configuration.
### Recovery Life Cycle
The intended life cycle of a successful recovery takes the following steps:
1. The account owner calls `create_recovery` to set up a recovery configuration
for their account.
2. At some later time, the account owner loses access to their account and wants
to recover it. Likely, they will need to create a new account and fund it with
enough balance to support the transaction fees and the deposit for the
recovery process.
3. Using this new account, they call `initiate_recovery`.
4. Then the account owner would contact their configured friends to vouch for
the recovery attempt. The account owner would provide their old account id
and the new account id, and friends would call `vouch_recovery` with those
parameters.
5. Once a threshold number of friends have vouched for the recovery attempt,
the account owner needs to wait until the delay period has passed, starting
when they initiated the recovery process.
6. Now the account owner is able to call `claim_recovery`, which subsequently
allows them to call `as_recovered` and directly make calls on-behalf-of the lost
account.
7. Using the now recovered account, the account owner can call `close_recovery`
on the recovery process they opened, reclaiming the recovery deposit they
placed.
8. Then the account owner should then call `remove_recovery` to remove the recovery
configuration on the recovered account and reclaim the recovery configuration
deposit they placed.
9. Using `as_recovered`, the account owner is able to call any other pallets
to clean up their state and reclaim any reserved or locked funds. They
can then transfer all funds from the recovered account to the new account.
10. When the recovered account becomes reaped (i.e. its free and reserved
balance drops to zero), the final recovery link is removed.
### Malicious Recovery Attempts
Initializing the recovery process for a recoverable account is open and
permissionless. However, the recovery deposit is an economic deterrent that
should disincentivize would-be attackers from trying to maliciously recover
accounts.
The recovery deposit can always be claimed by the account which is trying
to be recovered. In the case of a malicious recovery attempt, the account
owner who still has access to their account can claim the deposit and
essentially punish the malicious user.
Furthermore, the malicious recovery attempt can only be successful if the
attacker is also able to get enough friends to vouch for the recovery attempt.
In the case where the account owner prevents a malicious recovery process,
this pallet makes it near-zero cost to re-configure the recovery settings and
remove/replace friends who are acting inappropriately.
### Safety Considerations
It is important to note that this is a powerful pallet that can compromise the
security of an account if used incorrectly. Some recommended practices for users
of this pallet are:
- Configure a significant `delay_period` for your recovery process: As long as you
have access to your recoverable account, you need only check the blockchain once
every `delay_period` blocks to ensure that no recovery attempt is successful
against your account. Using off-chain notification systems can help with this,
but ultimately, setting a large `delay_period` means that even the most skilled
attacker will need to wait this long before they can access your account.
- Use a high threshold of approvals: Setting a value of 1 for the threshold means
that any of your friends would be able to recover your account. They would
simply need to start a recovery process and approve their own process. Similarly,
a threshold of 2 would mean that any 2 friends could work together to gain
access to your account. The only way to prevent against these kinds of attacks
is to choose a high threshold of approvals and select from a diverse friend
group that would not be able to reasonably coordinate with one another.
- Reset your configuration over time: Since the entire deposit of creating a
recovery configuration is returned to the user, the only cost of updating
your recovery configuration is the transaction fees for the calls. Thus,
it is strongly encouraged to regularly update your recovery configuration
as your life changes and your relationship with new and existing friends
change as well.
## Interface
### Dispatchable Functions
#### For General Users
- `create_recovery` - Create a recovery configuration for your account and make it recoverable.
- `initiate_recovery` - Start the recovery process for a recoverable account.
#### For Friends of a Recoverable Account
- `vouch_recovery` - As a `friend` of a recoverable account, vouch for a recovery attempt on the account.
#### For a User Who Successfully Recovered an Account
- `claim_recovery` - Claim access to the account that you have successfully completed the recovery process for.
- `as_recovered` - Send a transaction as an account that you have recovered. See other functions below.
#### For the Recoverable Account
- `close_recovery` - Close an active recovery process for your account and reclaim the recovery deposit.
- `remove_recovery` - Remove the recovery configuration from the account, making it un-recoverable.
#### For Super Users
- `set_recovered` - The ROOT origin is able to skip the recovery process and directly allow
one account to access another.
License: Apache-2.0
@@ -0,0 +1,469 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use crate::Pallet;
use alloc::{boxed::Box, vec, vec::Vec};
use frame::benchmarking::prelude::*;
const SEED: u32 = 0;
const DEFAULT_DELAY: u32 = 0;
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
pezframe_system::Pallet::<T>::assert_last_event(generic_event.into());
}
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
pezframe_system::Pallet::<T>::assert_has_event(generic_event.into());
}
fn get_total_deposit<T: Config>(
bounded_friends: &FriendsOf<T>,
) -> Option<<<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance>
{
let friend_deposit = T::FriendDepositFactor::get()
.checked_mul(&bounded_friends.len().saturated_into())
.unwrap();
T::ConfigDepositBase::get().checked_add(&friend_deposit)
}
fn generate_friends<T: Config>(num: u32) -> Vec<<T as pezframe_system::Config>::AccountId> {
// Create friends
let mut friends = (0..num).map(|x| account("friend", x, SEED)).collect::<Vec<_>>();
// Sort
friends.sort();
for friend in 0..friends.len() {
// Top up accounts of friends
T::Currency::make_free_balance_be(
&friends.get(friend).unwrap(),
BalanceOf::<T>::max_value(),
);
}
friends
}
fn add_caller_and_generate_friends<T: Config>(
caller: T::AccountId,
num: u32,
) -> Vec<<T as pezframe_system::Config>::AccountId> {
// Create friends
let mut friends = generate_friends::<T>(num - 1);
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
friends.push(caller);
// Sort
friends.sort();
friends
}
fn insert_recovery_config_with_max_friends<T: Config>(account: &T::AccountId) {
T::Currency::make_free_balance_be(&account, BalanceOf::<T>::max_value());
let n = T::MaxFriends::get();
let friends = generate_friends::<T>(n);
let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
// Get deposit for recovery
let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
let recovery_config = RecoveryConfig {
delay_period: DEFAULT_DELAY.into(),
deposit: total_deposit,
friends: bounded_friends,
threshold: n as u16,
};
// Reserve deposit for recovery
T::Currency::reserve(&account, total_deposit).unwrap();
<Recoverable<T>>::insert(&account, recovery_config);
}
fn setup_active_recovery_with_max_friends<T: Config>(
caller: &T::AccountId,
lost_account: &T::AccountId,
) {
insert_recovery_config_with_max_friends::<T>(&lost_account);
let n = T::MaxFriends::get();
let friends = generate_friends::<T>(n);
let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
let initial_recovery_deposit = T::RecoveryDeposit::get();
T::Currency::reserve(caller, initial_recovery_deposit).unwrap();
let active_recovery = ActiveRecovery {
created: DEFAULT_DELAY.into(),
deposit: initial_recovery_deposit,
friends: bounded_friends,
};
<ActiveRecoveries<T>>::insert(lost_account, caller, active_recovery);
}
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn as_recovered() {
let caller: T::AccountId = whitelisted_caller();
let recovered_account: T::AccountId = account("recovered_account", 0, SEED);
let recovered_account_lookup = T::Lookup::unlookup(recovered_account.clone());
let call: <T as Config>::RuntimeCall =
pezframe_system::Call::<T>::remark { remark: vec![] }.into();
Proxy::<T>::insert(&caller, &recovered_account);
#[extrinsic_call]
_(RawOrigin::Signed(caller), recovered_account_lookup, Box::new(call))
}
#[benchmark]
fn set_recovered() {
let lost: T::AccountId = whitelisted_caller();
let lost_lookup = T::Lookup::unlookup(lost.clone());
let rescuer: T::AccountId = whitelisted_caller();
let rescuer_lookup = T::Lookup::unlookup(rescuer.clone());
#[extrinsic_call]
_(RawOrigin::Root, lost_lookup, rescuer_lookup);
assert_last_event::<T>(
Event::AccountRecovered { lost_account: lost, rescuer_account: rescuer }.into(),
);
}
#[benchmark]
fn create_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
// Create friends
let friends = generate_friends::<T>(n);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), friends, n as u16, DEFAULT_DELAY.into());
assert_last_event::<T>(Event::RecoveryCreated { account: caller }.into());
}
#[benchmark]
fn initiate_recovery() {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
let lost_account: T::AccountId = account("lost_account", 0, SEED);
let lost_account_lookup = T::Lookup::unlookup(lost_account.clone());
insert_recovery_config_with_max_friends::<T>(&lost_account);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), lost_account_lookup);
assert_last_event::<T>(
Event::RecoveryInitiated { lost_account, rescuer_account: caller }.into(),
);
}
#[benchmark]
fn vouch_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
let caller: T::AccountId = whitelisted_caller();
let lost_account: T::AccountId = account("lost_account", 0, SEED);
let lost_account_lookup = T::Lookup::unlookup(lost_account.clone());
let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED);
let rescuer_account_lookup = T::Lookup::unlookup(rescuer_account.clone());
// Create friends
let friends = add_caller_and_generate_friends::<T>(caller.clone(), n);
let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
// Get deposit for recovery
let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
let recovery_config = RecoveryConfig {
delay_period: DEFAULT_DELAY.into(),
deposit: total_deposit,
friends: bounded_friends.clone(),
threshold: n as u16,
};
// Create the recovery config storage item
<Recoverable<T>>::insert(&lost_account, recovery_config.clone());
// Reserve deposit for recovery
T::Currency::reserve(&caller, total_deposit).unwrap();
// Create an active recovery status
let recovery_status = ActiveRecovery {
created: DEFAULT_DELAY.into(),
deposit: total_deposit,
friends: generate_friends::<T>(n - 1).try_into().unwrap(),
};
// Create the active recovery storage item
<ActiveRecoveries<T>>::insert(&lost_account, &rescuer_account, recovery_status);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), lost_account_lookup, rescuer_account_lookup);
assert_last_event::<T>(
Event::RecoveryVouched { lost_account, rescuer_account, sender: caller }.into(),
);
}
#[benchmark]
fn claim_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
let caller: T::AccountId = whitelisted_caller();
let lost_account: T::AccountId = account("lost_account", 0, SEED);
let lost_account_lookup = T::Lookup::unlookup(lost_account.clone());
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
// Create friends
let friends = generate_friends::<T>(n);
let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
// Get deposit for recovery
let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
let recovery_config = RecoveryConfig {
delay_period: 0u32.into(),
deposit: total_deposit,
friends: bounded_friends.clone(),
threshold: n as u16,
};
// Create the recovery config storage item
<Recoverable<T>>::insert(&lost_account, recovery_config.clone());
// Reserve deposit for recovery
T::Currency::reserve(&caller, total_deposit).unwrap();
// Create an active recovery status
let recovery_status = ActiveRecovery {
created: 0u32.into(),
deposit: total_deposit,
friends: bounded_friends.clone(),
};
// Create the active recovery storage item
<ActiveRecoveries<T>>::insert(&lost_account, &caller, recovery_status);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), lost_account_lookup);
assert_last_event::<T>(
Event::AccountRecovered { lost_account, rescuer_account: caller }.into(),
);
}
#[benchmark]
fn close_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
let caller: T::AccountId = whitelisted_caller();
let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED);
let rescuer_account_lookup = T::Lookup::unlookup(rescuer_account.clone());
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
T::Currency::make_free_balance_be(&rescuer_account, BalanceOf::<T>::max_value());
// Create friends
let friends = generate_friends::<T>(n);
let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
// Get deposit for recovery
let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
let recovery_config = RecoveryConfig {
delay_period: DEFAULT_DELAY.into(),
deposit: total_deposit,
friends: bounded_friends.clone(),
threshold: n as u16,
};
// Create the recovery config storage item
<Recoverable<T>>::insert(&caller, recovery_config.clone());
// Reserve deposit for recovery
T::Currency::reserve(&caller, total_deposit).unwrap();
// Create an active recovery status
let recovery_status = ActiveRecovery {
created: DEFAULT_DELAY.into(),
deposit: total_deposit,
friends: bounded_friends.clone(),
};
// Create the active recovery storage item
<ActiveRecoveries<T>>::insert(&caller, &rescuer_account, recovery_status);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), rescuer_account_lookup);
assert_last_event::<T>(
Event::RecoveryClosed { lost_account: caller, rescuer_account }.into(),
);
}
#[benchmark]
fn remove_recovery(n: Linear<1, { T::MaxFriends::get() }>) {
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
// Create friends
let friends = generate_friends::<T>(n);
let bounded_friends: FriendsOf<T> = friends.try_into().unwrap();
// Get deposit for recovery
let total_deposit = get_total_deposit::<T>(&bounded_friends).unwrap();
let recovery_config = RecoveryConfig {
delay_period: DEFAULT_DELAY.into(),
deposit: total_deposit,
friends: bounded_friends.clone(),
threshold: n as u16,
};
// Create the recovery config storage item
<Recoverable<T>>::insert(&caller, recovery_config);
// Reserve deposit for recovery
T::Currency::reserve(&caller, total_deposit).unwrap();
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()));
assert_last_event::<T>(Event::RecoveryRemoved { lost_account: caller }.into());
}
#[benchmark]
fn cancel_recovered() -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let account: T::AccountId = account("account", 0, SEED);
let account_lookup = T::Lookup::unlookup(account.clone());
pezframe_system::Pallet::<T>::inc_providers(&caller);
pezframe_system::Pallet::<T>::inc_consumers(&caller)?;
Proxy::<T>::insert(&caller, &account);
#[extrinsic_call]
_(RawOrigin::Signed(caller), account_lookup);
Ok(())
}
#[benchmark]
fn poke_deposit(n: Linear<1, { T::MaxFriends::get() }>) -> Result<(), BenchmarkError> {
let caller: T::AccountId = whitelisted_caller();
let lost_account: T::AccountId = account("lost_account", 0, SEED);
// Fund caller account
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
// 1. Setup recovery config for caller
insert_recovery_config_with_max_friends::<T>(&caller);
// 2. Setup active recovery for lost account
setup_active_recovery_with_max_friends::<T>(&caller, &lost_account);
// 3. Get initial deposits
let initial_config = <Recoverable<T>>::get(&caller).unwrap();
let initial_config_deposit = initial_config.deposit;
let initial_recovery_deposit = T::RecoveryDeposit::get();
assert_eq!(
T::Currency::reserved_balance(&caller),
initial_config_deposit.saturating_add(initial_recovery_deposit)
);
// 4. Artificially increase deposits
let increased_config_deposit = initial_config_deposit.saturating_add(2u32.into());
let increased_recovery_deposit = initial_recovery_deposit.saturating_add(2u32.into());
<Recoverable<T>>::try_mutate(&caller, |maybe_config| -> Result<(), BenchmarkError> {
let config = maybe_config.as_mut().unwrap();
T::Currency::reserve(
&caller,
increased_config_deposit.saturating_sub(initial_config_deposit),
)?;
config.deposit = increased_config_deposit;
Ok(())
})
.map_err(|_| BenchmarkError::Stop("Failed to mutate storage"))?;
<ActiveRecoveries<T>>::try_mutate(
&lost_account,
&caller,
|maybe_recovery| -> Result<(), BenchmarkError> {
let recovery = maybe_recovery.as_mut().unwrap();
T::Currency::reserve(
&caller,
increased_recovery_deposit.saturating_sub(initial_recovery_deposit),
)?;
recovery.deposit = increased_recovery_deposit;
Ok(())
},
)
.map_err(|_| BenchmarkError::Stop("Failed to mutate storage"))?;
// 5. Verify increased deposits
assert_eq!(
T::Currency::reserved_balance(&caller),
increased_config_deposit.saturating_add(increased_recovery_deposit)
);
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), Some(T::Lookup::unlookup(lost_account.clone())));
// 6. Assert final state
assert_eq!(
T::Currency::reserved_balance(&caller),
initial_config_deposit.saturating_add(initial_recovery_deposit)
);
// 7. Check events were emitted
assert_has_event::<T>(
Event::DepositPoked {
who: caller.clone(),
kind: DepositKind::RecoveryConfig,
old_deposit: increased_config_deposit,
new_deposit: initial_config_deposit,
}
.into(),
);
assert_has_event::<T>(
Event::DepositPoked {
who: caller,
kind: DepositKind::ActiveRecoveryFor(lost_account),
old_deposit: increased_recovery_deposit,
new_deposit: initial_recovery_deposit,
}
.into(),
);
Ok(())
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
+894
View File
@@ -0,0 +1,894 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! # Recovery Pallet
//!
//! - [`Config`]
//! - [`Call`]
//!
//! ## Overview
//!
//! The Recovery pallet is an M-of-N social recovery tool for users to gain
//! access to their accounts if the private key or other authentication mechanism
//! is lost. Through this pallet, a user is able to make calls on-behalf-of another
//! account which they have recovered. The recovery process is protected by trusted
//! "friends" whom the original account owner chooses. A threshold (M) out of N
//! friends are needed to give another account access to the recoverable account.
//!
//! ### Recovery Configuration
//!
//! The recovery process for each recoverable account can be configured by the account owner.
//! They are able to choose:
//! * `friends` - The list of friends that the account owner trusts to protect the recovery process
//! for their account.
//! * `threshold` - The number of friends that need to approve a recovery process for the account to
//! be successfully recovered.
//! * `delay_period` - The minimum number of blocks after the beginning of the recovery process that
//! need to pass before the account can be successfully recovered.
//!
//! There is a configurable deposit that all users need to pay to create a recovery
//! configuration. This deposit is composed of a base deposit plus a multiplier for
//! the number of friends chosen. This deposit is returned in full when the account
//! owner removes their recovery configuration.
//!
//! ### Recovery Life Cycle
//!
//! The intended life cycle of a successful recovery takes the following steps:
//! 1. The account owner calls `create_recovery` to set up a recovery configuration for their
//! account.
//! 2. At some later time, the account owner loses access to their account and wants to recover it.
//! Likely, they will need to create a new account and fund it with enough balance to support the
//! transaction fees and the deposit for the recovery process.
//! 3. Using this new account, they call `initiate_recovery`.
//! 4. Then the account owner would contact their configured friends to vouch for the recovery
//! attempt. The account owner would provide their old account id and the new account id, and
//! friends would call `vouch_recovery` with those parameters.
//! 5. Once a threshold number of friends have vouched for the recovery attempt, the account owner
//! needs to wait until the delay period has passed, starting when they initiated the recovery
//! process.
//! 6. Now the account owner is able to call `claim_recovery`, which subsequently allows them to
//! call `as_recovered` and directly make calls on-behalf-of the lost account.
//! 7. Using the now recovered account, the account owner can call `close_recovery` on the recovery
//! process they opened, reclaiming the recovery deposit they placed.
//! 8. Then the account owner should then call `remove_recovery` to remove the recovery
//! configuration on the recovered account and reclaim the recovery configuration deposit they
//! placed.
//! 9. Using `as_recovered`, the account owner is able to call any other pallets to clean up their
//! state and reclaim any reserved or locked funds. They can then transfer all funds from the
//! recovered account to the new account.
//! 10. When the recovered account becomes reaped (i.e. its free and reserved balance drops to
//! zero), the final recovery link is removed.
//!
//! ### Malicious Recovery Attempts
//!
//! Initializing the recovery process for a recoverable account is open and
//! permissionless. However, the recovery deposit is an economic deterrent that
//! should disincentivize would-be attackers from trying to maliciously recover
//! accounts.
//!
//! The recovery deposit can always be claimed by the account which is trying
//! to be recovered. In the case of a malicious recovery attempt, the account
//! owner who still has access to their account can claim the deposit and
//! essentially punish the malicious user.
//!
//! Furthermore, the malicious recovery attempt can only be successful if the
//! attacker is also able to get enough friends to vouch for the recovery attempt.
//! In the case where the account owner prevents a malicious recovery process,
//! this pallet makes it near-zero cost to re-configure the recovery settings and
//! remove/replace friends who are acting inappropriately.
//!
//! ### Safety Considerations
//!
//! It is important to note that this is a powerful pallet that can compromise the
//! security of an account if used incorrectly. Some recommended practices for users
//! of this pallet are:
//!
//! * Configure a significant `delay_period` for your recovery process: As long as you have access
//! to your recoverable account, you need only check the blockchain once every `delay_period`
//! blocks to ensure that no recovery attempt is successful against your account. Using off-chain
//! notification systems can help with this, but ultimately, setting a large `delay_period` means
//! that even the most skilled attacker will need to wait this long before they can access your
//! account.
//! * Use a high threshold of approvals: Setting a value of 1 for the threshold means that any of
//! your friends would be able to recover your account. They would simply need to start a recovery
//! process and approve their own process. Similarly, a threshold of 2 would mean that any 2
//! friends could work together to gain access to your account. The only way to prevent against
//! these kinds of attacks is to choose a high threshold of approvals and select from a diverse
//! friend group that would not be able to reasonably coordinate with one another.
//! * Reset your configuration over time: Since the entire deposit of creating a recovery
//! configuration is returned to the user, the only cost of updating your recovery configuration
//! is the transaction fees for the calls. Thus, it is strongly encouraged to regularly update
//! your recovery configuration as your life changes and your relationship with new and existing
//! friends change as well.
//!
//! ## Interface
//!
//! ### Dispatchable Functions
//!
//! #### For General Users
//!
//! * `create_recovery` - Create a recovery configuration for your account and make it recoverable.
//! * `initiate_recovery` - Start the recovery process for a recoverable account.
//!
//! #### For Friends of a Recoverable Account
//! * `vouch_recovery` - As a `friend` of a recoverable account, vouch for a recovery attempt on the
//! account.
//!
//! #### For a User Who Successfully Recovered an Account
//!
//! * `claim_recovery` - Claim access to the account that you have successfully completed the
//! recovery process for.
//! * `as_recovered` - Send a transaction as an account that you have recovered. See other functions
//! below.
//!
//! #### For the Recoverable Account
//!
//! * `close_recovery` - Close an active recovery process for your account and reclaim the recovery
//! deposit.
//! * `remove_recovery` - Remove the recovery configuration from the account, making it
//! un-recoverable.
//!
//! #### For Super Users
//!
//! * `set_recovered` - The ROOT origin is able to skip the recovery process and directly allow one
//! account to access another.
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
use alloc::{boxed::Box, vec::Vec};
use frame::{
prelude::*,
traits::{Currency, ReservableCurrency},
};
pub use pallet::*;
pub use weights::WeightInfo;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
pub type AccountIdLookupOf<T> = <<T as pezframe_system::Config>::Lookup as StaticLookup>::Source;
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
pub type BlockNumberFromProviderOf<T> =
<<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
pub type FriendsOf<T> =
BoundedVec<<T as pezframe_system::Config>::AccountId, <T as Config>::MaxFriends>;
/// An active recovery process.
#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct ActiveRecovery<BlockNumber, Balance, Friends> {
/// The block number when the recovery process started.
pub created: BlockNumber,
/// The amount held in reserve of the `depositor`,
/// to be returned once this recovery process is closed.
pub deposit: Balance,
/// The friends which have vouched so far. Always sorted.
pub friends: Friends,
}
/// Configuration for recovering an account.
#[derive(Clone, Eq, PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct RecoveryConfig<BlockNumber, Balance, Friends> {
/// The minimum number of blocks since the start of the recovery process before the account
/// can be recovered.
pub delay_period: BlockNumber,
/// The amount held in reserve of the `depositor`,
/// to be returned once this configuration is removed.
pub deposit: Balance,
/// The list of friends which can help recover an account. Always sorted.
pub friends: Friends,
/// The number of approving friends needed to recover an account.
pub threshold: u16,
}
/// The type of deposit
#[derive(
Clone,
Eq,
PartialEq,
Encode,
Decode,
DebugNoBound,
TypeInfo,
MaxEncodedLen,
DecodeWithMemTracking,
)]
pub enum DepositKind<T: Config> {
/// Recovery configuration deposit
RecoveryConfig,
/// Active recovery deposit for an account
ActiveRecoveryFor(<T as pezframe_system::Config>::AccountId),
}
#[frame::pallet]
pub mod pallet {
use super::*;
#[pallet::pallet]
pub struct Pallet<T>(_);
/// Configuration trait.
#[pallet::config]
pub trait Config: pezframe_system::Config {
/// The overarching event type.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// The overarching call type.
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo
+ From<pezframe_system::Call<Self>>;
/// Query the current block number.
///
/// Must return monotonically increasing values when called from consecutive blocks.
/// Can be configured to return either:
/// - the local block number of the runtime via `pezframe_system::Pallet`
/// - a remote block number, eg from the relay chain through `RelaychainDataProvider`
/// - an arbitrary value through a custom implementation of the trait
///
/// There is currently no migration provided to "hot-swap" block number providers and it may
/// result in undefined behavior when doing so. Teyrchains are therefore best off setting
/// this to their local block number provider if they have the pallet already deployed.
///
/// Suggested values:
/// - Solo- and Relay-chains: `pezframe_system::Pallet`
/// - Teyrchains that may produce blocks sparingly or only when needed (on-demand):
/// - already have the pallet deployed: `pezframe_system::Pallet`
/// - are freshly deploying this pallet: `RelaychainDataProvider`
/// - Teyrchains with a reliably block production rate (PLO or bulk-coretime):
/// - already have the pallet deployed: `pezframe_system::Pallet`
/// - are freshly deploying this pallet: no strong recommendation. Both local and remote
/// providers can be used. Relay provider can be a bit better in cases where the
/// teyrchain is lagging its block production to avoid clock skew.
type BlockNumberProvider: BlockNumberProvider;
/// The currency mechanism.
type Currency: ReservableCurrency<Self::AccountId>;
/// The base amount of currency needed to reserve for creating a recovery configuration.
///
/// This is held for an additional storage item whose value size is
/// `2 + sizeof(BlockNumber, Balance)` bytes.
#[pallet::constant]
type ConfigDepositBase: Get<BalanceOf<Self>>;
/// The amount of currency needed per additional user when creating a recovery
/// configuration.
///
/// This is held for adding `sizeof(AccountId)` bytes more into a pre-existing storage
/// value.
#[pallet::constant]
type FriendDepositFactor: Get<BalanceOf<Self>>;
/// The maximum amount of friends allowed in a recovery configuration.
///
/// NOTE: The threshold programmed in this Pallet uses u16, so it does
/// not really make sense to have a limit here greater than u16::MAX.
/// But also, that is a lot more than you should probably set this value
/// to anyway...
#[pallet::constant]
type MaxFriends: Get<u32>;
/// The base amount of currency needed to reserve for starting a recovery.
///
/// This is primarily held for deterring malicious recovery attempts, and should
/// have a value large enough that a bad actor would choose not to place this
/// deposit. It also acts to fund additional storage item whose value size is
/// `sizeof(BlockNumber, Balance + T * AccountId)` bytes. Where T is a configurable
/// threshold.
#[pallet::constant]
type RecoveryDeposit: Get<BalanceOf<Self>>;
}
/// Events type.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A recovery process has been set up for an account.
RecoveryCreated { account: T::AccountId },
/// A recovery process has been initiated for lost account by rescuer account.
RecoveryInitiated { lost_account: T::AccountId, rescuer_account: T::AccountId },
/// A recovery process for lost account by rescuer account has been vouched for by sender.
RecoveryVouched {
lost_account: T::AccountId,
rescuer_account: T::AccountId,
sender: T::AccountId,
},
/// A recovery process for lost account by rescuer account has been closed.
RecoveryClosed { lost_account: T::AccountId, rescuer_account: T::AccountId },
/// Lost account has been successfully recovered by rescuer account.
AccountRecovered { lost_account: T::AccountId, rescuer_account: T::AccountId },
/// A recovery process has been removed for an account.
RecoveryRemoved { lost_account: T::AccountId },
/// A deposit has been updated.
DepositPoked {
who: T::AccountId,
kind: DepositKind<T>,
old_deposit: BalanceOf<T>,
new_deposit: BalanceOf<T>,
},
}
#[pallet::error]
pub enum Error<T> {
/// User is not allowed to make a call on behalf of this account
NotAllowed,
/// Threshold must be greater than zero
ZeroThreshold,
/// Friends list must be greater than zero and threshold
NotEnoughFriends,
/// Friends list must be less than max friends
MaxFriends,
/// Friends list must be sorted and free of duplicates
NotSorted,
/// This account is not set up for recovery
NotRecoverable,
/// This account is already set up for recovery
AlreadyRecoverable,
/// A recovery process has already started for this account
AlreadyStarted,
/// A recovery process has not started for this rescuer
NotStarted,
/// This account is not a friend who can vouch
NotFriend,
/// The friend must wait until the delay period to vouch for this recovery
DelayPeriod,
/// This user has already vouched for this recovery
AlreadyVouched,
/// The threshold for recovering this account has not been met
Threshold,
/// There are still active recovery attempts that need to be closed
StillActive,
/// This account is already set up for recovery
AlreadyProxy,
/// Some internal state is broken.
BadState,
}
/// The set of recoverable accounts and their recovery configuration.
#[pallet::storage]
#[pallet::getter(fn recovery_config)]
pub type Recoverable<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
RecoveryConfig<BlockNumberFromProviderOf<T>, BalanceOf<T>, FriendsOf<T>>,
>;
/// Active recovery attempts.
///
/// First account is the account to be recovered, and the second account
/// is the user trying to recover the account.
#[pallet::storage]
#[pallet::getter(fn active_recovery)]
pub type ActiveRecoveries<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
T::AccountId,
Twox64Concat,
T::AccountId,
ActiveRecovery<BlockNumberFromProviderOf<T>, BalanceOf<T>, FriendsOf<T>>,
>;
/// The list of allowed proxy accounts.
///
/// Map from the user who can access it to the recovered account.
#[pallet::storage]
#[pallet::getter(fn proxy)]
pub type Proxy<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId>;
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Send a call through a recovered 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 want to make a call on-behalf-of.
/// - `call`: The call you want to make with the recovered account.
#[pallet::call_index(0)]
#[pallet::weight({
let dispatch_info = call.get_dispatch_info();
(
T::WeightInfo::as_recovered().saturating_add(dispatch_info.call_weight),
dispatch_info.class,
)})]
pub fn as_recovered(
origin: OriginFor<T>,
account: AccountIdLookupOf<T>,
call: Box<<T as Config>::RuntimeCall>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let account = T::Lookup::lookup(account)?;
// Check `who` is allowed to make a call on behalf of `account`
let target = Self::proxy(&who).ok_or(Error::<T>::NotAllowed)?;
ensure!(target == account, Error::<T>::NotAllowed);
call.dispatch(pezframe_system::RawOrigin::Signed(account).into())
.map(|_| ())
.map_err(|e| e.error)
}
/// Allow ROOT to bypass the recovery process and set a rescuer account
/// for a lost account directly.
///
/// The dispatch origin for this call must be _ROOT_.
///
/// Parameters:
/// - `lost`: The "lost account" to be recovered.
/// - `rescuer`: The "rescuer account" which can call as the lost account.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::set_recovered())]
pub fn set_recovered(
origin: OriginFor<T>,
lost: AccountIdLookupOf<T>,
rescuer: AccountIdLookupOf<T>,
) -> DispatchResult {
ensure_root(origin)?;
let lost = T::Lookup::lookup(lost)?;
let rescuer = T::Lookup::lookup(rescuer)?;
// Create the recovery storage item.
<Proxy<T>>::insert(&rescuer, &lost);
Self::deposit_event(Event::<T>::AccountRecovered {
lost_account: lost,
rescuer_account: rescuer,
});
Ok(())
}
/// Create a recovery configuration for your account. This makes your account recoverable.
///
/// Payment: `ConfigDepositBase` + `FriendDepositFactor` * #_of_friends balance
/// will be reserved for storing the recovery configuration. This deposit is returned
/// in full when the user calls `remove_recovery`.
///
/// The dispatch origin for this call must be _Signed_.
///
/// Parameters:
/// - `friends`: A list of friends you trust to vouch for recovery attempts. Should be
/// ordered and contain no duplicate values.
/// - `threshold`: The number of friends that must vouch for a recovery attempt before the
/// account can be recovered. Should be less than or equal to the length of the list of
/// friends.
/// - `delay_period`: The number of blocks after a recovery attempt is initialized that
/// needs to pass before the account can be recovered.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::create_recovery(friends.len() as u32))]
pub fn create_recovery(
origin: OriginFor<T>,
friends: Vec<T::AccountId>,
threshold: u16,
delay_period: BlockNumberFromProviderOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
// Check account is not already set up for recovery
ensure!(!<Recoverable<T>>::contains_key(&who), Error::<T>::AlreadyRecoverable);
// Check user input is valid
ensure!(threshold >= 1, Error::<T>::ZeroThreshold);
ensure!(!friends.is_empty(), Error::<T>::NotEnoughFriends);
ensure!(threshold as usize <= friends.len(), Error::<T>::NotEnoughFriends);
let bounded_friends: FriendsOf<T> =
friends.try_into().map_err(|_| Error::<T>::MaxFriends)?;
ensure!(Self::is_sorted_and_unique(&bounded_friends), Error::<T>::NotSorted);
// Calculate total deposit required
let total_deposit = Self::get_recovery_config_deposit(bounded_friends.len())?;
// Reserve the deposit
T::Currency::reserve(&who, total_deposit)?;
// Create the recovery configuration
let recovery_config = RecoveryConfig {
delay_period,
deposit: total_deposit,
friends: bounded_friends,
threshold,
};
// Create the recovery configuration storage item
<Recoverable<T>>::insert(&who, recovery_config);
Self::deposit_event(Event::<T>::RecoveryCreated { account: who });
Ok(())
}
/// Initiate the process for recovering a recoverable account.
///
/// Payment: `RecoveryDeposit` balance will be reserved for initiating the
/// recovery process. This deposit will always be repatriated to the account
/// trying to be recovered. See `close_recovery`.
///
/// The dispatch origin for this call must be _Signed_.
///
/// Parameters:
/// - `account`: The lost account that you want to recover. This account needs to be
/// recoverable (i.e. have a recovery configuration).
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::initiate_recovery())]
pub fn initiate_recovery(
origin: OriginFor<T>,
account: AccountIdLookupOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let account = T::Lookup::lookup(account)?;
// Check that the account is recoverable
ensure!(<Recoverable<T>>::contains_key(&account), Error::<T>::NotRecoverable);
// Check that the recovery process has not already been started
ensure!(
!<ActiveRecoveries<T>>::contains_key(&account, &who),
Error::<T>::AlreadyStarted
);
// Take recovery deposit
let recovery_deposit = T::RecoveryDeposit::get();
T::Currency::reserve(&who, recovery_deposit)?;
// Create an active recovery status
let recovery_status = ActiveRecovery {
created: T::BlockNumberProvider::current_block_number(),
deposit: recovery_deposit,
friends: Default::default(),
};
// Create the active recovery storage item
<ActiveRecoveries<T>>::insert(&account, &who, recovery_status);
Self::deposit_event(Event::<T>::RecoveryInitiated {
lost_account: account,
rescuer_account: who,
});
Ok(())
}
/// Allow a "friend" of a recoverable account to vouch for an active recovery
/// process for that account.
///
/// The dispatch origin for this call must be _Signed_ and must be a "friend"
/// for the recoverable account.
///
/// Parameters:
/// - `lost`: The lost account that you want to recover.
/// - `rescuer`: The account trying to rescue the lost account that you want to vouch for.
///
/// The combination of these two parameters must point to an active recovery
/// process.
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::vouch_recovery(T::MaxFriends::get()))]
pub fn vouch_recovery(
origin: OriginFor<T>,
lost: AccountIdLookupOf<T>,
rescuer: AccountIdLookupOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let lost = T::Lookup::lookup(lost)?;
let rescuer = T::Lookup::lookup(rescuer)?;
// Get the recovery configuration for the lost account.
let recovery_config = Self::recovery_config(&lost).ok_or(Error::<T>::NotRecoverable)?;
// Get the active recovery process for the rescuer.
let mut active_recovery =
Self::active_recovery(&lost, &rescuer).ok_or(Error::<T>::NotStarted)?;
// Make sure the voter is a friend
ensure!(Self::is_friend(&recovery_config.friends, &who), Error::<T>::NotFriend);
// Either insert the vouch, or return an error that the user already vouched.
match active_recovery.friends.binary_search(&who) {
Ok(_pos) => return Err(Error::<T>::AlreadyVouched.into()),
Err(pos) => active_recovery
.friends
.try_insert(pos, who.clone())
.map_err(|_| Error::<T>::MaxFriends)?,
}
// Update storage with the latest details
<ActiveRecoveries<T>>::insert(&lost, &rescuer, active_recovery);
Self::deposit_event(Event::<T>::RecoveryVouched {
lost_account: lost,
rescuer_account: rescuer,
sender: who,
});
Ok(())
}
/// Allow a successful rescuer to claim their recovered account.
///
/// The dispatch origin for this call must be _Signed_ and must be a "rescuer"
/// who has successfully completed the account recovery process: collected
/// `threshold` or more vouches, waited `delay_period` blocks since initiation.
///
/// Parameters:
/// - `account`: The lost account that you want to claim has been successfully recovered by
/// you.
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::claim_recovery(T::MaxFriends::get()))]
pub fn claim_recovery(
origin: OriginFor<T>,
account: AccountIdLookupOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let account = T::Lookup::lookup(account)?;
// Get the recovery configuration for the lost account
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 = T::BlockNumberProvider::current_block_number();
let recoverable_block_number = active_recovery
.created
.checked_add(&recovery_config.delay_period)
.ok_or(ArithmeticError::Overflow)?;
ensure!(recoverable_block_number <= current_block_number, Error::<T>::DelayPeriod);
// Make sure the threshold is met
ensure!(
recovery_config.threshold as usize <= active_recovery.friends.len(),
Error::<T>::Threshold
);
pezframe_system::Pallet::<T>::inc_consumers(&who).map_err(|_| Error::<T>::BadState)?;
// Create the recovery storage item
Proxy::<T>::insert(&who, &account);
Self::deposit_event(Event::<T>::AccountRecovered {
lost_account: account,
rescuer_account: who,
});
Ok(())
}
/// As the controller of a recoverable account, close an active recovery
/// process for your account.
///
/// Payment: By calling this function, the recoverable account will receive
/// the recovery deposit `RecoveryDeposit` placed by the rescuer.
///
/// The dispatch origin for this call must be _Signed_ and must be a
/// recoverable account with an active recovery process for it.
///
/// Parameters:
/// - `rescuer`: The account trying to rescue this recoverable account.
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::close_recovery(T::MaxFriends::get()))]
pub fn close_recovery(
origin: OriginFor<T>,
rescuer: AccountIdLookupOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let rescuer = T::Lookup::lookup(rescuer)?;
// Take the active recovery process started by the rescuer for this account.
let active_recovery =
<ActiveRecoveries<T>>::take(&who, &rescuer).ok_or(Error::<T>::NotStarted)?;
// Move the reserved funds from the rescuer to the rescued account.
// Acts like a slashing mechanism for those who try to maliciously recover accounts.
let res = T::Currency::repatriate_reserved(
&rescuer,
&who,
active_recovery.deposit,
BalanceStatus::Free,
);
debug_assert!(res.is_ok());
Self::deposit_event(Event::<T>::RecoveryClosed {
lost_account: who,
rescuer_account: rescuer,
});
Ok(())
}
/// 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.
///
/// Payment: By calling this function the recoverable account will unreserve
/// their recovery configuration deposit.
/// (`ConfigDepositBase` + `FriendDepositFactor` * #_of_friends)
///
/// The dispatch origin for this call must be _Signed_ and must be a
/// recoverable account (i.e. has a recovery configuration).
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::remove_recovery(T::MaxFriends::get()))]
pub fn remove_recovery(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
// Check there are no active recoveries
let mut active_recoveries = <ActiveRecoveries<T>>::iter_prefix_values(&who);
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(Event::<T>::RecoveryRemoved { lost_account: who });
Ok(())
}
/// 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.
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::cancel_recovered())]
pub fn cancel_recovered(
origin: OriginFor<T>,
account: AccountIdLookupOf<T>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let account = T::Lookup::lookup(account)?;
// 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);
pezframe_system::Pallet::<T>::dec_consumers(&who);
Ok(())
}
/// Poke deposits for recovery configurations and / or active recoveries.
///
/// This can be used by accounts to possibly lower their locked amount.
///
/// The dispatch origin for this call must be _Signed_.
///
/// Parameters:
/// - `maybe_account`: Optional recoverable account for which you have an active recovery
/// and want to adjust the deposit for the active recovery.
///
/// This function checks both recovery configuration deposit and active recovery deposits
/// of the caller:
/// - If the caller has created a recovery configuration, checks and adjusts its deposit
/// - If the caller has initiated any active recoveries, and provides the account in
/// `maybe_account`, checks and adjusts those deposits
///
/// If any deposit is updated, the difference will be reserved/unreserved from the caller's
/// account.
///
/// The transaction is made free if any deposit is updated and paid otherwise.
///
/// Emits `DepositPoked` if any deposit is updated.
/// Multiple events may be emitted in case both types of deposits are updated.
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::poke_deposit(T::MaxFriends::get()))]
pub fn poke_deposit(
origin: OriginFor<T>,
maybe_account: Option<AccountIdLookupOf<T>>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let mut deposit_updated = false;
// Check and update recovery config deposit
deposit_updated |= Self::poke_recovery_config_deposit(&who)?;
// Check and update active recovery deposit
if let Some(lost_account) = maybe_account {
let lost_account = T::Lookup::lookup(lost_account)?;
deposit_updated |= Self::poke_active_recovery_deposit(&who, &lost_account)?;
}
Ok(if deposit_updated { Pays::No } else { Pays::Yes }.into())
}
}
}
impl<T: Config> Pallet<T> {
/// Check that friends list is sorted and has no duplicates.
fn is_sorted_and_unique(friends: &Vec<T::AccountId>) -> bool {
friends.windows(2).all(|w| w[0] < w[1])
}
/// Check that a user is a friend in the friends list.
fn is_friend(friends: &Vec<T::AccountId>, friend: &T::AccountId) -> bool {
friends.binary_search(&friend).is_ok()
}
/// Helper function to calculate recovery config deposit
/// Total deposit is base fee + number of friends * factor fee
fn get_recovery_config_deposit(friends_count: usize) -> Result<BalanceOf<T>, DispatchError> {
let friend_deposit = T::FriendDepositFactor::get()
.checked_mul(&friends_count.saturated_into())
.ok_or(ArithmeticError::Overflow)?;
T::ConfigDepositBase::get()
.checked_add(&friend_deposit)
.ok_or(ArithmeticError::Overflow.into())
}
/// Helper function to poke the deposit reserved for creating a recovery config
fn poke_recovery_config_deposit(who: &T::AccountId) -> Result<bool, DispatchError> {
<Recoverable<T>>::try_mutate(&who, |maybe_config| -> Result<bool, DispatchError> {
let Some(config) = maybe_config.as_mut() else { return Ok(false) };
let old_deposit = config.deposit;
let new_deposit = Self::get_recovery_config_deposit(config.friends.len())?;
if old_deposit == new_deposit {
return Ok(false);
}
if new_deposit > old_deposit {
let extra = new_deposit.saturating_sub(old_deposit);
T::Currency::reserve(&who, extra)?;
} else {
let excess = old_deposit.saturating_sub(new_deposit);
let remaining_unreserved = T::Currency::unreserve(&who, excess);
if !remaining_unreserved.is_zero() {
defensive!(
"Failed to unreserve full amount. (Requested, Actual)",
(excess, excess.saturating_sub(remaining_unreserved))
);
}
}
config.deposit = new_deposit;
Self::deposit_event(Event::<T>::DepositPoked {
who: who.clone(),
kind: DepositKind::RecoveryConfig,
old_deposit,
new_deposit,
});
Ok(true)
})
}
/// Helper function to poke the deposit reserved for an active recovery
fn poke_active_recovery_deposit(
who: &T::AccountId,
lost_account: &T::AccountId,
) -> Result<bool, DispatchError> {
let new_deposit = T::RecoveryDeposit::get();
<ActiveRecoveries<T>>::try_mutate(
lost_account,
who,
|maybe_recovery| -> Result<bool, DispatchError> {
let recovery = maybe_recovery.as_mut().ok_or(Error::<T>::NotStarted)?;
let old_deposit = recovery.deposit;
// Skip if deposit hasn't changed
if recovery.deposit == new_deposit {
return Ok(false);
}
// Update deposit
if new_deposit > old_deposit {
let extra = new_deposit.saturating_sub(old_deposit);
T::Currency::reserve(who, extra)?;
} else {
let excess = old_deposit.saturating_sub(new_deposit);
let remaining_unreserved = T::Currency::unreserve(who, excess);
if !remaining_unreserved.is_zero() {
defensive!(
"Failed to unreserve full amount. (Requested, Actual)",
(excess, excess.saturating_sub(remaining_unreserved))
);
}
}
recovery.deposit = new_deposit;
Self::deposit_event(Event::<T>::DepositPoked {
who: who.clone(),
kind: DepositKind::ActiveRecoveryFor(lost_account.clone()),
old_deposit,
new_deposit,
});
Ok(true)
},
)
}
}
+87
View File
@@ -0,0 +1,87 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Test utilities
use super::*;
use crate as recovery;
use frame::{deps::pezsp_io, testing_prelude::*};
type Block = pezframe_system::mocking::MockBlock<Test>;
construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
Recovery: recovery,
}
);
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type Block = Block;
type AccountData = pezpallet_balances::AccountData<u128>;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type Balance = u128;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
parameter_types! {
pub static ConfigDepositBase: u64 = 10;
pub const FriendDepositFactor: u64 = 1;
pub static RecoveryDeposit: u64 = 10;
// Large number of friends for benchmarking.
pub const MaxFriends: u32 = 128;
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type RuntimeCall = RuntimeCall;
type BlockNumberProvider = System;
type Currency = Balances;
type ConfigDepositBase = ConfigDepositBase;
type FriendDepositFactor = FriendDepositFactor;
type MaxFriends = MaxFriends;
type RecoveryDeposit = RecoveryDeposit;
}
pub type BalancesCall = pezpallet_balances::Call<Test>;
pub type RecoveryCall = super::Call<Test>;
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pezpallet_balances::GenesisConfig::<Test> {
balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)],
..Default::default()
}
.assimilate_storage(&mut t)
.unwrap();
let mut ext: pezsp_io::TestExternalities = t.into();
ext.execute_with(|| System::set_block_number(1));
ext
}
+788
View File
@@ -0,0 +1,788 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Tests for the module.
use crate::{mock::*, *};
use frame::{deps::pezsp_runtime::bounded_vec, testing_prelude::*};
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
// Nothing in storage to start
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
assert_eq!(Balances::free_balance(1), 100);
});
}
#[test]
fn set_recovered_works() {
new_test_ext().execute_with(|| {
// Not accessible by a normal user
assert_noop!(Recovery::set_recovered(RuntimeOrigin::signed(1), 5, 1), BadOrigin);
// Root can set a recovered account though
assert_ok!(Recovery::set_recovered(RuntimeOrigin::root(), 5, 1));
// Account 1 should now be able to make a call through account 5
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 1,
value: 100,
}));
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
// Account 1 has successfully drained the funds from account 5
assert_eq!(Balances::free_balance(1), 200);
assert_eq!(Balances::free_balance(5), 0);
});
}
#[test]
fn recovery_life_cycle_works() {
new_test_ext().execute_with(|| {
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
// Account 5 sets up a recovery configuration on their account
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends,
threshold,
delay_period
));
// Some time has passed, and the user lost their keys!
System::run_to_block::<AllPalletsWithSystem>(10);
// Using account 1, the user begins the recovery process to recover the lost account
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
// Off chain, the user contacts their friends and asks them to vouch for the recovery
// attempt
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1));
// We met the threshold, lets try to recover the account...?
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::DelayPeriod
);
// We need to wait at least the delay_period number of blocks before we can recover
System::run_to_block::<AllPalletsWithSystem>(20);
assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5));
// Account 1 can use account 5 to close the active recovery process, claiming the deposited
// funds used to initiate the recovery process into account 5.
let call = Box::new(RuntimeCall::Recovery(RecoveryCall::close_recovery { rescuer: 1 }));
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
// Account 1 can then use account 5 to remove the recovery configuration, claiming the
// deposited funds used to create the recovery configuration into account 5.
let call = Box::new(RuntimeCall::Recovery(RecoveryCall::remove_recovery {}));
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
// Account 1 should now be able to make a call through account 5 to get all of their funds
assert_eq!(Balances::free_balance(5), 110);
let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death {
dest: 1,
value: 110,
}));
assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call));
// 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(RuntimeOrigin::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!(!<Proxy<Test>>::contains_key(&1));
});
}
#[test]
fn malicious_recovery_fails() {
new_test_ext().execute_with(|| {
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
// Account 5 sets up a recovery configuration on their account
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends,
threshold,
delay_period
));
// Some time has passed, and account 1 wants to try and attack this account!
System::run_to_block::<AllPalletsWithSystem>(10);
// Using account 1, the malicious user begins the recovery process on account 5
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
// Off chain, the user **tricks** their friends and asks them to vouch for the recovery
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1));
// shame on you
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1));
// shame on you
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1));
// shame on you
// We met the threshold, lets try to recover the account...?
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::DelayPeriod
);
// Account 1 needs to wait...
System::run_to_block::<AllPalletsWithSystem>(19);
// One more block to wait!
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::DelayPeriod
);
// Account 5 checks their account every `delay_period` and notices the malicious attack!
// Account 5 can close the recovery process before account 1 can claim it
assert_ok!(Recovery::close_recovery(RuntimeOrigin::signed(5), 1));
// By doing so, account 5 has now claimed the deposit originally reserved by account 1
assert_eq!(Balances::total_balance(&1), 90);
// Thanks for the free money!
assert_eq!(Balances::total_balance(&5), 110);
// The recovery process has been closed, so account 1 can't make the claim
System::run_to_block::<AllPalletsWithSystem>(20);
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::NotStarted
);
// Account 5 can remove their recovery config and pick some better friends
assert_ok!(Recovery::remove_recovery(RuntimeOrigin::signed(5)));
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
vec![22, 33, 44],
threshold,
delay_period
));
});
}
#[test]
fn create_recovery_handles_basic_errors() {
new_test_ext().execute_with(|| {
// No friends
assert_noop!(
Recovery::create_recovery(RuntimeOrigin::signed(5), vec![], 1, 0),
Error::<Test>::NotEnoughFriends
);
// Zero threshold
assert_noop!(
Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2], 0, 0),
Error::<Test>::ZeroThreshold
);
// Threshold greater than friends length
assert_noop!(
Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 4, 0),
Error::<Test>::NotEnoughFriends
);
// Too many friends
assert_noop!(
Recovery::create_recovery(
RuntimeOrigin::signed(5),
vec![1; (MaxFriends::get() + 1) as usize],
1,
0
),
Error::<Test>::MaxFriends
);
// Unsorted friends
assert_noop!(
Recovery::create_recovery(RuntimeOrigin::signed(5), vec![3, 2, 4], 3, 0),
Error::<Test>::NotSorted
);
// Duplicate friends
assert_noop!(
Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 2, 4], 3, 0),
Error::<Test>::NotSorted
);
// Already configured
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10));
assert_noop!(
Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10),
Error::<Test>::AlreadyRecoverable
);
});
}
#[test]
fn create_recovery_works() {
new_test_ext().execute_with(|| {
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
// Account 5 sets up a recovery configuration on their account
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
// Deposit is taken, and scales with the number of friends they pick
// Base 10 + 1 per friends = 13 total reserved
assert_eq!(Balances::reserved_balance(5), 13);
// Recovery configuration is correctly stored
let recovery_config = RecoveryConfig {
delay_period,
deposit: 13,
friends: friends.try_into().unwrap(),
threshold,
};
assert_eq!(Recovery::recovery_config(5), Some(recovery_config));
});
}
#[test]
fn initiate_recovery_handles_basic_errors() {
new_test_ext().execute_with(|| {
// No recovery process set up for the account
assert_noop!(
Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::NotRecoverable
);
// Create a recovery process for next test
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
// Same user cannot recover same account twice
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
assert_noop!(
Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::AlreadyStarted
);
// No double deposit
assert_eq!(Balances::reserved_balance(1), 10);
});
}
#[test]
fn initiate_recovery_works() {
new_test_ext().execute_with(|| {
// Create a recovery process for the test
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
// Recovery can be initiated
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
// Deposit is reserved
assert_eq!(Balances::reserved_balance(1), 10);
// Recovery status object is created correctly
let recovery_status =
ActiveRecovery { created: 1, deposit: 10, friends: Default::default() };
assert_eq!(<ActiveRecoveries<Test>>::get(&5, &1), Some(recovery_status));
// Multiple users can attempt to recover the same account
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(2), 5));
});
}
#[test]
fn vouch_recovery_handles_basic_errors() {
new_test_ext().execute_with(|| {
// Cannot vouch for non-recoverable account
assert_noop!(
Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1),
Error::<Test>::NotRecoverable
);
// Create a recovery process for next tests
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
// Cannot vouch a recovery process that has not started
assert_noop!(
Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1),
Error::<Test>::NotStarted
);
// Initiate a recovery process
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
// Cannot vouch if you are not a friend
assert_noop!(
Recovery::vouch_recovery(RuntimeOrigin::signed(22), 5, 1),
Error::<Test>::NotFriend
);
// Cannot vouch twice
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1));
assert_noop!(
Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1),
Error::<Test>::AlreadyVouched
);
});
}
#[test]
fn vouch_recovery_works() {
new_test_ext().execute_with(|| {
// Create and initiate a recovery process for the test
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
// Vouching works
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1));
// Handles out of order vouches
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1));
// Final recovery status object is updated correctly
let recovery_status =
ActiveRecovery { created: 1, deposit: 10, friends: bounded_vec![2, 3, 4] };
assert_eq!(<ActiveRecoveries<Test>>::get(&5, &1), Some(recovery_status));
});
}
#[test]
fn claim_recovery_handles_basic_errors() {
new_test_ext().execute_with(|| {
// Cannot claim a non-recoverable account
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::NotRecoverable
);
// Create a recovery process for the test
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
// Cannot claim an account which has not started the recovery process
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::NotStarted
);
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
// Cannot claim an account which has not passed the delay period
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::DelayPeriod
);
System::run_to_block::<AllPalletsWithSystem>(11);
// Cannot claim an account which has not passed the threshold number of votes
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1));
// Only 2/3 is not good enough
assert_noop!(
Recovery::claim_recovery(RuntimeOrigin::signed(1), 5),
Error::<Test>::Threshold
);
});
}
#[test]
fn claim_recovery_works() {
new_test_ext().execute_with(|| {
// Create, initiate, and vouch recovery process for the test
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 1));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 1));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 1));
System::run_to_block::<AllPalletsWithSystem>(11);
// Account can be recovered.
assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(1), 5));
// Recovered storage item is correctly created
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(RuntimeOrigin::signed(4), 5));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(2), 5, 4));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(3), 5, 4));
assert_ok!(Recovery::vouch_recovery(RuntimeOrigin::signed(4), 5, 4));
System::run_to_block::<AllPalletsWithSystem>(21);
// Account is re-recovered.
assert_ok!(Recovery::claim_recovery(RuntimeOrigin::signed(4), 5));
// Recovered storage item is correctly updated
assert_eq!(<Proxy<Test>>::get(&4), Some(5));
});
}
#[test]
fn close_recovery_handles_basic_errors() {
new_test_ext().execute_with(|| {
// Cannot close a non-active recovery
assert_noop!(
Recovery::close_recovery(RuntimeOrigin::signed(5), 1),
Error::<Test>::NotStarted
);
});
}
#[test]
fn remove_recovery_works() {
new_test_ext().execute_with(|| {
// Cannot remove an unrecoverable account
assert_noop!(
Recovery::remove_recovery(RuntimeOrigin::signed(5)),
Error::<Test>::NotRecoverable
);
// Create and initiate a recovery process for the test
let friends = vec![2, 3, 4];
let threshold = 3;
let delay_period = 10;
assert_ok!(Recovery::create_recovery(
RuntimeOrigin::signed(5),
friends.clone(),
threshold,
delay_period
));
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(2), 5));
// Cannot remove a recovery when there are active recoveries.
assert_noop!(
Recovery::remove_recovery(RuntimeOrigin::signed(5)),
Error::<Test>::StillActive
);
assert_ok!(Recovery::close_recovery(RuntimeOrigin::signed(5), 1));
// Still need to remove one more!
assert_noop!(
Recovery::remove_recovery(RuntimeOrigin::signed(5)),
Error::<Test>::StillActive
);
assert_ok!(Recovery::close_recovery(RuntimeOrigin::signed(5), 2));
// Finally removed
assert_ok!(Recovery::remove_recovery(RuntimeOrigin::signed(5)));
});
}
#[test]
fn poke_deposit_handles_unsigned_origin() {
new_test_ext().execute_with(|| {
assert_noop!(Recovery::poke_deposit(RuntimeOrigin::none(), None), DispatchError::BadOrigin);
});
}
#[test]
fn poke_deposit_works_for_recovery_config_deposits() {
new_test_ext().execute_with(|| {
// Create initial recovery config
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10));
// Verify initial state
let old_deposit = Balances::reserved_balance(5);
// Base 10 + 1 per friend
assert_eq!(old_deposit, 13);
let config = Recovery::recovery_config(5).unwrap();
assert_eq!(config.deposit, old_deposit);
// Change ConfigDepositBase to trigger deposit update
ConfigDepositBase::set(20);
// Poke deposit should work and be free
let result = Recovery::poke_deposit(RuntimeOrigin::signed(5), None);
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::No.into());
// Verify final state
let new_deposit = Balances::reserved_balance(5);
// New base 20 + 1 per friend
assert_eq!(new_deposit, 23);
let updated_config = Recovery::recovery_config(5).unwrap();
assert_eq!(updated_config.deposit, new_deposit);
// Check event was emitted
System::assert_has_event(
Event::<Test>::DepositPoked {
who: 5,
kind: DepositKind::RecoveryConfig,
old_deposit,
new_deposit,
}
.into(),
);
});
}
#[test]
fn poke_deposit_works_for_active_recovery_deposits() {
new_test_ext().execute_with(|| {
// Setup recovery config
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10));
// Account 1 initiates recovery
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(1), 5));
// Verify initial state
let old_deposit = Balances::reserved_balance(1);
assert_eq!(old_deposit, 10);
let recovery = <ActiveRecoveries<Test>>::get(&5, &1).unwrap();
assert_eq!(recovery.deposit, old_deposit);
// Change RecoveryDeposit to trigger update
let new_deposit = 15;
RecoveryDeposit::set(new_deposit);
// Poke deposit should work and be free
let result = Recovery::poke_deposit(RuntimeOrigin::signed(1), Some(5));
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::No.into());
// Verify final state
assert_eq!(Balances::reserved_balance(1), new_deposit.into());
let updated_recovery = <ActiveRecoveries<Test>>::get(&5, &1).unwrap();
assert_eq!(updated_recovery.deposit, new_deposit.into());
assert_eq!(updated_recovery.friends, recovery.friends);
// Check event was emitted
System::assert_has_event(
Event::<Test>::DepositPoked {
who: 1,
kind: DepositKind::ActiveRecoveryFor(5),
old_deposit,
new_deposit: new_deposit.into(),
}
.into(),
);
});
}
#[test]
fn poke_deposit_works_for_both_deposits() {
new_test_ext().execute_with(|| {
// Setup recovery config for account 5
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10));
// Account 5 also initiates recovery for another account
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(1), vec![2, 3, 4], 3, 10));
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(5), 1));
// Verify initial storage state
let initial_config_deposit = 13;
let initial_recovery_deposit = 10;
assert_eq!(Recovery::recovery_config(5).unwrap().deposit, initial_config_deposit);
assert_eq!(
<ActiveRecoveries<Test>>::get(&1, &5).unwrap().deposit,
initial_recovery_deposit
);
assert_eq!(
Balances::reserved_balance(5),
initial_config_deposit + initial_recovery_deposit
);
// Change both deposit requirements
ConfigDepositBase::set(20);
RecoveryDeposit::set(15);
// Poke deposits
let result = Recovery::poke_deposit(RuntimeOrigin::signed(5), Some(1));
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::No.into());
// Verify storage and balances were updated
let new_config_deposit = 23;
let new_recovery_deposit = 15;
assert_eq!(Recovery::recovery_config(5).unwrap().deposit, new_config_deposit);
assert_eq!(<ActiveRecoveries<Test>>::get(&1, &5).unwrap().deposit, new_recovery_deposit);
assert_eq!(Balances::reserved_balance(5), new_config_deposit + new_recovery_deposit);
// Check both events were emitted
System::assert_has_event(
Event::<Test>::DepositPoked {
who: 5,
kind: DepositKind::RecoveryConfig,
old_deposit: initial_config_deposit,
new_deposit: new_config_deposit,
}
.into(),
);
System::assert_has_event(
Event::<Test>::DepositPoked {
who: 5,
kind: DepositKind::ActiveRecoveryFor(1),
old_deposit: initial_recovery_deposit,
new_deposit: new_recovery_deposit,
}
.into(),
);
});
}
#[test]
fn poke_deposit_charges_fee_for_no_deposits() {
new_test_ext().execute_with(|| {
let result = Recovery::poke_deposit(RuntimeOrigin::signed(1), None);
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::Yes.into());
// No events should be emitted
assert!(!System::events().iter().any(|record| matches!(
record.event,
RuntimeEvent::Recovery(Event::DepositPoked { .. })
)));
});
}
#[test]
fn poke_deposit_charges_fee_for_unchanged_deposits() {
new_test_ext().execute_with(|| {
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10));
// Verify initial state
let old_deposit = Balances::reserved_balance(5);
// Base 10 + 1 per friend
assert_eq!(old_deposit, 13);
let config = Recovery::recovery_config(5).unwrap();
assert_eq!(config.deposit, old_deposit);
let result = Recovery::poke_deposit(RuntimeOrigin::signed(5), None);
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::Yes.into());
// Verify final state
let new_deposit = Balances::reserved_balance(5);
// New base 20 + 1 per friend
assert_eq!(new_deposit, old_deposit);
let updated_config = Recovery::recovery_config(5).unwrap();
assert_eq!(updated_config.deposit, old_deposit);
// No events should be emitted
assert!(!System::events().iter().any(|record| matches!(
record.event,
RuntimeEvent::Recovery(Event::DepositPoked { .. })
)));
});
}
#[test]
fn poke_deposit_works_with_multiple_active_recoveries() {
new_test_ext().execute_with(|| {
// Setup multiple accounts with recovery
for i in 1..=3 {
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(i), vec![2, 3, 4], 3, 10));
}
// Account 5 initiates recovery for all of them
let old_deposit = 10;
for i in 1..=3 {
assert_ok!(Recovery::initiate_recovery(RuntimeOrigin::signed(5), i));
// Verify initial state for each recovery
let recovery = <ActiveRecoveries<Test>>::get(&i, &5).unwrap();
assert_eq!(recovery.deposit, old_deposit);
}
// Initial total reserved = 3 * old_deposit
let initial_total_reserved = old_deposit * 3;
assert_eq!(Balances::reserved_balance(5), initial_total_reserved);
// Change recovery deposit
let new_deposit = 15;
RecoveryDeposit::set(new_deposit);
// Poke deposits
let result = Recovery::poke_deposit(RuntimeOrigin::signed(5), Some(1));
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::No.into());
let result = Recovery::poke_deposit(RuntimeOrigin::signed(5), Some(2));
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::No.into());
let result = Recovery::poke_deposit(RuntimeOrigin::signed(5), Some(3));
assert_ok!(result.as_ref());
assert_eq!(result.unwrap(), Pays::No.into());
// Verify final state for each recovery
for i in 1..=3 {
let updated_recovery = <ActiveRecoveries<Test>>::get(&i, &5).unwrap();
assert_eq!(updated_recovery.deposit, new_deposit.into());
}
// All deposits should be updated
let new_total_reserved = new_deposit * 3;
assert_eq!(Balances::reserved_balance(5), new_total_reserved.into());
System::assert_has_event(
Event::<Test>::DepositPoked {
who: 5,
kind: DepositKind::ActiveRecoveryFor(1),
old_deposit,
new_deposit: new_deposit.into(),
}
.into(),
);
System::assert_has_event(
Event::<Test>::DepositPoked {
who: 5,
kind: DepositKind::ActiveRecoveryFor(2),
old_deposit,
new_deposit: new_deposit.into(),
}
.into(),
);
System::assert_has_event(
Event::<Test>::DepositPoked {
who: 5,
kind: DepositKind::ActiveRecoveryFor(3),
old_deposit,
new_deposit: new_deposit.into(),
}
.into(),
);
});
}
#[test]
fn poke_deposit_handles_insufficient_balance() {
new_test_ext().execute_with(|| {
// Setup recovery config
assert_ok!(Recovery::create_recovery(RuntimeOrigin::signed(5), vec![2, 3, 4], 3, 10));
assert_eq!(Balances::reserved_balance(5), 13);
// Increase required deposit
ConfigDepositBase::set(200);
// Should fail due to insufficient balance
assert_noop!(
Recovery::poke_deposit(RuntimeOrigin::signed(5), None),
pezpallet_balances::Error::<Test>::InsufficientBalance
);
// Original deposit should remain unchanged
assert_eq!(Balances::reserved_balance(5), 13);
});
}
+376
View File
@@ -0,0 +1,376 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Autogenerated weights for `pezpallet_recovery`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
//! DATE: 2025-04-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `25e4b24639fd`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// frame-omni-bencher
// v1
// benchmark
// pallet
// --extrinsic=*
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
// --pallet=pezpallet_recovery
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/recovery/src/weights.rs
// --wasm-execution=compiled
// --steps=50
// --repeat=20
// --heap-pages=4096
// --template=bizinikiwi/.maintain/frame-umbrella-weight-template.hbs
// --no-storage-info
// --no-min-squares
// --no-median-slopes
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use frame::weights_prelude::*;
/// Weight functions needed for `pezpallet_recovery`.
pub trait WeightInfo {
fn as_recovered() -> Weight;
fn set_recovered() -> Weight;
fn create_recovery(n: u32, ) -> Weight;
fn initiate_recovery() -> Weight;
fn vouch_recovery(n: u32, ) -> Weight;
fn claim_recovery(n: u32, ) -> Weight;
fn close_recovery(n: u32, ) -> Weight;
fn remove_recovery(n: u32, ) -> Weight;
fn cancel_recovered() -> Weight;
fn poke_deposit(n: u32, ) -> Weight;
}
/// Weights for `pezpallet_recovery` using the Bizinikiwi node and recommended hardware.
pub struct BizinikiwiWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
/// Storage: `Recovery::Proxy` (r:1 w:0)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `TxPause::PausedCalls` (r:1 w:0)
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
fn as_recovered() -> Weight {
// Proof Size summary in bytes:
// Measured: `530`
// Estimated: `3997`
// Minimum execution time: 18_724_000 picoseconds.
Weight::from_parts(19_503_000, 3997)
.saturating_add(T::DbWeight::get().reads(3_u64))
}
/// Storage: `Recovery::Proxy` (r:0 w:1)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn set_recovered() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 6_933_000 picoseconds.
Weight::from_parts(7_249_000, 0)
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:1)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn create_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `279`
// Estimated: `3816`
// Minimum execution time: 26_004_000 picoseconds.
Weight::from_parts(27_597_455, 3816)
// Standard Error: 7_992
.saturating_add(Weight::from_parts(165_970, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:0)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
fn initiate_recovery() -> Weight {
// Proof Size summary in bytes:
// Measured: `376`
// Estimated: `3854`
// Minimum execution time: 33_370_000 picoseconds.
Weight::from_parts(34_078_000, 3854)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:0)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn vouch_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `464 + n * (64 ±0)`
// Estimated: `3854`
// Minimum execution time: 21_199_000 picoseconds.
Weight::from_parts(22_295_139, 3854)
// Standard Error: 5_681
.saturating_add(Weight::from_parts(255_188, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:0)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:0)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// Storage: `Recovery::Proxy` (r:1 w:1)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn claim_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `496 + n * (64 ±0)`
// Estimated: `3854`
// Minimum execution time: 26_340_000 picoseconds.
Weight::from_parts(27_828_030, 3854)
// Standard Error: 7_254
.saturating_add(Weight::from_parts(113_751, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn close_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `617 + n * (32 ±0)`
// Estimated: `3854`
// Minimum execution time: 36_912_000 picoseconds.
Weight::from_parts(38_818_613, 3854)
// Standard Error: 7_806
.saturating_add(Weight::from_parts(149_710, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:0)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// Storage: `Recovery::Recoverable` (r:1 w:1)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn remove_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `374 + n * (32 ±0)`
// Estimated: `3854`
// Minimum execution time: 30_824_000 picoseconds.
Weight::from_parts(31_960_172, 3854)
// Standard Error: 10_777
.saturating_add(Weight::from_parts(237_226, 0).saturating_mul(n.into()))
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Proxy` (r:1 w:1)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn cancel_recovered() -> Weight {
// Proof Size summary in bytes:
// Measured: `385`
// Estimated: `3545`
// Minimum execution time: 15_369_000 picoseconds.
Weight::from_parts(15_730_000, 3545)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:1)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn poke_deposit(_n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `1110`
// Estimated: `3854`
// Minimum execution time: 46_517_000 picoseconds.
Weight::from_parts(49_326_481, 3854)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `Recovery::Proxy` (r:1 w:0)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `TxPause::PausedCalls` (r:1 w:0)
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
fn as_recovered() -> Weight {
// Proof Size summary in bytes:
// Measured: `530`
// Estimated: `3997`
// Minimum execution time: 18_724_000 picoseconds.
Weight::from_parts(19_503_000, 3997)
.saturating_add(RocksDbWeight::get().reads(3_u64))
}
/// Storage: `Recovery::Proxy` (r:0 w:1)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn set_recovered() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 6_933_000 picoseconds.
Weight::from_parts(7_249_000, 0)
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:1)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn create_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `279`
// Estimated: `3816`
// Minimum execution time: 26_004_000 picoseconds.
Weight::from_parts(27_597_455, 3816)
// Standard Error: 7_992
.saturating_add(Weight::from_parts(165_970, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:0)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
fn initiate_recovery() -> Weight {
// Proof Size summary in bytes:
// Measured: `376`
// Estimated: `3854`
// Minimum execution time: 33_370_000 picoseconds.
Weight::from_parts(34_078_000, 3854)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:0)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn vouch_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `464 + n * (64 ±0)`
// Estimated: `3854`
// Minimum execution time: 21_199_000 picoseconds.
Weight::from_parts(22_295_139, 3854)
// Standard Error: 5_681
.saturating_add(Weight::from_parts(255_188, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:0)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:0)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// Storage: `Recovery::Proxy` (r:1 w:1)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn claim_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `496 + n * (64 ±0)`
// Estimated: `3854`
// Minimum execution time: 26_340_000 picoseconds.
Weight::from_parts(27_828_030, 3854)
// Standard Error: 7_254
.saturating_add(Weight::from_parts(113_751, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn close_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `617 + n * (32 ±0)`
// Estimated: `3854`
// Minimum execution time: 36_912_000 picoseconds.
Weight::from_parts(38_818_613, 3854)
// Standard Error: 7_806
.saturating_add(Weight::from_parts(149_710, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:0)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// Storage: `Recovery::Recoverable` (r:1 w:1)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn remove_recovery(n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `374 + n * (32 ±0)`
// Estimated: `3854`
// Minimum execution time: 30_824_000 picoseconds.
Weight::from_parts(31_960_172, 3854)
// Standard Error: 10_777
.saturating_add(Weight::from_parts(237_226, 0).saturating_mul(n.into()))
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Proxy` (r:1 w:1)
/// Proof: `Recovery::Proxy` (`max_values`: None, `max_size`: Some(80), added: 2555, mode: `MaxEncodedLen`)
fn cancel_recovered() -> Weight {
// Proof Size summary in bytes:
// Measured: `385`
// Estimated: `3545`
// Minimum execution time: 15_369_000 picoseconds.
Weight::from_parts(15_730_000, 3545)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `Recovery::Recoverable` (r:1 w:1)
/// Proof: `Recovery::Recoverable` (`max_values`: None, `max_size`: Some(351), added: 2826, mode: `MaxEncodedLen`)
/// Storage: `Recovery::ActiveRecoveries` (r:1 w:1)
/// Proof: `Recovery::ActiveRecoveries` (`max_values`: None, `max_size`: Some(389), added: 2864, mode: `MaxEncodedLen`)
/// The range of component `n` is `[1, 9]`.
fn poke_deposit(_n: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `1110`
// Estimated: `3854`
// Minimum execution time: 46_517_000 picoseconds.
Weight::from_parts(49_326_481, 3854)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
}