DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Wrong offset for `fromToken` in `ParaSwapUtils::swap` causing the paraswap call to revert

Summary

The fromToken parameter decoded by the ParaSwapUtils::swap read the value at the wrong offset, causing the subsequent paraswap call to always revert.

Vulnerability details

The ParaSwapUtils::swap function is used in the PerpetualVault::_doDexSwap which itself is called during most of swap actions:

File: contracts/libraries/ParaSwapUtils.sol
11: library ParaSwapUtils {
12: using SafeERC20 for IERC20;
13:
14: function swap(address to, bytes memory callData) external {
15: _validateCallData(to, callData);
16: address approvalAddress = IAugustusSwapper(to).getTokenTransferProxy();
17: address fromToken;
18: uint256 fromAmount;
19: assembly {
20: fromToken := mload(add(callData, 68))
21: fromAmount := mload(add(callData, 100))
22: }
23: IERC20(fromToken).safeApprove(approvalAddress, fromAmount);
24: (bool success, ) = to.call(callData);
25: require(success, "paraswap call reverted");
26: }
27:
28: function _validateCallData(address to, bytes memory callData) internal view {
29: require(to == address(0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57), "invalid paraswap callee");
30: address receiver;
31: assembly {
32: receiver := mload(add(callData, 196))
33: }
34: require(receiver == address(this), "invalid paraswap calldata");
35: }

We can see that the function takes a callData input that is then used to call the to address, which is verified to always match 0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57, which is the AugustusSwapper contract on Arbitrum and Avalanche.

We can also see that some parameters are extracted from callData using assembly:

  • fromToken by reading callData at offset 68

  • fromAmount by reading callData at offset 100

  • receiver by reading callData at offset 196

Each of these parameters correspond to the third, forth and sixth fields of a struct (if we don't forget to account for the 4-bytes selector at the beginning of the calldata)

Now, if we go check the source code of the AugustusSwapper at address 0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57(which correspond to Augustus v5), and look at the Utils.sol file we find that only one struct correspond to the decoding algorithm used in the ParaSwapUtils:

struct BuyData { offset (+selector)
address adapter; //0 (4)
address fromToken; //32 (36)
address toToken; //64 (68) <@ šŸ”“ fromToken != toToken
uint256 fromAmount; //96 (100) <@ fromAmount
uint256 toAmount; //128 (132)
uint256 expectedAmount; //160 (164)
address payable beneficiary; //192 (196) <@ receiver
Utils.Route[] route; //...
address payable partner;
uint256 feePercent;
bytes permit;
uint256 deadline;
bytes16 uuid;
}

The other possible structs (SellData, MegaSwapSellData and SimpleData) have no matching fields with the decoding in the ParaSwapUtils decoding, hence either the decoding is completely wrong, or it is wrong for fromToken only.

So, we see that there is an issue in the decoding offset for fromToken, which should be 36 and not 68, as in the current implementation the value that will be decoded is toToken, which will revert as this token is approved L23 but does not correspond to the token to swap.

Impact

Wrong calldata decoding causing every call to paraswap to revert.

High likelihood has this will happen every time Paraswap is called, medium/high impact as this will cause the swap to revert every time Paraswap is used.

Recommended Mitigation Steps

Update the offset to to correct one:

function swap(address to, bytes memory callData) external {
_validateCallData(to, callData);
address approvalAddress = IAugustusSwapper(to).getTokenTransferProxy();
address fromToken;
uint256 fromAmount;
assembly {
- fromToken := mload(add(callData, 68))
+ fromToken := mload(add(callData, 36))
fromAmount := mload(add(callData, 100))
}
Updates

Lead Judging Commences

n0kto Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

invalid_ParaswapUtils_incorrect_offset_SimpleSwap

Keepers use megaSwap with this struct: struct MegaSwapSellData { address fromToken; uint256 fromAmount; uint256 toAmount; uint256 expectedAmount; address payable beneficiary; Utils.MegaSwapPath[] path; address payable partner; uint256 feePercent; bytes permit; uint256 deadline; bytes16 uuid; } 32 first bytes of callData bytes array → length of the bytes array. 4 bytes selector, 32 bytes → offset of the struct → 68 bytes before the fromToken.

Support

FAQs

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