Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(withdraw): add yield liquidated test (c4-issue-307) #10

Merged
merged 1 commit into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Vault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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);
}
Expand Down
6 changes: 6 additions & 0 deletions test/unit/Vault/Undercollateralization.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);
}
}
87 changes: 87 additions & 0 deletions test/unit/Vault/Withdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion test/utils/Helpers.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading