From 44a6c6b081db5cc5e2acc4757a3c9dbaa6f60943 Mon Sep 17 00:00:00 2001 From: Pierrick Turelier Date: Mon, 7 Aug 2023 13:18:02 -0500 Subject: [PATCH] feat(withdraw): add yield liquidated test --- src/Vault.sol | 6 +- test/unit/Vault/Undercollateralization.t.sol | 6 ++ test/unit/Vault/Withdraw.t.sol | 87 ++++++++++++++++++++ test/utils/Helpers.t.sol | 2 +- 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/src/Vault.sol b/src/Vault.sol index 8833310..75f2a1a 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -1167,8 +1167,8 @@ contract Vault is ERC4626, ERC20Permit, ILiquidationSource, Ownable { /** * @notice Calculate exchange rate between the amount of assets withdrawable from the YieldVault * and the amount of shares minted by this Vault. - * @dev We exclude the amount of yield generated by the YieldVault, so user can only withdraw their share of deposits. - * Except when the vault is undercollateralized, in this case, any unclaim yield fee is included in the calculation. + * @dev We exclude the amount of yield generated by the YieldVault, so users can only withdraw their share of deposits. + * Except when the vault is undercollateralized, in this case, any unclaimed yield is included in the calculation. * @dev We start with an exchange rate of 1 which is equal to 1 underlying asset unit. * @return uint256 Exchange rate */ @@ -1182,6 +1182,8 @@ contract Vault is ERC4626, ERC20Permit, ILiquidationSource, Ownable { uint256 _withdrawableAssets = _yieldVault.maxWithdraw(address(this)); + // If the Vault is collateralized, we exclude the yield from the calculation + // so users can only withdraw their share of deposits. if (_withdrawableAssets > _totalSupplyToAssets) { _withdrawableAssets = _withdrawableAssets - (_withdrawableAssets - _totalSupplyToAssets); } diff --git a/test/unit/Vault/Undercollateralization.t.sol b/test/unit/Vault/Undercollateralization.t.sol index 72b1635..f981d31 100644 --- a/test/unit/Vault/Undercollateralization.t.sol +++ b/test/unit/Vault/Undercollateralization.t.sol @@ -207,17 +207,21 @@ contract VaultUndercollateralizationTest is UnitBaseSetup { assertEq(vault.availableYieldBalance(), 0); assertEq(vault.availableYieldFeeBalance(), 0); + // We burn underlying assets from the YieldVault to trigger the undercollateralization underlyingAsset.burn(address(yieldVault), 10_000_000e18); assertEq(vault.isVaultCollateralized(), false); uint256 _yieldFeeShares = vault.yieldFeeTotalSupply(); + // The Vault is now undercollateralized so we can't mint the yield fee vm.expectRevert(abi.encodeWithSelector(VaultUnderCollateralized.selector)); vault.mintYieldFee(_yieldFeeShares); vm.startPrank(bob); + // We lose in precision because the Vault is undercollateralized + // and the exchange rate is below 1e18 and rounded down _bobAmount = _getMaxWithdraw(bob, vault, yieldVault); assertApproxEqAbs(vault.maxWithdraw(bob), _bobAmount, 2382812); @@ -241,6 +245,8 @@ contract VaultUndercollateralizationTest is UnitBaseSetup { vault.withdraw(vault.maxWithdraw(address(this)), address(this), address(this)); assertApproxEqAbs(underlyingAsset.balanceOf(address(this)), _thisAmount, 280000); + assertEq(vault.totalSupply(), 0); + assertApproxEqAbs(underlyingAsset.balanceOf(address(yieldVault)), 0, 280000); } } diff --git a/test/unit/Vault/Withdraw.t.sol b/test/unit/Vault/Withdraw.t.sol index 3bd1de8..5ff3147 100644 --- a/test/unit/Vault/Withdraw.t.sol +++ b/test/unit/Vault/Withdraw.t.sol @@ -125,6 +125,93 @@ contract VaultWithdrawTest is UnitBaseSetup { vm.stopPrank(); } + function testWithdrawFullAmountYieldLiquidated() external { + _setLiquidationPair(); + + vault.setYieldFeePercentage(YIELD_FEE_PERCENTAGE); + + uint256 _aliceAmount = 1000e18; + underlyingAsset.mint(alice, _aliceAmount); + + vm.startPrank(alice); + + _deposit(underlyingAsset, vault, _aliceAmount, alice); + + vm.stopPrank(); + + uint256 _yield = 10e18; + _accrueYield(underlyingAsset, yieldVault, _yield); + + vm.startPrank(bob); + + prizeToken.mint(bob, type(uint256).max); + + // We liquidate the accrued yield + uint256 _liquidatedYield = vault.liquidatableBalanceOf(address(vault)); + + (uint256 _bobPrizeTokenBalanceBefore, uint256 _prizeTokenContributed) = _liquidate( + liquidationRouter, + liquidationPair, + prizeToken, + _liquidatedYield, + bob + ); + + assertEq(prizeToken.balanceOf(address(prizePool)), _prizeTokenContributed); + assertEq(prizeToken.balanceOf(bob), _bobPrizeTokenBalanceBefore - _prizeTokenContributed); + + uint256 _yieldFeeShares = _getYieldFeeShares(_liquidatedYield, YIELD_FEE_PERCENTAGE); + uint256 _bobAmount = _yield - _yieldFeeShares; + + // Bob now owns 9e18 Vault shares + assertEq(vault.balanceOf(bob), _yield - _yieldFeeShares); + + // 1e18 have been allocated as yield fee + assertEq(vault.yieldFeeTotalSupply(), _yieldFeeShares); + assertEq(_yield, _liquidatedYield + _yieldFeeShares); + + vm.stopPrank(); + + // The Vault is still collateralized, so users can withdraw their full deposit + assertEq(vault.maxWithdraw(bob), _bobAmount); + assertEq(vault.maxWithdraw(alice), _aliceAmount); + + // We burn the accrued yield to set the Vault in an undercollateralized state + underlyingAsset.burn(address(yieldVault), _yield); + + assertLt(vault.exchangeRate(), 1e18); + + // The Vault is now undercollateralized, so users can withdraw their share of the deposits + // any unclaimed yield fee goes proportionally to each user + + // We lose a bit of precision due to the exchange rate being below 1e18 and rounded down + assertApproxEqAbs(vault.maxWithdraw(bob), _getMaxWithdraw(bob, vault, yieldVault), 6); + assertApproxEqAbs(vault.maxWithdraw(alice), _getMaxWithdraw(alice, vault, yieldVault), 693); + + vm.startPrank(alice); + + uint256 _aliceWithdrawableAmount = vault.maxWithdraw(alice); + vault.withdraw(_aliceWithdrawableAmount, alice, alice); + + assertEq(vault.balanceOf(alice), 0); + assertEq(underlyingAsset.balanceOf(alice), _aliceWithdrawableAmount); + + vm.stopPrank(); + + vm.startPrank(bob); + + uint256 _bobWithdrawableAmount = vault.maxWithdraw(bob); + vault.withdraw(_bobWithdrawableAmount, bob, bob); + + assertEq(vault.balanceOf(bob), 0); + assertEq(underlyingAsset.balanceOf(bob), _bobWithdrawableAmount); + + assertEq(vault.totalSupply(), 0); + assertApproxEqAbs(underlyingAsset.balanceOf(address(yieldVault)), 0, 7); + + vm.stopPrank(); + } + function testWithdrawOnBehalf() external { uint256 _amount = 1000e18; underlyingAsset.mint(bob, _amount); diff --git a/test/utils/Helpers.t.sol b/test/utils/Helpers.t.sol index bea286f..e96e639 100644 --- a/test/utils/Helpers.t.sol +++ b/test/utils/Helpers.t.sol @@ -72,7 +72,7 @@ contract Helpers is Test { return _vault.depositWithPermit(_assets, _user, block.timestamp, _v, _r, _s); } - /* ============ Deposit ============ */ + /* ============ Mint ============ */ function _mint( IERC20 _underlyingAsset, Vault _vault,