Summary
StrategyMainnet.claimAndSwap() The function lacks validation that _routeNumber < nRoutes
before accessing route data from the mappings. This allows:
Access to uninitialized or invalid route configurations
Potential manipulation of swap paths by passing invalid route numbers
No guarantee that the accessed route data is properly configured
Because the code assumes route numbers will be valid without explicit validation, allowing access to undefined mapping values that could corrupt swap execution.
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
require(_minOut > _amountClaim, "minOut too low");
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
}
it could lead to execution of swaps with incorrect/undefined routes, access to uninitialized pool addresses and potential loss of funds through failed or corrupted swaps.
Vulnerability Details
The issue is in all three strategy implementations (Mainnet, Optimism, and Arbitrum) in their claimAndSwap
functions.
contract StrategyMainnet is BaseStrategy {
uint256 public nRoutes = 0;
mapping(uint256 => address[11]) public routes;
mapping(uint256 => uint256[5][5]) public swapParams;
mapping(uint256 => address[5]) public pools;
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
}
}
The bug is from missing validation of route numbers against the nRoutes
counter. When routes are added via addRoute()
, nRoutes
is incremented, but claimAndSwap()
never verifies that the provided route number is within bounds.
Impact
Root Cause:
Routes are added incrementally with nRoutes
tracking the count
claimAndSwap
accepts any uint256 as _routeNumber
No validation that _routeNumber < nRoutes
Allows access to uninitialized mapping values
Recommendations
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
+
+ require(_routeNumber < nRoutes, "Invalid route number");
+
+
+ require(routes[_routeNumber][0] != address(0), "Route not configured");
+ require(pools[_routeNumber][0] != address(0), "Pool not configured");
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
+
+ require(_minOut > _amountClaim, "Insufficient minimum output");
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
+
+ uint256 balAfter = asset.balanceOf(address(this));
+ require(balAfter > balBefore, "Swap failed");
}
+
+ function validateRoute(uint256 _routeNumber) public view returns (bool) {
+ if (_routeNumber >= nRoutes) return false;
+ if (routes[_routeNumber][0] == address(0)) return false;
+ if (pools[_routeNumber][0] == address(0)) return false;
+ return true;
+ }
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
IVeloRouter.route[] calldata _path
) external onlyKeepers {
+
+ require(_path.length > 0, "Empty path");
+ require(_path[0].from == address(underlying), "Invalid start token");
+ require(_path[_path.length-1].to == address(asset), "Invalid end token");
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
+
+ require(underlying.balanceOf(address(this)) >= _amountClaim, "Insufficient balance");
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
+
+ uint256 balAfter = asset.balanceOf(address(this));
+ require((balAfter - balBefore) >= _minOut, "Insufficient output");
}