Skip to content

Commit e8bfda7

Browse files
authored
Swap limit bug fix (#221)
* skipping liquidity check when swapping from perps to underlying * code review fixes
1 parent ffc90a8 commit e8bfda7

File tree

2 files changed

+39
-23
lines changed

2 files changed

+39
-23
lines changed

spot-contracts/contracts/RolloverVault.sol

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -440,8 +440,9 @@ contract RolloverVault is
440440
function swapUnderlyingForPerps(uint256 underlyingAmtIn) external nonReentrant whenNotPaused returns (uint256) {
441441
// Calculates the fee adjusted perp amount to transfer to the user.
442442
// NOTE: This operation should precede any token transfers.
443-
IERC20Upgradeable underlying_ = underlying;
444443
IPerpetualTranche perp_ = perp;
444+
IERC20Upgradeable underlying_ = underlying;
445+
uint256 underlyingBalPre = underlying_.balanceOf(address(this));
445446
(uint256 perpAmtOut, uint256 perpFeeAmtToBurn, SubscriptionParams memory s) = computeUnderlyingToPerpSwapAmt(
446447
underlyingAmtIn
447448
);
@@ -468,8 +469,16 @@ contract RolloverVault is
468469
// NOTE: In case this operation mints slightly more perps than that are required for the swap,
469470
// The vault continues to hold the perp dust until the subsequent `swapPerpsForUnderlying` or manual `recover(perp)`.
470471

471-
// Revert if vault liquidity is too low.
472-
_enforceUnderlyingBalAfterSwap(underlying_, s.vaultTVL);
472+
// If vault liquidity has reduced, revert if it reduced too much.
473+
// - Absolute balance is strictly greater than `minUnderlyingBal`.
474+
// - Ratio of the balance to the vault's TVL is strictly greater than `minUnderlyingPerc`.
475+
uint256 underlyingBalPost = underlying_.balanceOf(address(this));
476+
if (
477+
underlyingBalPost < underlyingBalPre &&
478+
(underlyingBalPost <= minUnderlyingBal || underlyingBalPost.mulDiv(ONE, s.vaultTVL) <= minUnderlyingPerc)
479+
) {
480+
revert InsufficientLiquidity();
481+
}
473482

474483
// sync underlying
475484
_syncAsset(underlying_);
@@ -482,11 +491,8 @@ contract RolloverVault is
482491
// Calculates the fee adjusted underlying amount to transfer to the user.
483492
IPerpetualTranche perp_ = perp;
484493
IERC20Upgradeable underlying_ = underlying;
485-
(
486-
uint256 underlyingAmtOut,
487-
uint256 perpFeeAmtToBurn,
488-
SubscriptionParams memory s
489-
) = computePerpToUnderlyingSwapAmt(perpAmtIn);
494+
uint256 underlyingBalPre = underlying_.balanceOf(address(this));
495+
(uint256 underlyingAmtOut, uint256 perpFeeAmtToBurn, ) = computePerpToUnderlyingSwapAmt(perpAmtIn);
490496

491497
// Revert if insufficient tokens are swapped in or out
492498
if (underlyingAmtOut <= 0 || perpAmtIn <= 0) {
@@ -507,8 +513,11 @@ contract RolloverVault is
507513
// transfer underlying out
508514
underlying_.safeTransfer(msg.sender, underlyingAmtOut);
509515

510-
// Revert if vault liquidity is too low.
511-
_enforceUnderlyingBalAfterSwap(underlying_, s.vaultTVL);
516+
// Revert if swap reduces vault's available liquidity.
517+
uint256 underlyingBalPost = underlying_.balanceOf(address(this));
518+
if (underlyingBalPost < underlyingBalPre) {
519+
revert InsufficientLiquidity();
520+
}
512521

513522
// sync underlying
514523
_syncAsset(underlying_);
@@ -964,15 +973,4 @@ contract RolloverVault is
964973
(uint256 trancheClaim, uint256 trancheSupply) = tranche.getTrancheCollateralization(collateralToken);
965974
return trancheClaim.mulDiv(trancheAmt, trancheSupply, MathUpgradeable.Rounding.Up);
966975
}
967-
968-
/// @dev Checks if the vault's underlying balance is above admin defined constraints.
969-
/// - Absolute balance is strictly greater than `minUnderlyingBal`.
970-
/// - Ratio of the balance to the vault's TVL is strictly greater than `minUnderlyingPerc`.
971-
/// NOTE: We assume the vault TVL and the underlying to have the same base denomination.
972-
function _enforceUnderlyingBalAfterSwap(IERC20Upgradeable underlying_, uint256 vaultTVL) private view {
973-
uint256 underlyingBal = underlying_.balanceOf(address(this));
974-
if (underlyingBal <= minUnderlyingBal || underlyingBal.mulDiv(ONE, vaultTVL) <= minUnderlyingPerc) {
975-
revert InsufficientLiquidity();
976-
}
977-
}
978976
}

spot-contracts/test/rollover-vault/RolloverVault_swap.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ describe("RolloverVault", function () {
114114
);
115115
expect(await vault.assetCount()).to.eq(2);
116116

117-
await collateralToken.approve(vault.address, toFixedPtAmt("1000"));
118-
await perp.approve(vault.address, toFixedPtAmt("1000"));
117+
await collateralToken.approve(vault.address, toFixedPtAmt("10000"));
118+
await perp.approve(vault.address, toFixedPtAmt("10000"));
119119
});
120120

121121
afterEach(async function () {
@@ -1260,5 +1260,23 @@ describe("RolloverVault", function () {
12601260
expect(await vault.callStatic.getTVL()).to.eq(toFixedPtAmt("4434.6153846153846152"));
12611261
});
12621262
});
1263+
1264+
describe("when vault reduces underlying liquidity", function () {
1265+
it("should be reverted", async function () {
1266+
await feePolicy.computePerpBurnFeePerc.returns(toPercFixedPtAmt("0.1"));
1267+
await feePolicy.computePerpToUnderlyingVaultSwapFeePerc.returns(toPercFixedPtAmt("0.15"));
1268+
await vault.swapPerpsForUnderlying(toFixedPtAmt("800"));
1269+
1270+
const bond = await getDepositBond(perp);
1271+
const tranches = await getTranches(bond);
1272+
await depositIntoBond(bond, toFixedPtAmt("1000"), deployer);
1273+
await tranches[0].approve(perp.address, toFixedPtAmt("200"));
1274+
await perp.deposit(tranches[0].address, toFixedPtAmt("200"));
1275+
await expect(vault.swapPerpsForUnderlying(toFixedPtAmt("1"))).to.be.revertedWithCustomError(
1276+
vault,
1277+
"InsufficientLiquidity",
1278+
);
1279+
});
1280+
});
12631281
});
12641282
});

0 commit comments

Comments
 (0)