Skip to content
Closed
16 changes: 16 additions & 0 deletions Cargo.lock

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

17 changes: 17 additions & 0 deletions crates/vesting-logic/Cargo.toml
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",
]
6 changes: 6 additions & 0 deletions crates/vesting-logic/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//! The vesting.
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing no_std mode declaration

Copy link
Contributor Author

Choose a reason for hiding this comment

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


#![cfg_attr(not(feature = "std"), no_std)]

mod traits;
pub use traits::*;
46 changes: 46 additions & 0 deletions crates/vesting-logic/src/traits.rs
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;
}
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.
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing no_std mode declaration

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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)
}
}
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;
}