From 77be958bdce428fb86420808defee3868c41c1df Mon Sep 17 00:00:00 2001 From: Dan OneTree Date: Fri, 3 Jan 2025 22:06:21 +0530 Subject: [PATCH 1/6] fixed maxWithdraw --- script/bnb/deploy_ynclisbnbk.s.sol | 2 +- src/KernelClisStrategy.sol | 29 +++++++ src/KernelStrategy.sol | 26 ++++-- test/mainnet/ynbnbk.spec.sol | 8 +- test/mainnet/ynclisbnbk.spec.sol | 31 ++++--- test/unit/deposit.t.sol | 6 +- test/unit/mocks/MockKernelVault.sol | 19 ++++- test/unit/mocks/MockStakerGateway.sol | 6 +- test/unit/withdraw.t.sol | 114 ++++++++------------------ 9 files changed, 133 insertions(+), 108 deletions(-) diff --git a/script/bnb/deploy_ynclisbnbk.s.sol b/script/bnb/deploy_ynclisbnbk.s.sol index c15d5f1d..e25095b0 100644 --- a/script/bnb/deploy_ynclisbnbk.s.sol +++ b/script/bnb/deploy_ynclisbnbk.s.sol @@ -15,7 +15,7 @@ import {BaseKernelScript} from "script/BaseKernelScript.sol"; import {console} from "lib/forge-std/src/console.sol"; import {KernelClisVaultViewer} from "src/utils/KernelClisVaultViewer.sol"; -import {BaseVaultViewer, KernelVaultViewer} from "src/utils/KernelVaultViewer.sol"; +import {KernelVaultViewer} from "src/utils/KernelVaultViewer.sol"; // FOUNDRY_PROFILE=mainnet forge script DeployYnclisBNBkStrategy --sender 0xd53044093F757E8a56fED3CCFD0AF5Ad67AeaD4a contract DeployYnclisBNBkStrategy is BaseKernelScript { diff --git a/src/KernelClisStrategy.sol b/src/KernelClisStrategy.sol index 5e243432..69cb1be4 100644 --- a/src/KernelClisStrategy.sol +++ b/src/KernelClisStrategy.sol @@ -3,7 +3,10 @@ pragma solidity ^0.8.24; import {KernelStrategy} from "./KernelStrategy.sol"; +import {IERC20, Math} from "lib/yieldnest-vault/src/Common.sol"; import {IWBNB} from "src/interface/external/IWBNB.sol"; + +import {IKernelConfig} from "src/interface/external/kernel/IKernelConfig.sol"; import {IStakerGateway} from "src/interface/external/kernel/IStakerGateway.sol"; /** @@ -38,4 +41,30 @@ contract KernelClisStrategy is KernelStrategy { //wrap native token IWBNB(asset_).deposit{value: assets}(); } + + /** + * @dev See {maxWithdrawAsset}. + */ + function _maxWithdrawAsset(address asset_, address owner) internal view override returns (uint256 maxAssets) { + if (paused() || !_getAssetStorage().assets[asset_].active) { + return 0; + } + + (maxAssets,) = _convertToAssets(asset_, balanceOf(owner), Math.Rounding.Floor); + + uint256 availableAssets = IERC20(asset_).balanceOf(address(this)); + + StrategyStorage storage strategyStorage = _getStrategyStorage(); + + if (strategyStorage.syncWithdraw && asset_ == asset()) { + IStakerGateway stakerGateway = IStakerGateway(strategyStorage.stakerGateway); + address clisbnb = IKernelConfig(stakerGateway.getConfig()).getClisBnbAddress(); + uint256 availableAssetsInKernel = stakerGateway.balanceOf(clisbnb, address(this)); + availableAssets += availableAssetsInKernel; + } + + if (availableAssets < maxAssets) { + maxAssets = availableAssets; + } + } } diff --git a/src/KernelStrategy.sol b/src/KernelStrategy.sol index bb091c61..0f51b845 100644 --- a/src/KernelStrategy.sol +++ b/src/KernelStrategy.sol @@ -92,11 +92,7 @@ contract KernelStrategy is Vault { * @return maxAssets The maximum amount of assets. */ function maxWithdraw(address owner) public view override returns (uint256 maxAssets) { - if (paused()) { - return 0; - } - - (maxAssets,) = _convertToAssets(asset(), balanceOf(owner), Math.Rounding.Floor); + maxAssets = _maxWithdrawAsset(asset(), owner); } /** @@ -119,11 +115,29 @@ contract KernelStrategy is Vault { * @return maxAssets The maximum amount of assets. */ function maxWithdrawAsset(address asset_, address owner) public view returns (uint256 maxAssets) { - if (paused()) { + maxAssets = _maxWithdrawAsset(asset_, owner); + } + + function _maxWithdrawAsset(address asset_, address owner) internal view virtual returns (uint256 maxAssets) { + if (paused() || !_getAssetStorage().assets[asset_].active) { return 0; } (maxAssets,) = _convertToAssets(asset_, balanceOf(owner), Math.Rounding.Floor); + + uint256 availableAssets = IERC20(asset_).balanceOf(address(this)); + + StrategyStorage storage strategyStorage = _getStrategyStorage(); + + if (strategyStorage.syncWithdraw) { + uint256 availableAssetsInKernel = + IStakerGateway(strategyStorage.stakerGateway).balanceOf(asset_, address(this)); + availableAssets += availableAssetsInKernel; + } + + if (availableAssets < maxAssets) { + maxAssets = availableAssets; + } } /** diff --git a/test/mainnet/ynbnbk.spec.sol b/test/mainnet/ynbnbk.spec.sol index 5555969f..e4113816 100644 --- a/test/mainnet/ynbnbk.spec.sol +++ b/test/mainnet/ynbnbk.spec.sol @@ -873,11 +873,11 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU uint256 beforeBobBalance = asset.balanceOf(bob); uint256 beforeBobShares = vault.balanceOf(bob); - uint256 maxWithdraw = vault.maxWithdrawAsset(MC.SLISBNB, bob); + uint256 maxAssets = vault.previewRedeemAsset(MC.SLISBNB, beforeBobShares); vm.prank(bob); - vm.expectRevert(abi.encodePacked("ERC20: transfer amount exceeds balance")); - uint256 shares = vault.withdrawAsset(MC.SLISBNB, maxWithdraw, bob, bob); + vm.expectRevert(); + uint256 shares = vault.withdrawAsset(MC.SLISBNB, maxAssets, bob, bob); assertEq(shares, 0, "Shares should be 0"); @@ -909,7 +909,7 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU assertEq(asset.balanceOf(bob), beforeBobBalance, "Bob balance should not increase"); assertEq(vault.balanceOf(bob), beforeBobShares, "Bob shares should not decrease"); - maxWithdraw = vault.maxWithdrawAsset(MC.SLISBNB, bob); + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.SLISBNB, bob); assertEqThreshold(maxWithdraw, amount, 5, "maxWithdraw should be equal to amount"); diff --git a/test/mainnet/ynclisbnbk.spec.sol b/test/mainnet/ynclisbnbk.spec.sol index 097f92e8..b2f147f1 100644 --- a/test/mainnet/ynclisbnbk.spec.sol +++ b/test/mainnet/ynclisbnbk.spec.sol @@ -6,6 +6,7 @@ import {Test} from "lib/forge-std/src/Test.sol"; import {TransparentUpgradeableProxy} from "lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IVault} from "lib/yieldnest-vault/src/BaseVault.sol"; import {IERC20} from "lib/yieldnest-vault/src/Common.sol"; import {Vault} from "lib/yieldnest-vault/src/Vault.sol"; @@ -36,7 +37,8 @@ contract YnClisBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, Va KernelClisVaultViewer public viewer; address public bob = address(0xB0B); - address public clisBnbVault; + address public clisBnb; + address public kernelVault; function setUp() public { kernelProvider = new BNBRateProvider(); @@ -44,7 +46,8 @@ contract YnClisBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, Va stakerGateway = IStakerGateway(MC.STAKER_GATEWAY); - clisBnbVault = IKernelConfig(stakerGateway.getConfig()).getClisBnbAddress(); + clisBnb = IKernelConfig(stakerGateway.getConfig()).getClisBnbAddress(); + assertEq(clisBnb, MC.CLISBNB); vault = deployClisBNBk(); viewer = KernelClisVaultViewer( @@ -63,7 +66,7 @@ contract YnClisBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, Va vm.label(address(vault), "kernel strategy"); vm.label(address(kernelProvider), "kernel rate provider"); - address kernelVault = IStakerGateway(MC.STAKER_GATEWAY).getVault(MC.CLISBNB); + kernelVault = IStakerGateway(MC.STAKER_GATEWAY).getVault(MC.CLISBNB); address config = IKernelVault(kernelVault).getConfig(); bytes32 role = IKernelConfig(config).ROLE_MANAGER(); @@ -170,7 +173,7 @@ contract YnClisBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, Va vm.startPrank(withdrawer); vm.expectRevert( abi.encodeWithSelector( - IAccessControl.AccessControlUnauthorizedAccount.selector, withdrawer, vault.ALLOCATOR_ROLE() + IVault.ExceededMaxWithdraw.selector, withdrawer, amount, vault.maxWithdraw(withdrawer) ) ); vault.withdrawAsset(MC.WBNB, amount, withdrawer, withdrawer); @@ -311,9 +314,7 @@ contract YnClisBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, Va ); assertEq(asset.balanceOf(bob), beforeBobBalance - amount, "Bob should not have the assets"); assertEq(vault.balanceOf(bob), beforeBobShares + shares, "Bob should have shares after deposit"); - assertEq( - stakerGateway.balanceOf(clisBnbVault, address(vault)), amount, "vault should have shares after deposit" - ); + assertEq(stakerGateway.balanceOf(clisBnb, address(vault)), amount, "vault should have shares after deposit"); } function test_ynclisBNBk_withdraw_success_syncEnabled(uint256 amount) public { @@ -335,15 +336,19 @@ contract YnClisBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, Va vault.processAccounting(); - uint256 maxWithdraw = vault.maxWithdraw(bob); - uint256 vaultShares = stakerGateway.balanceOf(clisBnbVault, address(vault)); + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.WBNB, bob); + uint256 vaultShares = stakerGateway.balanceOf(clisBnb, address(vault)); + uint256 kernelVaultBalance = IERC20(kernelVault).balanceOf(address(vault)); + assertEq(vaultShares, kernelVaultBalance, "vault should have shares after deposit"); + + assertEq(asset.balanceOf(address(vault)), 0, "vault should have no assets"); + assertEq(vault.balanceOf(bob), shares, "bob should have shares"); + assertGt(vaultShares, 0, "vault should have some shares"); assertGt(maxWithdraw, 0, "max withdraw should not be 0"); assertEq(maxWithdraw, shares, "incorrect maxWithdraw amount"); - assertEq( - stakerGateway.balanceOf(clisBnbVault, address(vault)), amount, "vault should have shares after deposit" - ); + assertEq(stakerGateway.balanceOf(clisBnb, address(vault)), amount, "vault should have shares after deposit"); uint256 withdrawAmount = vault.maxWithdraw(bob); assertGt(withdrawAmount, 0, "can't withdraw 0"); @@ -364,6 +369,6 @@ contract YnClisBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, Va ); assertEq(asset.balanceOf(bob), beforeBobBalance + amount, "Bob should have the assets"); assertEq(vault.balanceOf(bob), beforeBobShares - shares, "Bob should not have shares after withdraw"); - assertEq(stakerGateway.balanceOf(clisBnbVault, address(vault)), 0, "vault should have 0 shares after deposit"); + assertEq(stakerGateway.balanceOf(clisBnb, address(vault)), 0, "vault should have 0 shares after deposit"); } } diff --git a/test/unit/deposit.t.sol b/test/unit/deposit.t.sol index ea26ece0..7692d0ef 100644 --- a/test/unit/deposit.t.sol +++ b/test/unit/deposit.t.sol @@ -96,9 +96,10 @@ contract KernelStrategyDepositUnitTest is SetupKernelStrategy { // Check that shares were minted assertGt(sharesMinted, 0, "No shares were minted"); + address mockVault = mockGateway.getVault(address(wbnb)); // Check that the vault received the tokens assertEq( - wbnb.balanceOf(address(mockGateway)), + wbnb.balanceOf(address(mockVault)), beforeGatewayBalance + depositAmount, "KernelStrategy did not receive tokens" ); @@ -176,8 +177,9 @@ contract KernelStrategyDepositUnitTest is SetupKernelStrategy { assertGt(sharesMinted, 0, "No shares were minted"); assertEq(sharesMinted, previewDepositAsset, "Incorrect shares minted"); + address mockVault = mockGateway.getVault(address(slisbnb)); // Check that the vault received the tokens - assertEq(slisbnb.balanceOf(address(mockGateway)), depositAmount, "KernelStrategy did not receive tokens"); + assertEq(slisbnb.balanceOf(address(mockVault)), depositAmount, "KernelStrategy did not receive tokens"); // Check that Alice's token balance decreased assertEq(slisbnb.balanceOf(alice), 0, "Alice's balance did not decrease correctly"); diff --git a/test/unit/mocks/MockKernelVault.sol b/test/unit/mocks/MockKernelVault.sol index bcc65ec7..7cf0bea8 100644 --- a/test/unit/mocks/MockKernelVault.sol +++ b/test/unit/mocks/MockKernelVault.sol @@ -1,14 +1,31 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.24; +import {IERC20} from "lib/yieldnest-vault/src/Common.sol"; + contract MockKernelVault { address private _asset; + mapping(address => uint256) private _balanceOf; + constructor(address asset) { - asset = asset; + _asset = asset; + IERC20(asset).approve(msg.sender, type(uint256).max); } function getAsset() external view returns (address) { return _asset; } + + function balanceOf(address owner) external view returns (uint256) { + return _balanceOf[owner]; + } + + function mint(address owner, uint256 amount) external { + _balanceOf[owner] += amount; + } + + function burn(address owner, uint256 amount) external { + _balanceOf[owner] -= amount; + } } diff --git a/test/unit/mocks/MockStakerGateway.sol b/test/unit/mocks/MockStakerGateway.sol index 24e7e06f..fea85ebc 100644 --- a/test/unit/mocks/MockStakerGateway.sol +++ b/test/unit/mocks/MockStakerGateway.sol @@ -24,12 +24,14 @@ contract MockStakerGateway { } function stake(address asset, uint256 amount, string calldata) external { - IERC20(asset).transferFrom(msg.sender, address(this), amount); + IERC20(asset).transferFrom(msg.sender, _vaults[asset], amount); _balanceOf[asset][msg.sender] += amount; + MockKernelVault(_vaults[asset]).mint(msg.sender, amount); } function unstake(address asset, uint256 amount, string calldata) external { - IERC20(asset).transferFrom(address(this), msg.sender, amount); + IERC20(asset).transferFrom(_vaults[asset], msg.sender, amount); _balanceOf[asset][msg.sender] -= amount; + MockKernelVault(_vaults[asset]).burn(msg.sender, amount); } } diff --git a/test/unit/withdraw.t.sol b/test/unit/withdraw.t.sol index cc1eefcd..779ce5e8 100644 --- a/test/unit/withdraw.t.sol +++ b/test/unit/withdraw.t.sol @@ -148,7 +148,7 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { vm.startPrank(alice); vault.deposit(withdrawalAmount, alice); - vm.expectRevert(IVault.ZeroAddress.selector); + vm.expectRevert(); vault.withdrawAsset(address(0), withdrawalAmount, alice, alice); } @@ -197,7 +197,7 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { vault.updateAsset(0, IVault.AssetUpdateFields({active: false})); vm.prank(alice); - vm.expectRevert(IVault.AssetNotActive.selector); + vm.expectRevert(); vault.withdrawAsset(address(MC.WBNB), withdrawalAmount, alice, alice); } @@ -392,11 +392,27 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { // deposit amount vm.prank(alice); - vault.deposit(withdrawalAmount, alice); + uint256 shares = vault.deposit(withdrawalAmount, alice); - uint256 maxWithdraw = vault.maxWithdrawAsset(address(asset), alice); + assertEq(vault.balanceOf(alice), shares, "alice has incorrect shares"); + + address kernelVault = mockGateway.getVault(address(asset)); + assertEq(asset.balanceOf(address(vault)), 0, "Vault balance should be 0"); + assertEq( + asset.balanceOf(address(kernelVault)), withdrawalAmount, "KernelVault balance should be withdrawalAmount" + ); + assertEq( + IERC20(kernelVault).balanceOf(address(vault)), withdrawalAmount, "Vault should have the asset after deposit" + ); assertEq(vault.balanceOf(alice), withdrawalAmount, "alice has incorrect shares"); + assertEq( + mockGateway.balanceOf(address(asset), address(vault)), + withdrawalAmount, + "Vault should have the asset after deposit" + ); + + uint256 maxWithdraw = vault.maxWithdrawAsset(address(asset), alice); assertEqThreshold(maxWithdraw, withdrawalAmount, 2, "Max withdraw should be equal to withdrawalAmount"); uint256 beforeAliceBalance = asset.balanceOf(alice); @@ -406,12 +422,12 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { vm.expectEmit(); emit KernelStrategy.WithdrawAsset( - alice, alice, alice, address(asset), withdrawalAmount, vault.previewWithdraw(withdrawalAmount) + alice, alice, alice, address(asset), maxWithdraw, vault.previewWithdraw(withdrawalAmount) ); vm.prank(alice); - uint256 shares = vault.withdrawAsset(address(asset), withdrawalAmount, alice, alice); + shares = vault.withdrawAsset(address(asset), maxWithdraw, alice, alice); - assertEq(asset.balanceOf(address(vault)), withdrawalAmount - maxWithdraw, "Vault balance should be 0"); + assertEq(asset.balanceOf(address(vault)), 0, "Vault balance should be 0"); assertEq(asset.balanceOf(alice), beforeAliceBalance + maxWithdraw, "Alice balance should increase"); assertEq(vault.balanceOf(alice), beforeAliceShares - shares, "Alice shares should decrease"); } @@ -492,27 +508,17 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { assertEqThreshold(shares2 * 2, shares1, 3, "shares should be correct"); - uint256 totalShares = shares1 + shares2; - uint256 maxWithdraw = vault.maxWithdraw(alice); uint256 maxWithdraw1 = vault.maxWithdrawAsset(address(asset1), alice); assertEq(maxWithdraw, maxWithdraw1, "maxWithdraw is incorrect"); - uint256 expectedMax1 = vault.convertToAssets(totalShares); - - assertEq(maxWithdraw1, expectedMax1, "Max withdraw for asset1 is incorrect"); - - assertGe(maxWithdraw1, depositAmount, "Max withdraw for asset1 should be greater than depositAmount"); + assertEq(maxWithdraw1, depositAmount, "Max withdraw for asset1 is incorrect"); uint256 maxWithdraw2 = vault.maxWithdrawAsset(address(asset2), alice); - uint256 expectedMax2 = 2 * expectedMax1; - - assertEq(maxWithdraw2, expectedMax2, "Max withdraw for asset2 is incorrect"); - - assertGe(maxWithdraw2, depositAmount, "Max withdraw for asset2 should be greater than depositAmount"); + assertEq(maxWithdraw2, depositAmount, "Max withdraw for asset2 is incorrect"); } { @@ -601,27 +607,17 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { assertEq(asset1.balanceOf(address(vault)), depositAmount1, "asset1 balance is incorrect"); assertEq(asset2.balanceOf(address(vault)), depositAmount2, "asset2 balance is incorrect"); - uint256 totalShares = shares1 + shares2; - uint256 maxWithdraw = vault.maxWithdraw(alice); uint256 maxWithdraw2 = vault.maxWithdrawAsset(address(asset2), alice); assertEq(maxWithdraw, maxWithdraw2, "maxWithdraw is incorrect"); - uint256 expectedMax2 = vault.convertToAssets(totalShares); - - assertEq(maxWithdraw2, expectedMax2, "Max withdraw for asset2 is incorrect"); - - assertGe(maxWithdraw2, depositAmount2, "Max withdraw for asset2 should be greater than depositAmount"); + assertEq(maxWithdraw2, depositAmount2, "Max withdraw for asset2 is incorrect"); uint256 maxWithdraw1 = vault.maxWithdrawAsset(address(asset1), alice); - uint256 expectedMax1 = expectedMax2 / 1e10; - - assertEq(maxWithdraw1, expectedMax1, "Max withdraw for asset1 is incorrect"); - - assertGe(maxWithdraw1, depositAmount1, "Max withdraw for asset1 should be greater than depositAmount"); + assertEq(maxWithdraw1, depositAmount1, "Max withdraw for asset1 is incorrect"); } { @@ -712,27 +708,17 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { assertEq(asset1.balanceOf(address(vault)), depositAmount1, "asset1 balance is incorrect"); assertEq(asset2.balanceOf(address(vault)), depositAmount2, "asset2 balance is incorrect"); - uint256 totalShares = shares1 + shares2; - uint256 maxWithdraw = vault.maxWithdraw(alice); uint256 maxWithdraw2 = vault.maxWithdrawAsset(address(asset2), alice); assertEq(maxWithdraw, maxWithdraw2, "maxWithdraw is incorrect"); - uint256 expectedMax2 = vault.convertToAssets(totalShares); - - assertEq(maxWithdraw2, expectedMax2, "Max withdraw for asset2 is incorrect"); - - assertGe(maxWithdraw2, depositAmount2, "Max withdraw for asset2 should be greater than depositAmount"); + assertEq(maxWithdraw2, depositAmount2, "Max withdraw for asset2 is incorrect"); uint256 maxWithdraw1 = vault.maxWithdrawAsset(address(asset1), alice); - uint256 expectedMax1 = expectedMax2 / 1e9; - - assertEq(maxWithdraw1, expectedMax1, "Max withdraw for asset1 is incorrect"); - - assertGe(maxWithdraw1, depositAmount1, "Max withdraw for asset1 should be greater than depositAmount"); + assertEq(maxWithdraw1, depositAmount1, "Max withdraw for asset1 is incorrect"); } { @@ -810,27 +796,17 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { assertEqThreshold(shares2 * 2, shares1, 3, "shares should be correct"); - uint256 totalShares = shares1 + shares2; - uint256 maxWithdraw = vault.maxWithdraw(alice); uint256 maxWithdraw1 = vault.maxWithdrawAsset(address(asset1), alice); assertEq(maxWithdraw, maxWithdraw1, "maxWithdraw is incorrect"); - uint256 expectedMax1 = vault.convertToAssets(totalShares); - - assertEq(maxWithdraw1, expectedMax1, "Max withdraw for asset1 is incorrect"); - - assertGe(maxWithdraw1, depositAmount, "Max withdraw for asset1 should be greater than depositAmount"); + assertEq(maxWithdraw1, depositAmount, "Max withdraw for asset1 is incorrect"); uint256 maxWithdraw2 = vault.maxWithdrawAsset(address(asset2), alice); - uint256 expectedMax2 = 2 * expectedMax1; - - assertEq(maxWithdraw2, expectedMax2, "Max withdraw for asset2 is incorrect"); - - assertGe(maxWithdraw2, depositAmount, "Max withdraw for asset2 should be greater than depositAmount"); + assertEq(maxWithdraw2, depositAmount, "Max withdraw for asset2 is incorrect"); } { @@ -917,27 +893,17 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { assertEq(asset1.balanceOf(address(vault)), depositAmount1, "asset1 balance is incorrect"); assertEq(asset2.balanceOf(address(vault)), depositAmount2, "asset2 balance is incorrect"); - uint256 totalShares = shares1 + shares2; - uint256 maxWithdraw = vault.maxWithdraw(alice); uint256 maxWithdraw2 = vault.maxWithdrawAsset(address(asset2), alice); assertEq(maxWithdraw, maxWithdraw2, "maxWithdraw is incorrect"); - uint256 expectedMax2 = vault.convertToAssets(totalShares); - - assertEq(maxWithdraw2, expectedMax2, "Max withdraw for asset2 is incorrect"); - - assertGe(maxWithdraw2, depositAmount2, "Max withdraw for asset2 should be greater than depositAmount"); + assertEq(maxWithdraw2, depositAmount2, "Max withdraw for asset2 is incorrect"); uint256 maxWithdraw1 = vault.maxWithdrawAsset(address(asset1), alice); - uint256 expectedMax1 = expectedMax2 / 1e10; - - assertEq(maxWithdraw1, expectedMax1, "Max withdraw for asset1 is incorrect"); - - assertGe(maxWithdraw1, depositAmount1, "Max withdraw for asset1 should be greater than depositAmount"); + assertEq(maxWithdraw1, depositAmount1, "Max withdraw for asset1 is incorrect"); } { @@ -1024,27 +990,17 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { assertEq(asset1.balanceOf(address(vault)), depositAmount1, "asset1 balance is incorrect"); assertEq(asset2.balanceOf(address(vault)), depositAmount2, "asset2 balance is incorrect"); - uint256 totalShares = shares1 + shares2; - uint256 maxWithdraw = vault.maxWithdraw(alice); uint256 maxWithdraw2 = vault.maxWithdrawAsset(address(asset2), alice); assertEq(maxWithdraw, maxWithdraw2, "maxWithdraw is incorrect"); - uint256 expectedMax2 = vault.convertToAssets(totalShares); - - assertEq(maxWithdraw2, expectedMax2, "Max withdraw for asset2 is incorrect"); - - assertGe(maxWithdraw2, depositAmount2, "Max withdraw for asset2 should be greater than depositAmount"); + assertEq(maxWithdraw2, depositAmount2, "Max withdraw for asset2 is incorrect"); uint256 maxWithdraw1 = vault.maxWithdrawAsset(address(asset1), alice); - uint256 expectedMax1 = expectedMax2 / 1e9; - - assertEq(maxWithdraw1, expectedMax1, "Max withdraw for asset1 is incorrect"); - - assertGe(maxWithdraw1, depositAmount1, "Max withdraw for asset1 should be greater than depositAmount"); + assertEq(maxWithdraw1, depositAmount1, "Max withdraw for asset1 is incorrect"); } { From 78ddec016c55d4a090826a13f808c1302c97d5ca Mon Sep 17 00:00:00 2001 From: Dan OneTree Date: Fri, 3 Jan 2025 22:31:12 +0530 Subject: [PATCH 2/6] fixed maxRedeem --- src/KernelStrategy.sol | 15 ++++++++++----- test/mainnet/ynbnbk.spec.sol | 18 ++++++++++++------ test/unit/withdraw.t.sol | 5 ++++- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/KernelStrategy.sol b/src/KernelStrategy.sol index 0f51b845..fb441a57 100644 --- a/src/KernelStrategy.sol +++ b/src/KernelStrategy.sol @@ -101,11 +101,16 @@ contract KernelStrategy is Vault { * @return maxShares The maximum amount of shares. */ function maxRedeem(address owner) public view override returns (uint256 maxShares) { - if (paused()) { - return 0; - } + maxShares = _maxRedeemAsset(asset(), owner); + } + + function maxRedeemAsset(address asset_, address owner) public view returns (uint256 maxShares) { + maxShares = _maxRedeemAsset(asset_, owner); + } - return balanceOf(owner); + function _maxRedeemAsset(address asset_, address owner) internal view virtual returns (uint256 maxShares) { + uint256 maxAssets = _maxWithdrawAsset(asset_, owner); + (maxShares,) = _convertToShares(asset_, maxAssets, Math.Rounding.Floor); } /** @@ -205,7 +210,7 @@ contract KernelStrategy is Vault { if (paused()) { revert Paused(); } - uint256 maxShares = maxRedeem(owner); + uint256 maxShares = maxRedeemAsset(asset_, owner); if (shares > maxShares) { revert ExceededMaxRedeem(owner, shares, maxShares); } diff --git a/test/mainnet/ynbnbk.spec.sol b/test/mainnet/ynbnbk.spec.sol index e4113816..896d9354 100644 --- a/test/mainnet/ynbnbk.spec.sol +++ b/test/mainnet/ynbnbk.spec.sol @@ -935,12 +935,15 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU uint256 shares = depositIntoVault(MC.SLISBNB, amount, false); - uint256 previewAssets = vault.previewRedeemAsset(MC.SLISBNB, shares); + uint256 maxRedeem = vault.maxRedeemAsset(MC.SLISBNB, bob); + uint256 previewAssets = vault.previewRedeemAsset(MC.SLISBNB, maxRedeem); + + assertEqThreshold(maxRedeem, shares, 5, "Max redeem should be equal to shares"); assertGt(asset.balanceOf(address(vault)), previewAssets, "Vault should have enough assets to withdraw"); vm.prank(bob); - uint256 assets = vault.redeemAsset(MC.SLISBNB, shares, bob, bob); + uint256 assets = vault.redeemAsset(MC.SLISBNB, maxRedeem, bob, bob); assertEq(previewAssets, assets, "Preview assets should be equal to assets"); @@ -956,7 +959,7 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU beforeBobBalance - amount + assets, "Bob should have the amount deposited after withdraw" ); - assertEq(vault.balanceOf(bob), beforeBobShares, "Bob should have no shares after withdraw"); + assertEqThreshold(vault.balanceOf(bob), beforeBobShares, 5, "Bob should have no shares after withdraw"); } function test_Vault_ynBNBk_deposit_and_stake_slisBNB(uint256 amount) public { @@ -1072,10 +1075,13 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU uint256 beforeBobBalance = asset.balanceOf(bob); uint256 beforeBobShares = vault.balanceOf(bob); - uint256 previewAssets = vault.previewRedeemAsset(MC.SLISBNB, beforeBobShares); + uint256 maxRedeem = vault.maxRedeemAsset(MC.SLISBNB, bob); + uint256 previewAssets = vault.previewRedeemAsset(MC.SLISBNB, maxRedeem); + + assertEqThreshold(maxRedeem, beforeBobShares, 5, "Max redeem should be equal to shares"); vm.prank(bob); - uint256 assets = vault.redeemAsset(MC.SLISBNB, beforeBobShares, bob, bob); + uint256 assets = vault.redeemAsset(MC.SLISBNB, maxRedeem, bob, bob); assertEq(previewAssets, assets, "Preview assets should be equal to assets"); @@ -1091,7 +1097,7 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU ); assertEq(afterVaultBalance, beforeVaultBalance, "Vault balance should remain same"); assertEq(afterBobBalance, beforeBobBalance + assets, "Bob balance should increase by assets"); - assertEq(afterBobShares, 0, "Bob shares should decrease by shares"); + assertEqThreshold(afterBobShares, 0, 5, "Bob shares should decrease by shares"); } } } diff --git a/test/unit/withdraw.t.sol b/test/unit/withdraw.t.sol index 779ce5e8..bcbd700c 100644 --- a/test/unit/withdraw.t.sol +++ b/test/unit/withdraw.t.sol @@ -69,9 +69,10 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { depositAmount = bound(depositAmount, 10, INITIAL_BALANCE); // deposit amount vm.prank(alice); - vault.deposit(depositAmount, alice); + uint256 shares = vault.deposit(depositAmount, alice); assertEq(vault.maxWithdrawAsset(MC.WBNB, alice), depositAmount); + assertEq(vault.maxRedeemAsset(MC.WBNB, alice), shares); } function test_KernelStrategy_ynBNBk_withdraw_previewWithdrawAsset(uint256 depositAmount, uint256 withdrawalAmount) @@ -515,10 +516,12 @@ contract KernelStrategyWithdrawUnitTest is SetupKernelStrategy { assertEq(maxWithdraw, maxWithdraw1, "maxWithdraw is incorrect"); assertEq(maxWithdraw1, depositAmount, "Max withdraw for asset1 is incorrect"); + assertEq(vault.maxRedeemAsset(address(asset1), alice), shares1, "maxRedeem is incorrect"); uint256 maxWithdraw2 = vault.maxWithdrawAsset(address(asset2), alice); assertEq(maxWithdraw2, depositAmount, "Max withdraw for asset2 is incorrect"); + assertEq(vault.maxRedeemAsset(address(asset2), alice), shares2, "maxRedeem is incorrect"); } { From 6750e785559552f75421e15ec8e24028daff378a Mon Sep 17 00:00:00 2001 From: Dan OneTree Date: Fri, 3 Jan 2025 22:43:52 +0530 Subject: [PATCH 3/6] reduced size of MigratedKernelStrategy --- script/bnb/deploy_ynbnbk.s.sol | 36 ++++++++++++++++---------- src/MigratedKernelStrategy.sol | 46 +++------------------------------- test/mainnet/ynbnbk.spec.sol | 16 ++++++------ 3 files changed, 35 insertions(+), 63 deletions(-) diff --git a/script/bnb/deploy_ynbnbk.s.sol b/script/bnb/deploy_ynbnbk.s.sol index 4b7ac974..1ec8596c 100644 --- a/script/bnb/deploy_ynbnbk.s.sol +++ b/script/bnb/deploy_ynbnbk.s.sol @@ -91,20 +91,12 @@ contract DeployYnBNBkStrategy is BaseKernelScript, BatchScript { function deployMigrateVault(bool isSafeTx) internal { implementation = Vault(payable(address(new MigratedKernelStrategy()))); - MigratedKernelStrategy.Asset[] memory assets = new MigratedKernelStrategy.Asset[](3); - - assets[0] = MigratedKernelStrategy.Asset({asset: contracts.WBNB(), active: false}); - assets[1] = MigratedKernelStrategy.Asset({asset: contracts.SLISBNB(), active: true}); - assets[2] = MigratedKernelStrategy.Asset({asset: contracts.BNBX(), active: true}); - if (isSafeTx) { bytes memory initData = abi.encodeWithSelector( MigratedKernelStrategy.initializeAndMigrate.selector, actors_.ADMIN(), "YieldNest Restaked BNB - Kernel", symbol(), - assets, - contracts.STAKER_GATEWAY(), 0 ); @@ -115,8 +107,6 @@ contract DeployYnBNBkStrategy is BaseKernelScript, BatchScript { msg.sender, "YieldNest Restaked BNB - Kernel", symbol(), - assets, - contracts.STAKER_GATEWAY(), 0 ); @@ -186,6 +176,14 @@ contract DeployYnBNBkStrategy is BaseKernelScript, BatchScript { IStakerGateway stakerGateway = IStakerGateway(contracts.STAKER_GATEWAY()); + vault_.setStakerGateway(contracts.STAKER_GATEWAY()); + vault_.setSyncDeposit(true); + vault_.setSyncWithdraw(true); + + vault_.addAsset(contracts.WBNB(), false); + vault_.addAsset(contracts.SLISBNB(), true); + vault_.addAsset(contracts.BNBX(), true); + vault_.addAssetWithDecimals(stakerGateway.getVault(contracts.WBNB()), 18, false); vault_.addAssetWithDecimals(stakerGateway.getVault(contracts.SLISBNB()), 18, false); vault_.addAssetWithDecimals(stakerGateway.getVault(contracts.BNBX()), 18, false); @@ -206,9 +204,7 @@ contract DeployYnBNBkStrategy is BaseKernelScript, BatchScript { addToBatch( address(vault), 0, - abi.encodeWithSelector( - AccessControlUpgradeable.grantRole.selector, keccak256("DEFAULT_ADMIN_ROLE"), actors_.ADMIN() - ) + abi.encodeWithSelector(AccessControlUpgradeable.grantRole.selector, bytes32(0), actors_.ADMIN()) ); addToBatch( address(vault), @@ -305,6 +301,15 @@ contract DeployYnBNBkStrategy is BaseKernelScript, BatchScript { ) ); addToBatch(address(vault), 0, abi.encodeWithSelector(IVault.setProvider.selector, address(rateProvider))); + + addToBatch( + address(vault), + 0, + abi.encodeWithSelector(KernelStrategy.setStakerGateway.selector, contracts.STAKER_GATEWAY()) + ); + addToBatch(address(vault), 0, abi.encodeWithSelector(KernelStrategy.setSyncDeposit.selector, true)); + addToBatch(address(vault), 0, abi.encodeWithSelector(KernelStrategy.setSyncWithdraw.selector, true)); + addToBatch( address(vault), 0, @@ -323,6 +328,11 @@ contract DeployYnBNBkStrategy is BaseKernelScript, BatchScript { AccessControlUpgradeable.grantRole.selector, keccak256("ASSET_MANAGER_ROLE"), actors_.ADMIN() ) ); + + addToBatch(address(vault), 0, abi.encodeWithSelector(IVault.addAsset.selector, contracts.WBNB(), false)); + addToBatch(address(vault), 0, abi.encodeWithSelector(IVault.addAsset.selector, contracts.SLISBNB(), true)); + addToBatch(address(vault), 0, abi.encodeWithSelector(IVault.addAsset.selector, contracts.BNBX(), true)); + addToBatch( address(vault), 0, diff --git a/src/MigratedKernelStrategy.sol b/src/MigratedKernelStrategy.sol index b30e1445..7b9ec9f6 100644 --- a/src/MigratedKernelStrategy.sol +++ b/src/MigratedKernelStrategy.sol @@ -19,16 +19,6 @@ contract MigratedKernelStrategy is KernelStrategy { uint8 _underlyingDecimals; } - /** - * @dev Structure to represent an asset's details. - * @param asset The address of the asset. - * @param active Whether the asset is active. - */ - struct Asset { - address asset; - bool active; - } - /** * @notice Retrieves the storage location for ERC4626 storage. * @dev The storage slot is hardcoded for optimized access. @@ -45,46 +35,18 @@ contract MigratedKernelStrategy is KernelStrategy { * @param admin The address of the admin. * @param name The name of the vault. * @param symbol The symbol of the vault. - * @param assets The array of Asset structs containing addresses and active states. - * @param stakerGateway The address of the staker gateway. * @param baseWithdrawalFee The base fee for withdrawals. */ - function initializeAndMigrate( - address admin, - string memory name, - string memory symbol, - Asset[] calldata assets, - address stakerGateway, - uint64 baseWithdrawalFee - ) external reinitializer(2) { + function initializeAndMigrate(address admin, string memory name, string memory symbol, uint64 baseWithdrawalFee) + external + reinitializer(2) + { _initialize(admin, name, symbol, 18, baseWithdrawalFee, true, true); - StrategyStorage storage strategyStorage = _getStrategyStorage(); - strategyStorage.stakerGateway = stakerGateway; - strategyStorage.syncDeposit = true; - strategyStorage.syncWithdraw = true; - - _migrate(assets); - } - - /** - * @notice Migrates assets to the vault and resets ERC4626 storage. - * @dev This function clears the previous storage and adds new assets. - * @param assets The array of assets to be added to the vault. - */ - function _migrate(Asset[] memory assets) private { ERC4626Storage storage erc4626Storage = _getERC4626Storage(); // Clear existing storage erc4626Storage._asset = IERC20(0x0000000000000000000000000000000000000000); erc4626Storage._underlyingDecimals = 0; - - // Add new assets - Asset memory tempAsset; - for (uint256 i; i < assets.length; i++) { - tempAsset = assets[i]; - - _addAsset(tempAsset.asset, 18, tempAsset.active); - } } } diff --git a/test/mainnet/ynbnbk.spec.sol b/test/mainnet/ynbnbk.spec.sol index 896d9354..07b2d230 100644 --- a/test/mainnet/ynbnbk.spec.sol +++ b/test/mainnet/ynbnbk.spec.sol @@ -111,12 +111,6 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU } { - MigratedKernelStrategy.Asset[] memory assets = new MigratedKernelStrategy.Asset[](3); - - assets[0] = MigratedKernelStrategy.Asset({asset: MC.WBNB, active: false}); - assets[1] = MigratedKernelStrategy.Asset({asset: MC.SLISBNB, active: true}); - assets[2] = MigratedKernelStrategy.Asset({asset: MC.BNBX, active: true}); - vm.prank(proxyAdmin.owner()); proxyAdmin.upgradeAndCall( @@ -127,8 +121,6 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU address(ADMIN), "YieldNest Restaked BNB - Kernel", "ynBNBk", - assets, - MC.STAKER_GATEWAY, 0 // base fee ) ); @@ -216,6 +208,14 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU // set provider vault_.setProvider(address(MC.PROVIDER)); + vault_.setStakerGateway(MC.STAKER_GATEWAY); + + vault_.setSyncDeposit(true); + vault_.setSyncWithdraw(true); + + vault_.addAsset(MC.WBNB, false); + vault_.addAsset(MC.SLISBNB, true); + vault_.addAsset(MC.BNBX, true); vault_.addAssetWithDecimals(IStakerGateway(MC.STAKER_GATEWAY).getVault(MC.WBNB), 18, false); vault_.addAssetWithDecimals(IStakerGateway(MC.STAKER_GATEWAY).getVault(MC.SLISBNB), 18, false); From aada2059ecff11bd76519d2707e1a53bf10d74f1 Mon Sep 17 00:00:00 2001 From: Dan OneTree Date: Sat, 4 Jan 2025 13:30:42 +0530 Subject: [PATCH 4/6] calculate fees in max view functions + tests --- src/KernelClisStrategy.sol | 19 +- src/KernelStrategy.sol | 153 +++++++++++++--- test/mainnet/ynbnbk.spec.sol | 138 +++++++++++++- test/unit/helpers/SetupKernelStrategy.sol | 6 +- test/unit/withdrawfees.t.sol | 209 ++++++++++++++++++++++ 5 files changed, 481 insertions(+), 44 deletions(-) create mode 100644 test/unit/withdrawfees.t.sol diff --git a/src/KernelClisStrategy.sol b/src/KernelClisStrategy.sol index 69cb1be4..f1d4af5d 100644 --- a/src/KernelClisStrategy.sol +++ b/src/KernelClisStrategy.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; import {KernelStrategy} from "./KernelStrategy.sol"; -import {IERC20, Math} from "lib/yieldnest-vault/src/Common.sol"; +import {IERC20} from "lib/yieldnest-vault/src/Common.sol"; import {IWBNB} from "src/interface/external/IWBNB.sol"; import {IKernelConfig} from "src/interface/external/kernel/IKernelConfig.sol"; @@ -42,17 +42,8 @@ contract KernelClisStrategy is KernelStrategy { IWBNB(asset_).deposit{value: assets}(); } - /** - * @dev See {maxWithdrawAsset}. - */ - function _maxWithdrawAsset(address asset_, address owner) internal view override returns (uint256 maxAssets) { - if (paused() || !_getAssetStorage().assets[asset_].active) { - return 0; - } - - (maxAssets,) = _convertToAssets(asset_, balanceOf(owner), Math.Rounding.Floor); - - uint256 availableAssets = IERC20(asset_).balanceOf(address(this)); + function _availableAssets(address asset_) internal view virtual override returns (uint256 availableAssets) { + availableAssets = IERC20(asset_).balanceOf(address(this)); StrategyStorage storage strategyStorage = _getStrategyStorage(); @@ -62,9 +53,5 @@ contract KernelClisStrategy is KernelStrategy { uint256 availableAssetsInKernel = stakerGateway.balanceOf(clisbnb, address(this)); availableAssets += availableAssetsInKernel; } - - if (availableAssets < maxAssets) { - maxAssets = availableAssets; - } } } diff --git a/src/KernelStrategy.sol b/src/KernelStrategy.sol index fb441a57..b149f61c 100644 --- a/src/KernelStrategy.sol +++ b/src/KernelStrategy.sol @@ -95,24 +95,6 @@ contract KernelStrategy is Vault { maxAssets = _maxWithdrawAsset(asset(), owner); } - /** - * @notice Returns the maximum amount of shares that can be redeemed by a given owner. - * @param owner The address of the owner. - * @return maxShares The maximum amount of shares. - */ - function maxRedeem(address owner) public view override returns (uint256 maxShares) { - maxShares = _maxRedeemAsset(asset(), owner); - } - - function maxRedeemAsset(address asset_, address owner) public view returns (uint256 maxShares) { - maxShares = _maxRedeemAsset(asset_, owner); - } - - function _maxRedeemAsset(address asset_, address owner) internal view virtual returns (uint256 maxShares) { - uint256 maxAssets = _maxWithdrawAsset(asset_, owner); - (maxShares,) = _convertToShares(asset_, maxAssets, Math.Rounding.Floor); - } - /** * @notice Returns the maximum amount of assets that can be withdrawn for a specific asset by a given owner. * @param asset_ The address of the asset. @@ -123,14 +105,31 @@ contract KernelStrategy is Vault { maxAssets = _maxWithdrawAsset(asset_, owner); } + /** + * @notice Internal function to get the maximum amount of assets that can be withdrawn by a given owner. + * @param asset_ The address of the asset. + * @param owner The address of the owner. + * @return maxAssets The maximum amount of assets. + */ function _maxWithdrawAsset(address asset_, address owner) internal view virtual returns (uint256 maxAssets) { if (paused() || !_getAssetStorage().assets[asset_].active) { return 0; } - (maxAssets,) = _convertToAssets(asset_, balanceOf(owner), Math.Rounding.Floor); + uint256 availableAssets = _availableAssets(asset_); + + maxAssets = previewRedeemAsset(asset_, balanceOf(owner)); - uint256 availableAssets = IERC20(asset_).balanceOf(address(this)); + maxAssets = availableAssets < maxAssets ? availableAssets : maxAssets; + } + + /** + * @notice Internal function to get the available amount of assets. + * @param asset_ The address of the asset. + * @return availableAssets The available amount of assets. + */ + function _availableAssets(address asset_) internal view virtual returns (uint256 availableAssets) { + availableAssets = IERC20(asset_).balanceOf(address(this)); StrategyStorage storage strategyStorage = _getStrategyStorage(); @@ -139,10 +138,55 @@ contract KernelStrategy is Vault { IStakerGateway(strategyStorage.stakerGateway).balanceOf(asset_, address(this)); availableAssets += availableAssetsInKernel; } + } + + /** + * @notice Returns the maximum amount of shares that can be redeemed by a given owner. + * @param owner The address of the owner. + * @return maxShares The maximum amount of shares. + */ + function maxRedeem(address owner) public view override returns (uint256 maxShares) { + maxShares = _maxRedeemAsset(asset(), owner); + } - if (availableAssets < maxAssets) { - maxAssets = availableAssets; + /** + * @notice Returns the maximum amount of shares that can be redeemed by a given owner. + * @param asset_ The address of the asset. + * @param owner The address of the owner. + * @return maxShares The maximum amount of shares. + */ + function maxRedeemAsset(address asset_, address owner) public view returns (uint256 maxShares) { + maxShares = _maxRedeemAsset(asset_, owner); + } + + /** + * @notice Internal function to get the maximum amount of shares that can be redeemed by a given owner. + * @param asset_ The address of the asset. + * @param owner The address of the owner. + * @return maxShares The maximum amount of shares. + */ + function _maxRedeemAsset(address asset_, address owner) internal view virtual returns (uint256 maxShares) { + if (paused() || !_getAssetStorage().assets[asset_].active) { + return 0; } + + uint256 availableAssets = _availableAssets(asset_); + + maxShares = balanceOf(owner); + + maxShares = availableAssets < previewRedeemAsset(asset_, maxShares) + ? previewWithdrawAsset(asset_, availableAssets) + : maxShares; + } + + /** + * @notice Previews the amount of assets that would be required to mint a given amount of shares. + * @param asset_ The address of the asset. + * @param shares The amount of shares to mint. + * @return assets The equivalent amount of assets. + */ + function previewMintAsset(address asset_, uint256 shares) public view virtual returns (uint256 assets) { + (assets,) = _convertToAssets(asset_, shares, Math.Rounding.Ceil); } /** @@ -164,8 +208,24 @@ contract KernelStrategy is Vault { */ function previewRedeemAsset(address asset_, uint256 shares) public view virtual returns (uint256 assets) { (assets,) = _convertToAssets(asset_, shares, Math.Rounding.Floor); + assets = assets - _feeOnTotal(assets); + } - return assets - _feeOnTotal(assets); + /** + * @notice Withdraws a given amount of assets and burns the equivalent amount of shares from the owner. + * @param assets The amount of assets to withdraw. + * @param receiver The address of the receiver. + * @param owner The address of the owner. + * @return shares The equivalent amount of shares. + */ + function withdraw(uint256 assets, address receiver, address owner) + public + virtual + override + nonReentrant + returns (uint256 shares) + { + shares = _withdrawAsset(asset(), assets, receiver, owner); } /** @@ -181,6 +241,21 @@ contract KernelStrategy is Vault { virtual nonReentrant returns (uint256 shares) + { + shares = _withdrawAsset(asset_, assets, receiver, owner); + } + + /** + * @notice Internal function for withdraws assets and burns equivalent shares from the owner. + * @param asset_ The address of the asset. + * @param assets The amount of assets to withdraw. + * @param receiver The address of the receiver. + * @param owner The address of the owner. + * @return shares The equivalent amount of shares burned. + */ + function _withdrawAsset(address asset_, uint256 assets, address receiver, address owner) + internal + returns (uint256 shares) { if (paused()) { revert Paused(); @@ -193,6 +268,23 @@ contract KernelStrategy is Vault { _withdrawAsset(asset_, _msgSender(), receiver, owner, assets, shares); } + /** + * @notice Redeems a given amount of shares and transfers the equivalent amount of assets to the receiver. + * @param shares The amount of shares to redeem. + * @param receiver The address of the receiver. + * @param owner The address of the owner. + * @return assets The equivalent amount of assets. + */ + function redeem(uint256 shares, address receiver, address owner) + public + virtual + override + nonReentrant + returns (uint256 assets) + { + assets = _redeemAsset(asset(), shares, receiver, owner); + } + /** * @notice Redeems shares and transfers equivalent assets to the receiver. * @param asset_ The address of the asset. @@ -206,6 +298,21 @@ contract KernelStrategy is Vault { virtual nonReentrant returns (uint256 assets) + { + assets = _redeemAsset(asset_, shares, receiver, owner); + } + + /** + * @notice Internal function for redeems shares and transfers equivalent assets to the receiver. + * @param asset_ The address of the asset. + * @param shares The amount of shares to redeem. + * @param receiver The address of the receiver. + * @param owner The address of the owner. + * @return assets The equivalent amount of assets. + */ + function _redeemAsset(address asset_, uint256 shares, address receiver, address owner) + internal + returns (uint256 assets) { if (paused()) { revert Paused(); diff --git a/test/mainnet/ynbnbk.spec.sol b/test/mainnet/ynbnbk.spec.sol index 07b2d230..f724ce02 100644 --- a/test/mainnet/ynbnbk.spec.sol +++ b/test/mainnet/ynbnbk.spec.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.24; import {Test} from "lib/forge-std/src/Test.sol"; +import {FeeMath} from "lib/yieldnest-vault/src/module/FeeMath.sol"; + import {Initializable} from "lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol"; import { ITransparentUpgradeableProxy, @@ -205,6 +207,7 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU vault_.grantRole(vault_.KERNEL_DEPENDENCY_MANAGER_ROLE(), ADMIN); vault_.grantRole(vault_.DEPOSIT_MANAGER_ROLE(), ADMIN); vault_.grantRole(vault_.ALLOCATOR_MANAGER_ROLE(), ADMIN); + vault_.grantRole(vault_.FEE_MANAGER_ROLE(), ADMIN); // set provider vault_.setProvider(address(MC.PROVIDER)); @@ -923,7 +926,7 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU } function test_Vault_ynBNBk_redeem_slisBNB(uint256 amount) public { - amount = bound(amount, 10, 100_000 ether); + amount = bound(amount, 1 ether, 100_000 ether); getSlisBnb(amount); @@ -959,7 +962,7 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU beforeBobBalance - amount + assets, "Bob should have the amount deposited after withdraw" ); - assertEqThreshold(vault.balanceOf(bob), beforeBobShares, 5, "Bob should have no shares after withdraw"); + assertEq(vault.balanceOf(bob), beforeBobShares + shares - maxRedeem, "Bob should have no shares after withdraw"); } function test_Vault_ynBNBk_deposit_and_stake_slisBNB(uint256 amount) public { @@ -1043,7 +1046,7 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU } function test_Vault_ynBNBk_deposit_and_stake_and_redeem_slisBNB(uint256 amount) public { - amount = bound(amount, 10, 100_000 ether); + amount = bound(amount, 1 ether, 100_000 ether); getSlisBnb(amount); @@ -1097,7 +1100,134 @@ contract YnBNBkTest is Test, AssertUtils, MainnetKernelActors, EtchUtils, VaultU ); assertEq(afterVaultBalance, beforeVaultBalance, "Vault balance should remain same"); assertEq(afterBobBalance, beforeBobBalance + assets, "Bob balance should increase by assets"); - assertEqThreshold(afterBobShares, 0, 5, "Bob shares should decrease by shares"); + assertEq(afterBobShares, beforeBobShares - maxRedeem, "Bob shares should decrease by shares"); } } + + function test_Vault_maxRedeemWithFees(uint256 assets) external { + // Bound inputs to valid ranges + vm.assume(assets >= 100000 && assets <= 100_000 ether); + + vm.startPrank(ADMIN); + vault.setBaseWithdrawalFee(100_000); // Set base withdrawal fee to 0.1% (0.1% * 1e8) + vm.stopPrank(); + + getSlisBnb(assets); + + vm.prank(bob); + IERC20(MC.SLISBNB).approve(address(vault), assets); + + vm.prank(bob); + uint256 shares = vault.depositAsset(MC.SLISBNB, assets, bob); + + uint256 maxShares = vault.maxRedeemAsset(MC.SLISBNB, bob); + uint256 expectedAssets = vault.previewRedeemAsset(MC.SLISBNB, maxShares); + + uint256 convertedAssets = vault.previewMintAsset(MC.SLISBNB, maxShares); + uint256 expectedFee = (expectedAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + + vm.prank(bob); + uint256 redeemedAmount = vault.redeemAsset(MC.SLISBNB, maxShares, bob, bob); + + assertApproxEqRel(redeemedAmount, expectedAssets, 1e14, "Redeemed amount should match preview"); + + assertApproxEqRel( + redeemedAmount, convertedAssets - expectedFee, 1e14, "Redeemed amount should be total assets minus fee" + ); + + assertEq(vault.balanceOf(bob), shares - maxShares, "Alice should have correct shares remaining"); + } + + function test_Vault_maxWithdrawWithFees(uint256 assets) external { + // Bound inputs to valid ranges + vm.assume(assets >= 1000 && assets <= 100_000 ether); + + vm.startPrank(ADMIN); + vault.setBaseWithdrawalFee(100_000); // Set base withdrawal fee to 0.1% (0.1% * 1e8) + vm.stopPrank(); + + getSlisBnb(assets); + + vm.prank(bob); + IERC20(MC.SLISBNB).approve(address(vault), assets); + + vm.prank(bob); + vault.depositAsset(MC.SLISBNB, assets, bob); + + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.SLISBNB, bob); + uint256 previewRedeemAssets = vault.previewRedeemAsset(MC.SLISBNB, vault.balanceOf(bob)); + + assertEq( + maxWithdraw, previewRedeemAssets, "Max withdraw should equal previewRedeemAssets assets with full buffer" + ); + + uint256 expectedFee = (maxWithdraw * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + uint256 expectedShares = vault.previewDepositAsset(MC.SLISBNB, maxWithdraw + expectedFee); + + // Verify we can actually withdraw the max amount + vm.prank(bob); + uint256 withdrawnShares = vault.withdrawAsset(MC.SLISBNB, maxWithdraw, bob, bob); + + assertApproxEqAbs(withdrawnShares, expectedShares, 5, "Withdrawn shares should match expected with fee"); + assertApproxEqAbs(vault.balanceOf(bob), 0, 5, "Alice should have no shares remaining"); + } + + function test_Vault_redeemWithFees(uint256 assets, uint256 withdrawnAssets) external { + // Bound inputs to valid ranges + vm.assume(assets >= 100000 && assets <= 100_000 ether); + vm.assume(withdrawnAssets <= assets); + vm.assume(withdrawnAssets > 100000); + + vm.startPrank(ADMIN); + vault.setBaseWithdrawalFee(100_000); // Set base withdrawal fee to 0.1% (0.1% * 1e8) + vm.stopPrank(); + + getSlisBnb(assets); + + vm.prank(bob); + IERC20(MC.SLISBNB).approve(address(vault), assets); + + vm.prank(bob); + vault.depositAsset(MC.SLISBNB, assets, bob); + + uint256 withdrawnShares = vault.previewDepositAsset(MC.SLISBNB, withdrawnAssets); + + vm.prank(bob); + uint256 redeemedAmount = vault.redeemAsset(MC.SLISBNB, withdrawnShares, bob, bob); + uint256 expectedFee = (withdrawnAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + assertApproxEqRel( + redeemedAmount, withdrawnAssets - expectedFee, 1e14, "Withdrawal fee should be 0.1% of assets" + ); + } + + function test_Vault_withdrawWithFees(uint256 assets, uint256 withdrawnAssets) external { + vm.assume(assets >= 100000 && assets <= 10_000 ether); + vm.assume(withdrawnAssets <= assets); + vm.assume(withdrawnAssets > 0); + + vm.startPrank(ADMIN); + vault.setBaseWithdrawalFee(100_000); // Set base withdrawal fee to 0.1% (0.1% * 1e8) + vm.stopPrank(); + + getSlisBnb(assets); + + vm.prank(bob); + IERC20(MC.SLISBNB).approve(address(vault), assets); + + vm.prank(bob); + vault.depositAsset(MC.SLISBNB, assets, bob); + + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.SLISBNB, bob); + if (withdrawnAssets > maxWithdraw) { + withdrawnAssets = maxWithdraw; + } + + uint256 expectedFee = (withdrawnAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + uint256 expectedShares = vault.previewDepositAsset(MC.SLISBNB, withdrawnAssets + expectedFee); + + vm.prank(bob); + uint256 withdrawAmount = vault.withdrawAsset(MC.SLISBNB, withdrawnAssets, bob, bob); + + assertApproxEqAbs(withdrawAmount, expectedShares, 5, "Preview withdraw shares should match expected"); + } } diff --git a/test/unit/helpers/SetupKernelStrategy.sol b/test/unit/helpers/SetupKernelStrategy.sol index eebb7e0c..88b2d954 100644 --- a/test/unit/helpers/SetupKernelStrategy.sol +++ b/test/unit/helpers/SetupKernelStrategy.sol @@ -36,7 +36,10 @@ contract SetupKernelStrategy is Test, AssertUtils, MainnetKernelActors, EtchUtil IStakerGateway public mockGateway; MockRateProvider public lowDecimalProvider; - address public alice = address(0xa11ce); + address public alice = address(0x0a11ce); + address public bob = address(0x0b0b); + address public chad = address(0x0cad); + uint256 public constant INITIAL_BALANCE = 100_000 ether; function deploy() public { @@ -90,6 +93,7 @@ contract SetupKernelStrategy is Test, AssertUtils, MainnetKernelActors, EtchUtil vault.grantRole(vault.KERNEL_DEPENDENCY_MANAGER_ROLE(), ADMIN); vault.grantRole(vault.DEPOSIT_MANAGER_ROLE(), ADMIN); vault.grantRole(vault.ALLOCATOR_MANAGER_ROLE(), ADMIN); + vault.grantRole(vault.FEE_MANAGER_ROLE(), ADMIN); // set provider vault.setProvider(address(provider)); diff --git a/test/unit/withdrawfees.t.sol b/test/unit/withdrawfees.t.sol new file mode 100644 index 00000000..582d190b --- /dev/null +++ b/test/unit/withdrawfees.t.sol @@ -0,0 +1,209 @@ +// SPDX-License-Identifier: BSD Clause-3 +pragma solidity ^0.8.24; + +import {FeeMath} from "lib/yieldnest-vault/src/module/FeeMath.sol"; + +import {MainnetContracts as MC} from "script/Contracts.sol"; + +import {SetupKernelStrategy} from "test/unit/helpers/SetupKernelStrategy.sol"; + +contract KernelStrategyWithdrawFeesUnitTest is SetupKernelStrategy { + function setUp() public { + deploy(); + + // Give Alice some tokens + deal(alice, INITIAL_BALANCE); + wbnb.deposit{value: INITIAL_BALANCE}(); + wbnb.transfer(alice, INITIAL_BALANCE); + + // Approve vault to spend Alice's tokens + vm.startPrank(alice); + wbnb.approve(address(vault), type(uint256).max); + vm.stopPrank(); + + vm.startPrank(ADMIN); + vault.setSyncDeposit(true); + vault.setSyncWithdraw(true); + vault.setBaseWithdrawalFee(100_000); // Set base withdrawal fee to 0.1% (0.1% * 1e8) + vm.stopPrank(); + } + + function test_KernelStrategy_previewRedeemWithFees(uint256 assets, uint256 withdrawnAssets) external { + // Bound inputs to valid ranges + vm.assume(assets >= 100000 && assets <= 100_000 ether); + vm.assume(withdrawnAssets <= assets); + vm.assume(withdrawnAssets > 100000); + + vm.prank(alice); + vault.depositAsset(MC.WBNB, assets, alice); + + uint256 withdrawnShares = vault.previewDepositAsset(MC.WBNB, withdrawnAssets); + uint256 redeemedPreview = vault.previewRedeemAsset(MC.WBNB, withdrawnShares); + + uint256 expectedFee = (withdrawnAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + + assertApproxEqRel( + redeemedPreview, withdrawnAssets - expectedFee, 1e14, "Withdrawal fee should be 0.1% of assets" + ); + } + + function test_KernelStrategy_previewWithdrawWithFees(uint256 assets, uint256 withdrawnAssets) external { + vm.assume(assets >= 100000 && assets <= 100_000 ether); + vm.assume(withdrawnAssets <= assets); + vm.assume(withdrawnAssets > 0); + + vm.prank(alice); + vault.depositAsset(MC.WBNB, assets, alice); + + uint256 withdrawPreview = vault.previewWithdrawAsset(MC.WBNB, withdrawnAssets); + + // Base withdrawal fee is 0.1% (100_000) + // Buffer flat fee ratio is 80% (80_000_000) + // Vault buffer fraction is 10% (10_000_000) + uint256 expectedFee = (withdrawnAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + uint256 expectedShares = vault.previewDepositAsset(MC.WBNB, withdrawnAssets + expectedFee); + assertApproxEqAbs(withdrawPreview, expectedShares, 1, "Preview withdraw shares should match expected"); + } + + function test_KernelStrategy_maxRedeemWithFees(uint256 assets) external { + // Bound inputs to valid ranges + vm.assume(assets >= 100000 && assets <= 100_000 ether); + + vm.prank(alice); + uint256 shares = vault.depositAsset(MC.WBNB, assets, alice); + + uint256 maxShares = vault.maxRedeemAsset(MC.WBNB, alice); + uint256 expectedAssets = vault.previewRedeemAsset(MC.WBNB, maxShares); + + uint256 convertedAssets = vault.previewMintAsset(MC.WBNB, maxShares); + uint256 expectedFee = (expectedAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + + vm.prank(alice); + uint256 redeemedAmount = vault.redeemAsset(MC.WBNB, maxShares, alice, alice); + + assertApproxEqRel(redeemedAmount, expectedAssets, 1e14, "Redeemed amount should match preview"); + + assertApproxEqRel( + redeemedAmount, convertedAssets - expectedFee, 1e14, "Redeemed amount should be total assets minus fee" + ); + + assertEq(vault.balanceOf(alice), shares - maxShares, "Alice should have correct shares remaining"); + } + + function test_KernelStrategy_maxWithdrawWithFees(uint256 assets) external { + // Bound inputs to valid ranges + vm.assume(assets >= 1000 && assets <= 100_000 ether); + + vm.prank(alice); + vault.depositAsset(MC.WBNB, assets, alice); + + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.WBNB, alice); + uint256 previewRedeemAssets = vault.previewRedeemAsset(MC.WBNB, vault.balanceOf(alice)); + + assertEq( + maxWithdraw, previewRedeemAssets, "Max withdraw should equal previewRedeemAssets assets with full buffer" + ); + + uint256 expectedFee = (maxWithdraw * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + uint256 expectedShares = vault.previewDepositAsset(MC.WBNB, maxWithdraw + expectedFee); + + // Verify we can actually withdraw the max amount + vm.prank(alice); + uint256 withdrawnShares = vault.withdrawAsset(MC.WBNB, maxWithdraw, alice, alice); + + assertApproxEqAbs(withdrawnShares, expectedShares, 2, "Withdrawn shares should match expected with fee"); + assertApproxEqAbs(vault.balanceOf(alice), 0, 1, "Alice should have no shares remaining"); + } + + function test_KernelStrategy_redeemWithFees(uint256 assets, uint256 withdrawnAssets) external { + // Bound inputs to valid ranges + vm.assume(assets >= 100000 && assets <= 100_000 ether); + vm.assume(withdrawnAssets <= assets); + vm.assume(withdrawnAssets > 100000); + + vm.prank(alice); + vault.depositAsset(MC.WBNB, assets, alice); + + uint256 withdrawnShares = vault.previewDepositAsset(MC.WBNB, withdrawnAssets); + + vm.prank(alice); + uint256 redeemedAmount = vault.redeemAsset(MC.WBNB, withdrawnShares, alice, alice); + uint256 expectedFee = (withdrawnAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + assertApproxEqRel( + redeemedAmount, withdrawnAssets - expectedFee, 1e14, "Withdrawal fee should be 0.1% of assets" + ); + } + + function test_KernelStrategy_withdrawWithFees(uint256 assets, uint256 withdrawnAssets) external { + vm.assume(assets >= 100000 && assets <= 10_000 ether); + vm.assume(withdrawnAssets <= assets); + vm.assume(withdrawnAssets > 0); + + vm.prank(alice); + vault.depositAsset(MC.WBNB, assets, alice); + + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.WBNB, alice); + if (withdrawnAssets > maxWithdraw) { + withdrawnAssets = maxWithdraw; + } + + uint256 expectedFee = (withdrawnAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + uint256 expectedShares = vault.previewDepositAsset(MC.WBNB, withdrawnAssets + expectedFee); + + vm.prank(alice); + uint256 withdrawAmount = vault.withdrawAsset(MC.WBNB, withdrawnAssets, alice, alice); + + assertApproxEqAbs(withdrawAmount, expectedShares, 2, "Preview withdraw shares should match expected"); + } + + function test_KernelStrategy_feeOnRaw_FlatFee(uint256 assets) external { + if (assets < 10) return; + if (assets > 100_000 ether) return; + + vm.prank(alice); + vault.depositAsset(MC.WBNB, assets, alice); + + uint256 withdrawnAssets = assets / 2; + + uint256 fee = vault._feeOnRaw(withdrawnAssets); + + // Base withdrawal fee is 0.1% (100_000) + // Buffer flat fee ratio is 80% (80_000_000) + // Vault buffer fraction is 10% (10_000_000) + uint256 expectedFee = (withdrawnAssets * vault.baseWithdrawalFee()) / FeeMath.BASIS_POINT_SCALE; + assertApproxEqAbs(fee, expectedFee, 1, "Fee should be 0.1% of assets"); + } + + function test_KernelStrategy_withdraw_success(uint256 assets) external { + if (assets < 2) return; + if (assets > 100_000 ether) return; + + vm.prank(alice); + uint256 depositShares = vault.depositAsset(MC.WBNB, assets, alice); + + uint256 aliceBalanceBefore = vault.balanceOf(alice); + uint256 totalAssetsBefore = vault.totalAssets(); + + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.WBNB, alice); + uint256 previewAmount = vault.previewWithdraw(maxWithdraw); + + uint256 expectedFee = vault._feeOnTotal(assets); + + assertEq(maxWithdraw, assets - expectedFee, "Max withdraw should be equal to assets"); + + vm.prank(alice); + uint256 shares = vault.withdrawAsset(MC.WBNB, maxWithdraw, alice, alice); + uint256 totalAssetsAfter = vault.totalAssets(); + uint256 aliceBalanceAfter = vault.balanceOf(alice); + + assertEq(aliceBalanceBefore, aliceBalanceAfter + shares, "Alice's balance should be less the shares withdrawn"); + assertEq(previewAmount, shares, "Preview withdraw amount not preview amount"); + assertEq(depositShares, shares, "Deposit shares not match with withdraw shares"); + assertLt(totalAssetsAfter, totalAssetsBefore, "Total maxWithdraw should be less after withdraw"); + assertEq( + totalAssetsBefore, + totalAssetsAfter + maxWithdraw, + "Total maxWithdraw should be total assets after plus assets withdrawn" + ); + } +} From 152524b6ccb8a4ee06b1fe158bd73ccc1d5e1a2d Mon Sep 17 00:00:00 2001 From: Dan OneTree Date: Sat, 4 Jan 2025 18:57:25 +0530 Subject: [PATCH 5/6] added extra test for withdraw with fees --- test/unit/withdrawfees.t.sol | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/test/unit/withdrawfees.t.sol b/test/unit/withdrawfees.t.sol index 582d190b..dd1b4c09 100644 --- a/test/unit/withdrawfees.t.sol +++ b/test/unit/withdrawfees.t.sol @@ -198,7 +198,7 @@ contract KernelStrategyWithdrawFeesUnitTest is SetupKernelStrategy { assertEq(aliceBalanceBefore, aliceBalanceAfter + shares, "Alice's balance should be less the shares withdrawn"); assertEq(previewAmount, shares, "Preview withdraw amount not preview amount"); - assertEq(depositShares, shares, "Deposit shares not match with withdraw shares"); + assertEqThreshold(depositShares, shares, 10, "Deposit shares not match with withdraw shares"); assertLt(totalAssetsAfter, totalAssetsBefore, "Total maxWithdraw should be less after withdraw"); assertEq( totalAssetsBefore, @@ -206,4 +206,38 @@ contract KernelStrategyWithdrawFeesUnitTest is SetupKernelStrategy { "Total maxWithdraw should be total assets after plus assets withdrawn" ); } + + function test_KernelStrategy_withdraw_sync_disabled(uint256 assets) external { + assets = bound(assets, 2, 50_000 ether); + + vm.prank(alice); + vault.depositAsset(MC.WBNB, assets, alice); + + // the assets are in kernel vault + assertEq(wbnb.balanceOf(address(vault)), 0, "Vault balance should be 0"); + + vm.startPrank(ADMIN); + vault.setSyncDeposit(false); + vault.setSyncWithdraw(false); + vault.setBaseWithdrawalFee(1000_000); // Set base withdrawal fee to 1% (1% * 1e8) + vm.stopPrank(); + + vm.prank(alice); + uint256 depositShares = vault.depositAsset(MC.WBNB, assets, alice); + + assertEq(vault.balanceOf(alice), depositShares * 2, "Alice should have correct shares"); + + assertEq(wbnb.balanceOf(address(vault)), assets, "Vault balance should be assets"); + + uint256 maxWithdraw = vault.maxWithdrawAsset(MC.WBNB, alice); + + assertEq(maxWithdraw, assets, "Max withdraw should be equal to assets"); + + vm.prank(alice); + uint256 shares = vault.withdrawAsset(MC.WBNB, assets, alice, alice); + + uint256 burntShares = depositShares + vault._feeOnRaw(depositShares); + + assertEq(shares, burntShares, "Burnt shares must include fees"); + } } From 5cea29d4ebca661b47de1b8c77806c367e02be63 Mon Sep 17 00:00:00 2001 From: Dan OneTree Date: Sat, 4 Jan 2025 18:58:44 +0530 Subject: [PATCH 6/6] using rate provider for clisBNB --- src/KernelClisStrategy.sol | 2 +- src/utils/KernelClisVaultViewer.sol | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/KernelClisStrategy.sol b/src/KernelClisStrategy.sol index f1d4af5d..f849e191 100644 --- a/src/KernelClisStrategy.sol +++ b/src/KernelClisStrategy.sol @@ -51,7 +51,7 @@ contract KernelClisStrategy is KernelStrategy { IStakerGateway stakerGateway = IStakerGateway(strategyStorage.stakerGateway); address clisbnb = IKernelConfig(stakerGateway.getConfig()).getClisBnbAddress(); uint256 availableAssetsInKernel = stakerGateway.balanceOf(clisbnb, address(this)); - availableAssets += availableAssetsInKernel; + availableAssets += _convertAssetToBase(clisbnb, availableAssetsInKernel); } } } diff --git a/src/utils/KernelClisVaultViewer.sol b/src/utils/KernelClisVaultViewer.sol index 6173685c..c83b4b3c 100644 --- a/src/utils/KernelClisVaultViewer.sol +++ b/src/utils/KernelClisVaultViewer.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause pragma solidity ^0.8.24; -import {IERC20, Math} from "lib/yieldnest-vault/src/Common.sol"; +import {IERC20Metadata as IERC20, Math} from "lib/yieldnest-vault/src/Common.sol"; import {IKernelProvider} from "src/interface/IKernelProvider.sol"; import {IStakerGateway} from "src/interface/external/kernel/IStakerGateway.sol"; @@ -11,6 +11,14 @@ import {IKernelConfig} from "src/interface/external/kernel/IKernelConfig.sol"; import {KernelVaultViewer} from "src/utils/KernelVaultViewer.sol"; contract KernelClisVaultViewer is KernelVaultViewer { + error ZeroAddress(); + + function _convertAssetToBase(address asset_, uint256 assets) internal view virtual returns (uint256) { + if (asset_ == address(0)) revert ZeroAddress(); + uint256 rate = IKernelProvider(vault().provider()).getRate(asset_); + return Math.mulDiv(assets, rate, 10 ** (IERC20(asset_).decimals()), Math.Rounding.Floor); + } + function _maxWithdrawAsset(address asset_, address owner) internal view override returns (uint256 maxAssets) { if (!vault().getAsset(asset_).active) { return 0; @@ -24,7 +32,7 @@ contract KernelClisVaultViewer is KernelVaultViewer { address clisbnb = IKernelConfig(IStakerGateway(vault().getStakerGateway()).getConfig()).getClisBnbAddress(); address kernelVault = IStakerGateway(vault().getStakerGateway()).getVault(clisbnb); uint256 availableAssetsInKernel = IERC20(kernelVault).balanceOf(address(vault())); - availableAssets += availableAssetsInKernel; + availableAssets += _convertAssetToBase(clisbnb, availableAssetsInKernel); } if (availableAssets < maxAssets) {