From 2f6925134a973ab39c7e8a0a5bde4d564d2eeb45 Mon Sep 17 00:00:00 2001 From: Andrew Nguyen Date: Wed, 14 Aug 2024 09:02:40 +0700 Subject: [PATCH] add vesting start time --- programs/locker/src/errors.rs | 3 + programs/locker/src/events.rs | 5 +- programs/locker/src/instructions/claim.rs | 2 +- .../src/instructions/create_vesting_escrow.rs | 27 ++++++--- programs/locker/src/state/vesting_escrow.rs | 58 ++++++++++--------- tests/escrow_metadata.ts | 7 ++- tests/locker.ts | 11 ++-- tests/locker_utils/index.ts | 16 ++--- tests/update_recipient.ts | 35 ++++++----- 9 files changed, 94 insertions(+), 70 deletions(-) diff --git a/programs/locker/src/errors.rs b/programs/locker/src/errors.rs index 556727f..3d19229 100644 --- a/programs/locker/src/errors.rs +++ b/programs/locker/src/errors.rs @@ -23,4 +23,7 @@ pub enum LockerError { #[msg("Invalid escrow metadata")] InvalidEscrowMetadata, + + #[msg("Invalid vesting start time")] + InvalidVestingStartTime, } diff --git a/programs/locker/src/events.rs b/programs/locker/src/events.rs index 5d8f456..ff6d1d4 100644 --- a/programs/locker/src/events.rs +++ b/programs/locker/src/events.rs @@ -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, diff --git a/programs/locker/src/instructions/claim.rs b/programs/locker/src/instructions/claim.rs index e5b9142..2f62867 100644 --- a/programs/locker/src/instructions/claim.rs +++ b/programs/locker/src/instructions/claim.rs @@ -65,7 +65,7 @@ pub fn handle_claim(ctx: Context, max_amount: u64) -> Result<()> { "claim amount {} {} {}", amount, current_ts, - escrow.start_time + escrow.cliff_time ); drop(escrow); diff --git a/programs/locker/src/instructions/create_vesting_escrow.rs b/programs/locker/src/instructions/create_vesting_escrow.rs index b55796d..45798b7 100644 --- a/programs/locker/src/instructions/create_vesting_escrow.rs +++ b/programs/locker/src/instructions/create_vesting_escrow.rs @@ -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, @@ -16,7 +17,7 @@ pub struct CreateVestingEscrowParameters { impl CreateVestingEscrowParameters { pub fn get_total_deposit_amount(&self) -> Result { 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) } @@ -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, @@ -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(), @@ -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(()) } diff --git a/programs/locker/src/state/vesting_escrow.rs b/programs/locker/src/state/vesting_escrow.rs index 277f5e5..1cd01cb 100644 --- a/programs/locker/src/state/vesting_escrow.rs +++ b/programs/locker/src/state/vesting_escrow.rs @@ -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], } @@ -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, @@ -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; @@ -79,16 +81,16 @@ impl VestingEscrow { } pub fn get_max_unlocked_amount(&self, current_ts: u64) -> Result { - 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) @@ -118,32 +120,32 @@ 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!( @@ -151,23 +153,23 @@ mod escrow_test { 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 ); } } diff --git a/tests/escrow_metadata.ts b/tests/escrow_metadata.ts index 3ea2709..5e19bbf 100644 --- a/tests/escrow_metadata.ts +++ b/tests/escrow_metadata.ts @@ -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, diff --git a/tests/locker.ts b/tests/locker.ts index 94c7703..1d3fe9d 100644 --- a/tests/locker.ts +++ b/tests/locker.ts @@ -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, @@ -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"); } } diff --git a/tests/locker_utils/index.ts b/tests/locker_utils/index.ts index 2a7187a..4823027 100644 --- a/tests/locker_utils/index.ts +++ b/tests/locker_utils/index.ts @@ -52,9 +52,10 @@ 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, @@ -62,7 +63,7 @@ export interface CreateVestingPlanParams { } 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(); @@ -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, @@ -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()); diff --git a/tests/update_recipient.ts b/tests/update_recipient.ts index 362cff6..104dde3 100644 --- a/tests/update_recipient.ts +++ b/tests/update_recipient.ts @@ -78,14 +78,15 @@ describe("Update recipient", () => { 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, + vestingStartTime: new BN(0), 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, @@ -118,14 +119,15 @@ describe("Update recipient", () => { 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, + vestingStartTime: new BN(0), 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, @@ -157,14 +159,15 @@ describe("Update recipient", () => { 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, + vestingStartTime: new BN(0), 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, @@ -195,14 +198,15 @@ describe("Update recipient", () => { 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, + vestingStartTime: new BN(0), 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, @@ -231,14 +235,15 @@ describe("Update recipient", () => { 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,