Should distribute debt when a market's unrealized debt and realized debt are both 0.
The problem is that only when neither market's unrealized debt or realized debt is 0, the storage values are updated, as protocol assumes when both the debt values are 0, there is no debt update since the last time. However, this is a false assumption, it's possible that the market's realized debt changes from non-zero to zero (currrently in v1, the unrealized debt is alway 0).
pragma solidity 0.8.25;
import { Test, console } from "forge-std/Test.sol";
import "./Base.t.sol";
import { Errors } from "@zaros/utils/Errors.sol";
import { Vault } from "@zaros/market-making/leaves/Vault.sol";
import { Collateral } from "@zaros/market-making/leaves/Collateral.sol";
contract AuditTest is Base_Test {
address owner = makeAddr("Owner");
address feeRecipient = makeAddr("FeeRecipient");
function setUp() public override {
bool isTestnet = false;
address[] memory branches = deployPerpsEngineBranches(isTestnet);
bytes4[][] memory branchesSelectors = getPerpsEngineBranchesSelectors(isTestnet);
RootProxy.BranchUpgrade[] memory branchUpgrades =
getBranchUpgrades(branches, branchesSelectors, RootProxy.BranchUpgradeAction.Add);
address[] memory initializables = getInitializables(branches);
bytes[] memory initializePayloads = getInitializePayloads(owner);
branchUpgrades = deployPerpsEngineHarnesses(branchUpgrades);
RootProxy.InitParams memory initParams = RootProxy.InitParams({
initBranches: branchUpgrades,
initializables: initializables,
initializePayloads: initializePayloads
});
perpsEngine = IPerpsEngine(address(new MockEngine(initParams)));
address[] memory mmBranches = deployMarketMakingEngineBranches();
bytes4[][] memory mmBranchesSelectors = getMarketMakerBranchesSelectors();
RootProxy.BranchUpgrade[] memory mmBranchUpgrades =
getBranchUpgrades(mmBranches, mmBranchesSelectors, RootProxy.BranchUpgradeAction.Add);
RootProxy.BranchUpgrade[] memory mmbranchUpgrades = new RootProxy.BranchUpgrade[](mmBranchUpgrades.length);
for (uint256 i; i < mmbranchUpgrades.length; i++) {
mmbranchUpgrades[i] = mmBranchUpgrades[i];
}
initializables = getInitializables(mmBranches);
initializePayloads = getInitializePayloads(owner);
RootProxy.InitParams memory mmEngineInitParams = RootProxy.InitParams({
initBranches: mmbranchUpgrades,
initializables: initializables,
initializePayloads: initializePayloads
});
marketMakingEngine = IMarketMakingEngine(address(new MarketMakingEngine(mmEngineInitParams)));
vm.startPrank(owner);
uint256[2] memory marginCollateralIdsRange;
marginCollateralIdsRange[0] = INITIAL_MARGIN_COLLATERAL_ID;
marginCollateralIdsRange[1] = FINAL_MARGIN_COLLATERAL_ID;
mockSequencerUptimeFeed = address(new MockSequencerUptimeFeed(0));
configureMarginCollaterals(
IPerpsEngine(perpsEngine), marginCollateralIdsRange, true, mockSequencerUptimeFeed, owner
);
usdc = MockERC20(marginCollaterals[USDC_MARGIN_COLLATERAL_ID].marginCollateralAddress);
usdToken = MockUsdToken(marginCollaterals[USD_TOKEN_MARGIN_COLLATERAL_ID].marginCollateralAddress);
weEth = MockERC20(marginCollaterals[WEETH_MARGIN_COLLATERAL_ID].marginCollateralAddress);
wstEth = MockERC20(marginCollaterals[WSTETH_MARGIN_COLLATERAL_ID].marginCollateralAddress);
wEth = MockERC20(marginCollaterals[WETH_MARGIN_COLLATERAL_ID].marginCollateralAddress);
wBtc = MockERC20(marginCollaterals[WBTC_MARGIN_COLLATERAL_ID].marginCollateralAddress);
marketMakingEngine.configureCollateral(
address(usdc),
marginCollaterals[USDC_MARGIN_COLLATERAL_ID].priceAdapter,
MOCK_PERP_CREDIT_CONFIG_DEBT_CREDIT_RATIO,
true,
marginCollaterals[USDC_MARGIN_COLLATERAL_ID].tokenDecimals
);
marketMakingEngine.configureCollateral(
address(usdToken),
marginCollaterals[USD_TOKEN_MARGIN_COLLATERAL_ID].priceAdapter,
MOCK_PERP_CREDIT_CONFIG_DEBT_CREDIT_RATIO,
true,
marginCollaterals[USD_TOKEN_MARGIN_COLLATERAL_ID].tokenDecimals
);
marketMakingEngine.configureCollateral(
address(wEth),
marginCollaterals[WETH_MARGIN_COLLATERAL_ID].priceAdapter,
MOCK_PERP_CREDIT_CONFIG_DEBT_CREDIT_RATIO,
true,
marginCollaterals[WETH_MARGIN_COLLATERAL_ID].tokenDecimals
);
marketMakingEngine.setWeth(address(wEth));
marketMakingEngine.configureEngine(address(perpsEngine), address(usdToken), true);
marketMakingEngine.configureVaultDepositAndRedeemFeeRecipient(feeRecipient);
uint256[2] memory vaultsIdsRange;
vaultsIdsRange[0] = INITIAL_VAULT_ID;
vaultsIdsRange[1] = FINAL_VAULT_ID;
setupVaultsConfig();
createZlpVaults(address(marketMakingEngine), owner, vaultsIdsRange);
bool isTest = true;
setupPerpMarketsCreditConfig(isTest, address(perpsEngine), address(usdToken));
vm.stopPrank();
}
function testAudit_MarketDebt() public {
vm.startPrank(owner);
uint128 vaultId = WETH_CORE_VAULT_ID;
address indexToken = address(zlpVaults[vaultsConfig[vaultId].asset][vaultsConfig[vaultId].vaultType]);
ZlpVault zlpVault = ZlpVault(indexToken);
Collateral.Data memory collateral = Collateral.Data({
creditRatio: vaultsConfig[vaultId].creditRatio,
priceAdapter: vaultsConfig[vaultId].priceAdapter,
asset: vaultsConfig[vaultId].asset,
isEnabled: vaultsConfig[vaultId].isEnabled,
decimals: vaultsConfig[vaultId].decimals
});
Vault.CreateParams memory vaultCreatParams = Vault.CreateParams({
depositFee: vaultsConfig[vaultId].depositFee,
redeemFee: vaultsConfig[vaultId].redeemFee,
vaultId: vaultsConfig[vaultId].vaultId,
depositCap: vaultsConfig[vaultId].depositCap,
withdrawalDelay: vaultsConfig[vaultId].withdrawalDelay,
indexToken: indexToken,
engine: address(perpsEngine),
collateral: collateral
});
marketMakingEngine.createVault(vaultCreatParams);
Vault.UpdateParams memory vaultUpdateParams = Vault.UpdateParams({
vaultId: vaultId,
depositCap: vaultCreatParams.depositCap,
withdrawalDelay: vaultCreatParams.withdrawalDelay,
isLive: true,
lockedCreditRatio: 0
});
marketMakingEngine.updateVaultConfiguration(vaultUpdateParams);
uint256 depositAmount = 2e18;
wEth.mint(owner, depositAmount);
wEth.approve(address(marketMakingEngine), depositAmount);
marketMakingEngine.deposit(vaultsConfig[vaultId].vaultId, uint128(depositAmount), 0, "", false);
uint128 marketCreditConfigId = ETH_PERP_MARKET_CREDIT_CONFIG_ID;
uint128 marketId = perpMarketsCreditConfig[marketCreditConfigId].marketId;
marketMakingEngine.configureMarket(
address(perpsEngine),
perpMarketsCreditConfig[marketCreditConfigId].marketId,
perpMarketsCreditConfig[marketCreditConfigId].autoDeleverageStartThreshold,
perpMarketsCreditConfig[marketCreditConfigId].autoDeleverageEndThreshold,
perpMarketsCreditConfig[marketCreditConfigId].autoDeleverageExpoentZ
);
marketMakingEngine.unpauseMarket(marketId);
uint256[] memory marketIds = new uint256[](1);
marketIds[0] = marketId;
uint256[] memory vaultIds = new uint256[](1);
vaultIds[0] = vaultsConfig[vaultId].vaultId;
marketMakingEngine.connectVaultsAndMarkets(marketIds, vaultIds);
marketMakingEngine.updateVaultCreditCapacity(vaultsConfig[vaultId].vaultId);
vm.stopPrank();
vm.startPrank(address(perpsEngine));
wEth.mint(address(perpsEngine), 1 ether);
wEth.approve(address(marketMakingEngine), 1 ether);
marketMakingEngine.depositCreditForMarket(marketId, address(wEth), 1 ether);
vm.stopPrank();
int256 realizedDebt = marketMakingEngine.getMarketRealizedDebt(marketId);
assertEq(realizedDebt, 2000e18);
vm.prank(owner);
usdToken.mint(address(perpsEngine), 2000e18);
vm.startPrank(address(perpsEngine));
usdToken.approve(address(marketMakingEngine), 2000e18);
marketMakingEngine.depositCreditForMarket(marketId, address(usdToken), 2000e18);
vm.stopPrank();
realizedDebt = marketMakingEngine.getMarketRealizedDebt(marketId);
assertEq(realizedDebt, 0);
}
}
Incorrect accounting of market debt.
Distribute debt when market's unrealized debt and realized debt are both 0.