Skip to content
Closed
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
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions crates/vesting-schedule/Cargo.toml
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",
]
113 changes: 113 additions & 0 deletions crates/vesting-schedule/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! 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,
UniqueSaturatedInto, Zero,
};

mod traits;
pub use traits::*;

/// Implements linear westing logic with cliff.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Implements linear westing logic with cliff.
/// Implements linear vesting 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)
}
}
32 changes: 32 additions & 0 deletions crates/vesting-schedule/src/traits.rs
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;
}