Users can skip the conversion of their staked in PrelaunchPoints LRTs without losing points by deploying a mock ERC20 token and two pools on Uniswap, which will negatively affect the main purpose of PrelaunchPoints - commitment to the conversion. Bypassing the conversion is intended to only be allowed in withdraw().
There's a mention of a similar issue in the Publicly Known Issues section:
Crafting malicious calldata so that users get less funds as expected when claiming lpETH on LRT deposits (e.g. by setting big slippage/price impact)
But it does not apply here because its purpose is to invalidate user mistakes, social engineering attacks, and standard swap frontrunning (which are invalid anyway by the C4 rules), and the current report is about a malicious calldata that benefits the user.
Proof of Concept
During the claiming process, users are allowed to use their own calldata for the 0x call with some restrictions (see _validateData()). They can also choose the function that will execute the calldata: transformERC20() or sellTokenForEthToUniswapV3().
In both cases, there's no validation for any intermediary swaps or slippage, which allows users to use self-created pools to extract their deposit through the slippage and skip the conversion to lpETH.
function sellTokenForEthToUniswapV3(
bytes memory encodedPath,
uint256 sellAmount,
uint256 minBuyAmount,
address payable recipient
) public override returns (uint256 buyAmount) {
buyAmount = _swap(
encodedPath,
sellAmount,
minBuyAmount,
msg.sender,
address(this) // we are recipient because we need to unwrap WETH
);
WETH.withdraw(buyAmount);
// Transfer ETH to recipient.
(bool success, bytes memory revertData) = _normalizeRecipient(recipient).call{value: buyAmount}("");
if (!success) {
revertData.rrevert();
}
}
Deploy a mock ERC20 token (MOCK)
Deploy two pools on Uniswap:
LRT/MOCK with a larger amount of LRT and some negligible amount of MOCK (e.g., 10000000:1)
MOCK/WETH with a larger amount of MOCK and some negligible amount of WETH
Craft a calldata with the encodedPath containing the required LRT as the input token, MOCK as an intermediary token, WETH as the output token, and minBuyAmount set to 0.
Call claim(). 0x will swap the LRT to MOCK through the first pool with the slippage about 99%, resulting in some small amount of MOCK; then it'll swap MOCK to WETH through the second pool, resulting in some dust amount of WETH and finishing the transaction.
Remove the liquidity from the first pool, which will contain the deposit.
Tools Used
Manual review
Recommended Mitigation Steps
Review the validation of the calldata. Consider using only predefined routes.
Lines of code
https://github.com/code-423n4/2024-05-loop/blob/0dc8467ccff27230e7c0530b619524cc8401e22a/src/PrelaunchPoints.sol#L497
Vulnerability details
Impact
Users can skip the conversion of their staked in
PrelaunchPoints
LRTs without losing points by deploying a mock ERC20 token and two pools on Uniswap, which will negatively affect the main purpose ofPrelaunchPoints
- commitment to the conversion. Bypassing the conversion is intended to only be allowed inwithdraw()
.There's a mention of a similar issue in the Publicly Known Issues section:
But it does not apply here because its purpose is to invalidate user mistakes, social engineering attacks, and standard swap frontrunning (which are invalid anyway by the C4 rules), and the current report is about a malicious calldata that benefits the user.
Proof of Concept
During the claiming process, users are allowed to use their own calldata for the 0x call with some restrictions (see
_validateData()
). They can also choose the function that will execute the calldata:transformERC20()
orsellTokenForEthToUniswapV3()
.In both cases, there's no validation for any intermediary swaps or slippage, which allows users to use self-created pools to extract their deposit through the slippage and skip the conversion to lpETH.
Example with
sellTokenForEthToUniswapV3()
:https://github.com/0xProject/protocol/blob/e66307ba319e8c3e2a456767403298b576abc85e/contracts/zero-ex/contracts/src/features/UniswapV3Feature.sol#L107-L126
MOCK
)encodedPath
containing the required LRT as the input token, MOCK as an intermediary token, WETH as the output token, andminBuyAmount
set to 0.claim()
. 0x will swap the LRT to MOCK through the first pool with the slippage about 99%, resulting in some small amount of MOCK; then it'll swap MOCK to WETH through the second pool, resulting in some dust amount of WETH and finishing the transaction.Tools Used
Manual review
Recommended Mitigation Steps
Review the validation of the calldata. Consider using only predefined routes.
Assessed type
Uniswap