Summary
The current implementation allows swapping WETH to alETH at ratio better than 1:1 but without verifying whether the actual market price of alETH aligns with this ratio.
If the price of alETH is significantly lower than WETH in market terms, this can result in substantial value loss during the swap, causing financial harm to the protocol or its users.
Vulnerability Details
Price Mismatch Risk:
The swap ratio for WETH:alETH forces the keeper to only swap if the ratio is WETH 1:1.1 alETH but it doesn't take into account the difference between WETH/alETH market Price.
If the real market price of alETH is lower than WETH Price, the protocol effectively overpays in WETH for alETH, leading to value loss.
So there is an issue when swapping WETH for alETH because 1:1 ratio doesn't means same market Price so keeper can swap WETH: 3450$ for alETH: 3400$ there is loss in value when swapping WETH for alETH.
Dexes determine ratio if both Tokens has same amount but not same price.
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));
}
function _swapUnderlyingToAsset(uint256 _amount, uint256 minOut, IRamsesRouter.route[] calldata _path) internal {
require(minOut > _amount, "minOut too low");
uint256 underlyingBalance = underlying.balanceOf(address(this));
require(underlyingBalance >= _amount, "not enough underlying balance");
IRamsesRouter(router).swapExactTokensForTokens(_amount, minOut, _path, address(this), block.timestamp);
}
How Ramses Pairs get getAmountOut:
function _getAmountOut(
uint256 amountIn,
address tokenIn,
uint256 _reserve0,
uint256 _reserve1
) internal view returns (uint256) {
if (stable) {
uint256 xy = _k(_reserve0, _reserve1);
_reserve0 = (_reserve0 * 1e18) / decimals0;
_reserve1 = (_reserve1 * 1e18) / decimals1;
(uint256 reserveA, uint256 reserveB) = tokenIn == token0
? (_reserve0, _reserve1)
: (_reserve1, _reserve0);
amountIn = tokenIn == token0
? (amountIn * 1e18) / decimals0
: (amountIn * 1e18) / decimals1;
uint256 y = reserveB - _get_y(amountIn + reserveA, xy, reserveB);
return (y * (tokenIn == token0 ? decimals1 : decimals0)) / 1e18;
} else {
(uint256 reserveA, uint256 reserveB) = tokenIn == token0
? (_reserve0, _reserve1)
: (_reserve1, _reserve0);
return (amountIn * reserveB) / (reserveA + amountIn);
}
}
Impact
Financial Loss: The protocol may incur significant losses by swapping WETH for alETH.
Tools Used
Recommendations
Implement Price Validation:
Integrate an oracle-based price check to validate the actual market price of alETH against WETH before executing the swap:
Example:
uint256 marketPrice = priceOracle.getPrice(alETH, WETH);
require(marketPrice >= 1.1 ether, "Swap rate below threshold");