The executioner contract only supports ERC20<>ERC20 token trades. Native token swaps are supported by either wrapping / unwrapping the ERC20 wrapped native token before / after the trades respectively.
When exchanging from the native token, the wrapping occurs in the slingshot contract before it is transferred to the executioner contract. When exchanging to the native token, the unwrapping occurs in the executioner contract.
Examine how the initial and final balances are fetched in executeTrades().
Notice the discrepancy between the initial and final balance fetched if toToken is the native token. The balance before is the actual native token balance in the executioner contract, but the balance after is the wrapped token balance.
Effects
This leads to potentially wrong comparisons.
A malicious user can cause griefing by sending native tokens directly to the executioner contract (note that it has a receiver function) that exceeds the finalAmountMin of a user.
Wrapped tokens that are accidentally sent to the executioner contract can be exploited as they can be considered to be part of the attacker's trade output. For instance,
Alice accidentally sends WETH to the executioner contract
Mallory specifies a minimal swap with ETH_ADDRESS (nativeToken) as the toToken. Alice's stuck WETH in the executioner contract are included and sent to Mallory's trade.
Recommended Mitigation Steps
Based on the design, the balances fetched should be from the wrapped token if toToken is specified as the native token.
Handle
hickuphh3
Vulnerability details
Impact
The executioner contract only supports ERC20<>ERC20 token trades. Native token swaps are supported by either wrapping / unwrapping the ERC20 wrapped native token before / after the trades respectively.
When exchanging from the native token, the wrapping occurs in the slingshot contract before it is transferred to the executioner contract. When exchanging to the native token, the unwrapping occurs in the executioner contract.
Examine how the initial and final balances are fetched in
executeTrades()
.Initial Balance
uint256 initialBalance = _getTokenBalance(toToken);
Final Balance
_getTokenBalance()
Notice the discrepancy between the initial and final balance fetched if
toToken
is the native token. The balance before is the actual native token balance in the executioner contract, but the balance after is the wrapped token balance.Effects
This leads to potentially wrong comparisons.
finalAmountMin
of a user.ETH_ADDRESS
(nativeToken
) as thetoToken
. Alice's stuck WETH in the executioner contract are included and sent to Mallory's trade.Recommended Mitigation Steps
Based on the design, the balances fetched should be from the wrapped token if
toToken
is specified as the native token.Then both initial and final balances can be fetched by simply calling
_getTokenBalance()