Platform: Code4rena
Start Date: 03/05/2022
Pot Size: $30,000 USDC
Total HM: 6
Participants: 93
Period: 3 days
Judge: gzeon
Id: 118
League: ETH
Rank: 6/93
Findings: 5
Award: $1,428.33
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: pedroais
Also found by: AuditsAreUS, BowTiedWardens, GimelSec, IllIllI, WatchPug, defsec, leastwood
542.7657 USDC - $542.77
Judge has assessed an item in Issue #225 as Medium risk. The relevant finding follows:
Dutch Auction parameters can be changed by a malicious owner, after It is started. The malicious owner can manipulate all contract behaviour.
Navigate to the following contract.
The contract should implement the pause modifier on the setter functions.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L120
function setDaStartTime(uint256 _newTime) public onlyOwner { daStartTime = _newTime; } /** * @notice set the mintlist start timestamp */ function setMintlistStartTime(uint256 _newTime) public onlyOwner { mintlistStartTime = _newTime; } /** * @notice set the public sale start timestamp */ function setPublicStartTime(uint256 _newTime) public onlyOwner { publicStartTime = _newTime; } /** * @notice set the claims phase start timestamp */ function setClaimsStartTime(uint256 _newTime) public onlyOwner { claimsStartTime = _newTime; } /** * @notice set the self refund phase start timestamp */ function setSelfRefundsStartTime(uint256 _newTime) public onlyOwner { selfRefundsStartTime = _newTime; }
Code Review
Implement whenNotPaused modifier on the setter function. The variables should be changed when the contract is paused.
function setDaStartTime(uint256 _newTime) public onlyOwner whenNotPaused { daStartTime = _newTime; }
#0 - gzeoneth
2022-06-20T17:29:23Z
Duplicate of #38
🌟 Selected for report: throttle
Also found by: 0xDjango, BowTiedWardens, WatchPug, defsec, dipp, fatherOfBlocks, gzeon, hake, reassor, shung, unforgiven
296.7571 USDC - $296.76
Judge has assessed an item in Issue #225 as Medium risk. The relevant finding follows:
During the code review, It has been observed that all timestamps are missing sanity checks. With the following scenario, that can have serious consequences.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L463 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L456 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L449 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L442
Code Review
Consider adding sanity check in the discussed setter functions: require(time >= block.timestamp);.
#0 - gzeoneth
2022-06-20T17:32:38Z
Duplicate of #27
🌟 Selected for report: Kulk0
Also found by: 0x1f8b, 0xDjango, BowTiedWardens, Dinddle, broccolirob, defsec, dirk_y, hyh, rajatbeladiya, throttle, unforgiven
246.5367 USDC - $246.54
Judge has assessed an item in Issue #225 as Medium risk. The relevant finding follows:
With the teamSummon function, owner can mint unlimited warriors. This poses a security risk. The max/min limit should be implemented at the beginning of the function.
Navigate to the following contract.
The following contract function has an onlyOwner modifier and there is no limitation on the minting.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L257
function teamSummon(address recipient, uint256 count) external onlyOwner { require(address(recipient) != address(0), 'address req'); for (uint256 i = 0; i < count; i++) { _mint(recipient); } }
Code Review
It is recommended to implement limitation on the teamSummon function. The team should not mint without any restriction.
#0 - gzeoneth
2022-06-19T16:00:33Z
🌟 Selected for report: defsec
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0x52, 0xDjango, 0xf15ers, 0xkatana, 0xliumin, AuditsAreUS, BowTiedWardens, CertoraInc, Cr4ckM3, Funen, GimelSec, Hawkeye, IllIllI, Kulk0, M0ndoHEHE, MaratCerby, Picodes, Ruhum, TerrierLover, TrungOre, VAD37, WatchPug, berndartmueller, broccolirob, catchup, cccz, cryptphi, csanuragjain, delfin454000, dirk_y, eccentricexit, ellahi, fatherOfBlocks, gzeon, hake, hansfriese, hickuphh3, horsefacts, hubble, hyh, ilan, joestakey, kebabsec, kenta, kenzo, leastwood, m9800, marximimus, minhquanym, oyc_109, p4st13r4, pauliax, pedroais, peritoflores, plotchy, rajatbeladiya, reassor, rfa, robee, rotcivegaf, samruna, shenwilly, shung, simon135, sorrynotsorry, sseefried, teddav, throttle, tintin, unforgiven, z3s
296.351 USDC - $296.35
The afunctions that change critical parameters should emit events. Events allow capturing the changed parameters so that off-chain tools/interfaces can register such changes with timelocks that allow users to evaluate them and consider if they would like to engage/exit based on how they perceive the changes as affecting the trustworthiness of the protocol or profitability of the implemented financial services. The alternative of directly querying on-chain contract state for such changes is not considered practical for most users/usages.
Missing events and timelocks do not promote transparency and if such changes immediately affect users’ perception of fairness or trustworthiness, they could exit the protocol causing a reduction in liquidity which could negatively impact protocol TVL and reputation.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L441 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L448 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L455 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L462 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L469 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L480
See similar High-severity H03 finding OpenZeppelin’s Audit of Audius (https://blog.openzeppelin.com/audius-contracts-audit/#high) and Medium-severity M01 finding OpenZeppelin’s Audit of UMA Phase 4 (https://blog.openzeppelin.com/uma-audit-phase-4/)
None
Add events to all functions that change critical parameters.
The critical procedures should be two step process.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L441 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L448 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L455 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L462 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L469 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L480
Code Review
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
In the contracts, floating pragmas should not be used. Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.
https://swcregistry.io/docs/SWC-103
All Contracts
Manual code review
Lock the pragma version: delete pragma solidity 0.8.10 in favor of pragma solidity 0.8.10.
Missing checks for zero-addresses may lead to infunctional protocol, if the variable addresses are updated incorrectly.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L544 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L528
Code Review
Consider adding zero-address checks in the discussed constructors: require(newAddr != address(0));.
During the code review, It has been observed that all timestamps are missing sanity checks. With the following scenario, that can have serious consequences.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L463 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L456 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L449 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L442
Code Review
Consider adding sanity check in the discussed setter functions: require(time >= block.timestamp);.
The owner is the authorized user in the solidity contracts. Usually, an owner can be updated with transferOwnership function. However, the process is only completed with single transaction. If the address is updated incorrectly, an owner functionality will be lost forever.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L15 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L14
Code Review
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two step procedure on the critical functions.
Line Reference
https://github.com/code-423n4/2022-05-runes/blob/main/package.json#L68
I can verify that the installed version is 4.2.0 by executing the following commands:
yarn install yarn list @openzeppelin/contracts
Update the versions of @openzeppelin/contracts and @openzeppelin/contracts-upgradeable to be the latest in package.json. I also recommend double checking the versions of other dependencies as a precaution, as they may include important bug fixes.
It is good to add a require() statement that checks the return value of token transfers or to use something like OpenZeppelin’s safeTransfer/safeTransferFrom unless one is sure the given token reverts in case of a failure. Failure to do so will cause silent failures of transfers and affect token accounting in contract.
Reference: This similar medium-severity finding from Consensys Diligence Audit of Fei Protocol: https://consensys.net/diligence/audits/2021/01/fei-protocol/#unchecked-return-value-for-iweth-transfer-call
Navigate to the following contract.
transfer/transferFrom functions are used instead of safe transfer/transferFrom on the following contracts.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L175
Code Review
Consider using safeTransfer/safeTransferFrom or require() consistently.
During the code review, It has been noticed that to bidding mechanism is vulnerable to front-running. The bidding mechanism can have EOA check on the contract.
Navigate to the following contract.
The contract does not check for the External Owned Accounts. Without the check, any contract can interact with the function.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L120
Code Review
Consider to check EOA at the beginning of the function.
msg.sender == tx.origin && !isContract(msg.sender)
Dutch Auction parameters can be changed by a malicious owner, after It is started. The malicious owner can manipulate all contract behaviour.
Navigate to the following contract.
The contract should implement the pause modifier on the setter functions.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L120
function setDaStartTime(uint256 _newTime) public onlyOwner { daStartTime = _newTime; } /** * @notice set the mintlist start timestamp */ function setMintlistStartTime(uint256 _newTime) public onlyOwner { mintlistStartTime = _newTime; } /** * @notice set the public sale start timestamp */ function setPublicStartTime(uint256 _newTime) public onlyOwner { publicStartTime = _newTime; } /** * @notice set the claims phase start timestamp */ function setClaimsStartTime(uint256 _newTime) public onlyOwner { claimsStartTime = _newTime; } /** * @notice set the self refund phase start timestamp */ function setSelfRefundsStartTime(uint256 _newTime) public onlyOwner { selfRefundsStartTime = _newTime; }
Code Review
Implement whenNotPaused modifier on the setter function. The variables should be changed when the contract is paused.
function setDaStartTime(uint256 _newTime) public onlyOwner whenNotPaused { daStartTime = _newTime; }
With the teamSummon function, owner can mint unlimited warriors. This poses a security risk. The max/min limit should be implemented at the beginning of the function.
Navigate to the following contract.
The following contract function has an onlyOwner modifier and there is no limitation on the minting.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L257
function teamSummon(address recipient, uint256 count) external onlyOwner { require(address(recipient) != address(0), 'address req'); for (uint256 i = 0; i < count; i++) { _mint(recipient); } }
Code Review
It is recommended to implement limitation on the teamSummon function. The team should not mint without any restriction.
#0 - gzeoneth
2022-06-20T17:40:49Z
5, 10, 11 upgraded 4, 9(#147) Low 1, 2, 3, 6, 7, 8 Non-Critical
🌟 Selected for report: BowTiedWardens
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xDjango, 0xNazgul, 0xProf, 0xc0ffEE, 0xf15ers, 0xkatana, 0xliumin, ACai, AlleyCat, CertoraInc, Cityscape, Cr4ckM3, DavidGialdi, Dinddle, FSchmoede, Funen, GimelSec, Hawkeye, IllIllI, Kulk0, M0ndoHEHE, MaratCerby, MiloTruck, Picodes, RoiEvenHaim, Tadashi, TerrierLover, TrungOre, VAD37, WatchPug, antonttc, catchup, defsec, delfin454000, dirk_y, eccentricexit, ellahi, fatherOfBlocks, gzeon, hake, hansfriese, hickuphh3, horsefacts, ilan, joestakey, kebabsec, kenta, kenzo, marximimus, minhquanym, noobie, oyc_109, p4st13r4, pauliax, rajatbeladiya, reassor, rfa, robee, rotcivegaf, saian, samruna, shenwilly, shung, simon135, slywaters, sorrynotsorry, throttle, unforgiven, z3s
45.9055 USDC - $45.91
> 0 can be replaced with != 0 for gas optimization
Shortening revert strings to fit in 32 bytes will decrease deploy time gas and will decrease runtime gas when the revert condition has been met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
Revert strings > 32 bytes are here:
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L70 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L116
Manual Review
Shorten the revert strings to fit in 32 bytes. That will affect gas optimization.
For the arithmetic operations that will never over/underflow, using the unchecked directive (Solidity v0.8 has default overflow/underflow checks) can save some gas from the unnecessary internal over/underflow checks.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L104 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L219 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L220
None
Consider applying unchecked arithmetic where overflow/underflow is not possible. Example can be seen from below.
Unchecked{i++};
Since _amount can be 0. Checking if (_amount != 0) before the transfer can potentially save an external call and the unnecessary gas cost of a 0 token transfer.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L627 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L610
All Contracts
None
Consider checking amount != 0.
When a variable is declared solidity assigns the default value. In case the contract assigns the value again, it costs extra gas.
Example: uint x = 0 costs more gas than uint x without having any different functionality.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L259 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L162
Code Review
uint x = 0 costs more gas than uint x without having any different functionality.
> 0 can be replaced with != 0 for gas optimization
!= 0
is a cheaper operation compared to > 0
, when dealing with uint. (Before Pragma 0.8.13)
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L211 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L378 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L141
Code Review
Use "!=0" instead of ">0" for the gas optimization.
Using newer compiler versions and the optimizer gives gas optimizations and additional safety checks are available for free.
All Contracts
Solidity 0.8.10 has a useful change which reduced gas costs of external calls which expect a return value: https://blog.soliditylang.org/2021/11/09/solidity-0.8.10-release-announcement/
Solidity 0.8.13 has some improvements too but not well tested.
Code Generator: Skip existence check for external contract if return data is expected. In this case, the ABI decoder will revert if the contract does not exist
All Contracts
None
Consider to upgrade pragma to at least 0.8.10.
++i is more gas efficient than i++ in loops forwarding.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L259 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L162
Code Review
It is recommend to use unchecked{++i} and change i declaration to uint256.
Using double require instead of operator && can save more gas.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L141 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L211
Code Review
Example
using &&: function check(uint x)public view{ require(x == 0 && x < 1 ); } // gas cost 21630 using double require: require(x == 0 ); require( x < 1); } } // gas cost 21622
During the code review, It has been observed that the following check is redundant. Msg.sender can not be never zero address.
require(address(msg.sender) != address(0));
Code Review
Delete redundant check.
Strict inequalities add a check of non equality which costs around 3 gas.
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L355
Code Review
Use >= or <= instead of > and < when possible.
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Source Custom Errors in Solidity:
Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
Instances include:
All require Statements
Code Review
Recommended to replace revert strings with custom errors.