Forgeries contest - rvierdiiev'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: 26/77

Findings: 3

Award: $90.88

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

19.2206 USDC - $19.22

Labels

bug
3 (High Risk)
satisfactory
duplicate-146

External Links

Lines of code

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

Vulnerability details

Impact

Because recoverTimelock can be less than block.timestamp + drawBufferTime when startDraw is called, it's possible that user will win nft, but owner will call lastResortTimelockOwnerClaimNFT before winner will call winnerClaimNFT.

Proof of Concept

When VRFNFTRandomDraw contract is initialized it is provided with few variables. We will consider drawBufferTime and recoverTimelock. drawBufferTime time is needed to give winner some time to claim nft. It should be in range [1hour..1month]. Once new request for random is sent then request.drawTimelock = block.timestamp + drawBufferTime. recoverTimelock is the timestamp when owner can withdraw nft from contract. It is designed to be used if noone claimed nft for long time. It should be at least one week in future. When this data has passed then user can call lastResortTimelockOwnerClaimNFT function.

Owner can manipulate with lottery in following way.

Scenario. 1.Owner created contract and initialized with recoverTimelock in 1 week and drawBufferTime 1 day. 2.Owner didn't call startDraw for 7 days. 3.On 8th day when recoverTimelock has passed owner calls startDraw function. 4.The winner is found to be Alice, but owner doesn't like Alice and he calls lastResortTimelockOwnerClaimNFT function and withdraw nft. 5.As result Alice has won, but couldn't claim nft. Owner can in such way filter winners.

Tools Used

VsCode

Inside lastResortTimelockOwnerClaimNFT use such check. This will leave winners time to claim nft.

    if (settings.recoverTimelock > block.timestamp || block.timestamp < request.drawTimelock) {
            // Stop the withdraw
            revert RECOVERY_IS_NOT_YET_POSSIBLE();
        }

#0 - c4-judge

2022-12-17T12:35:19Z

gzeon-c4 marked the issue as duplicate of #146

#1 - c4-judge

2022-12-17T12:35:22Z

gzeon-c4 marked the issue as satisfactory

Awards

45.7078 USDC - $45.71

Labels

bug
downgraded by judge
grade-b
QA (Quality Assurance)
Q-10

External Links

Lines of code

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#L264-L274

Vulnerability details

Impact

After lastResortTimelockOwnerClaimNFT is called function hasUserWon will still show that user is winner.

Proof of Concept

When settings.recoverTimelock has passed owner can withdraw nft using lastResortTimelockOwnerClaimNFT function. https://github.com/code-423n4/2022-12-forgeries/blob/main/src/VRFNFTRandomDraw.sol#L304-L320

    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
        );
    }

Note, that this function doesn't do anything with request variable. In case if before lastResortTimelockOwnerClaimNFT function was called winner was choosed then request variable is provided with winner data.

And then when winner will call hasUserWon function it will still show that user won, but he is not able to claim. This is because request.hasChosenRandomNumber is still true. https://github.com/code-423n4/2022-12-forgeries/blob/main/src/VRFNFTRandomDraw.sol#L264-L274

    function hasUserWon(address user) public view returns (bool) {
        if (!request.hasChosenRandomNumber) {
            revert NEEDS_TO_HAVE_CHOSEN_A_NUMBER();
        }


        return
            user ==
            IERC721EnumerableUpgradeable(settings.drawingToken).ownerOf(
                request.currentChosenTokenId
            );
    }

As result user has shown incorrect info, as he was winner in the past, but in present moment he is not winner as winner can claim nft, but nft is already transferred to owner.

Tools Used

VsCode

When lastResortTimelockOwnerClaimNFT is called set request.hasChosenRandomNumber to false.

#0 - c4-judge

2022-12-17T12:55:44Z

gzeon-c4 changed the severity to QA (Quality Assurance)

#1 - c4-judge

2022-12-17T17:23:27Z

gzeon-c4 marked the issue as grade-c

#2 - c4-judge

2023-01-16T05:13:39Z

gzeon-c4 marked the issue as grade-b

Awards

25.9485 USDC - $25.95

Labels

bug
downgraded by judge
G (Gas Optimization)
grade-b
G-11

External Links

Lines of code

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

Vulnerability details

Impact

In case if request was sent, but result was not received it should not be possible to call redraw function

Proof of Concept

redraw function is created to ask for new random form chainlink in case if user hasn't claimed nft. https://github.com/code-423n4/2022-12-forgeries/blob/main/src/VRFNFTRandomDraw.sol#L204-L206

        if (request.drawTimelock >= block.timestamp) {
            revert TOO_SOON_TO_REDRAW();
        }

This check is present to control when you can call redraw. In case if respond time from chainlink is more than request.drawTimelock, then there is no need to call redraw as it's just waste of gas. You just need to wait, maybe chainlink subscription is not funded.

Tools Used

VsCode

Use such check.

        if (request.drawTimelock >= block.timestamp || !request.hasChosenRandomNumber) {
            revert TOO_SOON_TO_REDRAW();
        }

#0 - c4-judge

2022-12-17T16:20:53Z

gzeon-c4 changed the severity to G (Gas Optimization)

#1 - c4-judge

2022-12-17T17:45:16Z

gzeon-c4 marked the issue as grade-b

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