This will result in division by zero panic when calculating premiumDiscountFactor:
import { IMarketMakingEngine } from "@zaros/market-making/MarketMakingEngine.sol";
import { ZlpVault } from "@zaros/zlp/ZlpVault.sol";
import { Vault } from "@zaros/market-making/leaves/Vault.sol";
import { Market } from "@zaros/market-making/leaves/Market.sol";
import { CreditDelegation } from "@zaros/market-making/leaves/CreditDelegation.sol";
import { Distribution } from "@zaros/market-making/leaves/Distribution.sol";
import { MarketMakingEngineConfiguration } from "@zaros/market-making/leaves/MarketMakingEngineConfiguration.sol";
import { LiveMarkets } from "@zaros/market-making/leaves/LiveMarkets.sol";
import { Collateral } from "@zaros/market-making/leaves/Collateral.sol";
import { StabilityConfiguration } from "@zaros/market-making/leaves/StabilityConfiguration.sol";
import { UsdTokenSwapConfig } from "@zaros/market-making/leaves/UsdTokenSwapConfig.sol";
import { IVerifierProxy } from "@zaros/external/chainlink/interfaces/IVerifierProxy.sol";
import { PremiumReport } from "@zaros/external/chainlink/interfaces/IStreamsLookupCompatible.sol";
import { IFeeManager } from "@zaros/external/chainlink/interfaces/IFeeManager.sol";
import { DexSwapStrategy } from "@zaros/market-making/leaves/DexSwapStrategy.sol";
import { UniswapV3Adapter } from "@zaros/utils/dex-adapters/UniswapV3Adapter.sol";
import { UD60x18, ud60x18 } from "@prb-math/UD60x18.sol";
import { SD59x18, sd59x18 } from "@prb-math/SD59x18.sol";
import { Constants } from "@zaros/utils/Constants.sol";
import { SafeCast } from "@openzeppelin/utils/math/SafeCast.sol";
import { EnumerableMap } from "@openzeppelin/utils/structs/EnumerableMap.sol";
import { EnumerableSet } from "@openzeppelin/utils/structs/EnumerableSet.sol";
import { IERC4626 } from "@openzeppelin/token/ERC20/extensions/ERC4626.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { Errors } from "@zaros/utils/Errors.sol";
import { IERC20Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import { MockChainlinkFeeManager } from "test/mocks/MockChainlinkFeeManager.sol";
import { MockChainlinkVerifier } from "test/mocks/MockChainlinkVerifier.sol";
import { MockUniswapV3SwapStrategyRouter } from "test/mocks/MockUniswapV3SwapStrategyRouter.sol";
import { ERC20Mock } from "@openzeppelin/mocks/token/ERC20Mock.sol";
import "forge-std/Test.sol";
uint256 constant DEFAULT_DECIMAL = 18;
contract MockAsset is ERC20Mock { }
contract MockUSDT is ERC20Mock {
function burn(uint256 amount) external {
_burn(msg.sender, amount);
}
}
contract MockUSDC is ERC20Mock { }
contract MockWeth is ERC20Mock { }
contract MockZlpVault is ZlpVault {
bytes32 private constant ZLP_VAULT_STORAGE_LOCATION =
keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ZlpVault")) - 1)) & ~bytes32(uint256(0xff));
bytes32 private constant ERC4626StorageLocation =
0x0773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00;
function _getZlpVaultStorageOverride() private pure returns (ZlpVaultStorage storage zlpVaultStorage) {
bytes32 slot = ZLP_VAULT_STORAGE_LOCATION;
assembly {
zlpVaultStorage.slot := slot
}
}
function _getERC4626StorageOverride() private pure returns (ERC4626Storage storage $) {
assembly {
$.slot := ERC4626StorageLocation
}
}
constructor(uint128 vaultId, address asset_) {
ZlpVaultStorage storage zlpVaultStorage = _getZlpVaultStorageOverride();
zlpVaultStorage.marketMakingEngine = msg.sender;
zlpVaultStorage.vaultId = vaultId;
ERC4626Storage storage $ = _getERC4626StorageOverride();
$._asset = IERC20(asset_);
$._underlyingDecimals = uint8(DEFAULT_DECIMAL);
}
}
contract MockPriceAdapter {
function getPrice() external pure returns (uint256) {
return 10 ** DEFAULT_DECIMAL;
}
}
contract MockEngine {
function getUnrealizedDebt(uint128) external pure returns (int256) {
return 0;
}
}
contract MarketMakingEngineTest is Test, IMarketMakingEngine {
using Vault for Vault.Data;
using Market for Market.Data;
using CreditDelegation for CreditDelegation.Data;
using Collateral for Collateral.Data;
using SafeCast for uint256;
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableMap for EnumerableMap.AddressToUintMap;
using LiveMarkets for LiveMarkets.Data;
using MarketMakingEngineConfiguration for MarketMakingEngineConfiguration.Data;
using Distribution for Distribution.Data;
using StabilityConfiguration for StabilityConfiguration.Data;
using DexSwapStrategy for DexSwapStrategy.Data;
uint128 dexSwapStrategyId = 1;
MockAsset asset;
MockEngine mockEngine;
MockUSDT mockUsdt;
MockUSDC mockUsdc;
MockWeth mockWeth;
MockPriceAdapter priceAdapter;
UniswapV3Adapter dexAdapter;
uint256 userAssetAmount = 1000 * (10 ** DEFAULT_DECIMAL);
uint256 creditRatio = 10 ** DEFAULT_DECIMAL;
uint256[] marketIds = new uint256[](1);
uint256[] vaultIds = new uint256[](1);
uint128 marketId = 1;
uint128 vaultId = 1;
address user = makeAddr("alice");
function setUp() external {
asset = new MockAsset();
priceAdapter = new MockPriceAdapter();
mockEngine = new MockEngine();
mockUsdt = new MockUSDT();
mockUsdc = new MockUSDC();
mockWeth = new MockWeth();
_deployDexAdapter();
MarketMakingEngineConfiguration.Data storage configuration = MarketMakingEngineConfiguration.load();
configuration.weth = address(mockWeth);
configuration.usdc = address(mockUsdc);
configuration.isRegisteredEngine[address(mockEngine)] = true;
configuration.usdTokenOfEngine[address(mockEngine)] = address(mockUsdt);
configuration.isSystemKeeperEnabled[address(this)] = true;
configuration.vaultDepositAndRedeemFeeRecipient = makeAddr("feeRecipient");
marketIds[0] = uint256(marketId);
vaultIds[0] = uint256(vaultId);
LiveMarkets.Data storage liveMarkets = LiveMarkets.load();
_setUpCollaterals();
Market.Data storage market = Market.load(marketId);
market.id = marketId;
market.engine = address(mockEngine);
market.autoDeleverageStartThreshold = uint128(10 ** (DEFAULT_DECIMAL - 1));
market.autoDeleverageEndThreshold = uint128(10 ** DEFAULT_DECIMAL);
market.autoDeleverageExponentZ = uint128(3 * 10 ** DEFAULT_DECIMAL);
liveMarkets.addMarket(marketId);
DexSwapStrategy.Data storage dexSwapStrategy = DexSwapStrategy.load(dexSwapStrategyId);
dexSwapStrategy.id = dexSwapStrategyId;
dexSwapStrategy.dexAdapter = address(dexAdapter);
address mockChainlinkFeeManager = address(new MockChainlinkFeeManager());
address mockChainlinkVerifier = address(new MockChainlinkVerifier(IFeeManager(mockChainlinkFeeManager)));
StabilityConfiguration.Data storage stabilityConfiguration = StabilityConfiguration.load();
stabilityConfiguration.chainlinkVerifier = IVerifierProxy(mockChainlinkVerifier);
MockZlpVault indexToken = new MockZlpVault(vaultId, address(asset));
indexToken.approve(address(this), type(uint128).max);
indexToken.updateAssetAllowance(type(uint128).max);
Vault.Data storage vault = Vault.load(vaultId);
vault.id = vaultId;
vault.isLive = true;
vault.indexToken = address(indexToken);
vault.depositCap = type(uint128).max;
vault.collateral.decimals = uint8(DEFAULT_DECIMAL);
vault.collateral.priceAdapter = address(priceAdapter);
vault.collateral.creditRatio = creditRatio;
vault.collateral.asset = address(asset);
vault.collateral.isEnabled = true;
vault.engine = address(mockEngine);
vault.swapStrategy.usdcDexSwapStrategyId = 1;
vault.swapStrategy.assetDexSwapStrategyId = 1;
vm.startPrank(user);
asset.mint(user, userAssetAmount);
asset.approve(address(this), userAssetAmount);
IMarketMakingEngine(address(this)).deposit(vaultId, uint128(userAssetAmount), 0, "", false);
vm.stopPrank();
}
function testPoC() external {
Market.Data storage market = Market.load(marketId);
Vault.Data storage vault = Vault.load(vaultId);
uint256[] memory _vaultIds = vaultIds;
_vaultIds[0] = 1;
_connectVaultsAndMarkets(_vaultIds);
_recalculateVaultsCreditCapacity();
_depositCreditForMarket(address(mockUsdc), 1_000_000e18);
_receiveMarketFee(address(mockWeth), 1e18);
_recalculateVaultsCreditCapacity();
_depositCreditForMarket(address(mockUsdc), 1_000_000e18);
_receiveMarketFee(address(mockWeth), 1e18);
_recalculateVaultsCreditCapacity();
vm.startPrank(user);
mockUsdt.mint(user, userAssetAmount);
mockUsdt.approve(address(this), userAssetAmount);
uint128[] memory swapVaultIds = new uint128[](1);
swapVaultIds[0] = vaultId;
uint128[] memory amountsIn = new uint128[](1);
amountsIn[0] = uint128(userAssetAmount);
uint128[] memory minAmountsOut = new uint128[](1);
vm.expectRevert(stdError.divisionError);
IMarketMakingEngine(address(this)).initiateSwap(swapVaultIds, amountsIn, minAmountsOut);
vm.stopPrank();
}
function _deployDexAdapter() internal {
MockUniswapV3SwapStrategyRouter router = new MockUniswapV3SwapStrategyRouter();
dexAdapter = new UniswapV3Adapter();
vm.startPrank(address(0));
dexAdapter.setSwapAssetConfig(address(asset), uint8(DEFAULT_DECIMAL), address(priceAdapter));
dexAdapter.setSwapAssetConfig(address(mockUsdc), uint8(DEFAULT_DECIMAL), address(priceAdapter));
dexAdapter.setUniswapV3SwapStrategyRouter(address(router));
dexAdapter.setSlippageTolerance(100);
vm.stopPrank();
vm.startPrank(address(router));
mockUsdc.mint(address(router), 9_999_999e18);
vm.stopPrank();
}
function _depositCreditForMarket(address _asset, uint256 amount) internal {
vm.startPrank(address(mockEngine));
ERC20Mock(_asset).mint(address(mockEngine), amount);
ERC20Mock(_asset).approve(address(this), amount);
IMarketMakingEngine(address(this)).depositCreditForMarket(marketId, address(_asset), amount);
vm.stopPrank();
}
function _withdrawUsdTokenFromMarket(uint256 amount) internal {
vm.startPrank(address(mockEngine));
IMarketMakingEngine(address(this)).withdrawUsdTokenFromMarket(marketId, uint128(amount));
vm.stopPrank();
}
function _receiveMarketFee(address _asset, uint256 amount) internal {
vm.startPrank(address(mockEngine));
ERC20Mock(_asset).mint(address(mockEngine), amount);
ERC20Mock(_asset).approve(address(this), amount);
IMarketMakingEngine(address(this)).receiveMarketFee(marketId, address(_asset), amount);
vm.stopPrank();
}
function _recalculateVaultsCreditCapacity() internal {
for (uint256 i; i < marketIds.length; i++) {
IMarketMakingEngine(address(this)).updateMarketCreditDelegations(uint128(marketIds[i]));
}
}
function _connectVaultsAndMarkets(uint256[] memory _vaultIds) internal {
uint256[] memory _marketIds = marketIds;
vm.startPrank(address(0));
IMarketMakingEngine(address(this)).connectVaultsAndMarkets(marketIds, _vaultIds);
vm.stopPrank();
}
function _setUpCollaterals() internal {
address[] memory collaterals = new address[](4);
collaterals[0] = address(asset);
collaterals[1] = address(mockUsdt);
collaterals[2] = address(mockUsdc);
collaterals[3] = address(mockWeth);
for (uint256 i; i < collaterals.length; i++) {
address collateralAddr = collaterals[i];
Collateral.Data storage collateral = Collateral.load(address(collateralAddr));
collateral.isEnabled = true;
collateral.priceAdapter = address(priceAdapter);
collateral.creditRatio = creditRatio;
collateral.decimals = uint8(DEFAULT_DECIMAL);
}
}
function _getMockedSignedReport(uint256 price) internal view returns (bytes memory mockedSignedReport) {
bytes memory mockedReportData;
PremiumReport memory premiumReport = PremiumReport({
feedId: bytes32(0),
validFromTimestamp: uint32(block.timestamp),
observationsTimestamp: uint32(block.timestamp),
nativeFee: 0,
linkFee: 0,
expiresAt: uint32(block.timestamp + 100_000),
price: int192(int256(price)),
bid: int192(int256(price)),
ask: int192(int256(price))
});
mockedReportData = abi.encode(premiumReport);
bytes32[3] memory mockedSignatures;
mockedSignatures[0] = bytes32(uint256(keccak256(abi.encodePacked("mockedSignature1"))));
mockedSignatures[1] = bytes32(uint256(keccak256(abi.encodePacked("mockedSignature2"))));
mockedSignatures[2] = bytes32(uint256(keccak256(abi.encodePacked("mockedSignature3"))));
mockedSignedReport = abi.encode(mockedSignatures, mockedReportData);
}
}