pallet-vesting: Support multiple, merge-able vesting schedules (#9202)

* Support multiple, mergable vesting schedules

* Update node runtime

* Remove some TODO design questions and put them as commennts

* Update frame/vesting/src/benchmarking.rs

* Syntax and comment clean up

* Create filter enum for removing schedules

* Dry vesting calls with do_vest

* Improve old benchmarks to account for max schedules

* Update WeightInfo trait and make dummy fns

* Add merge_schedule weights

* Explicitly test multiple vesting scheudles

* Make new vesting tests more more clear

* Apply suggestions from code review

* Update remove_vesting_schedule to error with no index

* Try reduce spacing diff

* Apply suggestions from code review

* Use get on vesting for bounds check; check origin first

* No filter tuple; various simplifications

* unwrap or default when getting user schedules

* spaces be gone

* ReadMe fixes

* Update frame/vesting/src/lib.rs

Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>

* address some comments for docs

* merge sched docs

* Apply suggestions from code review

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* log error when trying to push to vesting vec

* use let Some, not is_some

* remove_vesting_schedule u32, not optin

* new not try_new, create validate builder; VestingInfo

* Merge prep: break out tests and mock

* Add files forgot to include in merge

* revert some accidental changes to merged files

* Revert remaining accidental file changes

* More revert of accidental file change

* Try to reduce diff on tests

* namespace Vesting; check key when key should not exist;

* ending_block throws error on per_block of 0

* Try improve merge vesting info comment

* Update frame/vesting/src/lib.rs

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* add validate + correct; handle duration > blocknumber

* Move vesting_info module to its own file

* Seperate Vesting/locks updates from writing

* Add can_add_vesting schedule

* Adjust min vested transfer to be greater than all ED

* Initial integrity test impl

* merge_finished_and_yet_to_be_started_schedules

* Make sure to assert storage items are cleaned up

* Migration initial impl (not tested)

* Correct try-runtime hooks

* Apply suggestions from code review

Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>

* header

* WIP: improve benchmarks

* Benchmarking working

* benchmarking: step over max schedules

* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_vesting --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/vesting/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* Simplify APIs by accepting vec; convert to bounded on write

* Test:  build_genesis_has_storage_version_v1

* Test more error cases

* Hack to get polkadot weights to work; should revert later

* Improve benchmarking; works on polkadot

* cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_vesting --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/vesting/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* WIP override storage

* Set storage not working example

* Remove unused tests

* VestingInfo: make public, derive MaxEndcodedLen

* Rename ending_block to ending_block_as_balance

* Superificial improvements

* Check for end block infinite, not just duration

* More superficial update

* Update tests

* Test vest with multi schedule

* Don't use half max balance in benchmarks

* Use debug_assert when locked is unexpected 0

* Implement exec_action

* Simplify per_block calc in vesting_info

* VestingInfo.validate in add_vesting_schedule & can_add_vesting_schedule

* Simplify post migrate check

* Remove merge event

* Minor benchmarking updates

* Remove VestingInfo.correct

* per_block accesor max with 1

* Improve comment

* Remoe debug

* Fix add schedule comment

* Apply suggestions from code review

Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>

* no ref for should_remove param

* Remove unused vestingaction derive

* Asserts to show balance unlock in merge benchmark

* Remove unused imports

* trivial

* Fix benchmark asserts to handle non-multiple of 20 locked

* Add generate_storage_info

* migration :facepalm

* Remove per_block 0 logic

* Update frame/vesting/src/lib.rs

* Do not check for ending later than greatest block

* Apply suggestions from code review

* Benchmarks: simplify vesting schedule creation

* Add log back for migration

* Add note in ext docs explaining that all schedules will vest

* Make integrity test work

* Improve integrity test

* Remove unnescary type param from VestingInfo::new

* Remove unnescary resut for ending_block_as_balance

* Remove T param from ending_block_as_balance

* Reduce visibility of raw_per_block

* Remove unused type param for validate

* update old comment

* Make log a dep; log warn in migrate

* VestingInfo.validate returns Err(()), no T type param

* Try improve report_schedule_updates

* is_valid, not validate

* revert node runtime reorg;

* change schedule validity check to just warning

* Simplify merge_vesting_info return type

* Apply suggestions from code review

* Apply suggestions from code review

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* Add warning for migration

* Fix indentation

* Delete duplicate warnings

* Reduce diff in node runtime

* Fix benchmark build

* Upgrade cargo.toml to use 4.0.0-dev

* Cleanup

* MaxVestingSchedulesGetter initial impl

* MinVestedTransfer getter inintial impl

* Test MaxVestingSchedules & MinVestedTransfer getters; use getters in benchmarks

* Run cargo fmt

* Revert MinVestedTransfer & MaxVestingSchedules getters; Add integrity test

* Make MAX_VESTING_SCHEDULES a const

* fmt

* WIP: benchmark improvements

* Finish benchmark update

* Add test for transfer to account with less than ed

* Rm min_new_account_transfer; move sp-io to dev-dep

* Reduce cargo.toml diff

* Explain MAX_VESTING_SCHEDULES choice

* Fix after merge

* Try fix CI complaints

* cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_vesting --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/vesting/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* cargo run --quiet --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_vesting --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/vesting/src/weights.rs --template=./.maintain/frame-weight-template.hbs

* fmt

* trigger

* fmt

Co-authored-by: Parity Bot <admin@parity.io>
Co-authored-by: Peter Goodspeed-Niklaus <coriolinus@users.noreply.github.com>
Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: kianenigma <kian@parity.io>
This commit is contained in:
Zeke Mostov
2021-08-23 17:15:27 -07:00
committed by GitHub
parent fb408e3e85
commit ede36408a9
11 changed files with 1984 additions and 357 deletions
+1
View File
@@ -5898,6 +5898,7 @@ dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"log 0.4.14",
"pallet-balances",
"parity-scale-codec",
"sp-core",
+3
View File
@@ -1062,6 +1062,9 @@ impl pallet_vesting::Config for Runtime {
type BlockNumberToBalance = ConvertInto;
type MinVestedTransfer = MinVestedTransfer;
type WeightInfo = pallet_vesting::weights::SubstrateWeight<Runtime>;
// `VestingInfo` encode length is 36bytes. 28 schedules gets encoded as 1009 bytes, which is the
// highest number of schedules that encodes less than 2^10.
const MAX_VESTING_SCHEDULES: u32 = 28;
}
impl pallet_mmr::Config for Runtime {
@@ -80,8 +80,8 @@ pub trait VestingSchedule<AccountId> {
/// Adds a vesting schedule to a given account.
///
/// If there already exists a vesting schedule for the given account, an `Err` is returned
/// and nothing is updated.
/// If the account has `MaxVestingSchedules`, an Error is returned and nothing
/// is updated.
///
/// Is a no-op if the amount to be vested is zero.
///
@@ -93,8 +93,16 @@ pub trait VestingSchedule<AccountId> {
starting_block: Self::Moment,
) -> DispatchResult;
/// Checks if `add_vesting_schedule` would work against `who`.
fn can_add_vesting_schedule(
who: &AccountId,
locked: <Self::Currency as Currency<AccountId>>::Balance,
per_block: <Self::Currency as Currency<AccountId>>::Balance,
starting_block: Self::Moment,
) -> DispatchResult;
/// Remove a vesting schedule for a given account.
///
/// NOTE: This doesn't alter the free balance of the account.
fn remove_vesting_schedule(who: &AccountId);
fn remove_vesting_schedule(who: &AccountId, schedule_index: u32) -> DispatchResult;
}
+2 -1
View File
@@ -21,9 +21,10 @@ sp-runtime = { version = "4.0.0-dev", default-features = false, path = "../../pr
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true }
log = { version = "0.4.0", default-features = false }
[dev-dependencies]
sp-io = { version = "4.0.0-dev", path = "../../primitives/io" }
sp-io = { version = "4.0.0-dev", default-features = false, path = "../../primitives/io" }
sp-core = { version = "4.0.0-dev", path = "../../primitives/core" }
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
+210 -59
View File
@@ -19,12 +19,12 @@
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
use frame_support::assert_ok;
use frame_system::{Pallet as System, RawOrigin};
use sp_runtime::traits::Bounded;
use sp_runtime::traits::{Bounded, CheckedDiv, CheckedMul};
use super::*;
use crate::Pallet as Vesting;
const SEED: u32 = 0;
@@ -35,42 +35,63 @@ type BalanceOf<T> =
fn add_locks<T: Config>(who: &T::AccountId, n: u8) {
for id in 0..n {
let lock_id = [id; 8];
let locked = 100u32;
let locked = 256u32;
let reasons = WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE;
T::Currency::set_lock(lock_id, who, locked.into(), reasons);
}
}
fn add_vesting_schedule<T: Config>(who: &T::AccountId) -> Result<(), &'static str> {
let locked = 100u32;
let per_block = 10u32;
fn add_vesting_schedules<T: Config>(
target: <T::Lookup as StaticLookup>::Source,
n: u32,
) -> Result<BalanceOf<T>, &'static str> {
let min_transfer = T::MinVestedTransfer::get();
let locked = min_transfer.checked_mul(&20u32.into()).unwrap();
// Schedule has a duration of 20.
let per_block = min_transfer;
let starting_block = 1u32;
System::<T>::set_block_number(0u32.into());
let source: T::AccountId = account("source", 0, SEED);
let source_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(source.clone());
T::Currency::make_free_balance_be(&source, BalanceOf::<T>::max_value());
// Add schedule to avoid `NotVesting` error.
Vesting::<T>::add_vesting_schedule(
&who,
locked.into(),
per_block.into(),
starting_block.into(),
)?;
Ok(())
System::<T>::set_block_number(T::BlockNumber::zero());
let mut total_locked: BalanceOf<T> = Zero::zero();
for _ in 0..n {
total_locked += locked;
let schedule = VestingInfo::new(locked, per_block, starting_block.into());
assert_ok!(Vesting::<T>::do_vested_transfer(
source_lookup.clone(),
target.clone(),
schedule
));
// Top up to guarantee we can always transfer another schedule.
T::Currency::make_free_balance_be(&source, BalanceOf::<T>::max_value());
}
Ok(total_locked.into())
}
benchmarks! {
vest_locked {
let l in 0 .. MaxLocksOf::<T>::get();
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 1 .. T::MAX_VESTING_SCHEDULES;
let caller: T::AccountId = whitelisted_caller();
let caller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(caller.clone());
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance());
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
add_locks::<T>(&caller, l as u8);
add_vesting_schedule::<T>(&caller)?;
let expected_balance = add_vesting_schedules::<T>(caller_lookup, s)?;
// At block zero, everything is vested.
System::<T>::set_block_number(T::BlockNumber::zero());
assert_eq!(System::<T>::block_number(), T::BlockNumber::zero());
assert_eq!(
Vesting::<T>::vesting_balance(&caller),
Some(100u32.into()),
Some(expected_balance.into()),
"Vesting schedule not added",
);
}: vest(RawOrigin::Signed(caller.clone()))
@@ -78,20 +99,24 @@ benchmarks! {
// Nothing happened since everything is still vested.
assert_eq!(
Vesting::<T>::vesting_balance(&caller),
Some(100u32.into()),
Some(expected_balance.into()),
"Vesting schedule was removed",
);
}
vest_unlocked {
let l in 0 .. MaxLocksOf::<T>::get();
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 1 .. T::MAX_VESTING_SCHEDULES;
let caller: T::AccountId = whitelisted_caller();
let caller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(caller.clone());
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance());
let caller = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
add_locks::<T>(&caller, l as u8);
add_vesting_schedule::<T>(&caller)?;
// At block 20, everything is unvested.
System::<T>::set_block_number(20u32.into());
add_vesting_schedules::<T>(caller_lookup, s)?;
// At block 21, everything is unlocked.
System::<T>::set_block_number(21u32.into());
assert_eq!(
Vesting::<T>::vesting_balance(&caller),
Some(BalanceOf::<T>::zero()),
@@ -108,18 +133,20 @@ benchmarks! {
}
vest_other_locked {
let l in 0 .. MaxLocksOf::<T>::get();
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 1 .. T::MAX_VESTING_SCHEDULES;
let other: T::AccountId = account("other", 0, SEED);
let other_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(other.clone());
T::Currency::make_free_balance_be(&other, BalanceOf::<T>::max_value());
add_locks::<T>(&other, l as u8);
add_vesting_schedule::<T>(&other)?;
let expected_balance = add_vesting_schedules::<T>(other_lookup.clone(), s)?;
// At block zero, everything is vested.
System::<T>::set_block_number(T::BlockNumber::zero());
assert_eq!(System::<T>::block_number(), T::BlockNumber::zero());
assert_eq!(
Vesting::<T>::vesting_balance(&other),
Some(100u32.into()),
Some(expected_balance),
"Vesting schedule not added",
);
@@ -129,21 +156,23 @@ benchmarks! {
// Nothing happened since everything is still vested.
assert_eq!(
Vesting::<T>::vesting_balance(&other),
Some(100u32.into()),
Some(expected_balance.into()),
"Vesting schedule was removed",
);
}
vest_other_unlocked {
let l in 0 .. MaxLocksOf::<T>::get();
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 1 .. T::MAX_VESTING_SCHEDULES;
let other: T::AccountId = account("other", 0, SEED);
let other_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(other.clone());
T::Currency::make_free_balance_be(&other, BalanceOf::<T>::max_value());
add_locks::<T>(&other, l as u8);
add_vesting_schedule::<T>(&other)?;
// At block 20, everything is unvested.
System::<T>::set_block_number(20u32.into());
add_vesting_schedules::<T>(other_lookup.clone(), s)?;
// At block 21 everything is unlocked.
System::<T>::set_block_number(21u32.into());
assert_eq!(
Vesting::<T>::vesting_balance(&other),
Some(BalanceOf::<T>::zero()),
@@ -153,7 +182,7 @@ benchmarks! {
let caller: T::AccountId = whitelisted_caller();
}: vest_other(RawOrigin::Signed(caller.clone()), other_lookup)
verify {
// Vesting schedule is removed!
// Vesting schedule is removed.
assert_eq!(
Vesting::<T>::vesting_balance(&other),
None,
@@ -162,65 +191,187 @@ benchmarks! {
}
vested_transfer {
let l in 0 .. MaxLocksOf::<T>::get();
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 0 .. T::MAX_VESTING_SCHEDULES - 1;
let caller: T::AccountId = whitelisted_caller();
T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
let target: T::AccountId = account("target", 0, SEED);
let target_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(target.clone());
// Give target existing locks
add_locks::<T>(&target, l as u8);
// Add one vesting schedules.
let mut expected_balance = add_vesting_schedules::<T>(target_lookup.clone(), s)?;
let transfer_amount = T::MinVestedTransfer::get();
let per_block = transfer_amount.checked_div(&20u32.into()).unwrap();
expected_balance += transfer_amount;
let vesting_schedule = VestingInfo {
locked: transfer_amount,
per_block: 10u32.into(),
starting_block: 1u32.into(),
};
let vesting_schedule = VestingInfo::new(
transfer_amount,
per_block,
1u32.into(),
);
}: _(RawOrigin::Signed(caller), target_lookup, vesting_schedule)
verify {
assert_eq!(
T::MinVestedTransfer::get(),
expected_balance,
T::Currency::free_balance(&target),
"Transfer didn't happen",
);
assert_eq!(
Vesting::<T>::vesting_balance(&target),
Some(T::MinVestedTransfer::get()),
"Lock not created",
Some(expected_balance),
"Lock not correctly updated",
);
}
force_vested_transfer {
let l in 0 .. MaxLocksOf::<T>::get();
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 0 .. T::MAX_VESTING_SCHEDULES - 1;
let source: T::AccountId = account("source", 0, SEED);
let source_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(source.clone());
T::Currency::make_free_balance_be(&source, BalanceOf::<T>::max_value());
let target: T::AccountId = account("target", 0, SEED);
let target_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(target.clone());
// Give target existing locks
add_locks::<T>(&target, l as u8);
// Add one less than max vesting schedules
let mut expected_balance = add_vesting_schedules::<T>(target_lookup.clone(), s)?;
let transfer_amount = T::MinVestedTransfer::get();
let per_block = transfer_amount.checked_div(&20u32.into()).unwrap();
expected_balance += transfer_amount;
let vesting_schedule = VestingInfo {
locked: transfer_amount,
per_block: 10u32.into(),
starting_block: 1u32.into(),
};
let vesting_schedule = VestingInfo::new(
transfer_amount,
per_block,
1u32.into(),
);
}: _(RawOrigin::Root, source_lookup, target_lookup, vesting_schedule)
verify {
assert_eq!(
T::MinVestedTransfer::get(),
expected_balance,
T::Currency::free_balance(&target),
"Transfer didn't happen",
);
assert_eq!(
Vesting::<T>::vesting_balance(&target),
Some(T::MinVestedTransfer::get()),
"Lock not created",
Some(expected_balance.into()),
"Lock not correctly updated",
);
}
not_unlocking_merge_schedules {
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 2 .. T::MAX_VESTING_SCHEDULES;
let caller: T::AccountId = account("caller", 0, SEED);
let caller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(caller.clone());
// Give target existing locks.
add_locks::<T>(&caller, l as u8);
// Add max vesting schedules.
let expected_balance = add_vesting_schedules::<T>(caller_lookup.clone(), s)?;
// Schedules are not vesting at block 0.
assert_eq!(System::<T>::block_number(), T::BlockNumber::zero());
assert_eq!(
Vesting::<T>::vesting_balance(&caller),
Some(expected_balance),
"Vesting balance should equal sum locked of all schedules",
);
assert_eq!(
Vesting::<T>::vesting(&caller).unwrap().len(),
s as usize,
"There should be exactly max vesting schedules"
);
}: merge_schedules(RawOrigin::Signed(caller.clone()), 0, s - 1)
verify {
let expected_schedule = VestingInfo::new(
T::MinVestedTransfer::get() * 20u32.into() * 2u32.into(),
T::MinVestedTransfer::get() * 2u32.into(),
1u32.into(),
);
let expected_index = (s - 2) as usize;
assert_eq!(
Vesting::<T>::vesting(&caller).unwrap()[expected_index],
expected_schedule
);
assert_eq!(
Vesting::<T>::vesting_balance(&caller),
Some(expected_balance),
"Vesting balance should equal total locked of all schedules",
);
assert_eq!(
Vesting::<T>::vesting(&caller).unwrap().len(),
(s - 1) as usize,
"Schedule count should reduce by 1"
);
}
unlocking_merge_schedules {
let l in 0 .. MaxLocksOf::<T>::get() - 1;
let s in 2 .. T::MAX_VESTING_SCHEDULES;
// Destination used just for currency transfers in asserts.
let test_dest: T::AccountId = account("test_dest", 0, SEED);
let caller: T::AccountId = account("caller", 0, SEED);
let caller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(caller.clone());
// Give target other locks.
add_locks::<T>(&caller, l as u8);
// Add max vesting schedules.
let total_transferred = add_vesting_schedules::<T>(caller_lookup.clone(), s)?;
// Go to about half way through all the schedules duration. (They all start at 1, and have a duration of 20 or 21).
System::<T>::set_block_number(11u32.into());
// We expect half the original locked balance (+ any remainder that vests on the last block).
let expected_balance = total_transferred / 2u32.into();
assert_eq!(
Vesting::<T>::vesting_balance(&caller),
Some(expected_balance),
"Vesting balance should reflect that we are half way through all schedules duration",
);
assert_eq!(
Vesting::<T>::vesting(&caller).unwrap().len(),
s as usize,
"There should be exactly max vesting schedules"
);
// The balance is not actually transferable because it has not been unlocked.
assert!(T::Currency::transfer(&caller, &test_dest, expected_balance, ExistenceRequirement::AllowDeath).is_err());
}: merge_schedules(RawOrigin::Signed(caller.clone()), 0, s - 1)
verify {
let expected_schedule = VestingInfo::new(
T::MinVestedTransfer::get() * 2u32.into() * 10u32.into(),
T::MinVestedTransfer::get() * 2u32.into(),
11u32.into(),
);
let expected_index = (s - 2) as usize;
assert_eq!(
Vesting::<T>::vesting(&caller).unwrap()[expected_index],
expected_schedule,
"New schedule is properly created and placed"
);
assert_eq!(
Vesting::<T>::vesting(&caller).unwrap()[expected_index],
expected_schedule
);
assert_eq!(
Vesting::<T>::vesting_balance(&caller),
Some(expected_balance),
"Vesting balance should equal half total locked of all schedules",
);
assert_eq!(
Vesting::<T>::vesting(&caller).unwrap().len(),
(s - 1) as usize,
"Schedule count should reduce by 1"
);
// Since merge unlocks all schedules we can now transfer the balance.
assert_ok!(
T::Currency::transfer(&caller, &test_dest, expected_balance, ExistenceRequirement::AllowDeath)
);
}
}
+460 -117
View File
@@ -45,14 +45,16 @@
#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
mod migrations;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
mod vesting_info;
pub mod weights;
use codec::{Decode, Encode};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
ensure,
pallet_prelude::*,
@@ -64,10 +66,14 @@ use frame_support::{
use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
pub use pallet::*;
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, StaticLookup, Zero},
traits::{
AtLeast32BitUnsigned, Bounded, Convert, MaybeSerializeDeserialize, One, Saturating,
StaticLookup, Zero,
},
RuntimeDebug,
};
use sp_std::{fmt::Debug, prelude::*};
use sp_std::{convert::TryInto, fmt::Debug, prelude::*};
pub use vesting_info::*;
pub use weights::WeightInfo;
type BalanceOf<T> =
@@ -77,37 +83,62 @@ type MaxLocksOf<T> =
const VESTING_ID: LockIdentifier = *b"vesting ";
/// Struct to encode the vesting schedule of an individual account.
#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug)]
pub struct VestingInfo<Balance, BlockNumber> {
/// Locked amount at genesis.
pub locked: Balance,
/// Amount that gets unlocked every block after `starting_block`.
pub per_block: Balance,
/// Starting block for unlocking(vesting).
pub starting_block: BlockNumber,
// A value placed in storage that represents the current version of the Vesting storage.
// This value is used by `on_runtime_upgrade` to determine whether we run storage migration logic.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen)]
enum Releases {
V0,
V1,
}
impl<Balance: AtLeast32BitUnsigned + Copy, BlockNumber: AtLeast32BitUnsigned + Copy>
VestingInfo<Balance, BlockNumber>
{
/// Amount locked at block `n`.
pub fn locked_at<BlockNumberToBalance: Convert<BlockNumber, Balance>>(
&self,
n: BlockNumber,
) -> Balance {
// Number of blocks that count toward vesting
// Saturating to 0 when n < starting_block
let vested_block_count = n.saturating_sub(self.starting_block);
let vested_block_count = BlockNumberToBalance::convert(vested_block_count);
// Return amount that is still locked in vesting
let maybe_balance = vested_block_count.checked_mul(&self.per_block);
if let Some(balance) = maybe_balance {
self.locked.saturating_sub(balance)
} else {
Zero::zero()
impl Default for Releases {
fn default() -> Self {
Releases::V0
}
}
/// Actions to take against a user's `Vesting` storage entry.
#[derive(Clone, Copy)]
enum VestingAction {
/// Do not actively remove any schedules.
Passive,
/// Remove the schedule specified by the index.
Remove(usize),
/// Remove the two schedules, specified by index, so they can be merged.
Merge(usize, usize),
}
impl VestingAction {
/// Whether or not the filter says the schedule index should be removed.
fn should_remove(&self, index: usize) -> bool {
match self {
Self::Passive => false,
Self::Remove(index1) => *index1 == index,
Self::Merge(index1, index2) => *index1 == index || *index2 == index,
}
}
/// Pick the schedules that this action dictates should continue vesting undisturbed.
fn pick_schedules<'a, T: Config>(
&'a self,
schedules: Vec<VestingInfo<BalanceOf<T>, T::BlockNumber>>,
) -> impl Iterator<Item = VestingInfo<BalanceOf<T>, T::BlockNumber>> + 'a {
schedules.into_iter().enumerate().filter_map(move |(index, schedule)| {
if self.should_remove(index) {
None
} else {
Some(schedule)
}
})
}
}
// Wrapper for `T::MAX_VESTING_SCHEDULES` to satisfy `trait Get`.
pub struct MaxVestingSchedulesGet<T>(PhantomData<T>);
impl<T: Config> Get<u32> for MaxVestingSchedulesGet<T> {
fn get() -> u32 {
T::MAX_VESTING_SCHEDULES
}
}
#[frame_support::pallet]
@@ -131,16 +162,65 @@ pub mod pallet {
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
/// Maximum number of vesting schedules an account may have at a given moment.
const MAX_VESTING_SCHEDULES: u32;
}
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
// TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed.
#[allow(non_snake_case)]
fn MaxVestingSchedules() -> u32 {
T::MAX_VESTING_SCHEDULES
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<(), &'static str> {
migrations::v1::pre_migrate::<T>()
}
fn on_runtime_upgrade() -> Weight {
if StorageVersion::<T>::get() == Releases::V0 {
StorageVersion::<T>::put(Releases::V1);
migrations::v1::migrate::<T>().saturating_add(T::DbWeight::get().reads_writes(1, 1))
} else {
T::DbWeight::get().reads(1)
}
}
#[cfg(feature = "try-runtime")]
fn post_upgrade() -> Result<(), &'static str> {
migrations::v1::post_migrate::<T>()
}
fn integrity_test() {
assert!(T::MAX_VESTING_SCHEDULES > 0, "`MaxVestingSchedules` must ge greater than 0");
}
}
/// Information regarding the vesting of a given account.
#[pallet::storage]
#[pallet::getter(fn vesting)]
pub type Vesting<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, VestingInfo<BalanceOf<T>, T::BlockNumber>>;
pub type Vesting<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<VestingInfo<BalanceOf<T>, T::BlockNumber>, MaxVestingSchedulesGet<T>>,
>;
/// Storage version of the pallet.
///
/// New networks start with latest version, as determined by the genesis build.
#[pallet::storage]
pub(crate) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::generate_storage_info]
pub struct Pallet<T>(_);
#[pallet::genesis_config]
@@ -160,6 +240,9 @@ pub mod pallet {
fn build(&self) {
use sp_runtime::traits::Saturating;
// Genesis uses the latest storage version.
StorageVersion::<T>::put(Releases::V1);
// Generate initial vesting configuration
// * who - Account which we are generating vesting configuration for
// * begin - Block when the account will start to vest
@@ -172,8 +255,14 @@ pub mod pallet {
let locked = balance.saturating_sub(liquid);
let length_as_balance = T::BlockNumberToBalance::convert(length);
let per_block = locked / length_as_balance.max(sp_runtime::traits::One::one());
let vesting_info = VestingInfo::new(locked, per_block, begin);
if !vesting_info.is_valid() {
panic!("Invalid VestingInfo params at genesis")
};
Vesting::<T>::try_append(who, vesting_info)
.expect("Too many vesting schedules at genesis.");
Vesting::<T>::insert(who, VestingInfo { locked, per_block, starting_block: begin });
let reasons = WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE;
T::Currency::set_lock(VESTING_ID, who, locked, reasons);
}
@@ -182,13 +271,15 @@ pub mod pallet {
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
#[pallet::metadata(T::AccountId = "AccountId", BalanceOf<T> = "Balance")]
#[pallet::metadata(
T::AccountId = "AccountId", BalanceOf<T> = "Balance", T::BlockNumber = "BlockNumber"
)]
pub enum Event<T: Config> {
/// The amount vested has been updated. This could indicate more funds are available. The
/// balance given is the amount which is left unvested (and thus locked).
/// The amount vested has been updated. This could indicate a change in funds available.
/// The balance given is the amount which is left unvested (and thus locked).
/// \[account, unvested\]
VestingUpdated(T::AccountId, BalanceOf<T>),
/// An \[account\] has become fully vested. No further vesting can happen.
/// An \[account\] has become fully vested.
VestingCompleted(T::AccountId),
}
@@ -197,10 +288,15 @@ pub mod pallet {
pub enum Error<T> {
/// The account given is not vesting.
NotVesting,
/// An existing vesting schedule already exists for this account that cannot be clobbered.
ExistingVestingSchedule,
/// The account already has `MaxVestingSchedules` count of schedules and thus
/// cannot add another one. Consider merging existing schedules in order to add another.
AtMaxVestingSchedules,
/// Amount being transferred is too low to create a vesting schedule.
AmountLow,
/// An index was out of bounds of the vesting schedules.
ScheduleIndexOutOfBounds,
/// Failed to create a new schedule because some parameter was invalid.
InvalidScheduleParams,
}
#[pallet::call]
@@ -218,12 +314,12 @@ pub mod pallet {
/// - Reads: Vesting Storage, Balances Locks, [Sender Account]
/// - Writes: Vesting Storage, Balances Locks, [Sender Account]
/// # </weight>
#[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::<T>::get())
.max(T::WeightInfo::vest_unlocked(MaxLocksOf::<T>::get()))
#[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
.max(T::WeightInfo::vest_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
)]
pub fn vest(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
Self::update_lock(who)
Self::do_vest(who)
}
/// Unlock any vested funds of a `target` account.
@@ -241,61 +337,46 @@ pub mod pallet {
/// - Reads: Vesting Storage, Balances Locks, Target Account
/// - Writes: Vesting Storage, Balances Locks, Target Account
/// # </weight>
#[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::<T>::get())
.max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::<T>::get()))
#[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
.max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
)]
pub fn vest_other(
origin: OriginFor<T>,
target: <T::Lookup as StaticLookup>::Source,
) -> DispatchResult {
ensure_signed(origin)?;
Self::update_lock(T::Lookup::lookup(target)?)
let who = T::Lookup::lookup(target)?;
Self::do_vest(who)
}
/// Create a vested transfer.
///
/// The dispatch origin for this call must be _Signed_.
///
/// - `target`: The account that should be transferred the vested funds.
/// - `amount`: The amount of funds to transfer and will be vested.
/// - `target`: The account receiving the vested funds.
/// - `schedule`: The vesting schedule attached to the transfer.
///
/// Emits `VestingCreated`.
///
/// NOTE: This will unlock all schedules through the current block.
///
/// # <weight>
/// - `O(1)`.
/// - DbWeight: 3 Reads, 3 Writes
/// - Reads: Vesting Storage, Balances Locks, Target Account, [Sender Account]
/// - Writes: Vesting Storage, Balances Locks, Target Account, [Sender Account]
/// # </weight>
#[pallet::weight(T::WeightInfo::vested_transfer(MaxLocksOf::<T>::get()))]
#[pallet::weight(
T::WeightInfo::vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
)]
pub fn vested_transfer(
origin: OriginFor<T>,
target: <T::Lookup as StaticLookup>::Source,
schedule: VestingInfo<BalanceOf<T>, T::BlockNumber>,
) -> DispatchResult {
let transactor = ensure_signed(origin)?;
ensure!(schedule.locked >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
let who = T::Lookup::lookup(target)?;
ensure!(!Vesting::<T>::contains_key(&who), Error::<T>::ExistingVestingSchedule);
T::Currency::transfer(
&transactor,
&who,
schedule.locked,
ExistenceRequirement::AllowDeath,
)?;
Self::add_vesting_schedule(
&who,
schedule.locked,
schedule.per_block,
schedule.starting_block,
)
.expect("user does not have an existing vesting schedule; q.e.d.");
Ok(())
let transactor = <T::Lookup as StaticLookup>::unlookup(transactor);
Self::do_vested_transfer(transactor, target, schedule)
}
/// Force a vested transfer.
@@ -304,18 +385,21 @@ pub mod pallet {
///
/// - `source`: The account whose funds should be transferred.
/// - `target`: The account that should be transferred the vested funds.
/// - `amount`: The amount of funds to transfer and will be vested.
/// - `schedule`: The vesting schedule attached to the transfer.
///
/// Emits `VestingCreated`.
///
/// NOTE: This will unlock all schedules through the current block.
///
/// # <weight>
/// - `O(1)`.
/// - DbWeight: 4 Reads, 4 Writes
/// - Reads: Vesting Storage, Balances Locks, Target Account, Source Account
/// - Writes: Vesting Storage, Balances Locks, Target Account, Source Account
/// # </weight>
#[pallet::weight(T::WeightInfo::force_vested_transfer(MaxLocksOf::<T>::get()))]
#[pallet::weight(
T::WeightInfo::force_vested_transfer(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
)]
pub fn force_vested_transfer(
origin: OriginFor<T>,
source: <T::Lookup as StaticLookup>::Source,
@@ -323,26 +407,53 @@ pub mod pallet {
schedule: VestingInfo<BalanceOf<T>, T::BlockNumber>,
) -> DispatchResult {
ensure_root(origin)?;
ensure!(schedule.locked >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
Self::do_vested_transfer(source, target, schedule)
}
let target = T::Lookup::lookup(target)?;
let source = T::Lookup::lookup(source)?;
ensure!(!Vesting::<T>::contains_key(&target), Error::<T>::ExistingVestingSchedule);
/// Merge two vesting schedules together, creating a new vesting schedule that unlocks over
/// the highest possible start and end blocks. If both schedules have already started the
/// current block will be used as the schedule start; with the caveat that if one schedule
/// is finished by the current block, the other will be treated as the new merged schedule,
/// unmodified.
///
/// NOTE: If `schedule1_index == schedule2_index` this is a no-op.
/// NOTE: This will unlock all schedules through the current block prior to merging.
/// NOTE: If both schedules have ended by the current block, no new schedule will be created
/// and both will be removed.
///
/// Merged schedule attributes:
/// - `starting_block`: `MAX(schedule1.starting_block, scheduled2.starting_block,
/// current_block)`.
/// - `ending_block`: `MAX(schedule1.ending_block, schedule2.ending_block)`.
/// - `locked`: `schedule1.locked_at(current_block) + schedule2.locked_at(current_block)`.
///
/// The dispatch origin for this call must be _Signed_.
///
/// - `schedule1_index`: index of the first schedule to merge.
/// - `schedule2_index`: index of the second schedule to merge.
#[pallet::weight(
T::WeightInfo::not_unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES)
.max(T::WeightInfo::unlocking_merge_schedules(MaxLocksOf::<T>::get(), T::MAX_VESTING_SCHEDULES))
)]
pub fn merge_schedules(
origin: OriginFor<T>,
schedule1_index: u32,
schedule2_index: u32,
) -> DispatchResult {
let who = ensure_signed(origin)?;
if schedule1_index == schedule2_index {
return Ok(())
};
let schedule1_index = schedule1_index as usize;
let schedule2_index = schedule2_index as usize;
T::Currency::transfer(
&source,
&target,
schedule.locked,
ExistenceRequirement::AllowDeath,
)?;
let schedules = Self::vesting(&who).ok_or(Error::<T>::NotVesting)?;
let merge_action = VestingAction::Merge(schedule1_index, schedule2_index);
Self::add_vesting_schedule(
&target,
schedule.locked,
schedule.per_block,
schedule.starting_block,
)
.expect("user does not have an existing vesting schedule; q.e.d.");
let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), merge_action)?;
Self::write_vesting(&who, schedules)?;
Self::write_lock(&who, locked_now);
Ok(())
}
@@ -350,39 +461,233 @@ pub mod pallet {
}
impl<T: Config> Pallet<T> {
/// (Re)set or remove the pallet's currency lock on `who`'s account in accordance with their
/// current unvested amount.
fn update_lock(who: T::AccountId) -> DispatchResult {
let vesting = Self::vesting(&who).ok_or(Error::<T>::NotVesting)?;
let now = <frame_system::Pallet<T>>::block_number();
let locked_now = vesting.locked_at::<T::BlockNumberToBalance>(now);
// Create a new `VestingInfo`, based off of two other `VestingInfo`s.
// NOTE: We assume both schedules have had funds unlocked up through the current block.
fn merge_vesting_info(
now: T::BlockNumber,
schedule1: VestingInfo<BalanceOf<T>, T::BlockNumber>,
schedule2: VestingInfo<BalanceOf<T>, T::BlockNumber>,
) -> Option<VestingInfo<BalanceOf<T>, T::BlockNumber>> {
let schedule1_ending_block = schedule1.ending_block_as_balance::<T::BlockNumberToBalance>();
let schedule2_ending_block = schedule2.ending_block_as_balance::<T::BlockNumberToBalance>();
let now_as_balance = T::BlockNumberToBalance::convert(now);
if locked_now.is_zero() {
T::Currency::remove_lock(VESTING_ID, &who);
Vesting::<T>::remove(&who);
Self::deposit_event(Event::<T>::VestingCompleted(who));
// Check if one or both schedules have ended.
match (schedule1_ending_block <= now_as_balance, schedule2_ending_block <= now_as_balance) {
// If both schedules have ended, we don't merge and exit early.
(true, true) => return None,
// If one schedule has ended, we treat the one that has not ended as the new
// merged schedule.
(true, false) => return Some(schedule2),
(false, true) => return Some(schedule1),
// If neither schedule has ended don't exit early.
_ => {},
}
let locked = schedule1
.locked_at::<T::BlockNumberToBalance>(now)
.saturating_add(schedule2.locked_at::<T::BlockNumberToBalance>(now));
// This shouldn't happen because we know at least one ending block is greater than now,
// thus at least a schedule a some locked balance.
debug_assert!(
!locked.is_zero(),
"merge_vesting_info validation checks failed to catch a locked of 0"
);
let ending_block = schedule1_ending_block.max(schedule2_ending_block);
let starting_block = now.max(schedule1.starting_block()).max(schedule2.starting_block());
let per_block = {
let duration = ending_block
.saturating_sub(T::BlockNumberToBalance::convert(starting_block))
.max(One::one());
(locked / duration).max(One::one())
};
let schedule = VestingInfo::new(locked, per_block, starting_block);
debug_assert!(schedule.is_valid(), "merge_vesting_info schedule validation check failed");
Some(schedule)
}
// Execute a vested transfer from `source` to `target` with the given `schedule`.
fn do_vested_transfer(
source: <T::Lookup as StaticLookup>::Source,
target: <T::Lookup as StaticLookup>::Source,
schedule: VestingInfo<BalanceOf<T>, T::BlockNumber>,
) -> DispatchResult {
// Validate user inputs.
ensure!(schedule.locked() >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);
if !schedule.is_valid() {
return Err(Error::<T>::InvalidScheduleParams.into())
};
let target = T::Lookup::lookup(target)?;
let source = T::Lookup::lookup(source)?;
// Check we can add to this account prior to any storage writes.
Self::can_add_vesting_schedule(
&target,
schedule.locked(),
schedule.per_block(),
schedule.starting_block(),
)?;
T::Currency::transfer(
&source,
&target,
schedule.locked(),
ExistenceRequirement::AllowDeath,
)?;
// We can't let this fail because the currency transfer has already happened.
let res = Self::add_vesting_schedule(
&target,
schedule.locked(),
schedule.per_block(),
schedule.starting_block(),
);
debug_assert!(res.is_ok(), "Failed to add a schedule when we had to succeed.");
Ok(())
}
/// Iterate through the schedules to track the current locked amount and
/// filter out completed and specified schedules.
///
/// Returns a tuple that consists of:
/// - Vec of vesting schedules, where completed schedules and those specified
/// by filter are removed. (Note the vec is not checked for respecting
/// bounded length.)
/// - The amount locked at the current block number based on the given schedules.
///
/// NOTE: the amount locked does not include any schedules that are filtered out via `action`.
fn report_schedule_updates(
schedules: Vec<VestingInfo<BalanceOf<T>, T::BlockNumber>>,
action: VestingAction,
) -> (Vec<VestingInfo<BalanceOf<T>, T::BlockNumber>>, BalanceOf<T>) {
let now = <frame_system::Pallet<T>>::block_number();
let mut total_locked_now: BalanceOf<T> = Zero::zero();
let filtered_schedules = action
.pick_schedules::<T>(schedules)
.filter_map(|schedule| {
let locked_now = schedule.locked_at::<T::BlockNumberToBalance>(now);
if locked_now.is_zero() {
None
} else {
total_locked_now = total_locked_now.saturating_add(locked_now);
Some(schedule)
}
})
.collect::<Vec<_>>();
(filtered_schedules, total_locked_now)
}
/// Write an accounts updated vesting lock to storage.
fn write_lock(who: &T::AccountId, total_locked_now: BalanceOf<T>) {
if total_locked_now.is_zero() {
T::Currency::remove_lock(VESTING_ID, who);
Self::deposit_event(Event::<T>::VestingCompleted(who.clone()));
} else {
let reasons = WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE;
T::Currency::set_lock(VESTING_ID, &who, locked_now, reasons);
Self::deposit_event(Event::<T>::VestingUpdated(who, locked_now));
T::Currency::set_lock(VESTING_ID, who, total_locked_now, reasons);
Self::deposit_event(Event::<T>::VestingUpdated(who.clone(), total_locked_now));
};
}
/// Write an accounts updated vesting schedules to storage.
fn write_vesting(
who: &T::AccountId,
schedules: Vec<VestingInfo<BalanceOf<T>, T::BlockNumber>>,
) -> Result<(), DispatchError> {
let schedules: BoundedVec<
VestingInfo<BalanceOf<T>, T::BlockNumber>,
MaxVestingSchedulesGet<T>,
> = schedules.try_into().map_err(|_| Error::<T>::AtMaxVestingSchedules)?;
if schedules.len() == 0 {
Vesting::<T>::remove(&who);
} else {
Vesting::<T>::insert(who, schedules)
}
Ok(())
}
/// Unlock any vested funds of `who`.
fn do_vest(who: T::AccountId) -> DispatchResult {
let schedules = Self::vesting(&who).ok_or(Error::<T>::NotVesting)?;
let (schedules, locked_now) =
Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
Self::write_vesting(&who, schedules)?;
Self::write_lock(&who, locked_now);
Ok(())
}
/// Execute a `VestingAction` against the given `schedules`. Returns the updated schedules
/// and locked amount.
fn exec_action(
schedules: Vec<VestingInfo<BalanceOf<T>, T::BlockNumber>>,
action: VestingAction,
) -> Result<(Vec<VestingInfo<BalanceOf<T>, T::BlockNumber>>, BalanceOf<T>), DispatchError> {
let (schedules, locked_now) = match action {
VestingAction::Merge(idx1, idx2) => {
// The schedule index is based off of the schedule ordering prior to filtering out
// any schedules that may be ending at this block.
let schedule1 = *schedules.get(idx1).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
let schedule2 = *schedules.get(idx2).ok_or(Error::<T>::ScheduleIndexOutOfBounds)?;
// The length of `schedules` decreases by 2 here since we filter out 2 schedules.
// Thus we know below that we can push the new merged schedule without error
// (assuming initial state was valid).
let (mut schedules, mut locked_now) =
Self::report_schedule_updates(schedules.to_vec(), action);
let now = <frame_system::Pallet<T>>::block_number();
if let Some(new_schedule) = Self::merge_vesting_info(now, schedule1, schedule2) {
// Merging created a new schedule so we:
// 1) need to add it to the accounts vesting schedule collection,
schedules.push(new_schedule);
// (we use `locked_at` in case this is a schedule that started in the past)
let new_schedule_locked =
new_schedule.locked_at::<T::BlockNumberToBalance>(now);
// and 2) update the locked amount to reflect the schedule we just added.
locked_now = locked_now.saturating_add(new_schedule_locked);
} // In the None case there was no new schedule to account for.
(schedules, locked_now)
},
_ => Self::report_schedule_updates(schedules.to_vec(), action),
};
debug_assert!(
locked_now > Zero::zero() && schedules.len() > 0 ||
locked_now == Zero::zero() && schedules.len() == 0
);
Ok((schedules, locked_now))
}
}
impl<T: Config> VestingSchedule<T::AccountId> for Pallet<T>
where
BalanceOf<T>: MaybeSerializeDeserialize + Debug,
{
type Moment = T::BlockNumber;
type Currency = T::Currency;
type Moment = T::BlockNumber;
/// Get the amount that is currently being vested and cannot be transferred out of this account.
fn vesting_balance(who: &T::AccountId) -> Option<BalanceOf<T>> {
if let Some(v) = Self::vesting(who) {
let now = <frame_system::Pallet<T>>::block_number();
let locked_now = v.locked_at::<T::BlockNumberToBalance>(now);
Some(T::Currency::free_balance(who).min(locked_now))
let total_locked_now = v.iter().fold(Zero::zero(), |total, schedule| {
schedule.locked_at::<T::BlockNumberToBalance>(now).saturating_add(total)
});
Some(T::Currency::free_balance(who).min(total_locked_now))
} else {
None
}
@@ -390,14 +695,16 @@ where
/// Adds a vesting schedule to a given account.
///
/// If there already exists a vesting schedule for the given account, an `Err` is returned
/// and nothing is updated.
/// If the account has `MaxVestingSchedules`, an Error is returned and nothing
/// is updated.
///
/// On success, a linearly reducing amount of funds will be locked. In order to realise any
/// reduction of the lock over time as it diminishes, the account owner must use `vest` or
/// `vest_other`.
///
/// Is a no-op if the amount to be vested is zero.
///
/// NOTE: This doesn't alter the free balance of the account.
fn add_vesting_schedule(
who: &T::AccountId,
locked: BalanceOf<T>,
@@ -407,22 +714,58 @@ where
if locked.is_zero() {
return Ok(())
}
if Vesting::<T>::contains_key(who) {
Err(Error::<T>::ExistingVestingSchedule)?
let vesting_schedule = VestingInfo::new(locked, per_block, starting_block);
// Check for `per_block` or `locked` of 0.
if !vesting_schedule.is_valid() {
return Err(Error::<T>::InvalidScheduleParams.into())
};
let mut schedules = Self::vesting(who).unwrap_or_default();
// NOTE: we must push the new schedule so that `exec_action`
// will give the correct new locked amount.
ensure!(schedules.try_push(vesting_schedule).is_ok(), Error::<T>::AtMaxVestingSchedules);
let (schedules, locked_now) =
Self::exec_action(schedules.to_vec(), VestingAction::Passive)?;
Self::write_vesting(&who, schedules)?;
Self::write_lock(who, locked_now);
Ok(())
}
// Ensure we can call `add_vesting_schedule` without error. This should always
// be called prior to `add_vesting_schedule`.
fn can_add_vesting_schedule(
who: &T::AccountId,
locked: BalanceOf<T>,
per_block: BalanceOf<T>,
starting_block: T::BlockNumber,
) -> DispatchResult {
// Check for `per_block` or `locked` of 0.
if !VestingInfo::new(locked, per_block, starting_block).is_valid() {
return Err(Error::<T>::InvalidScheduleParams.into())
}
let vesting_schedule = VestingInfo { locked, per_block, starting_block };
Vesting::<T>::insert(who, vesting_schedule);
// it can't fail, but even if somehow it did, we don't really care.
let res = Self::update_lock(who.clone());
debug_assert!(res.is_ok());
ensure!(
(Vesting::<T>::decode_len(who).unwrap_or_default() as u32) < T::MAX_VESTING_SCHEDULES,
Error::<T>::AtMaxVestingSchedules
);
Ok(())
}
/// Remove a vesting schedule for a given account.
fn remove_vesting_schedule(who: &T::AccountId) {
Vesting::<T>::remove(who);
// it can't fail, but even if somehow it did, we don't really care.
let res = Self::update_lock(who.clone());
debug_assert!(res.is_ok());
fn remove_vesting_schedule(who: &T::AccountId, schedule_index: u32) -> DispatchResult {
let schedules = Self::vesting(who).ok_or(Error::<T>::NotVesting)?;
let remove_action = VestingAction::Remove(schedule_index as usize);
let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), remove_action)?;
Self::write_vesting(&who, schedules)?;
Self::write_lock(who, locked_now);
Ok(())
}
}
+95
View File
@@ -0,0 +1,95 @@
// This file is part of Substrate.
// Copyright (C) 2017-2021 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.
//! Storage migrations for the vesting pallet.
use super::*;
// Migration from single schedule to multiple schedules.
pub(crate) mod v1 {
use super::*;
#[cfg(feature = "try-runtime")]
pub(crate) fn pre_migrate<T: Config>() -> Result<(), &'static str> {
assert!(StorageVersion::<T>::get() == Releases::V0, "Storage version too high.");
log::debug!(
target: "runtime::vesting",
"migration: Vesting storage version v1 PRE migration checks succesful!"
);
Ok(())
}
/// Migrate from single schedule to multi schedule storage.
/// WARNING: This migration will delete schedules if `MaxVestingSchedules < 1`.
pub(crate) fn migrate<T: Config>() -> Weight {
let mut reads_writes = 0;
Vesting::<T>::translate::<VestingInfo<BalanceOf<T>, T::BlockNumber>, _>(
|_key, vesting_info| {
reads_writes += 1;
let v: Option<
BoundedVec<
VestingInfo<BalanceOf<T>, T::BlockNumber>,
MaxVestingSchedulesGet<T>,
>,
> = vec![vesting_info].try_into().ok();
if v.is_none() {
log::warn!(
target: "runtime::vesting",
"migration: Failed to move a vesting schedule into a BoundedVec"
);
}
v
},
);
T::DbWeight::get().reads_writes(reads_writes, reads_writes)
}
#[cfg(feature = "try-runtime")]
pub(crate) fn post_migrate<T: Config>() -> Result<(), &'static str> {
assert_eq!(StorageVersion::<T>::get(), Releases::V1);
for (_key, schedules) in Vesting::<T>::iter() {
assert!(
schedules.len() == 1,
"A bounded vec with incorrect count of items was created."
);
for s in schedules {
// It is ok if this does not pass, but ideally pre-existing schedules would pass
// this validation logic so we can be more confident about edge cases.
if !s.is_valid() {
log::warn!(
target: "runtime::vesting",
"migration: A schedule does not pass new validation logic.",
)
}
}
}
log::debug!(
target: "runtime::vesting",
"migration: Vesting storage version v1 POST migration checks successful!"
);
Ok(())
}
}
+22 -7
View File
@@ -92,24 +92,33 @@ impl Config for Test {
type BlockNumberToBalance = Identity;
type Currency = Balances;
type Event = Event;
const MAX_VESTING_SCHEDULES: u32 = 3;
type MinVestedTransfer = MinVestedTransfer;
type WeightInfo = ();
}
pub struct ExtBuilder {
existential_deposit: u64,
vesting_genesis_config: Option<Vec<(u64, u64, u64, u64)>>,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self { existential_deposit: 1 }
Self { existential_deposit: 1, vesting_genesis_config: None }
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn vesting_genesis_config(mut self, config: Vec<(u64, u64, u64, u64)>) -> Self {
self.vesting_genesis_config = Some(config);
self
}
pub fn build(self) -> sp_io::TestExternalities {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
@@ -120,19 +129,25 @@ impl ExtBuilder {
(3, 30 * self.existential_deposit),
(4, 40 * self.existential_deposit),
(12, 10 * self.existential_deposit),
(13, 9999 * self.existential_deposit),
],
}
.assimilate_storage(&mut t)
.unwrap();
pallet_vesting::GenesisConfig::<Test> {
vesting: vec![
let vesting = if let Some(vesting_config) = self.vesting_genesis_config {
vesting_config
} else {
vec![
(1, 0, 10, 5 * self.existential_deposit),
(2, 10, 20, 0),
(12, 10, 20, 5 * self.existential_deposit),
],
}
.assimilate_storage(&mut t)
.unwrap();
]
};
pallet_vesting::GenesisConfig::<Test> { vesting }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
File diff suppressed because it is too large Load Diff
+114
View File
@@ -0,0 +1,114 @@
// This file is part of Substrate.
// Copyright (C) 2017-2021 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.
//! Module to enforce private fields on `VestingInfo`.
use super::*;
/// Struct to encode the vesting schedule of an individual account.
#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen)]
pub struct VestingInfo<Balance, BlockNumber> {
/// Locked amount at genesis.
locked: Balance,
/// Amount that gets unlocked every block after `starting_block`.
per_block: Balance,
/// Starting block for unlocking(vesting).
starting_block: BlockNumber,
}
impl<Balance, BlockNumber> VestingInfo<Balance, BlockNumber>
where
Balance: AtLeast32BitUnsigned + Copy,
BlockNumber: AtLeast32BitUnsigned + Copy + Bounded,
{
/// Instantiate a new `VestingInfo`.
pub fn new(
locked: Balance,
per_block: Balance,
starting_block: BlockNumber,
) -> VestingInfo<Balance, BlockNumber> {
VestingInfo { locked, per_block, starting_block }
}
/// Validate parameters for `VestingInfo`. Note that this does not check
/// against `MinVestedTransfer`.
pub fn is_valid(&self) -> bool {
!self.locked.is_zero() && !self.raw_per_block().is_zero()
}
/// Locked amount at schedule creation.
pub fn locked(&self) -> Balance {
self.locked
}
/// Amount that gets unlocked every block after `starting_block`. Corrects for `per_block` of 0.
/// We don't let `per_block` be less than 1, or else the vesting will never end.
/// This should be used whenever accessing `per_block` unless explicitly checking for 0 values.
pub fn per_block(&self) -> Balance {
self.per_block.max(One::one())
}
/// Get the unmodified `per_block`. Generally should not be used, but is useful for
/// validating `per_block`.
pub(crate) fn raw_per_block(&self) -> Balance {
self.per_block
}
/// Starting block for unlocking(vesting).
pub fn starting_block(&self) -> BlockNumber {
self.starting_block
}
/// Amount locked at block `n`.
pub fn locked_at<BlockNumberToBalance: Convert<BlockNumber, Balance>>(
&self,
n: BlockNumber,
) -> Balance {
// Number of blocks that count toward vesting;
// saturating to 0 when n < starting_block.
let vested_block_count = n.saturating_sub(self.starting_block);
let vested_block_count = BlockNumberToBalance::convert(vested_block_count);
// Return amount that is still locked in vesting.
vested_block_count
.checked_mul(&self.per_block()) // `per_block` accessor guarantees at least 1.
.map(|to_unlock| self.locked.saturating_sub(to_unlock))
.unwrap_or(Zero::zero())
}
/// Block number at which the schedule ends (as type `Balance`).
pub fn ending_block_as_balance<BlockNumberToBalance: Convert<BlockNumber, Balance>>(
&self,
) -> Balance {
let starting_block = BlockNumberToBalance::convert(self.starting_block);
let duration = if self.per_block() >= self.locked {
// If `per_block` is bigger than `locked`, the schedule will end
// the block after starting.
One::one()
} else {
self.locked / self.per_block() +
if (self.locked % self.per_block()).is_zero() {
Zero::zero()
} else {
// `per_block` does not perfectly divide `locked`, so we need an extra block to
// unlock some amount less than `per_block`.
One::one()
}
};
starting_block.saturating_add(duration)
}
}
+143 -69
View File
@@ -18,7 +18,7 @@
//! Autogenerated weights for pallet_vesting
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2021-08-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2021-08-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
// Executed Command:
@@ -45,135 +45,209 @@ use sp_std::marker::PhantomData;
/// Weight functions needed for pallet_vesting.
pub trait WeightInfo {
fn vest_locked(l: u32, ) -> Weight;
fn vest_unlocked(l: u32, ) -> Weight;
fn vest_other_locked(l: u32, ) -> Weight;
fn vest_other_unlocked(l: u32, ) -> Weight;
fn vested_transfer(l: u32, ) -> Weight;
fn force_vested_transfer(l: u32, ) -> Weight;
fn vest_locked(l: u32, s: u32, ) -> Weight;
fn vest_unlocked(l: u32, s: u32, ) -> Weight;
fn vest_other_locked(l: u32, s: u32, ) -> Weight;
fn vest_other_unlocked(l: u32, s: u32, ) -> Weight;
fn vested_transfer(l: u32, s: u32, ) -> Weight;
fn force_vested_transfer(l: u32, s: u32, ) -> Weight;
fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight;
fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight;
}
/// Weights for pallet_vesting using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
// Storage: Vesting Vesting (r:1 w:0)
// Storage: Balances Locks (r:1 w:1)
fn vest_locked(l: u32, ) -> Weight {
(42_983_000 as Weight)
// Standard Error: 9_000
.saturating_add((190_000 as Weight).saturating_mul(l as Weight))
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(1 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
fn vest_unlocked(l: u32, ) -> Weight {
(46_213_000 as Weight)
// Standard Error: 5_000
.saturating_add((158_000 as Weight).saturating_mul(l as Weight))
fn vest_locked(l: u32, s: u32, ) -> Weight {
(50_642_000 as Weight)
// Standard Error: 1_000
.saturating_add((144_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 3_000
.saturating_add((177_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
// Storage: Vesting Vesting (r:1 w:0)
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
fn vest_unlocked(l: u32, s: u32, ) -> Weight {
(50_830_000 as Weight)
// Standard Error: 1_000
.saturating_add((115_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 3_000
.saturating_add((112_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(2 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn vest_other_locked(l: u32, ) -> Weight {
(42_644_000 as Weight)
// Standard Error: 11_000
.saturating_add((202_000 as Weight).saturating_mul(l as Weight))
fn vest_other_locked(l: u32, s: u32, ) -> Weight {
(52_151_000 as Weight)
// Standard Error: 1_000
.saturating_add((130_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 3_000
.saturating_add((162_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
.saturating_add(T::DbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn vest_other_unlocked(l: u32, ) -> Weight {
(45_765_000 as Weight)
// Standard Error: 5_000
.saturating_add((159_000 as Weight).saturating_mul(l as Weight))
fn vest_other_unlocked(l: u32, s: u32, ) -> Weight {
(51_009_000 as Weight)
// Standard Error: 4_000
.saturating_add((123_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 9_000
.saturating_add((118_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: System Account (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
fn vested_transfer(l: u32, ) -> Weight {
(97_417_000 as Weight)
// Standard Error: 11_000
.saturating_add((235_000 as Weight).saturating_mul(l as Weight))
fn vested_transfer(l: u32, s: u32, ) -> Weight {
(89_517_000 as Weight)
// Standard Error: 5_000
.saturating_add((114_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 10_000
.saturating_add((23_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: System Account (r:2 w:2)
// Storage: Balances Locks (r:1 w:1)
fn force_vested_transfer(l: u32, ) -> Weight {
(97_661_000 as Weight)
// Standard Error: 16_000
.saturating_add((239_000 as Weight).saturating_mul(l as Weight))
fn force_vested_transfer(l: u32, s: u32, ) -> Weight {
(87_903_000 as Weight)
// Standard Error: 6_000
.saturating_add((121_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 12_000
.saturating_add((56_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(4 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight {
(54_463_000 as Weight)
// Standard Error: 2_000
.saturating_add((123_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 5_000
.saturating_add((149_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight {
(53_674_000 as Weight)
// Standard Error: 1_000
.saturating_add((137_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 4_000
.saturating_add((152_000 as Weight).saturating_mul(s as Weight))
.saturating_add(T::DbWeight::get().reads(3 as Weight))
.saturating_add(T::DbWeight::get().writes(3 as Weight))
}
}
// For backwards compatibility and tests
impl WeightInfo for () {
// Storage: Vesting Vesting (r:1 w:0)
// Storage: Balances Locks (r:1 w:1)
fn vest_locked(l: u32, ) -> Weight {
(42_983_000 as Weight)
// Standard Error: 9_000
.saturating_add((190_000 as Weight).saturating_mul(l as Weight))
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(1 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
fn vest_unlocked(l: u32, ) -> Weight {
(46_213_000 as Weight)
// Standard Error: 5_000
.saturating_add((158_000 as Weight).saturating_mul(l as Weight))
fn vest_locked(l: u32, s: u32, ) -> Weight {
(50_642_000 as Weight)
// Standard Error: 1_000
.saturating_add((144_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 3_000
.saturating_add((177_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
// Storage: Vesting Vesting (r:1 w:0)
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
fn vest_unlocked(l: u32, s: u32, ) -> Weight {
(50_830_000 as Weight)
// Standard Error: 1_000
.saturating_add((115_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 3_000
.saturating_add((112_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn vest_other_locked(l: u32, ) -> Weight {
(42_644_000 as Weight)
// Standard Error: 11_000
.saturating_add((202_000 as Weight).saturating_mul(l as Weight))
fn vest_other_locked(l: u32, s: u32, ) -> Weight {
(52_151_000 as Weight)
// Standard Error: 1_000
.saturating_add((130_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 3_000
.saturating_add((162_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(2 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn vest_other_unlocked(l: u32, ) -> Weight {
(45_765_000 as Weight)
// Standard Error: 5_000
.saturating_add((159_000 as Weight).saturating_mul(l as Weight))
fn vest_other_unlocked(l: u32, s: u32, ) -> Weight {
(51_009_000 as Weight)
// Standard Error: 4_000
.saturating_add((123_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 9_000
.saturating_add((118_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: System Account (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
fn vested_transfer(l: u32, ) -> Weight {
(97_417_000 as Weight)
// Standard Error: 11_000
.saturating_add((235_000 as Weight).saturating_mul(l as Weight))
fn vested_transfer(l: u32, s: u32, ) -> Weight {
(89_517_000 as Weight)
// Standard Error: 5_000
.saturating_add((114_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 10_000
.saturating_add((23_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: System Account (r:2 w:2)
// Storage: Balances Locks (r:1 w:1)
fn force_vested_transfer(l: u32, ) -> Weight {
(97_661_000 as Weight)
// Standard Error: 16_000
.saturating_add((239_000 as Weight).saturating_mul(l as Weight))
fn force_vested_transfer(l: u32, s: u32, ) -> Weight {
(87_903_000 as Weight)
// Standard Error: 6_000
.saturating_add((121_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 12_000
.saturating_add((56_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(4 as Weight))
.saturating_add(RocksDbWeight::get().writes(4 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight {
(54_463_000 as Weight)
// Standard Error: 2_000
.saturating_add((123_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 5_000
.saturating_add((149_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
// Storage: Vesting Vesting (r:1 w:1)
// Storage: Balances Locks (r:1 w:1)
// Storage: System Account (r:1 w:1)
fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight {
(53_674_000 as Weight)
// Standard Error: 1_000
.saturating_add((137_000 as Weight).saturating_mul(l as Weight))
// Standard Error: 4_000
.saturating_add((152_000 as Weight).saturating_mul(s as Weight))
.saturating_add(RocksDbWeight::get().reads(3 as Weight))
.saturating_add(RocksDbWeight::get().writes(3 as Weight))
}
}