Summary
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)
);
The contract maintains a counter nRoutes that tracks valid route configurations, but fails to validate that _routeNumber parameter is within bounds (< nRoutes) before accessing route data from the mappings.
This allows:
Accessing undefined routes beyond the valid range
Using uninitialized or garbage data in exchange calls
Potential manipulation by keepers providing invalid route numbers
Because the contract assumes all route numbers passed to claimAndSwap are valid without validation. This breaks a core safety assumption that only configured routes should be usable for exchanges, the function can be called with route numbers >= nRoutes, violating the safety property that all used routes must be previously configured.
Vulnerability Details
In StrategyMainnet.claimAndSwap()
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
}
* ╔═══════════════════════════════════════════════════════════════════════════════╗
* ║ VULNERABILITY IMPACT ANALYSIS ║
* ╠══════════════════╦════════════════╦═══════════════════╦══════════════════════╣
* ║ Strategy ║ Router ║ Chain Impact ║ Route Issues ║
* ╠══════════════════╬════════════════╬═══════════════════╬══════════════════════╣
* ║ StrategyMainnet ║ Curve Router ║ Failed swaps & ║ Complex route ║
* ║ ║ ║ unexpected routes ║ configurations ║
* ╠══════════════════╬════════════════╬═══════════════════╬══════════════════════╣
* ║ StrategyOp ║ Velodrome ║ Incorrect pool ║ Route arrays ║
* ║ ║ Router ║ access ║ validation ║
* ╠══════════════════╬════════════════╬═══════════════════╬══════════════════════╣
* ║ StrategyArb ║ Ramses Router ║ Malformed route ║ Route structures ║
* ║ ║ ║ data ║ handling ║
* ╠══════════════════╩════════════════╩═══════════════════╩══════════════════════╣
* ║ ROOT CAUSES ║
* ╠════════════════════════════════════════════════════════════════════════════════
* ║ • Missing bounds validation for route data access ║
* ║ • Implicit trust in keeper-provided route numbers ║
* ║ • Lack of safety checks for uninitialized routes ║
* ╠════════════════════════════════════════════════════════════════════════════════
* ║ TOKEN IMPACT ║
* ╠════════════════════════════════════════════════════════════════════════════════
* ║ • WETH->alETH: Swap failures or incorrect routing ║
* ║ • Transmuter claims: Asset stuck risk ║
* ║ • Strategy shares: Unfavorable rate execution ║
* ╠════════════════════════════════════════════════════════════════════════════════
* ║ KEEPER EXPLOITATION ║
* ╠════════════════════════════════════════════════════════════════════════════════
* ║ • Can access undefined routes ║
* ║ • Can use uninitialized parameters ║
* ║ • Can route through non-existent pools ║
* ╠════════════════════════════════════════════════════════════════════════════════
* ║ CRITICAL CONSIDERATIONS ║
* ╠════════════════════════════════════════════════════════════════════════════════
* ║ • Strategies handle user deposits ║
* ║ • Multiple protocol interactions (Alchemix, DEXes) ║
* ║ • Cross-chain operations with varied routing ║
* ╚════════════════════════════════════════════════════════════════════════════════
*/
Impact
StrategyMainnet
mapping(uint256 => address[11]) public routes;
mapping(uint256 => uint256[5][5]) public swapParams;
mapping(uint256 => address[5]) public pools;
function addRoute(
address[11] calldata _route,
uint256[5][5] calldata _swapParams,
address[5] calldata _pools
) external onlyManagement {
routes[nRoutes] = _route;
swapParams[nRoutes] = _swapParams;
pools[nRoutes] = _pools;
nRoutes++;
}
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
uint256 _routeNumber
) external onlyKeepers {
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
}
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(_amountClaim > 0, "Amount must be greater than 0");
require(_minOut > _amountClaim, "minOut too low");
router.exchange(
routes[_routeNumber],
swapParams[_routeNumber],
_amountClaim,
_minOut,
pools[_routeNumber],
address(this)
);
}
+
+ function validateRoute(uint256 _routeNumber) internal view returns (bool) {
+ return _routeNumber < nRoutes &&
+ routes[_routeNumber][0] != address(0) &&
+ pools[_routeNumber][0] != address(0);
+ }
function claimAndSwap(
uint256 _amountClaim,
uint256 _minOut,
IVeloRouter.route[] calldata _path
) external onlyKeepers {
+
+ require(_path.length > 0, "Empty route 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(_amountClaim > 0, "Amount must be greater than 0");
require(_minOut > _amountClaim, "minOut too low");
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
}