Skip to content

Commit

Permalink
Merge pull request #10 from GenerationSoftware/pierrick/gen-261-c4-is…
Browse files Browse the repository at this point in the history
…sue-307

feat(withdraw): add yield liquidated test (c4-issue-307)
  • Loading branch information
asselstine authored Aug 15, 2023
2 parents d893ad4 + 44a6c6b commit 0649674
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 3 deletions.
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

0 comments on commit 0649674

Please sign in to comment.