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