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

feat: deprecating schedules array in favour of single schedule #4

Merged
merged 7 commits into from
Sep 25, 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
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
Loading