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: 12/77
Findings: 2
Award: $365.84
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 9svR6w
Also found by: 0xdeadbeef0x, BAHOZ, codeislight, deliriusz, gasperpre, trustindistrust
320.1346 USDC - $320.13
Owners can leverage the insufficient funds in Chainlink subscription to successfully call startDraw
and not appoint a winner.
This is medium because theoretically, users can add funds to the subscriptions if such a case is noticed as anyone can fund a subscription. It is less likely, because it would require them to pay extra funds for a chance to win the raffle.
The bug is because fulfillRandomWords
will revert. This is due to Chainlink calculating that the payment is more than the balance from the subscription:
https://github.com/smartcontractkit/chainlink/blob/286a65065fcfa5e1b2362745079cdc218e40e68d/contracts/src/v0.8/VRFCoordinatorV2.sol#L595
(See previous submission: Raffle is insolvent if owner cancels Chainlink subscription [194])
The following POC demonstrates the creation of a raffle and a successful startDraw
.
No funding of the subscription.
Users will not able to call hasUserWon
After lock period. owner will withdraw the NFT.
Add the following test to VRFNFTRandomDraw.t.sol
:
https://github.com/code-423n4/2022-12-forgeries/blob/fc271cf20c05ce857d967728edfb368c58881d85/test/VRFNFTRandomDraw.t.sol#L541
function test_insufficiantFunding() public { 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: 2 weeks, keyHash: bytes32( 0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15 ), subscriptionId: subscriptionId }) ); vm.label(consumerAddress, "drawing instance"); mockCoordinator.addConsumer(subscriptionId, consumerAddress); VRFNFTRandomDraw drawing = VRFNFTRandomDraw(consumerAddress); targetNFT.setApprovalForAll(consumerAddress, true); // Start draw uint256 drawingId = drawing.startDraw(); vm.stopPrank(); // Revert because insufficiant funds vm.expectRevert(); mockCoordinator.fulfillRandomWords(drawingId, consumerAddress); vm.warp(2 weeks); // forward two weeks // validate no number was chosen (Although start draw was called) vm.expectRevert(IVRFNFTRandomDraw.NEEDS_TO_HAVE_CHOSEN_A_NUMBER.selector); drawing.hasUserWon(address(this)); vm.prank(admin); drawing.lastResortTimelockOwnerClaimNFT(); // validate admin received his NFT assertEq(targetNFT.balanceOf(admin), 1); }
To run the POC, execute: forge test -m "test_insufficiantFunding" -vvvv
VS Code, Foundry
Validate in the initialization of the contract or the startDraw
that the amount of funds currently in the subscription are sufficient for a minimum amount of redraws (related to the lock period). If possible, automatically transfer a portion of the revenue of the raffle tickets to pay for the subscription funding.
Here is the function that can receive the amount of funds in the subscription: https://github.com/smartcontractkit/chainlink/blob/286a65065fcfa5e1b2362745079cdc218e40e68d/contracts/src/v0.8/VRFCoordinatorV2.sol#L685
#0 - c4-judge
2022-12-17T13:52:26Z
gzeon-c4 marked the issue as duplicate of #194
#1 - c4-judge
2023-01-23T16:51:13Z
gzeon-c4 marked the issue as satisfactory
45.7078 USDC - $45.71
The owner can decide not to redraw
. Therefore if no winner is selected the first time, the owner can wait for the lock period to end and get their NFT back.
redraw
function is set with the onlyOwner
modifier. It can only be called by the owner.
https://github.com/code-423n4/2022-12-forgeries/blob/fc271cf20c05ce857d967728edfb368c58881d85/src/VRFNFTRandomDraw.sol#L203:
function redraw() external onlyOwner returns (uint256) {
VS Code
The protocol has placed a lot of trust in the owner. Users should be able to call redraw
as well.
#0 - c4-judge
2022-12-17T16:00:13Z
gzeon-c4 marked the issue as primary issue
#1 - c4-judge
2022-12-17T16:00:26Z
gzeon-c4 marked the issue as satisfactory
#2 - gzeoneth
2022-12-17T16:01:15Z
I think this is low risk given the permissioned design of the contract, will wait for sponsor comment.
#3 - iainnash
2022-12-19T20:04:35Z
@gzeoneth Agreed with low/QA. The contract is permissioned due to the dependency on chainlink and a relative level of trust of the owner.
#4 - c4-sponsor
2023-01-01T18:32:22Z
iainnash marked the issue as sponsor disputed
#5 - c4-judge
2023-01-07T17:01:45Z
gzeon-c4 changed the severity to QA (Quality Assurance)
#6 - c4-judge
2023-01-07T17:02:34Z
gzeon-c4 marked the issue as grade-b