Summary
In StrategyOp.sol and StrategyArb.sol, the claimAndSwap
function has a price check that requires minOut > _amount
, but this is insufficient as a slippage protection mechanism since it only ensures receiving more than 1:1, not accounting for actual market price and maximum acceptable slippage.
Vulnerability Details
Found in:
StrategyOp.sol [Lines 92-105]:
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 _swapUnderlyingToAsset(uint256 _amount, uint256 minOut, IVeloRouter.route[] calldata _path) internal {
require(minOut > _amount, "minOut too low");
}
StrategyArb.sol has the same issue with identical code structure.
Impact
MEV bots can sandwich the swaps
No protection against price manipulation in the path
Keepers could potentially frontrun with unfavorable prices as long as it's slightly above 1:1
Loss of value through excessive slippage
Proof of Code
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
contract SlippageExploitTest is Test {
address keeper = address(0x1);
address strategy = address(0x2);
function testSlippageExploit() public {
uint256 marketPrice = 1.5 ether;
uint256 keeperMinOut = 1.01 ether;
assertTrue(keeperMinOut > 1 ether, "Passes minOut check");
uint256 loss = marketPrice - keeperMinOut;
assertGt(loss, 0.4 ether, "Significant loss occurred");
}
}
Recommendations
Implement proper slippage protection using an oracle or TWAP:
function _swapUnderlyingToAsset(uint256 _amount, uint256 minOut, IVeloRouter.route[] calldata _path) internal {
uint256 oraclePrice = getOraclePrice();
uint256 expectedOutput = (_amount * oraclePrice) / 1e18;
uint256 minAcceptableOutput = (expectedOutput * (10000 - MAX_SLIPPAGE)) / 10000;
require(minOut >= minAcceptableOutput, "Excessive slippage");
require(underlying.balanceOf(address(this)) >= _amount, "not enough underlying balance");
IVeloRouter(router).swapExactTokensForTokens(_amount, minOut, _path, address(this), block.timestamp);
}