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: 75/173
Findings: 2
Award: $28.53
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
The smart contracts are well separated. Across all of the contracts the comments are descriptive which gives a clear idea of what the functions intend to do. This report is regarding the non-critical issues of the protocol.
Inside Erc20Quest.sol
:
function maxProtocolReward() public view returns (uint256) { return (maxTotalRewards() * questFee) / 10_000; } function protocolFee() public view returns (uint256) { return (receiptRedeemers() * rewardAmountInWeiOrTokenId * questFee) / 10_000; }
Erc20Quest.sol
/// @dev Requires that the balance of the rewards in the contract is greater than or equal to the maximum amount of rewards that can be claimed by all users and the protocol function start() public override { if (IERC20(rewardToken).balanceOf(address(this)) < maxTotalRewards() + maxProtocolReward()) revert TotalAmountExceedsBalance();
As we can see from the comment, the balance of the contract should be greater than or equal to the maximum amount rewards. However, only the <
sign is used and even if the amounts are equal the function will revert.
Recommendation
Add the =
sign.
Inside Quest.sol
the functions pause
and unpause
can be replaced by implementing the OpenZeppelin Pausable smart contract, since it is well-tested and optimized.
Inside Quest.sol
the imported OZ ECDSA library is unused. Consider removing it.
All of the contracts use floating pragmas ^0.8.15;
. As a best practice, it is always better to lock the pragma to a specific version.
Inside IQuest.sol
interface there is an open TODO:
// TODO clean this whole thing up
Recommendation
Use temporary TODOs as you work on a feature, but make sure to treat them before merging. Either add a link to a proper issue in your TODO, or remove it from the code.
settings: { optimizer: { enabled: true, runs: 5000, },
Protocol has enabled optional compiler optimizations in Solidity.
There have been several optimization bugs with security implications. Moreover, optimizations are actively being developed. Solidity compiler optimizations are disabled by default, and it is unclear how many contracts in the wild actually use them.
Therefore, it is unclear how well they are being tested and exercised.
High-severity security issues due to optimization bugs have occurred in the past. A high-severity bug in the emscripten-generated solc-js compiler used by Truffle and Remix persisted until late 2018. The fix for this bug was not reported in the Solidity CHANGELOG.
Another high-severity optimization bug resulting in incorrect bit shift results was patched in Solidity 0.5.6. More recently, another bug due to the incorrect caching of keccak256 was reported. A compiler audit of Solidity from November 2018 concluded that the optional optimizations may not be safe.
It is likely that there are latent bugs related to optimization and that new bugs will be introduced due to future optimizations.
Recommendation
Short term, measure the gas savings from optimizations and carefully weigh them against the possibility of an optimization-related bug.
Long term, monitor the development and adoption of Solidity compiler optimizations to assess their maturity.
Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.
https://docs.soliditylang.org/en/v0.8.17/style-guide.html
The initialize()
function is marked as public in three of the contracts: QuestFactory.sol
, RabbitHoleReceipt.sol
and RabbitHoleTickets.sol
.
If someone wants to extend via inheritance, it might make more sense that the overridden initialize(...)
function calls the internal {...}_init
function, not the parent public initialize(...)
function.
External instead of public would give more sense of the initialize(...)
functions to behave like a constructor (only called on deployment, so should only be called externally).
From a security point of view, it might be safer so that it cannot be called internally by accident in the child contract.
It might cost a bit less gas to use external over public.
It is possible to override a function from external to public (= “opening it up”) ✅ but it is not possible to override a function from public to external (= “narrow it down”). ❌
For the above reasons, you can change initialize(...)
to external:
https://github.com/OpenZeppelin/openzeppelin-contracts/issues/3750
The test coverage is relatively low and could be increased.
------------------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Lines |
---|---|---|---|---|---|
contracts/ | 81.62 | 59.23 | 74.03 | 81.46 | |
Erc1155Quest.sol | 100 | 75 | 100 | 85.71 | 35 |
Erc20Quest.sol | 100 | 66.67 | 100 | 94.12 | 60 |
Quest.sol | 90.91 | 81.58 | 73.33 | 90.48 | 123,130,141,146 |
QuestFactory.sol | 94.29 | 72.5 | 85.71 | 94.23 | 146,173,200 |
RabbitHoleReceipt.sol | 54.29 | 26.92 | 46.67 | 61.54 | ... 184,185,193 |
RabbitHoleTickets.sol | 58.33 | 18.75 | 50 | 59.09 | ... 113,114,122 |
ReceiptRenderer.sol | 100 | 100 | 100 | 100 | |
TicketRenderer.sol | 100 | 100 | 100 | 100 | |
contracts/interfaces/ | 100 | 100 | 100 | 100 | |
IQuest.sol | 100 | 100 | 100 | 100 | |
IQuestFactory.sol | 100 | 100 | 100 | 100 | |
contracts/test/ | 25 | 0 | 50 | 28.57 | |
SampleERC20.sol | 100 | 100 | 100 | 100 | |
SampleErc1155.sol | 100 | 100 | 100 | 100 | |
TestERC20.sol | 0 | 0 | 0 | 0 | 8,14,15,16,18 |
------------------------ | ---------- | ---------- | ---------- | ---------- | ---------------- |
All files | 78.47 | 57.46 | 72.84 | 79.72 | |
------------------------ | ---------- | ---------- | ---------- | ---------- | ---------------- |
#0 - c4-sponsor
2023-02-07T22:59:11Z
waynehoover marked the issue as sponsor confirmed
#1 - c4-judge
2023-02-14T09:57:35Z
kirk-baird marked the issue as grade-b
🌟 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
<X> += <Y>
COSTS MORE GAS THAN <X> = <X> + <Y>
FOR STATE VARIABLESUsing the addition operator instead of plus-equals saves gas
Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
Contracts are allowed to override their parents’ functions and change the visibility from external to public and can save gas by doing so.
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L159 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L172 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L179 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L219 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L140 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L145 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L150
If a function modifier or require such as onlyOwner/onlyX is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L150 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L63 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L57 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L142 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L159 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L165 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L172 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L179 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/QuestFactory.sol#L186 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleReceipt.sol#L65 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleReceipt.sol#L71 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleReceipt.sol#L77 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleReceipt.sol#L83 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleReceipt.sol#L90 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleTickets.sol#L54 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleTickets.sol#L60 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleTickets.sol#L66 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/RabbitHoleTickets.sol#L73
Recommended Mitigation Steps
Functions guaranteed to revert when called by normal users can be marked payable.
CONSTRUCTOR
TO PAYABLE
Saves ~13 gas per instance
https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc1155Quest.sol#L13 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Erc20Quest.sol#L17 https://github.com/rabbitholegg/quest-protocol/blob/8c4c1f71221570b14a0479c216583342bd652d8d/contracts/Quest.sol#L26
#0 - c4-judge
2023-02-14T09:56:06Z
kirk-baird marked the issue as grade-b