Skip to content
This repository was archived by the owner on Dec 20, 2025. It is now read-only.

Commit abec526

Browse files
authored
feat: deprecating schedules array in favour of single schedule (#4)
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`
1 parent e324816 commit abec526

4 files changed

Lines changed: 69 additions & 124 deletions

File tree

program/src/instruction.rs

Lines changed: 26 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,19 @@ impl Arbitrary for VestingInstruction {
2020
let choice = u.choose(&[0, 1, 2])?;
2121
match choice {
2222
0 => {
23-
let number_of_schedules = u.arbitrary()?;
2423
return Ok(Self::Init {
2524
seeds,
26-
number_of_schedules,
2725
});
2826
}
2927
1 => {
30-
let schedules: [Schedule; 10] = u.arbitrary()?;
28+
let schedule: [Schedule; 10] = u.arbitrary()?;
3129
let key_bytes: [u8; 32] = u.arbitrary()?;
3230
let mint_address: Pubkey = Pubkey::new_from_array(key_bytes);
3331
let key_bytes: [u8; 32] = u.arbitrary()?;
34-
let destination_token_address: Pubkey = Pubkey::new_from_array(key_bytes);
3532
return Ok(Self::Create {
3633
seeds,
3734
mint_address,
38-
destination_token_address,
39-
schedules: schedules.to_vec(),
35+
schedule: schedule,
4036
});
4137
}
4238
_ => return Ok(Self::Unlock { seeds }),
@@ -70,8 +66,6 @@ pub enum VestingInstruction {
7066
Init {
7167
// The seed used to derive the vesting accounts address
7268
seeds: [u8; 32],
73-
// The number of release schedules for this contract to hold
74-
number_of_schedules: u32,
7569
},
7670
/// Creates a new vesting schedule contract
7771
///
@@ -86,7 +80,7 @@ pub enum VestingInstruction {
8680
Create {
8781
seeds: [u8; 32],
8882
mint_address: Pubkey,
89-
schedules: Vec<Schedule>,
83+
schedule: Schedule,
9084
},
9185
/// Unlocks a simple vesting contract (SVC) - can only be invoked by the program itself
9286
/// Accounts expected by this instruction:
@@ -110,14 +104,8 @@ impl VestingInstruction {
110104
.get(..32)
111105
.and_then(|slice| slice.try_into().ok())
112106
.unwrap();
113-
let number_of_schedules = rest
114-
.get(32..36)
115-
.and_then(|slice| slice.try_into().ok())
116-
.map(u32::from_le_bytes)
117-
.ok_or(InvalidInstruction)?;
118107
Self::Init {
119108
seeds,
120-
number_of_schedules,
121109
}
122110
}
123111
1 => {
@@ -130,30 +118,25 @@ impl VestingInstruction {
130118
.and_then(|slice| slice.try_into().ok())
131119
.map(Pubkey::new_from_array)
132120
.ok_or(InvalidInstruction)?;
133-
let number_of_schedules = rest[64..].len() / SCHEDULE_SIZE;
134-
let mut schedules: Vec<Schedule> = Vec::with_capacity(number_of_schedules);
135-
let mut offset = 64;
136-
for _ in 0..number_of_schedules {
137-
let release_time = rest
138-
.get(offset..offset + 8)
139-
.and_then(|slice| slice.try_into().ok())
140-
.map(u64::from_le_bytes)
141-
.ok_or(InvalidInstruction)?;
142-
let amount = rest
143-
.get(offset + 8..offset + 16)
144-
.and_then(|slice| slice.try_into().ok())
145-
.map(u64::from_le_bytes)
146-
.ok_or(InvalidInstruction)?;
147-
offset += SCHEDULE_SIZE;
148-
schedules.push(Schedule {
149-
release_time,
150-
amount,
151-
})
152-
}
121+
let offset = 64;
122+
let release_time = rest
123+
.get(offset..offset + 8)
124+
.and_then(|slice| slice.try_into().ok())
125+
.map(u64::from_le_bytes)
126+
.ok_or(InvalidInstruction)?;
127+
let amount = rest
128+
.get(offset + 8..offset + 16)
129+
.and_then(|slice| slice.try_into().ok())
130+
.map(u64::from_le_bytes)
131+
.ok_or(InvalidInstruction)?;
132+
let schedule = Schedule {
133+
release_time,
134+
amount,
135+
};
153136
Self::Create {
154137
seeds,
155138
mint_address,
156-
schedules,
139+
schedule,
157140
}
158141
}
159142
2 => {
@@ -175,24 +158,20 @@ impl VestingInstruction {
175158
match self {
176159
&Self::Init {
177160
seeds,
178-
number_of_schedules,
179161
} => {
180162
buf.push(0);
181163
buf.extend_from_slice(&seeds);
182-
buf.extend_from_slice(&number_of_schedules.to_le_bytes())
183164
}
184165
Self::Create {
185166
seeds,
186167
mint_address,
187-
schedules,
168+
schedule,
188169
} => {
189170
buf.push(1);
190171
buf.extend_from_slice(seeds);
191172
buf.extend_from_slice(&mint_address.to_bytes());
192-
for s in schedules.iter() {
193-
buf.extend_from_slice(&s.release_time.to_le_bytes());
194-
buf.extend_from_slice(&s.amount.to_le_bytes());
195-
}
173+
buf.extend_from_slice(&schedule.release_time.to_le_bytes());
174+
buf.extend_from_slice(&schedule.amount.to_le_bytes());
196175
}
197176
&Self::Unlock { seeds } => {
198177
buf.push(2);
@@ -211,11 +190,9 @@ pub fn init(
211190
payer_key: &Pubkey,
212191
vesting_account: &Pubkey,
213192
seeds: [u8; 32],
214-
number_of_schedules: u32,
215193
) -> Result<Instruction, ProgramError> {
216194
let data = VestingInstruction::Init {
217195
seeds,
218-
number_of_schedules,
219196
}
220197
.pack();
221198
let accounts = vec![
@@ -240,13 +217,13 @@ pub fn create(
240217
source_token_account_owner_key: &Pubkey,
241218
source_token_account_key: &Pubkey,
242219
mint_address: &Pubkey,
243-
schedules: Vec<Schedule>,
220+
schedule: Schedule,
244221
seeds: [u8; 32],
245222
) -> Result<Instruction, ProgramError> {
246223
let data = VestingInstruction::Create {
247224
mint_address: *mint_address,
248225
seeds,
249-
schedules,
226+
schedule,
250227
}
251228
.pack();
252229
let accounts = vec![
@@ -298,10 +275,10 @@ mod test {
298275

299276
let original_create = VestingInstruction::Create {
300277
seeds: [50u8; 32],
301-
schedules: vec![Schedule {
278+
schedule: Schedule {
302279
amount: 42,
303280
release_time: 250,
304-
}],
281+
},
305282
mint_address: mint_address.clone(),
306283
};
307284
let packed_create = original_create.pack();
@@ -315,7 +292,6 @@ mod test {
315292
);
316293

317294
let original_init = VestingInstruction::Init {
318-
number_of_schedules: 42,
319295
seeds: [50u8; 32],
320296
};
321297
assert_eq!(

program/src/processor.rs

Lines changed: 25 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ use spl_token::{instruction::transfer, state::Account};
1818

1919
use crate::{
2020
error::VestingError,
21-
instruction::{Schedule, VestingInstruction, SCHEDULE_SIZE},
22-
state::{pack_schedules_into_slice, unpack_schedules, VestingSchedule, VestingScheduleHeader},
21+
instruction::{Schedule, VestingInstruction},
22+
state::{pack_schedule_into_slice, unpack_schedule, VestingSchedule, VestingScheduleHeader},
2323
};
2424

2525
pub struct Processor {}
@@ -28,8 +28,7 @@ impl Processor {
2828
pub fn process_init(
2929
program_id: &Pubkey,
3030
accounts: &[AccountInfo],
31-
seeds: [u8; 32],
32-
schedules: u32
31+
seeds: [u8; 32]
3332
) -> ProgramResult {
3433
let accounts_iter = &mut accounts.iter();
3534

@@ -47,7 +46,7 @@ impl Processor {
4746
return Err(ProgramError::InvalidArgument);
4847
}
4948

50-
let state_size = (schedules as usize) * VestingSchedule::LEN + VestingScheduleHeader::LEN;
49+
let state_size = VestingSchedule::LEN + VestingScheduleHeader::LEN;
5150

5251
let init_vesting_account = create_account(
5352
&payer.key,
@@ -74,7 +73,7 @@ impl Processor {
7473
accounts: &[AccountInfo],
7574
seeds: [u8; 32],
7675
mint_address: &Pubkey,
77-
schedules: Vec<Schedule>,
76+
schedule: Schedule,
7877
) -> ProgramResult {
7978
let accounts_iter = &mut accounts.iter();
8079

@@ -133,26 +132,22 @@ impl Processor {
133132
};
134133

135134
let mut data = vesting_account.data.borrow_mut();
136-
if data.len() != VestingScheduleHeader::LEN + schedules.len() * VestingSchedule::LEN {
135+
if data.len() != VestingScheduleHeader::LEN + VestingSchedule::LEN {
137136
return Err(ProgramError::InvalidAccountData)
138137
}
139138
state_header.pack_into_slice(&mut data);
140139

141-
let mut offset = VestingScheduleHeader::LEN;
142140
let mut total_amount: u64 = 0;
143141

144-
for s in schedules.iter() {
145-
let state_schedule = VestingSchedule {
146-
release_time: s.release_time,
147-
amount: s.amount,
148-
};
149-
state_schedule.pack_into_slice(&mut data[offset..]);
150-
let delta = total_amount.checked_add(s.amount);
151-
match delta {
152-
Some(n) => total_amount = n,
153-
None => return Err(ProgramError::InvalidInstructionData), // Total amount overflows u64
154-
}
155-
offset += SCHEDULE_SIZE;
142+
let state_schedule = VestingSchedule {
143+
release_time: schedule.release_time,
144+
amount: schedule.amount,
145+
};
146+
state_schedule.pack_into_slice(&mut data[VestingScheduleHeader::LEN..]);
147+
let delta = total_amount.checked_add(schedule.amount);
148+
match delta {
149+
Some(n) => total_amount = n,
150+
None => return Err(ProgramError::InvalidInstructionData), // Total amount overflows u64
156151
}
157152

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

229-
for s in schedules.iter_mut() {
230-
if clock.unix_timestamp as u64 >= s.release_time {
231-
total_amount_to_transfer += s.amount;
232-
s.amount = 0;
233-
}
224+
if clock.unix_timestamp as u64 >= schedule.release_time {
225+
total_amount_to_transfer += schedule.amount;
226+
schedule.amount = 0;
234227
}
228+
235229
if total_amount_to_transfer == 0 {
236230
msg!("Vesting contract has not yet reached release time");
237231
return Err(ProgramError::InvalidArgument);
@@ -258,8 +252,8 @@ impl Processor {
258252
)?;
259253

260254
// Reset released amounts to 0. This makes the simple unlock safe with complex scheduling contracts
261-
pack_schedules_into_slice(
262-
schedules,
255+
pack_schedule_into_slice(
256+
schedule,
263257
&mut packed_state.borrow_mut()[VestingScheduleHeader::LEN..],
264258
);
265259

@@ -277,10 +271,9 @@ impl Processor {
277271
match instruction {
278272
VestingInstruction::Init {
279273
seeds,
280-
number_of_schedules,
281274
} => {
282275
msg!("Instruction: Init");
283-
Self::process_init(program_id, accounts, seeds, number_of_schedules)
276+
Self::process_init(program_id, accounts, seeds)
284277
}
285278
VestingInstruction::Unlock { seeds } => {
286279
msg!("Instruction: Unlock");
@@ -289,15 +282,15 @@ impl Processor {
289282
VestingInstruction::Create {
290283
seeds,
291284
mint_address,
292-
schedules,
285+
schedule,
293286
} => {
294287
msg!("Instruction: Create Schedule");
295288
Self::process_create(
296289
program_id,
297290
accounts,
298291
seeds,
299292
&mint_address,
300-
schedules,
293+
schedule,
301294
)
302295
}
303296
}

0 commit comments

Comments
 (0)