From 3a0aaa00e0eca27a9d3aa731748d2c385015085a Mon Sep 17 00:00:00 2001 From: ashhanai Date: Wed, 10 Dec 2025 18:20:49 +0100 Subject: [PATCH 1/2] feat: remove vault allocation/deallocation slippage checks --- src/vault/SingleStrategyVault.sol | 38 +--------- tests/fork/SingleStrategyVault.fork.t.sol | 42 ------------ tests/unit/vault/SingleStrategyVault.t.sol | 80 ---------------------- 3 files changed, 2 insertions(+), 158 deletions(-) diff --git a/src/vault/SingleStrategyVault.sol b/src/vault/SingleStrategyVault.sol index da7137ea..e837acbc 100644 --- a/src/vault/SingleStrategyVault.sol +++ b/src/vault/SingleStrategyVault.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.29; import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol"; -import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { ControlledERC7575Vault, SafeERC20, IERC20, IController } from "./ControlledERC7575Vault.sol"; @@ -14,17 +13,6 @@ import { ControlledERC7575Vault, SafeERC20, IERC20, IController } from "./Contro * and deallocates assets when withdrawals require more than the available unallocated balance */ contract SingleStrategyVault is ControlledERC7575Vault { - using Math for uint256; - - /** - * @notice The maximum allowed slippage during allocation/deallocation operations - */ - uint256 public constant MAX_ALLOCATION_SLIPPAGE = 1; // 0.00001% - /** - * @notice The number of decimals used for slippage calculations - */ - uint256 public constant ALLOCATION_SLIPPAGE_DECIMALS = 7; - /** * @notice The ERC4626 vault used for yield generation */ @@ -64,10 +52,6 @@ contract SingleStrategyVault is ControlledERC7575Vault { * @notice Thrown when caller is not the authorized manager */ error CallerNotManager(); - /** - * @notice Thrown when slippage exceeds the maximum allowed during allocation/deallocation - */ - error ExceedsMaxSlippage(); /** * @notice Constructs a new SingleStrategyVault @@ -153,44 +137,26 @@ contract SingleStrategyVault is ControlledERC7575Vault { /** * @notice Internal function to allocate assets to the strategy - * @dev Deposits assets into the strategy and emits an Allocate event. - * Reverts if allocation slippage exceeds the maximum allowed. + * @dev Deposits assets into the strategy and emits an Allocate event * @param assets The amount of assets to deposit into the strategy * @return shares The number of strategy shares received */ function _allocate(uint256 assets) private returns (uint256 shares) { - uint256 originalTotalAssets = totalAssets(); shares = _strategy.deposit(assets, address(this)); - require(totalAssets() >= _slippageMinAssets(originalTotalAssets), ExceedsMaxSlippage()); emit Allocate(address(_strategy), assets); } /** * @notice Internal function to deallocate assets from the strategy - * @dev Withdraws assets from the strategy to this vault and emits a Deallocate event. - * Reverts if deallocation slippage exceeds the maximum allowed. + * @dev Withdraws assets from the strategy to this vault and emits a Deallocate event * @param assets The amount of assets to withdraw from the strategy * @return shares The number of strategy shares burned */ function _deallocate(uint256 assets) private returns (uint256 shares) { - uint256 originalTotalAssets = totalAssets(); shares = _strategy.withdraw(assets, address(this), address(this)); - require(totalAssets() >= _slippageMinAssets(originalTotalAssets), ExceedsMaxSlippage()); emit Deallocate(address(_strategy), assets); } - /** - * @notice Calculates the minimum acceptable assets after accounting for maximum slippage - * @dev Used to ensure that allocation/deallocation does not exceed the allowed slippage - * @param assets The original amount of assets before allocation/deallocation - * @return The minimum acceptable amount of assets after slippage - */ - function _slippageMinAssets(uint256 assets) private pure returns (uint256) { - return assets.mulDiv( - 10 ** ALLOCATION_SLIPPAGE_DECIMALS - MAX_ALLOCATION_SLIPPAGE, 10 ** ALLOCATION_SLIPPAGE_DECIMALS - ); - } - /** * @notice Calculates assets owned by the vault but not held directly in the vault * @dev Override from ControlledERC7575Vault. Returns only assets allocated to strategies, diff --git a/tests/fork/SingleStrategyVault.fork.t.sol b/tests/fork/SingleStrategyVault.fork.t.sol index 44ef26f5..b31c365b 100644 --- a/tests/fork/SingleStrategyVault.fork.t.sol +++ b/tests/fork/SingleStrategyVault.fork.t.sol @@ -95,46 +95,4 @@ contract SingleStrategyVault_Strategy_ForkTest is SingleStrategyVaultForkTest { assertEq(USDC.balanceOf(address(vault)), 0); assertApproxEqAbs(vault.totalAssets(), unallocatedDeposit, maxErrorDelta); // strategy rounds down } - - function test_shouldRevert_whenAllocateWithSlippage() public { - uint256 depositAssets = 200e6; // 200 USDC - - vm.prank(users[0]); - USDC.approve(address(vault), depositAssets); - - bytes[] memory shareBalances = new bytes[](2); - shareBalances[0] = abi.encode(0); - shareBalances[1] = abi.encode(USDC_STRATEGY.convertToShares(depositAssets - 1e6)); // simulate 1 USDC slippage - vm.mockCalls( - address(USDC_STRATEGY), abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), shareBalances - ); - - vm.expectRevert(Vault.ExceedsMaxSlippage.selector); - vm.prank(users[0]); - vault.deposit(depositAssets, users[0]); - } - - function test_shouldRevert_whenDeallocateWithSlippage() public { - uint256 assets = 500e6; // 500 USDC - - vm.startPrank(users[0]); - USDC.approve(address(vault), assets); - vault.deposit(assets, address(this)); - vm.stopPrank(); - - assertEq(USDC.balanceOf(address(vault)), 0); - assertApproxEqAbs(vault.totalAssets(), assets, maxErrorDelta); // strategy rounds down - - uint256 withdrawAssets = 300e6; // 300 USDC - - bytes[] memory assetBalances = new bytes[](3); - assetBalances[0] = abi.encode(0); - assetBalances[1] = abi.encode(0); - assetBalances[2] = abi.encode(withdrawAssets - 1e6); // simulate 1 USDC slippage - vm.mockCalls(address(USDC), abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), assetBalances); - - vm.expectRevert(Vault.ExceedsMaxSlippage.selector); - vm.prank(users[0]); - vault.withdraw(withdrawAssets, users[0], users[0]); - } } diff --git a/tests/unit/vault/SingleStrategyVault.t.sol b/tests/unit/vault/SingleStrategyVault.t.sol index b396b76a..1062482d 100644 --- a/tests/unit/vault/SingleStrategyVault.t.sol +++ b/tests/unit/vault/SingleStrategyVault.t.sol @@ -66,19 +66,6 @@ contract SingleStrategyVault_Allocate_Test is SingleStrategyVaultTest { function setUp() public override { super.setUp(); - bytes[] memory assetBalances = new bytes[](2); - assetBalances[0] = abi.encode(assets); - assetBalances[1] = abi.encode(0); - vm.mockCalls(asset, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), assetBalances); - - bytes[] memory shareBalances = new bytes[](2); - shareBalances[0] = abi.encode(0); - shareBalances[1] = abi.encode(shares); - vm.mockCalls(strategy, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), shareBalances); - - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, 0), abi.encode(0)); - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, shares), abi.encode(assets)); - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.deposit.selector), abi.encode(shares)); } @@ -95,16 +82,6 @@ contract SingleStrategyVault_Allocate_Test is SingleStrategyVaultTest { vault.allocate(assets); } - function testFuzz_shouldRevert_whenSlippageTooHigh(uint256 _assets) public { - _assets = bound(_assets, 0, assets * (1e7 - 1) / 1e7 - 1); - - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, shares), abi.encode(_assets)); - - vm.prank(manager); - vm.expectRevert(Vault.ExceedsMaxSlippage.selector); - vault.allocate(assets); - } - function test_shouldEmit_Allocate() public { vm.expectEmit(); emit Vault.Allocate(strategy, assets); @@ -123,19 +100,6 @@ contract SingleStrategyVault_Deallocate_Test is SingleStrategyVaultTest { function setUp() public override { super.setUp(); - bytes[] memory assetBalances = new bytes[](2); - assetBalances[0] = abi.encode(0); - assetBalances[1] = abi.encode(assets); - vm.mockCalls(asset, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), assetBalances); - - bytes[] memory shareBalances = new bytes[](2); - shareBalances[0] = abi.encode(shares); - shareBalances[1] = abi.encode(0); - vm.mockCalls(strategy, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), shareBalances); - - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, 0), abi.encode(0)); - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, shares), abi.encode(assets)); - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.withdraw.selector), abi.encode(shares)); } @@ -152,19 +116,6 @@ contract SingleStrategyVault_Deallocate_Test is SingleStrategyVaultTest { vault.deallocate(assets); } - function testFuzz_shouldRevert_whenSlippageTooHigh(uint256 _assets) public { - _assets = bound(_assets, 0, assets * (1e7 - 1) / 1e7 - 1); - - bytes[] memory assetBalances = new bytes[](2); - assetBalances[0] = abi.encode(0); - assetBalances[1] = abi.encode(_assets); - vm.mockCalls(asset, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), assetBalances); - - vm.prank(manager); - vm.expectRevert(Vault.ExceedsMaxSlippage.selector); - vault.deallocate(assets); - } - function test_shouldEmit_Deallocate() public { vm.expectEmit(); emit Vault.Deallocate(strategy, assets); @@ -233,22 +184,6 @@ contract SingleStrategyVault_BeforeWithdraw_Test is SingleStrategyVaultTest { vm.assume(withdrawAssets > unallocatedAssets); uint256 deallocateAssets = withdrawAssets - unallocatedAssets; - bytes[] memory assetBalances = new bytes[](3); - assetBalances[0] = abi.encode(unallocatedAssets); - assetBalances[1] = abi.encode(unallocatedAssets); - assetBalances[2] = abi.encode(withdrawAssets); - vm.mockCalls(asset, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), assetBalances); - - bytes[] memory shareBalances = new bytes[](2); - shareBalances[0] = abi.encode(shares); - shareBalances[1] = abi.encode(0); - vm.mockCalls(strategy, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), shareBalances); - - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, 0), abi.encode(0)); - vm.mockCall( - strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, shares), abi.encode(deallocateAssets) - ); - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.withdraw.selector), abi.encode(shares)); vm.expectCall( @@ -291,21 +226,6 @@ contract SingleStrategyVault_AfterDeposit_Test is SingleStrategyVaultTest { function testFuzz_shouldAllocateToStrategy_whenAboveThreshold(uint256 depositAssets) public { vm.assume(depositAssets >= threshold); - bytes[] memory assetBalances = new bytes[](2); - assetBalances[0] = abi.encode(depositAssets); - assetBalances[1] = abi.encode(0); - vm.mockCalls(asset, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), assetBalances); - - bytes[] memory shareBalances = new bytes[](2); - shareBalances[0] = abi.encode(0); - shareBalances[1] = abi.encode(shares); - vm.mockCalls(strategy, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), shareBalances); - - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, 0), abi.encode(0)); - vm.mockCall( - strategy, abi.encodeWithSelector(IERC4626.convertToAssets.selector, shares), abi.encode(depositAssets) - ); - vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.deposit.selector), abi.encode(shares)); vm.expectCall(strategy, abi.encodeWithSelector(IERC4626.deposit.selector, depositAssets, address(vault))); From 2764e5877faa4b9e43858a20d2d78aac12fee867 Mon Sep 17 00:00:00 2001 From: ashhanai Date: Wed, 10 Dec 2025 18:41:49 +0100 Subject: [PATCH 2/2] fix: test --- tests/unit/vault/SingleStrategyVault.t.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/vault/SingleStrategyVault.t.sol b/tests/unit/vault/SingleStrategyVault.t.sol index 1062482d..50fb1b1d 100644 --- a/tests/unit/vault/SingleStrategyVault.t.sol +++ b/tests/unit/vault/SingleStrategyVault.t.sol @@ -184,6 +184,9 @@ contract SingleStrategyVault_BeforeWithdraw_Test is SingleStrategyVaultTest { vm.assume(withdrawAssets > unallocatedAssets); uint256 deallocateAssets = withdrawAssets - unallocatedAssets; + vm.mockCall( + asset, abi.encodeWithSelector(IERC20.balanceOf.selector, address(vault)), abi.encode(unallocatedAssets) + ); vm.mockCall(strategy, abi.encodeWithSelector(IERC4626.withdraw.selector), abi.encode(shares)); vm.expectCall(