Skip to content

Commit

Permalink
add vesting start time (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewsource147 authored Aug 15, 2024
1 parent 9812739 commit 744c93f
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 70 deletions.
3 changes: 3 additions & 0 deletions programs/locker/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ pub enum LockerError {

#[msg("Invalid escrow metadata")]
InvalidEscrowMetadata,

#[msg("Invalid vesting start time")]
InvalidVestingStartTime,
}
5 changes: 3 additions & 2 deletions programs/locker/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ use anchor_lang::prelude::*;

#[event]
pub struct EventCreateVestingEscrow {
pub start_time: u64,
pub vesting_start_time: u64,
pub cliff_time: u64,
pub frequency: u64,
pub initial_unlock_amount: u64,
pub cliff_unlock_amount: u64,
pub amount_per_period: u64,
pub number_of_period: u64,
pub update_recipient_mode: u8,
Expand Down
2 changes: 1 addition & 1 deletion programs/locker/src/instructions/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub fn handle_claim(ctx: Context<ClaimCtx>, max_amount: u64) -> Result<()> {
"claim amount {} {} {}",
amount,
current_ts,
escrow.start_time
escrow.cliff_time
);

drop(escrow);
Expand Down
27 changes: 18 additions & 9 deletions programs/locker/src/instructions/create_vesting_escrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ use anchor_spl::token::{Token, TokenAccount, Transfer};

/// Accounts for [locker::create_vesting_escrow].
pub struct CreateVestingEscrowParameters {
pub start_time: u64,
pub vesting_start_time: u64,
pub cliff_time: u64,
pub frequency: u64,
pub initial_unlock_amount: u64,
pub cliff_unlock_amount: u64,
pub amount_per_period: u64,
pub number_of_period: u64,
pub update_recipient_mode: u8,
Expand All @@ -16,7 +17,7 @@ pub struct CreateVestingEscrowParameters {
impl CreateVestingEscrowParameters {
pub fn get_total_deposit_amount(&self) -> Result<u64> {
let total_amount = self
.initial_unlock_amount
.cliff_unlock_amount
.safe_add(self.amount_per_period.safe_mul(self.number_of_period)?)?;
Ok(total_amount)
}
Expand Down Expand Up @@ -64,14 +65,20 @@ pub fn handle_create_vesting_escrow(
params: &CreateVestingEscrowParameters,
) -> Result<()> {
let &CreateVestingEscrowParameters {
start_time,
vesting_start_time,
cliff_time,
frequency,
initial_unlock_amount,
cliff_unlock_amount,
amount_per_period,
number_of_period,
update_recipient_mode,
} = params;

require!(
cliff_time >= vesting_start_time,
LockerError::InvalidVestingStartTime
);

require!(
UpdateRecipientMode::try_from(update_recipient_mode).is_ok(),
LockerError::InvalidUpdateRecipientMode,
Expand All @@ -91,9 +98,10 @@ pub fn handle_create_vesting_escrow(

let mut escrow = ctx.accounts.escrow.load_init()?;
escrow.init(
start_time,
vesting_start_time,
cliff_time,
frequency,
initial_unlock_amount,
cliff_unlock_amount,
amount_per_period,
number_of_period,
ctx.accounts.recipient.key(),
Expand All @@ -117,14 +125,15 @@ pub fn handle_create_vesting_escrow(
)?;

emit_cpi!(EventCreateVestingEscrow {
start_time,
cliff_time,
frequency,
initial_unlock_amount,
cliff_unlock_amount,
amount_per_period,
number_of_period,
recipient: ctx.accounts.recipient.key(),
escrow: ctx.accounts.escrow.key(),
update_recipient_mode,
vesting_start_time,
});
Ok(())
}
58 changes: 30 additions & 28 deletions programs/locker/src/state/vesting_escrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ pub struct VestingEscrow {
pub update_recipient_mode: u8,
/// padding
pub padding_0: [u8; 6],
/// start time
pub start_time: u64,
/// cliff time
pub cliff_time: u64,
/// frequency
pub frequency: u64,
/// initial unlock amount
pub initial_unlock_amount: u64,
/// cliff unlock amount
pub cliff_unlock_amount: u64,
/// amount per period
pub amount_per_period: u64,
/// number of period
pub number_of_period: u64,
/// total claimed amount
pub total_claimed_amount: u64,
/// padding
pub padding_1: [u8; 8],
/// vesting start time
pub vesting_start_time: u64,
/// buffer
pub buffer: [u128; 6],
}
Expand All @@ -53,9 +53,10 @@ const_assert_eq!(VestingEscrow::INIT_SPACE, 288); // 32 * 4 + 8 * 8 + 16 * 6
impl VestingEscrow {
pub fn init(
&mut self,
start_time: u64,
vesting_start_time: u64,
cliff_time: u64,
frequency: u64,
initial_unlock_amount: u64,
cliff_unlock_amount: u64,
amount_per_period: u64,
number_of_period: u64,
recipient: Pubkey,
Expand All @@ -65,9 +66,10 @@ impl VestingEscrow {
escrow_bump: u8,
update_recipient_mode: u8,
) {
self.start_time = start_time;
self.vesting_start_time = vesting_start_time;
self.cliff_time = cliff_time;
self.frequency = frequency;
self.initial_unlock_amount = initial_unlock_amount;
self.cliff_unlock_amount = cliff_unlock_amount;
self.amount_per_period = amount_per_period;
self.number_of_period = number_of_period;
self.recipient = recipient;
Expand All @@ -79,16 +81,16 @@ impl VestingEscrow {
}

pub fn get_max_unlocked_amount(&self, current_ts: u64) -> Result<u64> {
if current_ts < self.start_time {
if current_ts < self.cliff_time {
return Ok(0);
}
let period = current_ts
.safe_sub(self.start_time)?
.safe_sub(self.cliff_time)?
.safe_div(self.frequency)?;
let period = period.min(self.number_of_period);

let unlocked_amount = self
.initial_unlock_amount
.cliff_unlock_amount
.safe_add(period.safe_mul(self.amount_per_period)?)?;

Ok(unlocked_amount)
Expand Down Expand Up @@ -118,56 +120,56 @@ mod escrow_test {
proptest! {
#[test]
fn test_get_max_unlocked_amount(
start_time in 1..=u64::MAX/2,
cliff_time in 1..=u64::MAX/2,
frequency in 1..2592000u64,
number_of_period in 0..10000u64,
initial_unlock_amount in 0..u64::MAX / 100,
cliff_unlock_amount in 0..u64::MAX / 100,
amount_per_period in 0..u64::MAX / 10000,
) {
let mut escrow = VestingEscrow::default();
escrow.start_time = start_time;
escrow.cliff_time = cliff_time;
escrow.frequency = frequency;
escrow.number_of_period = number_of_period;
escrow.initial_unlock_amount = initial_unlock_amount;
escrow.cliff_unlock_amount = cliff_unlock_amount;
escrow.amount_per_period = amount_per_period;

let unlocked_amount = escrow.get_max_unlocked_amount(start_time - 1).unwrap();
let unlocked_amount = escrow.get_max_unlocked_amount(cliff_time - 1).unwrap();
assert_eq!(unlocked_amount, 0);

let unlocked_amount = escrow.get_max_unlocked_amount(start_time).unwrap();
assert_eq!(unlocked_amount, initial_unlock_amount);
let unlocked_amount = escrow.get_max_unlocked_amount(cliff_time).unwrap();
assert_eq!(unlocked_amount, cliff_unlock_amount);

let unlocked_amount = escrow
.get_max_unlocked_amount(start_time + frequency * 1)
.get_max_unlocked_amount(cliff_time + frequency * 1)
.unwrap();
assert_eq!(unlocked_amount, initial_unlock_amount + amount_per_period * 1);
assert_eq!(unlocked_amount, cliff_unlock_amount + amount_per_period * 1);

let unlocked_amount = escrow
.get_max_unlocked_amount(start_time + frequency * number_of_period - 1)
.get_max_unlocked_amount(cliff_time + frequency * number_of_period - 1)
.unwrap();
if number_of_period == 0 {
assert_eq!(
unlocked_amount,
0
);
} else {
assert_eq!(unlocked_amount, initial_unlock_amount+ amount_per_period * (number_of_period-1));
assert_eq!(unlocked_amount, cliff_unlock_amount+ amount_per_period * (number_of_period-1));
}

let unlocked_amount = escrow
.get_max_unlocked_amount(start_time + frequency * number_of_period)
.get_max_unlocked_amount(cliff_time + frequency * number_of_period)
.unwrap();
assert_eq!(
unlocked_amount,
initial_unlock_amount + amount_per_period * number_of_period
cliff_unlock_amount + amount_per_period * number_of_period
);

let unlocked_amount = escrow
.get_max_unlocked_amount(start_time + frequency * number_of_period + 1)
.get_max_unlocked_amount(cliff_time + frequency * number_of_period + 1)
.unwrap();
assert_eq!(
unlocked_amount,
initial_unlock_amount + amount_per_period * number_of_period
cliff_unlock_amount + amount_per_period * number_of_period
);
}
}
Expand Down
7 changes: 4 additions & 3 deletions tests/escrow_metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,15 @@ describe("Escrow metadata", () => {
console.log("Create vesting plan");
const program = createLockerProgram(new anchor.Wallet(UserKP));
let currentBlockTime = await getCurrentBlockTime(program.provider.connection);
const startTime = new BN(currentBlockTime).add(new BN(5));
const cliffTime = new BN(currentBlockTime).add(new BN(5));
let escrow = await createVestingPlan({
ownerKeypair: UserKP,
tokenMint: TOKEN,
vestingStartTime: new BN(0),
isAssertion: true,
startTime,
cliffTime,
frequency: new BN(1),
initialUnlockAmount: new BN(100_000),
cliffUnlockAmount: new BN(100_000),
amountPerPeriod: new BN(50_000),
numberOfPeriod: new BN(2),
recipient: ReceipentKP.publicKey,
Expand Down
11 changes: 6 additions & 5 deletions tests/locker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,14 +95,15 @@ describe("Full flow", () => {
console.log("Create vesting plan");
const program = createLockerProgram(new anchor.Wallet(UserKP));
let currentBlockTime = await getCurrentBlockTime(program.provider.connection);
const startTime = new BN(currentBlockTime).add(new BN(5));
const cliffTime = new BN(currentBlockTime).add(new BN(5));
let escrow = await createVestingPlan({
vestingStartTime: new BN(0),
ownerKeypair: UserKP,
tokenMint: TOKEN,
isAssertion: true,
startTime,
cliffTime,
frequency: new BN(1),
initialUnlockAmount: new BN(100_000),
cliffUnlockAmount: new BN(100_000),
amountPerPeriod: new BN(50_000),
numberOfPeriod: new BN(2),
recipient: ReceipentKP.publicKey,
Expand All @@ -112,11 +113,11 @@ describe("Full flow", () => {

while (true) {
const currentBlockTime = await getCurrentBlockTime(program.provider.connection);
if (currentBlockTime > startTime.toNumber()) {
if (currentBlockTime > cliffTime.toNumber()) {
break;
} else {
await sleep(1000);
console.log("Wait until startTime");
console.log("Wait until cliffTime");
}
}

Expand Down
16 changes: 9 additions & 7 deletions tests/locker_utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,18 @@ export interface CreateVestingPlanParams {
ownerKeypair: web3.Keypair,
tokenMint: web3.PublicKey,
isAssertion: boolean,
startTime: BN,
vestingStartTime: BN,
cliffTime: BN,
frequency: BN,
initialUnlockAmount: BN,
cliffUnlockAmount: BN,
amountPerPeriod: BN,
numberOfPeriod: BN,
recipient: web3.PublicKey,
updateRecipientMode: number,
}

export async function createVestingPlan(params: CreateVestingPlanParams) {
let { isAssertion, tokenMint, ownerKeypair, startTime, frequency, initialUnlockAmount, amountPerPeriod, numberOfPeriod, recipient, updateRecipientMode } = params;
let { isAssertion, tokenMint, ownerKeypair, cliffTime, frequency, cliffUnlockAmount, amountPerPeriod, numberOfPeriod, recipient, updateRecipientMode, vestingStartTime } = params;
const program = createLockerProgram(new Wallet(ownerKeypair));

const baseKP = web3.Keypair.generate();
Expand All @@ -85,12 +86,13 @@ export async function createVestingPlan(params: CreateVestingPlanParams) {
ASSOCIATED_TOKEN_PROGRAM_ID
);
await program.methods.createVestingEscrow({
startTime,
cliffTime,
frequency,
initialUnlockAmount,
cliffUnlockAmount,
amountPerPeriod,
numberOfPeriod,
updateRecipientMode,
vestingStartTime,
}).accounts({
base: baseKP.publicKey,
senderToken,
Expand All @@ -115,9 +117,9 @@ export async function createVestingPlan(params: CreateVestingPlanParams) {

if (isAssertion) {
const escrowState = await program.account.vestingEscrow.fetch(escrow);
expect(escrowState.startTime.toString()).eq(startTime.toString());
expect(escrowState.cliffTime.toString()).eq(cliffTime.toString());
expect(escrowState.frequency.toString()).eq(frequency.toString());
expect(escrowState.initialUnlockAmount.toString()).eq(initialUnlockAmount.toString());
expect(escrowState.cliffUnlockAmount.toString()).eq(cliffUnlockAmount.toString());
expect(escrowState.amountPerPeriod.toString()).eq(amountPerPeriod.toString());
expect(escrowState.numberOfPeriod.toString()).eq(numberOfPeriod.toString());
expect(escrowState.recipient.toString()).eq(recipient.toString());
Expand Down
Loading

0 comments on commit 744c93f

Please sign in to comment.