difftreelog
Orml-vesting fix
in: master
8 files changed
pallets/orml-vesting/ README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/orml-vesting/ README.md
@@ -0,0 +1,9 @@
+# Vesting Module
+
+## Overview
+
+Vesting module provides a means of scheduled balance lock on an account. It uses the *graded vesting* way, which unlocks a specific amount of balance every period of time, until all balance unlocked.
+
+### Vesting Schedule
+
+The schedule of a vesting is described by data structure `VestingSchedule`: from the block number of `start`, for every `period` amount of blocks, `per_period` amount of balance would unlocked, until number of periods `period_count` reached. Note in vesting schedules, *time* is measured by block number. All `VestingSchedule`s under an account could be queried in chain state.
pallets/orml-vesting/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/orml-vesting/Cargo.toml
@@ -0,0 +1,40 @@
+[package]
+name = "orml-vesting"
+description = "Provides scheduled balance locking mechanism, in a *graded vesting* way."
+license = "Apache-2.0"
+version = "0.4.1-dev"
+authors = ["Laminar Developers <hello@laminar.one>"]
+edition = "2018"
+
+[dependencies]
+scale-info = { version = "1.0", default-features = false, features = ["derive"] }
+serde = { version = "1.0.124", optional = true }
+codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false, features = ["max-encoded-len"] }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12", default-features = false }
+
+[dev-dependencies]
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" }
+pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" }
+
+[features]
+default = ["std"]
+std = [
+ "serde",
+ "codec/std",
+ "scale-info/std",
+ "sp-runtime/std",
+ "sp-std/std",
+ "sp-io/std",
+ "frame-support/std",
+ "frame-system/std",
+]
+runtime-benchmarks = [
+ "frame-support/runtime-benchmarks",
+ "frame-system/runtime-benchmarks",
+]
+try-runtime = ["frame-support/try-runtime"]
pallets/orml-vesting/src/default_weight.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/orml-vesting/src/default_weight.rs
@@ -0,0 +1,27 @@
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0
+
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::weights::{constants::RocksDbWeight as DbWeight, Weight};
+
+impl crate::WeightInfo for () {
+ fn vested_transfer() -> Weight {
+ (310_862_000 as Weight)
+ .saturating_add(DbWeight::get().reads(4 as Weight))
+ .saturating_add(DbWeight::get().writes(4 as Weight))
+ }
+ fn claim(i: u32) -> Weight {
+ (158_614_000 as Weight)
+ .saturating_add((958_000 as Weight).saturating_mul(i as Weight))
+ .saturating_add(DbWeight::get().reads(3 as Weight))
+ .saturating_add(DbWeight::get().writes(3 as Weight))
+ }
+ fn update_vesting_schedules(i: u32) -> Weight {
+ (119_811_000 as Weight)
+ .saturating_add((2_320_000 as Weight).saturating_mul(i as Weight))
+ .saturating_add(DbWeight::get().reads(2 as Weight))
+ .saturating_add(DbWeight::get().writes(3 as Weight))
+ }
+}
pallets/orml-vesting/src/lib.rsdiffbeforeafterboth1//! # Vesting Module2//!3//! ## Overview4//!5//! Vesting module provides a means of scheduled balance lock on an account. It6//! uses the *graded vesting* way, which unlocks a specific amount of balance7//! every period of time, until all balance unlocked.8//!9//! ### Vesting Schedule10//!11//! The schedule of a vesting is described by data structure `VestingSchedule`:12//! from the block number of `start`, for every `period` amount of blocks,13//! `per_period` amount of balance would unlocked, until number of periods14//! `period_count` reached. Note in vesting schedules, *time* is measured by15//! block number. All `VestingSchedule`s under an account could be queried in16//! chain state.17//!18//! ## Interface19//!20//! ### Dispatchable Functions21//!22//! - `vested_transfer` - Add a new vesting schedule for an account.23//! - `claim` - Claim unlocked balances.24//! - `update_vesting_schedules` - Update all vesting schedules under an25//! account, `root` origin required.2627#![cfg_attr(not(feature = "std"), no_std)]28#![allow(clippy::unused_unit)]2930use codec::{HasCompact, MaxEncodedLen};31use frame_support::{32 ensure,33 pallet_prelude::*,34 traits::{Currency, EnsureOrigin, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, WithdrawReasons},35 transactional, BoundedVec,36};37use frame_system::{ensure_root, ensure_signed, pallet_prelude::*};38use scale_info::TypeInfo;39use sp_runtime::{40 traits::{AtLeast32Bit, BlockNumberProvider, CheckedAdd, Saturating, StaticLookup, Zero},41 ArithmeticError, DispatchResult, RuntimeDebug,42};43use sp_std::{44 cmp::{Eq, PartialEq},45 convert::TryInto,46 vec::Vec,47};4849mod mock;50mod tests;51mod weights;5253pub use module::*;54pub use weights::WeightInfo;5556pub const VESTING_LOCK_ID: LockIdentifier = *b"ormlvest";5758/// The vesting schedule.59///60/// Benefits would be granted gradually, `per_period` amount every `period`61/// of blocks after `start`.62#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)]63pub struct VestingSchedule<BlockNumber, Balance: HasCompact> {64 /// Vesting starting block65 pub start: BlockNumber,66 /// Number of blocks between vest67 pub period: BlockNumber,68 /// Number of vest69 pub period_count: u32,70 /// Amount of tokens to release per vest71 #[codec(compact)]72 pub per_period: Balance,73}7475impl<BlockNumber: AtLeast32Bit + Copy, Balance: AtLeast32Bit + Copy> VestingSchedule<BlockNumber, Balance> {76 /// Returns the end of all periods, `None` if calculation overflows.77 pub fn end(&self) -> Option<BlockNumber> {78 // period * period_count + start79 self.period80 .checked_mul(&self.period_count.into())?81 .checked_add(&self.start)82 }8384 /// Returns all locked amount, `None` if calculation overflows.85 pub fn total_amount(&self) -> Option<Balance> {86 self.per_period.checked_mul(&self.period_count.into())87 }8889 /// Returns locked amount for a given `time`.90 ///91 /// Note this func assumes schedule is a valid one(non-zero period and92 /// non-overflow total amount), and it should be guaranteed by callers.93 pub fn locked_amount(&self, time: BlockNumber) -> Balance {94 // full = (time - start) / period95 // unrealized = period_count - full96 // per_period * unrealized97 let full = time98 .saturating_sub(self.start)99 .checked_div(&self.period)100 .expect("ensured non-zero period; qed");101 let unrealized = self.period_count.saturating_sub(full.unique_saturated_into());102 self.per_period103 .checked_mul(&unrealized.into())104 .expect("ensured non-overflow total amount; qed")105 }106}107108#[frame_support::pallet]109pub mod module {110 use super::*;111112 pub(crate) type BalanceOf<T> =113 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;114 pub(crate) type VestingScheduleOf<T> = VestingSchedule<<T as frame_system::Config>::BlockNumber, BalanceOf<T>>;115 pub type ScheduledItem<T> = (116 <T as frame_system::Config>::AccountId,117 <T as frame_system::Config>::BlockNumber,118 <T as frame_system::Config>::BlockNumber,119 u32,120 BalanceOf<T>,121 );122123 #[pallet::config]124 pub trait Config: frame_system::Config {125 type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;126127 type Currency: LockableCurrency<Self::AccountId, Moment = Self::BlockNumber>;128129 #[pallet::constant]130 /// The minimum amount transferred to call `vested_transfer`.131 type MinVestedTransfer: Get<BalanceOf<Self>>;132133 /// Required origin for vested transfer.134 type VestedTransferOrigin: EnsureOrigin<Self::Origin, Success = Self::AccountId>;135136 /// Weight information for extrinsics in this module.137 type WeightInfo: WeightInfo;138139 /// The maximum vesting schedules140 type MaxVestingSchedules: Get<u32>;141142 // The block number provider143 type BlockNumberProvider: BlockNumberProvider<BlockNumber = Self::BlockNumber>;144 }145146 #[pallet::error]147 pub enum Error<T> {148 /// Vesting period is zero149 ZeroVestingPeriod,150 /// Number of vests is zero151 ZeroVestingPeriodCount,152 /// Insufficient amount of balance to lock153 InsufficientBalanceToLock,154 /// This account have too many vesting schedules155 TooManyVestingSchedules,156 /// The vested transfer amount is too low157 AmountLow,158 /// Failed because the maximum vesting schedules was exceeded159 MaxVestingSchedulesExceeded,160 }161162 #[pallet::event]163 #[pallet::generate_deposit(fn deposit_event)]164 pub enum Event<T: Config> {165 /// Added new vesting schedule. \[from, to, vesting_schedule\]166 VestingScheduleAdded(T::AccountId, T::AccountId, VestingScheduleOf<T>),167 /// Claimed vesting. \[who, locked_amount\]168 Claimed(T::AccountId, BalanceOf<T>),169 /// Updated vesting schedules. \[who\]170 VestingSchedulesUpdated(T::AccountId),171 }172173 /// Vesting schedules of an account.174 ///175 /// VestingSchedules: map AccountId => Vec<VestingSchedule>176 #[pallet::storage]177 #[pallet::getter(fn vesting_schedules)]178 pub type VestingSchedules<T: Config> = StorageMap<179 _,180 Blake2_128Concat,181 T::AccountId,182 BoundedVec<VestingScheduleOf<T>, T::MaxVestingSchedules>,183 ValueQuery,184 >;185186 #[pallet::genesis_config]187 pub struct GenesisConfig<T: Config> {188 pub vesting: Vec<ScheduledItem<T>>,189 }190191 #[cfg(feature = "std")]192 impl<T: Config> Default for GenesisConfig<T> {193 fn default() -> Self {194 GenesisConfig { vesting: vec![] }195 }196 }197198 #[pallet::genesis_build]199 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {200 fn build(&self) {201 self.vesting202 .iter()203 .for_each(|(who, start, period, period_count, per_period)| {204 let mut bounded_schedules = VestingSchedules::<T>::get(who);205 bounded_schedules206 .try_push(VestingSchedule {207 start: *start,208 period: *period,209 period_count: *period_count,210 per_period: *per_period,211 })212 .expect("Max vesting schedules exceeded");213 let total_amount = bounded_schedules214 .iter()215 .try_fold::<_, _, Result<BalanceOf<T>, DispatchError>>(Zero::zero(), |acc_amount, schedule| {216 let amount = ensure_valid_vesting_schedule::<T>(schedule)?;217 Ok(acc_amount + amount)218 })219 .expect("Invalid vesting schedule");220221 assert!(222 T::Currency::free_balance(who) >= total_amount,223 "Account do not have enough balance"224 );225226 T::Currency::set_lock(VESTING_LOCK_ID, who, total_amount, WithdrawReasons::all());227 VestingSchedules::<T>::insert(who, bounded_schedules);228 });229 }230 }231232 #[pallet::pallet]233 pub struct Pallet<T>(_);234235 #[pallet::hooks]236 impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {}237238 #[pallet::call]239 impl<T: Config> Pallet<T> {240 #[pallet::weight(T::WeightInfo::claim((<T as Config>::MaxVestingSchedules::get() / 2) as u32))]241 pub fn claim(origin: OriginFor<T>) -> DispatchResult {242 let who = ensure_signed(origin)?;243 let locked_amount = Self::do_claim(&who);244245 Self::deposit_event(Event::Claimed(who, locked_amount));246 Ok(())247 }248249 #[pallet::weight(T::WeightInfo::vested_transfer())]250 pub fn vested_transfer(251 origin: OriginFor<T>,252 dest: <T::Lookup as StaticLookup>::Source,253 schedule: VestingScheduleOf<T>,254 ) -> DispatchResult {255 let from = T::VestedTransferOrigin::ensure_origin(origin)?;256 let to = T::Lookup::lookup(dest)?;257 Self::do_vested_transfer(&from, &to, schedule.clone())?;258259 Self::deposit_event(Event::VestingScheduleAdded(from, to, schedule));260 Ok(())261 }262263 #[pallet::weight(T::WeightInfo::update_vesting_schedules(vesting_schedules.len() as u32))]264 pub fn update_vesting_schedules(265 origin: OriginFor<T>,266 who: <T::Lookup as StaticLookup>::Source,267 vesting_schedules: Vec<VestingScheduleOf<T>>,268 ) -> DispatchResult {269 ensure_root(origin)?;270271 let account = T::Lookup::lookup(who)?;272 Self::do_update_vesting_schedules(&account, vesting_schedules)?;273274 Self::deposit_event(Event::VestingSchedulesUpdated(account));275 Ok(())276 }277278 #[pallet::weight(T::WeightInfo::claim((<T as Config>::MaxVestingSchedules::get() / 2) as u32))]279 pub fn claim_for(origin: OriginFor<T>, dest: <T::Lookup as StaticLookup>::Source) -> DispatchResult {280 let _ = ensure_signed(origin)?;281 let who = T::Lookup::lookup(dest)?;282 let locked_amount = Self::do_claim(&who);283284 Self::deposit_event(Event::Claimed(who, locked_amount));285 Ok(())286 }287 }288}289290impl<T: Config> Pallet<T> {291 fn do_claim(who: &T::AccountId) -> BalanceOf<T> {292 let locked = Self::locked_balance(who);293 if locked.is_zero() {294 // cleanup the storage and unlock the fund295 <VestingSchedules<T>>::remove(who);296 T::Currency::remove_lock(VESTING_LOCK_ID, who);297 } else {298 T::Currency::set_lock(VESTING_LOCK_ID, who, locked, WithdrawReasons::all());299 }300 locked301 }302303 /// Returns locked balance based on current block number.304 fn locked_balance(who: &T::AccountId) -> BalanceOf<T> {305 let now = T::BlockNumberProvider::current_block_number();306 <VestingSchedules<T>>::mutate_exists(who, |maybe_schedules| {307 let total = if let Some(schedules) = maybe_schedules.as_mut() {308 let mut total: BalanceOf<T> = Zero::zero();309 schedules.retain(|s| {310 let amount = s.locked_amount(now);311 total = total.saturating_add(amount);312 !amount.is_zero()313 });314 total315 } else {316 Zero::zero()317 };318 if total.is_zero() {319 *maybe_schedules = None;320 }321 total322 })323 }324325 #[transactional]326 fn do_vested_transfer(from: &T::AccountId, to: &T::AccountId, schedule: VestingScheduleOf<T>) -> DispatchResult {327 let schedule_amount = ensure_valid_vesting_schedule::<T>(&schedule)?;328329 let total_amount = Self::locked_balance(to)330 .checked_add(&schedule_amount)331 .ok_or(ArithmeticError::Overflow)?;332333 T::Currency::transfer(from, to, schedule_amount, ExistenceRequirement::AllowDeath)?;334 T::Currency::set_lock(VESTING_LOCK_ID, to, total_amount, WithdrawReasons::all());335 <VestingSchedules<T>>::try_append(to, schedule).map_err(|_| Error::<T>::MaxVestingSchedulesExceeded)?;336 Ok(())337 }338339 fn do_update_vesting_schedules(who: &T::AccountId, schedules: Vec<VestingScheduleOf<T>>) -> DispatchResult {340 let bounded_schedules: BoundedVec<VestingScheduleOf<T>, T::MaxVestingSchedules> = schedules341 .try_into()342 .map_err(|_| Error::<T>::MaxVestingSchedulesExceeded)?;343344 // empty vesting schedules cleanup the storage and unlock the fund345 if bounded_schedules.len().is_zero() {346 <VestingSchedules<T>>::remove(who);347 T::Currency::remove_lock(VESTING_LOCK_ID, who);348 return Ok(());349 }350351 let total_amount = bounded_schedules352 .iter()353 .try_fold::<_, _, Result<BalanceOf<T>, DispatchError>>(Zero::zero(), |acc_amount, schedule| {354 let amount = ensure_valid_vesting_schedule::<T>(schedule)?;355 Ok(acc_amount + amount)356 })?;357 ensure!(358 T::Currency::free_balance(who) >= total_amount,359 Error::<T>::InsufficientBalanceToLock,360 );361362 T::Currency::set_lock(VESTING_LOCK_ID, who, total_amount, WithdrawReasons::all());363 <VestingSchedules<T>>::insert(who, bounded_schedules);364365 Ok(())366 }367}368369/// Returns `Ok(total_total)` if valid schedule, or error.370fn ensure_valid_vesting_schedule<T: Config>(schedule: &VestingScheduleOf<T>) -> Result<BalanceOf<T>, DispatchError> {371 ensure!(!schedule.period.is_zero(), Error::<T>::ZeroVestingPeriod);372 ensure!(!schedule.period_count.is_zero(), Error::<T>::ZeroVestingPeriodCount);373 ensure!(schedule.end().is_some(), ArithmeticError::Overflow);374375 let total_total = schedule.total_amount().ok_or(ArithmeticError::Overflow)?;376377 ensure!(total_total >= T::MinVestedTransfer::get(), Error::<T>::AmountLow);378379 Ok(total_total)380}pallets/orml-vesting/src/mock.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/orml-vesting/src/mock.rs
@@ -0,0 +1,153 @@
+//! Mocks for the vesting module.
+
+#![cfg(test)]
+
+use super::*;
+use frame_support::{
+ construct_runtime, parameter_types,
+ traits::{EnsureOrigin, Everything},
+};
+use frame_system::RawOrigin;
+use sp_core::H256;
+use sp_runtime::{testing::Header, traits::IdentityLookup};
+
+use crate as vesting;
+
+parameter_types! {
+ pub const BlockHashCount: u64 = 250;
+}
+
+pub type AccountId = u128;
+impl frame_system::Config for Runtime {
+ type Origin = Origin;
+ type Call = Call;
+ type Index = u64;
+ type BlockNumber = u64;
+ type Hash = H256;
+ type Hashing = ::sp_runtime::traits::BlakeTwo256;
+ type AccountId = AccountId;
+ type Lookup = IdentityLookup<Self::AccountId>;
+ type Header = Header;
+ type Event = Event;
+ type BlockHashCount = BlockHashCount;
+ type BlockWeights = ();
+ type BlockLength = ();
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = pallet_balances::AccountData<u64>;
+ type OnNewAccount = ();
+ type OnKilledAccount = ();
+ type DbWeight = ();
+ type BaseCallFilter = Everything;
+ type SystemWeightInfo = ();
+ type SS58Prefix = ();
+ type OnSetCode = ();
+}
+
+type Balance = u64;
+
+parameter_types! {
+ pub const ExistentialDeposit: u64 = 1;
+}
+
+impl pallet_balances::Config for Runtime {
+ type Balance = Balance;
+ type DustRemoval = ();
+ type Event = Event;
+ type ExistentialDeposit = ExistentialDeposit;
+ type AccountStore = frame_system::Pallet<Runtime>;
+ type MaxLocks = ();
+ type MaxReserves = ();
+ type ReserveIdentifier = [u8; 8];
+ type WeightInfo = ();
+}
+
+pub struct EnsureAliceOrBob;
+impl EnsureOrigin<Origin> for EnsureAliceOrBob {
+ type Success = AccountId;
+
+ fn try_origin(o: Origin) -> Result<Self::Success, Origin> {
+ Into::<Result<RawOrigin<AccountId>, Origin>>::into(o).and_then(|o| match o {
+ RawOrigin::Signed(ALICE) => Ok(ALICE),
+ RawOrigin::Signed(BOB) => Ok(BOB),
+ r => Err(Origin::from(r)),
+ })
+ }
+
+ #[cfg(feature = "runtime-benchmarks")]
+ fn successful_origin() -> Origin {
+ Origin::from(RawOrigin::Signed(Default::default()))
+ }
+}
+
+parameter_types! {
+ pub const MaxVestingSchedule: u32 = 2;
+ pub const MinVestedTransfer: u64 = 5;
+ pub static MockBlockNumberProvider: u64 = 0;
+}
+
+impl BlockNumberProvider for MockBlockNumberProvider {
+ type BlockNumber = u64;
+
+ fn current_block_number() -> Self::BlockNumber {
+ Self::get()
+ }
+}
+
+impl Config for Runtime {
+ type Event = Event;
+ type Currency = PalletBalances;
+ type MinVestedTransfer = MinVestedTransfer;
+ type VestedTransferOrigin = EnsureAliceOrBob;
+ type WeightInfo = ();
+ type MaxVestingSchedules = MaxVestingSchedule;
+ type BlockNumberProvider = MockBlockNumberProvider;
+}
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
+type Block = frame_system::mocking::MockBlock<Runtime>;
+
+construct_runtime!(
+ pub enum Runtime where
+ Block = Block,
+ NodeBlock = Block,
+ UncheckedExtrinsic = UncheckedExtrinsic,
+ {
+ System: frame_system::{Pallet, Call, Storage, Config, Event<T>},
+ Vesting: vesting::{Pallet, Storage, Call, Event<T>, Config<T>},
+ PalletBalances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
+ }
+);
+
+pub const ALICE: AccountId = 1;
+pub const BOB: AccountId = 2;
+pub const CHARLIE: AccountId = 3;
+
+#[derive(Default)]
+pub struct ExtBuilder;
+
+impl ExtBuilder {
+ pub fn build() -> sp_io::TestExternalities {
+ let mut t = frame_system::GenesisConfig::default()
+ .build_storage::<Runtime>()
+ .unwrap();
+
+ pallet_balances::GenesisConfig::<Runtime> {
+ balances: vec![(ALICE, 100), (CHARLIE, 50)],
+ }
+ .assimilate_storage(&mut t)
+ .unwrap();
+
+ vesting::GenesisConfig::<Runtime> {
+ vesting: vec![
+ // who, start, period, period_count, per_period
+ (CHARLIE, 2, 3, 1, 5),
+ (CHARLIE, 2 + 3, 3, 3, 5),
+ ],
+ }
+ .assimilate_storage(&mut t)
+ .unwrap();
+
+ t.into()
+ }
+}
pallets/orml-vesting/src/test.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/orml-vesting/src/test.rs
@@ -0,0 +1,411 @@
+//! Unit tests for the vesting module.
+
+#![cfg(test)]
+
+use super::*;
+use frame_support::{assert_noop, assert_ok, error::BadOrigin};
+use mock::{Event, *};
+use pallet_balances::{BalanceLock, Reasons};
+
+#[test]
+fn vesting_from_chain_spec_works() {
+ ExtBuilder::build().execute_with(|| {
+ assert_ok!(PalletBalances::ensure_can_withdraw(
+ &CHARLIE,
+ 10,
+ WithdrawReasons::TRANSFER,
+ 20
+ ));
+ assert!(PalletBalances::ensure_can_withdraw(&CHARLIE, 11, WithdrawReasons::TRANSFER, 19).is_err());
+
+ assert_eq!(
+ Vesting::vesting_schedules(&CHARLIE),
+ vec![
+ VestingSchedule {
+ start: 2u64,
+ period: 3u64,
+ period_count: 1u32,
+ per_period: 5u64,
+ },
+ VestingSchedule {
+ start: 2u64 + 3u64,
+ period: 3u64,
+ period_count: 3u32,
+ per_period: 5u64,
+ }
+ ]
+ );
+
+ MockBlockNumberProvider::set(13);
+
+ assert_ok!(Vesting::claim(Origin::signed(CHARLIE)));
+
+ assert_ok!(PalletBalances::ensure_can_withdraw(
+ &CHARLIE,
+ 25,
+ WithdrawReasons::TRANSFER,
+ 5
+ ));
+ assert!(PalletBalances::ensure_can_withdraw(&CHARLIE, 26, WithdrawReasons::TRANSFER, 4).is_err());
+
+ MockBlockNumberProvider::set(14);
+
+ assert_ok!(Vesting::claim(Origin::signed(CHARLIE)));
+
+ assert_ok!(PalletBalances::ensure_can_withdraw(
+ &CHARLIE,
+ 30,
+ WithdrawReasons::TRANSFER,
+ 0
+ ));
+ });
+}
+
+#[test]
+fn vested_transfer_works() {
+ ExtBuilder::build().execute_with(|| {
+ System::set_block_number(1);
+
+ let schedule = VestingSchedule {
+ start: 0u64,
+ period: 10u64,
+ period_count: 1u32,
+ per_period: 100u64,
+ };
+ assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule.clone()));
+ assert_eq!(Vesting::vesting_schedules(&BOB), vec![schedule.clone()]);
+ System::assert_last_event(Event::Vesting(crate::Event::VestingScheduleAdded(ALICE, BOB, schedule)));
+ });
+}
+
+#[test]
+fn add_new_vesting_schedule_merges_with_current_locked_balance_and_until() {
+ 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));
+
+ MockBlockNumberProvider::set(12);
+
+ let another_schedule = VestingSchedule {
+ start: 10u64,
+ period: 13u64,
+ period_count: 1u32,
+ per_period: 7u64,
+ };
+ assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), BOB, another_schedule));
+
+ assert_eq!(
+ PalletBalances::locks(&BOB).get(0),
+ Some(&BalanceLock {
+ id: VESTING_LOCK_ID,
+ amount: 17u64,
+ reasons: Reasons::All,
+ })
+ );
+ });
+}
+
+#[test]
+fn cannot_use_fund_if_not_claimed() {
+ ExtBuilder::build().execute_with(|| {
+ let schedule = VestingSchedule {
+ start: 10u64,
+ period: 10u64,
+ period_count: 1u32,
+ per_period: 50u64,
+ };
+ assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule));
+ assert!(PalletBalances::ensure_can_withdraw(&BOB, 1, WithdrawReasons::TRANSFER, 49).is_err());
+ });
+}
+
+#[test]
+fn vested_transfer_fails_if_zero_period_or_count() {
+ ExtBuilder::build().execute_with(|| {
+ let schedule = VestingSchedule {
+ start: 1u64,
+ period: 0u64,
+ period_count: 1u32,
+ per_period: 100u64,
+ };
+ assert_noop!(
+ Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule),
+ Error::<Runtime>::ZeroVestingPeriod
+ );
+
+ let schedule = VestingSchedule {
+ start: 1u64,
+ period: 1u64,
+ period_count: 0u32,
+ per_period: 100u64,
+ };
+ assert_noop!(
+ Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule),
+ Error::<Runtime>::ZeroVestingPeriodCount
+ );
+ });
+}
+
+#[test]
+fn vested_transfer_fails_if_transfer_err() {
+ ExtBuilder::build().execute_with(|| {
+ let schedule = VestingSchedule {
+ start: 1u64,
+ period: 1u64,
+ period_count: 1u32,
+ per_period: 100u64,
+ };
+ assert_noop!(
+ Vesting::vested_transfer(Origin::signed(BOB), ALICE, schedule),
+ pallet_balances::Error::<Runtime, _>::InsufficientBalance,
+ );
+ });
+}
+
+#[test]
+fn vested_transfer_fails_if_overflow() {
+ ExtBuilder::build().execute_with(|| {
+ let schedule = VestingSchedule {
+ start: 1u64,
+ period: 1u64,
+ period_count: 2u32,
+ per_period: u64::MAX,
+ };
+ assert_noop!(
+ Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule),
+ ArithmeticError::Overflow,
+ );
+
+ let another_schedule = VestingSchedule {
+ start: u64::MAX,
+ period: 1u64,
+ period_count: 2u32,
+ per_period: 1u64,
+ };
+ assert_noop!(
+ Vesting::vested_transfer(Origin::signed(ALICE), BOB, another_schedule),
+ ArithmeticError::Overflow,
+ );
+ });
+}
+
+#[test]
+fn vested_transfer_fails_if_bad_origin() {
+ ExtBuilder::build().execute_with(|| {
+ let schedule = VestingSchedule {
+ start: 0u64,
+ period: 10u64,
+ period_count: 1u32,
+ per_period: 100u64,
+ };
+ assert_noop!(
+ Vesting::vested_transfer(Origin::signed(CHARLIE), BOB, schedule),
+ BadOrigin
+ );
+ });
+}
+
+#[test]
+fn claim_works() {
+ 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));
+
+ MockBlockNumberProvider::set(11);
+ // remain locked if not claimed
+ assert!(PalletBalances::transfer(Origin::signed(BOB), ALICE, 10).is_err());
+ // unlocked after claiming
+ assert_ok!(Vesting::claim(Origin::signed(BOB)));
+ assert!(VestingSchedules::<Runtime>::contains_key(BOB));
+ assert_ok!(PalletBalances::transfer(Origin::signed(BOB), ALICE, 10));
+ // more are still locked
+ assert!(PalletBalances::transfer(Origin::signed(BOB), ALICE, 1).is_err());
+
+ MockBlockNumberProvider::set(21);
+ // claim more
+ assert_ok!(Vesting::claim(Origin::signed(BOB)));
+ assert!(!VestingSchedules::<Runtime>::contains_key(BOB));
+ assert_ok!(PalletBalances::transfer(Origin::signed(BOB), ALICE, 10));
+ // all used up
+ assert_eq!(PalletBalances::free_balance(BOB), 0);
+
+ // no locks anymore
+ assert_eq!(PalletBalances::locks(&BOB), vec![]);
+ });
+}
+
+#[test]
+fn claim_for_works() {
+ 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));
+
+ assert_ok!(Vesting::claim_for(Origin::signed(ALICE), BOB));
+
+ assert_eq!(
+ PalletBalances::locks(&BOB).get(0),
+ Some(&BalanceLock {
+ id: VESTING_LOCK_ID,
+ amount: 20u64,
+ reasons: Reasons::All,
+ })
+ );
+ assert!(VestingSchedules::<Runtime>::contains_key(&BOB));
+
+ MockBlockNumberProvider::set(21);
+
+ assert_ok!(Vesting::claim_for(Origin::signed(ALICE), BOB));
+
+ // no locks anymore
+ assert_eq!(PalletBalances::locks(&BOB), vec![]);
+ assert!(!VestingSchedules::<Runtime>::contains_key(&BOB));
+ });
+}
+
+#[test]
+fn update_vesting_schedules_works() {
+ 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));
+
+ let updated_schedule = VestingSchedule {
+ start: 0u64,
+ period: 20u64,
+ period_count: 2u32,
+ per_period: 10u64,
+ };
+ assert_ok!(Vesting::update_vesting_schedules(
+ Origin::root(),
+ BOB,
+ vec![updated_schedule]
+ ));
+
+ MockBlockNumberProvider::set(11);
+ assert_ok!(Vesting::claim(Origin::signed(BOB)));
+ assert!(PalletBalances::transfer(Origin::signed(BOB), ALICE, 1).is_err());
+
+ MockBlockNumberProvider::set(21);
+ assert_ok!(Vesting::claim(Origin::signed(BOB)));
+ assert_ok!(PalletBalances::transfer(Origin::signed(BOB), ALICE, 10));
+
+ // empty vesting schedules cleanup the storage and unlock the fund
+ assert!(VestingSchedules::<Runtime>::contains_key(BOB));
+ assert_eq!(
+ PalletBalances::locks(&BOB).get(0),
+ Some(&BalanceLock {
+ id: VESTING_LOCK_ID,
+ amount: 10u64,
+ reasons: Reasons::All,
+ })
+ );
+ assert_ok!(Vesting::update_vesting_schedules(Origin::root(), BOB, vec![]));
+ assert!(!VestingSchedules::<Runtime>::contains_key(BOB));
+ assert_eq!(PalletBalances::locks(&BOB), vec![]);
+ });
+}
+
+#[test]
+fn update_vesting_schedules_fails_if_unexpected_existing_locks() {
+ ExtBuilder::build().execute_with(|| {
+ assert_ok!(PalletBalances::transfer(Origin::signed(ALICE), BOB, 1));
+ PalletBalances::set_lock(*b"prelocks", &BOB, 0u64, WithdrawReasons::all());
+ });
+}
+
+#[test]
+fn vested_transfer_check_for_min() {
+ ExtBuilder::build().execute_with(|| {
+ let schedule = VestingSchedule {
+ start: 1u64,
+ period: 1u64,
+ period_count: 1u32,
+ per_period: 3u64,
+ };
+ assert_noop!(
+ Vesting::vested_transfer(Origin::signed(BOB), ALICE, schedule),
+ Error::<Runtime>::AmountLow
+ );
+ });
+}
+
+#[test]
+fn multiple_vesting_schedule_claim_works() {
+ 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()));
+
+ let schedule2 = VestingSchedule {
+ start: 0u64,
+ period: 10u64,
+ period_count: 3u32,
+ per_period: 10u64,
+ };
+ assert_ok!(Vesting::vested_transfer(Origin::signed(ALICE), BOB, schedule2.clone()));
+
+ assert_eq!(Vesting::vesting_schedules(&BOB), vec![schedule, schedule2.clone()]);
+
+ MockBlockNumberProvider::set(21);
+
+ assert_ok!(Vesting::claim(Origin::signed(BOB)));
+
+ assert_eq!(Vesting::vesting_schedules(&BOB), vec![schedule2]);
+
+ MockBlockNumberProvider::set(31);
+
+ assert_ok!(Vesting::claim(Origin::signed(BOB)));
+
+ assert!(!VestingSchedules::<Runtime>::contains_key(&BOB));
+
+ 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];
+
+ assert_noop!(
+ Vesting::update_vesting_schedules(Origin::root(), BOB, schedules),
+ Error::<Runtime>::MaxVestingSchedulesExceeded
+ );
+ });
+}
pallets/orml-vesting/src/weights.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/orml-vesting/src/weights.rs
@@ -0,0 +1,59 @@
+//! Autogenerated weights for orml_vesting
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
+//! DATE: 2021-05-04, STEPS: [50, ], REPEAT: 20, LOW RANGE: [], HIGH RANGE: []
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
+
+// Executed Command:
+// /Users/xiliangchen/projects/acala/target/release/acala
+// benchmark
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=orml_vesting
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --output=./vesting/src/weights.rs
+// --template
+// ../templates/orml-weight-template.hbs
+
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(clippy::unnecessary_cast)]
+
+use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for orml_vesting.
+pub trait WeightInfo {
+ fn vested_transfer() -> Weight;
+ fn claim(i: u32, ) -> Weight;
+ fn update_vesting_schedules(i: u32, ) -> Weight;
+}
+
+/// Default weights.
+impl WeightInfo for () {
+ fn vested_transfer() -> Weight {
+ (69_000_000 as Weight)
+ .saturating_add(RocksDbWeight::get().reads(4 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(4 as Weight))
+ }
+ fn claim(i: u32, ) -> Weight {
+ (31_747_000 as Weight)
+ // Standard Error: 4_000
+ .saturating_add((63_000 as Weight).saturating_mul(i as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+ }
+ fn update_vesting_schedules(i: u32, ) -> Weight {
+ (29_457_000 as Weight)
+ // Standard Error: 4_000
+ .saturating_add((117_000 as Weight).saturating_mul(i as Weight))
+ .saturating_add(RocksDbWeight::get().reads(2 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(3 as Weight))
+ }
+}
runtime/Cargo.tomldiffbeforeafterboth--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -362,7 +362,7 @@
default-features = false
[dependencies.orml-vesting]
-git = "https://github.com/open-web3-stack/open-runtime-module-library"
+path = '../pallets/orml-vesting'
version = "0.4.1-dev"
default-features = false