The ParaSwapUtils.swap()
function incorrectly decodes the calldata for Paraswap's simpleSwap()
function by using wrong memory offsets to extract fromToken
(or fromAmount
). This causes the function to approve the wrong token for transfer or approve incorrect amounts, leading to failed swaps or potential loss of funds.
The ParaSwapUtils.swap()
function attempts to extract fromToken
and fromAmount
from the calldata of Paraswap's simpleSwap()
function using assembly, but uses incorrect memory offsets:
This what the Paraswap simpleSwap function and its struct param looks like:
The correct ParaSwapUtils::swap
implementation:
The actual calldata layout for simpleSwap(SimpleData)
should be:
The function currently:
Reads toToken
(offset 68) instead of fromToken
(offset 36)
Uses incorrect spacing between fromToken
and fromAmount
(32 bytes instead of 64 bytes)
Note
Even if fromToken
were correctly extracted, fromAmount
would still be incorrectly extracted, leading to similar swap revert or incorrect transfer amount errors. This is because the extraction offsets for these two values do not align with the SimpleData layout. The correct layout requires a 64-byte gap between these values, but the swap function only accounts for 32 bytes (from offset 68 to 100).
A simple test contract demonstrating proper ABI encoding for struct parameters:
Calling with [1, "0xab"]
(`x=1, data="0xab") produces calldata showing proper struct encoding:
When the function attempts to swap tokens through Paraswap, it will either approve the wrong token for transfer (if reading toToken
as fromToken
) or approve an incorrect amount. This will cause all swap transactions to revert, making the swap functionality unusable i.e. DoS of PerpetualVault::deposit
and PerpetualVault::withdraw
functions.
Manual Review
Update the assembly block in ParaSwapUtils.swap()
to use correct offsets:
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.
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.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.