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: 8/93
Findings: 4
Award: $1,212.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: VAD37
Also found by: AuditsAreUS, IllIllI, MaratCerby, rfa, sorrynotsorry
1116.8018 USDC - $1,116.80
Judge has assessed an item in Issue #243 as Medium risk. The relevant finding follows:
Title: Using SafeERC20 library in ForgottenRunesWarriorsMinter.sol
There are some token which are not implementing current ERC20 standard (example: USDT, OmiseGo and BNB). Using SafeERC20 library will be nice to prevent them stuck when the transaction failed.
using SafeERC20 for IERC20; function forwardERC20s(IERC20 token, uint256 amount) public onlyOwner { require(address(msg.sender) != address(0)); IERC20(token).safeTransfer(msg.sender, amount);
#0 - gzeoneth
2022-06-18T17:09:10Z
Duplicate of #70
48.5872 USDC - $48.59
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L610 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L618
Posibilty of DOS at withdraw()
and withdrawAll
functions
payable.send() is considered the go-to solution for making payments to EOAs or other smart contracts. But since the amount of gas it forward to the called smart contract is very low, the called smart contract can easily run out of gas. This would make the payment impossible.
This might possible if the receiving contract is't designed carefully not to exceed the gas limit. Those function has a critical role to withdraw all the funds. So it is better to use payable.call
https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/
Use call()
method instead of send()
#0 - KenzoAgada
2022-06-06T12:48:21Z
Duplicate of #254
#1 - gzeoneth
2022-06-18T17:23:26Z
This is true but also these are onlyOwner
function where the owner have full control of the destination address to mitigate any issue in production. Downgrading to Low / QA.
🌟 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
30.3871 USDC - $30.39
QA REPORTS
Title: Implement check effect interaction when calling _safemint()
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L103-L104 Although I can't found any vulnerability in current implementation, its better doing state update before doing external call to prevent reentrancy
RECOMMENDED MITIGATION STEP:
numMinted += 1; // @audit-info: Better updating state before _safeMint() _safeMint(recipient, tokenId);
Title: Unnecessary initialize()
function
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L52-L54
The function doesn't have any modifier (as how most initializer function implemented) which make it called only once when the contract is deployed, and it also have same goal as directly call setMinter()
function. I recommend to initialize the minter
in the constructor()
(To prevent minter
== address(0)), removing initial function, and use setMinter()
instead of calling initialize()
to set minter
which can save both deployment and execution gas fee:
RECOMMENDED MITIGATION STEP:
constructor(string memory baseURI, address newMinter) ERC721('ForgottenRunesWarriorsGuild', 'WARRIORS') { setBaseURI(baseURI); setMinter(newMinter); }
Title: Throw false error
Occurrences: 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
If the user input 0 it will throw error message: 'You can summon no more than 20 Warriors at a time' I recommend to change the error message: 'You can summon 0 or no more than 20 Warriors at a time'
Title: No check that _newTime
> daStartTime
(+ expected duration)
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L448-L450
If mintListStartTime
< daStartTime (+ expected duration) is happened, bidSummon() will be DOS function. It's quite easy to handle, but for better implementation I recommend to check it
function setMintlistStartTime(uint256 _newTime) public onlyOwner { require(_newTime > daStartTime + duration); //@auditInfo: var duration(estimated) can be determined by admin/owner. mintlistStartTime = _newTime; }
It also can be implemented on all function which set publicStartTime
and claimsStartTime
https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L455-L464
Those implementation were implemented in setPhaseTime function (only for newPublicStartTime
and newClaimsStartTime
). But not when we call each function alone.
Title: Using SafeERC20 library in ForgottenRunesWarriorsMinter.sol
There are some token which are not implementing current ERC20 standard (example: USDT, OmiseGo and BNB). Using SafeERC20 library will be nice to prevent them stuck when the transaction failed.
using SafeERC20 for IERC20; function forwardERC20s(IERC20 token, uint256 amount) public onlyOwner { require(address(msg.sender) != address(0)); IERC20(token).safeTransfer(msg.sender, amount);
🌟 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
16.5224 USDC - $16.52
GAS FINDINGS
Title: Initializing var with default value
Occurrences: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L24
By declaring var by not set its default value (0 for uint) can save deployment gas cost Change to:
uint256 public numMinted;
Also for i inside for(): Occurrences: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L162 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L259-L261
Title: Using calldata
to store string parameter
Occurrences: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L42
Using calldata
to store read only string can save gas:
constructor(string calldata baseURI) {}
Title: Using msg.sender instead _msgSender()
Occurrences: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L101 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L115
Calling msg.sender directly can save more gas instead calling it through function:
require(msg.sender == minter, 'Not a minter');
Title: Preventing SLOAD in mint()
function
By caching numMinted
earlier in the function can save gas by preventing SLOAD if require()
condition is true
RECOMMENDED MITIGATION STEP
uint256 tokenId = numMinted; require(tokenId < MAX_WARRIORS, 'All warriors have been summoned'); //@audit-info: prevent SLOAD here require(_msgSender() == minter, 'Not a minter'); _safeMint(recipient, tokenId); numMinted += 1; return tokenId;
Title: Using && in require is not effective
Occurrences: 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
Using && is consuming 15 more execution gas fee although its cost more on deployment. I recommend to use multiple require() to save gas:
RECOMMENDED MItiGATION STEP:
require( numWarriors > 0, 'You can't summon 0 Warrior' ); require( numWarriors <= 20, 'You can summon no more than 20 Warriors at a time' );
Title: Using prefix increment and unchecked for i
in a for() loop
Occurrences: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L162 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L259-L261
Using prefix increment and unchecked for i
can save execution gas fee:
for (uint256 i = 0; i < numWarriors;) { _mint(msg.sender); } unchecked {++i;}
Title: Using ++var to do +1 increment
Occurrences: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L193 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsMinter.sol#L248
Change:
numSold += 1;
To:
++numSold;
It saves 67 execution gas cost per call
Title: Unnecessary checks numSold < maxForsale
numSold < maxForSale
(L207) will always true if numSold + numWarriors <= maxForSale
== true. I recommend to remove L207
Title: Some functions visibility can set to external
Occurrences: https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L52 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L85 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L94 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L113 https://github.com/code-423n4/2022-05-runes/blob/main/contracts/ForgottenRunesWarriorsGuild.sol#L163
Setting the visibility to external instead of public is more effective