Closed c4-bot-1 closed 5 months ago
koolexcrypto marked the issue as primary issue
This is technically possible, but we are tracking the Claim event for points calculation, so users cannot exploit this to get more points
koolexcrypto changed the severity to QA (Quality Assurance)
koolexcrypto marked the issue as grade-c
Lines of code
https://github.com/code-423n4/2024-05-loop/blob/40167e469edde09969643b6808c57e25d1b9c203/src/PrelaunchPoints.sol#L240-L266
Vulnerability details
Impact
An malicious user with locked non-ETH tokens can exploit the
claim()
function to withdraw his locked tokens during situations where he should not be allowed to withdraw (block.timestamp >= startClaimDate
andemergencyMode = false
). By exploiting this, the user can deposit huge amount of tokens to get their off-chain points computed, and then retrieve his tokens back without needing to convert the locked funds toLpETH
.Proof of Concept
When
block.timestamp >= startClaimDate
andemergencyMode = false
, users are unable to withdraw their locked tokens. However, any user can exploit theclaim()
function by using crafted_data
arguments to effectively withdraw their locked tokens from the contract.The
claim()
function calls_claim()
, which, when dealing with non-ETH tokens, verifies the_data
arguments (the data passed for the 0x Exchange Router). However, it only validates the input and output token addresses and input amount. It doesn't check if the data corresponds to a single hop swap or a multi hop swap. This allows any user to use multi hop swaps to retrieve their locked tokens without converting them toLpETH
.For example, let's say Alice wants to withdraw her
swETH
locked in thePrelaunchPoints
contract. To exploit this, Alice needs to deploy an ERC20 token (tokenA
), which she fully controls the supply, create two new UniswapV3 pools that she can manipulate the token balances, and execute a multi hop swap passing through those pools using theclaim()
function.Each pool serves a purpose:
swETH - tokenA
pai) is intended to swap large amounts ofswETH
for minimal amounts oftokenA
. Since Alice controls all the supply oftokenA
, she can manipulate the pool balances to achieve the desiredswETH/tokenA
exchange rate. Consequently, after the first hop, Alice's locked funds (swETH
) are now "stored" in the pool balance.tokenA - WETH
pair) is used to converttokenA
back toWETH
, as the outputToken (in this caseWETH
) is validated by the_validateData()
function.Alice can then craft her
_data
to pass all required validations done by_validateData()
(see code below) and then callclaim()
using her crafted_data
.After the multi-hop swap (
swETH - tokenA
, thentokenA - WETH
) executed duringclaim()
, arbitrary amounts ofswETH
would be converted in dust amounts ofWETH
, which is then converted toLpETH
, while Alice'sswETH
is now in the first pool balance. Note that theminBuyAmount
field used bysellTokenForEthToUniswapV3()
is also in_data
and thus controlled by Alice, so this unfavorable swap succeeds if she setsminBuyAmount = 0
.Subsequently, after the
claim()
call, Alice can recover herswETH
tokens by removing the liquidity from the first pool. As she is the sole liquidity provider to that pool, she is able to recover it all. Therefore, as described, any user is able to exploit theclaim()
function to withdraw their locked tokens.Tools Used
Manual Review.
Recommended Mitigation Steps
Restrict the swaps to be single hop, which is not ideal as this may not be the most efficient swap path on some circumstances, or verify that the
_data
argument comes from Loop's off-chain services, for example by signing the data and validating the signing in the contract before executing the swap.Assessed type
Other