From 2f531ad314e11650e6dc16b10e800648dae83dba Mon Sep 17 00:00:00 2001 From: Vitalii Koval Date: Thu, 12 Dec 2024 14:19:11 +0400 Subject: [PATCH 1/3] implement deposit limit; refactoring --- programs/tokenized_vault/src/constants.rs | 2 +- programs/tokenized_vault/src/errors.rs | 6 ++ .../src/instructions/deposit.rs | 38 ++++++---- .../src/instructions/direct_deposit.rs | 46 +++++++----- .../src/instructions/revoke_whitelisting.rs | 17 ++--- .../src/instructions/whitelist.rs | 14 ++-- .../src/instructions/withdraw.rs | 34 +++++++-- programs/tokenized_vault/src/state/mod.rs | 4 +- .../src/state/strategy_data.rs | 32 -------- .../tokenized_vault/src/state/user_data.rs | 24 ++++++ programs/tokenized_vault/src/state/vault.rs | 3 + .../tokenized_vault/src/state/whitelist.rs | 38 ---------- programs/tokenized_vault/src/utils/mod.rs | 2 + .../tokenized_vault/src/utils/unchecked.rs | 26 +++++++ programs/tokenized_vault/src/utils/vault.rs | 16 ++-- tests/integration/vault.ts | 75 +++++++++++++++++++ 16 files changed, 238 insertions(+), 139 deletions(-) create mode 100644 programs/tokenized_vault/src/state/user_data.rs delete mode 100644 programs/tokenized_vault/src/state/whitelist.rs create mode 100644 programs/tokenized_vault/src/utils/unchecked.rs diff --git a/programs/tokenized_vault/src/constants.rs b/programs/tokenized_vault/src/constants.rs index 93969da..48310b1 100644 --- a/programs/tokenized_vault/src/constants.rs +++ b/programs/tokenized_vault/src/constants.rs @@ -5,7 +5,7 @@ pub const UNDERLYING_SEED: &str = "underlying"; pub const ROLES_SEED: &str = "roles"; pub const CONFIG_SEED: &str = "config"; pub const STRATEGY_DATA_SEED: &str = "strategy_data"; -pub const WHITELISTED_SEED: &str = "whitelisted"; +pub const USER_DATA_SEED: &str = "user_data"; pub const MAX_BPS: u64 = 10_000; pub const FEE_BPS: u64 = 10_000; diff --git a/programs/tokenized_vault/src/errors.rs b/programs/tokenized_vault/src/errors.rs index 46e23b7..20af7e3 100644 --- a/programs/tokenized_vault/src/errors.rs +++ b/programs/tokenized_vault/src/errors.rs @@ -67,4 +67,10 @@ pub enum ErrorCode { #[msg("Account is not whitelisted")] NotWhitelisted, + + #[msg("User deposit limit exceeded")] + ExceedUserDepositLimit, + + #[msg("Serialization error")] + SerializationError, } diff --git a/programs/tokenized_vault/src/instructions/deposit.rs b/programs/tokenized_vault/src/instructions/deposit.rs index c23d92e..badfcd3 100644 --- a/programs/tokenized_vault/src/instructions/deposit.rs +++ b/programs/tokenized_vault/src/instructions/deposit.rs @@ -1,3 +1,4 @@ +use access_control::state::UserRole; use access_control::{ constants::USER_ROLE_SEED, program::AccessControl, @@ -6,10 +7,10 @@ use access_control::{ use anchor_lang::prelude::*; use anchor_spl::token::{Mint, Token, TokenAccount}; -use crate::constants::{SHARES_SEED, UNDERLYING_SEED, WHITELISTED_SEED}; +use crate::constants::{SHARES_SEED, UNDERLYING_SEED, USER_DATA_SEED}; use crate::events::VaultDepositEvent; -use crate::state::Vault; +use crate::state::{UserData, Vault}; use crate::utils::{token, vault}; #[derive(Accounts)] @@ -29,32 +30,35 @@ pub struct Deposit<'info> { #[account(mut)] pub user_shares_account: Account<'info, TokenAccount>, - /// CHECK: this account may not exist #[account( + init_if_needed, + payer = user, + space = UserData::LEN, seeds = [ - USER_ROLE_SEED.as_bytes(), - user.key().as_ref(), - Role::KYCVerified.to_seed().as_ref() + USER_DATA_SEED.as_bytes(), + vault.key().as_ref(), + user.key().as_ref() ], - bump, - seeds::program = access_control.key() - )] - pub kyc_verified: UncheckedAccount<'info>, + bump + )] + pub user_data: Account<'info, UserData>, /// CHECK: this account may not exist #[account( seeds = [ - WHITELISTED_SEED.as_bytes(), - vault.key().as_ref(), + USER_ROLE_SEED.as_bytes(), user.key().as_ref(), + Role::KYCVerified.to_seed().as_ref() ], bump, + seeds::program = access_control.key() )] - pub whitelisted: UncheckedAccount<'info>, - + pub kyc_verified: Account<'info, UserRole>, + #[account(mut)] pub user: Signer<'info>, + pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, pub access_control: Program<'info, AccessControl>, } @@ -62,8 +66,8 @@ pub struct Deposit<'info> { pub fn handle_deposit(ctx: Context, amount: u64) -> Result<()> { vault::validate_deposit( &ctx.accounts.vault, - ctx.accounts.kyc_verified.to_account_info(), - ctx.accounts.whitelisted.to_account_info(), + &ctx.accounts.kyc_verified, + &ctx.accounts.user_data, false, amount )?; @@ -87,6 +91,8 @@ pub fn handle_deposit(ctx: Context, amount: u64) -> Result<()> { &ctx.accounts.vault.load()?.seeds_shares(), )?; + ctx.accounts.user_data.deposited += amount; + let mut vault = ctx.accounts.vault.load_mut()?; vault.handle_deposit(amount, shares); diff --git a/programs/tokenized_vault/src/instructions/direct_deposit.rs b/programs/tokenized_vault/src/instructions/direct_deposit.rs index 5979615..8873ba7 100644 --- a/programs/tokenized_vault/src/instructions/direct_deposit.rs +++ b/programs/tokenized_vault/src/instructions/direct_deposit.rs @@ -1,7 +1,7 @@ use access_control::{ constants::USER_ROLE_SEED, program::AccessControl, - state::Role + state::{Role, UserRole} }; use anchor_lang::prelude::*; use anchor_spl::{ @@ -10,12 +10,16 @@ use anchor_spl::{ }; use strategy::program::Strategy; -use crate::constants::{SHARES_SEED, STRATEGY_DATA_SEED, UNDERLYING_SEED, WHITELISTED_SEED}; +use crate::constants::{ + SHARES_SEED, + STRATEGY_DATA_SEED, + UNDERLYING_SEED, + USER_DATA_SEED +}; use crate::events::{VaultDepositEvent, UpdatedCurrentDebtForStrategyEvent}; -use crate::state::{Vault, StrategyData}; -use crate::utils::{token, vault}; -use crate::utils::strategy as strategy_utils; +use crate::state::{UserData, Vault, StrategyData}; +use crate::utils::{strategy as strategy_utils, token, vault}; #[derive(Accounts)] pub struct DirectDeposit<'info> { @@ -49,6 +53,19 @@ pub struct DirectDeposit<'info> { )] pub strategy_data: Account<'info, StrategyData>, + #[account( + init_if_needed, + payer = user, + space = UserData::LEN, + seeds = [ + USER_DATA_SEED.as_bytes(), + vault.key().as_ref(), + user.key().as_ref() + ], + bump + )] + pub user_data: Account<'info, UserData>, + #[account( mut, seeds = [UNDERLYING_SEED.as_bytes(), strategy.key().as_ref()], @@ -67,22 +84,12 @@ pub struct DirectDeposit<'info> { bump, seeds::program = access_control.key() )] - pub kyc_verified: UncheckedAccount<'info>, + pub kyc_verified: Account<'info, UserRole>, - /// CHECK: this account may not exist - #[account( - seeds = [ - WHITELISTED_SEED.as_bytes(), - vault.key().as_ref(), - user.key().as_ref(), - ], - bump, - )] - pub whitelisted: UncheckedAccount<'info>, - #[account(mut)] pub user: Signer<'info>, + pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, pub access_control: Program<'info, AccessControl>, pub strategy_program: Program<'info, Strategy>, @@ -91,8 +98,8 @@ pub struct DirectDeposit<'info> { pub fn handle_direct_deposit<'info>(ctx: Context<'_, '_, '_, 'info, DirectDeposit<'info>>, amount: u64) -> Result<()> { vault::validate_deposit( &ctx.accounts.vault, - ctx.accounts.kyc_verified.to_account_info(), - ctx.accounts.whitelisted.to_account_info(), + &ctx.accounts.kyc_verified, + &ctx.accounts.user_data, true, amount )?; @@ -135,6 +142,7 @@ pub fn handle_direct_deposit<'info>(ctx: Context<'_, '_, '_, 'info, DirectDeposi ctx.accounts.strategy_data.increase_current_debt(amount)?; vault.handle_direct_deposit(amount, shares); + ctx.accounts.user_data.deposited += amount; emit!(VaultDepositEvent { vault_key: vault.key, diff --git a/programs/tokenized_vault/src/instructions/revoke_whitelisting.rs b/programs/tokenized_vault/src/instructions/revoke_whitelisting.rs index 9bc70e6..88733df 100644 --- a/programs/tokenized_vault/src/instructions/revoke_whitelisting.rs +++ b/programs/tokenized_vault/src/instructions/revoke_whitelisting.rs @@ -5,23 +5,22 @@ use access_control::{ state::{UserRole, Role} }; -use crate::constants::WHITELISTED_SEED; -use crate::state::{Vault, Whitelisted}; +use crate::constants::USER_DATA_SEED; +use crate::state::{Vault, UserData}; #[derive(Accounts)] #[instruction(user: Pubkey)] pub struct RevokeWhitelisting<'info> { #[account( mut, - close = recipient, seeds = [ - WHITELISTED_SEED.as_bytes(), + USER_DATA_SEED.as_bytes(), vault.key().as_ref(), user.as_ref() ], bump, )] - pub whitelisted: Account<'info, Whitelisted>, + pub user_data: Account<'info, UserData>, #[account(mut)] pub vault: AccountLoader<'info, Vault>, @@ -40,16 +39,12 @@ pub struct RevokeWhitelisting<'info> { #[account(mut, constraint = roles.check_role()?)] pub signer: Signer<'info>, - /// CHECK: - #[account(mut)] - pub recipient: UncheckedAccount<'info>, - pub access_control: Program<'info, AccessControl>, pub system_program: Program<'info, System>, pub rent: Sysvar<'info, Rent>, } - -pub fn handle_revoke_whitelisting(_ctx: Context, _user: Pubkey) -> Result<()> { +pub fn handle_revoke_whitelisting(ctx: Context, _user: Pubkey) -> Result<()> { + ctx.accounts.user_data.whitelisted = false; Ok(()) } diff --git a/programs/tokenized_vault/src/instructions/whitelist.rs b/programs/tokenized_vault/src/instructions/whitelist.rs index 52ceda9..139f589 100644 --- a/programs/tokenized_vault/src/instructions/whitelist.rs +++ b/programs/tokenized_vault/src/instructions/whitelist.rs @@ -5,24 +5,24 @@ use access_control::{ state::{UserRole, Role} }; -use crate::constants::WHITELISTED_SEED; -use crate::state::{Vault, Whitelisted}; +use crate::constants::USER_DATA_SEED; +use crate::state::{UserData, Vault}; #[derive(Accounts)] #[instruction(user: Pubkey)] pub struct Whitelist<'info> { #[account( - init, + init_if_needed, seeds = [ - WHITELISTED_SEED.as_bytes(), + USER_DATA_SEED.as_bytes(), vault.key().as_ref(), user.as_ref() ], bump, payer = signer, - space = Whitelisted::LEN, + space = UserData::LEN, )] - pub whitelisted: Account<'info, Whitelisted>, + pub user_data: Account<'info, UserData>, #[account(mut)] pub vault: AccountLoader<'info, Vault>, @@ -48,6 +48,6 @@ pub struct Whitelist<'info> { pub fn handle_whitelist(ctx: Context, _user: Pubkey) -> Result<()> { - ctx.accounts.whitelisted.is_whitelisted = true; + ctx.accounts.user_data.whitelisted = true; Ok(()) } diff --git a/programs/tokenized_vault/src/instructions/withdraw.rs b/programs/tokenized_vault/src/instructions/withdraw.rs index 8298376..e806a30 100644 --- a/programs/tokenized_vault/src/instructions/withdraw.rs +++ b/programs/tokenized_vault/src/instructions/withdraw.rs @@ -1,17 +1,17 @@ use anchor_lang::prelude::*; use anchor_spl::{ token::Token, - token_interface::{ Mint, TokenAccount }, + token_interface::{ Mint, TokenAccount } }; use strategy::program::Strategy; use crate::events::VaultWithdrawlEvent; -use crate::state::{StrategyDataAccInfo, Vault}; -use crate::utils::strategy as strategy_utils; -use crate::utils::token; +use crate::state::{StrategyData, UserData, Vault}; +use crate::utils::{strategy as strategy_utils, token, unchecked::*}; use crate::errors::ErrorCode; use crate::constants::{ UNDERLYING_SEED, + USER_DATA_SEED, SHARES_SEED, MAX_BPS }; @@ -33,6 +33,18 @@ pub struct Withdraw<'info> { #[account(mut)] pub user_shares_account: InterfaceAccount<'info, TokenAccount>, + /// CHECK: can be missing + #[account( + mut, + seeds = [ + USER_DATA_SEED.as_bytes(), + vault.key().as_ref(), + user.key().as_ref() + ], + bump + )] + pub user_data: UncheckedAccount<'info>, + #[account(mut)] pub user: Signer<'info>, @@ -122,6 +134,12 @@ pub fn handle_withdraw<'info>( &ctx.accounts.vault.load()?.seeds() )?; + if !ctx.accounts.user_data.data_is_empty() { + let mut user_data: UserData = ctx.accounts.user_data.deserialize()?; + user_data.handle_withdraw(assets_to_transfer)?; + ctx.accounts.user_data.serialize(&user_data)?; + } + let vault = ctx.accounts.vault.load()?; emit!(VaultWithdrawlEvent { @@ -186,7 +204,7 @@ fn validate_max_withdraw<'info>( let mut loss = 0; for strategy_accounts in strategies { - let current_debt = strategy_accounts.strategy_data.current_debt(); + let current_debt = strategy_accounts.strategy_data.deserialize::()?.current_debt; let mut to_withdraw = std::cmp::min(max_assets - have, current_debt); let mut unrealised_loss = strategy_utils::assess_share_of_unrealised_losses( @@ -247,7 +265,7 @@ fn withdraw_assets<'info>( for i in 0..strategies.len() { let strategy_acc = &strategies[i].strategy_acc; - let mut current_debt = strategies[i].strategy_data.current_debt(); + let mut current_debt = strategies[i].strategy_data.deserialize::()?.current_debt; let mut to_withdraw = std::cmp::min(assets_needed as u64, current_debt); let strategy_limit = strategy_utils::get_max_withdraw(&strategy_acc)?; @@ -313,7 +331,9 @@ fn withdraw_assets<'info>( let vault_mut = &mut vault_acc.load_mut()?; - strategies[i].strategy_data.set_current_debt(new_debt)?; + let mut strategy_data: StrategyData = strategies[i].strategy_data.deserialize()?; + strategy_data.update_current_debt(new_debt)?; + strategies[i].strategy_data.serialize(strategy_data)?; vault_mut.total_debt = total_debt; vault_mut.total_idle = total_idle; diff --git a/programs/tokenized_vault/src/state/mod.rs b/programs/tokenized_vault/src/state/mod.rs index 3b9e2a3..104b61e 100644 --- a/programs/tokenized_vault/src/state/mod.rs +++ b/programs/tokenized_vault/src/state/mod.rs @@ -1,9 +1,9 @@ pub mod config; pub mod strategy_data; pub mod vault; -pub mod whitelist; +pub mod user_data; pub use config::*; pub use strategy_data::*; pub use vault::*; -pub use whitelist::*; +pub use user_data::*; diff --git a/programs/tokenized_vault/src/state/strategy_data.rs b/programs/tokenized_vault/src/state/strategy_data.rs index 8fd4497..61fe5a3 100644 --- a/programs/tokenized_vault/src/state/strategy_data.rs +++ b/programs/tokenized_vault/src/state/strategy_data.rs @@ -12,38 +12,6 @@ pub struct StrategyData { pub last_update: i64, } -pub fn deserialize(acc_info: &AccountInfo) -> Result> { - let data = acc_info.try_borrow_data()?; - Ok(Box::new(StrategyData::try_from_slice(&data[8..]).unwrap())) -} - -pub trait StrategyDataAccInfo { - fn set_current_debt(&self, amount: u64) -> Result<()>; - fn deserialize(&self) -> Result>; - fn current_debt(&self) -> u64; - -} - -impl<'a> StrategyDataAccInfo for AccountInfo<'a> { - fn deserialize(&self) -> Result> { - let data = self.try_borrow_data()?; - Ok(Box::new(StrategyData::try_from_slice(&data[8..]).unwrap())) - } - - fn current_debt(&self) -> u64 { - let data = self.try_borrow_data().unwrap(); - StrategyData::try_from_slice(&data[8..]).unwrap().current_debt - } - - fn set_current_debt(&self, amount: u64) -> Result<()> { - let mut data = self.try_borrow_mut_data()?; - let mut strategy_data = StrategyData::try_from_slice(&data[8..]).unwrap(); - strategy_data.update_current_debt(amount)?; - strategy_data.serialize(&mut &mut data[8..])?; - Ok(()) - } -} - impl StrategyData { pub const LEN: usize = DISCRIMINATOR_LEN + StrategyData::INIT_SPACE; diff --git a/programs/tokenized_vault/src/state/user_data.rs b/programs/tokenized_vault/src/state/user_data.rs new file mode 100644 index 0000000..915a0b3 --- /dev/null +++ b/programs/tokenized_vault/src/state/user_data.rs @@ -0,0 +1,24 @@ +use anchor_lang::prelude::*; + +use crate::constants::DISCRIMINATOR_LEN; + +#[account] +#[derive(Default, Debug, InitSpace)] +pub struct UserData { + pub deposited: u64, + pub whitelisted: bool, +} + +impl UserData { + pub const LEN: usize = DISCRIMINATOR_LEN + UserData::INIT_SPACE; + + pub fn handle_withdraw(&mut self, amount: u64) -> Result<()> { + if self.deposited < amount { + self.deposited = 0; + } else { + self.deposited -= amount; + } + + Ok(()) + } +} \ No newline at end of file diff --git a/programs/tokenized_vault/src/state/vault.rs b/programs/tokenized_vault/src/state/vault.rs index cd04bfe..96b0216 100644 --- a/programs/tokenized_vault/src/state/vault.rs +++ b/programs/tokenized_vault/src/state/vault.rs @@ -24,6 +24,7 @@ pub struct Vault { pub minimum_total_idle: u64, pub total_idle: u64, pub deposit_limit: u64, + pub user_deposit_limit: u64, pub min_user_deposit: u64, pub strategies_amount: u64, @@ -43,6 +44,7 @@ pub struct Vault { #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] pub struct VaultConfig { pub deposit_limit: u64, + pub user_deposit_limit: u64, pub min_user_deposit: u64, pub accountant: Pubkey, pub profit_max_unlock_time: u64, @@ -95,6 +97,7 @@ impl Vault { self.accountant = config.accountant; self.deposit_limit = config.deposit_limit; + self.user_deposit_limit = config.user_deposit_limit; self.min_user_deposit = config.min_user_deposit; self.profit_max_unlock_time = config.profit_max_unlock_time; self.kyc_verified_only = config.kyc_verified_only; diff --git a/programs/tokenized_vault/src/state/whitelist.rs b/programs/tokenized_vault/src/state/whitelist.rs deleted file mode 100644 index 1af46f3..0000000 --- a/programs/tokenized_vault/src/state/whitelist.rs +++ /dev/null @@ -1,38 +0,0 @@ -use anchor_lang::prelude::*; - -use crate::constants::DISCRIMINATOR_LEN; - -#[account] -#[derive(Default, Debug, InitSpace)] -pub struct Whitelisted { - pub is_whitelisted: bool, -} - -impl Whitelisted { - pub const LEN: usize = DISCRIMINATOR_LEN + Whitelisted::INIT_SPACE; - -} - -pub trait WhitelistedAccInfo { - fn deserialize(&self) -> Result>; - fn is_whitelisted(&self) -> bool; -} - -impl<'a> WhitelistedAccInfo for AccountInfo<'a> { - fn deserialize(&self) -> Result> { - let data = self.try_borrow_data()?; - Ok(Box::new(Whitelisted::try_from_slice(&data[8..]).unwrap())) - } - - fn is_whitelisted(&self) -> bool { - let data = self.try_borrow_data().unwrap(); - if data.len() == 0 { - return false; - } - let roles = Whitelisted::try_from_slice(&data[8..]); - if roles.is_err() { - return false; - } - roles.unwrap().is_whitelisted - } -} \ No newline at end of file diff --git a/programs/tokenized_vault/src/utils/mod.rs b/programs/tokenized_vault/src/utils/mod.rs index 9846a84..e7adbd9 100644 --- a/programs/tokenized_vault/src/utils/mod.rs +++ b/programs/tokenized_vault/src/utils/mod.rs @@ -1,9 +1,11 @@ pub mod accountant; pub mod strategy; pub mod token; +pub mod unchecked; pub mod vault; pub use accountant::*; pub use strategy::*; pub use token::*; +pub use unchecked::*; pub use vault::*; diff --git a/programs/tokenized_vault/src/utils/unchecked.rs b/programs/tokenized_vault/src/utils/unchecked.rs new file mode 100644 index 0000000..3786df9 --- /dev/null +++ b/programs/tokenized_vault/src/utils/unchecked.rs @@ -0,0 +1,26 @@ +use anchor_lang::prelude::*; + +use crate::errors::ErrorCode; + +pub trait AccountInfoExt { + fn deserialize(&self) -> Result where T: AnchorDeserialize; + fn serialize(&self, account: T) -> Result<()> where T: AnchorSerialize; +} + +impl<'a> AccountInfoExt for AccountInfo<'a> { + fn deserialize(&self) -> Result + where T: AnchorDeserialize + { + let data = self.try_borrow_data()?; + Ok(T::try_from_slice(&data[8..]).unwrap()) + } + + fn serialize(&self, account: T) -> Result<()> + where T: AnchorSerialize + { + let writer: &mut dyn std::io::Write = &mut &mut self.try_borrow_mut_data()?[8..]; + account.try_to_vec().map_err(|_| ErrorCode::SerializationError.into()).and_then(|vec| { + writer.write_all(&vec).map_err(|_| ErrorCode::SerializationError.into()) + }) + } +} \ No newline at end of file diff --git a/programs/tokenized_vault/src/utils/vault.rs b/programs/tokenized_vault/src/utils/vault.rs index 6287749..a28d0db 100644 --- a/programs/tokenized_vault/src/utils/vault.rs +++ b/programs/tokenized_vault/src/utils/vault.rs @@ -1,13 +1,13 @@ +use access_control::state::UserRole; use anchor_lang::prelude::*; -use access_control::utils::UserRoleAccInfo; use crate::errors::ErrorCode; -use crate::state::{Vault, WhitelistedAccInfo}; +use crate::state::{UserData, Vault}; pub fn validate_deposit<'info>( vault_loader: &AccountLoader<'info, Vault>, - kyc_verified: AccountInfo<'info>, - whitelisted: AccountInfo<'info>, + kyc_verified: &Account<'info, UserRole>, + user_data: &Account<'info, UserData>, is_direct: bool, amount: u64 ) -> Result<()> { @@ -25,6 +25,10 @@ pub fn validate_deposit<'info>( return Err(ErrorCode::DirectDepositDisabled.into()); } + if vault.user_deposit_limit > 0 && user_data.deposited + amount > vault.user_deposit_limit { + return Err(ErrorCode::ExceedUserDepositLimit.into()); + } + if amount < vault.min_user_deposit { return Err(ErrorCode::MinDepositNotReached.into()); } @@ -34,11 +38,11 @@ pub fn validate_deposit<'info>( return Err(ErrorCode::ExceedDepositLimit.into()); } - if vault.kyc_verified_only && !kyc_verified.has_role() { + if vault.kyc_verified_only && !kyc_verified.has_role { return Err(ErrorCode::KYCRequired.into()); } - if vault.whitelisted_only && !whitelisted.is_whitelisted() { + if vault.whitelisted_only && !user_data.whitelisted { return Err(ErrorCode::NotWhitelisted.into()); } diff --git a/tests/integration/vault.ts b/tests/integration/vault.ts index 41ac4a6..09ca209 100644 --- a/tests/integration/vault.ts +++ b/tests/integration/vault.ts @@ -318,6 +318,8 @@ describe("tokenized_vault", () => { profitMaxUnlockTime: new BN(0), kycVerifiedOnly: true, directDepositEnabled: true, + whitelistedOnly: true, + userDepositLimit: new BN(100000), }; const sharesConfig = { @@ -534,6 +536,26 @@ describe("tokenized_vault", () => { accessControlProgram.programId, )[0]; + await vaultProgram.methods.whitelist(user.publicKey) + .accounts({ + vault, + signer: admin.publicKey, + }) + .signers([admin]) + .rpc(); + + let user_data_addr = web3.PublicKey.findProgramAddressSync( + [ + Buffer.from("user_data"), + vault.toBuffer(), + user.publicKey.toBuffer(), + ], + vaultProgram.programId + )[0]; + + let user_data = await vaultProgram.account.userData.fetch(user_data_addr); + console.log("deposited: ", user_data.deposited.toString()); + await vaultProgram.methods.deposit(new BN(100)) .accounts({ vault, @@ -548,9 +570,62 @@ describe("tokenized_vault", () => { ]) .rpc(); + user_data = await vaultProgram.account.userData.fetch(user_data_addr); + console.log("deposited: ", user_data.deposited.toString()); + + const remainingAccountsMap = { + accountsMap: [ + { + strategyAcc: new BN(0), + strategyTokenAccount: new BN(1), + strategyData: new BN(2), + remainingAccounts: [new BN(0)], + }] + }; + const vaultAccount = await vaultProgram.account.vault.fetch(vault); console.log("Vault balance after deposit:", vaultAccount.totalDebt.toString()); + const strategyData = web3.PublicKey.findProgramAddressSync( + [Buffer.from("strategy_data"), vault.toBuffer(), strategy.toBuffer()], + vaultProgram.programId, + )[0]; + + await vaultProgram.methods.redeem(new BN(1), new BN(10000), remainingAccountsMap) + .accounts({ + vault, + user: user.publicKey, + userTokenAccount, + userSharesAccount, + }) + .remainingAccounts([ + { pubkey: strategy, isWritable: true, isSigner: false }, + { pubkey: strategyTokenAccount, isWritable: true, isSigner: false }, + { pubkey: strategyData, isWritable: true, isSigner: false }, + ]) + .signers([user]) + .rpc(); + + user_data = await vaultProgram.account.userData.fetch(user_data_addr); + console.log("deposited: ", user_data.deposited.toString()); + + await vaultProgram.methods.deposit(new BN(1)) + .accounts({ + vault, + // strategy, + user: user.publicKey, + userTokenAccount, + userSharesAccount, + }) + .signers([user]) + .remainingAccounts([ + { pubkey: kycVerified, isWritable: false, isSigner: false }, + ]) + .rpc(); + + user_data = await vaultProgram.account.userData.fetch(user_data_addr); + console.log("deposited: ", user_data.deposited.toString()); + // Fetch the vault token account balance to verify the deposit let vaultTokenAccountInfo = await token.getAccount(provider.connection, vaultTokenAccount); assert.strictEqual(vaultTokenAccountInfo.amount.toString(), '100'); From d7d10142a53c365fe9b70b0b285d121cabc3f1cc Mon Sep 17 00:00:00 2001 From: Vitalii Koval Date: Thu, 12 Dec 2024 15:38:53 +0400 Subject: [PATCH 2/3] fix kyc verified issue --- programs/tokenized_vault/src/instructions/deposit.rs | 2 +- programs/tokenized_vault/src/lib.rs | 2 +- programs/tokenized_vault/src/utils/vault.rs | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/programs/tokenized_vault/src/instructions/deposit.rs b/programs/tokenized_vault/src/instructions/deposit.rs index 554b378..f6110ef 100644 --- a/programs/tokenized_vault/src/instructions/deposit.rs +++ b/programs/tokenized_vault/src/instructions/deposit.rs @@ -59,7 +59,7 @@ pub struct Deposit<'info> { bump, seeds::program = access_control.key() )] - pub kyc_verified: Account<'info, UserRole>, + pub kyc_verified: UncheckedAccount<'info>, #[account(mut)] pub user: Signer<'info>, diff --git a/programs/tokenized_vault/src/lib.rs b/programs/tokenized_vault/src/lib.rs index 4ebcc65..5c2df6b 100644 --- a/programs/tokenized_vault/src/lib.rs +++ b/programs/tokenized_vault/src/lib.rs @@ -10,7 +10,7 @@ use anchor_lang::prelude::*; pub use state::{SharesConfig, VaultConfig}; pub use instructions::*; -declare_id!("HdQsT53sANBQmPb6xWRaZXUzAXydLteNsJW1Y6kJDbMm"); +declare_id!("5rcNAxHYxpmNBByNs9N9Te2Crf1a2KxixZqj6fzM3fY1"); #[program] pub mod tokenized_vault { diff --git a/programs/tokenized_vault/src/utils/vault.rs b/programs/tokenized_vault/src/utils/vault.rs index a28d0db..3c4d9a2 100644 --- a/programs/tokenized_vault/src/utils/vault.rs +++ b/programs/tokenized_vault/src/utils/vault.rs @@ -3,10 +3,11 @@ use anchor_lang::prelude::*; use crate::errors::ErrorCode; use crate::state::{UserData, Vault}; +use crate::utils::unchecked::*; pub fn validate_deposit<'info>( vault_loader: &AccountLoader<'info, Vault>, - kyc_verified: &Account<'info, UserRole>, + kyc_verified: &AccountInfo<'info>, user_data: &Account<'info, UserData>, is_direct: bool, amount: u64 @@ -38,7 +39,7 @@ pub fn validate_deposit<'info>( return Err(ErrorCode::ExceedDepositLimit.into()); } - if vault.kyc_verified_only && !kyc_verified.has_role { + if vault.kyc_verified_only && !kyc_verified.deserialize::()?.has_role { return Err(ErrorCode::KYCRequired.into()); } From fdbed8f60d77bd6ded8952c5d7a177d20babe8f0 Mon Sep 17 00:00:00 2001 From: Vitalii Koval Date: Thu, 12 Dec 2024 15:58:18 +0400 Subject: [PATCH 3/3] fix kyc verification issue --- programs/tokenized_vault/src/instructions/direct_deposit.rs | 2 +- programs/tokenized_vault/src/utils/vault.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/programs/tokenized_vault/src/instructions/direct_deposit.rs b/programs/tokenized_vault/src/instructions/direct_deposit.rs index 0bd1af1..44b8a49 100644 --- a/programs/tokenized_vault/src/instructions/direct_deposit.rs +++ b/programs/tokenized_vault/src/instructions/direct_deposit.rs @@ -87,7 +87,7 @@ pub struct DirectDeposit<'info> { bump, seeds::program = access_control.key() )] - pub kyc_verified: Account<'info, UserRole>, + pub kyc_verified: UncheckedAccount<'info>, #[account(mut)] pub user: Signer<'info>, diff --git a/programs/tokenized_vault/src/utils/vault.rs b/programs/tokenized_vault/src/utils/vault.rs index 3c4d9a2..4021231 100644 --- a/programs/tokenized_vault/src/utils/vault.rs +++ b/programs/tokenized_vault/src/utils/vault.rs @@ -39,8 +39,10 @@ pub fn validate_deposit<'info>( return Err(ErrorCode::ExceedDepositLimit.into()); } - if vault.kyc_verified_only && !kyc_verified.deserialize::()?.has_role { - return Err(ErrorCode::KYCRequired.into()); + if vault.kyc_verified_only { + if kyc_verified.data_is_empty() || !kyc_verified.deserialize::()?.has_role { + return Err(ErrorCode::KYCRequired.into()); + } } if vault.whitelisted_only && !user_data.whitelisted {