Forgeries contest - Soosh's results

A protocol for on-chain games with NFT prizes on Ethereum.

General Information

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

Forgeries

Findings Distribution

Researcher Performance

Rank: 58/77

Findings: 1

Award: $24.99

🌟 Selected for report: 1

🚀 Solo Findings: 0

Awards

24.9868 USDC - $24.99

Labels

bug
3 (High Risk)
primary issue
satisfactory
selected for report
sponsor confirmed
upgraded by judge
H-01

External Links

Lines of code

https://github.com/code-423n4/2022-12-forgeries/blob/main/src/VRFNFTRandomDraw.sol#L304-L320

Vulnerability details

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()
  • The winner should have up to drawTimelock to claim before an admin can call redraw() and pick a new winner.
  • The winner should have up to 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.

Impact

Protocol does not work as intended.

Recommendations

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)

#7 - c4-judge

2023-01-23T17:08:03Z

gzeon-c4 marked the issue as selected for report

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter