-
Notifications
You must be signed in to change notification settings - Fork 19
Define vesting general logic #424
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
Changes from all commits
c686721
7857a38
5d14f88
b95b1df
ccfeca8
c98e14d
4b5566c
8f3076f
14ab8fa
3b015c4
0f811bc
458d760
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| [package] | ||
| name = "vesting-logic" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| publish = false | ||
|
|
||
| [dependencies] | ||
| vesting-schedule = { version = "0.1", path = "../vesting-schedule", default-features = false } | ||
|
|
||
| frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } | ||
|
|
||
| [features] | ||
| default = ["std"] | ||
| std = [ | ||
| "frame-support/std", | ||
| "vesting-schedule/std", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| //! The vesting. | ||
|
|
||
| #![cfg_attr(not(feature = "std"), no_std)] | ||
|
|
||
| mod traits; | ||
| pub use traits::*; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| //! Generic vesting related traits to abstract away the implementations. | ||
|
|
||
| use frame_support::{dispatch::DispatchResult, traits::Currency}; | ||
| use vesting_schedule::VestingSchedule; | ||
|
|
||
| /// A general vesting logic. | ||
| pub trait Vesting<AccountId> { | ||
| /// Defines logic of vesting schedule to be used. | ||
| type VestingSchedule: VestingSchedule<AccountId>; | ||
|
|
||
| /// Get the amount that is currently being vested and cannot be transferred out of this account. | ||
| /// Returns `None` if the account has no vesting schedule. | ||
| fn vesting_balance( | ||
| who: &AccountId, | ||
| ) -> Option< | ||
| <<Self::VestingSchedule as VestingSchedule<AccountId>>::Currency as Currency<AccountId>>::Balance, | ||
| >; | ||
|
|
||
| /// Adds a vesting schedule to a given account. | ||
| /// | ||
| /// If the account has `MaxVestingSchedules`, an Error is returned and nothing | ||
| /// is updated. | ||
| /// | ||
| /// Is a no-op if the amount to be vested is zero. | ||
| fn add_vesting_schedule( | ||
| who: &AccountId, | ||
| locked: <<Self::VestingSchedule as VestingSchedule<AccountId>>::Currency as Currency< | ||
| AccountId, | ||
| >>::Balance, | ||
| start: <Self::VestingSchedule as VestingSchedule<AccountId>>::Moment, | ||
| vesting_schedule: Self::VestingSchedule, | ||
| ) -> DispatchResult; | ||
|
|
||
| /// Checks if `add_vesting_schedule` would work against `who`. | ||
| fn can_add_vesting_schedule( | ||
| who: &AccountId, | ||
| locked: <<Self::VestingSchedule as VestingSchedule<AccountId>>::Currency as Currency< | ||
| AccountId, | ||
| >>::Balance, | ||
| start: <Self::VestingSchedule as VestingSchedule<AccountId>>::Moment, | ||
| vesting_schedule: Self::VestingSchedule, | ||
| ) -> DispatchResult; | ||
|
|
||
| /// Remove a vesting schedule for a given account. | ||
| fn remove_vesting_schedule(who: &AccountId, schedule_index: u32) -> DispatchResult; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| [package] | ||
| name = "vesting-schedule" | ||
| version = "0.1.0" | ||
| edition = "2021" | ||
| publish = false | ||
|
|
||
| [dependencies] | ||
| frame-support = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } | ||
| sp-arithmetic = { default-features = false, git = "https://github.com/humanode-network/substrate", branch = "master" } | ||
|
|
||
| [features] | ||
| default = ["std"] | ||
| std = [ | ||
| "frame-support/std", | ||
| "sp-arithmetic/std", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| //! The vesting schedule. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| #![cfg_attr(not(feature = "std"), no_std)] | ||
|
|
||
| use frame_support::traits::Currency as CurrencyT; | ||
| use sp_arithmetic::traits::{ | ||
| AtLeast32BitUnsigned, CheckedDiv, CheckedMul, Saturating, UniqueSaturatedFrom, | ||
| UniqueSaturatedInto, Zero, | ||
| }; | ||
|
|
||
| mod traits; | ||
| pub use traits::*; | ||
|
|
||
| /// Implements linear westing logic with cliff. | ||
| pub struct LinearWithCliff<AccountId, Moment, Currency: CurrencyT<AccountId>> { | ||
| /// Vesting cliff. | ||
| cliff: Moment, | ||
| /// Vesting period. | ||
| period: Moment, | ||
| /// Amount that should be unlocked per one vesting period. (!= 0) | ||
| per_period: Currency::Balance, | ||
| } | ||
|
|
||
| /// An error that can occur at linear with cliff vesting schedule logic. | ||
| pub enum LinearWithCliffError { | ||
| /// We don't let `per_period` be less than 1, or else the vesting will never end. | ||
| ZeroPerPeriod, | ||
| /// Genesis locked shouldn't be zero. | ||
| ZeroGenesisLocked, | ||
| } | ||
|
|
||
| impl<AccountId, Moment, Currency> VestingSchedule<AccountId> | ||
| for LinearWithCliff<AccountId, Moment, Currency> | ||
| where | ||
| Currency: CurrencyT<AccountId>, | ||
| Moment: AtLeast32BitUnsigned + Copy, | ||
| { | ||
| type Moment = Moment; | ||
|
|
||
| type Currency = Currency; | ||
|
|
||
| type Error = LinearWithCliffError; | ||
|
|
||
| fn validate( | ||
| &self, | ||
| genesis_locked: Currency::Balance, | ||
| _start: Self::Moment, | ||
| ) -> Result<(), Self::Error> { | ||
| if self.per_period == Zero::zero() { | ||
| return Err(LinearWithCliffError::ZeroPerPeriod); | ||
| } | ||
| if genesis_locked == Zero::zero() { | ||
| return Err(LinearWithCliffError::ZeroGenesisLocked); | ||
| } | ||
| Ok(()) | ||
| } | ||
|
|
||
| fn locked_at( | ||
| &self, | ||
| genesis_locked: Currency::Balance, | ||
| start: Self::Moment, | ||
| moment: Self::Moment, | ||
| ) -> Currency::Balance { | ||
| let actual_start = start.saturating_add(self.cliff); | ||
| if actual_start > moment { | ||
| return genesis_locked; | ||
| } | ||
|
|
||
| let actual_end = self.end(genesis_locked, start); | ||
| if actual_end < moment { | ||
| return Zero::zero(); | ||
| } | ||
|
|
||
| let actual_vesting_time = moment.saturating_sub(actual_start); | ||
| let periods_number = <Moment as UniqueSaturatedInto<u32>>::unique_saturated_into( | ||
| actual_vesting_time | ||
| .checked_div(&self.period) | ||
| .unwrap_or_else(Zero::zero), | ||
| ); | ||
| let vested_balance = self | ||
| .per_period | ||
| .checked_mul( | ||
| &<Currency::Balance as UniqueSaturatedFrom<u32>>::unique_saturated_from( | ||
| periods_number, | ||
| ), | ||
| ) | ||
| .unwrap_or_else(Zero::zero); | ||
| genesis_locked.saturating_sub(vested_balance) | ||
| } | ||
|
|
||
| fn end(&self, genesis_locked: Currency::Balance, start: Self::Moment) -> Self::Moment { | ||
| let periods_number = <Currency::Balance as UniqueSaturatedInto<u32>>::unique_saturated_into( | ||
| genesis_locked | ||
| .checked_div(&self.per_period) | ||
| .unwrap_or_else(Zero::zero), | ||
| ) + if (genesis_locked % self.per_period).is_zero() { | ||
| 0 | ||
| } else { | ||
| // `per_period` does not perfectly divide `locked`, so we need an extra period to | ||
| // unlock some amount less than `per_period`. | ||
| 1 | ||
| }; | ||
|
|
||
| let actual_start = start.saturating_add(self.cliff); | ||
| let actual_vesting_time = self | ||
| .period | ||
| .checked_mul( | ||
| &<Moment as UniqueSaturatedFrom<u32>>::unique_saturated_from(periods_number), | ||
| ) | ||
| .unwrap_or_else(Zero::zero); | ||
| actual_start.saturating_add(actual_vesting_time) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| //! Generic vesting schedule related traits to abstract away the implementations. | ||
|
|
||
| use frame_support::traits::Currency; | ||
|
|
||
| /// [`VestingSchedule`] defines logic for currency vesting(unlocking). | ||
| pub trait VestingSchedule<AccountId> { | ||
| /// The type used to denote time: Timestamp, BlockNumber, etc. | ||
| type Moment; | ||
| /// The currency that this schedule applies to. | ||
| type Currency: Currency<AccountId>; | ||
| /// An error that can occur at vesting schedule logic. | ||
| type Error; | ||
| /// Validate the schedule. | ||
| fn validate( | ||
| &self, | ||
| genesis_locked: <Self::Currency as Currency<AccountId>>::Balance, | ||
| start: Self::Moment, | ||
| ) -> Result<(), Self::Error>; | ||
| /// Locked amount at provided moment. | ||
| fn locked_at( | ||
| &self, | ||
| genesis_locked: <Self::Currency as Currency<AccountId>>::Balance, | ||
| start: Self::Moment, | ||
| moment: Self::Moment, | ||
| ) -> <Self::Currency as Currency<AccountId>>::Balance; | ||
| /// Moment at which the schedule ends. | ||
| fn end( | ||
| &self, | ||
| genesis_locked: <Self::Currency as Currency<AccountId>>::Balance, | ||
| start: Self::Moment, | ||
| ) -> Self::Moment; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing
no_stdmode declarationThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f3444f6