Skip to content
Snippets Groups Projects
Commit ddbcefe9 authored by Gavin Wood's avatar Gavin Wood Committed by GitHub
Browse files

Adds `vested_transfer` to Vesting pallet (#5029)


* 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: default avatarShawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/vesting/src/lib.rs

Co-Authored-By: default avatarShawn Tabrizi <shawntabrizi@gmail.com>

* Update frame/vesting/src/lib.rs

Co-Authored-By: default avatarBastian Köcher <bkchr@users.noreply.github.com>

* Update frame/vesting/src/lib.rs

Co-Authored-By: default avatarBastian Köcher <bkchr@users.noreply.github.com>

* Update frame/vesting/src/lib.rs

Co-Authored-By: default avatarBastian Köcher <bkchr@users.noreply.github.com>

* Add tab to line 249

* Rename to `MinVestedTransfer` for clarity

Co-authored-by: default avatarShawn Tabrizi <shawntabrizi@gmail.com>
Co-authored-by: default avatarBastian Köcher <bkchr@users.noreply.github.com>
parent 0f2606cc
No related merge requests found
...@@ -82,8 +82,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { ...@@ -82,8 +82,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to 0. If only runtime // and set impl_version to 0. If only runtime
// implementation changes and behavior does not, then leave spec_version as // implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version. // is and increment impl_version.
spec_version: 227, spec_version: 229,
impl_version: 1, impl_version: 0,
apis: RUNTIME_API_VERSIONS, apis: RUNTIME_API_VERSIONS,
}; };
...@@ -590,10 +590,15 @@ impl pallet_society::Trait for Runtime { ...@@ -590,10 +590,15 @@ impl pallet_society::Trait for Runtime {
type ChallengePeriod = ChallengePeriod; type ChallengePeriod = ChallengePeriod;
} }
parameter_types! {
pub const MinVestedTransfer: Balance = 100 * DOLLARS;
}
impl pallet_vesting::Trait for Runtime { impl pallet_vesting::Trait for Runtime {
type Event = Event; type Event = Event;
type Currency = Balances; type Currency = Balances;
type BlockNumberToBalance = ConvertInto; type BlockNumberToBalance = ConvertInto;
type MinVestedTransfer = MinVestedTransfer;
} }
construct_runtime!( construct_runtime!(
......
...@@ -52,10 +52,12 @@ use codec::{Encode, Decode}; ...@@ -52,10 +52,12 @@ use codec::{Encode, Decode};
use sp_runtime::{DispatchResult, RuntimeDebug, traits::{ use sp_runtime::{DispatchResult, RuntimeDebug, traits::{
StaticLookup, Zero, AtLeast32Bit, MaybeSerializeDeserialize, Convert 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::{ 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}; use frame_system::{self as system, ensure_signed};
type BalanceOf<T> = <<T as Trait>::Currency as Currency<<T as frame_system::Trait>::AccountId>>::Balance; 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 { ...@@ -69,6 +71,9 @@ pub trait Trait: frame_system::Trait {
/// Convert the block number into a balance. /// Convert the block number into a balance.
type BlockNumberToBalance: Convert<Self::BlockNumber, BalanceOf<Self>>; 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 "; const VESTING_ID: LockIdentifier = *b"vesting ";
...@@ -158,6 +163,8 @@ decl_error! { ...@@ -158,6 +163,8 @@ decl_error! {
NotVesting, NotVesting,
/// An existing vesting schedule already exists for this account that cannot be clobbered. /// An existing vesting schedule already exists for this account that cannot be clobbered.
ExistingVestingSchedule, ExistingVestingSchedule,
/// Amount being transferred is too low to create a vesting schedule.
AmountLow,
} }
} }
...@@ -166,6 +173,9 @@ decl_module! { ...@@ -166,6 +173,9 @@ decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin { pub struct Module<T: Trait> for enum Call where origin: T::Origin {
type Error = Error<T>; 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; fn deposit_event() = default;
/// Unlock any vested funds of the sender account. /// Unlock any vested funds of the sender account.
...@@ -206,6 +216,40 @@ decl_module! { ...@@ -206,6 +216,40 @@ decl_module! {
ensure_signed(origin)?; ensure_signed(origin)?;
Self::update_lock(T::Lookup::lookup(target)?) 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 { ...@@ -348,10 +392,14 @@ mod tests {
type ExistentialDeposit = ExistentialDeposit; type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System; type AccountStore = System;
} }
parameter_types! {
pub const MinVestedTransfer: u64 = 256 * 2;
}
impl Trait for Test { impl Trait for Test {
type Event = (); type Event = ();
type Currency = Balances; type Currency = Balances;
type BlockNumberToBalance = Identity; type BlockNumberToBalance = Identity;
type MinVestedTransfer = MinVestedTransfer;
} }
type System = frame_system::Module<Test>; type System = frame_system::Module<Test>;
type Balances = pallet_balances::Module<Test>; type Balances = pallet_balances::Module<Test>;
...@@ -613,4 +661,95 @@ mod tests { ...@@ -613,4 +661,95 @@ mod tests {
assert_ok!(Balances::transfer(Some(12).into(), 3, 256 * 5)); 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);
});
}
} }
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment