Platform: Code4rena
Start Date: 01/05/2024
Pot Size: $12,100 USDC
Total HM: 1
Participants: 47
Period: 7 days
Judge: Koolex
Id: 371
League: ETH
Rank: 47/47
Findings: 1
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: pamprikrumplikas
Also found by: 0xnev, 0xrex, ParthMandale, Pechenite, SpicyMeatball, ZanyBonzy, caglankaan, chainchief, cheatc0d3, karsar, krisp, oualidpro, peanuts, popeye, slvDev
0 USDC - $0.00
In function _processLock
there is a separate condition for WETH
to be
locked - if (_token == address(WETH))
and in that condition the WETH
is been
converted into ETH
and added to the totalSupply
, and user's balance of ETH
(not ERC20 WETH _token)
PrelaunchPoints.sol#L188
if (_token == address(WETH)) { WETH.withdraw(_amount); totalSupply = totalSupply + _amount; @> balances[_receiver][ETH] += _amount; }
Issue comes when user calls function _claim
with input param to be WETH token
(because that's what user locked, so now he will claim that token only) But here
user will be getting a revert because line #245 in function _claim
because
user's WETH balance is not updated instead user's ETH balance is updated in
function _processLock
-
PrelaunchPoints.sol#L245
// fetch user stake uint256 userStake = balances[msg.sender][_token]; // if user has nothing to claim revert the tx @> if (userStake == 0) { revert NothingToClaim(); }
To avoid getting revert with WETH token while claiming for locked WETH token,
considering implementing this in function _claim
uint256 userStake; if ( _token == address(WETH)) { userStake = balances[msg.sender][ETH]; } userStake = balances[msg.sender][_token]; if (userStake == 0) { revert NothingToClaim(); } if (_token == ETH || _token == address(WETH)) { claimedAmount = userStake.mulDiv(totalLpETH, totalSupply); balances[msg.sender][_token] = 0; lpETH.safeTransfer(_receiver, claimedAmount); }
In function -_claim
else block, which handles ERC20 token claims, it lacks a check to ensure that the claim percentage (_percentage
input param) is greater than 0. failing to validate inputs can lead to unexpected behavior and could potentially be exploited in more complex contracts or in combination with other oversights.
// ... existing code ... else { @> require(_percentage > 0, "Percentage must be greater than 0"); uint256 userClaim = userStake * _percentage / 100; _validateData(_token, userClaim, _exchange, _data); balances[msg.sender][_token] = userStake - userClaim; // ... existing code ...
The docs Invariant says -
Withdrawals are only active on emergency mode or during 7 days after loopActivation is set
Real scenario - All functioning is properly set by the owner, and therefore all
dates are also properly set and ClaimDate
has started from owners side (after
calling function conertAllETH
, ie. 7 days has passed where user was having
chance to withdraw). Now in future the owner sets the emergencyMode: true
due to
certain reason, then the user who has locked his ETH
in protocol, but have not
claimed it yet, and now due to emergency user don't want to claim it anymore,
but want to simply withdraw his locked ETH
but now he won't be able to
withdraw it because there are no ETH
present in contract anymore, because of
function conertAllETH
converted it into lpETH
.
So here line - PrelaunchPoints.sol#L291
is executed so that users should
should now claim for his ETH
, as user can not get back his ETH withdrawn back.
if (block.timestamp >= startClaimDate){ revert UseClaimInstead(); }
This issue can be marked as a LOW severity because - Although user can claim
token, but still as invariant says that user must be able to
Withdraw when emergency mode active (ie. emergencyMode: true)
. So here it still
breaks the invariant.
Codebase logic should updated in such a way that user can withdraw his locked ETH
while emergencyMode: true
.
_fillQuote
and _claim
it mismatches.So sceanrio where somone mistakly sends ETH into the contract, so now he wont be able to get those ETH back ever. But that's not the issue, issue of Event values mismatches -
Now if a normal user try to claim his LpETH on his locked ERC20 token then in
function _fillQuote
this line -
boughtETHAmount = address(this).balance - boughtETHAmount;
which tells the
[New Balance -old Balance] is what user boughtETHAmount
which user is supposed
to get for his locked token, which then is used in Event -
emit SwappedTokens(address(_sellToken), _amount, boughtETHAmount);
.
But the main issue comes when in function _claim
in else condition -
@> claimedAmount = address(this).balance; lpETH.deposit{value: claimedAmount}(_receiver); } emit Claimed(msg.sender, _token, claimedAmount);
because of this line claimedAmount = address(this).balance;
it deposit that
amount in lpETH contract and get back those LPETH as claim for that specific
user, which will be more than what that user deserves for his locked token
amount, this is because someone sent ETH from outside of contract.
And in function _claim
as the Event -
emit Claimed(msg.sender, _token, claimedAmount);
it emits different value of
claimedAmount
as compared to boughtETHAmount
, where as this both must be
same as the ETH:lpETH ratio is 1:1
Before emitting event Claimed
in function _claim
, update the value of
claimAmount
as same as boughtETHAmount
by -
// ... existing code ... _fillQuote(IERC20(_token), userClaim, _data); // Convert swapped ETH to lpETH (1 to 1 conversion) claimedAmount = address(this).balance; lpETH.deposit{value: claimedAmount}(_receiver); } @> claimedAmount = address(this).balance - claimedAmount emit Claimed(msg.sender, _token, claimedAmount); }
#0 - CloudEllie
2024-05-11T17:25:19Z
#1 - 0xd4n1el
2024-05-13T17:31:02Z
Good report
#2 - c4-judge
2024-06-03T10:48:21Z
koolexcrypto marked the issue as grade-b
#3 - kazantseff
2024-06-06T10:11:10Z
Hey @koolexcrypto, I think L-4 here can be a duplicate of #33. Could you please kindly check it again?
#4 - koolexcrypto
2024-06-10T15:11:02Z
Hi @kazantseff
ETH sent mistakenly will be re-evaluated as a QA.