Platform: Code4rena
Start Date: 13/12/2022
Pot Size: $36,500 USDC
Total HM: 5
Participants: 77
Period: 3 days
Judge: gzeon
Total Solo HM: 1
Id: 191
League: ETH
Rank: 58/77
Findings: 1
Award: $24.99
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: Soosh
Also found by: 9svR6w, Apocalypto, Ch_301, HE1M, Koolex, SmartSek, Titi, Trust, Zarf, bin2chen, btk, carrotsmuggler, csanuragjain, dic0de, dipp, gz627, hansfriese, hihen, imare, immeas, indijanc, jadezti, kuldeep, ladboy233, maks, neumo, obront, rvierdiiev, sces60107, sk8erboy
24.9868 USDC - $24.99
https://github.com/code-423n4/2022-12-forgeries/blob/main/src/VRFNFTRandomDraw.sol#L304-L320
On contest page:
"If no users ultimately claim the NFT, the admin specifies a timelock period after which they can retrieve the raffled NFT."
Let's assume a recoverTimelock of 1 week.
The specification suggests that 1 week from the winner not having claimed the NFT. Meaning that the admin should only be able to call lastResortTimelockOwnerClaimNFT()
only after <block.timestamp at fulfillRandomWords()> + request.drawTimelock + 1 weeks
.
Specification:
drawTimelock recoverTimelock │ │ ▼ ▼ ┌────┬──────────────────────────────┐ │ │ 1 week │ └────┴──────────────────────────────┘ ▲ │ fulfillRandomWords()
drawTimelock
to claim before an admin can call redraw()
and pick a new winner.recoverTimelock
to claim before an admin can call lastResortTimelockOwnerClaimNFT()
to cancel the raffle.But this is not the case.
recoverTimelock is set in the initialize(...)
function and not anywhere else. That means 1 week from initialization, the admin can call lastResortTimelockOwnerClaimNFT()
. redraw()
also does not update recoverTimelock
.
In fact, startDraw()
does not have to be called at the same time as initialize(...)
. That means that if the draw was started after having been initialized for 1 week, the admin can withdraw at any time after that.
Protocol does not work as intended.
Just like for drawTimelock
, recoverTimelock
should also be updated for each dice roll.
<block.timestamp at fulfillRandomWords()> + request.drawTimelock + <recoverBufferTime>
. where <recoverBufferTime>
is essentially the drawBufferTime
currently used, but for recoverTimelock
.
Note: currently, drawTimelock
is updated in the _requestRoll()
function. This is "technically less correct" as chainlink will take some time before fulfillRandomWords(...)
callback. So the timelock is actually set before the winner has been chosen. This should be insignificant under normal network conditions (Chainlink VRF shouldn't take > 1min) but both timelocks should be updated in the same function - either _requestRoll()
or fulfillRandomWords(...)
.
#0 - c4-judge
2022-12-17T12:34:35Z
gzeon-c4 marked the issue as primary issue
#1 - c4-judge
2022-12-17T12:34:53Z
gzeon-c4 marked the issue as satisfactory
#2 - iainnash
2022-12-19T20:25:53Z
This seems to be a dupe of a previous issue where the timelock is not passed.
Give this timelock is validated from the end of the auction the risk here seems low.
#3 - iainnash
2022-12-19T20:25:57Z
This seems to be a dupe of a previous issue where the timelock is not passed.
Give this timelock is validated from the end of the auction the risk here seems low.
#4 - c4-sponsor
2023-01-01T18:38:18Z
iainnash marked the issue as sponsor confirmed
#5 - c4-judge
2023-01-16T05:35:29Z
gzeon-c4 changed the severity to 3 (High Risk)
#6 - gzeon-c4
2023-01-16T05:35:34Z
#7 - c4-judge
2023-01-23T17:08:03Z
gzeon-c4 marked the issue as selected for report