Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add vesting start time #9

Merged
merged 1 commit into from
Aug 15, 2024
Merged
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
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
Loading