b6d35f6faf
Updated 4763 files with dual copyright: - Parity Technologies (UK) Ltd. - Dijital Kurdistan Tech Institute
1265 lines
46 KiB
Rust
1265 lines
46 KiB
Rust
// This file is part of Bizinikiwi.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
|
|
// 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.
|
|
|
|
use codec::EncodeLike;
|
|
use pezframe_support::{assert_noop, assert_ok, assert_storage_noop};
|
|
use pezframe_system::RawOrigin;
|
|
use pezsp_runtime::{
|
|
traits::{BadOrigin, Identity},
|
|
TokenError,
|
|
};
|
|
|
|
use super::{Vesting as VestingStorage, *};
|
|
use crate::mock::{vesting_events_since_last_call, Balances, ExtBuilder, System, Test, Vesting};
|
|
|
|
/// A default existential deposit.
|
|
const ED: u64 = 256;
|
|
|
|
/// Calls vest, and asserts that there is no entry for `account`
|
|
/// in the `Vesting` storage item.
|
|
fn vest_and_assert_no_vesting<T>(account: u64)
|
|
where
|
|
u64: EncodeLike<<T as pezframe_system::Config>::AccountId>,
|
|
T: pezpallet::Config,
|
|
{
|
|
// Its ok for this to fail because the user may already have no schedules.
|
|
let _result = Vesting::vest(Some(account).into());
|
|
assert!(!<VestingStorage<T>>::contains_key(account));
|
|
}
|
|
|
|
#[test]
|
|
fn check_vesting_status() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let user1_free_balance = Balances::free_balance(&1);
|
|
let user2_free_balance = Balances::free_balance(&2);
|
|
let user12_free_balance = Balances::free_balance(&12);
|
|
assert_eq!(user1_free_balance, ED * 10); // Account 1 has free balance
|
|
assert_eq!(user2_free_balance, ED * 20); // Account 2 has free balance
|
|
assert_eq!(user12_free_balance, ED * 10); // Account 12 has free balance
|
|
let user1_vesting_schedule = VestingInfo::new(
|
|
ED * 5,
|
|
128, // Vesting over 10 blocks
|
|
0,
|
|
);
|
|
let user2_vesting_schedule = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
let user12_vesting_schedule = VestingInfo::new(
|
|
ED * 5,
|
|
64, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&1).unwrap(), vec![user1_vesting_schedule]); // Account 1 has a vesting schedule
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![user2_vesting_schedule]); // Account 2 has a vesting schedule
|
|
assert_eq!(VestingStorage::<Test>::get(&12).unwrap(), vec![user12_vesting_schedule]); // Account 12 has a vesting schedule
|
|
|
|
// Account 1 has only 128 units vested from their illiquid ED * 5 units at block 1
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(128 * 9));
|
|
// Account 2 has their full balance locked
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(user2_free_balance));
|
|
// Account 12 has only their illiquid funds locked
|
|
assert_eq!(Vesting::vesting_balance(&12), Some(user12_free_balance - ED * 5));
|
|
|
|
System::set_block_number(10);
|
|
assert_eq!(System::block_number(), 10);
|
|
|
|
// Account 1 has fully vested by block 10
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(0));
|
|
// Account 2 has started vesting by block 10
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(user2_free_balance));
|
|
// Account 12 has started vesting by block 10
|
|
assert_eq!(Vesting::vesting_balance(&12), Some(user12_free_balance - ED * 5));
|
|
|
|
System::set_block_number(30);
|
|
assert_eq!(System::block_number(), 30);
|
|
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(0)); // Account 1 is still fully vested, and not negative
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(0)); // Account 2 has fully vested by block 30
|
|
assert_eq!(Vesting::vesting_balance(&12), Some(0)); // Account 2 has fully vested by block 30
|
|
|
|
// Once we unlock the funds, they are removed from storage.
|
|
vest_and_assert_no_vesting::<Test>(1);
|
|
vest_and_assert_no_vesting::<Test>(2);
|
|
vest_and_assert_no_vesting::<Test>(12);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn check_vesting_status_for_multi_schedule_account() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
assert_eq!(System::block_number(), 1);
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
// Account 2 already has a vesting schedule.
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
|
|
// Account 2's free balance is from sched0.
|
|
let free_balance = Balances::free_balance(&2);
|
|
assert_eq!(free_balance, ED * (20));
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(free_balance));
|
|
|
|
// Add a 2nd schedule that is already unlocking by block #1.
|
|
let sched1 = VestingInfo::new(
|
|
ED * 10,
|
|
ED, // Vesting over 10 blocks
|
|
0,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 2, sched1));
|
|
// Free balance is equal to the two existing schedules total amount.
|
|
let free_balance = Balances::free_balance(&2);
|
|
assert_eq!(free_balance, ED * (10 + 20));
|
|
// The most recently added schedule exists.
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched1]);
|
|
// sched1 has free funds at block #1, but nothing else.
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(free_balance - sched1.per_block()));
|
|
|
|
// Add a 3rd schedule.
|
|
let sched2 = VestingInfo::new(
|
|
ED * 30,
|
|
ED, // Vesting over 30 blocks
|
|
5,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 2, sched2));
|
|
|
|
System::set_block_number(9);
|
|
// Free balance is equal to the 3 existing schedules total amount.
|
|
let free_balance = Balances::free_balance(&2);
|
|
assert_eq!(free_balance, ED * (10 + 20 + 30));
|
|
// sched1 and sched2 are freeing funds at block #9.
|
|
assert_eq!(
|
|
Vesting::vesting_balance(&2),
|
|
Some(free_balance - sched1.per_block() * 9 - sched2.per_block() * 4)
|
|
);
|
|
|
|
System::set_block_number(20);
|
|
// At block #20 sched1 is fully unlocked while sched2 and sched0 are partially unlocked.
|
|
assert_eq!(
|
|
Vesting::vesting_balance(&2),
|
|
Some(
|
|
free_balance - sched1.locked() - sched2.per_block() * 15 - sched0.per_block() * 10
|
|
)
|
|
);
|
|
|
|
System::set_block_number(30);
|
|
// At block #30 sched0 and sched1 are fully unlocked while sched2 is partially unlocked.
|
|
assert_eq!(
|
|
Vesting::vesting_balance(&2),
|
|
Some(free_balance - sched1.locked() - sched2.per_block() * 25 - sched0.locked())
|
|
);
|
|
|
|
// At block #35 sched2 fully unlocks and thus all schedules funds are unlocked.
|
|
System::set_block_number(35);
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(0));
|
|
// Since we have not called any extrinsics that would unlock funds the schedules
|
|
// are still in storage,
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched1, sched2]);
|
|
// but once we unlock the funds, they are removed from storage.
|
|
vest_and_assert_no_vesting::<Test>(2);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn unvested_balance_should_not_transfer() {
|
|
ExtBuilder::default().existential_deposit(10).build().execute_with(|| {
|
|
let user1_free_balance = Balances::free_balance(&1);
|
|
assert_eq!(user1_free_balance, 100); // Account 1 has free balance
|
|
// Account 1 has only 5 units vested at block 1 (plus 50 unvested)
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(45));
|
|
// Account 1 cannot send more than vested amount...
|
|
assert_noop!(Balances::transfer_allow_death(Some(1).into(), 2, 56), TokenError::Frozen);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_balance_should_transfer() {
|
|
ExtBuilder::default().existential_deposit(10).build().execute_with(|| {
|
|
let user1_free_balance = Balances::free_balance(&1);
|
|
assert_eq!(user1_free_balance, 100); // Account 1 has free balance
|
|
// Account 1 has only 5 units vested at block 1 (plus 50 unvested)
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(45));
|
|
assert_ok!(Vesting::vest(Some(1).into()));
|
|
assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_balance_should_transfer_with_multi_sched() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let sched0 = VestingInfo::new(5 * ED, 128, 0);
|
|
assert_ok!(Vesting::vested_transfer(Some(13).into(), 1, sched0));
|
|
// Total 10*ED locked for all the schedules.
|
|
assert_eq!(VestingStorage::<Test>::get(&1).unwrap(), vec![sched0, sched0]);
|
|
|
|
let user1_free_balance = Balances::free_balance(&1);
|
|
assert_eq!(user1_free_balance, 3840); // Account 1 has free balance
|
|
|
|
// Account 1 has only 256 units unlocking at block 1 (plus 1280 already fee).
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(2304));
|
|
assert_ok!(Vesting::vest(Some(1).into()));
|
|
assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 1536));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn non_vested_cannot_vest() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
assert!(!<VestingStorage<Test>>::contains_key(4));
|
|
assert_noop!(Vesting::vest(Some(4).into()), Error::<Test>::NotVesting);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_balance_should_transfer_using_vest_other() {
|
|
ExtBuilder::default().existential_deposit(10).build().execute_with(|| {
|
|
let user1_free_balance = Balances::free_balance(&1);
|
|
assert_eq!(user1_free_balance, 100); // Account 1 has free balance
|
|
// Account 1 has only 5 units vested at block 1 (plus 50 unvested)
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(45));
|
|
assert_ok!(Vesting::vest_other(Some(2).into(), 1));
|
|
assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_balance_should_transfer_using_vest_other_with_multi_sched() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let sched0 = VestingInfo::new(5 * ED, 128, 0);
|
|
assert_ok!(Vesting::vested_transfer(Some(13).into(), 1, sched0));
|
|
// Total of 10*ED of locked for all the schedules.
|
|
assert_eq!(VestingStorage::<Test>::get(&1).unwrap(), vec![sched0, sched0]);
|
|
|
|
let user1_free_balance = Balances::free_balance(&1);
|
|
assert_eq!(user1_free_balance, 3840); // Account 1 has free balance
|
|
|
|
// Account 1 has only 256 units unlocking at block 1 (plus 1280 already free).
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(2304));
|
|
assert_ok!(Vesting::vest_other(Some(2).into(), 1));
|
|
assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 1536));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn non_vested_cannot_vest_other() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
assert!(!<VestingStorage<Test>>::contains_key(4));
|
|
assert_noop!(Vesting::vest_other(Some(3).into(), 4), Error::<Test>::NotVesting);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn extra_balance_should_transfer() {
|
|
ExtBuilder::default().existential_deposit(10).build().execute_with(|| {
|
|
assert_ok!(Balances::transfer_allow_death(Some(3).into(), 1, 100));
|
|
assert_ok!(Balances::transfer_allow_death(Some(3).into(), 2, 100));
|
|
|
|
let user1_free_balance = Balances::free_balance(&1);
|
|
assert_eq!(user1_free_balance, 200); // Account 1 has 100 more free balance than normal
|
|
|
|
let user2_free_balance = Balances::free_balance(&2);
|
|
assert_eq!(user2_free_balance, 300); // Account 2 has 100 more free balance than normal
|
|
|
|
// Account 1 has only 5 units vested at block 1 (plus 150 unvested)
|
|
assert_eq!(Vesting::vesting_balance(&1), Some(45));
|
|
assert_ok!(Vesting::vest(Some(1).into()));
|
|
// Account 1 can send extra units gained
|
|
assert_ok!(Balances::transfer_allow_death(Some(1).into(), 3, 155));
|
|
|
|
// Account 2 has no units vested at block 1, but gained 100
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(200));
|
|
assert_ok!(Vesting::vest(Some(2).into()));
|
|
// Account 2 can send extra units gained
|
|
assert_ok!(Balances::transfer_allow_death(Some(2).into(), 3, 100));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn liquid_funds_should_transfer_with_delayed_vesting() {
|
|
ExtBuilder::default().existential_deposit(256).build().execute_with(|| {
|
|
let user12_free_balance = Balances::free_balance(&12);
|
|
|
|
// Account 12 has free balance
|
|
assert_eq!(user12_free_balance, 2560);
|
|
// Account 12 has liquid funds
|
|
assert_eq!(Vesting::vesting_balance(&12), Some(user12_free_balance - 256 * 5));
|
|
|
|
// Account 12 has delayed vesting
|
|
let user12_vesting_schedule = VestingInfo::new(
|
|
256 * 5,
|
|
// Vesting over 20 blocks
|
|
64,
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&12).unwrap(), vec![user12_vesting_schedule]);
|
|
|
|
// Account 12 can still send liquid funds
|
|
assert_ok!(Balances::transfer_allow_death(Some(12).into(), 3, 256 * 5));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_transfer_works() {
|
|
ExtBuilder::default().existential_deposit(256).build().execute_with(|| {
|
|
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!(VestingStorage::<Test>::get(&4), None);
|
|
// Make the schedule for the new transfer.
|
|
let new_vesting_schedule = VestingInfo::new(
|
|
256 * 5,
|
|
64, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(3).into(), 4, new_vesting_schedule));
|
|
// Verify that the last events are `VestingCreated/VestingUpdated`.
|
|
assert_eq!(
|
|
vesting_events_since_last_call(),
|
|
vec![
|
|
Event::VestingCreated { account: 4, schedule_index: 0 },
|
|
Event::VestingUpdated { account: 4, unvested: 1280 },
|
|
]
|
|
);
|
|
// Now account 4 should have vesting.
|
|
assert_eq!(VestingStorage::<Test>::get(&4).unwrap(), vec![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));
|
|
// and after unlocking its schedules are removed from storage.
|
|
vest_and_assert_no_vesting::<Test>(4);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_transfer_correctly_fails() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let user2_free_balance = Balances::free_balance(&2);
|
|
let user4_free_balance = Balances::free_balance(&4);
|
|
assert_eq!(user2_free_balance, ED * 20);
|
|
assert_eq!(user4_free_balance, ED * 40);
|
|
|
|
// Account 2 should already have a vesting schedule.
|
|
let user2_vesting_schedule = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![user2_vesting_schedule]);
|
|
|
|
// Fails due to too low transfer amount.
|
|
let new_vesting_schedule_too_low =
|
|
VestingInfo::new(<Test as Config>::MinVestedTransfer::get() - 1, 64, 10);
|
|
assert_noop!(
|
|
Vesting::vested_transfer(Some(3).into(), 4, new_vesting_schedule_too_low),
|
|
Error::<Test>::AmountLow,
|
|
);
|
|
|
|
// `per_block` is 0, which would result in a schedule with infinite duration.
|
|
let schedule_per_block_0 =
|
|
VestingInfo::new(<Test as Config>::MinVestedTransfer::get(), 0, 10);
|
|
assert_noop!(
|
|
Vesting::vested_transfer(Some(13).into(), 4, schedule_per_block_0),
|
|
Error::<Test>::InvalidScheduleParams,
|
|
);
|
|
|
|
// `locked` is 0.
|
|
let schedule_locked_0 = VestingInfo::new(0, 1, 10);
|
|
assert_noop!(
|
|
Vesting::vested_transfer(Some(3).into(), 4, schedule_locked_0),
|
|
Error::<Test>::AmountLow,
|
|
);
|
|
|
|
// Free balance has not changed.
|
|
assert_eq!(user2_free_balance, Balances::free_balance(&2));
|
|
assert_eq!(user4_free_balance, Balances::free_balance(&4));
|
|
// Account 4 has no schedules.
|
|
vest_and_assert_no_vesting::<Test>(4);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_transfer_allows_max_schedules() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let mut user_4_free_balance = Balances::free_balance(&4);
|
|
let max_schedules = <Test as Config>::MAX_VESTING_SCHEDULES;
|
|
let sched = VestingInfo::new(
|
|
<Test as Config>::MinVestedTransfer::get(),
|
|
1, // Vest over 2 * 256 blocks.
|
|
10,
|
|
);
|
|
|
|
// Add max amount schedules to user 4.
|
|
for _ in 0..max_schedules {
|
|
assert_ok!(Vesting::vested_transfer(Some(13).into(), 4, sched));
|
|
}
|
|
|
|
// The schedules count towards vesting balance
|
|
let transferred_amount = <Test as Config>::MinVestedTransfer::get() * max_schedules as u64;
|
|
assert_eq!(Vesting::vesting_balance(&4), Some(transferred_amount));
|
|
// and free balance.
|
|
user_4_free_balance += transferred_amount;
|
|
assert_eq!(Balances::free_balance(&4), user_4_free_balance);
|
|
|
|
// Cannot insert a 4th vesting schedule when `MaxVestingSchedules` === 3,
|
|
assert_noop!(
|
|
Vesting::vested_transfer(Some(3).into(), 4, sched),
|
|
Error::<Test>::AtMaxVestingSchedules,
|
|
);
|
|
// so the free balance does not change.
|
|
assert_eq!(Balances::free_balance(&4), user_4_free_balance);
|
|
|
|
// Account 4 has fully vested when all the schedules end,
|
|
System::set_block_number(
|
|
<Test as Config>::MinVestedTransfer::get() + sched.starting_block(),
|
|
);
|
|
assert_eq!(Vesting::vesting_balance(&4), Some(0));
|
|
// and after unlocking its schedules are removed from storage.
|
|
vest_and_assert_no_vesting::<Test>(4);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn force_vested_transfer_works() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let user3_free_balance = Balances::free_balance(&3);
|
|
let user4_free_balance = Balances::free_balance(&4);
|
|
assert_eq!(user3_free_balance, ED * 30);
|
|
assert_eq!(user4_free_balance, ED * 40);
|
|
// Account 4 should not have any vesting yet.
|
|
assert_eq!(VestingStorage::<Test>::get(&4), None);
|
|
// Make the schedule for the new transfer.
|
|
let new_vesting_schedule = VestingInfo::new(
|
|
ED * 5,
|
|
64, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
|
|
assert_noop!(
|
|
Vesting::force_vested_transfer(Some(4).into(), 3, 4, new_vesting_schedule),
|
|
BadOrigin
|
|
);
|
|
assert_ok!(Vesting::force_vested_transfer(
|
|
RawOrigin::Root.into(),
|
|
3,
|
|
4,
|
|
new_vesting_schedule
|
|
));
|
|
|
|
// Verify that the last events are `VestingCreated/VestingUpdated`.
|
|
assert_eq!(
|
|
vesting_events_since_last_call(),
|
|
vec![
|
|
Event::VestingCreated { account: 4, schedule_index: 0 },
|
|
Event::VestingUpdated { account: 4, unvested: 1280 },
|
|
]
|
|
);
|
|
// Now account 4 should have vesting.
|
|
assert_eq!(VestingStorage::<Test>::get(&4).unwrap()[0], new_vesting_schedule);
|
|
assert_eq!(VestingStorage::<Test>::get(&4).unwrap().len(), 1);
|
|
// Ensure the transfer happened correctly.
|
|
let user3_free_balance_updated = Balances::free_balance(&3);
|
|
assert_eq!(user3_free_balance_updated, ED * 25);
|
|
let user4_free_balance_updated = Balances::free_balance(&4);
|
|
assert_eq!(user4_free_balance_updated, ED * 45);
|
|
// Account 4 has 5 * ED locked.
|
|
assert_eq!(Vesting::vesting_balance(&4), Some(ED * 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));
|
|
// and after unlocking its schedules are removed from storage.
|
|
vest_and_assert_no_vesting::<Test>(4);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn force_vested_transfer_correctly_fails() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let user2_free_balance = Balances::free_balance(&2);
|
|
let user4_free_balance = Balances::free_balance(&4);
|
|
assert_eq!(user2_free_balance, ED * 20);
|
|
assert_eq!(user4_free_balance, ED * 40);
|
|
// Account 2 should already have a vesting schedule.
|
|
let user2_vesting_schedule = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![user2_vesting_schedule]);
|
|
|
|
// Too low transfer amount.
|
|
let new_vesting_schedule_too_low =
|
|
VestingInfo::new(<Test as Config>::MinVestedTransfer::get() - 1, 64, 10);
|
|
assert_noop!(
|
|
Vesting::force_vested_transfer(
|
|
RawOrigin::Root.into(),
|
|
3,
|
|
4,
|
|
new_vesting_schedule_too_low
|
|
),
|
|
Error::<Test>::AmountLow,
|
|
);
|
|
|
|
// `per_block` is 0.
|
|
let schedule_per_block_0 =
|
|
VestingInfo::new(<Test as Config>::MinVestedTransfer::get(), 0, 10);
|
|
assert_noop!(
|
|
Vesting::force_vested_transfer(RawOrigin::Root.into(), 13, 4, schedule_per_block_0),
|
|
Error::<Test>::InvalidScheduleParams,
|
|
);
|
|
|
|
// `locked` is 0.
|
|
let schedule_locked_0 = VestingInfo::new(0, 1, 10);
|
|
assert_noop!(
|
|
Vesting::force_vested_transfer(RawOrigin::Root.into(), 3, 4, schedule_locked_0),
|
|
Error::<Test>::AmountLow,
|
|
);
|
|
|
|
// Verify no currency transfer happened.
|
|
assert_eq!(user2_free_balance, Balances::free_balance(&2));
|
|
assert_eq!(user4_free_balance, Balances::free_balance(&4));
|
|
// Account 4 has no schedules.
|
|
vest_and_assert_no_vesting::<Test>(4);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn force_vested_transfer_allows_max_schedules() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let mut user_4_free_balance = Balances::free_balance(&4);
|
|
let max_schedules = <Test as Config>::MAX_VESTING_SCHEDULES;
|
|
let sched = VestingInfo::new(
|
|
<Test as Config>::MinVestedTransfer::get(),
|
|
1, // Vest over 2 * 256 blocks.
|
|
10,
|
|
);
|
|
|
|
// Add max amount schedules to user 4.
|
|
for _ in 0..max_schedules {
|
|
assert_ok!(Vesting::force_vested_transfer(RawOrigin::Root.into(), 13, 4, sched));
|
|
}
|
|
|
|
// The schedules count towards vesting balance.
|
|
let transferred_amount = <Test as Config>::MinVestedTransfer::get() * max_schedules as u64;
|
|
assert_eq!(Vesting::vesting_balance(&4), Some(transferred_amount));
|
|
// and free balance.
|
|
user_4_free_balance += transferred_amount;
|
|
assert_eq!(Balances::free_balance(&4), user_4_free_balance);
|
|
|
|
// Cannot insert a 4th vesting schedule when `MaxVestingSchedules` === 3
|
|
assert_noop!(
|
|
Vesting::force_vested_transfer(RawOrigin::Root.into(), 3, 4, sched),
|
|
Error::<Test>::AtMaxVestingSchedules,
|
|
);
|
|
// so the free balance does not change.
|
|
assert_eq!(Balances::free_balance(&4), user_4_free_balance);
|
|
|
|
// Account 4 has fully vested when all the schedules end,
|
|
System::set_block_number(<Test as Config>::MinVestedTransfer::get() + 10);
|
|
assert_eq!(Vesting::vesting_balance(&4), Some(0));
|
|
// and after unlocking its schedules are removed from storage.
|
|
vest_and_assert_no_vesting::<Test>(4);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_schedules_that_have_not_started() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
// Account 2 should already have a vesting schedule.
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vest over 20 blocks.
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
assert_eq!(Balances::usable_balance(&2), 0);
|
|
|
|
// Add a schedule that is identical to the one that already exists.
|
|
assert_ok!(Vesting::vested_transfer(Some(3).into(), 2, sched0));
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched0]);
|
|
assert_eq!(Balances::usable_balance(&2), 0);
|
|
assert_ok!(Vesting::merge_schedules(Some(2).into(), 0, 1));
|
|
|
|
// Since we merged identical schedules, the new schedule finishes at the same
|
|
// time as the original, just with double the amount.
|
|
let sched1 = VestingInfo::new(
|
|
sched0.locked() * 2,
|
|
sched0.per_block() * 2,
|
|
10, // Starts at the block the schedules are merged/
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched1]);
|
|
|
|
assert_eq!(Balances::usable_balance(&2), 0);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_ongoing_schedules() {
|
|
// Merging two schedules that have started will vest both before merging.
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
// Account 2 should already have a vesting schedule.
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vest over 20 blocks.
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
|
|
let sched1 = VestingInfo::new(
|
|
ED * 10,
|
|
// Vest over 10 blocks.
|
|
ED,
|
|
// Start at block 15.
|
|
sched0.starting_block() + 5,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 2, sched1));
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched1]);
|
|
|
|
// Got to half way through the second schedule where both schedules are actively vesting.
|
|
let cur_block = 20;
|
|
System::set_block_number(cur_block);
|
|
|
|
// Account 2 has no usable balances prior to the merge because they have not unlocked
|
|
// with `vest` yet.
|
|
assert_eq!(Balances::usable_balance(&2), 0);
|
|
|
|
assert_ok!(Vesting::merge_schedules(Some(2).into(), 0, 1));
|
|
|
|
// Merging schedules un-vests all pre-existing schedules prior to merging, which is
|
|
// reflected in account 2's updated usable balance.
|
|
let sched0_vested_now = sched0.per_block() * (cur_block - sched0.starting_block());
|
|
let sched1_vested_now = sched1.per_block() * (cur_block - sched1.starting_block());
|
|
assert_eq!(Balances::usable_balance(&2), sched0_vested_now + sched1_vested_now);
|
|
|
|
// The locked amount is the sum of what both schedules have locked at the current block.
|
|
let sched2_locked = sched1
|
|
.locked_at::<Identity>(cur_block)
|
|
.saturating_add(sched0.locked_at::<Identity>(cur_block));
|
|
// End block of the new schedule is the greater of either merged schedule.
|
|
let sched2_end = sched1
|
|
.ending_block_as_balance::<Identity>()
|
|
.max(sched0.ending_block_as_balance::<Identity>());
|
|
let sched2_duration = sched2_end - cur_block;
|
|
// Based off the new schedules total locked and its duration, we can calculate the
|
|
// amount to unlock per block.
|
|
let sched2_per_block = sched2_locked / sched2_duration;
|
|
|
|
let sched2 = VestingInfo::new(sched2_locked, sched2_per_block, cur_block);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched2]);
|
|
|
|
// And just to double check, we assert the new merged schedule we be cleaned up as expected.
|
|
System::set_block_number(30);
|
|
vest_and_assert_no_vesting::<Test>(2);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merging_shifts_other_schedules_index() {
|
|
// Schedules being merged are filtered out, schedules to the right of any merged
|
|
// schedule shift left and the merged schedule is always last.
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let sched0 = VestingInfo::new(
|
|
ED * 10,
|
|
ED, // Vesting over 10 blocks.
|
|
10,
|
|
);
|
|
let sched1 = VestingInfo::new(
|
|
ED * 11,
|
|
ED, // Vesting over 11 blocks.
|
|
11,
|
|
);
|
|
let sched2 = VestingInfo::new(
|
|
ED * 12,
|
|
ED, // Vesting over 12 blocks.
|
|
12,
|
|
);
|
|
|
|
// Account 3 starts out with no schedules,
|
|
assert_eq!(VestingStorage::<Test>::get(&3), None);
|
|
// and some usable balance.
|
|
let usable_balance = Balances::usable_balance(&3);
|
|
assert_eq!(usable_balance, 30 * ED);
|
|
|
|
let cur_block = 1;
|
|
assert_eq!(System::block_number(), cur_block);
|
|
|
|
// Transfer the above 3 schedules to account 3.
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 3, sched0));
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 3, sched1));
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 3, sched2));
|
|
|
|
// With no schedules vested or merged they are in the order they are created
|
|
assert_eq!(VestingStorage::<Test>::get(&3).unwrap(), vec![sched0, sched1, sched2]);
|
|
// and the usable balance has not changed.
|
|
assert_eq!(usable_balance, Balances::usable_balance(&3));
|
|
|
|
assert_ok!(Vesting::merge_schedules(Some(3).into(), 0, 2));
|
|
|
|
// Create the merged schedule of sched0 & sched2.
|
|
// The merged schedule will have the max possible starting block,
|
|
let sched3_start = sched1.starting_block().max(sched2.starting_block());
|
|
// `locked` equal to the sum of the two schedules locked through the current block,
|
|
let sched3_locked =
|
|
sched2.locked_at::<Identity>(cur_block) + sched0.locked_at::<Identity>(cur_block);
|
|
// and will end at the max possible block.
|
|
let sched3_end = sched2
|
|
.ending_block_as_balance::<Identity>()
|
|
.max(sched0.ending_block_as_balance::<Identity>());
|
|
let sched3_duration = sched3_end - sched3_start;
|
|
let sched3_per_block = sched3_locked / sched3_duration;
|
|
let sched3 = VestingInfo::new(sched3_locked, sched3_per_block, sched3_start);
|
|
|
|
// The not touched schedule moves left and the new merged schedule is appended.
|
|
assert_eq!(VestingStorage::<Test>::get(&3).unwrap(), vec![sched1, sched3]);
|
|
// The usable balance hasn't changed since none of the schedules have started.
|
|
assert_eq!(Balances::usable_balance(&3), usable_balance);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_ongoing_and_yet_to_be_started_schedules() {
|
|
// Merge an ongoing schedule that has had `vest` called and a schedule that has not already
|
|
// started.
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
// Account 2 should already have a vesting schedule.
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
|
|
// Fast forward to half way through the life of sched1.
|
|
let mut cur_block =
|
|
(sched0.starting_block() + sched0.ending_block_as_balance::<Identity>()) / 2;
|
|
assert_eq!(cur_block, 20);
|
|
System::set_block_number(cur_block);
|
|
|
|
// Prior to vesting there is no usable balance.
|
|
let mut usable_balance = 0;
|
|
assert_eq!(Balances::usable_balance(&2), usable_balance);
|
|
// Vest the current schedules (which is just sched0 now).
|
|
Vesting::vest(Some(2).into()).unwrap();
|
|
|
|
// After vesting the usable balance increases by the unlocked amount.
|
|
let sched0_vested_now = sched0.locked() - sched0.locked_at::<Identity>(cur_block);
|
|
usable_balance += sched0_vested_now;
|
|
assert_eq!(Balances::usable_balance(&2), usable_balance);
|
|
|
|
// Go forward a block.
|
|
cur_block += 1;
|
|
System::set_block_number(cur_block);
|
|
|
|
// And add a schedule that starts after this block, but before sched0 finishes.
|
|
let sched1 = VestingInfo::new(
|
|
ED * 10,
|
|
1, // Vesting over 256 * 10 (2560) blocks
|
|
cur_block + 1,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 2, sched1));
|
|
|
|
// Merge the schedules before sched1 starts.
|
|
assert_ok!(Vesting::merge_schedules(Some(2).into(), 0, 1));
|
|
// After merging, the usable balance only changes by the amount sched0 vested since we
|
|
// last called `vest` (which is just 1 block). The usable balance is not affected by
|
|
// sched1 because it has not started yet.
|
|
usable_balance += sched0.per_block();
|
|
assert_eq!(Balances::usable_balance(&2), usable_balance);
|
|
|
|
// The resulting schedule will have the later starting block of the two,
|
|
let sched2_start = sched1.starting_block();
|
|
// `locked` equal to the sum of the two schedules locked through the current block,
|
|
let sched2_locked =
|
|
sched0.locked_at::<Identity>(cur_block) + sched1.locked_at::<Identity>(cur_block);
|
|
// and will end at the max possible block.
|
|
let sched2_end = sched0
|
|
.ending_block_as_balance::<Identity>()
|
|
.max(sched1.ending_block_as_balance::<Identity>());
|
|
let sched2_duration = sched2_end - sched2_start;
|
|
let sched2_per_block = sched2_locked / sched2_duration;
|
|
|
|
let sched2 = VestingInfo::new(sched2_locked, sched2_per_block, sched2_start);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched2]);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_finished_and_ongoing_schedules() {
|
|
// If a schedule finishes by the current block we treat the ongoing schedule,
|
|
// without any alterations, as the merged one.
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
// Account 2 should already have a vesting schedule.
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // Vesting over 20 blocks.
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
|
|
let sched1 = VestingInfo::new(
|
|
ED * 40,
|
|
ED, // Vesting over 40 blocks.
|
|
10,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(4).into(), 2, sched1));
|
|
|
|
// Transfer a 3rd schedule, so we can demonstrate how schedule indices change.
|
|
// (We are not merging this schedule.)
|
|
let sched2 = VestingInfo::new(
|
|
ED * 30,
|
|
ED, // Vesting over 30 blocks.
|
|
10,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(3).into(), 2, sched2));
|
|
|
|
// The schedules are in expected order prior to merging.
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched1, sched2]);
|
|
|
|
// Fast forward to sched0's end block.
|
|
let cur_block = sched0.ending_block_as_balance::<Identity>();
|
|
System::set_block_number(cur_block);
|
|
assert_eq!(System::block_number(), 30);
|
|
|
|
// Prior to `merge_schedules` and with no vest/vest_other called the user has no usable
|
|
// balance.
|
|
assert_eq!(Balances::usable_balance(&2), 0);
|
|
assert_ok!(Vesting::merge_schedules(Some(2).into(), 0, 1));
|
|
|
|
// sched2 is now the first, since sched0 & sched1 get filtered out while "merging".
|
|
// sched1 gets treated like the new merged schedule by getting pushed onto back
|
|
// of the vesting schedules vec. Note: sched0 finished at the current block.
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched2, sched1]);
|
|
|
|
// sched0 has finished, so its funds are fully unlocked.
|
|
let sched0_unlocked_now = sched0.locked();
|
|
// The remaining schedules are ongoing, so their funds are partially unlocked.
|
|
let sched1_unlocked_now = sched1.locked() - sched1.locked_at::<Identity>(cur_block);
|
|
let sched2_unlocked_now = sched2.locked() - sched2.locked_at::<Identity>(cur_block);
|
|
|
|
// Since merging also vests all the schedules, the users usable balance after merging
|
|
// includes all pre-existing schedules unlocked through the current block, including
|
|
// schedules not merged.
|
|
assert_eq!(
|
|
Balances::usable_balance(&2),
|
|
sched0_unlocked_now + sched1_unlocked_now + sched2_unlocked_now
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_finishing_schedules_does_not_create_a_new_one() {
|
|
// If both schedules finish by the current block we don't create new one
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
// Account 2 should already have a vesting schedule.
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // 20 block duration.
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
|
|
// Create sched1 and transfer it to account 2.
|
|
let sched1 = VestingInfo::new(
|
|
ED * 30,
|
|
ED, // 30 block duration.
|
|
10,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(3).into(), 2, sched1));
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched1]);
|
|
|
|
let all_scheds_end = sched0
|
|
.ending_block_as_balance::<Identity>()
|
|
.max(sched1.ending_block_as_balance::<Identity>());
|
|
|
|
assert_eq!(all_scheds_end, 40);
|
|
System::set_block_number(all_scheds_end);
|
|
|
|
// Prior to merge_schedules and with no vest/vest_other called the user has no usable
|
|
// balance.
|
|
assert_eq!(Balances::usable_balance(&2), 0);
|
|
|
|
// Merge schedule 0 and 1.
|
|
assert_ok!(Vesting::merge_schedules(Some(2).into(), 0, 1));
|
|
// The user no longer has any more vesting schedules because they both ended at the
|
|
// block they where merged,
|
|
assert!(!<VestingStorage<Test>>::contains_key(&2));
|
|
// and their usable balance has increased by the total amount locked in the merged
|
|
// schedules.
|
|
assert_eq!(Balances::usable_balance(&2), sched0.locked() + sched1.locked());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_finished_and_yet_to_be_started_schedules() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
// Account 2 should already have a vesting schedule.
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // 20 block duration.
|
|
10, // Ends at block 30
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
|
|
let sched1 = VestingInfo::new(
|
|
ED * 30,
|
|
ED * 2, // 30 block duration.
|
|
35,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(13).into(), 2, sched1));
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched1]);
|
|
|
|
let sched2 = VestingInfo::new(
|
|
ED * 40,
|
|
ED, // 40 block duration.
|
|
30,
|
|
);
|
|
// Add a 3rd schedule to demonstrate how sched1 shifts.
|
|
assert_ok!(Vesting::vested_transfer(Some(13).into(), 2, sched2));
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched1, sched2]);
|
|
|
|
System::set_block_number(30);
|
|
|
|
// At block 30, sched0 has finished unlocking while sched1 and sched2 are still fully
|
|
// locked,
|
|
assert_eq!(Vesting::vesting_balance(&2), Some(sched1.locked() + sched2.locked()));
|
|
// but since we have not vested usable balance is still 0.
|
|
assert_eq!(Balances::usable_balance(&2), 0);
|
|
|
|
// Merge schedule 0 and 1.
|
|
assert_ok!(Vesting::merge_schedules(Some(2).into(), 0, 1));
|
|
|
|
// sched0 is removed since it finished, and sched1 is removed and then pushed on the back
|
|
// because it is treated as the merged schedule
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched2, sched1]);
|
|
|
|
// The usable balance is updated because merging fully unlocked sched0.
|
|
assert_eq!(Balances::usable_balance(&2), sched0.locked());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_schedules_throws_proper_errors() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
// Account 2 should already have a vesting schedule.
|
|
let sched0 = VestingInfo::new(
|
|
ED * 20,
|
|
ED, // 20 block duration.
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0]);
|
|
|
|
// Account 2 only has 1 vesting schedule.
|
|
assert_noop!(
|
|
Vesting::merge_schedules(Some(2).into(), 0, 1),
|
|
Error::<Test>::ScheduleIndexOutOfBounds
|
|
);
|
|
|
|
// Account 4 has 0 vesting schedules.
|
|
assert_eq!(VestingStorage::<Test>::get(&4), None);
|
|
assert_noop!(Vesting::merge_schedules(Some(4).into(), 0, 1), Error::<Test>::NotVesting);
|
|
|
|
// There are enough schedules to merge but an index is non-existent.
|
|
Vesting::vested_transfer(Some(3).into(), 2, sched0).unwrap();
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![sched0, sched0]);
|
|
assert_noop!(
|
|
Vesting::merge_schedules(Some(2).into(), 0, 2),
|
|
Error::<Test>::ScheduleIndexOutOfBounds
|
|
);
|
|
|
|
// It is a storage noop with no errors if the indexes are the same.
|
|
assert_storage_noop!(Vesting::merge_schedules(Some(2).into(), 0, 0).unwrap());
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn generates_multiple_schedules_from_genesis_config() {
|
|
let vesting_config = vec![
|
|
// 5 * existential deposit locked.
|
|
(1, 0, 10, 5 * ED),
|
|
// 1 * existential deposit locked.
|
|
(2, 10, 20, 19 * ED),
|
|
// 2 * existential deposit locked.
|
|
(2, 10, 20, 18 * ED),
|
|
// 1 * existential deposit locked.
|
|
(12, 10, 20, 9 * ED),
|
|
// 2 * existential deposit locked.
|
|
(12, 10, 20, 8 * ED),
|
|
// 3 * existential deposit locked.
|
|
(12, 10, 20, 7 * ED),
|
|
];
|
|
ExtBuilder::default()
|
|
.existential_deposit(ED)
|
|
.vesting_genesis_config(vesting_config)
|
|
.build()
|
|
.execute_with(|| {
|
|
let user1_sched1 = VestingInfo::new(5 * ED, 128, 0u64);
|
|
assert_eq!(VestingStorage::<Test>::get(&1).unwrap(), vec![user1_sched1]);
|
|
|
|
let user2_sched1 = VestingInfo::new(1 * ED, 12, 10u64);
|
|
let user2_sched2 = VestingInfo::new(2 * ED, 25, 10u64);
|
|
assert_eq!(VestingStorage::<Test>::get(&2).unwrap(), vec![user2_sched1, user2_sched2]);
|
|
|
|
let user12_sched1 = VestingInfo::new(1 * ED, 12, 10u64);
|
|
let user12_sched2 = VestingInfo::new(2 * ED, 25, 10u64);
|
|
let user12_sched3 = VestingInfo::new(3 * ED, 38, 10u64);
|
|
assert_eq!(
|
|
VestingStorage::<Test>::get(&12).unwrap(),
|
|
vec![user12_sched1, user12_sched2, user12_sched3]
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn multiple_schedules_from_genesis_config_errors() {
|
|
// MaxVestingSchedules is 3, but this config has 4 for account 12 so we panic when building
|
|
// from genesis.
|
|
let vesting_config =
|
|
vec![(12, 10, 20, ED), (12, 10, 20, ED), (12, 10, 20, ED), (12, 10, 20, ED)];
|
|
ExtBuilder::default()
|
|
.existential_deposit(ED)
|
|
.vesting_genesis_config(vesting_config)
|
|
.build();
|
|
}
|
|
|
|
#[test]
|
|
fn build_genesis_has_storage_version_v1() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
assert_eq!(StorageVersion::<Test>::get(), Releases::V1);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn merge_vesting_handles_per_block_0() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
let sched0 = VestingInfo::new(
|
|
ED, 0, // Vesting over 256 blocks.
|
|
1,
|
|
);
|
|
assert_eq!(sched0.ending_block_as_balance::<Identity>(), 257);
|
|
let sched1 = VestingInfo::new(
|
|
ED * 2,
|
|
0, // Vesting over 512 blocks.
|
|
10,
|
|
);
|
|
assert_eq!(sched1.ending_block_as_balance::<Identity>(), 512u64 + 10);
|
|
|
|
let merged = VestingInfo::new(764, 1, 10);
|
|
assert_eq!(Vesting::merge_vesting_info(5, sched0, sched1), Some(merged));
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vesting_info_validate_works() {
|
|
let min_transfer = <Test as Config>::MinVestedTransfer::get();
|
|
// Does not check for min transfer.
|
|
assert_eq!(VestingInfo::new(min_transfer - 1, 1u64, 10u64).is_valid(), true);
|
|
|
|
// `locked` cannot be 0.
|
|
assert_eq!(VestingInfo::new(0, 1u64, 10u64).is_valid(), false);
|
|
|
|
// `per_block` cannot be 0.
|
|
assert_eq!(VestingInfo::new(min_transfer + 1, 0u64, 10u64).is_valid(), false);
|
|
|
|
// With valid inputs it does not error.
|
|
assert_eq!(VestingInfo::new(min_transfer, 1u64, 10u64).is_valid(), true);
|
|
}
|
|
|
|
#[test]
|
|
fn vesting_info_ending_block_as_balance_works() {
|
|
// Treats `per_block` 0 as 1.
|
|
let per_block_0 = VestingInfo::new(256u32, 0u32, 10u32);
|
|
assert_eq!(per_block_0.ending_block_as_balance::<Identity>(), 256 + 10);
|
|
|
|
// `per_block >= locked` always results in a schedule ending the block after it starts
|
|
let per_block_gt_locked = VestingInfo::new(256u32, 256 * 2u32, 10u32);
|
|
assert_eq!(
|
|
per_block_gt_locked.ending_block_as_balance::<Identity>(),
|
|
1 + per_block_gt_locked.starting_block()
|
|
);
|
|
let per_block_eq_locked = VestingInfo::new(256u32, 256u32, 10u32);
|
|
assert_eq!(
|
|
per_block_gt_locked.ending_block_as_balance::<Identity>(),
|
|
per_block_eq_locked.ending_block_as_balance::<Identity>()
|
|
);
|
|
|
|
// Correctly calcs end if `locked % per_block != 0`. (We need a block to unlock the remainder).
|
|
let imperfect_per_block = VestingInfo::new(256u32, 250u32, 10u32);
|
|
assert_eq!(
|
|
imperfect_per_block.ending_block_as_balance::<Identity>(),
|
|
imperfect_per_block.starting_block() + 2u32,
|
|
);
|
|
assert_eq!(
|
|
imperfect_per_block
|
|
.locked_at::<Identity>(imperfect_per_block.ending_block_as_balance::<Identity>()),
|
|
0
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn per_block_works() {
|
|
let per_block_0 = VestingInfo::new(256u32, 0u32, 10u32);
|
|
assert_eq!(per_block_0.per_block(), 1u32);
|
|
assert_eq!(per_block_0.raw_per_block(), 0u32);
|
|
|
|
let per_block_1 = VestingInfo::new(256u32, 1u32, 10u32);
|
|
assert_eq!(per_block_1.per_block(), 1u32);
|
|
assert_eq!(per_block_1.raw_per_block(), 1u32);
|
|
}
|
|
|
|
// When an accounts free balance + schedule.locked is less than ED, the vested transfer will fail.
|
|
#[test]
|
|
fn vested_transfer_less_than_existential_deposit_fails() {
|
|
ExtBuilder::default().existential_deposit(4 * ED).build().execute_with(|| {
|
|
// MinVestedTransfer is less the ED.
|
|
assert!(
|
|
<Test as Config>::Currency::minimum_balance()
|
|
> <Test as Config>::MinVestedTransfer::get()
|
|
);
|
|
|
|
let sched =
|
|
VestingInfo::new(<Test as Config>::MinVestedTransfer::get() as u64, 1u64, 10u64);
|
|
// The new account balance with the schedule's locked amount would be less than ED.
|
|
assert!(
|
|
Balances::free_balance(&99) + sched.locked()
|
|
< <Test as Config>::Currency::minimum_balance()
|
|
);
|
|
|
|
// vested_transfer fails.
|
|
assert_noop!(Vesting::vested_transfer(Some(3).into(), 99, sched), TokenError::BelowMinimum,);
|
|
// force_vested_transfer fails.
|
|
assert_noop!(
|
|
Vesting::force_vested_transfer(RawOrigin::Root.into(), 3, 99, sched),
|
|
TokenError::BelowMinimum,
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn remove_vesting_schedule() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
assert_eq!(Balances::free_balance(&3), 256 * 30);
|
|
assert_eq!(Balances::free_balance(&4), 256 * 40);
|
|
// Account 4 should not have any vesting yet.
|
|
assert_eq!(VestingStorage::<Test>::get(&4), None);
|
|
// Make the schedule for the new transfer.
|
|
let new_vesting_schedule = VestingInfo::new(
|
|
ED * 5,
|
|
(ED * 5) / 20, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
assert_ok!(Vesting::vested_transfer(Some(3).into(), 4, new_vesting_schedule));
|
|
// Verify that the last events are `VestingCreated/VestingUpdated`.
|
|
assert_eq!(
|
|
vesting_events_since_last_call(),
|
|
vec![
|
|
Event::VestingCreated { account: 4, schedule_index: 0 },
|
|
Event::VestingUpdated { account: 4, unvested: 1280 },
|
|
]
|
|
);
|
|
|
|
// Now account 4 should have vesting.
|
|
assert_eq!(VestingStorage::<Test>::get(&4).unwrap(), vec![new_vesting_schedule]);
|
|
// Account 4 has 5 * 256 locked.
|
|
assert_eq!(Vesting::vesting_balance(&4), Some(256 * 5));
|
|
// Verify only root can call.
|
|
assert_noop!(Vesting::force_remove_vesting_schedule(Some(4).into(), 4, 0), BadOrigin);
|
|
// Verify that root can remove the schedule.
|
|
assert_ok!(Vesting::force_remove_vesting_schedule(RawOrigin::Root.into(), 4, 0));
|
|
// Verify that last event is VestingCompleted.
|
|
System::assert_last_event(Event::VestingCompleted { account: 4 }.into());
|
|
// Appropriate storage is cleaned up.
|
|
assert!(!<VestingStorage<Test>>::contains_key(4));
|
|
// Check the vesting balance is zero.
|
|
assert_eq!(VestingStorage::<Test>::get(&4), None);
|
|
// Verifies that trying to remove a schedule when it doesnt exist throws error.
|
|
assert_noop!(
|
|
Vesting::force_remove_vesting_schedule(RawOrigin::Root.into(), 4, 0),
|
|
Error::<Test>::InvalidScheduleParams
|
|
);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn vested_transfer_impl_works() {
|
|
ExtBuilder::default().existential_deposit(ED).build().execute_with(|| {
|
|
assert_eq!(Balances::free_balance(&3), 256 * 30);
|
|
assert_eq!(Balances::free_balance(&4), 256 * 40);
|
|
// Account 4 should not have any vesting yet.
|
|
assert_eq!(VestingStorage::<Test>::get(&4), None);
|
|
|
|
// Basic working scenario
|
|
assert_ok!(<Vesting as VestedTransfer<_>>::vested_transfer(
|
|
&3,
|
|
&4,
|
|
ED * 5,
|
|
ED * 5 / 20,
|
|
10
|
|
));
|
|
// Now account 4 should have vesting.
|
|
let new_vesting_schedule = VestingInfo::new(
|
|
ED * 5,
|
|
(ED * 5) / 20, // Vesting over 20 blocks
|
|
10,
|
|
);
|
|
assert_eq!(VestingStorage::<Test>::get(&4).unwrap(), vec![new_vesting_schedule]);
|
|
// Account 4 has 5 * 256 locked.
|
|
assert_eq!(Vesting::vesting_balance(&4), Some(256 * 5));
|
|
|
|
// If the transfer fails (because they don't have enough balance), no storage is changed.
|
|
assert_noop!(
|
|
<Vesting as VestedTransfer<_>>::vested_transfer(&3, &4, ED * 9999, ED * 5 / 20, 10),
|
|
TokenError::FundsUnavailable
|
|
);
|
|
|
|
// If applying the vesting schedule fails (per block is 0), no storage is changed.
|
|
assert_noop!(
|
|
<Vesting as VestedTransfer<_>>::vested_transfer(&3, &4, ED * 5, 0, 10),
|
|
Error::<Test>::InvalidScheduleParams
|
|
);
|
|
});
|
|
}
|