Platform: Code4rena
Start Date: 25/01/2023
Pot Size: $90,500 USDC
Total HM: 3
Participants: 26
Period: 9 days
Judge: GalloDaSballo
Id: 209
League: ETH
Rank: 19/26
Findings: 1
Award: $131.98
π Selected for report: 0
π Solo Findings: 0
π Selected for report: descharre
Also found by: 0xA5DF, 0xSmartContract, Aymen0909, Deivitto, NoamYakov, ReyAdmirado, Rolezn, chaduke, cryptostellar5, matrix_0wl
131.9758 USDC - $131.98
Issue | Instances | |
---|---|---|
1 | Multiple address/IDs mappings can be combined into a single mapping of an address/id to a struct | 2 |
2 | Use unchecked blocks to save gas | 5 |
3 | storage variable should be cached into memory variables instead of re-reading them | 1 |
4 | x += y/x -= y costs more gas than x = x + y/x = x - y for state variables | 4 |
5 | require() strings longer than 32 bytes cost extra gas | 5 |
6 | ++i/i++ should be unchecked{++i} /unchecked{i++} when it is not possible for them to overflow, as in the case when used in for & while loops | 14 |
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There are 2 instances of this issue :
File: Caller.sol 88 mapping(address => EnumerableSet.AddressSet) internal _authorized; 90 mapping(address => uint256) public nonce;
These mappings could be refactored into the following struct and mapping for example :
struct Sender { uint256 nonce; EnumerableSet.AddressSet _authorized; } mapping(address => Sender) private _senders;
unchecked
blocks to save gas :Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isnβt possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block.
There are 5 instances of this issue:
File: Drips.sol Line 283-284
receivableCycles = toCycle - fromCycle - maxCycles; toCycle -= receivableCycles;
The first operation cannot underflow due to the if check statement that preceeds it and the second operation cannot underflow because we will always have toCycle > receivableCycles
from the first operation, so both operations should be marked as unchecked
.
File: DripsHub.sol Line 632
totalBalances[erc20] += amt;
The above operation cannot overflow due to the check that preceeds Line 283 so it should be marked as unchecked
.
File: Splits.sol Line 130-131
splitAmt = uint128((uint160(amount) * splitsWeight) / _TOTAL_SPLITS_WEIGHT); collectableAmt = amount - splitAmt;
The first operation cannot overflow due because we always have _TOTAL_SPLITS_WEIGHT > splitsWeight
when setting the split, and the second operation cannot underflow because we will always have amount > splitAmt
from the first operation, so both operations should be marked as unchecked
.
storage
variable should be cached into memory
variables instead of re-reading them :The instances below point to the second+ access of a state variable within a function, the caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read, thus saves 100gas for each instance.
There is 1 instance of this issue :
File: Drips.sol Line 540-542
In the code linked above the value of state.updateTime
is read multiple times (2) from storage and it's value does not change so it should be cached into a memory variable in order to save gas by avoiding multiple reads from storage.
x += y/x -= y
costs more gas than x = x + y/x = x - y
for state variables :Using the addition operator instead of plus-equals saves 113 gas as explained here
There are 4 instances of this issue:
File: Drips.sol 253 amtDeltas[toCycle].thisCycle += finalAmtPerCycle; File: DripsHub.sol 632 totalBalances[erc20] += amt; 636 _dripsHubStorage().totalBalances[erc20] -= amt; File: Splits.sol 168 balance.collectable += collectableAmt;
require()
strings longer than 32 bytes cost extra gas :Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas.
There are 5 instances of this issue :
File: Drips.sol 454 require(dripsHash == 0, "Drips history entry with hash and receivers"); 540 require(timestamp >= state.updateTime, "Timestamp before last drips update"); 798 require(amtPerSec != 0, "Drips receiver amtPerSec is zero"); File: NFTDriver.sol 41 require( _isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved" ); File: Splits.sol 239 require(prevUserId < userId, "Splits receivers not sorted by user ID");
++i/i++
should be unchecked{++i}/unchecked{i++}
when it is not possible for them to overflow, as in the case when used in for & while loops :There are 14 instances of this issue:
File: Caller.sol 196 for (uint256 i = 0; i < calls.length; i++) File: Drips.sol 247 for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) 287 for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) 358 for (uint256 i = 0; i < squeezedNum; i++) 422 for (uint256 i = 1; i <= dripsHistory.length && i <= currCycleConfigs; i++) 458 for (uint256 i = 0; i < dripsHistory.length; i++) 490 for (; idx < receivers.length; idx++) 563 for (uint256 i = 0; i < receivers.length; i++) 664 for (uint256 i = 0; i < newReceivers.length; i++) File: DripsHub.sol 613 for (uint256 i = 0; i < userMetadata.length; i++) File: ImmutableSplitsDriver.sol 61 for (uint256 i = 0; i < receivers.length; i++) File: Splits.sol 127 for (uint256 i = 0; i < currReceivers.length; i++) 158 for (uint256 i = 0; i < currReceivers.length; i++) 231 for (uint256 i = 0; i < receivers.length; i++)
#0 - GalloDaSballo
2023-02-24T10:26:00Z
3- storage variable should be cached into memory variables instead of re-reading them :
100
400 from +=
300 from unchecked
800
#1 - c4-judge
2023-02-24T11:02:07Z
GalloDaSballo marked the issue as grade-b