From c68672182b1d4be15fe6920b5ade9b465d3736b3 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 20 Jul 2022 13:55:56 +0300 Subject: [PATCH 1/8] Define VestingSchedule trait --- Cargo.lock | 7 +++++++ crates/vesting-schedule/Cargo.toml | 14 ++++++++++++++ crates/vesting-schedule/src/lib.rs | 4 ++++ crates/vesting-schedule/src/traits.rs | 22 ++++++++++++++++++++++ 4 files changed, 47 insertions(+) create mode 100644 crates/vesting-schedule/Cargo.toml create mode 100644 crates/vesting-schedule/src/lib.rs create mode 100644 crates/vesting-schedule/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index 067fc101c..a6f70cd9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10038,6 +10038,13 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vesting-schedule" +version = "0.1.0" +dependencies = [ + "frame-support", +] + [[package]] name = "void" version = "1.0.2" diff --git a/crates/vesting-schedule/Cargo.toml b/crates/vesting-schedule/Cargo.toml new file mode 100644 index 000000000..0712bad8e --- /dev/null +++ b/crates/vesting-schedule/Cargo.toml @@ -0,0 +1,14 @@ +[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" } + +[features] +default = ["std"] +std = [ + "frame-support/std", +] diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs new file mode 100644 index 000000000..2eacfd611 --- /dev/null +++ b/crates/vesting-schedule/src/lib.rs @@ -0,0 +1,4 @@ +//! The vesting schedule. + +mod traits; +pub use traits::*; diff --git a/crates/vesting-schedule/src/traits.rs b/crates/vesting-schedule/src/traits.rs new file mode 100644 index 000000000..ea2b21358 --- /dev/null +++ b/crates/vesting-schedule/src/traits.rs @@ -0,0 +1,22 @@ +//! 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 { + /// The type used to denote time: Timestamp, BlockNumber, etc. + type Moment; + /// The currency that this schedule applies to. + type Currency: Currency; + /// Locked amount at provided moment. + fn locked_at( + genesis_locked: >::Balance, + start: Self::Moment, + moment: Self::Moment, + ) -> >::Balance; + /// Moment at which the schedule ends. + fn end( + genesis_locked: >::Balance, + start: Self::Moment, + ) -> Self::Moment; +} From 7857a38230638bfbdb738bb8d19c7f99cf0abc27 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 20 Jul 2022 17:17:48 +0300 Subject: [PATCH 2/8] Implement locked_at --- Cargo.lock | 1 + crates/vesting-schedule/Cargo.toml | 2 + crates/vesting-schedule/src/lib.rs | 59 +++++++++++++++++++++++++++ crates/vesting-schedule/src/traits.rs | 2 + 4 files changed, 64 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index a6f70cd9b..786d2d414 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10043,6 +10043,7 @@ name = "vesting-schedule" version = "0.1.0" dependencies = [ "frame-support", + "sp-arithmetic", ] [[package]] diff --git a/crates/vesting-schedule/Cargo.toml b/crates/vesting-schedule/Cargo.toml index 0712bad8e..0c3d98025 100644 --- a/crates/vesting-schedule/Cargo.toml +++ b/crates/vesting-schedule/Cargo.toml @@ -6,9 +6,11 @@ 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", ] diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs index 2eacfd611..802d0d16f 100644 --- a/crates/vesting-schedule/src/lib.rs +++ b/crates/vesting-schedule/src/lib.rs @@ -1,4 +1,63 @@ //! The vesting schedule. +use frame_support::traits::Currency as CurrencyT; +use sp_arithmetic::traits::{ + AtLeast32BitUnsigned, CheckedMul, Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, +}; + mod traits; pub use traits::*; + +pub struct LinearWithCliff> { + cliff: Moment, + period: Moment, + per_period: Currency::Balance, +} + +impl VestingSchedule + for LinearWithCliff +where + Currency: CurrencyT, + Moment: AtLeast32BitUnsigned + Copy, +{ + type Moment = Moment; + + type Currency = Currency; + + 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 = >::unique_saturated_into( + actual_vesting_time + .checked_div(&self.period) + .unwrap_or_else(Zero::zero), + ); + let vested_balance = self + .per_period + .checked_mul( + &>::unique_saturated_from( + periods_number, + ), + ) + .unwrap(); + genesis_locked.saturating_sub(vested_balance) + } + + fn end(&self, genesis_locked: Currency::Balance, start: Self::Moment) -> Self::Moment { + todo!() + } +} diff --git a/crates/vesting-schedule/src/traits.rs b/crates/vesting-schedule/src/traits.rs index ea2b21358..6ebbefbda 100644 --- a/crates/vesting-schedule/src/traits.rs +++ b/crates/vesting-schedule/src/traits.rs @@ -10,12 +10,14 @@ pub trait VestingSchedule { type Currency: Currency; /// Locked amount at provided moment. fn locked_at( + &self, genesis_locked: >::Balance, start: Self::Moment, moment: Self::Moment, ) -> >::Balance; /// Moment at which the schedule ends. fn end( + &self, genesis_locked: >::Balance, start: Self::Moment, ) -> Self::Moment; From 5d14f88aaae899e455ea9b42e00566cf01e81b13 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 20 Jul 2022 17:21:17 +0300 Subject: [PATCH 3/8] Add docs to LinearWithCliff --- crates/vesting-schedule/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs index 802d0d16f..82dfdccde 100644 --- a/crates/vesting-schedule/src/lib.rs +++ b/crates/vesting-schedule/src/lib.rs @@ -8,9 +8,13 @@ use sp_arithmetic::traits::{ mod traits; pub use traits::*; +/// Implements linear westing logic with cliff. pub struct LinearWithCliff> { + /// Vesting cliff. cliff: Moment, + /// Vesting period. period: Moment, + /// Amount that should be unlocked per one vesting period. per_period: Currency::Balance, } From b95b1df6f121701c0188ff653ca1f1ba15dbc0fb Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 20 Jul 2022 17:34:14 +0300 Subject: [PATCH 4/8] Implement end --- crates/vesting-schedule/src/lib.rs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs index 82dfdccde..b9c873637 100644 --- a/crates/vesting-schedule/src/lib.rs +++ b/crates/vesting-schedule/src/lib.rs @@ -2,7 +2,8 @@ use frame_support::traits::Currency as CurrencyT; use sp_arithmetic::traits::{ - AtLeast32BitUnsigned, CheckedMul, Saturating, UniqueSaturatedFrom, UniqueSaturatedInto, Zero, + AtLeast32BitUnsigned, CheckedDiv, CheckedMul, Saturating, UniqueSaturatedFrom, + UniqueSaturatedInto, Zero, }; mod traits; @@ -57,11 +58,30 @@ where periods_number, ), ) - .unwrap(); + .unwrap_or_else(Zero::zero); genesis_locked.saturating_sub(vested_balance) } fn end(&self, genesis_locked: Currency::Balance, start: Self::Moment) -> Self::Moment { - todo!() + let periods_number = >::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( + &>::unique_saturated_from(periods_number), + ) + .unwrap_or_else(Zero::zero); + actual_start.saturating_add(actual_vesting_time) } } From ccfeca81a7834732282b1f669e03d3187fd94b61 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Wed, 20 Jul 2022 17:39:45 +0300 Subject: [PATCH 5/8] Add little note --- crates/vesting-schedule/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs index b9c873637..8b3ed651b 100644 --- a/crates/vesting-schedule/src/lib.rs +++ b/crates/vesting-schedule/src/lib.rs @@ -15,7 +15,7 @@ pub struct LinearWithCliff> { cliff: Moment, /// Vesting period. period: Moment, - /// Amount that should be unlocked per one vesting period. + /// Amount that should be unlocked per one vesting period. (!= 0) per_period: Currency::Balance, } From c98e14d381c7c82af21318b8e3b95dd1aae66840 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Thu, 21 Jul 2022 14:20:42 +0300 Subject: [PATCH 6/8] Add validate logic --- crates/vesting-schedule/src/lib.rs | 15 +++++++++++++++ crates/vesting-schedule/src/traits.rs | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs index 8b3ed651b..dfcf7b2cd 100644 --- a/crates/vesting-schedule/src/lib.rs +++ b/crates/vesting-schedule/src/lib.rs @@ -19,6 +19,12 @@ pub struct LinearWithCliff> { 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, +} + impl VestingSchedule for LinearWithCliff where @@ -29,6 +35,15 @@ where type Currency = Currency; + type Error = LinearWithCliffError; + + fn validate(&self) -> Result<(), Self::Error> { + if self.per_period == Zero::zero() { + return Err(LinearWithCliffError::ZeroPerPeriod); + } + Ok(()) + } + fn locked_at( &self, genesis_locked: Currency::Balance, diff --git a/crates/vesting-schedule/src/traits.rs b/crates/vesting-schedule/src/traits.rs index 6ebbefbda..03e796179 100644 --- a/crates/vesting-schedule/src/traits.rs +++ b/crates/vesting-schedule/src/traits.rs @@ -8,6 +8,10 @@ pub trait VestingSchedule { type Moment; /// The currency that this schedule applies to. type Currency: Currency; + /// An error that can occur at vesting schedule logic. + type Error; + /// Validate the schedule. + fn validate(&self) -> Result<(), Self::Error>; /// Locked amount at provided moment. fn locked_at( &self, From 4b5566cb5fcb3d107314f67f69a9a731f21edc00 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Mon, 25 Jul 2022 12:24:12 +0300 Subject: [PATCH 7/8] Enable no_std mod --- crates/vesting-schedule/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs index dfcf7b2cd..ac818f03b 100644 --- a/crates/vesting-schedule/src/lib.rs +++ b/crates/vesting-schedule/src/lib.rs @@ -1,5 +1,7 @@ //! The vesting schedule. +#![cfg_attr(not(feature = "std"), no_std)] + use frame_support::traits::Currency as CurrencyT; use sp_arithmetic::traits::{ AtLeast32BitUnsigned, CheckedDiv, CheckedMul, Saturating, UniqueSaturatedFrom, From 14ab8fa7eee6c289a1a9b948f4384d066ce497a8 Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenov Date: Tue, 26 Jul 2022 19:54:20 +0300 Subject: [PATCH 8/8] Improve validate logic for LinearWithCliff --- crates/vesting-schedule/src/lib.rs | 11 ++++++++++- crates/vesting-schedule/src/traits.rs | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/crates/vesting-schedule/src/lib.rs b/crates/vesting-schedule/src/lib.rs index ac818f03b..9c4345394 100644 --- a/crates/vesting-schedule/src/lib.rs +++ b/crates/vesting-schedule/src/lib.rs @@ -25,6 +25,8 @@ pub struct LinearWithCliff> { 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 VestingSchedule @@ -39,10 +41,17 @@ where type Error = LinearWithCliffError; - fn validate(&self) -> Result<(), Self::Error> { + 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(()) } diff --git a/crates/vesting-schedule/src/traits.rs b/crates/vesting-schedule/src/traits.rs index 03e796179..d295d3898 100644 --- a/crates/vesting-schedule/src/traits.rs +++ b/crates/vesting-schedule/src/traits.rs @@ -11,7 +11,11 @@ pub trait VestingSchedule { /// An error that can occur at vesting schedule logic. type Error; /// Validate the schedule. - fn validate(&self) -> Result<(), Self::Error>; + fn validate( + &self, + genesis_locked: >::Balance, + start: Self::Moment, + ) -> Result<(), Self::Error>; /// Locked amount at provided moment. fn locked_at( &self,