Platform: Code4rena
Start Date: 31/01/2023
Pot Size: $90,500 USDC
Total HM: 47
Participants: 169
Period: 7 days
Judge: LSDan
Total Solo HM: 9
Id: 211
League: ETH
Rank: 74/169
Findings: 2
Award: $105.30
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x3b, 0xAgro, 0xBeirao, 0xMirce, 0xNineDec, 0xRobocop, 0xSmartContract, 0xTraub, 0xWeiss, 2997ms, 41i3xn, Awesome, Aymen0909, Bauer, Bnke0x0, Breeje, Cryptor, DadeKuma, Deathstore, Deekshith99, DevABDee, DevTimSch, Dewaxindo, Diana, Ermaniwe, Guild_3, H0, IceBear, Inspectah, JDeryl, Kaiziron, Kaysoft, Kenshin, Mukund, Praise, RaymondFam, Rickard, Rolezn, Ruhum, Sathish9098, SkyWalkerMan, SleepingBugs, UdarTeam, Udsen, Walter, aashar, adeolu, apvlki, arialblack14, ast3ros, btk, chaduke, chandkommanaboyina, chrisdior4, climber2002, codetilda, cryptonue, cryptostellar5, csanuragjain, ddimitrov22, descharre, dharma09, doublesharp, eccentricexit, ethernomad, fs0c, georgits, halden, hansfriese, hashminer0725, immeas, lukris02, luxartvinsec, matrix_0wl, merlin, mookimgo, mrpathfindr, nadin, olegthegoat, pavankv, rbserver, rebase, savi0ur, sayan, scokaf, seeu, shark, simon135, tnevler, tsvetanovv, ulqiorra, ustas, waldenyan20, y1cunhui, yongskiws, yosuke
35.4779 USDC - $35.48
ID | Finding | Instances |
---|---|---|
L-01 | Missing checks for 0 address | 4 |
L-02 | Wrong check for tokenFees | 1 |
L-03 | Unspecific compiler version pragma | 1 |
L-04 | Return value of call is not checked | 1 |
L-05 | Account existence check for low-level calls | 1 |
ID | Finding | Instances |
---|---|---|
N-01 | Use indexed fields in an event | 18 |
N-02 | Unused return variables | 2 |
N-03 | Modifiers should not make state changes | 5 |
The if statement reverts when the tokenFee is larger than 1e17. However the natspec commment says 1e18 is 100% and I don't see anywhere in the documentation where it says max fee can be 1e17. The if statent should instead check if it's not larger than 1e18.
if (tokenFees[i] >= 1e17) revert DontGetGreedy(tokenFees[i]);
Avoid floating pragmas for non-library contracts. Contracts should always be deployed with the same compiler version and flags that they have been tested with. Locking the pragma helps ensure that contracts do not accidentally get deployed using a compiler which may have higher risks of undiscovered bugs.
The return value of call is not checked in the execute() function of AdminProxy.sol
. This can lead to unexpected failures.
A recommendation is to check the boolean success and revert the transaction if it's false.
Low-level calls call/delegatecall/staticcall return true even if the account called is non-existent (per EVM design). Account existence must be checked before the calling.
When an event is missing indexed fields it can be hard for off-chain scripts to efficiently filter those events.
When you have named return variables and you don't use them, then it's better to just specify the type you are gonna return.
Modifiers should only implement checks and not make state changes. This violates the checks-effects-interactions-pattern.
_mint
function which makes state changes. Additonally it also changes the state variables highWaterMark
and feesUpdatedAt
.highWaterMark
highWaterMark
and calls the _mint
function.#0 - c4-judge
2023-02-28T15:07:23Z
dmvt marked the issue as grade-b
🌟 Selected for report: c3phas
Also found by: 0xSmartContract, 0xackermann, 0xdaydream, Aymen0909, CodingNameKiki, Dewaxindo, Diana, IllIllI, Madalad, NoamYakov, Pheonix, Polaris_tow, ReyAdmirado, Rolezn, arialblack14, atharvasama, cryptostellar5, descharre, eyexploit, lukris02, saneryee
69.8247 USDC - $69.82
ID | Finding | Gas saved | Instances |
---|---|---|---|
G-01 | Make for loop unchecked | - | - |
G-02 | It's not necessary to do a safecast on block.timestamp | 450 | 9 |
G-03 | Use unchecked block when computation can't under/overflow | - | 2 |
G-04 | Checks are unnecessary in _transfer function | 240 | 2 |
G-05 | Don't assign to a memory variable when it's only used once | 783 | 1 |
The risk of for loops getting overflowed is extremely low. Because it always increments by 1 and is limited to the arrays length. Even if the arrays are extremely long, it will take a massive amount of time and gas to let the for loop overflow. The longer the array, the more gas you will save.
- for (uint256 i = 0; i < escrowIds.length; i++) { + for (uint256 i = 0; i < escrowIds.length; ) { selectedEscrows[i] = escrows[escrowIds[i]]; + unchecked{ + i++; + } }
getEscrows()
gas saved: 105claimRewards()
gas saved: 75setFees()
gas saved: 68
The same amount of gas can be saved for all the for loops with uint256 in VaultController.sol and PermissionRegistry.solWhen you cast block.timestamp to a uint32 it will never overflow. That's why it's not necessary to safecast. You can cast to uint32 in the regular way instead. Saves around 50 gas.
- block.timestamp.safeCastTo32() + uint32(block.timestamp)
When a variable is incremented by one there is no risk in using a unchecked block. It will take a massive amount of time and gas to make it overflow
An unchecked block can also be used when previous functions or statement make sure there is no possibility of over/underflowing
_transfer
functionAll the if statements are unnecessary because they are also in the _mint
and _burn
functions. It's just a gas saver to do them twice
MultiRewardStaking.sol#L147-L148
transfer()
gas saved: 241transferFrom()
gas saved: 242When a variable is only accessed once it's a waste of gas to assign it to a memory variable first. Especially when you write struct to memory like in the following example, you can save a lot of gas with this method.
MultiRewardStaking.sol#L255:addRewardToken()
gas saved: 783
- RewardInfo memory rewards = rewardInfos[rewardToken]; - if (rewards.lastUpdatedTimestamp > 0) revert RewardTokenAlreadyExist(rewardToken); + if (rewardInfos[rewardToken].lastUpdatedTimestamp > 0) revert RewardTokenAlreadyExist(rewardToken);
#0 - c4-judge
2023-02-28T14:52:37Z
dmvt marked the issue as grade-b