From 1e01aa3cc74a04549be50e535b9571cca22beab6 Mon Sep 17 00:00:00 2001 From: armaniferrante Date: Thu, 24 Sep 2020 19:57:07 -0700 Subject: [PATCH] safe: Use linear unlock function for Vesting accounts --- safe/Cargo.toml | 5 +- safe/program/src/deposit.rs | 130 +++++++++++----------- safe/program/src/lib.rs | 14 +-- safe/program/src/mint.rs | 2 +- safe/program/src/withdraw.rs | 2 +- safe/src/accounts/mod.rs | 8 +- safe/src/accounts/vesting.rs | 203 ++++++++++++++++++----------------- safe/src/client_ext.rs | 1 + safe/src/error.rs | 9 +- safe/src/lib.rs | 17 +-- 10 files changed, 209 insertions(+), 182 deletions(-) diff --git a/safe/Cargo.toml b/safe/Cargo.toml index 7818a82..81cf324 100644 --- a/safe/Cargo.toml +++ b/safe/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [features] program = ["solana-client-gen/program", "spl-token/program", "serum-common/program"] -client = ["solana-client-gen/client", "spl-token/default", "serum-common/client"] +client = ["solana-client-gen/client", "spl-token/default", "serum-common/client", "lazy_static"] client-ext = [] test = ["rand", "solana-client-gen/client", "spl-token/default"] strict = [] @@ -22,5 +22,8 @@ solana-client-gen = { path = "../solana-client-gen" } serum-common = { path = "../common" } bytemuck = "1.4.0" +# Client only. +lazy_static = { version = "1.4.0", optional = true } + # Used for testing. rand = { version = "0.7.3", optional = true } diff --git a/safe/program/src/deposit.rs b/safe/program/src/deposit.rs index bdeb103..ac0d3ff 100644 --- a/safe/program/src/deposit.rs +++ b/safe/program/src/deposit.rs @@ -4,6 +4,7 @@ use serum_safe::error::{SafeError, SafeErrorCode}; use solana_sdk::account_info::{next_account_info, AccountInfo}; use solana_sdk::info; use solana_sdk::pubkey::Pubkey; +use solana_sdk::sysvar::clock::Clock; use solana_sdk::sysvar::rent::Rent; use solana_sdk::sysvar::Sysvar; use spl_token::pack::Pack as TokenPack; @@ -13,8 +14,9 @@ pub fn handler<'a>( program_id: &'a Pubkey, accounts: &'a [AccountInfo<'a>], vesting_acc_beneficiary: Pubkey, - vesting_slots: Vec, - vesting_amounts: Vec, + end_slot: u64, + period_count: u64, + deposit_amount: u64, ) -> Result<(), SafeError> { info!("handler: deposit"); @@ -27,11 +29,14 @@ pub fn handler<'a>( let safe_acc_info = next_account_info(acc_infos)?; let token_program_acc_info = next_account_info(acc_infos)?; let rent_acc_info = next_account_info(acc_infos)?; + let clock_acc_info = next_account_info(acc_infos)?; + let clock_slot = Clock::from_account_info(clock_acc_info)?.slot; access_control(AccessControlRequest { - vesting_slots: &vesting_slots, - vesting_amounts: &vesting_amounts, program_id, + end_slot, + period_count, + deposit_amount, vesting_acc_info, safe_acc_info, depositor_acc_info, @@ -39,6 +44,8 @@ pub fn handler<'a>( safe_vault_acc_info, token_program_acc_info, rent_acc_info, + clock_acc_info, + clock_slot, })?; // Same deal with unpack_unchecked. See the comment in `access_control` @@ -47,8 +54,10 @@ pub fn handler<'a>( &mut vesting_acc_info.try_borrow_mut_data()?, &mut |vesting_acc: &mut Vesting| { state_transition(StateTransitionRequest { - vesting_slots: vesting_slots.clone(), - vesting_amounts: vesting_amounts.clone(), + clock_slot, + end_slot, + period_count, + deposit_amount, vesting_acc, vesting_acc_beneficiary, safe_acc_info, @@ -64,11 +73,14 @@ pub fn handler<'a>( Ok(()) } -fn access_control<'a, 'b>(req: AccessControlRequest<'a, 'b>) -> Result<(), SafeError> { +fn access_control<'a>(req: AccessControlRequest<'a>) -> Result<(), SafeError> { info!("access-control: deposit"); let AccessControlRequest { program_id, + end_slot, + period_count, + deposit_amount, vesting_acc_info, safe_acc_info, depositor_acc_info, @@ -76,8 +88,8 @@ fn access_control<'a, 'b>(req: AccessControlRequest<'a, 'b>) -> Result<(), SafeE depositor_authority_acc_info, token_program_acc_info, rent_acc_info, - vesting_slots, - vesting_amounts, + clock_acc_info, + clock_slot, } = req; // Depositor authorization. @@ -118,53 +130,18 @@ fn access_control<'a, 'b>(req: AccessControlRequest<'a, 'b>) -> Result<(), SafeE // Vesting. { - let vesting_data = vesting_acc_info.try_borrow_data()?; - - // Check the account's data-dependent size is correct before unpacking. - if vesting_data.len() != Vesting::size_dyn(vesting_slots.len())? as usize { - return Err(SafeErrorCode::VestingAccountDataInvalid)?; - } - // Perform an unpack_unchecked--that is, unsafe--deserialization. - // - // We might lose information when deserializing from all zeroes, because - // Vesting has variable length Vecs (i.e., if you deserializ vec![0; 100]), - // it can deserialize to vec![0; 0], depending on the serializer. This - // is the case for bincode serialization. In other words, we might *not* - // use the entire data array upon deserializing here. - // - // As a result, we follow this with a check on the slots and amounts to - // guarantee that all subsequent instructions deal with non-zero vecs - // (thus making our serialization size deterministic). And so all further - // instructions should use the safe `unpack` variant method. - // - // This latter check is nice to have anyway, to prevent useless deposits. - // - // Switch serializers if this is a problem. - let vesting = Vesting::unpack_unchecked(&vesting_data)?; - if vesting.initialized { - return Err(SafeErrorCode::AlreadyInitialized)?; - } - if !vesting_slots - .iter() - .filter(|slot| **slot == 0) - .collect::>() - .is_empty() - { - return Err(SafeErrorCode::InvalidVestingSlots)?; - } - if !vesting_amounts - .iter() - .filter(|slot| **slot == 0) - .collect::>() - .is_empty() - { - return Err(SafeErrorCode::InvalidVestingAmounts)?; - } if vesting_acc_info.owner != program_id { return Err(SafeErrorCode::NotOwnedByProgram)?; } + let vesting = Vesting::unpack(&vesting_acc_info.try_borrow_data()?)?; + if vesting.initialized { + return Err(SafeErrorCode::AlreadyInitialized)?; + } let rent = Rent::from_account_info(rent_acc_info)?; - if !rent.is_exempt(vesting_acc_info.lamports(), vesting_data.len()) { + if !rent.is_exempt( + vesting_acc_info.lamports(), + vesting_acc_info.try_data_len()?, + ) { return Err(SafeErrorCode::NotRentExempt)?; } } @@ -183,6 +160,22 @@ fn access_control<'a, 'b>(req: AccessControlRequest<'a, 'b>) -> Result<(), SafeE } } + // Vesting schedule. + { + if *clock_acc_info.key != solana_sdk::sysvar::clock::id() { + return Err(SafeErrorCode::InvalidClock)?; + } + if end_slot <= clock_slot { + return Err(SafeErrorCode::InvalidSlot)?; + } + if period_count == 0 { + return Err(SafeErrorCode::InvalidPeriod)?; + } + if deposit_amount == 0 { + return Err(SafeErrorCode::InvalidDepositAmount)?; + } + } + // Depositor. { let depositor = spl_token::state::Account::unpack(&depositor_acc_info.try_borrow_data()?)?; @@ -201,11 +194,13 @@ fn state_transition<'a, 'b>(req: StateTransitionRequest<'a, 'b>) -> Result<(), S info!("state-transition: deposit"); let StateTransitionRequest { + clock_slot, + end_slot, + period_count, + deposit_amount, vesting_acc, vesting_acc_beneficiary, safe_acc_info, - vesting_slots, - vesting_amounts, depositor_acc_info, safe_vault_acc_info, depositor_authority_acc_info, @@ -217,8 +212,12 @@ fn state_transition<'a, 'b>(req: StateTransitionRequest<'a, 'b>) -> Result<(), S vesting_acc.safe = safe_acc_info.key.clone(); vesting_acc.beneficiary = vesting_acc_beneficiary; vesting_acc.initialized = true; - vesting_acc.slots = vesting_slots.clone(); - vesting_acc.amounts = vesting_amounts.clone(); + vesting_acc.locked_outstanding = 0; + vesting_acc.period_count = period_count; + vesting_acc.start_balance = deposit_amount; + vesting_acc.end_slot = end_slot; + vesting_acc.start_slot = clock_slot; + vesting_acc.balance = deposit_amount; } // Now transfer SPL funds from the depositor, to the @@ -226,15 +225,13 @@ fn state_transition<'a, 'b>(req: StateTransitionRequest<'a, 'b>) -> Result<(), S { info!("invoke SPL token transfer"); - let total_deposit = vesting_amounts.iter().sum(); - let deposit_instruction = spl_token::instruction::transfer( &spl_token::ID, depositor_acc_info.key, safe_vault_acc_info.key, depositor_authority_acc_info.key, &[], - total_deposit, + deposit_amount, )?; solana_sdk::program::invoke_signed( &deposit_instruction, @@ -253,8 +250,11 @@ fn state_transition<'a, 'b>(req: StateTransitionRequest<'a, 'b>) -> Result<(), S Ok(()) } -struct AccessControlRequest<'a, 'b> { +struct AccessControlRequest<'a> { program_id: &'a Pubkey, + end_slot: u64, + period_count: u64, + deposit_amount: u64, vesting_acc_info: &'a AccountInfo<'a>, safe_acc_info: &'a AccountInfo<'a>, depositor_acc_info: &'a AccountInfo<'a>, @@ -262,16 +262,18 @@ struct AccessControlRequest<'a, 'b> { safe_vault_acc_info: &'a AccountInfo<'a>, token_program_acc_info: &'a AccountInfo<'a>, rent_acc_info: &'a AccountInfo<'a>, - vesting_slots: &'b [u64], - vesting_amounts: &'b [u64], + clock_acc_info: &'a AccountInfo<'a>, + clock_slot: u64, } struct StateTransitionRequest<'a, 'b> { + clock_slot: u64, + end_slot: u64, + period_count: u64, + deposit_amount: u64, vesting_acc: &'b mut Vesting, vesting_acc_beneficiary: Pubkey, safe_acc_info: &'a AccountInfo<'a>, - vesting_slots: Vec, - vesting_amounts: Vec, depositor_acc_info: &'a AccountInfo<'a>, safe_vault_acc_info: &'a AccountInfo<'a>, depositor_authority_acc_info: &'a AccountInfo<'a>, diff --git a/safe/program/src/lib.rs b/safe/program/src/lib.rs index 2c98f34..5859d48 100644 --- a/safe/program/src/lib.rs +++ b/safe/program/src/lib.rs @@ -33,15 +33,17 @@ fn process_instruction<'a>( initialize::handler(program_id, accounts, authority, nonce) } SafeInstruction::Deposit { - vesting_account_beneficiary, - vesting_slots, - vesting_amounts, + beneficiary, + end_slot, + period_count, + deposit_amount, } => deposit::handler( program_id, accounts, - vesting_account_beneficiary, - vesting_slots, - vesting_amounts, + beneficiary, + end_slot, + period_count, + deposit_amount, ), SafeInstruction::MintLocked { token_account_owner, diff --git a/safe/program/src/mint.rs b/safe/program/src/mint.rs index 98c3ab4..80f2837 100644 --- a/safe/program/src/mint.rs +++ b/safe/program/src/mint.rs @@ -102,7 +102,7 @@ fn access_control<'a>(req: AccessControlRequest<'a>) -> Result<(), SafeError> { } // Do we have sufficient balance? if vesting.available_for_mint() < 1 { - return Err(SafeErrorCode::InsufficientBalance)?; + return Err(SafeErrorCode::InsufficientMintBalance)?; } } diff --git a/safe/program/src/withdraw.rs b/safe/program/src/withdraw.rs index bd4772b..aa6108d 100644 --- a/safe/program/src/withdraw.rs +++ b/safe/program/src/withdraw.rs @@ -127,7 +127,7 @@ fn access_control<'a>(req: AccessControlRequest<'a>) -> Result<(), SafeError> { // Do we have sufficient balance? let clock = Clock::from_account_info(clock_acc_info)?; if amount > vesting.available_for_withdrawal(clock.slot) { - return Err(SafeErrorCode::InsufficientBalance)?; + return Err(SafeErrorCode::InsufficientWithdrawalBalance)?; } } diff --git a/safe/src/accounts/mod.rs b/safe/src/accounts/mod.rs index 7e3cb11..b889250 100644 --- a/safe/src/accounts/mod.rs +++ b/safe/src/accounts/mod.rs @@ -1,9 +1,9 @@ //! mod accounts defines the storage layout for the accounts used by this program. -mod mint_receipt; -mod safe; -mod token_vault; -mod vesting; +pub mod mint_receipt; +pub mod safe; +pub mod token_vault; +pub mod vesting; pub use mint_receipt::MintReceipt; pub use safe::Safe; diff --git a/safe/src/accounts/vesting.rs b/safe/src/accounts/vesting.rs index 14b10ec..e2eb8f5 100644 --- a/safe/src/accounts/vesting.rs +++ b/safe/src/accounts/vesting.rs @@ -1,15 +1,15 @@ -use crate::error::SafeError; use solana_client_gen::solana_sdk::pubkey::Pubkey; -use std::cmp::Ordering; + +#[cfg(feature = "client")] +lazy_static::lazy_static! { + pub static ref SIZE: u64 = Vesting::default() + .size() + .expect("Vesting has a fixed size"); +} /// The Vesting account represents a single deposit of a token /// available for withdrawal over a period of time determined by /// a vesting schedule. -/// -/// Note that, unlike other accounts, this account is dynamically -/// sized, which clients must consider when creating these accounts. -/// use the `size_dyn` method to determine how large the account -/// data should be. #[derive(Default, Debug, serde::Serialize, serde::Deserialize)] pub struct Vesting { /// The Safe instance this account is associated with. @@ -18,70 +18,84 @@ pub struct Vesting { pub beneficiary: Pubkey, /// True iff the vesting account has been initialized via deposit. pub initialized: bool, - /// The amount of locked SRM outstanding. + /// The amount of locked SRM minted and in circulation. pub locked_outstanding: u64, - /// The Solana slots at which each amount vests. - pub slots: Vec, - /// The amount that vests at each slot. - pub amounts: Vec, + /// The outstanding SRM deposit backing this vesting account. + pub balance: u64, + /// The starting balance of this vesting account, i.e., how much was + /// originally deposited. + pub start_balance: u64, + /// The slot at which this vesting account was created. + pub start_slot: u64, + /// The slot at which all the tokens associated with this account + /// should be vested. + pub end_slot: u64, + /// The number of times vesting will occur. For example, if vesting + /// is once a year over seven years, this will be 7. + pub period_count: u64, } impl Vesting { - /// Returns the total deposit in this vesting account. - pub fn total(&self) -> u64 { - self.amounts.iter().sum() + /// Deducts the given amount from the vesting account upon withdrawal. + pub fn deduct(&mut self, amount: u64) { + self.balance -= amount; } /// Returns the amount available for minting locked token NFTs. pub fn available_for_mint(&self) -> u64 { - self.total() - self.locked_outstanding + self.balance - self.locked_outstanding } /// Returns the amount available for withdrawal as of the given slot. - pub fn available_for_withdrawal(&self, slot: u64) -> u64 { - self.vested_amount(slot) - self.locked_outstanding + pub fn available_for_withdrawal(&self, current_slot: u64) -> u64 { + std::cmp::min(self.balance_vested(current_slot), self.available_for_mint()) } - /// Returns the total vested amount up to the given slot. This is not - /// necessarily available for withdrawal. - pub fn vested_amount(&self, slot: u64) -> u64 { - self.slots - .iter() - .filter(|s| **s <= slot) - .enumerate() - .map(|(idx, _slot)| self.amounts[idx]) - .sum() + // The outstanding SRM deposit associated with this account that has not + // been withdraw. Does not consider outstanding lSRM in circulation. + fn balance_vested(&self, current_slot: u64) -> u64 { + self.total_vested(current_slot) - self.withdrawn_amount() } - /// Deducts the given amount from the vesting account from the earliest - /// vesting slots. - pub fn deduct(&mut self, mut amount: u64) { - for k in 0..self.amounts.len() { - match amount.cmp(&self.amounts[k]) { - Ordering::Less => { - self.amounts[k] -= amount; - return; - } - Ordering::Equal => { - self.amounts[k] = 0; - return; - } - Ordering::Greater => { - let old = self.amounts[k]; - self.amounts[k] = 0; - amount -= old; - } - } + // Returns the total vested amount up to the given slot. + fn total_vested(&self, current_slot: u64) -> u64 { + assert!(current_slot >= self.start_slot); + + if current_slot >= self.end_slot { + return self.start_balance; } + self.linear_unlock(current_slot) } - /// Returns the dynamic size of the account's data array, assuming it has - /// `slot_account` vesting periods. - pub fn size_dyn(slot_count: usize) -> Result { - let mut d: Vesting = Default::default(); - d.slots = vec![0u64; slot_count]; - d.amounts = vec![0u64; slot_count]; - Ok(d.size()?) + // Returns the amount withdrawn from this vesting account. + fn withdrawn_amount(&self) -> u64 { + self.start_balance - self.balance + } + + fn linear_unlock(&self, current_slot: u64) -> u64 { + let (end_slot, start_slot) = { + // If we can't perfectly partition the vesting window, + // push the start window back so that we can. + // + // This has the effect of making the first vesting period act as + // a minor "cliff" that vests slightly more than the rest of the + // periods. + let overflow = (self.end_slot - self.start_slot) % self.period_count; + if overflow != 0 { + (self.end_slot, self.start_slot - overflow) + } else { + (self.end_slot, self.start_slot) + } + }; + + let vested_period_count = { + let period = (end_slot - start_slot) / self.period_count; + let current_period_count = (current_slot - start_slot) / period; + std::cmp::min(current_period_count, self.period_count) + }; + let reward_per_period = self.start_balance / self.period_count; + + return vested_period_count * reward_per_period; } } @@ -98,22 +112,28 @@ mod tests { // Given a vesting account. let safe = Keypair::generate(&mut OsRng).pubkey(); let beneficiary = Keypair::generate(&mut OsRng).pubkey(); - let amounts = vec![1, 2, 3, 4]; - let slots = vec![5, 6, 7, 8]; let initialized = true; let locked_outstanding = 99; + let start_balance = 10; + let balance = start_balance; + let start_slot = 11; + let end_slot = 12; + let period_count = 13; let vesting_acc = Vesting { safe, beneficiary, initialized, locked_outstanding, - amounts: amounts.clone(), - slots: slots.clone(), + balance, + start_balance, + start_slot, + end_slot, + period_count, }; // When I pack it into a slice. let mut dst = vec![]; - dst.resize(Vesting::size_dyn(slots.len()).unwrap() as usize, 0u8); + dst.resize(Vesting::default().size().unwrap() as usize, 0u8); Vesting::pack(vesting_acc, &mut dst).unwrap(); // Then I can unpack it from a slice. @@ -121,27 +141,23 @@ mod tests { assert_eq!(va.safe, safe); assert_eq!(va.beneficiary, beneficiary); assert_eq!(va.locked_outstanding, locked_outstanding); - - assert_eq!(va.amounts.len(), amounts.len()); - assert_eq!(va.slots.len(), slots.len()); - let match_amounts = va - .amounts - .iter() - .zip(&amounts) - .filter(|&(a, b)| a == b) - .count(); - assert_eq!(va.amounts.len(), match_amounts); - let match_slots = va.slots.iter().zip(&slots).filter(|&(a, b)| a == b).count(); - assert_eq!(va.amounts.len(), match_slots); assert_eq!(va.initialized, initialized); + assert_eq!(va.start_balance, start_balance); + assert_eq!(va.balance, balance); + assert_eq!(va.start_slot, start_slot); + assert_eq!(va.end_slot, end_slot); + assert_eq!(va.period_count, period_count); } #[test] fn available_for_withdrawal() { let safe = Keypair::generate(&mut OsRng).pubkey(); let beneficiary = Keypair::generate(&mut OsRng).pubkey(); - let amounts = vec![1, 2, 3, 4]; - let slots = vec![5, 6, 7, 8]; + let balance = 10; + let start_balance = 10; + let start_slot = 10; + let end_slot = 20; + let period_count = 5; let initialized = true; let locked_outstanding = 0; let vesting_acc = Vesting { @@ -149,39 +165,34 @@ mod tests { beneficiary, initialized, locked_outstanding, - amounts: amounts.clone(), - slots: slots.clone(), + balance, + start_balance, + start_slot, + end_slot, + period_count, }; - assert_eq!(0, vesting_acc.available_for_withdrawal(4)); - assert_eq!(1, vesting_acc.available_for_withdrawal(5)); - assert_eq!(3, vesting_acc.available_for_withdrawal(6)); - assert_eq!(10, vesting_acc.available_for_withdrawal(8)); + assert_eq!(0, vesting_acc.available_for_withdrawal(10)); + assert_eq!(0, vesting_acc.available_for_withdrawal(11)); + assert_eq!(2, vesting_acc.available_for_withdrawal(12)); + assert_eq!(2, vesting_acc.available_for_withdrawal(13)); + assert_eq!(4, vesting_acc.available_for_withdrawal(14)); + assert_eq!(8, vesting_acc.available_for_withdrawal(19)); + assert_eq!(10, vesting_acc.available_for_withdrawal(20)); assert_eq!(10, vesting_acc.available_for_withdrawal(100)); } #[test] - fn unpack_zeroes_size() { - let og_size = Vesting::size_dyn(5).unwrap(); + fn unpack_zeroes() { + let og_size = Vesting::default().size().unwrap(); let zero_data = vec![0; og_size as usize]; - let r = Vesting::unpack(&zero_data); - match r { - Ok(_) => panic!("expect error"), - Err(e) => assert_eq!(e, ProgramError::InvalidAccountData), - } - } - - #[test] - fn unpack_unchecked_zeroes_size() { - let og_size = Vesting::size_dyn(5).unwrap(); - let zero_data = vec![0; og_size as usize]; - let r = Vesting::unpack_unchecked(&zero_data).unwrap(); + let r = Vesting::unpack(&zero_data).unwrap(); assert_eq!(r.initialized, false); assert_eq!(r.safe, Pubkey::new(&[0; 32])); assert_eq!(r.beneficiary, Pubkey::new(&[0; 32])); assert_eq!(r.locked_outstanding, 0); - // Notice how we lose information here when deserializing from - // all zeroes. - assert_eq!(r.slots.len(), 0); - assert_eq!(r.amounts.len(), 0); + assert_eq!(r.balance, 0); + assert_eq!(r.start_slot, 0); + assert_eq!(r.end_slot, 0); + assert_eq!(r.period_count, 0); } } diff --git a/safe/src/client_ext.rs b/safe/src/client_ext.rs index 1febef5..aee93e1 100644 --- a/safe/src/client_ext.rs +++ b/safe/src/client_ext.rs @@ -1,5 +1,6 @@ //! The client_ext module extends the auto-generated program client. +use crate::accounts::vesting; use crate::accounts::{MintReceipt, Safe}; use serum_common::pack::Pack; use solana_client_gen::prelude::*; diff --git a/safe/src/error.rs b/safe/src/error.rs index 1d9fede..397594b 100644 --- a/safe/src/error.rs +++ b/safe/src/error.rs @@ -23,7 +23,7 @@ pub enum SafeErrorCode { SafeDataInvalid = 8, NotSignedByAuthority = 11, WrongNumberOfAccounts = 12, - InsufficientBalance = 13, + InsufficientMintBalance = 13, Unauthorized = 14, MintAlreadyInitialized = 15, ReceiptAlreadyInitialized = 16, @@ -40,12 +40,15 @@ pub enum SafeErrorCode { InvalidSerialization = 27, SizeNotAvailable = 28, UnitializedTokenMint = 29, - InvalidVestingSlots = 30, - InvalidVestingAmounts = 31, + InvalidSlot = 30, + InvalidClock = 31, InvalidRentSysvar = 32, InvalidMint = 33, WrongSafe = 34, WrongVestingAccount = 35, + InvalidDepositAmount = 36, + InvalidPeriod = 37, + InsufficientWithdrawalBalance = 38, Unknown = 1000, } diff --git a/safe/src/lib.rs b/safe/src/lib.rs index 52f5663..1cae33c 100644 --- a/safe/src/lib.rs +++ b/safe/src/lib.rs @@ -46,15 +46,20 @@ pub mod instruction { /// 4. `[]` Safe instance. /// 5. `[]` SPL token program. /// 6. `[]` Rent sysvar. - #[cfg_attr(feature = "client", create_account(..))] + /// 7. `[]` Clock sysvar. + #[cfg_attr(feature = "client", create_account(*vesting::SIZE))] Deposit { /// The beneficiary of the vesting account, i.e., /// the user who will own the SRM upon vesting. - vesting_account_beneficiary: Pubkey, - /// The Solana slot number at which point a vesting amount unlocks. - vesting_slots: Vec, - /// The amount of SRM to release for each vesting_slot. - vesting_amounts: Vec, + beneficiary: Pubkey, + /// The Solana slot number at which point the entire deposit will + /// be vested. + end_slot: u64, + /// The number of vesting periods for the account. For example, + /// a vesting yearly over seven years would make this 7. + period_count: u64, + /// The amount to deposit into the vesting account. + deposit_amount: u64, }, /// Withdraw withdraws the given amount from the given vesting /// account subject to a vesting schedule.