function _call1InchSwap(bytes memory _swapParams, address _asset, uint256 _minReturnAmount)
internal
returns (uint256 returnAmount)
{
(bool success, bytes memory result) = address(oneInchRouter).call(_swapParams);
require(success, "1inch swap failed");
if (result.length > 0) {
(returnAmount,) = abi.decode(result, (uint256, uint256));
} else {
}
require(returnAmount >= _minReturnAmount, "Insufficient return amount from swap");
}
pragma solidity ^0.8.13;
import {Test} from "forge-std/Test.sol";
import {Stratax} from "../../src/Stratax.sol";
import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
contract MockERC20 {
mapping(address => uint256) public balanceOf;
function mint(address to, uint256 amount) external { balanceOf[to] += amount; }
function decimals() external pure returns (uint8) { return 18; }
function transfer(address to, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount; balanceOf[to] += amount; return true;
}
function transferFrom(address from, address to, uint256 amount) external returns (bool) {
balanceOf[from] -= amount; balanceOf[to] += amount; return true;
}
function approve(address, uint256) external pure returns (bool) { return true; }
}
contract MockOneInchEmpty {
MockERC20 public token;
uint256 public actualOutput;
constructor(MockERC20 _token, uint256 _out) { token = _token; actualOutput = _out; }
fallback() external {
if (actualOutput > 0) token.transfer(msg.sender, actualOutput);
assembly { return(0, 0) }
}
}
contract StrataxHarness is Stratax {
function exposed_call1InchSwap(bytes memory _swapParams, address _asset, uint256 _min)
external returns (uint256)
{ return _call1InchSwap(_swapParams, _asset, _min); }
}
contract SlippageBypassPoCTest is Test {
uint256 constant PRE_EXISTING = 200e18;
uint256 constant ACTUAL_OUTPUT = 600e18;
uint256 constant MIN_RETURN = 700e18;
function test_slippageBypass() public {
MockERC20 token = new MockERC20();
MockOneInchEmpty router = new MockOneInchEmpty(token, ACTUAL_OUTPUT);
token.mint(address(router), ACTUAL_OUTPUT);
StrataxHarness impl = new StrataxHarness();
UpgradeableBeacon beacon = new UpgradeableBeacon(address(impl), address(this));
bytes memory init = abi.encodeWithSelector(
Stratax.initialize.selector, address(0), address(0), address(router), address(token), address(0)
);
StrataxHarness harness = StrataxHarness(address(new BeaconProxy(address(beacon), init)));
token.mint(address(harness), PRE_EXISTING);
uint256 returned = harness.exposed_call1InchSwap(abi.encodeWithSignature("swap()"), address(token), MIN_RETURN);
assertLt(ACTUAL_OUTPUT, MIN_RETURN, "actual output is below user minimum");
assertEq(returned, PRE_EXISTING + ACTUAL_OUTPUT, "returnAmount inflated by pre-existing balance");
assertTrue(returned >= MIN_RETURN, "slippage check passed despite bad swap rate");
}
}
function _call1InchSwap(bytes memory _swapParams, address _asset, uint256 _minReturnAmount)
internal
returns (uint256 returnAmount)
{
+ uint256 balanceBefore = IERC20(_asset).balanceOf(address(this));
(bool success, bytes memory result) = address(oneInchRouter).call(_swapParams);
require(success, "1inch swap failed");
if (result.length > 0) {
(returnAmount,) = abi.decode(result, (uint256, uint256));
} else {
- returnAmount = IERC20(_asset).balanceOf(address(this));
+ returnAmount = IERC20(_asset).balanceOf(address(this)) - balanceBefore;
}
require(returnAmount >= _minReturnAmount, "Insufficient return amount from swap");
}