diff --git a/tests/cfo/programs/cfo/src/lib.rs b/tests/cfo/programs/cfo/src/lib.rs index 6a5e7dd7..57c8d93b 100644 --- a/tests/cfo/programs/cfo/src/lib.rs +++ b/tests/cfo/programs/cfo/src/lib.rs @@ -30,6 +30,7 @@ pub mod cfo { registrar: Pubkey, msrm_registrar: Pubkey, swap_interval: i64, + max_swap_usdc: u64, ) -> Result<()> { let officer = &mut ctx.accounts.officer; officer.authority = *ctx.accounts.authority.key; @@ -43,6 +44,7 @@ pub mod cfo { officer.srm_vault = *ctx.accounts.srm_vault.to_account_info().key; officer.bumps = bumps; officer.swap_interval = swap_interval; + officer.max_swap_usdc = max_swap_usdc; emit!(OfficerDidCreate { pubkey: *officer.to_account_info().key, }); @@ -114,18 +116,34 @@ pub mod cfo { ctx.accounts.market_auth.last_trade = Clock::get()?.unix_timestamp; // Trade. - let seeds = [ - ctx.accounts.dex_program.key.as_ref(), - &[ctx.accounts.officer.bumps.bump], - ]; - let cpi_ctx = CpiContext::from(&*ctx.accounts); - swap::cpi::swap( - cpi_ctx.with_signer(&[&seeds]), - swap::Side::Ask, - ctx.accounts.from_vault.amount, - min_exchange_rate.into(), - ) - .map_err(Into::into) + let amount_traded_usdc = { + // USDC before the swap. + let usdc_before = ctx.accounts.usdc_vault.amount; + + // Swap.. + let seeds = [ + ctx.accounts.dex_program.key.as_ref(), + &[ctx.accounts.officer.bumps.bump], + ]; + let cpi_ctx = CpiContext::from(&*ctx.accounts); + swap::cpi::swap( + cpi_ctx.with_signer(&[&seeds]), + swap::Side::Ask, + ctx.accounts.from_vault.amount, + min_exchange_rate.into(), + )?; + + // USDC after the swap. + ctx.accounts.usdc_vault.reload()?; + ctx.accounts.usdc_vault.amount - usdc_before + }; + + // Reject any swap over the max size. + if amount_traded_usdc > ctx.accounts.officer.max_swap_usdc { + return Err(ErrorCode::SwapTooLarge.into()); + } + + Ok(()) } /// Convert the CFO's entire token balance into SRM. @@ -151,10 +169,14 @@ pub mod cfo { swap::cpi::swap( cpi_ctx.with_signer(&[&seeds]), swap::Side::Bid, - ctx.accounts.usdc_vault.amount, + std::cmp::min( + ctx.accounts.usdc_vault.amount, + ctx.accounts.officer.max_swap_usdc, + ), min_exchange_rate.into(), - ) - .map_err(Into::into) + )?; + + Ok(()) } /// Distributes srm tokens to the various categories. Before calling this, @@ -694,6 +716,8 @@ pub struct Officer { pub authority: Pubkey, // Required trade interval in seconds. pub swap_interval: i64, + // Maximum amount of native USDC that can be swapped in one tx. + pub max_swap_usdc: u64, // Vault holding the officer's SRM tokens prior to distribution. pub srm_vault: Pubkey, // Escrow SRM vault holding tokens which are dropped onto stakers. @@ -935,6 +959,8 @@ pub enum ErrorCode { InsufficientStakeReward, #[msg("Swap interval must pass before another trade can occur")] SwapIntervalBlocked, + #[msg("Swap too large")] + SwapTooLarge, } // Access control. diff --git a/tests/cfo/tests/cfo.js b/tests/cfo/tests/cfo.js index 37579069..110d2045 100644 --- a/tests/cfo/tests/cfo.js +++ b/tests/cfo/tests/cfo.js @@ -28,6 +28,7 @@ const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey( ); const FEES = "6160355581"; const SWAP_INTERVAL = new anchor.BN(1); +const MAX_SWAP_USDC = new anchor.BN(50000000000); describe("cfo", () => { anchor.setProvider(anchor.Provider.env()); @@ -127,9 +128,23 @@ describe("cfo", () => { }); it("BOILERPLATE: Sets up the staking pools", async () => { - await setupStakePool(ORDERBOOK_ENV.mintA, ORDERBOOK_ENV.godA); - registrar = ORDERBOOK_ENV.usdc; - msrmRegistrar = registrar; + const { + registrar: _registrar, + rewardEventQ: _rewardEventQ, + poolMint: _poolMint, + } = await setupStakePool(ORDERBOOK_ENV.mintA, ORDERBOOK_ENV.godA); + const { + registrar: _msrmRegistrar, + rewardEventQ: _msrmRewardEventQ, + poolMint: _msrmPoolMint, + } = await setupStakePool(ORDERBOOK_ENV.mintB, ORDERBOOK_ENV.godB); + + registrar = _registrar; + rewardEventQ = _rewardEventQ; + poolMint = _poolMint; + msrmRegistrar = _msrmRegistrar; + msrmRewardEventQ = _msrmRewardEventQ; + msrmPoolMint = _msrmPoolMint; }); it("BOILERPLATE: Finds PDA addresses", async () => { @@ -243,6 +258,7 @@ describe("cfo", () => { registrar, msrmRegistrar, SWAP_INTERVAL, + MAX_SWAP_USDC, { accounts: { officer, diff --git a/tests/cfo/tests/utils/stake.js b/tests/cfo/tests/utils/stake.js index 844e01c6..d6f15b1b 100644 --- a/tests/cfo/tests/utils/stake.js +++ b/tests/cfo/tests/utils/stake.js @@ -161,6 +161,13 @@ async function setupStakePool(mint, god) { tokenProgram: TokenInstructions.TOKEN_PROGRAM_ID, }, }); + + return { + registrar: registrar.publicKey, + poolMint, + rewardEventQ: rewardQ.publicKey, + member, + }; } module.exports = {