DeFiFoundrySolidity
16,653 OP
View results
Submission Details
Severity: medium
Invalid

Denial of service via excessive slippage in `claimAndSwap`

Summary

The claimAndSwap function in the StrategyArb contract is vulnerable to a denial-of-service (DoS) attack. A malicious keeper can provide an excessively high _minOut value, causing the swap transaction to revert consistently. This prevents legitimate keepers from executing the intended function, effectively blocking the strategy's core functionality.

Vulnerability Details

The claimAndSwap function is designed to allow keepers to claim WETH from the transmuter, swap it for alETH using a provided path on a router, and then deposit the acquired alETH back into the transmuter. The function includes a slippage check:

require((balAfter - balBefore) >= _minOut, "Slippage too high");

https://github.com/Cyfrin/2024-12-alchemix/blob/82798f4891e41959eef866bd1d4cb44fc1e26439/src/StrategyArb.sol#L76

This check is intended to protect the strategy from receiving less alETH than expected due to price fluctuations during the swap. However, the function relies entirely on the keeper to provide the _minOut value. There is no upper bound or validation on this input.

function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IRamsesRouter.route[] calldata _path) external onlyKeepers {
// ...
_swapUnderlyingToAsset(_amountClaim, _minOut, _path);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
// ...
}
function _swapUnderlyingToAsset(uint256 _amount, uint256 minOut, IRamsesRouter.route[] calldata _path) internal {
require(minOut > _amount, "minOut too low"); // Insufficient check
// ...
IRamsesRouter(router).swapExactTokensForTokens(_amount, minOut, _path, address(this), block.timestamp);
}

https://github.com/Cyfrin/2024-12-alchemix/blob/82798f4891e41959eef866bd1d4cb44fc1e26439/src/StrategyArb.sol#L71-L88

PoC:

Assume the following scenario:

  • The strategy has a balance of WETH and alETH.

  • A keeper calls claimAndSwap.

  • The keeper sets _minOut to an extremely high value, for example, 10 times the expected output of the swap.

In this case, the _swapUnderlyingToAsset function will execute the swap. However, because _minOut is set artificially high, the require((balAfter - balBefore) >= _minOut, "Slippage too high"); check will always fail, causing the transaction to revert.

PoC values:

  • _amountClaim: 1 WETH (1 ether)

  • Expected alETH output (hypothetical): 1.01 alETH

  • _minOut: 10 alETH

In this case, the swap will likely return around 1.01 alETH. The check 1.01 >= 10 will fail, causing a revert.

pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/StrategyArb.sol"; // Path to your contract
import {MockTransmuter} from "./mocks/MockTransmuter.sol";
import {MockERC20} from "./mocks/MockERC20.sol";
import {MockRamsesRouter} from "./mocks/MockRamsesRouter.sol";
contract StrategyArbTest is Test {
StrategyArb public strategy;
MockTransmuter public transmuter;
MockERC20 public alETH;
MockERC20 public wETH;
MockRamsesRouter public router;
function setUp() public {
alETH = new MockERC20("alETH", "alETH", 18);
wETH = new MockERC20("WETH", "WETH", 18);
transmuter = new MockTransmuter(wETH, alETH);
router = new MockRamsesRouter(wETH, alETH);
strategy = new StrategyArb(address(alETH), address(transmuter), "Test Strategy");
strategy.setRouter(address(router));
vm.prank(address(strategy));
alETH.mint(address(strategy), 10 ether);
wETH.mint(address(strategy), 10 ether);
}
function testDoSMinOut() public {
uint256 amountClaim = 1 ether;
uint256 minOut = 10 ether; // Extremely high minOut
IRamsesRouter.route[] memory path;
path.push(IRamsesRouter.route(address(wETH),false));
path.push(IRamsesRouter.route(address(alETH),false));
vm.prank(address(1)); // Keeper address
vm.expectRevert("Slippage too high");
strategy.claimAndSwap(amountClaim, minOut,path);
}
}

The test will pass, demonstrating that the transaction reverts with the "Slippage too high" error when _minOut is set to an excessive value.

Impact

  1. Legitimate keepers are unable to execute claimAndSwap, preventing the strategy from performing its core function of claiming and swapping WETH for alETH.

  2. The strategy cannot capitalize on arbitrage opportunities or other profitable events that require the claimAndSwap function.

Tools Used

Manual review.

Recommendations

Instead of relying on the keeper's input for _minOut, calculate an acceptable slippage tolerance within the contract. This can be done by using a percentage-based slippage limit (e.g., 1% or 2%) or by using a price oracle to determine a more accurate minimum output.

function claimAndSwap(uint256 _amountClaim, IRamsesRouter.route[] calldata _path) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
uint256 expectedOut = // Get expected output from router or oracle
uint256 minOut = expectedOut * 99 / 100; // 1% slippage tolerance
_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));
}
Updates

Appeal created

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.