Skip to content

Commit 4892ef8

Browse files
authored
Ensure returned accountValueIssued never negative in #_executePositionTrades (#189)
1 parent 62def3e commit 4892ef8

File tree

2 files changed

+93
-0
lines changed

2 files changed

+93
-0
lines changed

contracts/protocol/modules/PerpV2LeverageModule.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,12 @@ contract PerpV2LeverageModule is ModuleBase, ReentrancyGuard, Ownable, SetTokenA
772772
accountValueIssued.sub(deltaQuote.toInt256());
773773
}
774774

775+
// After trading, verify that accountValueIssued is not negative. In some post-liquidation states the
776+
// account could be bankrupt and we represent that as zero.
777+
if (accountValueIssued <= 0) {
778+
return 0;
779+
}
780+
775781
// Return value in collateral decimals (e.g USDC = 6)
776782
// Use preciseDivCeil when issuing to ensure we don't under-collateralize due to rounding error
777783
return (_isIssue)

test/integration/perpV2LeverageSlippageIssuance.spec.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,69 @@ describe("PerpV2LeverageSlippageIssuance", () => {
669669
expect(toUSDCDecimals(finalCollateralBalance)).to.be.closeTo(expectedCollateralBalance, 2);
670670
});
671671
});
672+
673+
describe("when liquidation results in negative account value", () => {
674+
beforeEach(async () => {
675+
subjectQuantity = ether(1);
676+
677+
// Calculated leverage = ~8.5X = 8_654_438_822_995_683_587
678+
await leverUp(
679+
setToken,
680+
perpLeverageModule,
681+
perpSetup,
682+
owner,
683+
baseToken,
684+
6,
685+
ether(.02),
686+
true
687+
);
688+
689+
// Move oracle price down to 5 USDC to enable liquidation
690+
await perpSetup.setBaseTokenOraclePrice(vETH, usdcUnits(5));
691+
692+
// Move price down by maker selling 20_000 USDC of vETH
693+
// Post trade spot price drops from ~10 USDC to 6_380_562_015_950_425_028
694+
await perpSetup.clearingHouse.connect(maker.wallet).openPosition({
695+
baseToken: vETH.address,
696+
isBaseToQuote: true, // short
697+
isExactInput: false, // `amount` is USDC
698+
amount: ether(20_000),
699+
oppositeAmountBound: ZERO,
700+
deadline: MAX_UINT_256,
701+
sqrtPriceLimitX96: ZERO,
702+
referralCode: ZERO_BYTES
703+
});
704+
705+
await perpSetup
706+
.clearingHouse
707+
.connect(otherTrader.wallet)
708+
.liquidate(subjectSetToken, baseToken);
709+
});
710+
711+
// In this test case, the account is bankrupt:
712+
// collateralBalance = 10050000000000000000
713+
// owedRealizedPnl = -31795534271984084912
714+
it("should issue without transferring any usdc (because account worth 0)", async () => {
715+
const issueQuantityWithFees = (await slippageIssuanceModule.calculateTotalFees(
716+
subjectSetToken,
717+
subjectQuantity,
718+
true
719+
))[0];
720+
721+
const initialIssuerUSDCBalance = await usdc.balanceOf(subjectCaller.address);
722+
const initialTotalSupply = await setToken.totalSupply();
723+
724+
await subject();
725+
726+
const finalIssuerUSDCBalance = await usdc.balanceOf(subjectCaller.address);
727+
const finalTotalSupply = await setToken.totalSupply();
728+
729+
const expectedTotalSupply = initialTotalSupply.add(issueQuantityWithFees);
730+
731+
expect(finalTotalSupply).eq(expectedTotalSupply);
732+
expect(finalIssuerUSDCBalance).eq(initialIssuerUSDCBalance);
733+
});
734+
});
672735
});
673736
});
674737

@@ -1209,6 +1272,30 @@ describe("PerpV2LeverageSlippageIssuance", () => {
12091272
.liquidate(subjectSetToken, baseToken);
12101273
});
12111274

1275+
// In this test case, the account is bankrupt:
1276+
// collateralBalance = 10050000000000000000
1277+
// owedRealizedPnl = -31795534271984084912
1278+
it("should redeem without transferring any usdc (because account worth 0)", async () => {
1279+
const redeemQuantityWithFees = (await slippageIssuanceModule.calculateTotalFees(
1280+
subjectSetToken,
1281+
subjectQuantity,
1282+
false
1283+
))[0];
1284+
1285+
const initialRedeemerUSDCBalance = await usdc.balanceOf(subjectCaller.address);
1286+
const initialTotalSupply = await setToken.totalSupply();
1287+
1288+
await subject();
1289+
1290+
const finalRedeemerUSDCBalance = await usdc.balanceOf(subjectCaller.address);
1291+
const finalTotalSupply = await setToken.totalSupply();
1292+
1293+
const expectedTotalSupply = initialTotalSupply.sub(redeemQuantityWithFees);
1294+
1295+
expect(finalTotalSupply).eq(expectedTotalSupply);
1296+
expect(finalRedeemerUSDCBalance).eq(initialRedeemerUSDCBalance);
1297+
});
1298+
12121299
it("should be possible to remove the module", async () => {
12131300
await subject();
12141301

0 commit comments

Comments
 (0)