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

Unchecked Return Values in claimAndSwap For DEX Router Calls

Summary

In all three strategy contracts, the return values from the DEX router swap functions are not checked, which could lead to silent failures and asset loss when minimum output is still satisfied but swap fails for other reasons.

Root Cause

Let's look at the implementations:

In StrategyOp.sol:

function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IVeloRouter.route[] calldata _path) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
// @audit - Return value not checked
IVeloRouter(router).swapExactTokensForTokens(_amount, minOut, _path, address(this), block.timestamp);
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}

Similar unchecked swap calls in:

  • StrategyArb.sol

  • StrategyMainnet.sol

The issue:

  1. DEX routers return a uint256[] containing actual amounts received

  2. This return value is ignored

  3. Only the balance difference is checked

  4. A swap could fail but the check still passes if tokens were transferred directly

Impact

Let's say attacker creates this setup:

  1. Has a malicious router contract

  2. The swap function reverts silently

  3. Directly transfers minimum tokens to pass the balance check

  4. Takes strategy's tokens without providing proper liquidity

Example attack flow:

1. Strategy has 100 WETH
2. Malicious router set
3. claimAndSwap called for 100 WETH expecting 101 alETH minimum
4. Router's swap fails but transfers 101 alETH directly
5. Balance check passes
6. Attacker has strategy's WETH but provided less alETH than should be received

This vulnerability is rated as HIGH severity because:

  1. Could lead to direct loss of funds

  2. Affects core swap functionality

  3. Present in all strategy implementations

  4. No protection against malicious routers\

Proof of Code

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
contract MaliciousRouter {
IERC20 weth;
IERC20 aleth;
address attacker;
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts) {
// Take WETH
weth.transferFrom(msg.sender, attacker, amountIn);
// Send minimum alETH directly to pass check
aleth.transferFrom(attacker, to, amountOutMin);
// Return empty array - would fail if checked
return new uint256[](0);
}
}
contract RouterExploitTest is Test {
function testSwapExploit() public {
// Setup contracts
weth = new MockWETH();
aleth = new MockALETH();
strategy = new Strategy();
maliciousRouter = new MaliciousRouter();
// Strategy has 100 WETH
weth.transfer(address(strategy), 100e18);
// Set malicious router
strategy.setRouter(address(maliciousRouter));
// Exploit
vm.prank(keeper);
strategy.claimAndSwap(100e18, 101e18, path);
// Check results
assertEq(weth.balanceOf(attacker), 100e18, "Attacker got WETH");
assertEq(aleth.balanceOf(address(strategy)), 101e18, "Strategy got minimum alETH");
// Should have received ~150e18 alETH at market price
}
}

Recommendations

  1. Check router return values:

function claimAndSwap(uint256 _amountClaim, uint256 _minOut, IVeloRouter.route[] calldata _path) external onlyKeepers {
transmuter.claim(_amountClaim, address(this));
uint256 balBefore = asset.balanceOf(address(this));
// Store and verify return value
uint256[] memory amounts = IVeloRouter(router).swapExactTokensForTokens(
_amount,
minOut,
_path,
address(this),
block.timestamp
);
require(amounts.length > 0, "Invalid swap return");
require(amounts[amounts.length - 1] >= _minOut, "Insufficient output from swap");
uint256 balAfter = asset.balanceOf(address(this));
require((balAfter - balBefore) >= _minOut, "Slippage too high");
transmuter.deposit(asset.balanceOf(address(this)), address(this));
}
  1. Add additional safety checks:

require(path[0] == address(underlying), "Invalid input token");
require(path[path.length - 1] == address(asset), "Invalid output token");
Updates

Appeal created

inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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