Summary
function _swapUnderlyingToAsset(uint256 _amount, uint256 minOut, IVeloRouter.route[] calldata _path) internal {
require(minOut > _amount, "minOut too low");
uint256 underlyingBalance = underlying.balanceOf(address(this));
require(underlyingBalance >= _amount, "not enough underlying balance");
IVeloRouter(router).swapExactTokensForTokens(_amount, minOut, _path, address(this), block.timestamp);
}
From the disconnect between local validation and router-level enforcement. While the strategy performs a local check require(minOut > _amount), this value is not properly enforced in the actual swap execution. The router's swapExactTokensForTokens function receives the minOut parameter, but there's no guarantee that the router respects or enforces this value in the same way the strategy expects.
This creates a scenario where:
Local validation passes (minOut > _amount)
Swap executes through router
Router could return any amount of tokens
Strategy's post-swap balance check would fail, but the swap has already occurred
Vulnerability Details
In StrategyArb.claimAndSwap
function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IRamsesRouter.route[] calldata _path) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}
In StrategyOp.claimAndSwap
function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IVeloRouter.route[] calldata _path) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}
Because the strategy claims WETH from the transmuter before ensuring the swap will be profitable. This creates a critical state transition issue:
WETH is claimed from transmuter
If the swap fails or reverts due to slippage
The strategy is left holding WETH instead of alETH
This breaks the core invariant that funds should always be deployed in the transmuter unless actively being swapped
Impact
Recommendations
// StrategyArb.sol, StrategyOp.sol, StrategyMainnet.sol
- function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IVeloRouter.route[] calldata _path) external onlyKeepers {
- transmuter.claim(_amountClaim, address(this));
- uint256 balBefore = asset.balanceOf(address(this));
- _swapUnderlyingToAsset(_amountClaim, _minOut, _path);
- uint256 balAfter = asset.balanceOf(address(this));
- require((balAfter - balBefore) >= _minOut, "Slippage too high");
- transmuter.deposit(asset.balanceOf(address(this)), address(this));
- }
+ function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IVeloRouter.route[] calldata _path) external onlyKeepers {
+ // @Mitigation - Validate profitability before claiming
+ require(_minOut > _amountClaim, "Unprofitable swap ratio");
+
+ // @Mitigation - Query expected output before state changes
+ uint256 expectedOut = router.getAmountsOut(_amountClaim, _path)[_path.length - 1];
+ require(expectedOut >= _minOut, "Insufficient expected output");
+
+ // @Mitigation - Execute operations in atomic transaction
+ transmuter.claim(_amountClaim, address(this));
+
+ uint256 balBefore = asset.balanceOf(address(this));
+ _swapUnderlyingToAsset(_amountClaim, _minOut, _path);
+ uint256 balAfter = asset.balanceOf(address(this));
+
+ // @Mitigation - Verify actual output meets requirements
+ require((balAfter - balBefore) >= _minOut, "Slippage too high");
+
+ // @Mitigation - Immediately redeposit to maintain maximum deployment
+ transmuter.deposit(asset.balanceOf(address(this)), address(this));
+
+ // @Mitigation - Emit event for monitoring
+ emit SwapExecuted(_amountClaim, balAfter - balBefore, _path[0], _path[_path.length-1]);
+ }
+ // @Mitigation - Add event for tracking swaps
+ event SwapExecuted(uint256 amountIn, uint256 amountOut, address tokenIn, address tokenOut);