diff --git a/.gitignore b/.gitignore index ee7038e..1806442 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,5 @@ js/lib /node_modules -js/dist \ No newline at end of file +js/dist +**/wallet.json \ No newline at end of file diff --git a/js/src/example.ts b/js/src/example.ts index 172fbea..58259fc 100644 --- a/js/src/example.ts +++ b/js/src/example.ts @@ -20,32 +20,7 @@ const wallet = Keypair.fromSecretKey( ); /** There are better way to generate an array of dates but be careful as it's irreversible */ -const DATES = [ - new Date(2022, 12), - new Date(2023, 1), - new Date(2023, 2), - new Date(2023, 3), - new Date(2023, 4), - new Date(2023, 5), - new Date(2023, 6), - new Date(2023, 7), - new Date(2023, 8), - new Date(2023, 9), - new Date(2023, 10), - new Date(2023, 11), - new Date(2024, 12), - new Date(2024, 2), - new Date(2024, 3), - new Date(2024, 4), - new Date(2024, 5), - new Date(2024, 6), - new Date(2024, 7), - new Date(2024, 8), - new Date(2024, 9), - new Date(2024, 10), - new Date(2024, 11), - new Date(2024, 12), -]; +const DATE = new Date(2022, 12); /** Info about the desintation */ const DESTINATION_OWNER = new PublicKey(''); @@ -86,19 +61,14 @@ const checks = async () => { /** Function that locks the tokens */ const lock = async () => { await checks(); - const schedules: Schedule[] = []; - for (let date of DATES) { - schedules.push( - new Schedule( - /** Has to be in seconds */ - // @ts-ignore - new Numberu64(date.getTime() / 1_000), - /** Don't forget to add decimals */ - // @ts-ignore - new Numberu64(AMOUNT_PER_SCHEDULE * Math.pow(10, DECIMALS)), - ), - ); - } + const schedule: Schedule = new Schedule( + /** Has to be in seconds */ + // @ts-ignore + new Numberu64(DATE.getTime() / 1_000), + /** Don't forget to add decimals */ + // @ts-ignore + new Numberu64(AMOUNT_PER_SCHEDULE * Math.pow(10, DECIMALS)), + ); const seed = generateRandomSeed(); console.log(`Seed: ${seed}`); @@ -112,7 +82,7 @@ const lock = async () => { SOURCE_TOKEN_ACCOUNT, DESTINATION_TOKEN_ACCOUNT, MINT, - schedules, + schedule, ); const tx = await signAndSendInstructions(connection, [], wallet, instruction); diff --git a/js/src/instructions.ts b/js/src/instructions.ts index 75e9cc9..cc96473 100644 --- a/js/src/instructions.ts +++ b/js/src/instructions.ts @@ -12,14 +12,11 @@ export function createInitInstruction( vestingProgramId: PublicKey, payerKey: PublicKey, vestingAccountKey: PublicKey, - seeds: Array, - numberOfSchedules: number, + seeds: Array ): TransactionInstruction { let buffers = [ Buffer.from(Int8Array.from([0]).buffer), Buffer.concat(seeds), - // @ts-ignore - new Numberu32(numberOfSchedules).toBuffer(), ]; const data = Buffer.concat(buffers); @@ -62,7 +59,7 @@ export function createCreateInstruction( sourceTokenAccountKey: PublicKey, destinationTokenAccountKey: PublicKey, mintAddress: PublicKey, - schedules: Array, + schedule: Schedule, seeds: Array, ): TransactionInstruction { let buffers = [ @@ -72,9 +69,7 @@ export function createCreateInstruction( destinationTokenAccountKey.toBuffer(), ]; - schedules.forEach(s => { - buffers.push(s.toBuffer()); - }); + buffers.push(schedule.toBuffer()); const data = Buffer.concat(buffers); const keys = [ @@ -158,3 +153,51 @@ export function createUnlockInstruction( data, }); } + +export function createInitializeUnlockInstruction( + vestingProgramId: PublicKey, + tokenProgramId: PublicKey, + clockSysvarId: PublicKey, + vestingAccountKey: PublicKey, + vestingTokenAccountKey: PublicKey, + destinationTokenAccountKey: PublicKey, + seeds: Array, +): TransactionInstruction { + const data = Buffer.concat([ + Buffer.from(Int8Array.from([3]).buffer), + Buffer.concat(seeds), + ]); + + const keys = [ + { + pubkey: tokenProgramId, + isSigner: false, + isWritable: false, + }, + { + pubkey: clockSysvarId, + isSigner: false, + isWritable: false, + }, + { + pubkey: vestingAccountKey, + isSigner: false, + isWritable: true, + }, + { + pubkey: vestingTokenAccountKey, + isSigner: false, + isWritable: true, + }, + { + pubkey: destinationTokenAccountKey, + isSigner: false, + isWritable: true, + }, + ]; + return new TransactionInstruction({ + keys, + programId: vestingProgramId, + data, + }); +} diff --git a/js/src/main.ts b/js/src/main.ts index 1a21e37..1d60ea9 100644 --- a/js/src/main.ts +++ b/js/src/main.ts @@ -14,6 +14,7 @@ import { createCreateInstruction, createInitInstruction, createUnlockInstruction, + createInitializeUnlockInstruction, } from './instructions'; import { ContractInfo, Schedule } from './state'; import { assert } from 'console'; @@ -36,7 +37,7 @@ export const TOKEN_VESTING_PROGRAM_ID = new PublicKey( * @param possibleSourceTokenPubkey The source token account (i.e where locked tokens are originating from), if null it defaults to the ATA * @param destinationTokenPubkey The destination token account i.e where unlocked tokens will be transfered * @param mintAddress The mint of the tokens being vested - * @param schedules The array of vesting schedules + * @param schedule The vesting schedule * @returns An array of `TransactionInstruction` */ export async function create( @@ -48,7 +49,7 @@ export async function create( possibleSourceTokenPubkey: PublicKey | null, destinationTokenPubkey: PublicKey, mintAddress: PublicKey, - schedules: Array, + schedule: Schedule, ): Promise> { // If no source token account was given, use the associated source account if (possibleSourceTokenPubkey == null) { @@ -92,8 +93,7 @@ export async function create( programId, payer, vestingAccountKey, - [seedWord], - schedules.length, + [seedWord] ), createAssociatedTokenAccountInstruction( payer, @@ -110,7 +110,7 @@ export async function create( possibleSourceTokenPubkey, destinationTokenPubkey, mintAddress, - schedules, + schedule, [seedWord], ), ]; @@ -161,6 +161,50 @@ export async function unlock( return instruction; } +/** + * This function can be used to initialize the unlock of vested tokens + * @param connection The Solana RPC connection object + * @param programId The token vesting program ID + * @param seedWord Seed words used to derive the vesting account + * @param mintAddress The mint of the vested tokens + * @returns An array of `TransactionInstruction` + */ +export async function initializeUnlock( + connection: Connection, + programId: PublicKey, + seedWord: Buffer | Uint8Array, + mintAddress: PublicKey, +): Promise> { + seedWord = seedWord.slice(0, 31); + const [vestingAccountKey, bump] = await PublicKey.findProgramAddress( + [seedWord], + programId, + ); + seedWord = Buffer.from(seedWord.toString('hex') + bump.toString(16), 'hex'); + + const vestingTokenAccountKey = await getAssociatedTokenAddress( + mintAddress, + vestingAccountKey, + true, + ); + + const vestingInfo = await getContractInfo(connection, vestingAccountKey); + + let instruction = [ + createInitializeUnlockInstruction( + programId, + TOKEN_PROGRAM_ID, + SYSVAR_CLOCK_PUBKEY, + vestingAccountKey, + vestingTokenAccountKey, + vestingInfo.destinationAddress, + [seedWord], + ), + ]; + + return instruction; +} + /** * This function can be used retrieve information about a vesting account * @param connection The Solana RPC connection object diff --git a/program/Cargo.lock b/program/Cargo.lock index e209dd8..6a95bf8 100644 --- a/program/Cargo.lock +++ b/program/Cargo.lock @@ -4466,6 +4466,29 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-test-framework" +version = "1.18.0" +source = "git+https://github.com/halbornteam/solana-test-framework?branch=solana1.18#946016679bf8d9c85b2e72fd441d4920b43ef716" +dependencies = [ + "async-trait", + "bincode", + "borsh 0.9.3", + "chrono-humanize", + "futures", + "log", + "serde", + "solana-banks-client", + "solana-client", + "solana-program", + "solana-program-runtime", + "solana-program-test", + "solana-sdk", + "spl-associated-token-account 1.1.3", + "spl-token 4.0.0", + "thiserror", +] + [[package]] name = "solana-thin-client" version = "1.18.23" @@ -5311,6 +5334,7 @@ dependencies = [ "solana-program", "solana-program-test", "solana-sdk", + "solana-test-framework", "spl-associated-token-account 1.1.3", "spl-token 3.5.0", "thiserror", diff --git a/program/Cargo.toml b/program/Cargo.toml index 3ca5f4e..582b91d 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -28,8 +28,9 @@ arbitrary = { version = "0.4", features = ["derive"], optional = true } honggfuzz = { version = "0.5", optional = true } [dev-dependencies] -solana-sdk = "1.5.6" -solana-program-test = "1.5.6" +solana-sdk = "1.18.23" +solana-program-test = "1.18.23" +solana-test-framework = { git = "https://github.com/halbornteam/solana-test-framework", branch = "solana1.18" } tokio = { version = "1.0", features = ["macros"]} [lib] diff --git a/program/src/instruction.rs b/program/src/instruction.rs index 9556a47..f8ccd9a 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -17,7 +17,7 @@ use arbitrary::Arbitrary; impl Arbitrary for VestingInstruction { fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { let seeds: [u8; 32] = u.arbitrary()?; - let choice = u.choose(&[0, 1, 2])?; + let choice = u.choose(&[0, 1, 2, 3])?; match choice { 0 => { return Ok(Self::Init { @@ -35,7 +35,8 @@ impl Arbitrary for VestingInstruction { schedule: schedule, }); } - _ => return Ok(Self::Unlock { seeds }), + 2 => return Ok(Self::Unlock { seeds }), + _ => return Ok(Self::InitializeUnlock { seeds }), } } } @@ -92,6 +93,17 @@ pub enum VestingInstruction { /// 2. `[writable]` The vesting spl-token account /// 3. `[writable]` The destination spl-token account Unlock { seeds: [u8; 32] }, + + /// Initializes the unlocking period - can only be invoked by the program itself + /// Accounts expected by this instruction: + /// + /// * Single owner + /// 0. `[]` The spl-token program account + /// 1. `[]` The clock sysvar account + /// 1. `[writable]` The vesting account + /// 2. `[writable]` The vesting spl-token account + /// 3. `[writable]` The destination spl-token account + InitializeUnlock { seeds: [u8; 32] }, } impl VestingInstruction { @@ -139,12 +151,15 @@ impl VestingInstruction { schedule, } } - 2 => { + 2 | 3 => { let seeds: [u8; 32] = rest .get(..32) .and_then(|slice| slice.try_into().ok()) .unwrap(); - Self::Unlock { seeds } + match tag { + 2 => Self::Unlock { seeds }, + _ => Self::InitializeUnlock { seeds }, + } } _ => { msg!("Unsupported tag"); @@ -177,6 +192,10 @@ impl VestingInstruction { buf.push(2); buf.extend_from_slice(&seeds); } + &Self::InitializeUnlock { seeds } => { + buf.push(3); + buf.extend_from_slice(&seeds); + } }; buf } @@ -265,6 +284,31 @@ pub fn unlock( }) } +// Creates an `Unlock` instruction +pub fn initialize_unlock( + vesting_program_id: &Pubkey, + token_program_id: &Pubkey, + clock_sysvar_id: &Pubkey, + vesting_account_key: &Pubkey, + vesting_token_account_key: &Pubkey, + destination_token_account_key: &Pubkey, + seeds: [u8; 32], +) -> Result { + let data = VestingInstruction::InitializeUnlock { seeds }.pack(); + let accounts = vec![ + AccountMeta::new_readonly(*token_program_id, false), + AccountMeta::new_readonly(*clock_sysvar_id, false), + AccountMeta::new(*vesting_account_key, false), + AccountMeta::new(*vesting_token_account_key, false), + AccountMeta::new(*destination_token_account_key, false), + ]; + Ok(Instruction { + program_id: *vesting_program_id, + accounts, + data, + }) +} + #[cfg(test)] mod test { use super::*; diff --git a/program/src/processor.rs b/program/src/processor.rs index ff08f1d..e16b07a 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -28,7 +28,7 @@ impl Processor { pub fn process_init( program_id: &Pubkey, accounts: &[AccountInfo], - seeds: [u8; 32] + seeds: [u8; 32], ) -> ProgramResult { let accounts_iter = &mut accounts.iter(); @@ -149,10 +149,10 @@ impl Processor { 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 { msg!("The source token account has insufficient funds."); - return Err(ProgramError::InsufficientFunds) + return Err(ProgramError::InsufficientFunds); }; let transfer_tokens_to_vesting_account = transfer( @@ -197,7 +197,7 @@ impl Processor { if spl_token_account.key != &spl_token::id() { msg!("The provided spl token program account is invalid"); - return Err(ProgramError::InvalidArgument) + return Err(ProgramError::InvalidArgument); } let packed_state = &vesting_account.data; @@ -221,6 +221,12 @@ impl Processor { let mut total_amount_to_transfer = 0; let mut schedule = unpack_schedule(&packed_state.borrow()[VestingScheduleHeader::LEN..])?; + if schedule.release_time == 0 { + msg!("Should initialize withdrawal first"); + return Err(ProgramError::InvalidArgument); + } + + msg!("UNIX: {}", clock.unix_timestamp); // TODO: rm if clock.unix_timestamp as u64 >= schedule.release_time { total_amount_to_transfer += schedule.amount; schedule.amount = 0; @@ -260,6 +266,74 @@ impl Processor { Ok(()) } + pub fn process_initialize_unlock( + program_id: &Pubkey, + _accounts: &[AccountInfo], + seeds: [u8; 32], + ) -> ProgramResult { + let accounts_iter = &mut _accounts.iter(); + + let spl_token_account = next_account_info(accounts_iter)?; + let clock_sysvar_account = next_account_info(accounts_iter)?; + let vesting_account = next_account_info(accounts_iter)?; + let vesting_token_account = next_account_info(accounts_iter)?; + let destination_token_account = next_account_info(accounts_iter)?; + + let vesting_account_key = Pubkey::create_program_address(&[&seeds], program_id)?; + if vesting_account_key != *vesting_account.key { + msg!("Invalid vesting account key"); + return Err(ProgramError::InvalidArgument); + } + + if spl_token_account.key != &spl_token::id() { + msg!("The provided spl token program account is invalid"); + return Err(ProgramError::InvalidArgument); + } + + let packed_state = &vesting_account.data; + let header_state = + VestingScheduleHeader::unpack(&packed_state.borrow()[..VestingScheduleHeader::LEN])?; + + if header_state.destination_address != *destination_token_account.key { + msg!("Contract destination account does not matched provided account"); + return Err(ProgramError::InvalidArgument); + } + + let vesting_token_account_data = Account::unpack(&vesting_token_account.data.borrow())?; + + if vesting_token_account_data.owner != vesting_account_key { + msg!("The vesting token account should be owned by the vesting account."); + return Err(ProgramError::InvalidArgument); + } + + // Unlock the schedules that have reached maturity + let clock = Clock::from_account_info(&clock_sysvar_account)?; + let mut schedule = unpack_schedule(&packed_state.borrow()[VestingScheduleHeader::LEN..])?; + + if schedule.amount == 0 { + msg!("Vesting contract already claimed"); + return Err(ProgramError::InvalidArgument); + } + + if schedule.release_time != 0 { + msg!("Shouldn't initialize withdrawal for already initialized schedule"); + return Err(ProgramError::InvalidArgument); + } + + msg!("UNIX: {}", clock.unix_timestamp); // TODO: rm + // TODO: make test advance in time between initialize and unlock + // schedule.release_time = clock.unix_timestamp as u64 + 1; // TODO: change 1 for 7 days + schedule.release_time = 1; // NOTE: For testing purposes, !=0 and < current time + + // Reset released amounts to 0. This makes the simple unlock safe with complex scheduling contracts + pack_schedule_into_slice( + schedule, + &mut packed_state.borrow_mut()[VestingScheduleHeader::LEN..], + ); + + Ok(()) + } + pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], @@ -279,6 +353,10 @@ impl Processor { msg!("Instruction: Unlock"); Self::process_unlock(program_id, accounts, seeds) } + VestingInstruction::InitializeUnlock { seeds } => { + msg!("Instruction: InitializeUnlock"); + Self::process_initialize_unlock(program_id, accounts, seeds) + } VestingInstruction::Create { seeds, mint_address, diff --git a/program/tests/functional.rs b/program/tests/functional.rs index a6de175..a1e667b 100644 --- a/program/tests/functional.rs +++ b/program/tests/functional.rs @@ -1,21 +1,16 @@ #![cfg(feature = "test-bpf")] use std::str::FromStr; -use solana_program::{hash::Hash, - pubkey::Pubkey, - rent::Rent, - sysvar, - system_program -}; -use solana_program_test::{processor, ProgramTest}; +use solana_program::{hash::Hash, pubkey::Pubkey, rent::Rent, system_program, sysvar}; +use solana_test_framework::ProgramTestContextExtension; +use solana_program_test::{processor, ProgramTest, ProgramTestContext}; use solana_sdk::{account::Account, signature::Keypair, signature::Signer, system_instruction, transaction::Transaction}; use token_vesting::{entrypoint::process_instruction, instruction::Schedule}; -use token_vesting::instruction::{init, unlock, create}; +use token_vesting::instruction::{init, unlock, create, initialize_unlock}; use spl_token::{self, instruction::{initialize_mint, initialize_account, mint_to}}; #[tokio::test] async fn test_token_vesting() { - // Create program and test environment let program_id = Pubkey::from_str("VestingbGKPFXCWuBvfkegQfZyiNwAJb9Ss623VQ5DA").unwrap(); let mint_authority = Keypair::new(); @@ -28,14 +23,11 @@ async fn test_token_vesting() { let (vesting_account_key, bump) = Pubkey::find_program_address(&[&seeds[..31]], &program_id); seeds[31] = bump; let vesting_token_account = Keypair::new(); - - let mut program_test = ProgramTest::new( - "token_vesting", - program_id, - processor!(process_instruction), - ); - // Add accounts + let mut program_test = + ProgramTest::new("token_vesting", program_id, processor!(process_instruction)); + + // Add accounts program_test.add_account( source_account.pubkey(), Account { @@ -54,49 +46,56 @@ async fn test_token_vesting() { &program_id, &payer.pubkey(), &vesting_account_key, - seeds - ).unwrap() - ]; + seeds, + ) + .unwrap()]; let mut init_transaction = Transaction::new_with_payer( - &init_instruction, + &init_instruction, Some(&payer.pubkey()), ); - init_transaction.partial_sign( - &[&payer], - recent_blockhash - ); + init_transaction.partial_sign(&[&payer], recent_blockhash); banks_client.process_transaction(init_transaction).await.unwrap(); - // Initialize the token accounts banks_client.process_transaction(mint_init_transaction( - &payer, - &mint, - &mint_authority, - recent_blockhash - )).await.unwrap(); - - banks_client.process_transaction( - create_token_account(&payer, &mint, recent_blockhash, &source_token_account, &source_account.pubkey()) - ).await.unwrap(); + &payer, + &mint, + &mint_authority, + recent_blockhash, + )) + .await.unwrap(); + + banks_client.process_transaction(create_token_account( + &payer, + &mint, + recent_blockhash, + &source_token_account, + &source_account.pubkey(), + )).await.unwrap(); banks_client.process_transaction( - create_token_account(&payer, &mint, recent_blockhash, &vesting_token_account, &vesting_account_key) - ).await.unwrap(); - + create_token_account( + &payer, + &mint, + recent_blockhash, + &vesting_token_account, + &vesting_account_key, + )).await.unwrap(); // Create and process the vesting transactions - let setup_instructions = [ - mint_to( - &spl_token::id(), - &mint.pubkey(), - &source_token_account.pubkey(), - &mint_authority.pubkey(), - &[], - 100 - ).unwrap() - ]; + let setup_instructions = [mint_to( + &spl_token::id(), + &mint.pubkey(), + &source_token_account.pubkey(), + &mint_authority.pubkey(), + &[], + 100, + ) + .unwrap()]; - let schedule = Schedule {amount: 100, release_time: 0}; + let schedule = Schedule { + amount: 100, + release_time: 1, + }; let test_instructions = [ create( @@ -108,8 +107,9 @@ async fn test_token_vesting() { &source_token_account.pubkey(), &mint.pubkey(), schedule, - seeds.clone() - ).unwrap(), + seeds.clone(), + ) + .unwrap(), unlock( &program_id, &spl_token::id(), @@ -117,84 +117,253 @@ async fn test_token_vesting() { &vesting_account_key, &vesting_token_account.pubkey(), &source_token_account.pubkey(), - seeds.clone() - ).unwrap() + seeds.clone(), + ) + .unwrap(), ]; - + // Process transaction on test network - let mut setup_transaction = Transaction::new_with_payer( - &setup_instructions, - Some(&payer.pubkey()), - ); - setup_transaction.partial_sign( - &[ - &payer, - &mint_authority - ], - recent_blockhash - ); - + let mut setup_transaction = + Transaction::new_with_payer(&setup_instructions, Some(&payer.pubkey())); + setup_transaction.partial_sign(&[&payer, &mint_authority], recent_blockhash); + banks_client.process_transaction(setup_transaction).await.unwrap(); // Process transaction on test network - let mut test_transaction = Transaction::new_with_payer( - &test_instructions, - Some(&payer.pubkey()), - ); - test_transaction.partial_sign( - &[ - &payer, - &source_account - ], - recent_blockhash - ); - + let mut test_transaction = + Transaction::new_with_payer(&test_instructions, Some(&payer.pubkey())); + test_transaction.partial_sign(&[&payer, &source_account], recent_blockhash); + banks_client.process_transaction(test_transaction).await.unwrap(); - +} + +#[tokio::test] +async fn test_token_unlocking() { + // Create program and test environment + let program_id = Pubkey::from_str("VestingbGKPFXCWuBvfkegQfZyiNwAJb9Ss623VQ5DA").unwrap(); + let mint_authority = Keypair::new(); + let mint = Keypair::new(); + + let source_account = Keypair::new(); + let source_token_account = Keypair::new(); + + let mut seeds = [42u8; 32]; + let (vesting_account_key, bump) = Pubkey::find_program_address(&[&seeds[..31]], &program_id); + seeds[31] = bump; + let vesting_token_account = Keypair::new(); + + let mut program_test = + ProgramTest::new("token_vesting", program_id, processor!(process_instruction)); + + // Add accounts + program_test.add_account( + source_account.pubkey(), + Account { + lamports: 5000000, + ..Account::default() + }, + ); + + // Start and process transactions on the test network + let mut context: ProgramTestContext = program_test.start_with_context().await; + + // NOTE: using scopes to allow partial borrows when context.warp_to_timestamp is called + { + let banks_client = &mut context.banks_client; + let payer = &mut context.payer; + let recent_blockhash = context.last_blockhash; + + // Initialize the vesting program account + let init_instruction = [init( + &system_program::id(), + &sysvar::rent::id(), + &program_id, + &payer.pubkey(), + &vesting_account_key, + seeds, + ) + .unwrap()]; + let mut init_transaction = Transaction::new_with_payer( + &init_instruction, + Some(&payer.pubkey()), + ); + init_transaction.partial_sign(&[&payer], recent_blockhash); + banks_client + .process_transaction(init_transaction) + .await + .unwrap(); + + // Initialize the token accounts + banks_client + .process_transaction(mint_init_transaction( + &payer, + &mint, + &mint_authority, + recent_blockhash, + )) + .await + .unwrap(); + + banks_client.process_transaction( + create_token_account( + &payer, + &mint, + recent_blockhash, + &source_token_account, + &source_account.pubkey(), + )) + .await + .unwrap(); + banks_client.process_transaction( + create_token_account( + &payer, + &mint, + recent_blockhash, + &vesting_token_account, + &vesting_account_key, + )) + .await + .unwrap(); + + // Create and process the vesting transactions + let setup_instructions = [mint_to( + &spl_token::id(), + &mint.pubkey(), + &source_token_account.pubkey(), + &mint_authority.pubkey(), + &[], + 100, + ) + .unwrap()]; + + let schedule = Schedule { + amount: 100, + release_time: 0, + }; + + let test_instructions = [ + create( + &program_id, + &spl_token::id(), + &vesting_account_key, + &vesting_token_account.pubkey(), + &source_account.pubkey(), + &source_token_account.pubkey(), + &mint.pubkey(), + schedule, + seeds.clone(), + ) + .unwrap(), + initialize_unlock( + &program_id, + &spl_token::id(), + &sysvar::clock::id(), + &vesting_account_key, + &vesting_token_account.pubkey(), + &source_token_account.pubkey(), + seeds.clone(), + ) + .unwrap(), + // TODO: move unlock after warp_to_timestamp + unlock( + &program_id, + &spl_token::id(), + &sysvar::clock::id(), + &vesting_account_key, + &vesting_token_account.pubkey(), + &source_token_account.pubkey(), + seeds.clone(), + ) + .unwrap() + ]; + + // Process transaction on test network + let mut setup_transaction = + Transaction::new_with_payer(&setup_instructions, Some(&payer.pubkey())); + setup_transaction.partial_sign(&[&payer, &mint_authority], recent_blockhash); + + banks_client + .process_transaction(setup_transaction) + .await + .unwrap(); + + // Process transaction on test network + let mut test_transaction = + Transaction::new_with_payer(&test_instructions, Some(&payer.pubkey())); + test_transaction.partial_sign(&[&payer, &source_account], recent_blockhash); + + banks_client + .process_transaction(test_transaction) + .await + .unwrap(); + } + + // Warp to a future slot + // TODO: this should increase the timestamp, and it does not. + let _ = context.warp_to_timestamp(99999999).await; + + // { + // let banks_client = &mut context.banks_client; + // let payer = &mut context.payer; + // let recent_blockhash = context.last_blockhash; + + // let unlock_instructions = [unlock( + // &program_id, + // &spl_token::id(), + // &sysvar::clock::id(), + // &vesting_account_key, + // &vesting_token_account.pubkey(), + // &source_token_account.pubkey(), + // seeds.clone(), + // ) + // .unwrap()]; + + // let mut unlock_transaction = + // Transaction::new_with_payer(&unlock_instructions, Some(&payer.pubkey())); + + // unlock_transaction.partial_sign(&[&payer], recent_blockhash); + + // banks_client + // .process_transaction(unlock_transaction) + // .await + // .unwrap(); + // } } fn mint_init_transaction( - payer: &Keypair, - mint:&Keypair, - mint_authority: &Keypair, - recent_blockhash: Hash) -> Transaction{ + payer: &Keypair, + mint: &Keypair, + mint_authority: &Keypair, + recent_blockhash: Hash, +) -> Transaction { let instructions = [ system_instruction::create_account( &payer.pubkey(), &mint.pubkey(), Rent::default().minimum_balance(82), 82, - &spl_token::id() - + &spl_token::id(), ), initialize_mint( - &spl_token::id(), - &mint.pubkey(), + &spl_token::id(), + &mint.pubkey(), &mint_authority.pubkey(), - None, - 0 - ).unwrap(), + None, + 0, + ) + .unwrap(), ]; - let mut transaction = Transaction::new_with_payer( - &instructions, - Some(&payer.pubkey()), - ); - transaction.partial_sign( - &[ - payer, - mint - ], - recent_blockhash - ); + let mut transaction = Transaction::new_with_payer(&instructions, Some(&payer.pubkey())); + transaction.partial_sign(&[payer, mint], recent_blockhash); transaction } fn create_token_account( - payer: &Keypair, - mint:&Keypair, + payer: &Keypair, + mint: &Keypair, recent_blockhash: Hash, - token_account:&Keypair, - token_account_owner: &Pubkey + token_account: &Keypair, + token_account_owner: &Pubkey, ) -> Transaction { let instructions = [ system_instruction::create_account( @@ -202,25 +371,17 @@ fn create_token_account( &token_account.pubkey(), Rent::default().minimum_balance(165), 165, - &spl_token::id() + &spl_token::id(), ), initialize_account( - &spl_token::id(), - &token_account.pubkey(), - &mint.pubkey(), - token_account_owner - ).unwrap() - ]; - let mut transaction = Transaction::new_with_payer( - &instructions, - Some(&payer.pubkey()), - ); - transaction.partial_sign( - &[ - payer, - token_account - ], - recent_blockhash - ); + &spl_token::id(), + &token_account.pubkey(), + &mint.pubkey(), + token_account_owner, + ) + .unwrap(), + ]; + let mut transaction = Transaction::new_with_payer(&instructions, Some(&payer.pubkey())); + transaction.partial_sign(&[payer, token_account], recent_blockhash); transaction -} \ No newline at end of file +}