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: 59/77
Findings: 1
Award: $19.22
🌟 Selected for report: 0
🚀 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
19.2206 USDC - $19.22
https://github.com/0xigami/vrf-nft-raffle/blob/main/src/VRFNFTRandomDraw.sol#L304
It was observed that if a winner is chosen and request.drawTimelock
is still remaining, still Admin can withdraw the NFT. This will cause winner to get nothing
startDraw
function. Lets say settings.recoverTimelock
is Xfunction startDraw() external onlyOwner returns (uint256) { // Only can be called on first drawing if (request.currentChainlinkRequestId != 0) { revert REQUEST_IN_FLIGHT(); } // Emit setup draw user event emit SetupDraw(msg.sender, settings); // Request initial roll _requestRoll(); // Attempt to transfer token into this address try IERC721EnumerableUpgradeable(settings.token).transferFrom( msg.sender, address(this), settings.tokenId ) {} catch { revert TOKEN_NEEDS_TO_BE_APPROVED_TO_CONTRACT(); } // Return the current chainlink request id return request.currentChainlinkRequestId; }
A winner is chosen by chainlink using the fulfillRandomWords
function
Assume User A has won
settings.recoverTimelock
(X) time has passed, but request.drawTimelock
is still left
Admin simply calls lastResortTimelockOwnerClaimNFT
function which transfer the NFT back to Admin even though request.drawTimelock
is not reached and winner was already present
function lastResortTimelockOwnerClaimNFT() external onlyOwner { // If recoverTimelock is not setup, or if not yet occurred if (settings.recoverTimelock > block.timestamp) { // Stop the withdraw revert RECOVERY_IS_NOT_YET_POSSIBLE(); } // Send event for indexing that the owner reclaimed the NFT emit OwnerReclaimedNFT(owner()); // Transfer token to the admin/owner. IERC721EnumerableUpgradeable(settings.token).transferFrom( address(this), owner(), settings.tokenId ); }
Admin should not be allowed to withdraw if request.drawTimelock
is not over.
function lastResortTimelockOwnerClaimNFT() external onlyOwner { require(request.drawTimelock < block.timestamp && request.hasChosenRandomNumber, "Cannot withdraw now"); ... }
#0 - c4-judge
2022-12-17T16:41:25Z
gzeon-c4 marked the issue as duplicate of #146
#1 - c4-judge
2023-01-23T16:46:19Z
gzeon-c4 marked the issue as satisfactory
#2 - c4-judge
2023-01-23T17:09:45Z
gzeon-c4 changed the severity to 3 (High Risk)