Armani Ferrante 4 years ago
parent
commit
31ceb4e22e
2 changed files with 67 additions and 12 deletions
  1. 62 11
      tests/cfo/programs/cfo/src/lib.rs
  2. 5 1
      tests/cfo/tests/cfo.js

+ 62 - 11
tests/cfo/programs/cfo/src/lib.rs

@@ -29,6 +29,7 @@ pub mod cfo {
         d: Distribution,
         registrar: Pubkey,
         msrm_registrar: Pubkey,
+        swap_interval: i64,
     ) -> Result<()> {
         let officer = &mut ctx.accounts.officer;
         officer.authority = *ctx.accounts.authority.key;
@@ -41,6 +42,7 @@ pub mod cfo {
         officer.treasury = *ctx.accounts.treasury.to_account_info().key;
         officer.srm_vault = *ctx.accounts.srm_vault.to_account_info().key;
         officer.bumps = bumps;
+        officer.swap_interval = swap_interval;
         emit!(OfficerDidCreate {
             pubkey: *officer.to_account_info().key,
         });
@@ -50,6 +52,7 @@ pub mod cfo {
     /// Creates a market authorization token.
     pub fn authorize_market(ctx: Context<AuthorizeMarket>, bump: u8) -> Result<()> {
         ctx.accounts.market_auth.bump = bump;
+        ctx.accounts.market_auth.last_trade = Clock::get()?.unix_timestamp;
         Ok(())
     }
 
@@ -98,11 +101,19 @@ pub mod cfo {
 
     /// Convert the CFO's entire non-SRM token balance into USDC.
     /// Assumes USDC is the quote currency.
-    #[access_control(is_not_trading(&ctx.accounts.instructions))]
+    #[access_control(can_swap(
+				&ctx.accounts.officer,
+				&ctx.accounts.market_auth,
+				&ctx.accounts.instructions,
+		))]
     pub fn swap_to_usdc<'info>(
         ctx: Context<'_, '_, '_, 'info, SwapToUsdc<'info>>,
         min_exchange_rate: ExchangeRate,
     ) -> Result<()> {
+        // Update last trade ts.
+        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],
@@ -119,11 +130,19 @@ pub mod cfo {
 
     /// Convert the CFO's entire token balance into SRM.
     /// Assumes SRM is the base currency.
-    #[access_control(is_not_trading(&ctx.accounts.instructions))]
+    #[access_control(can_swap(
+				&ctx.accounts.officer,
+				&ctx.accounts.market_auth,
+				&ctx.accounts.instructions,
+		))]
     pub fn swap_to_srm<'info>(
         ctx: Context<'_, '_, '_, 'info, SwapToSrm<'info>>,
         min_exchange_rate: ExchangeRate,
     ) -> Result<()> {
+        // Update last trade ts.
+        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],
@@ -156,7 +175,9 @@ pub mod cfo {
             .unwrap()
             .try_into()
             .map_err(|_| ErrorCode::U128CannotConvert)?;
-        token::burn(ctx.accounts.into_burn().with_signer(&[&seeds]), burn_amount)?;
+        if burn_amount > 0 {
+            token::burn(ctx.accounts.into_burn().with_signer(&[&seeds]), burn_amount)?;
+        }
 
         // Stake.
         let stake_amount: u64 = u128::from(total_fees)
@@ -166,10 +187,12 @@ pub mod cfo {
             .unwrap()
             .try_into()
             .map_err(|_| ErrorCode::U128CannotConvert)?;
-        token::transfer(
-            ctx.accounts.into_stake_transfer().with_signer(&[&seeds]),
-            stake_amount,
-        )?;
+        if stake_amount > 0 {
+            token::transfer(
+                ctx.accounts.into_stake_transfer().with_signer(&[&seeds]),
+                stake_amount,
+            )?;
+        }
 
         // Treasury.
         let treasury_amount: u64 = u128::from(total_fees)
@@ -179,10 +202,12 @@ pub mod cfo {
             .unwrap()
             .try_into()
             .map_err(|_| ErrorCode::U128CannotConvert)?;
-        token::transfer(
-            ctx.accounts.into_treasury_transfer().with_signer(&[&seeds]),
-            treasury_amount,
-        )?;
+        if treasury_amount > 0 {
+            token::transfer(
+                ctx.accounts.into_treasury_transfer().with_signer(&[&seeds]),
+                treasury_amount,
+            )?;
+        }
 
         Ok(())
     }
@@ -667,6 +692,8 @@ pub struct DropStakeRewardPool<'info> {
 pub struct Officer {
     // Priviledged account.
     pub authority: Pubkey,
+    // Required trade interval in seconds.
+    pub swap_interval: i64,
     // Vault holding the officer's SRM tokens prior to distribution.
     pub srm_vault: Pubkey,
     // Escrow SRM vault holding tokens which are dropped onto stakers.
@@ -701,6 +728,9 @@ pub struct Officer {
 #[account]
 #[derive(Default)]
 pub struct MarketAuth {
+    // Unix timestamp of hte last time the program traded on this market.
+    // Used to help ensure the program doesn't have too much market impact.
+    pub last_trade: i64,
     // Bump seed for this account's PDA.
     pub bump: u8,
 }
@@ -903,6 +933,8 @@ pub enum ErrorCode {
     InsufficientDistributionAmount,
     #[msg("Must drop more SRM onto the stake pool")]
     InsufficientStakeReward,
+    #[msg("Swap interval must pass before another trade can occur")]
+    SwapIntervalBlocked,
 }
 
 // Access control.
@@ -921,6 +953,25 @@ fn is_distribution_ready(accounts: &Distribute) -> Result<()> {
     Ok(())
 }
 
+// Asserts the given accounts can swap in this transaction.
+fn can_swap(officer: &Officer, auth: &MarketAuth, ixs: &UncheckedAccount) -> Result<()> {
+    is_swap_interval_elapsed(officer, auth)?;
+    is_not_trading(ixs)?;
+    Ok(())
+}
+
+fn is_swap_interval_elapsed(officer: &Officer, auth: &MarketAuth) -> Result<()> {
+    if Clock::get()?
+        .unix_timestamp
+        .checked_sub(auth.last_trade)
+        .unwrap()
+        < officer.swap_interval
+    {
+        return Err(ErrorCode::SwapIntervalBlocked.into());
+    }
+    Ok(())
+}
+
 // `ixs` must be the Instructions sysvar.
 fn is_not_trading(ixs: &UncheckedAccount) -> Result<()> {
     let data = ixs.try_borrow_data()?;

+ 5 - 1
tests/cfo/tests/cfo.js

@@ -27,6 +27,7 @@ const SYSVAR_INSTRUCTIONS_PUBKEY = new PublicKey(
   "Sysvar1nstructions1111111111111111111111111"
 );
 const FEES = "6160355581";
+const SWAP_INTERVAL = new anchor.BN(1);
 
 describe("cfo", () => {
   anchor.setProvider(anchor.Provider.env());
@@ -240,7 +241,8 @@ describe("cfo", () => {
       bumps,
       distribution,
       registrar,
-      msrmRegistrar,
+			msrmRegistrar,
+			SWAP_INTERVAL,
       {
         accounts: {
           officer,
@@ -367,6 +369,8 @@ describe("cfo", () => {
         systemProgram: SystemProgram.programId,
       },
     });
+		// Let the "last trade interval" elapse.
+		await serumCmn.sleep(1000);
   });
 
   it("Transfers into the mintB vault", async () => {