Platform: Code4rena
Start Date: 06/03/2023
Pot Size: $36,500 USDC
Total HM: 8
Participants: 93
Period: 3 days
Judge: cccz
Total Solo HM: 3
Id: 218
League: ETH
Rank: 82/93
Findings: 1
Award: $12.72
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Rolezn
Also found by: 0x1f8b, 0x6980, 0xSmartContract, 0xhacksmithh, 0xnev, Haipls, Inspectah, JCN, LethL, Madalad, MiniGlome, Pheonix, Rageur, RaymondFam, ReyAdmirado, SAAJ, Sathish9098, adriro, air, arialblack14, atharvasama, c3phas, ch0bu, ddimitrov22, descharre, hunter_w3b, igingu, matrix_0wl, rokso, saneryee, schrodinger, slvDev, volodya, yongskiws
12.7206 USDC - $12.72
Issue | Instances | |
---|---|---|
[G-001] | x += y or x -= y costs more gas than x = x + y or x = x - y for state variables | 7 |
[G-002] | storage pointer to a structure is cheaper than copying each value of the structure into memory , same for array and mapping | 2 |
[G-003] | Functions guaranteed to revert when called by normal users can be marked payable | 4 |
[G-004] | Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate | 1 |
[G-005] | internal functions only called once can be inlined to save gas | 1 |
[G-006] | Replace modifier with function | 3 |
[G-007] | Expressions that cannot be overflowed can be unchecked | 2 |
Using the addition operator instead of plus-equals saves 113 gas. Usually does not work with struct and mappings.
Total:7
src/staking/StakedTokenLock.sol#L43
43: depositedBalance -= amount;
src/staking/StakedTokenLock.sol#L30
30: depositedBalance += amount;
96: winTier += uint8(intersection & uint120(1));
29: ticketSize += (ticket & uint256(1));
84: bonusMulti += (excessPot * EXCESS_BONUS_ALLOCATION) / (ticketsSold * expectedPayout);
55: newProfit -= int256(expectedRewardsOut);
64: excessPotInt -= int256(fixedJackpotSize);
storage
pointer to a structure is cheaper than copying each value of the structure into memory
, same for array
and mapping
It may not be obvious, but every time you copy a storage struct/array/mapping
to a memory
variable, you are literally copying each member by reading it from storage
, which is expensive. And when you use the storage
keyword, you are just storing a pointer to the storage, which is much cheaper.
Total:2
54: uint8[] memory numbers = new uint8[](selectionSize);
63: bool[] memory selected = new bool[](selectionMax);
Using storage
instead of memory
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
The extra opcodes avoided are CALLVALUE(2)
,DUP1(3)
,ISZERO(3)
,PUSH2(3)
,JUMPI(10)
,PUSH1(3)
,DUP1(3)
,REVERT(0)
,JUMPDEST(1)
,POP(2)
, which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
Total:4
src/staking/StakedTokenLock.sol#L24
24: function deposit(uint256 amount) external override onlyOwner {
src/staking/StakedTokenLock.sol#L37
37: function withdraw(uint256 amount) external override onlyOwner {
src/RNSourceController.sol#L77
77: function initSource(IRNSource rnSource) external override onlyOwner {
src/RNSourceController.sol#L89
89: function swapSource(IRNSource newSource) external override onlyOwner {
Mark the function as payable.
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.
Total:1
src/staking/Staking.sol#L19-L20
19: mapping(address => uint256) public override userRewardPerTokenPaid; 20: mapping(address => uint256) public override rewards;
Not inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
Total:1
160: function _baseJackpot(uint256 _initialPot) internal view returns (uint256) {
Modifiers make code more elegant, but cost more than normal functions.
Total:3
104: modifier requireJackpotInitialized() {
60: modifier onlyWhenExecutingDraw() {
52: modifier whenNotExecutingDraw() {
There are several cases that do not lead to overflow and can be unchecked.
Total:2
171: if ((rewards[winTier] % divisor) != 0) {
58: numbers[i] = uint8(randomNumber % currentSelectionCount);
#0 - c4-judge
2023-03-12T14:26:24Z
thereksfour marked the issue as grade-b
#1 - c4-sponsor
2023-03-14T13:07:53Z
TutaRicky marked the issue as sponsor acknowledged