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 { 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 { ERC20Mock } from "@openzeppelin/mocks/token/ERC20Mock.sol";
import "forge-std/Test.sol";
uint256 constant DEFAULT_DECIMAL = 18;
contract MockAsset 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 1 ** DEFAULT_DECIMAL;
}
}
contract MockEngine {
function getUnrealizedDebt(uint128) external pure returns (int256) {
return 0;
}
}
contract ReedemFailureTest 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;
uint128 marketId = 1;
uint128 vaultId = 1;
MockAsset asset;
MockZlpVault indexToken;
address usdc = vm.addr(2);
address weth = vm.addr(3);
uint256 userAssetAmount = 1000 * (10 ** DEFAULT_DECIMAL);
uint256 creditRatio = 1e18;
uint256[] vaultIds = new uint256[](1);
address[] users = new address[](3);
function setUp() external {
asset = new MockAsset();
indexToken = new MockZlpVault(vaultId, address(asset));
MockPriceAdapter priceAdapter = new MockPriceAdapter();
MockEngine mockEngine = new MockEngine();
MarketMakingEngineConfiguration.Data storage configuration = MarketMakingEngineConfiguration.load();
configuration.usdc = usdc;
Market.Data storage market = Market.load(marketId);
market.engine = address(mockEngine);
uint256[] memory marketIds = new uint256[](1);
marketIds[0] = uint256(marketId);
vaultIds[0] = 1;
market.id = marketId;
LiveMarkets.Data storage liveMarkets = LiveMarkets.load();
liveMarkets.addMarket(marketId);
Collateral.Data storage collateral = Collateral.load(address(asset));
collateral.isEnabled = true;
collateral.priceAdapter = address(priceAdapter);
collateral.creditRatio = creditRatio;
Vault.Data storage vault = Vault.load(vaultId);
vault.id = vaultId;
vault.isLive = true;
vault.indexToken = address(indexToken);
vault.depositCap = type(uint128).max;
vault.collateral.asset = address(asset);
vault.collateral.isEnabled = true;
vault.collateral.decimals = uint8(DEFAULT_DECIMAL);
vault.collateral.priceAdapter = address(priceAdapter);
vault.collateral.creditRatio = creditRatio;
vault.lockedCreditRatio = 0.5e18;
vault.wethRewardDistribution.setActorShares(bytes32(0), ud60x18(1e18));
_connectVaultsAndMarkets();
users[0] = makeAddr("alice");
users[1] = makeAddr("bob");
users[2] = makeAddr("eve");
for (uint256 i; i < users.length; i++) {
asset.mint(users[i], userAssetAmount);
vm.startPrank(users[i]);
asset.approve(address(this), userAssetAmount);
IMarketMakingEngine(address(this)).deposit(vaultId, uint128(userAssetAmount), 0, "", false);
vm.stopPrank();
}
}
function testRevertOnRedeem(uint128 redeemAmount) external {
Vault.Data storage vault = Vault.load(vaultId);
_recalculateVaultsCreditCapacity();
for (uint256 i; i < users.length; i++) {
vm.startPrank(users[i]);
redeemAmount = uint128(bound(uint256(redeemAmount), 1e15, indexToken.balanceOf(users[i])));
indexToken.approve(address(this), redeemAmount);
IMarketMakingEngine(address(this)).initiateWithdrawal(vaultId, redeemAmount);
uint128 withdrawalRequestId = vault.withdrawalRequestIdCounter[users[i]];
vm.expectRevert(Errors.NotEnoughUnlockedCreditCapacity.selector);
IMarketMakingEngine(address(this)).redeem(vaultId, withdrawalRequestId, 0);
vm.stopPrank();
}
}
function _connectVaultsAndMarkets() internal {
uint256[] memory marketIds = new uint256[](1);
marketIds[0] = uint256(marketId);
uint256[] memory _vaultIds = vaultIds;
vm.startPrank(address(0));
IMarketMakingEngine(address(this)).connectVaultsAndMarkets(marketIds, _vaultIds);
vm.stopPrank();
}
function _recalculateVaultsCreditCapacity() internal {
IMarketMakingEngine(address(this)).updateMarketCreditDelegations(marketId);
}
}