Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Adds vested_transfer to Vesting pallet #5029

Merged
merged 18 commits into from Mar 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 7 additions & 2 deletions bin/node/runtime/src/lib.rs
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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!(
Expand Down
143 changes: 141 additions & 2 deletions frame/vesting/src/lib.rs
Expand Up @@ -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;
Expand All @@ -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 ";
Expand Down Expand Up @@ -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,
}
}

Expand All @@ -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.
Expand Down Expand Up @@ -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(())
}
}
}

Expand Down Expand Up @@ -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>;
Expand Down Expand Up @@ -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);
});
}
}