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: 88/173
Findings: 2
Award: $19.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 0xMAKEOUTHILL, 0xMirce, 7siech, AkshaySrivastav, AlexCzm, Awesome, Aymen0909, Cryptor, Deivitto, DimitarDimitrov, ElKu, Garrett, Jayus, Josiah, Kenshin, KrisApostolov, RaymondFam, SovaSlava, Timenov, UdarTeam, amaechieth, btk, c3phas, codeislight, fellows, frankudoags, gzeon, hansfriese, luxartvinsec, millersplanet, mookimgo, navinavu, oberon, paspe, pavankv, petersspetrov, pfapostol, prestoncodes, rbserver, sakshamguruji, shark, thekmj, trustindistrust, tsvetanovv, usmannk, vagrant, vanko1, xAriextz, yosuke
2.5852 USDC - $2.59
Anyone can call the mint functions on line 83 & 97 and supply malicious arguments considering it's a public function and no adequate checks are in these blocks of code. The protocol is fundamentally broken due to the lack of access controls on these vital functions.
modifier onlyMinter() { msg.sender == minterAddress; _; }
The onlyMinter()
modifier does not produce the intended result of ensuring that only the minterAddress
can supply arguments to the mint functions. Currently the modifier pushes the minterAddress
into the stack with an SLOAD
and then pushes minterAddress
out of the stack 6 opcodes later without actually performing the check. You need to add the require statement to this modifier to prevent anyone from minting.
Manual review
The require statement needs to be in the modifier for the revert to execute.
modifier onlyMinter() { require(msg.sender == minterAddress); _; }
#0 - c4-judge
2023-02-05T05:32:56Z
kirk-baird marked the issue as duplicate of #9
#1 - c4-judge
2023-02-14T08:38:04Z
kirk-baird marked the issue as satisfactory
🌟 Selected for report: CodingNameKiki
Also found by: 0x1f8b, 0x4non, 0x5rings, 0xAgro, 0xMirce, 0xRobocop, 0xSmartContract, 0xackermann, AkshaySrivastav, ArmedGoose, Aymen0909, BClabs, Breeje, Dewaxindo, Diana, HollaDieWaldfee, IceBear, IllIllI, Iurii3, Josiah, Krayt, PaludoX0, Phenomana, PrasadLak, RaymondFam, Rolezn, SaeedAlipoor01988, SaharDevep, SleepingBugs, adriro, arialblack14, bin2chen, brevis, btk, carlitox477, carrotsmuggler, catellatech, chaduke, chrisdior4, cryptonue, cryptostellar5, csanuragjain, ddimitrov22, fellows, glcanvas, halden, hl_, horsefacts, jat, joestakey, kenta, libratus, lukris02, luxartvinsec, manikantanynala97, martin, matrix_0wl, nadin, nicobevi, oberon, peanuts, prestoncodes, rbserver, sakshamguruji, sashik_eth, sayan, seeu, thekmj, tnevler, trustindistrust, tsvetanovv, xAriextz, zadaru13, zaskoh
17.196 USDC - $17.20
Issue List | |
---|---|
[L-01] | MAXPROTOCOLREWARD() & PROTOCOLFEE() CAN ROUND DOWN TO 0 |
[L-02] | LACK OF MSG.SENDER CHECKS |
[L-03] | MISSING ZERO ADDRESS CHECKS |
[L-04] | USE 2 STEP OWNABLE |
[NC-01] | LACK OF EMIT EVENTS |
[NC-02] | OUTDATED COMPILER |
[NC-03] | SPELLING ERROR |
[NC-04] | OPEN TODO |
Context: Erc20Quest.sol:52 Erc20Quest.sol:96
function maxTotalRewards() public view returns (uint256) { return totalParticipants * rewardAmountInWeiOrTokenId; } function maxProtocolReward() public view returns (uint256) { return (maxTotalRewards() * questFee) / 10_000; }
function receiptRedeemers() public view returns (uint256) { return questFactoryContract.getNumberMinted(questId); } function protocolFee() public view returns (uint256) { return (receiptRedeemers() * rewardAmountInWeiOrTokenId * questFee) / 10_000; }
Description: If the totalParticipants, rewardAmountInWeiOrToken & questFee calculation comes out to below 10,000 all instances where the maxProtocolReward() is used will have faulty logic. The same problem is in protocolFee().
Recommendation: Add checks to ensure the the contract will only be constructed if reasonable minimum values for the amounts are given.
Context: RabbitHoleReceipt.sol:109
function getOwnedTokenIdsOfQuest( string memory questId_, address claimingAddress_ ) public view returns (uint[] memory) { uint msgSenderBalance = balanceOf(claimingAddress_); uint[] memory tokenIdsForQuest = new uint[](msgSenderBalance);
Description:
Your contracts lack checks almost everywhere, but we'll look at this specific example. Here we have a function that is explicitly defining itself to be dealing with msg.sender
logic by the verbiage msgSenderBalance
yet anyone can insert a claiming address and get the balanceOf
. The Quest.sol:99 may have the msg.sender, but that doesn't make the function safe due to its public declaration. This code block eventually leads to a _safeTransfer
in Erc20Quest.sol.
Recommendation:
+ require(msg.sender == claimingAddress_); uint msgSenderBalance = balanceOf(claimingAddress_);
Context: All contracts and functions fail to add this sanity check besides QuestFactory.sol:166
Description: Adding a zero address sanity check to your methods prevents potentially costly mistakes. Adding the check is an easy and effective way to reduce human error.
Recommendation:
require([ADDR_ARGUMENT] != address(0));
Context: Quest.sol
Description: Using a 1 step ownable is not recommended and can cause serious issues if a mistake is made. It's standard practice to make use of a multi step ownable setup instead.
Recommendation: Ownable2Step.sol
Context: Quest.sol
Description: Incredibly important functions such as start(), pause() and unPause() lack emit events. When state changes these won't emit information that users and dApps desire.
function pause() public onlyOwner onlyStarted { isPaused = true; emit Pause(isPaused); }
Solidity version 0.8.15 is missing features and bug features listed here.
QuestFactory.sol remave -> remove
Please ensure that TODOs don't end up being deployed.
#0 - c4-judge
2023-02-05T05:31:28Z
kirk-baird marked the issue as grade-b
#1 - c4-sponsor
2023-02-08T04:07:46Z
jonathandiep marked the issue as sponsor acknowledged