From c12aba8a0c6e57151c9f62b80a6fb30708467e92 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Wed, 9 Mar 2022 19:57:29 -0600 Subject: [PATCH 01/10] Temp updates on instruction --- .gitignore | 4 +- program/src/error.rs | 40 +++++++- program/src/instruction.rs | 47 ++++----- program/src/processor.rs | 11 ++- program/src/processor/withdraw.rs | 157 ++++++++++++++++++++++++++++++ 5 files changed, 228 insertions(+), 31 deletions(-) create mode 100644 program/src/processor/withdraw.rs diff --git a/.gitignore b/.gitignore index 2444b49..cbe5c9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ program/target -scripts/node_modules \ No newline at end of file +scripts/node_modules + +.DS_Store diff --git a/program/src/error.rs b/program/src/error.rs index a862893..e541c5c 100644 --- a/program/src/error.rs +++ b/program/src/error.rs @@ -8,8 +8,46 @@ pub enum SubscriptionError { SampleError, } +#[derive(Error, Debug, Copy, Clone, FromPrimitive, PartialEq)] +pub enum AccountError { + #[error("Invalid vault owner.")] + InvalidVaultOwner, + #[error("Account balance insufficient for requested withdraw amount.")] + InsufficientWithdrawBalance, +} + +#[derive(Error, Debug, Copy, Clone, FromPrimitive, PartialEq)] +pub enum EchoError { + #[error("Account must be writable.")] + AccountMustBeWritable, + #[error("Account not initialized.")] + AccountNotInitialized, + #[error("Missing required signature.")] + MissingRequiredSignature, + #[error("Invalid program address.")] + InvalidProgramAddress, + #[error("Invalid account address.")] + InvalidAccountAddress, + #[error("Default error.")] + DefaultError, + #[error("Instruction not implemented.")] + NotImplemented, +} + impl From for ProgramError { fn from(e: SubscriptionError) -> Self { ProgramError::Custom(e as u32) } -} \ No newline at end of file +} + +impl From for ProgramError { + fn from(e: AccountError) -> Self { + ProgramError::Custom(e as u32) + } +} + +impl From for ProgramError { + fn from(e: EchoError) -> Self { + ProgramError::Custom(e as u32) + } +} diff --git a/program/src/instruction.rs b/program/src/instruction.rs index b18b19c..1bb4991 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -1,27 +1,24 @@ use borsh::{BorshDeserialize, BorshSerialize}; -use solana_program::{ - pubkey::Pubkey, -}; +use solana_program::pubkey::Pubkey; #[derive(BorshSerialize, BorshDeserialize, Debug, Clone)] pub enum SubscriptionInstruction { - /// Initializes a new subscription. /// /// Creates new subscription metadata account, and a new associated /// token account as a vault for payments. /// /// Accounts expected by this instruction: - /// + /// /// 0. `[writable, signer]` user - /// 1. `[writable]` (PDA) subscription metadata + /// 1. `[writable]` (PDA) subscription metadata /// 2. `[writable]` (PDA) deposit vault /// 3. `[]` (PDA) deposit vault mint /// 4. `[]` system program /// 5. `[]` sysvar rent /// 6. `[]` token program /// 7. `[]` associated token program - /// + /// Initialize { payee: Pubkey, amount: u64, @@ -29,43 +26,39 @@ pub enum SubscriptionInstruction { }, /// Wrapper on transfer function. Deposits token into deposit vault. - /// + /// /// Accounts expected by this instruction: - /// + /// /// 0. `[writable, signer]` payer /// 1. `[writable]` payer token account /// 2. `[writable]` deposit vault /// 3. `[]` token program for token transfers - /// - Deposit { - amount: u64 - }, + /// + Deposit { amount: u64 }, /// Wrapper on transfer function. Withdraws token from deposit vault /// as long as caller is rightful owner. - /// + /// /// Accounts expected by this instruction: - /// + /// /// 0. `[writable, signer]` payer /// 1. `[writable]` payer token account /// 2. `[writable]` deposit vault /// 3. `[]` subscription metadata /// 4. `[]` token program for token transfers - /// - Withdraw { - amount: u64 - }, + /// + Withdraw { amount: u64 }, /// Renews or deactivates a provided subscription. - /// + /// /// Checks if the time is up for a renewal, and if not, reverts. Creates a new token mint /// and updates subscription metadata. If vault balance is high enough, it will transfer - /// funds to payee specified by metadata, as well as a small fee to the caller of this + /// funds to payee specified by metadata, as well as a small fee to the caller of this /// function, and mint a new token to the payer for maintaining an active subscription. /// If the vault balance is not high enough, it will not transfer funds or mint a token. - /// + /// /// Accounts expected by this instruction: - /// + /// /// 0. `[writable, signer]` caller /// 1. `[writable]` (PDA) subscription metadata /// 2. `[writable]` (PDA) deposit vault @@ -77,17 +70,17 @@ pub enum SubscriptionInstruction { /// 8. `[]` sysvar rent program /// 9. `[]` token program /// 10. `[]` associated token program - /// + /// Renew {}, /// Withdraws rent from subscription metadata. Can only be called by account that /// initialized the subscription. - /// + /// /// Accounts expected by this instruction: - /// + /// /// 0. `[writable, signer]` user /// 1. `[writable]` subscription metadata /// 2. `[]` system program - /// + /// Close {}, } diff --git a/program/src/processor.rs b/program/src/processor.rs index 68f60e7..00be13f 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -8,6 +8,8 @@ use spl_token::*; use crate::instruction::SubscriptionInstruction; +pub mod withdraw; + pub struct Processor {} impl Processor { @@ -20,7 +22,11 @@ impl Processor { .map_err(|_| ProgramError::InvalidInstructionData)?; match instruction { - SubscriptionInstruction::Initialize { payee, amount, duration } => { + SubscriptionInstruction::Initialize { + payee, + amount, + duration, + } => { msg!("Instruction: Initialize"); msg!("payee: {}", payee); msg!("amount: {}", amount); @@ -37,13 +43,14 @@ impl Processor { SubscriptionInstruction::Withdraw { amount } => { msg!("Instruction: Withdraw"); msg!("amount: {}", amount); + withdraw::process(program_id, accounts, amount)?; } SubscriptionInstruction::Renew {} => { msg!("Instruction: Renew "); // create new mint // update metadata // check enough balance - // transfer balance + mint new token + // transfer balance + mint new token } SubscriptionInstruction::Close {} => { msg!("Instruction: Close"); diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs new file mode 100644 index 0000000..755291b --- /dev/null +++ b/program/src/processor/withdraw.rs @@ -0,0 +1,157 @@ +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + msg, + program_error::ProgramError, + program_pack::Pack, + pubkey::Pubkey, + program::invoke, + system_instruction, +}; + +use spl_token::{ + instruction, + state::{Account as TokenAccount}, +} + +use borsh::BorshDeserialize; + +use crate::error::{AccountError, EchoError}; + +struct Context<'a, 'b: 'a> { + payer: &'a AccountInfo<'b>, + payer_token_account: &'a AccountInfo<'b>, + deposit_vault: &'a AccountInfo<'b>, + subscription_metadata: &'a AccountInfo<'b>, + token_program: &'a AccountInfo<'b>, +} + +impl<'a, 'b: 'a> Context<'a, 'b> { + pub fn parse(accounts: &'a [AccountInfo<'b>]) -> Result { + let accounts_iter = &mut accounts.iter(); + + let mut ctx = Self { + payer: next_account_info(accounts_iter)?, + payer_token_account: next_account_info(accounts_iter)?, // SPL toke naccount + deposit_vault: next_account_info(accounts_iter)?, + subscription_metadata: next_account_info(accounts_iter)?, + token_program: next_account_info(accounts_iter)?, + }; + + if !ctx.payer.is_writable { + msg!("Payer must be writable"); + return Err(EchoError::AccountMustBeWritable.into()); + } + + if !ctx.payer.is_signer { + msg!("Payer must be signer"); + return Err(EchoError::AccountMustBeWritable.into()); + } + + if !ctx.payer_token_account.is_writable { + msg!("Payer token account must be writable"); + return Err(EchoError::AccountMustBeWritable.into()); + } + + if !ctx.deposit_vault.is_writable { + msg!("Deposit vault must be writable"); + return Err(EchoError::AccountMustBeWritable.into()); + } + + Ok(ctx) + } +} + +pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> ProgramResult { + let ctx = Context::parse(accounts)?; + + ctx.payer_token_account_spl = TokenAccount(payer_token_account) + + // Transfer to self... ??? + if ctx.payer.key == ctx.deposit_vault.key { + return Ok(()); + } + + // Check that payer is owner of vault + assert( + &ctx.deposit_vault.owner != *ctx.payer.key, + AccountError::InvalidVaultOwner.into(), + "Invalid", + ) + if &ctx.deposit_vault.owner != *ctx.payer.key { + return Err(AccountError::InvalidVaultOwner.into()); + } + + // TODO: include gas + if ctx.deposit_vault.lamports() < amount { + return Err(AccountError::InsufficientWithdrawBalance.into()); + } + + // Transfer + let instruction = system_instruction::transfer(&ctx.deposit_vault.key, &ctx.payer_token_account.key, amount); + + invoke( + &instruction, + &[ + ctx.deposit_vault.clone(), + ctx.payer_token_account.clone(), + ], + )?; + + msg!( + "[Buoyant] Withdraw completed. Owner balance: {}", + ctx.payer_token_account.lamports() + ); + + + + + + + /// Wrapper on transfer function. Withdraws token from deposit vault + /// as long as caller is rightful owner. + /// + /// Accounts expected by this instruction: + + let buffer = &mut (*ctx.authorized_buffer.data).borrow_mut(); + + // check the size of the account before trying to read it + if buffer.len() < AUTH_BUFF_HEADER_SIZE { + msg!("Invalid authorized buffer size, {}", buffer.len()); + return Err(EchoError::AccountNotInitialized.into()); + } + + // in order to validate the PDA address, we first read it to access the buffer seed + let buffer_header = AuthorizedBufferHeader::try_from_slice(&buffer[..AUTH_BUFF_HEADER_SIZE])?; + + // verify that the PDA account is the correct address + let pda = Pubkey::create_program_address( + &[ + b"authority", + ctx.authority.key.as_ref(), + &buffer_header.buffer_seed.to_le_bytes(), + &[buffer_header.bump_seed], + ], + program_id, + )?; + + if pda != *ctx.authorized_buffer.key { + msg!("Invalid account address or authority"); + return Err(EchoError::InvalidAccountAddress.into()); + } + + // this is the 'rest' of the account's data (beyond the header info) + let buffer_data = &mut buffer[AUTH_BUFF_HEADER_SIZE..]; + + // loop over each byte in the rest of account's data + for index in 0..buffer_data.len() { + buffer_data[index] = match index < data.len() { + true => data[index], + false => 0, + }; + } + + Ok(()) +} + +// test cases: From 96fd7f4fa79e53fdf159f09cca9b4731e27f663f Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Wed, 9 Mar 2022 21:08:07 -0600 Subject: [PATCH 02/10] Context mgmt --- program/src/error.rs | 4 -- program/src/processor/withdraw.rs | 107 ++++++++++-------------------- program/src/utils.rs | 10 +++ 3 files changed, 44 insertions(+), 77 deletions(-) create mode 100644 program/src/utils.rs diff --git a/program/src/error.rs b/program/src/error.rs index e541c5c..5172695 100644 --- a/program/src/error.rs +++ b/program/src/error.rs @@ -6,10 +6,6 @@ use thiserror::Error; pub enum SubscriptionError { #[error("Error is sample.")] SampleError, -} - -#[derive(Error, Debug, Copy, Clone, FromPrimitive, PartialEq)] -pub enum AccountError { #[error("Invalid vault owner.")] InvalidVaultOwner, #[error("Account balance insufficient for requested withdraw amount.")] diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs index 755291b..4c2c700 100644 --- a/program/src/processor/withdraw.rs +++ b/program/src/processor/withdraw.rs @@ -12,15 +12,16 @@ use solana_program::{ use spl_token::{ instruction, state::{Account as TokenAccount}, -} +}; use borsh::BorshDeserialize; -use crate::error::{AccountError, EchoError}; +use crate::{error::{SubscriptionError, EchoError}, utils::assert_msg}; struct Context<'a, 'b: 'a> { payer: &'a AccountInfo<'b>, - payer_token_account: &'a AccountInfo<'b>, + payer_token_account: TokenAccount, + payer_token_account_ai: &'a AccountInfo<'b>, deposit_vault: &'a AccountInfo<'b>, subscription_metadata: &'a AccountInfo<'b>, token_program: &'a AccountInfo<'b>, @@ -30,61 +31,69 @@ impl<'a, 'b: 'a> Context<'a, 'b> { pub fn parse(accounts: &'a [AccountInfo<'b>]) -> Result { let accounts_iter = &mut accounts.iter(); - let mut ctx = Self { - payer: next_account_info(accounts_iter)?, - payer_token_account: next_account_info(accounts_iter)?, // SPL toke naccount - deposit_vault: next_account_info(accounts_iter)?, - subscription_metadata: next_account_info(accounts_iter)?, - token_program: next_account_info(accounts_iter)?, - }; + let payer = next_account_info(accounts_iter)?; + let payer_token_account_ai = next_account_info(accounts_iter)?; // SPL token account + let deposit_vault = next_account_info(accounts_iter)?; + let subscription_metadata = next_account_info(accounts_iter)?; + let token_program = next_account_info(accounts_iter)?; - if !ctx.payer.is_writable { + if !payer.is_writable { msg!("Payer must be writable"); return Err(EchoError::AccountMustBeWritable.into()); } - if !ctx.payer.is_signer { + if !payer.is_signer { msg!("Payer must be signer"); return Err(EchoError::AccountMustBeWritable.into()); } - if !ctx.payer_token_account.is_writable { + if !payer_token_account_ai.is_writable { msg!("Payer token account must be writable"); return Err(EchoError::AccountMustBeWritable.into()); } - if !ctx.deposit_vault.is_writable { + if !deposit_vault.is_writable { msg!("Deposit vault must be writable"); return Err(EchoError::AccountMustBeWritable.into()); } - Ok(ctx) + // Deserialize token account + let payer_token_account = TokenAccount::unpack_from_slice(&payer_token_account_ai.try_borrow_data()?)?; + + Ok(Self { + payer, + payer_token_account, + payer_token_account_ai, + deposit_vault, + subscription_metadata, + token_program, + }) } } pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> ProgramResult { let ctx = Context::parse(accounts)?; - ctx.payer_token_account_spl = TokenAccount(payer_token_account) + // Validations + assert_msg( + ctx.payer_token_account.owner != *ctx.payer.key, + SubscriptionError::InvalidVaultOwner.into(), + "Invalid vault owner", + )?; + // Transfer to self... ??? if ctx.payer.key == ctx.deposit_vault.key { return Ok(()); } - // Check that payer is owner of vault - assert( - &ctx.deposit_vault.owner != *ctx.payer.key, - AccountError::InvalidVaultOwner.into(), - "Invalid", - ) - if &ctx.deposit_vault.owner != *ctx.payer.key { - return Err(AccountError::InvalidVaultOwner.into()); + if *ctx.deposit_vault.owner != *ctx.payer.key { + return Err(SubscriptionError::InvalidVaultOwner.into()); } // TODO: include gas if ctx.deposit_vault.lamports() < amount { - return Err(AccountError::InsufficientWithdrawBalance.into()); + return Err(SubscriptionError::InsufficientWithdrawBalance.into()); } // Transfer @@ -103,54 +112,6 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> Pr ctx.payer_token_account.lamports() ); - - - - - - /// Wrapper on transfer function. Withdraws token from deposit vault - /// as long as caller is rightful owner. - /// - /// Accounts expected by this instruction: - - let buffer = &mut (*ctx.authorized_buffer.data).borrow_mut(); - - // check the size of the account before trying to read it - if buffer.len() < AUTH_BUFF_HEADER_SIZE { - msg!("Invalid authorized buffer size, {}", buffer.len()); - return Err(EchoError::AccountNotInitialized.into()); - } - - // in order to validate the PDA address, we first read it to access the buffer seed - let buffer_header = AuthorizedBufferHeader::try_from_slice(&buffer[..AUTH_BUFF_HEADER_SIZE])?; - - // verify that the PDA account is the correct address - let pda = Pubkey::create_program_address( - &[ - b"authority", - ctx.authority.key.as_ref(), - &buffer_header.buffer_seed.to_le_bytes(), - &[buffer_header.bump_seed], - ], - program_id, - )?; - - if pda != *ctx.authorized_buffer.key { - msg!("Invalid account address or authority"); - return Err(EchoError::InvalidAccountAddress.into()); - } - - // this is the 'rest' of the account's data (beyond the header info) - let buffer_data = &mut buffer[AUTH_BUFF_HEADER_SIZE..]; - - // loop over each byte in the rest of account's data - for index in 0..buffer_data.len() { - buffer_data[index] = match index < data.len() { - true => data[index], - false => 0, - }; - } - Ok(()) } diff --git a/program/src/utils.rs b/program/src/utils.rs new file mode 100644 index 0000000..d4b77a1 --- /dev/null +++ b/program/src/utils.rs @@ -0,0 +1,10 @@ +use solana_program::{entrypoint::ProgramResult, msg, program_error::ProgramError, pubkey::Pubkey}; + +pub fn assert_msg(statement: bool, err: ProgramError, msg: &str) -> ProgramResult { + if !statement { + msg!(msg); + Err(err) + } else { + Ok(()) + } +} \ No newline at end of file From 4381ea5268a214f4eb7c04aabe6017b0c4ebc68b Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 13 Mar 2022 18:27:24 -0500 Subject: [PATCH 03/10] Use more assert_msg --- program/src/utils.rs | 55 ++++++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/program/src/utils.rs b/program/src/utils.rs index dda951a..e7fd68e 100644 --- a/program/src/utils.rs +++ b/program/src/utils.rs @@ -18,31 +18,28 @@ pub fn assert_msg(statement: bool, err: ProgramError, msg: &str) -> ProgramResul } pub fn check_signer(account: &AccountInfo) -> ProgramResult { - if !account.is_signer { - msg!("Missing required signature on account: {}", account.key); - Err(ProgramError::MissingRequiredSignature) - } else { - Ok(()) - } + assert_msg( + account.is_signer, + ProgramError::MissingRequiredSignature, + format!("Missing required signature on account: {}", account.key), + ) } pub fn check_writable(account: &AccountInfo) -> ProgramResult { - if !account.is_writable { - msg!("Account should be writable: {}", account.key); - Err(ProgramError::MissingRequiredSignature) - } else { - Ok(()) - } + assert_msg( + account.is_writable, + ProgramError::MissingRequiredSignature, + format!("Account should be writable: {}", account.key), + ) } pub fn check_pda(account: &AccountInfo, seeds: &[&[u8]], program_id: &Pubkey) -> ProgramResult { let (pda, _) = Pubkey::find_program_address(seeds, program_id); - if *account.key != pda { - msg!("Invalid PDA:\tExpected: {}\tGot: {}", &pda, account.key); - Err(UtilsError::InvalidProgramAddress.into()) - } else { - Ok(()) - } + assert_msg( + *account.key == pda, + UtilsError::InvalidProgramAddress.into(), + format!("Invalid PDA:\tExpected: {}\tGot: {}", &pda, account.key), + ) } pub fn check_ata( @@ -52,12 +49,11 @@ pub fn check_ata( ) -> ProgramResult { // check pda let ata = get_associated_token_address(user_address, mint_address); - if *account.key != ata { - msg!("Invalid ATA address:\tExpected: {}\tGot: {}", &ata, account.key); - Err(UtilsError::InvalidProgramAddress.into()) - } else { - Ok(()) - } + assert_msg( + *account.key == ata, + UtilsError::InvalidProgramAddress.into(), + format!("Invalid ATA address:\tExpected: {}\tGot: {}", &ata, account.key) + ) } pub fn check_initialized_ata( @@ -85,12 +81,11 @@ pub fn check_initialized_ata( } pub fn check_program_id(account: &AccountInfo, program_id: &Pubkey) -> ProgramResult { - if *account.key != *program_id { - msg!("Invalid program id:\tExpected: {}\tGot: {}", program_id, account.key); - Err(ProgramError::IncorrectProgramId) - } else { - Ok(()) - } + assert_msg( + *account.key == *program_id, + ProgramError::IncorrectProgramId, + format!("Invalid program id:\tExpected: {}\tGot: {}", program_id, account.key) + ) } #[derive(Error, Debug, Copy, Clone, FromPrimitive, PartialEq)] From 60863cf9c14199ab71706b5ae2c1edec2b1baa7e Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 13 Mar 2022 18:54:16 -0500 Subject: [PATCH 04/10] Draft of withdrawal --- program/src/processor/withdraw.rs | 146 ++++++++++++++---------------- 1 file changed, 67 insertions(+), 79 deletions(-) diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs index 4c2c700..4e53def 100644 --- a/program/src/processor/withdraw.rs +++ b/program/src/processor/withdraw.rs @@ -1,72 +1,61 @@ -use solana_program::{ - account_info::{next_account_info, AccountInfo}, - entrypoint::ProgramResult, - msg, - program_error::ProgramError, - program_pack::Pack, - pubkey::Pubkey, - program::invoke, - system_instruction, +use { + crate::{ + error::SubscriptionError, + instruction::SubscriptionInstruction, + state::{Counter, Subscription}, + utils::{ + assert_msg, check_ata, check_initialized_ata, check_pda, check_program_id, check_signer, + check_writable, + }, + }, + borsh::{BorshDeserialize, BorshSerialize}, + solana_program::{ + account_info::{next_account_info, AccountInfo}, + clock::Clock, + entrypoint::ProgramResult, + msg, + program::{invoke, invoke_signed}, + program_error::ProgramError, + program_pack::Pack, + pubkey::Pubkey, + system_instruction, system_program, + sysvar::{rent, Sysvar}, + }, + spl_token::{error::TokenError, state::Account as TokenAccount, state::Mint}, }; -use spl_token::{ - instruction, - state::{Account as TokenAccount}, -}; - -use borsh::BorshDeserialize; - -use crate::{error::{SubscriptionError, EchoError}, utils::assert_msg}; - struct Context<'a, 'b: 'a> { - payer: &'a AccountInfo<'b>, + payer_ai: &'a AccountInfo<'b>, payer_token_account: TokenAccount, payer_token_account_ai: &'a AccountInfo<'b>, - deposit_vault: &'a AccountInfo<'b>, - subscription_metadata: &'a AccountInfo<'b>, - token_program: &'a AccountInfo<'b>, + deposit_vault_ai: &'a AccountInfo<'b>, + subscription_metadata_ai: &'a AccountInfo<'b>, + token_program_ai: &'a AccountInfo<'b>, } impl<'a, 'b: 'a> Context<'a, 'b> { pub fn parse(accounts: &'a [AccountInfo<'b>]) -> Result { - let accounts_iter = &mut accounts.iter(); - - let payer = next_account_info(accounts_iter)?; - let payer_token_account_ai = next_account_info(accounts_iter)?; // SPL token account - let deposit_vault = next_account_info(accounts_iter)?; - let subscription_metadata = next_account_info(accounts_iter)?; - let token_program = next_account_info(accounts_iter)?; + let account_info_iter = &mut accounts.iter(); - if !payer.is_writable { - msg!("Payer must be writable"); - return Err(EchoError::AccountMustBeWritable.into()); - } + let payer_ai = next_account_info(account_info_iter)?; + let payer_token_account_ai = next_account_info(account_info_iter)?; // SPL token account + let deposit_vault_ai = next_account_info(account_info_iter)?; + let subscription_metadata_ai = next_account_info(account_info_iter)?; + let token_program_ai = next_account_info(account_info_iter)?; - if !payer.is_signer { - msg!("Payer must be signer"); - return Err(EchoError::AccountMustBeWritable.into()); - } - - if !payer_token_account_ai.is_writable { - msg!("Payer token account must be writable"); - return Err(EchoError::AccountMustBeWritable.into()); - } - - if !deposit_vault.is_writable { - msg!("Deposit vault must be writable"); - return Err(EchoError::AccountMustBeWritable.into()); - } + check_signer(payer_ai); + [payer_ai, payer_token_account_ai, deposit_vault_ai].iter().map(|x| check_writable(x)); // Deserialize token account let payer_token_account = TokenAccount::unpack_from_slice(&payer_token_account_ai.try_borrow_data()?)?; Ok(Self { - payer, + payer_ai, payer_token_account, payer_token_account_ai, - deposit_vault, - subscription_metadata, - token_program, + deposit_vault_ai, + subscription_metadata_ai, + token_program_ai, }) } } @@ -76,42 +65,41 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> Pr // Validations assert_msg( - ctx.payer_token_account.owner != *ctx.payer.key, + ctx.payer_token_account.owner == *ctx.payer_ai.key, SubscriptionError::InvalidVaultOwner.into(), "Invalid vault owner", )?; - - // Transfer to self... ??? - if ctx.payer.key == ctx.deposit_vault.key { - return Ok(()); - } - - if *ctx.deposit_vault.owner != *ctx.payer.key { - return Err(SubscriptionError::InvalidVaultOwner.into()); - } - - // TODO: include gas - if ctx.deposit_vault.lamports() < amount { - return Err(SubscriptionError::InsufficientWithdrawBalance.into()); - } - - // Transfer - let instruction = system_instruction::transfer(&ctx.deposit_vault.key, &ctx.payer_token_account.key, amount); - - invoke( - &instruction, + // NOTE: do we check for insufficient amount + // or do we just pass it to spl_token to deal separately? + + msg!("Transferring funds to owner..."); + // spl token instruction + let instruction = &spl_token::instruction::transfer( + &spl_token::id(), + ctx.deposit_vault_ai.key, + ctx.payer_token_account_ai.key, + ctx.payer_ai.key, + &[], + amount, + )?; + let withdrawal_seeds = &[ + b"withdrawal_metadata", + ctx.deposit_vault_ai.key.as_ref(), + ctx.payer_ai.key.as_ref(), + &amount.to_le_bytes(), + ]; + + invoke_signed( + instruction, &[ - ctx.deposit_vault.clone(), - ctx.payer_token_account.clone(), + ctx.deposit_vault_ai.clone(), + ctx.payer_token_account_ai.clone(), + ctx.payer_ai.clone(), ], + &[withdrawal_seeds], )?; - msg!( - "[Buoyant] Withdraw completed. Owner balance: {}", - ctx.payer_token_account.lamports() - ); - Ok(()) } From 17619cb08fd3b0515ae831f8000cebe9e0aedfe5 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Sun, 13 Mar 2022 23:57:24 -0500 Subject: [PATCH 05/10] Update withdraw process --- .gitignore | 5 +- program/src/error.rs | 2 + program/src/instruction.rs | 20 ++--- program/src/processor.rs | 5 +- program/src/processor/withdraw.rs | 123 +++++++++++++++--------------- program/src/utils.rs | 1 + 6 files changed, 80 insertions(+), 76 deletions(-) diff --git a/.gitignore b/.gitignore index a6355a1..034641c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ program/target scripts/node_modules -<<<<<<< HEAD +scripts/dist .DS_Store -======= -scripts/dist ->>>>>>> master diff --git a/program/src/error.rs b/program/src/error.rs index 0df5451..c3a69bf 100644 --- a/program/src/error.rs +++ b/program/src/error.rs @@ -14,6 +14,8 @@ pub enum SubscriptionError { AlreadyExpired, #[error("Invalid vault owner.")] InvalidVaultOwner, + #[error("Invalid subscription owner.")] + InvalidSubscriptionOwner, #[error("Account balance insufficient for requested withdraw amount.")] InsufficientWithdrawBalance, } diff --git a/program/src/instruction.rs b/program/src/instruction.rs index 336a597..b7e800a 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -12,13 +12,13 @@ pub enum SubscriptionInstruction { /// /// 0. `[writable, signer]` user /// 1. `[writable]` (PDA) metadata counter - /// 1. `[writable]` (PDA) subscription metadata - /// 2. `[writable]` (PDA) deposit vault - /// 3. `[]` (PDA) deposit vault mint - /// 4. `[]` system program - /// 5. `[]` sysvar rent - /// 6. `[]` token program - /// 7. `[]` associated token program + /// 2. `[writable]` (PDA) subscription metadata + /// 3. `[writable]` (PDA) deposit vault + /// 4. `[]` (PDA) deposit vault mint + /// 5. `[]` system program + /// 6. `[]` sysvar rent + /// 7. `[]` token program + /// 8. `[]` associated token program /// Initialize { payee: Pubkey, @@ -37,8 +37,8 @@ pub enum SubscriptionInstruction { /// Deposit { amount: u64 }, - /// Wrapper on transfer function. Withdraws token from deposit vault - /// as long as caller is rightful owner. + /// Wrapper on transfer function. Withdraws token from deposit vault as long as the + /// the caller is the owner of the subscription associated with that deposit vault. /// /// Accounts expected by this instruction: /// @@ -48,7 +48,7 @@ pub enum SubscriptionInstruction { /// 3. `[]` subscription metadata /// 4. `[]` token program for token transfers /// - Withdraw { amount: u64 }, + Withdraw { amount: u64, count: u64 }, /// Renews or deactivates a provided subscription. /// diff --git a/program/src/processor.rs b/program/src/processor.rs index 257894c..fcc4030 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -207,10 +207,11 @@ impl Processor { msg!("Instruction: Deposit"); msg!("amount: {}", amount); } - SubscriptionInstruction::Withdraw { amount } => { + SubscriptionInstruction::Withdraw { amount, count } => { msg!("Instruction: Withdraw"); msg!("amount: {}", amount); - withdraw::process(program_id, accounts, amount)?; + msg!("count: {}", count); + withdraw::process_withdraw(program_id, accounts, amount, count)?; } SubscriptionInstruction::Renew { count } => { msg!("Instruction: Renew"); diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs index 4e53def..e341942 100644 --- a/program/src/processor/withdraw.rs +++ b/program/src/processor/withdraw.rs @@ -2,105 +2,108 @@ use { crate::{ error::SubscriptionError, instruction::SubscriptionInstruction, - state::{Counter, Subscription}, + state::{Subscription}, utils::{ - assert_msg, check_ata, check_initialized_ata, check_pda, check_program_id, check_signer, - check_writable, + assert_msg, check_ata, check_signer, check_writable, }, }, - borsh::{BorshDeserialize, BorshSerialize}, + borsh::{BorshDeserialize}, + num_traits::pow, solana_program::{ account_info::{next_account_info, AccountInfo}, - clock::Clock, entrypoint::ProgramResult, msg, - program::{invoke, invoke_signed}, - program_error::ProgramError, + program::{invoke_signed}, program_pack::Pack, pubkey::Pubkey, - system_instruction, system_program, - sysvar::{rent, Sysvar}, }, - spl_token::{error::TokenError, state::Account as TokenAccount, state::Mint}, + spl_token::{state::Account as TokenAccount}, }; -struct Context<'a, 'b: 'a> { - payer_ai: &'a AccountInfo<'b>, - payer_token_account: TokenAccount, - payer_token_account_ai: &'a AccountInfo<'b>, - deposit_vault_ai: &'a AccountInfo<'b>, - subscription_metadata_ai: &'a AccountInfo<'b>, - token_program_ai: &'a AccountInfo<'b>, -} - -impl<'a, 'b: 'a> Context<'a, 'b> { - pub fn parse(accounts: &'a [AccountInfo<'b>]) -> Result { - let account_info_iter = &mut accounts.iter(); - - let payer_ai = next_account_info(account_info_iter)?; - let payer_token_account_ai = next_account_info(account_info_iter)?; // SPL token account - let deposit_vault_ai = next_account_info(account_info_iter)?; - let subscription_metadata_ai = next_account_info(account_info_iter)?; - let token_program_ai = next_account_info(account_info_iter)?; +// TODO: store `count` parameter in subscription metadata (struct) and remove the paramter +pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, count: u64) -> ProgramResult { + let account_info_iter = &mut accounts.iter(); - check_signer(payer_ai); - [payer_ai, payer_token_account_ai, deposit_vault_ai].iter().map(|x| check_writable(x)); + let payer_ai = next_account_info(account_info_iter)?; + let payer_token_account_ai = next_account_info(account_info_iter)?; // SPL token account + let deposit_vault_ai = next_account_info(account_info_iter)?; + let subscription_ai = next_account_info(account_info_iter)?; + let token_program_ai = next_account_info(account_info_iter)?; - // Deserialize token account - let payer_token_account = TokenAccount::unpack_from_slice(&payer_token_account_ai.try_borrow_data()?)?; + check_signer(payer_ai); + [payer_ai, payer_token_account_ai, deposit_vault_ai].iter().map(|x| check_writable(x)); - Ok(Self { - payer_ai, - payer_token_account, - payer_token_account_ai, - deposit_vault_ai, - subscription_metadata_ai, - token_program_ai, - }) - } -} + // Deserialize token account + let deposit_vault = TokenAccount::unpack_from_slice(&deposit_vault_ai.try_borrow_data()?)?; + let payer_token_account = TokenAccount::unpack_from_slice(&payer_token_account_ai.try_borrow_data()?)?; -pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64) -> ProgramResult { - let ctx = Context::parse(accounts)?; + // Get subscription data + let subscription = Subscription::try_from_slice(&subscription_ai.try_borrow_data()?)?; // Validations + assert_msg( - ctx.payer_token_account.owner == *ctx.payer_ai.key, + payer_token_account.owner == *payer_ai.key, SubscriptionError::InvalidVaultOwner.into(), "Invalid vault owner", )?; - // NOTE: do we check for insufficient amount - // or do we just pass it to spl_token to deal separately? + check_ata( + deposit_vault_ai, + subscription_ai.key, + &subscription.deposit_mint, + )?; + + // Check that caller is the rightful owner, ie. owner (payer) of the subscription + + if let Some(current_mint) = subscription.mint { + assert_msg( + payer_token_account.mint == current_mint, + SubscriptionError::InvalidSubscriptionOwner.into(), + "Invalid subscription owner. Only the owner of a subscription associated with the deposit vault can withdraw.", + )?; + } + + assert_msg( + payer_token_account.amount > (0.3f64 * pow(10u64, 9) as f64) as u64, + SubscriptionError::InsufficientWithdrawBalance.into(), + "Insufficient funds to withdraw.", + )?; + + // Just ignore request if insufficient fund to withdraw + if deposit_vault.amount < amount { + msg!("Insufficient funds to withdraw."); + return Ok(()); + } + + msg!("Transferring the requested fund to the owner..."); - msg!("Transferring funds to owner..."); - // spl token instruction let instruction = &spl_token::instruction::transfer( &spl_token::id(), - ctx.deposit_vault_ai.key, - ctx.payer_token_account_ai.key, - ctx.payer_ai.key, + deposit_vault_ai.key, + payer_token_account_ai.key, + payer_ai.key, &[], amount, )?; + let withdrawal_seeds = &[ - b"withdrawal_metadata", - ctx.deposit_vault_ai.key.as_ref(), - ctx.payer_ai.key.as_ref(), + b"subscription_withdrawal", + deposit_vault_ai.key.as_ref(), + payer_ai.key.as_ref(), &amount.to_le_bytes(), + &count.to_le_bytes(), ]; invoke_signed( instruction, &[ - ctx.deposit_vault_ai.clone(), - ctx.payer_token_account_ai.clone(), - ctx.payer_ai.clone(), + deposit_vault_ai.clone(), + payer_token_account_ai.clone(), + subscription_ai.clone(), ], &[withdrawal_seeds], )?; Ok(()) } - -// test cases: diff --git a/program/src/utils.rs b/program/src/utils.rs index e7fd68e..74513b7 100644 --- a/program/src/utils.rs +++ b/program/src/utils.rs @@ -42,6 +42,7 @@ pub fn check_pda(account: &AccountInfo, seeds: &[&[u8]], program_id: &Pubkey) -> ) } +/// Validate that given acount is indeed the associated token address of user (and mint) address pub fn check_ata( account: &AccountInfo, user_address: &Pubkey, From e1d61d4f8dd18880e52206afc509996df2519a55 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Mon, 14 Mar 2022 21:34:14 -0500 Subject: [PATCH 06/10] Catch necessary and edge cases --- program/src/error.rs | 2 ++ program/src/instruction.rs | 7 ++++--- program/src/processor/withdraw.rs | 32 ++++++++++++++++++++++--------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/program/src/error.rs b/program/src/error.rs index c3a69bf..1a4d4c1 100644 --- a/program/src/error.rs +++ b/program/src/error.rs @@ -14,6 +14,8 @@ pub enum SubscriptionError { AlreadyExpired, #[error("Invalid vault owner.")] InvalidVaultOwner, + #[error("Invalid mint owner.")] + InvalidMintOwner, #[error("Invalid subscription owner.")] InvalidSubscriptionOwner, #[error("Account balance insufficient for requested withdraw amount.")] diff --git a/program/src/instruction.rs b/program/src/instruction.rs index b7e800a..fc344a2 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -44,9 +44,10 @@ pub enum SubscriptionInstruction { /// /// 0. `[writable, signer]` payer /// 1. `[writable]` payer token account - /// 2. `[writable]` deposit vault - /// 3. `[]` subscription metadata - /// 4. `[]` token program for token transfers + /// 2. `[writable]` (PDA) deposit vault + /// 4. `[writable]` (PDA) token mint + /// 5. `[]` (PDA) subscription metadata + /// 6. `[]` token program for token transfers /// Withdraw { amount: u64, count: u64 }, diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs index e341942..c7fa663 100644 --- a/program/src/processor/withdraw.rs +++ b/program/src/processor/withdraw.rs @@ -4,7 +4,7 @@ use { instruction::SubscriptionInstruction, state::{Subscription}, utils::{ - assert_msg, check_ata, check_signer, check_writable, + assert_msg, check_ata, check_signer, check_pda, check_writable, }, }, borsh::{BorshDeserialize}, @@ -27,19 +27,28 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u let payer_ai = next_account_info(account_info_iter)?; let payer_token_account_ai = next_account_info(account_info_iter)?; // SPL token account let deposit_vault_ai = next_account_info(account_info_iter)?; + let mint_ai = next_account_info(account_info_iter)?; let subscription_ai = next_account_info(account_info_iter)?; let token_program_ai = next_account_info(account_info_iter)?; check_signer(payer_ai); - [payer_ai, payer_token_account_ai, deposit_vault_ai].iter().map(|x| check_writable(x)); + [payer_ai, payer_token_account_ai, deposit_vault_ai, mint_ai].iter().map(|x| check_writable(x)); // Deserialize token account let deposit_vault = TokenAccount::unpack_from_slice(&deposit_vault_ai.try_borrow_data()?)?; let payer_token_account = TokenAccount::unpack_from_slice(&payer_token_account_ai.try_borrow_data()?)?; + let mint_account = TokenAccount::unpack_from_slice(&mint_ai.try_borrow_data()?)?; // Get subscription data let subscription = Subscription::try_from_slice(&subscription_ai.try_borrow_data()?)?; + let mint_seeds = &[ + b"subscription_mint", + subscription_ai.key.as_ref(), + &subscription.renewal_count.to_le_bytes(), + ]; + check_pda(mint_ai, mint_seeds, program_id)?; + // Validations assert_msg( @@ -48,6 +57,12 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u "Invalid vault owner", )?; + assert_msg( + mint_account.owner == payer_token_account.owner, + SubscriptionError::InvalidMintOwner.into(), + "Invalid mint owner", + )?; + check_ata( deposit_vault_ai, subscription_ai.key, @@ -62,20 +77,19 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u SubscriptionError::InvalidSubscriptionOwner.into(), "Invalid subscription owner. Only the owner of a subscription associated with the deposit vault can withdraw.", )?; + assert_msg( + deposit_vault.mint == current_mint && deposit_vault.amount > 0, + SubscriptionError::InvalidReceiver.into(), + "Invalid receiver.", + )?; } assert_msg( - payer_token_account.amount > (0.3f64 * pow(10u64, 9) as f64) as u64, + payer_token_account.amount > 0, SubscriptionError::InsufficientWithdrawBalance.into(), "Insufficient funds to withdraw.", )?; - // Just ignore request if insufficient fund to withdraw - if deposit_vault.amount < amount { - msg!("Insufficient funds to withdraw."); - return Ok(()); - } - msg!("Transferring the requested fund to the owner..."); let instruction = &spl_token::instruction::transfer( From 4a5fea28b13277fc44275b57a01337a629a07c3c Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Mon, 14 Mar 2022 21:51:16 -0500 Subject: [PATCH 07/10] Actually check ata now, eh? --- program/src/instruction.rs | 2 +- program/src/processor/withdraw.rs | 35 ++++--------------------------- 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/program/src/instruction.rs b/program/src/instruction.rs index fc344a2..3c372c3 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -44,8 +44,8 @@ pub enum SubscriptionInstruction { /// /// 0. `[writable, signer]` payer /// 1. `[writable]` payer token account + /// 4. `[writable]` (PDA) payer token vault /// 2. `[writable]` (PDA) deposit vault - /// 4. `[writable]` (PDA) token mint /// 5. `[]` (PDA) subscription metadata /// 6. `[]` token program for token transfers /// diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs index c7fa663..2e6b193 100644 --- a/program/src/processor/withdraw.rs +++ b/program/src/processor/withdraw.rs @@ -1,14 +1,12 @@ use { crate::{ error::SubscriptionError, - instruction::SubscriptionInstruction, state::{Subscription}, utils::{ - assert_msg, check_ata, check_signer, check_pda, check_writable, + assert_msg, check_initialized_ata, check_signer, check_writable, }, }, borsh::{BorshDeserialize}, - num_traits::pow, solana_program::{ account_info::{next_account_info, AccountInfo}, entrypoint::ProgramResult, @@ -26,29 +24,21 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u let payer_ai = next_account_info(account_info_iter)?; let payer_token_account_ai = next_account_info(account_info_iter)?; // SPL token account + let payer_vault_ai = next_account_info(account_info_iter)?; let deposit_vault_ai = next_account_info(account_info_iter)?; - let mint_ai = next_account_info(account_info_iter)?; let subscription_ai = next_account_info(account_info_iter)?; let token_program_ai = next_account_info(account_info_iter)?; check_signer(payer_ai); - [payer_ai, payer_token_account_ai, deposit_vault_ai, mint_ai].iter().map(|x| check_writable(x)); + [payer_ai, payer_token_account_ai, payer_vault_ai, deposit_vault_ai].iter().map(|x| check_writable(x)); // Deserialize token account let deposit_vault = TokenAccount::unpack_from_slice(&deposit_vault_ai.try_borrow_data()?)?; let payer_token_account = TokenAccount::unpack_from_slice(&payer_token_account_ai.try_borrow_data()?)?; - let mint_account = TokenAccount::unpack_from_slice(&mint_ai.try_borrow_data()?)?; // Get subscription data let subscription = Subscription::try_from_slice(&subscription_ai.try_borrow_data()?)?; - let mint_seeds = &[ - b"subscription_mint", - subscription_ai.key.as_ref(), - &subscription.renewal_count.to_le_bytes(), - ]; - check_pda(mint_ai, mint_seeds, program_id)?; - // Validations assert_msg( @@ -57,31 +47,14 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u "Invalid vault owner", )?; - assert_msg( - mint_account.owner == payer_token_account.owner, - SubscriptionError::InvalidMintOwner.into(), - "Invalid mint owner", - )?; - - check_ata( - deposit_vault_ai, - subscription_ai.key, - &subscription.deposit_mint, - )?; - // Check that caller is the rightful owner, ie. owner (payer) of the subscription - if let Some(current_mint) = subscription.mint { + check_initialized_ata(payer_vault_ai, payer_ai.key, ¤t_mint)?; assert_msg( payer_token_account.mint == current_mint, SubscriptionError::InvalidSubscriptionOwner.into(), "Invalid subscription owner. Only the owner of a subscription associated with the deposit vault can withdraw.", )?; - assert_msg( - deposit_vault.mint == current_mint && deposit_vault.amount > 0, - SubscriptionError::InvalidReceiver.into(), - "Invalid receiver.", - )?; } assert_msg( From 109bfb398009e5dcee534f6e62ee607657c0fec0 Mon Sep 17 00:00:00 2001 From: Jongwon Park Date: Mon, 14 Mar 2022 22:06:35 -0500 Subject: [PATCH 08/10] Fix invoke seeds & checks --- program/src/processor/withdraw.rs | 32 +++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs index 2e6b193..a00f649 100644 --- a/program/src/processor/withdraw.rs +++ b/program/src/processor/withdraw.rs @@ -1,9 +1,10 @@ use { crate::{ error::SubscriptionError, + instruction::SubscriptionInstruction, state::{Subscription}, utils::{ - assert_msg, check_initialized_ata, check_signer, check_writable, + assert_msg, check_initialized_ata, check_pda, check_signer, check_writable, }, }, borsh::{BorshDeserialize}, @@ -38,6 +39,8 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u // Get subscription data let subscription = Subscription::try_from_slice(&subscription_ai.try_borrow_data()?)?; + let duration = subscription.duration; + let payee = subscription.payee; // Validations @@ -58,7 +61,7 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u } assert_msg( - payer_token_account.amount > 0, + deposit_vault.amount > amount, SubscriptionError::InsufficientWithdrawBalance.into(), "Insufficient funds to withdraw.", )?; @@ -74,14 +77,27 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u amount, )?; - let withdrawal_seeds = &[ - b"subscription_withdrawal", - deposit_vault_ai.key.as_ref(), - payer_ai.key.as_ref(), + let subscription_seeds = &[ + b"subscription_metadata", + payee.as_ref(), &amount.to_le_bytes(), + &duration.to_le_bytes(), &count.to_le_bytes(), ]; - + + check_pda(subscription_ai, subscription_seeds, program_id)?; + + let (_, subscription_bump) = Pubkey::find_program_address(subscription_seeds, program_id); + + let subscription_seeds = &[ + b"subscription_metadata", + payee.as_ref(), + &amount.to_le_bytes(), + &duration.to_le_bytes(), + &count.to_le_bytes(), + &[subscription_bump], + ]; + invoke_signed( instruction, &[ @@ -89,7 +105,7 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u payer_token_account_ai.clone(), subscription_ai.clone(), ], - &[withdrawal_seeds], + &[subscription_seeds], )?; Ok(()) From 19f4c399da64512768319e6f299b6a36a59fd4f7 Mon Sep 17 00:00:00 2001 From: Alec Chen Date: Tue, 15 Mar 2022 00:18:12 -0500 Subject: [PATCH 09/10] Minor swaps and renaming --- program/src/instruction.rs | 4 ++-- program/src/processor/withdraw.rs | 28 +++++++++++++--------------- program/src/utils.rs | 10 +++++----- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/program/src/instruction.rs b/program/src/instruction.rs index 3c372c3..ac436c0 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -43,8 +43,8 @@ pub enum SubscriptionInstruction { /// Accounts expected by this instruction: /// /// 0. `[writable, signer]` payer - /// 1. `[writable]` payer token account - /// 4. `[writable]` (PDA) payer token vault + /// 1. `[writable]` (PDA) payer subscription token account + /// 4. `[writable]` (PDA) payer deposit token account /// 2. `[writable]` (PDA) deposit vault /// 5. `[]` (PDA) subscription metadata /// 6. `[]` token program for token transfers diff --git a/program/src/processor/withdraw.rs b/program/src/processor/withdraw.rs index a00f649..2eabf11 100644 --- a/program/src/processor/withdraw.rs +++ b/program/src/processor/withdraw.rs @@ -20,7 +20,7 @@ use { }; // TODO: store `count` parameter in subscription metadata (struct) and remove the paramter -pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, count: u64) -> ProgramResult { +pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], withdraw_amount: u64, count: u64) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let payer_ai = next_account_info(account_info_iter)?; @@ -31,11 +31,12 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u let token_program_ai = next_account_info(account_info_iter)?; check_signer(payer_ai); - [payer_ai, payer_token_account_ai, payer_vault_ai, deposit_vault_ai].iter().map(|x| check_writable(x)); + [payer_ai, payer_vault_ai, deposit_vault_ai].iter().map(|x| check_writable(x)); // Deserialize token account let deposit_vault = TokenAccount::unpack_from_slice(&deposit_vault_ai.try_borrow_data()?)?; let payer_token_account = TokenAccount::unpack_from_slice(&payer_token_account_ai.try_borrow_data()?)?; + let payer_vault = TokenAccount::unpack_from_slice(&payer_vault_ai.try_borrow_data()?)?; // Get subscription data let subscription = Subscription::try_from_slice(&subscription_ai.try_borrow_data()?)?; @@ -44,24 +45,21 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u // Validations - assert_msg( - payer_token_account.owner == *payer_ai.key, - SubscriptionError::InvalidVaultOwner.into(), - "Invalid vault owner", - )?; + // Check payer token account + check_initialized_ata(payer_vault_ai, payer_ai.key, &subscription.deposit_vault)?; // Check that caller is the rightful owner, ie. owner (payer) of the subscription if let Some(current_mint) = subscription.mint { - check_initialized_ata(payer_vault_ai, payer_ai.key, ¤t_mint)?; + check_initialized_ata(payer_token_account_ai, payer_ai.key, ¤t_mint)?; assert_msg( - payer_token_account.mint == current_mint, + payer_token_account.amount > 0, SubscriptionError::InvalidSubscriptionOwner.into(), "Invalid subscription owner. Only the owner of a subscription associated with the deposit vault can withdraw.", )?; } assert_msg( - deposit_vault.amount > amount, + deposit_vault.amount > withdraw_amount, SubscriptionError::InsufficientWithdrawBalance.into(), "Insufficient funds to withdraw.", )?; @@ -71,16 +69,16 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u let instruction = &spl_token::instruction::transfer( &spl_token::id(), deposit_vault_ai.key, - payer_token_account_ai.key, + payer_vault_ai.key, payer_ai.key, &[], - amount, + withdraw_amount, )?; let subscription_seeds = &[ b"subscription_metadata", payee.as_ref(), - &amount.to_le_bytes(), + &subscription.amount.to_le_bytes(), &duration.to_le_bytes(), &count.to_le_bytes(), ]; @@ -92,7 +90,7 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u let subscription_seeds = &[ b"subscription_metadata", payee.as_ref(), - &amount.to_le_bytes(), + &subscription.amount.to_le_bytes(), &duration.to_le_bytes(), &count.to_le_bytes(), &[subscription_bump], @@ -102,7 +100,7 @@ pub fn process_withdraw(program_id: &Pubkey, accounts: &[AccountInfo], amount: u instruction, &[ deposit_vault_ai.clone(), - payer_token_account_ai.clone(), + payer_vault_ai.clone(), subscription_ai.clone(), ], &[subscription_seeds], diff --git a/program/src/utils.rs b/program/src/utils.rs index 74513b7..5dbdd68 100644 --- a/program/src/utils.rs +++ b/program/src/utils.rs @@ -21,7 +21,7 @@ pub fn check_signer(account: &AccountInfo) -> ProgramResult { assert_msg( account.is_signer, ProgramError::MissingRequiredSignature, - format!("Missing required signature on account: {}", account.key), + &format!("Missing required signature on account: {}", account.key), ) } @@ -29,7 +29,7 @@ pub fn check_writable(account: &AccountInfo) -> ProgramResult { assert_msg( account.is_writable, ProgramError::MissingRequiredSignature, - format!("Account should be writable: {}", account.key), + &format!("Account should be writable: {}", account.key), ) } @@ -38,7 +38,7 @@ pub fn check_pda(account: &AccountInfo, seeds: &[&[u8]], program_id: &Pubkey) -> assert_msg( *account.key == pda, UtilsError::InvalidProgramAddress.into(), - format!("Invalid PDA:\tExpected: {}\tGot: {}", &pda, account.key), + &format!("Invalid PDA:\tExpected: {}\tGot: {}", &pda, account.key), ) } @@ -53,7 +53,7 @@ pub fn check_ata( assert_msg( *account.key == ata, UtilsError::InvalidProgramAddress.into(), - format!("Invalid ATA address:\tExpected: {}\tGot: {}", &ata, account.key) + &format!("Invalid ATA address:\tExpected: {}\tGot: {}", &ata, account.key) ) } @@ -85,7 +85,7 @@ pub fn check_program_id(account: &AccountInfo, program_id: &Pubkey) -> ProgramRe assert_msg( *account.key == *program_id, ProgramError::IncorrectProgramId, - format!("Invalid program id:\tExpected: {}\tGot: {}", program_id, account.key) + &format!("Invalid program id:\tExpected: {}\tGot: {}", program_id, account.key) ) } From dac8fe184346d25298b5d99d6799df7a4c0258eb Mon Sep 17 00:00:00 2001 From: Alec Chen Date: Tue, 15 Mar 2022 00:19:21 -0500 Subject: [PATCH 10/10] Forgot to rename another thing and pushed commit already --- program/src/instruction.rs | 2 +- program/src/processor.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/program/src/instruction.rs b/program/src/instruction.rs index ac436c0..39603c6 100644 --- a/program/src/instruction.rs +++ b/program/src/instruction.rs @@ -49,7 +49,7 @@ pub enum SubscriptionInstruction { /// 5. `[]` (PDA) subscription metadata /// 6. `[]` token program for token transfers /// - Withdraw { amount: u64, count: u64 }, + Withdraw { withdraw_amount: u64, count: u64 }, /// Renews or deactivates a provided subscription. /// diff --git a/program/src/processor.rs b/program/src/processor.rs index fcc4030..5bb5870 100644 --- a/program/src/processor.rs +++ b/program/src/processor.rs @@ -207,11 +207,11 @@ impl Processor { msg!("Instruction: Deposit"); msg!("amount: {}", amount); } - SubscriptionInstruction::Withdraw { amount, count } => { + SubscriptionInstruction::Withdraw { withdraw_amount, count } => { msg!("Instruction: Withdraw"); - msg!("amount: {}", amount); + msg!("withdraw_amount: {}", withdraw_amount); msg!("count: {}", count); - withdraw::process_withdraw(program_id, accounts, amount, count)?; + withdraw::process_withdraw(program_id, accounts, withdraw_amount, count)?; } SubscriptionInstruction::Renew { count } => { msg!("Instruction: Renew");