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: 61/93
Findings: 2
Award: $45.77
π Selected for report: 0
π Solo Findings: 0
π 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.2759 USDC - $30.28
onlyOwner
functionsIn ForgottenRunesWarriorsGuild
, all non-view functions except mint
has onlyOwner
modifier but 2 of them have external
visibility and others have public
visibility.
external
https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L152 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L157
public
https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L52 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L129 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L137 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L145 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L163 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L173
If owner is EOA, should change all of them to external
to save gas.
safeTransfer
instead of transfer
transfer() might return false instead of reverting, in this case, ignoring return value leads to considering it successful. Use safeTransfer() or check the return value if length of returned data is > 0.
https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsMinter.sol#L629 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L175
Use safeTransfer
instead
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
15.491 USDC - $15.49
bytes32
rather than string
If data can fit into 32 bytes, then you should use bytes32 datatype rather than string as it is much cheaper in solidity. Basically, Any fixed size variable in solidity is cheaper than variable size. That will save gas on the contract.
Change string
to bytes32
msg.sender != address(0)
In function ForgottenRunesWarriorsGuild.forwardERC20s
, line 174 check if msg.sender != address(0)
. Itβs unnecessary since msg.sender
will never equal address(0)
(either EOA address or contract address).
Instead maybe you should check if address(token) != address(0)
.
Similarly line 628 of ForgottenRunesWarriorsMinter
.
https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsGuild.sol#L174 https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsMinter.sol#L628
Remove line 174 in ForgottenRunesWarriorsGuild
and line 628 in ForgottenRunesWarriorsMinter
daMinters
list uniqueCheck if an address existed in daMinters
or not can be done by checking daNumMinted[msg.sender] == 0
It can save gas when we iterate through daMinters
to issue refund.
https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsMinter.sol#L151-L153
daMinters
is only updated in line 151. Also in line 153, daNumMinted[msg.sender] += numWarriors
and numWarriors > 0
is checked in line 141.
So msg.sender
is existed in daMinters
only when daNumMinted[msg.sender] > 0
Change line 151 to
if (daNumMinted[msg.sender] == 0) { daMinters.push(msg.sender); }
Function currentDaPrice()
is called everytime users call bidSummon
and cost significant amount of gas. This function uses multiple states repeatedly to calculate current price.
We can save gas by caching some states that been used repeatedly. It will reduced gas cost for users especially when there is a gas war during auction.
https://github.com/code-423n4/2022-05-runes/blob/060b4f82b79c8308fe65674a39a07c44fa586cd3/contracts/ForgottenRunesWarriorsMinter.sol#L275-L297
Using the code in next part reduce average gas usage of bidSummon
function from 285939
to 285228
(testset in repo)
Update function currentDaPrice()
to
function currentDaPrice() public view returns (uint256) { uint256 _startPrice = startPrice; uint256 _lowestPrice = lowestPrice; uint256 _daStartTime = daStartTime; uint256 _daPriceCurveLength = daPriceCurveLength; uint256 _daDropInterval = daDropInterval; if (!daStarted()) { return _startPrice; } if (block.timestamp >= _daStartTime + _daPriceCurveLength) { // end of the curve return _lowestPrice; } uint256 dropPerStep = (_startPrice - _lowestPrice) / (_daPriceCurveLength / _daDropInterval); uint256 elapsed = block.timestamp - _daStartTime; uint256 steps = elapsed / _daDropInterval; uint256 stepDeduction = steps * dropPerStep; // don't go negative in the next step if (stepDeduction > _startPrice) { return _lowestPrice; } uint256 currentPrice = _startPrice - stepDeduction; return currentPrice > _lowestPrice ? currentPrice : _lowestPrice; }