Skip to content

Commit

Permalink
feat: deprecating schedules array in favour of single schedule (#4)
Browse files Browse the repository at this point in the history
The original codebase https://github.com/Bonfida/token-vesting supports
an array of Dates to define a Vesting Contract. In that way, it can say:
"unlock 10 at May 10, 10 at May 20, ..." and so on.

The intended behaviour is to have only 1 Date per vesting / lockup, this
PR replaces the Schedules<> array for a single Schedule, and deprecates
the variable `number_of_schedules`
  • Loading branch information
wei3erHase authored Sep 25, 2024
1 parent e324816 commit abec526
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 124 deletions.
76 changes: 26 additions & 50 deletions program/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,19 @@ impl Arbitrary for VestingInstruction {
let choice = u.choose(&[0, 1, 2])?;
match choice {
0 => {
let number_of_schedules = u.arbitrary()?;
return Ok(Self::Init {
seeds,
number_of_schedules,
});
}
1 => {
let schedules: [Schedule; 10] = u.arbitrary()?;
let schedule: [Schedule; 10] = u.arbitrary()?;
let key_bytes: [u8; 32] = u.arbitrary()?;
let mint_address: Pubkey = Pubkey::new_from_array(key_bytes);
let key_bytes: [u8; 32] = u.arbitrary()?;
let destination_token_address: Pubkey = Pubkey::new_from_array(key_bytes);
return Ok(Self::Create {
seeds,
mint_address,
destination_token_address,
schedules: schedules.to_vec(),
schedule: schedule,
});
}
_ => return Ok(Self::Unlock { seeds }),
Expand Down Expand Up @@ -70,8 +66,6 @@ pub enum VestingInstruction {
Init {
// The seed used to derive the vesting accounts address
seeds: [u8; 32],
// The number of release schedules for this contract to hold
number_of_schedules: u32,
},
/// Creates a new vesting schedule contract
///
Expand All @@ -86,7 +80,7 @@ pub enum VestingInstruction {
Create {
seeds: [u8; 32],
mint_address: Pubkey,
schedules: Vec<Schedule>,
schedule: Schedule,
},
/// Unlocks a simple vesting contract (SVC) - can only be invoked by the program itself
/// Accounts expected by this instruction:
Expand All @@ -110,14 +104,8 @@ impl VestingInstruction {
.get(..32)
.and_then(|slice| slice.try_into().ok())
.unwrap();
let number_of_schedules = rest
.get(32..36)
.and_then(|slice| slice.try_into().ok())
.map(u32::from_le_bytes)
.ok_or(InvalidInstruction)?;
Self::Init {
seeds,
number_of_schedules,
}
}
1 => {
Expand All @@ -130,30 +118,25 @@ impl VestingInstruction {
.and_then(|slice| slice.try_into().ok())
.map(Pubkey::new_from_array)
.ok_or(InvalidInstruction)?;
let number_of_schedules = rest[64..].len() / SCHEDULE_SIZE;
let mut schedules: Vec<Schedule> = Vec::with_capacity(number_of_schedules);
let mut offset = 64;
for _ in 0..number_of_schedules {
let release_time = rest
.get(offset..offset + 8)
.and_then(|slice| slice.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
let amount = rest
.get(offset + 8..offset + 16)
.and_then(|slice| slice.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
offset += SCHEDULE_SIZE;
schedules.push(Schedule {
release_time,
amount,
})
}
let offset = 64;
let release_time = rest
.get(offset..offset + 8)
.and_then(|slice| slice.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
let amount = rest
.get(offset + 8..offset + 16)
.and_then(|slice| slice.try_into().ok())
.map(u64::from_le_bytes)
.ok_or(InvalidInstruction)?;
let schedule = Schedule {
release_time,
amount,
};
Self::Create {
seeds,
mint_address,
schedules,
schedule,
}
}
2 => {
Expand All @@ -175,24 +158,20 @@ impl VestingInstruction {
match self {
&Self::Init {
seeds,
number_of_schedules,
} => {
buf.push(0);
buf.extend_from_slice(&seeds);
buf.extend_from_slice(&number_of_schedules.to_le_bytes())
}
Self::Create {
seeds,
mint_address,
schedules,
schedule,
} => {
buf.push(1);
buf.extend_from_slice(seeds);
buf.extend_from_slice(&mint_address.to_bytes());
for s in schedules.iter() {
buf.extend_from_slice(&s.release_time.to_le_bytes());
buf.extend_from_slice(&s.amount.to_le_bytes());
}
buf.extend_from_slice(&schedule.release_time.to_le_bytes());
buf.extend_from_slice(&schedule.amount.to_le_bytes());
}
&Self::Unlock { seeds } => {
buf.push(2);
Expand All @@ -211,11 +190,9 @@ pub fn init(
payer_key: &Pubkey,
vesting_account: &Pubkey,
seeds: [u8; 32],
number_of_schedules: u32,
) -> Result<Instruction, ProgramError> {
let data = VestingInstruction::Init {
seeds,
number_of_schedules,
}
.pack();
let accounts = vec![
Expand All @@ -240,13 +217,13 @@ pub fn create(
source_token_account_owner_key: &Pubkey,
source_token_account_key: &Pubkey,
mint_address: &Pubkey,
schedules: Vec<Schedule>,
schedule: Schedule,
seeds: [u8; 32],
) -> Result<Instruction, ProgramError> {
let data = VestingInstruction::Create {
mint_address: *mint_address,
seeds,
schedules,
schedule,
}
.pack();
let accounts = vec![
Expand Down Expand Up @@ -298,10 +275,10 @@ mod test {

let original_create = VestingInstruction::Create {
seeds: [50u8; 32],
schedules: vec![Schedule {
schedule: Schedule {
amount: 42,
release_time: 250,
}],
},
mint_address: mint_address.clone(),
};
let packed_create = original_create.pack();
Expand All @@ -315,7 +292,6 @@ mod test {
);

let original_init = VestingInstruction::Init {
number_of_schedules: 42,
seeds: [50u8; 32],
};
assert_eq!(
Expand Down
57 changes: 25 additions & 32 deletions program/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use spl_token::{instruction::transfer, state::Account};

use crate::{
error::VestingError,
instruction::{Schedule, VestingInstruction, SCHEDULE_SIZE},
state::{pack_schedules_into_slice, unpack_schedules, VestingSchedule, VestingScheduleHeader},
instruction::{Schedule, VestingInstruction},
state::{pack_schedule_into_slice, unpack_schedule, VestingSchedule, VestingScheduleHeader},
};

pub struct Processor {}
Expand All @@ -28,8 +28,7 @@ impl Processor {
pub fn process_init(
program_id: &Pubkey,
accounts: &[AccountInfo],
seeds: [u8; 32],
schedules: u32
seeds: [u8; 32]
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();

Expand All @@ -47,7 +46,7 @@ impl Processor {
return Err(ProgramError::InvalidArgument);
}

let state_size = (schedules as usize) * VestingSchedule::LEN + VestingScheduleHeader::LEN;
let state_size = VestingSchedule::LEN + VestingScheduleHeader::LEN;

let init_vesting_account = create_account(
&payer.key,
Expand All @@ -74,7 +73,7 @@ impl Processor {
accounts: &[AccountInfo],
seeds: [u8; 32],
mint_address: &Pubkey,
schedules: Vec<Schedule>,
schedule: Schedule,
) -> ProgramResult {
let accounts_iter = &mut accounts.iter();

Expand Down Expand Up @@ -133,26 +132,22 @@ impl Processor {
};

let mut data = vesting_account.data.borrow_mut();
if data.len() != VestingScheduleHeader::LEN + schedules.len() * VestingSchedule::LEN {
if data.len() != VestingScheduleHeader::LEN + VestingSchedule::LEN {
return Err(ProgramError::InvalidAccountData)
}
state_header.pack_into_slice(&mut data);

let mut offset = VestingScheduleHeader::LEN;
let mut total_amount: u64 = 0;

for s in schedules.iter() {
let state_schedule = VestingSchedule {
release_time: s.release_time,
amount: s.amount,
};
state_schedule.pack_into_slice(&mut data[offset..]);
let delta = total_amount.checked_add(s.amount);
match delta {
Some(n) => total_amount = n,
None => return Err(ProgramError::InvalidInstructionData), // Total amount overflows u64
}
offset += SCHEDULE_SIZE;
let state_schedule = VestingSchedule {
release_time: schedule.release_time,
amount: schedule.amount,
};
state_schedule.pack_into_slice(&mut data[VestingScheduleHeader::LEN..]);
let delta = total_amount.checked_add(schedule.amount);
match delta {
Some(n) => total_amount = n,
None => return Err(ProgramError::InvalidInstructionData), // Total amount overflows u64
}

if Account::unpack(&source_token_account.data.borrow())?.amount < total_amount {
Expand Down Expand Up @@ -224,14 +219,13 @@ impl Processor {
// Unlock the schedules that have reached maturity
let clock = Clock::from_account_info(&clock_sysvar_account)?;
let mut total_amount_to_transfer = 0;
let mut schedules = unpack_schedules(&packed_state.borrow()[VestingScheduleHeader::LEN..])?;
let mut schedule = unpack_schedule(&packed_state.borrow()[VestingScheduleHeader::LEN..])?;

for s in schedules.iter_mut() {
if clock.unix_timestamp as u64 >= s.release_time {
total_amount_to_transfer += s.amount;
s.amount = 0;
}
if clock.unix_timestamp as u64 >= schedule.release_time {
total_amount_to_transfer += schedule.amount;
schedule.amount = 0;
}

if total_amount_to_transfer == 0 {
msg!("Vesting contract has not yet reached release time");
return Err(ProgramError::InvalidArgument);
Expand All @@ -258,8 +252,8 @@ impl Processor {
)?;

// Reset released amounts to 0. This makes the simple unlock safe with complex scheduling contracts
pack_schedules_into_slice(
schedules,
pack_schedule_into_slice(
schedule,
&mut packed_state.borrow_mut()[VestingScheduleHeader::LEN..],
);

Expand All @@ -277,10 +271,9 @@ impl Processor {
match instruction {
VestingInstruction::Init {
seeds,
number_of_schedules,
} => {
msg!("Instruction: Init");
Self::process_init(program_id, accounts, seeds, number_of_schedules)
Self::process_init(program_id, accounts, seeds)
}
VestingInstruction::Unlock { seeds } => {
msg!("Instruction: Unlock");
Expand All @@ -289,15 +282,15 @@ impl Processor {
VestingInstruction::Create {
seeds,
mint_address,
schedules,
schedule,
} => {
msg!("Instruction: Create Schedule");
Self::process_create(
program_id,
accounts,
seeds,
&mint_address,
schedules,
schedule,
)
}
}
Expand Down
Loading

0 comments on commit abec526

Please sign in to comment.