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: 137/173
Findings: 1
Award: $11.33
π Selected for report: 0
π Solo Findings: 0
π Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0xAgro, 0xSmartContract, 0xhacksmithh, 0xngndev, Aymen0909, Bnke0x0, Breeje, Deivitto, Diana, Dug, Iurii3, LethL, MiniGlome, NoamYakov, RaymondFam, ReyAdmirado, Rolezn, SAAJ, adriro, ali, arialblack14, atharvasama, c3phas, carlitox477, catellatech, chaduke, cryptonue, cryptostellar5, ddimitrov22, dharma09, doublesharp, favelanky, georgits, glcanvas, gzeon, halden, horsefacts, jasonxiale, joestakey, karanctf, lukris02, matrix_0wl, nadin, navinavu, saneryee, shark, thekmj
11.3269 USDC - $11.33
Saves gas in deployment due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where itβs used, and not adding another entry to the method ID table
Saves 11238 gas
on deployment
There is 1 instance of this issue:
Can be changed like this:
// @audit gas // deployment 5268486 -> 5257248 = -11238 gas saved bytes32 private constant CREATE_QUEST_ROLE = keccak256('CREATE_QUEST_ROLE');
Cache frequently used storage variables in memory/stack, converting multiple SLOAD into 1 SLOAD.
Saves 13398 - 11598 gas
on deployment
Saves 716 gas
on function createQuest call
There are 12 instances of this issue:
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L70 Can be changed like this:
Quest storage quest = quests[questId_]; if (quest.questAddress != address(0)) revert QuestIdUsed();
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L98 Can be changed like this:
quest.questAddress = address(newQuest);
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L99 Can be changed like this:
quest.totalParticipants = totalParticipants_;
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L129 Can be changed like this:
quest.questAddress = address(newQuest);
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L130 Can be changed like this:
quest.totalParticipants = totalParticipants_;
Deployment cost before: 5300118 after: 5288520
Saves 11598 gas
on deployment
Can be changed like this:
// @audit gas Quest storage quest = quests[questId_]; // deployment 5300118 -> 5288520 = -11598 gas saved Quest storage quest = quests[questId_]; return ( quest.questAddress, quest.totalParticipants, quest.numberMinted );
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L220 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L221 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L225 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L226
Deployment cost before: 5251239 after: 5231184
Saves 20055 gas
on deployment
Function calling cost before: 288075 after: 287092
Saves 983 gas
on function call
Can be changed like this:
Quest storage quest = quests[questId_];
if (quest.numberMinted + 1 > quests[questId_].totalParticipants) revert OverMaxAllowedToMint(); if (quest.addressMinted[msg.sender] == true) revert AddressAlreadyMinted();
quest.addressMinted[msg.sender] = true; quest.numberMinted++;
>=
is cheaper than >
Non-strict inequalities >=
are cheaper than strict ones >
. This is due to some supplementary checks (ISZERO, 3 gas)).
There are 3 instances of this issue:
Deployment cost before: 5234829 after: 5234589
Saves 240 gas
on deployment
Function calling cost before: 36037 after: 36034
Saves 3 gas
on function call
Can be changed like this:
if (questFee_ >= 10_001) revert QuestFeeTooHigh();
Deployment cost before: 5234589 after: 5231340
Saves 3249 gas
on deployment
Function calling cost before: 287092 after: 287016
Saves 76 gas
on function call
Can be changed like this:
if (quest.numberMinted >= quests[questId_].totalParticipants) revert OverMaxAllowedToMint();
Deployment cost before: 2761124 after: 2760896
Saves 228 gas
on deployment
Can be changed like this:
if (tokenIdsForQuest[i] >= 1) {
There are 8 instances of this issue:
Deployment cost before: 5246034 after: 5234829
Saves 11205 gas
on deployment
Function calling cost before: 36313 after: 36262
Saves 51 gas
on function call
Can be changed like this:
assembly{ sstore(claimSignerAddress.slot, claimSignerAddress_) }
Deployment cost before: 5257248 after: 5246034
Saves 11214 gas
on deployment
Function calling cost before: 36369 after: 36318
Saves 51 gas
on function call
Can be changed like this:
assembly{ sstore(protocolFeeRecipient.slot, protocolFeeRecipient_) }
Deployment cost before: 2776002 after: 2766963
Saves 9039 gas
on deployment
Can be changed like this:
assembly{ sstore(royaltyRecipient.slot, royaltyRecipient_) }
Deployment cost before: 2766963 after: 2761124
Saves 5839 gas
on deployment
Can be changed like this:
assembly{ sstore(minterAddress.slot, minterAddress_) }
Can be changed like this:
assembly{ sstore(royaltyFee.slot, royaltyFee_) }
Deployment cost before: 2623629 after: 2612418
Saves 11211 gas
on deployment
Can be changed like this:
assembly{ sstore(royaltyRecipient.slot, royaltyRecipient_) }
Can be changed like this:
assembly{ sstore(royaltyFee.slot, royaltyFee_) }
Deployment cost before: 2612418 after: 2604455
Saves 7963 gas
on deployment
Can be changed like this:
assembly{ sstore(minterAddress.slot, minterAddress_) }
Explicitly initializing a variable with it's default value costs unnecessary gas.
There are 7 instances of this issue:
Can be changed like this:
uint foundTokens;
Can be changed like this:
uint filterTokensIndexTracker;
Can be changed like this:
for (uint i; i < msgSenderBalance; i++)
Can be changed like this:
for (uint i; i < msgSenderBalance; i++)
Can be changed like this:
redeemedTokens;
Can be changed like this:
for (uint i; i < tokenIds_.length; i++) {
Can be changed like this:
for (uint i; i < tokens.length; i++) {
There are 4 instances of this issue:
Deployment cost before: 2760896 after: 2758760
Saves 2136 gas
on deployment
Can be changed like this:
for (uint i; i < msgSenderBalance;) { // @audit uint tokenId = tokenOfOwnerByIndex(claimingAddress_, i); if (keccak256(bytes(questIdForTokenId[tokenId])) == keccak256(bytes(questId_))) { tokenIdsForQuest[i] = tokenId; foundTokens++; } unchecked { ++i; } }
Deployment cost before: 2758760 after: 2756564
Saves 2196 gas
on deployment
Can be changed like this:
for (uint i; i < msgSenderBalance;) { // @audit if (tokenIdsForQuest[i] >= 1) { filteredTokens[filterTokensIndexTracker] = tokenIdsForQuest[i]; filterTokensIndexTracker++; } unchecked { ++i; } }
Can be changed like this:
for (uint i; i < tokenIds_.length;) { claimedList[tokenIds_[i]] = true; unchecked { ++i; } }
Can be changed like this:
for (uint i; i < tokens.length;) { if (!isClaimed(tokens[i])) { redeemableTokenCount++; } unchecked { ++i; } }
The difference between the prefix increment and postfix increment expression lies in the return value of the expression.
The prefix increment expression (++i) returns the updated value after it's incremented. The postfix increment expression (i++) returns the original value.
The prefix increment expression is cheaper in terms of gas.
Consider using the prefix increment expression whenever the return value is not needed.
There are 3 instances of this issue:
Deployment cost before: 2756564 after: 2756156
Saves 408 gas
on deployment
Can be changed like this:
++foundTokens;
Deployment cost before: 2756156 after: 2755736
Saves 420 gas
on deployment
Can be changed like this:
++filterTokensIndexTracker;
Can be changed like this:
++redeemableTokenCount;
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety.
There is 1 instance of this issue:
Deployment cost before: 2604455 after: 2602931
Saves 1524 gas
on deployment
Can be changed like this:
assembly { let c := mul(salePrice_, royaltyFee.slot) if lt(c, salePrice_) { mstore(0x00, "overflow") revert(0x00, 0x20) } royaltyPayment := div(c, 10000) if gt(royaltyPayment, c) { mstore(0x00, "underflow") revert(0x00, 0x20) } }
Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.
There is 1 instance of this issue:
Deployment cost before: 5231184 after: 5230968
Saves 216 gas
on deployment
Function calling cost before: 1500237 after: 1499669
Saves 568 gas
on function call
Can be changed like this:
uint256 len = tokenIds_.length; for (uint i; i < len;) { claimedList[tokenIds_[i]] = true; unchecked { ++i; } }
There is 1 instance of this issue:
Can be changed like this:
redeemedTokens = redeemedTokens + redeemableTokenCount;
These changes saves more than ~122k gas in total.
#0 - c4-judge
2023-02-05T07:52:46Z
kirk-baird marked the issue as grade-b