Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BoundedVec to vesting #497

Merged
merged 3 commits into from
May 19, 2021
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 41 additions & 31 deletions vesting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ use codec::HasCompact;
use frame_support::{
ensure,
pallet_prelude::*,
traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, WithdrawReasons},
transactional,
traits::{
Currency, EnsureOrigin, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, MaxEncodedLen,
WithdrawReasons,
},
transactional, BoundedVec,
};
use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};
use sp_runtime::{
Expand All @@ -41,6 +44,7 @@ use sp_runtime::{
};
use sp_std::{
cmp::{Eq, PartialEq},
convert::TryInto,
vec::Vec,
};

Expand All @@ -51,16 +55,13 @@ mod weights;
pub use module::*;
pub use weights::WeightInfo;

/// The maximum number of vesting schedules an account can have.
pub const MAX_VESTINGS: usize = 20;

pub const VESTING_LOCK_ID: LockIdentifier = *b"ormlvest";

/// The vesting schedule.
///
/// Benefits would be granted gradually, `per_period` amount every `period`
/// of blocks after `start`.
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, MaxEncodedLen)]
pub struct VestingSchedule<BlockNumber, Balance: HasCompact> {
/// Vesting starting block
pub start: BlockNumber,
Expand Down Expand Up @@ -136,6 +137,9 @@ pub mod module {

/// Weight information for extrinsics in this module.
type WeightInfo: WeightInfo;

/// The maximum vesting schedules
type MaxVestingSchedules: Get<u32>;
}

#[pallet::error]
Expand All @@ -150,6 +154,8 @@ pub mod module {
TooManyVestingSchedules,
/// The vested transfer amount is too low
AmountLow,
/// Failed because the maximum vesting schedules was exceeded
MaxVestingSchedulesExceeded,
}

#[pallet::event]
Expand All @@ -166,8 +172,13 @@ pub mod module {
/// Vesting schedules of an account.
#[pallet::storage]
#[pallet::getter(fn vesting_schedules)]
pub type VestingSchedules<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, Vec<VestingScheduleOf<T>>, ValueQuery>;
pub type VestingSchedules<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
BoundedVec<VestingScheduleOf<T>, T::MaxVestingSchedules>,
ValueQuery,
>;

#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
Expand All @@ -189,21 +200,23 @@ pub mod module {
.for_each(|(who, start, period, period_count, per_period)| {
let total = *per_period * Into::<BalanceOf<T>>::into(*period_count);

assert!(
T::Currency::free_balance(who) >= total,
"Account do not have enough balance"
);

T::Currency::set_lock(VESTING_LOCK_ID, who, total, WithdrawReasons::all());
VestingSchedules::<T>::insert(
who,
let bounded_schedule: BoundedVec<VestingScheduleOf<T>, T::MaxVestingSchedules> =
vec![VestingSchedule {
start: *start,
period: *period,
period_count: *period_count,
per_period: *per_period,
}],
}]
.try_into()
.expect("Max vesting schedules exceeded");

assert!(
T::Currency::free_balance(who) >= total,
"Account do not have enough balance"
);

T::Currency::set_lock(VESTING_LOCK_ID, who, total, WithdrawReasons::all());
VestingSchedules::<T>::insert(who, bounded_schedule);
});
}
}
Expand All @@ -216,8 +229,7 @@ pub mod module {

#[pallet::call]
impl<T: Config> Pallet<T> {
// can not get VestingSchedule count from `who`, so use `MAX_VESTINGS / 2`
#[pallet::weight(T::WeightInfo::claim((MAX_VESTINGS / 2) as u32))]
#[pallet::weight(T::WeightInfo::claim((<T as Config>::MaxVestingSchedules::get() / 2) as u32))]
pub fn claim(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let locked_amount = Self::do_claim(&who);
Expand Down Expand Up @@ -294,36 +306,34 @@ impl<T: Config> Pallet<T> {
fn do_vested_transfer(from: &T::AccountId, to: &T::AccountId, schedule: VestingScheduleOf<T>) -> DispatchResult {
let schedule_amount = Self::ensure_valid_vesting_schedule(&schedule)?;

ensure!(
<VestingSchedules<T>>::decode_len(to).unwrap_or(0) < MAX_VESTINGS,
Error::<T>::TooManyVestingSchedules
);

let total_amount = Self::locked_balance(to)
.checked_add(&schedule_amount)
.ok_or(ArithmeticError::Overflow)?;

T::Currency::transfer(from, to, schedule_amount, ExistenceRequirement::AllowDeath)?;
T::Currency::set_lock(VESTING_LOCK_ID, to, total_amount, WithdrawReasons::all());
<VestingSchedules<T>>::append(to, schedule);
<VestingSchedules<T>>::try_append(to, schedule).map_err(|_| Error::<T>::MaxVestingSchedulesExceeded)?;
Ok(())
}

fn do_update_vesting_schedules(who: &T::AccountId, schedules: Vec<VestingScheduleOf<T>>) -> DispatchResult {
let total_amount = schedules.iter().try_fold::<_, _, Result<BalanceOf<T>, DispatchError>>(
Zero::zero(),
|acc_amount, schedule| {
let bounded_schedules: BoundedVec<VestingScheduleOf<T>, T::MaxVestingSchedules> = schedules
.try_into()
.map_err(|_| Error::<T>::MaxVestingSchedulesExceeded)?;

let total_amount = bounded_schedules
.iter()
.try_fold::<_, _, Result<BalanceOf<T>, DispatchError>>(Zero::zero(), |acc_amount, schedule| {
let amount = Self::ensure_valid_vesting_schedule(schedule)?;
Ok(acc_amount + amount)
},
)?;
})?;
ensure!(
T::Currency::free_balance(who) >= total_amount,
Error::<T>::InsufficientBalanceToLock,
);

T::Currency::set_lock(VESTING_LOCK_ID, who, total_amount, WithdrawReasons::all());
<VestingSchedules<T>>::insert(who, schedules);
<VestingSchedules<T>>::insert(who, bounded_schedules);

Ok(())
}
Expand Down
5 changes: 5 additions & 0 deletions vesting/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,17 @@ impl EnsureOrigin<Origin> for EnsureAliceOrBob {
}
}

parameter_types! {
pub const MaxVestingSchedule: u32 = 2;
}

impl Config for Runtime {
type Event = Event;
type Currency = PalletBalances;
type MinVestedTransfer = MinVestedTransfer;
type VestedTransferOrigin = EnsureAliceOrBob;
type WeightInfo = ();
type MaxVestingSchedules = MaxVestingSchedule;
}

type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
Expand Down
29 changes: 27 additions & 2 deletions vesting/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,15 @@ fn vested_transfer_fails_if_overflow() {
start: 1u64,
period: 1u64,
period_count: 2u32,
per_period: u64::max_value(),
per_period: u64::MAX,
};
assert_noop!(
Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule),
ArithmeticError::Overflow,
);

let another_schedule = VestingSchedule {
start: u64::max_value(),
start: u64::MAX,
period: 1u64,
period_count: 2u32,
per_period: 1u64,
Expand Down Expand Up @@ -329,3 +329,28 @@ fn multiple_vesting_schedule_claim_works() {
assert_eq!(PalletBalances::locks(&BOB), vec![]);
});
}

#[test]
fn exceeding_maximum_schedules_should_fail() {
ExtBuilder::build().execute_with(|| {
let schedule = VestingSchedule {
start: 0u64,
period: 10u64,
period_count: 2u32,
per_period: 10u64,
};
assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule.clone()));
assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule.clone()));
assert_noop!(
Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule.clone()),
Error::<Runtime>::MaxVestingSchedulesExceeded
);

let schedules = vec![schedule.clone(), schedule.clone(), schedule.clone()];

assert_noop!(
Vesting::update_vesting_schedules(Origin::root(), BOB, schedules),
Error::<Runtime>::MaxVestingSchedulesExceeded
);
});
}