Platform: Code4rena
Start Date: 24/10/2023
Pot Size: $149,725 USDC
Total HM: 7
Participants: 52
Period: 21 days
Judge: ronnyx2017
Total Solo HM: 2
Id: 300
League: ETH
Rank: 21/52
Findings: 1
Award: $162.76
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: DavidGiladi
Also found by: 0xhex, 0xta, Collinsoden, JCK, Madalad, SAQ, SY_S, Sathish9098, cheatc0d3, codeslide, hihen, hunter_w3b, jamshed, lsaudit, mgf15, nonseodion, oualidpro, petrichor, sivanesh_808, tala7985, unique, ybansal2403, zabihullahazadzoi
162.763 USDC - $162.76
8000 GAS
, 4 SLOT
Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct.
Subsequent reads as well as writes have smaller gas savings.
2 SLOT
, 4000 GAS
FILE: Breadcrumbs2023-10-badger/packages/contracts/contracts/BorrowerOperations.sol struct MoveTokensParams { address user; + bool isCollIncrease; + bool isDebtIncrease; uint256 collSharesChange; uint256 collAddUnderlying; // ONLY for isCollIncrease=true - bool isCollIncrease; uint256 netDebtChange; - bool isDebtIncrease; }
FILE: 2023-10-badger/packages/contracts/contracts/Interfaces/IPriceFeed.sol struct ChainlinkResponse { uint80 roundEthBtcId; uint80 roundStEthEthId; + bool success; uint256 answer; uint256 timestampEthBtc; uint256 timestampStEthEth; - bool success; }
1 SLOT
, 2000 GAS
FILE: 2023-10-badger/packages/contracts/contracts/Interfaces/IPriceFeed.sol struct FallbackResponse { uint256 answer; - uint256 timestamp; + uint248 timestamp; bool success; }
2000 GAS
, 1 SLOT
The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to eachother in storage and this will pack the values together into a single 32 byte storage slot (if the values combined are <= 32 bytes). If the variables packed together are retrieved together in functions we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).
minuteDecayFactor
and redemptionsPaused
can be packed within same slot : Saves 2000 GAS
, 1 SLOT
minuteDecayFactor
value is not exceeds the MAX_MINUTE_DECAY_FACTOR = 999999999999999999
because this check _minuteDecayFactor <= MAX_MINUTE_DECAY_FACTOR
in setMinuteDecayFactor()
function. So we can downcast this to uint248
so down casting is safe.
require( _minuteDecayFactor <= MAX_MINUTE_DECAY_FACTOR, "CDPManager: new minute decay factor out of range" );
FILE: 2023-10-badger/packages/contracts/contracts/CdpManagerStorage.sol bool public redemptionsPaused; /* * Half-life of 12h. 12h = 720 min * (1/2) = d^720 => d = (1/2)^(1/720) */ - uint256 public minuteDecayFactor = 999037758833783000; + uint248 public minuteDecayFactor = 999037758833783000; uint256 public constant MIN_MINUTE_DECAY_FACTOR = 1; // Non-zero uint256 public constant MAX_MINUTE_DECAY_FACTOR = 999999999999999999; // Corresponds to a very fast decay rate, but not too extreme
40000 GAS
with 2 instances
Saves a storage slot. If the variable is assigned a non-zero value, saves Gsset (20000 gas)
. If it's assigned a zero value, saves Gsreset (2900 gas)
. If the variable remains unassigned, there is no gas savings unless the variable is public, in which case the compiler-generated non-payable getter deployment cost is saved. If the state variable is overriding an interface's public function, mark the variable as constant or immutable so that it does not use a storage slot.
FILE: 2023-10-badger/packages/contracts/contracts/Dependencies/RolesAuthority.sol 30: EnumerableSet.Bytes32Set internal enabledFunctionSigsPublic;
FILE: 2023-10-badger/packages/contracts/contracts/Governor.sol 17: bytes32 NO_ROLES = bytes32(0);
systemCollShares
should be cached100 GAS
,1 SLOD
FILE: Breadcrumbs2023-10-badger/packages/contracts/contracts/ActivePool.sol + uint256 systemCollShares_ = systemCollShares; require( - collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares), + collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares_ ), "ActivePool: Must repay Balance" ); require( - collateral.sharesOf(address(this)) >= systemCollShares, + collateral.sharesOf(address(this)) >= systemCollShares_ , "ActivePool: Must repay Share" );
lastRedemptionTimestamp
should be cached400 GAS
, 4 SLOD
FILE: 2023-10-badger/packages/contracts/contracts/CdpManager.sol function _updateLastRedemptionTimestamp() internal { + uint256 lastRedemptionTimestamp_ = lastRedemptionTimestamp ; - uint256 timePassed = block.timestamp > lastRedemptionTimestamp + uint256 timePassed = block.timestamp > lastRedemptionTimestamp_ - ? block.timestamp - lastRedemptionTimestamp + ? block.timestamp - lastRedemptionTimestamp_ : 0; if (timePassed >= SECONDS_IN_ONE_MINUTE) { // Using the effective elapsed time that is consumed so far to update lastRedemptionTimestamp // instead block.timestamp for consistency with _calcDecayedBaseRate() - lastRedemptionTimestamp += _minutesPassedSinceLastRedemption() * SECONDS_IN_ONE_MINUTE; + lastRedemptionTimestamp = lastRedemptionTimestamp_ + _minutesPassedSinceLastRedemption() * SECONDS_IN_ONE_MINUTE; emit LastRedemptionTimestampUpdated(block.timestamp); } } function _minutesPassedSinceLastRedemption() internal view returns (uint256) { + uint256 lastRedemptionTimestamp_ = lastRedemptionTimestamp ; return - block.timestamp > lastRedemptionTimestamp + block.timestamp > lastRedemptionTimestamp_ - ? ((block.timestamp - lastRedemptionTimestamp) / SECONDS_IN_ONE_MINUTE) + ? ((block.timestamp - lastRedemptionTimestamp_ ) / SECONDS_IN_ONE_MINUTE) : 0; }
Cdps[_redeemColFromCdp.cdpId].debt
should be cached100 GAS
FILE: 2023-10-badger/packages/contracts/contracts/CdpManager.sol + uint256 debt_ = Cdps[_redeemColFromCdp.cdpId].debt ; singleRedemption.debtToRedeem = EbtcMath._min( _redeemColFromCdp.maxEBTCamount, - Cdps[_redeemColFromCdp.cdpId].debt /// @audit Redeem everything + debt_ /// @audit Redeem everything ); singleRedemption.collSharesDrawn = collateral.getSharesByPooledEth( (singleRedemption.debtToRedeem * DECIMAL_PRECISION) / _redeemColFromCdp.price ); // Repurposing this struct here to avoid stack too deep. CdpDebtAndCollShares memory _oldDebtAndColl = CdpDebtAndCollShares( - Cdps[_redeemColFromCdp.cdpId].debt, + debt_, Cdps[_redeemColFromCdp.cdpId].coll );
getUserRoles[user]
should be cachedFILE: Breadcrumbs2023-10-badger/packages/contracts/contracts/Dependencies/RolesAuthority.sol } else { + bytes32 user_ = getUserRoles[user] ; - getUserRoles[user] &= ~bytes32(1 << role); + user_ &= ~bytes32(1 << role); // Remove user if no more roles - if (getUserRoles[user] == bytes32(0)) { + if (user_ == bytes32(0)) { users.remove(user); }
sortedCdps.nonExistId()
call should be cached rather than re-calling the functionThe instances below point to the second+ call of the function within a single function
2100 GAS
function _isValidFirstRedemptionHint( bytes32 _firstRedemptionHint, uint256 _price ) internal view returns (bool) { + bytes32 nonExistId_ = sortedCdps.nonExistId() ; if ( - _firstRedemptionHint == sortedCdps.nonExistId() || + _firstRedemptionHint == nonExistId_ || !sortedCdps.contains(_firstRedemptionHint) || getSyncedICR(_firstRedemptionHint, _price) < MCR ) { return false; } bytes32 nextCdp = sortedCdps.getNext(_firstRedemptionHint); - return nextCdp == sortedCdps.nonExistId() || getSyncedICR(nextCdp, _price) < MCR; + return nextCdp == nonExistId_ || getSyncedICR(nextCdp, _price) < MCR; }
Emitting memory or constant variables instead of state variables in Solidity events can be a useful optimization technique, particularly in the context of gas efficiency and contract optimization
FILE: Breadcrumbs2023-10-badger/packages/contracts/contracts/CdpManager.sol 53: stakingRewardSplit = STAKING_REWARD_SPLIT; 54: // Emit initial value for analytics - 55: emit StakingRewardSplitSet(stakingRewardSplit); + 55: emit StakingRewardSplitSet(STAKING_REWARD_SPLIT);
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.
FILE: 2023-10-badger/packages/contracts/contracts/Dependencies/RolesAuthority.sol 32: mapping(address => bytes32) public getUserRoles; 36: mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability;
#0 - c4-pre-sort
2023-11-17T14:43:20Z
bytes032 marked the issue as sufficient quality report
#1 - jhsagd76
2023-11-28T02:52:36Z
How to measure the increase in gas cost when decode
#2 - c4-judge
2023-11-28T02:52:43Z
jhsagd76 marked the issue as grade-c
#3 - sathishpic22
2023-12-06T08:53:48Z
Hi @jhsagd76
All of my findings have already been proven, and not increase any gas after optimization.
[G-1] Optimizing storage usage with packed structs
This is already proven finding accepted many old contest. This will not increase any gas cost.
[G-1.1] user,isCollIncrease,isDebtIncrease can be packed with same slot : Saves 2 SLOT , 4000 GAS
[G-1.2] roundEthBtcId , roundStEthEthId , success can be packed with same slot : Saves 1 SLOT , 2000 GAS
Without struct optimization bool will occupy the 1 SLOT and incurs the gas overhead
[G-1.3] timestamp , success can be packed with same slot : Saves 1 SLOT , 2000 GAS
Usually for timestamp uint32 mostly used in many cases and many protocols.
[G-2] State variables can be packed into fewer storage slots
[G-2.1] minuteDecayFactor and redemptionsPaused can be packed within same slot : Saves 2000 GAS , 1 SLOT
This state variable optimization is safe. because minuteDecayFactor is not exceed the MAX_MINUTE_DECAY_FACTOR this constant value (999999999999999999). In safe hand my suggestion is uint248 . This will not harm protocol and not increase any gas after optimizations .
[G-3] Remove or replace unused state variables
Unused state variables removed or replaced to reduce the gas cost
[G-4] systemCollShares should be cached [G-5] lastRedemptionTimestamp should be cached
Instead of calling storage variables repeatedly when caching this with memory variable saves the gas.
[G-6] Cdps[_redeemColFromCdp.cdpId].debt should be cached [G-7] getUserRoles[user] should be cached
Mappings should be cached instead of calling repeatedly
[G-8] The result of sortedCdps.nonExistId() call should be cached rather than re-calling the function
Repeated function calls should be cached intead calling same function again and again
[G-9] Emit memory or constant variables instead of state variables
Emitting contract variable consumes less gas than state variable.
[G-10] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate
I am perplexed by the contradiction in my findings. Although I have submitted a greater number of low-impact findings than other wardens, my findings have contributed to significantly higher gas savings than any other reports. While it is true that some wardens have submitted a higher total number of findings, these findings are predominantly classified as low-impact. All of my findings fall under the low-impact category based on the established criteria.
Despite implementing gas optimizations, the gas consumption did not increase but rather decreased more effectively. My findings have been thoroughly validated and widely accepted in previous competitions.
Certainly, I'll be happy to provide further clarifications if needed.
Thank you
#4 - jhsagd76
2023-12-06T09:14:19Z
Upon reviewing the gas report again, I realized that I missed it while filtering out a significant amount of bot output. I will elevate this report.
#5 - c4-judge
2023-12-06T09:14:38Z
jhsagd76 marked the issue as grade-a
#6 - jhsagd76
2023-12-06T20:07:32Z
3 * 2 + 2 * 1 + 5 * 4
28