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: 6/77
Findings: 3
Award: $618.25
🌟 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
A fraudulent raffle can be ran by admin
because recoverTimelock
is set in the initialize
function.
This enables an admin
to withdraw the prize before the rightful winner can claim it.
admin
sets recoverTimelock
for just over 1 week after initialize
is called.recoverTimelock
time has been reached admin
calls startDraw
.admin
calls lastResortTimelockOwnerClaimNFT
and effectively cheats the contest timelock ssytem.if (_settings.recoverTimelock < block.timestamp + WEEK_IN_SECONDS) { revert RECOVER_TIMELOCK_NEEDS_TO_BE_AT_LEAST_A_WEEK(); }
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 ); }
Set recoverTimelock
during startDraw()
or preferably as mentioned in another finding, call startDraw()
during initialize()
.
#0 - c4-judge
2022-12-17T16:27:01Z
gzeon-c4 marked the issue as duplicate of #146
#1 - c4-judge
2023-01-23T16:53:17Z
gzeon-c4 marked the issue as satisfactory
#2 - c4-judge
2023-01-23T17:09:39Z
gzeon-c4 changed the severity to 3 (High Risk)
🌟 Selected for report: gasperpre
Also found by: SmartSek, evan, hansfriese, orion
553.319 USDC - $553.32
https://github.com/code-423n4/2022-12-forgeries/blob/fc271cf20c05ce857d967728edfb368c58881d85/src/VRFNFTRandomDraw.sol#L125-L136 https://github.com/code-423n4/2022-12-forgeries/blob/fc271cf20c05ce857d967728edfb368c58881d85/src/VRFNFTRandomDraw.sol#L172-L198
admin
can sell, transfer or have their tokenId
NFT stolen via phishing attack to another address after initialising the VRFNFTRandomDraw
contract.
This would make it impossible to startDraw
because admin
is no longer in possession of tokenId
.
An admin
with the intention of never really going through with the raffle could maliciously initialize
a raffle just for PR purposes and then back out.
This makes the contract far from trustless.
initialize()
checks if admin
owns NFT but startDraw()
doesn't.
// Get owner of raffled tokenId and ensure the current owner is the admin try IERC721EnumerableUpgradeable(_settings.token).ownerOf( _settings.tokenId ) returns (address nftOwner) { // Check if address is the admin address if (nftOwner != admin) { revert DOES_NOT_OWN_NFT(); } } catch { revert TOKEN_BEING_OFFERED_NEEDS_TO_EXIST();
function 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; }
For the reasons above and others mentioned in separate findings, I believe the disjointed execution of initialize()
and startDraw()
allows for admin
to manipulate incentives for users to join a raffle and not follow through with his promise.
Make startDraw()
internal and call it within initialize()
.
#0 - c4-judge
2022-12-17T16:28:34Z
gzeon-c4 marked the issue as unsatisfactory: Invalid
#1 - trust1995
2023-01-16T09:56:25Z
@gzeon-c4 Seems to be dup of #104 . Same exact attack path as example 1 of 104.
#2 - c4-judge
2023-01-16T09:57:37Z
gzeon-c4 marked the issue as duplicate of #104
#3 - c4-judge
2023-01-23T16:51:58Z
gzeon-c4 marked the issue as satisfactory