From ddbcefe928bd9cc1bceb0c4e60cea15965bd7fd1 Mon Sep 17 00:00:00 2001 From: Gavin Wood <gavin@parity.io> Date: Thu, 5 Mar 2020 12:03:20 +0100 Subject: [PATCH] Adds `vested_transfer` to Vesting pallet (#5029) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add the vested_transfer function with tests * Add VestingDeposit for minimum amount to create a new schedule * Remove irrelevant file commit * Bump spec * Add weight comment * Fix CI build * Make the check before the transfer * Update frame/vesting/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/vesting/src/lib.rs Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * Update frame/vesting/src/lib.rs Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Update frame/vesting/src/lib.rs Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Update frame/vesting/src/lib.rs Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com> * Add tab to line 249 * Rename to `MinVestedTransfer` for clarity Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> --- substrate/bin/node/runtime/src/lib.rs | 9 +- substrate/frame/vesting/src/lib.rs | 143 +++++++++++++++++++++++++- 2 files changed, 148 insertions(+), 4 deletions(-) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 3a7df27466c..0844b32f7c3 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -82,8 +82,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 227, - impl_version: 1, + spec_version: 229, + impl_version: 0, apis: RUNTIME_API_VERSIONS, }; @@ -590,10 +590,15 @@ impl pallet_society::Trait for Runtime { type ChallengePeriod = ChallengePeriod; } +parameter_types! { + pub const MinVestedTransfer: Balance = 100 * DOLLARS; +} + impl pallet_vesting::Trait for Runtime { type Event = Event; type Currency = Balances; type BlockNumberToBalance = ConvertInto; + type MinVestedTransfer = MinVestedTransfer; } construct_runtime!( diff --git a/substrate/frame/vesting/src/lib.rs b/substrate/frame/vesting/src/lib.rs index fdc480ddd04..d4a34d4e172 100644 --- a/substrate/frame/vesting/src/lib.rs +++ b/substrate/frame/vesting/src/lib.rs @@ -52,10 +52,12 @@ use codec::{Encode, Decode}; use sp_runtime::{DispatchResult, RuntimeDebug, traits::{ StaticLookup, Zero, AtLeast32Bit, MaybeSerializeDeserialize, Convert }}; -use frame_support::{decl_module, decl_event, decl_storage, decl_error}; +use frame_support::{decl_module, decl_event, decl_storage, decl_error, ensure}; use frame_support::traits::{ - Currency, LockableCurrency, VestingSchedule, WithdrawReason, LockIdentifier + Currency, LockableCurrency, VestingSchedule, WithdrawReason, LockIdentifier, ExistenceRequirement, + Get, }; +use frame_support::weights::SimpleDispatchInfo; use frame_system::{self as system, ensure_signed}; type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance; @@ -69,6 +71,9 @@ pub trait Trait: frame_system::Trait { /// Convert the block number into a balance. type BlockNumberToBalance: Convert<Self::BlockNumber, BalanceOf<Self>>; + + /// The minimum amount transferred to call `vested_transfer`. + type MinVestedTransfer: Get<BalanceOf<Self>>; } const VESTING_ID: LockIdentifier = *b"vesting "; @@ -158,6 +163,8 @@ decl_error! { NotVesting, /// An existing vesting schedule already exists for this account that cannot be clobbered. ExistingVestingSchedule, + /// Amount being transferred is too low to create a vesting schedule. + AmountLow, } } @@ -166,6 +173,9 @@ decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { type Error = Error<T>; + /// The minimum amount to be transferred to create a new vesting schedule. + const MinVestedTransfer: BalanceOf<T> = T::MinVestedTransfer::get(); + fn deposit_event() = default; /// Unlock any vested funds of the sender account. @@ -206,6 +216,40 @@ decl_module! { ensure_signed(origin)?; Self::update_lock(T::Lookup::lookup(target)?) } + + /// 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. + /// - `schedule`: The vesting schedule attached to the transfer. + /// + /// Emits `VestingCreated`. + /// + /// # <weight> + /// - Creates a new storage entry, but is protected by a minimum transfer + /// amount needed to succeed. + /// # </weight> + #[weight = SimpleDispatchInfo::FixedNormal(1_000_000)] + pub fn vested_transfer( + origin, + 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(()) + } } } @@ -348,10 +392,14 @@ mod tests { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; } + parameter_types! { + pub const MinVestedTransfer: u64 = 256 * 2; + } impl Trait for Test { type Event = (); type Currency = Balances; type BlockNumberToBalance = Identity; + type MinVestedTransfer = MinVestedTransfer; } type System = frame_system::Module<Test>; type Balances = pallet_balances::Module<Test>; @@ -613,4 +661,95 @@ mod tests { assert_ok!(Balances::transfer(Some(12).into(), 3, 256 * 5)); }); } + + #[test] + fn vested_transfer_works() { + ExtBuilder::default() + .existential_deposit(256) + .build() + .execute_with(|| { + assert_eq!(System::block_number(), 1); + let user3_free_balance = Balances::free_balance(&3); + let user4_free_balance = Balances::free_balance(&4); + assert_eq!(user3_free_balance, 256 * 30); + assert_eq!(user4_free_balance, 256 * 40); + // Account 4 should not have any vesting yet. + assert_eq!(Vesting::vesting(&4), None); + // Make the schedule for the new transfer. + let new_vesting_schedule = VestingInfo { + locked: 256 * 5, + per_block: 64, // Vesting over 20 blocks + starting_block: 10, + }; + assert_ok!(Vesting::vested_transfer(Some(3).into(), 4, new_vesting_schedule)); + // Now account 4 should have vesting. + assert_eq!(Vesting::vesting(&4), Some(new_vesting_schedule)); + // Ensure the transfer happened correctly. + let user3_free_balance_updated = Balances::free_balance(&3); + assert_eq!(user3_free_balance_updated, 256 * 25); + let user4_free_balance_updated = Balances::free_balance(&4); + assert_eq!(user4_free_balance_updated, 256 * 45); + // Account 4 has 5 * 256 locked. + assert_eq!(Vesting::vesting_balance(&4), Some(256 * 5)); + + System::set_block_number(20); + assert_eq!(System::block_number(), 20); + + // Account 4 has 5 * 64 units vested by block 20. + assert_eq!(Vesting::vesting_balance(&4), Some(10 * 64)); + + System::set_block_number(30); + assert_eq!(System::block_number(), 30); + + // Account 4 has fully vested. + assert_eq!(Vesting::vesting_balance(&4), Some(0)); + }); + } + + #[test] + fn vested_transfer_correctly_fails() { + ExtBuilder::default() + .existential_deposit(256) + .build() + .execute_with(|| { + assert_eq!(System::block_number(), 1); + let user2_free_balance = Balances::free_balance(&2); + let user4_free_balance = Balances::free_balance(&4); + assert_eq!(user2_free_balance, 256 * 20); + assert_eq!(user4_free_balance, 256 * 40); + // Account 2 should already have a vesting schedule. + let user2_vesting_schedule = VestingInfo { + locked: 256 * 20, + per_block: 256, // Vesting over 20 blocks + starting_block: 10, + }; + assert_eq!(Vesting::vesting(&2), Some(user2_vesting_schedule)); + + // The vesting schedule we will try to create, fails due to pre-existence of schedule. + let new_vesting_schedule = VestingInfo { + locked: 256 * 5, + per_block: 64, // Vesting over 20 blocks + starting_block: 10, + }; + assert_noop!( + Vesting::vested_transfer(Some(4).into(), 2, new_vesting_schedule), + Error::<Test>::ExistingVestingSchedule, + ); + + // Fails due to too low transfer amount. + let new_vesting_schedule_too_low = VestingInfo { + locked: 256 * 1, + per_block: 64, + starting_block: 10, + }; + assert_noop!( + Vesting::vested_transfer(Some(3).into(), 4, new_vesting_schedule_too_low), + Error::<Test>::AmountLow, + ); + + // Verify no currency transfer happened. + assert_eq!(user2_free_balance, 256 * 20); + assert_eq!(user4_free_balance, 256 * 40); + }); + } } -- GitLab