diff --git a/client/src/client.rs b/client/src/client.rs index 918a9eb87..65ca11283 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1275,6 +1275,8 @@ impl MangoClient { accounts: { let mut ams = anchor_lang::ToAccountMetas::to_account_metas( &mango_v4::accounts::FlashLoanBegin { + account: self.mango_account_address, + owner: self.owner(), token_program: Token::id(), instructions: solana_sdk::sysvar::instructions::id(), }, diff --git a/programs/mango-v4/src/instructions/flash_loan.rs b/programs/mango-v4/src/instructions/flash_loan.rs index 3c4328533..a9b77dbec 100644 --- a/programs/mango-v4/src/instructions/flash_loan.rs +++ b/programs/mango-v4/src/instructions/flash_loan.rs @@ -11,9 +11,19 @@ use crate::util::checked_math as cm; use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; use anchor_lang::Discriminator; +use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{self, Token, TokenAccount}; use fixed::types::I80F48; +pub mod jupiter_mainnet_4 { + use solana_program::declare_id; + declare_id!("JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB"); +} +pub mod jupiter_mainnet_3 { + use solana_program::declare_id; + declare_id!("JUP3c2Uh3WA4Ng34tw6kPd2G4C5BB21Xo36Je1s32Ph"); +} + /// Sets up mango vaults for flash loan /// /// In addition to these accounts, there must be remaining_accounts: @@ -24,6 +34,10 @@ use fixed::types::I80F48; /// 4. the mango group #[derive(Accounts)] pub struct FlashLoanBegin<'info> { + pub account: AccountLoaderDynamic<'info, MangoAccount>, + // owner is checked at #1 + pub owner: Signer<'info>, + pub token_program: Program<'info, Token>, /// Instructions Sysvar for instruction introspection @@ -42,11 +56,9 @@ pub struct FlashLoanBegin<'info> { /// 4. the mango group #[derive(Accounts)] pub struct FlashLoanEnd<'info> { - #[account( - mut, - has_one = owner - )] + #[account(mut)] pub account: AccountLoaderDynamic<'info, MangoAccount>, + // owner is checked at #1 pub owner: Signer<'info>, pub token_program: Program<'info, Token>, @@ -65,6 +77,14 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( ctx: Context<'key, 'accounts, 'remaining, 'info, FlashLoanBegin<'info>>, loan_amounts: Vec, ) -> Result<()> { + let account = ctx.accounts.account.load_mut()?; + + // account constraint #1 + require!( + account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), + MangoError::SomeError + ); + let num_loans = loan_amounts.len(); require_eq!(ctx.remaining_accounts.len(), 3 * num_loans + 1); let banks = &ctx.remaining_accounts[..num_loans]; @@ -156,6 +176,15 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( Err(e) => return Err(e.into()), }; + if account.fixed.is_delegate(ctx.accounts.owner.key()) { + require_msg!( + ix.program_id == AssociatedToken::id() + || ix.program_id == jupiter_mainnet_3::ID + || ix.program_id == jupiter_mainnet_4::ID, + "delegate is only allowed to pass in ixs to ATA or Jupiter v3 or v4 programs" + ); + } + // Check that the mango program key is not used if ix.program_id == crate::id() { // must be the last mango ix -- this could possibly be relaxed, but right now @@ -172,6 +201,11 @@ pub fn flash_loan_begin<'key, 'accounts, 'remaining, 'info>( MangoError::SomeError ); + require_msg!( + ctx.accounts.account.key() == ix.accounts[0].pubkey, + "the mango account passed to FlashLoanBegin and End must match" + ); + // check that the same vaults and token accounts are passed let begin_accounts = &ctx.remaining_accounts[num_loans..]; let end_accounts = &ix.accounts[ix.accounts.len() - begin_accounts.len()..]; @@ -208,6 +242,13 @@ pub fn flash_loan_end<'key, 'accounts, 'remaining, 'info>( flash_loan_type: FlashLoanType, ) -> Result<()> { let mut account = ctx.accounts.account.load_mut()?; + + // account constraint #1 + require!( + account.fixed.is_owner_or_delegate(ctx.accounts.owner.key()), + MangoError::SomeError + ); + let group = account.fixed.group; let remaining_len = ctx.remaining_accounts.len(); diff --git a/programs/mango-v4/src/state/mango_account.rs b/programs/mango-v4/src/state/mango_account.rs index aa54bc7d4..a46d30758 100644 --- a/programs/mango-v4/src/state/mango_account.rs +++ b/programs/mango-v4/src/state/mango_account.rs @@ -220,6 +220,10 @@ impl MangoAccountFixed { self.owner == ix_signer || self.delegate == ix_signer } + pub fn is_delegate(&self, ix_signer: Pubkey) -> bool { + self.delegate == ix_signer + } + pub fn being_liquidated(&self) -> bool { self.being_liquidated == 1 } diff --git a/programs/mango-v4/tests/program_test/mango_client.rs b/programs/mango-v4/tests/program_test/mango_client.rs index ac9339d84..7e441d34b 100644 --- a/programs/mango-v4/tests/program_test/mango_client.rs +++ b/programs/mango-v4/tests/program_test/mango_client.rs @@ -367,7 +367,9 @@ pub async fn check_prev_instruction_post_health(solana: &SolanaCookie, account: // pub struct FlashLoanBeginInstruction { + pub account: Pubkey, pub group: Pubkey, + pub owner: TestKeypair, pub mango_token_bank: Pubkey, pub mango_token_vault: Pubkey, pub target_token_account: Pubkey, @@ -384,6 +386,8 @@ impl ClientInstruction for FlashLoanBeginInstruction { let program_id = mango_v4::id(); let accounts = Self::Accounts { + account: self.account, + owner: self.owner.pubkey(), token_program: Token::id(), instructions: solana_program::sysvar::instructions::id(), }; diff --git a/programs/mango-v4/tests/test_margin_trade.rs b/programs/mango-v4/tests/test_margin_trade.rs index 65bc68111..88d5fa104 100644 --- a/programs/mango-v4/tests/test_margin_trade.rs +++ b/programs/mango-v4/tests/test_margin_trade.rs @@ -123,6 +123,8 @@ async fn test_margin_trade() -> Result<(), BanksClientError> { let send_flash_loan_tx = |solana, withdraw_amount, deposit_amount| async move { let mut tx = ClientTransaction::new(solana); tx.add_instruction(FlashLoanBeginInstruction { + account, + owner, group, mango_token_bank: bank, mango_token_vault: vault, diff --git a/ts/client/src/accounts/healthCache.spec.ts b/ts/client/src/accounts/healthCache.spec.ts index 276f1e6df..65f47cb23 100644 --- a/ts/client/src/accounts/healthCache.spec.ts +++ b/ts/client/src/accounts/healthCache.spec.ts @@ -203,7 +203,7 @@ describe('Health Cache', () => { 0, new BN(0), new BN(0), - new BN(0), + new BN(0), ); const pi1 = PerpInfo.fromPerpPosition(pM, pp); diff --git a/ts/client/src/client.ts b/ts/client/src/client.ts index c3da83477..596d5909f 100644 --- a/ts/client/src/client.ts +++ b/ts/client/src/client.ts @@ -2052,7 +2052,6 @@ export class MangoClient { .flashLoanEnd(flashLoanType) .accounts({ account: mangoAccount.publicKey, - owner: (this.program.provider as AnchorProvider).wallet.publicKey, }) .remainingAccounts([ ...parsedHealthAccounts, @@ -2076,6 +2075,8 @@ export class MangoClient { ) /* we don't care about borrowing the target amount, this is just a dummy */, ]) .accounts({ + account: mangoAccount.publicKey, + owner: (this.program.provider as AnchorProvider).wallet.publicKey, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, }) .remainingAccounts([ diff --git a/ts/client/src/mango_v4.ts b/ts/client/src/mango_v4.ts index 4f404b09f..e32afc98f 100644 --- a/ts/client/src/mango_v4.ts +++ b/ts/client/src/mango_v4.ts @@ -1239,6 +1239,16 @@ export type MangoV4 = { { "name": "flashLoanBegin", "accounts": [ + { + "name": "account", + "isMut": false, + "isSigner": false + }, + { + "name": "owner", + "isMut": false, + "isSigner": true + }, { "name": "tokenProgram", "isMut": false, @@ -8058,6 +8068,16 @@ export const IDL: MangoV4 = { { "name": "flashLoanBegin", "accounts": [ + { + "name": "account", + "isMut": false, + "isSigner": false + }, + { + "name": "owner", + "isMut": false, + "isSigner": true + }, { "name": "tokenProgram", "isMut": false,