Platform: Code4rena
Start Date: 09/12/2021
Pot Size: $25,000 USDC
Total HM: 12
Participants: 25
Period: 4 days
Judge: LSDan
Total Solo HM: 4
Id: 64
League: ETH
Rank: 16/25
Findings: 3
Award: $393.76
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: johnnycash
Also found by: 0x421f, cmichel, gpersoon, gzeon, harleythedog, kemmio, kenzo, sirhashalot
cmichel
The TwabRewards
contract has an implicit restriction of 256 epochs per promotion as it uses a bitmask in a uint256
to mark claimed epochs 0-255, see _isClaimedEpoch
.
"/// @dev We pack epochs claimed by a user into a uint256. So we can't store more than 255 epochs."
First, this comment should be "cannot store more than 256 epochs" as there are 256 bits that can be set in a uint256
, but the epoch index cannot be more than 255
(epochs are indexed at 0).
Second, this restriction is never enforced and the 257th epoch can be claimed over and over again, which allows an attacker to steal the entire contract balance.
_token
that is already used in a different promotion (for example, WETH) with 257
epochs: createPromotion(_ticket, _token=WETH, _tokensPerEpoch=1e18, _startTimestamp=now, _epochDuration=0, _numberOfEpochs=257)
. They need to transfer 257 * 1e18
tokens. (The ticket contract does not matter, they can even create their own fake ticket contract where they own 100% of the total supply)claimRewards([256, 256, 256, ...])
. The _updateClaimedEpoch(_userClaimedEpochs, _epochId=256)
call that is supposed to mark the 257th epoch as claimed does not do anything as the 1 << 256
does not fit into a uint256
and is 0
. Then _userClaimedEpochs | (uint256(1) << _epochId) = 0 | (1 << 256) = 0 | 0 = 0
returns 0
, the initial value, which allows reclaiming it.I tested the relevant code in this test file.
All tokens in the contract (from other promotions) can be stolen.
Check _numberOfEpochs <= 256
in createPromotion
and _promotion.numberOfEpochs + _numberOfEpochs <= 256
in extendPromotion
#0 - PierrickGT
2021-12-13T16:14:53Z
🌟 Selected for report: cmichel
280.3604 USDC - $280.36
cmichel
The TwabRewards.claimRewards
function allows claiming the tokens of other users.
While the tokens are sent to the correct address, this can lead to issues with smart contracts that might rely on claiming the tokens themselves.
As one example, suppose the _to
address corresponds to a smart contract that has a function of the following form:
solidity function claimAndDoSomething(epochs) { uint256 claimedAmount = token.balanceOf(address(this)); twabRewards.claimRewards(address(this), 123, epochs); claimedAmount = token.balanceOf(address(this)) - claimedAmount; token.transfer(externalWallet, claimedAmount); }
If the contract has no other functions to transfer out funds, they may be locked forever in this contract. Claiming can also incur a taxable event and the timing is better left to the actual owner.
Do not allow users to claim on behalf of other addresses.
#0 - PierrickGT
2021-12-15T22:13:34Z
It's a design pattern that we use pretty regularly across our code base. In order to avoid scenarios like the one described in the issue, we expect developers to pay attention when calling this function. About taxable events, this is a relevant concern and we have not yet heard any user complain about someone claiming on their behalf. For these reasons, I've acknowledged the issue but we won't make any changes.
🌟 Selected for report: gpersoon
Also found by: 0xabcc, WatchPug, cmichel, danb, gzeon, pauliax, pmerkleplant, sirhashalot
cmichel
The _promotionEndTimestamp > 0
check in TwabRewards._requirePromotionActive
is redundant when _promotionEndTimestamp >= block.timestamp
is also checked as block.timestamp
can be assumed to always be greater than zero already.
#0 - PierrickGT
2021-12-13T16:22:11Z