Platform: Code4rena
Start Date: 16/02/2023
Pot Size: $144,750 USDC
Total HM: 17
Participants: 154
Period: 19 days
Judge: Trust
Total Solo HM: 5
Id: 216
League: ETH
Rank: 36/154
Findings: 2
Award: $370.05
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: c3phas
Also found by: 0x3b, 0x6980, 0x73696d616f, 0xSmartContract, 0xackermann, 0xhacksmithh, 0xsomeone, Bnke0x0, Bough, Budaghyan, Darshan, DeFiHackLabs, Deivitto, GalloDaSballo, JCN, LethL, Madalad, MiniGlome, Morraez, P-384, PaludoX0, Phantasmagoria, Praise, RHaO-sec, Rageur, RaymondFam, ReyAdmirado, Rickard, Rolezn, SaeedAlipoor01988, Saintcode_, Sathish9098, TheSavageTeddy, Tomio, Viktor_Cortess, abiih, arialblack14, atharvasama, banky, codeislight, cryptonue, ddimitrov22, dec3ntraliz3d, descharre, dharma09, emmac002, favelanky, hl_, hunter_w3b, kaden, kodyvim, matrix_0wl, oyc_109, pavankv, scokaf, seeu, yamapyblack
308.7866 USDC - $308.79
address
/ID
mappings
can be combined into a single mapping
of an address
/ID
to a struct
, where appropriateif 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.
Both mapping being used in the same functions mostly consider making them a struct instead
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesUsing the addition operator instead of plus-equals saves gas. Same goes for other operands like -=
.
ReaperVaultV2.sol#L168 totalAllocBPS += _allocBPS;
ReaperVaultV2.sol#L196 totalAllocBPS += _allocBPS;
ReaperVaultV2.sol#L450 stratParams.losses += loss;
ReaperVaultV2.sol#L505 strategy.gains += vars.gain;
ReaperVaultV2.sol#L520 strategy.allocated += vars.credit;
ReaperVaultV2.sol#L521 totalAllocated += vars.credit;
ReaperVaultV2.sol#L194 totalAllocBPS -= strategies[_strategy].allocBPS;
ReaperVaultV2.sol#L214 totalAllocBPS -= strategies[_strategy].allocBPS;
ReaperVaultV2.sol#L395 strategies[stratAddr].allocated -= actualWithdrawn;
ReaperVaultV2.sol#L396 totalAllocated -= actualWithdrawn;
ReaperVaultV2.sol#L444 stratParams.allocBPS -= bpsChange;
ReaperVaultV2.sol#L445 totalAllocBPS -= bpsChange;
ReaperVaultV2.sol#L451 stratParams.allocated -= loss;
ReaperVaultV2.sol#L452 totalAllocated -= loss;
ReaperVaultV2.sol#L514 strategy.allocated -= vars.debtPayment;
ReaperVaultV2.sol#L515 totalAllocated -= vars.debtPayment;
Rather than re assigning values to the variables each iterations, at for loops it is being redeclared each variable all over again.
-ReaperStrategyGranarySupplyOnly.sol#L120
unchecked{++i}
to save gasIn Solidity 0.8+, there’s a default overflow check on unsigned integers. It’s possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline without an extra function. This extra function is used in some parts of the code, however in some locations it is not used
CollateralConfig.sol#L56 for(uint256 i = 0; i < _collaterals.length; i++) {
ActivePool.sol#L108 for(uint256 i = 0; i < numCollaterals; i++) {
TroveManager.sol#L608 for (vars.i = 0; vars.i < _n && vars.user != firstUser; vars.i++) {
TroveManager.sol#L690 for (vars.i = 0; vars.i < _n; vars.i++) {
TroveManager.sol#L808 for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {
TroveManager.sol#L882 for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {
StabilityPool.sol#L351 for (uint i = 0; i < numCollaterals; i++) {
StabilityPool.sol#L397 for (uint i = 0; i < numCollaterals; i++) {
StabilityPool.sol#L640 for (uint i = 0; i < assets.length; i++) {
StabilityPool.sol#L810 for (uint i = 0; i < collaterals.length; i++) {
StabilityPool.sol#L831 for (uint i = 0; i < collaterals.length; i++) {
StabilityPool.sol#L859 for (uint i = 0; i < numCollaterals; i++) {
LQTYStaking.sol#L206 for (uint i = 0; i < assets.length; i++) {
LQTYStaking.sol#L228 for (uint i = 0; i < collaterals.length; i++) {
LQTYStaking.sol#L240 for (uint i = 0; i < numCollaterals; i++) {
ReaperVaultV2.sol#L128v4.sol#L77](https://github.com/code-423n4/2023-02-ethos/blob/34ba1e7dd2d259f06f9d5fa13f5d96be7829ff34/Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol#L77) for (uint256 i = 0; i < numStrategists; i = i.uncheckedInc()) {
ReaperStrategyGranarySupplyOnly.sol#L117 for (uint256 i = 0; i < numSteps; i = i.uncheckedInc()) {
ReaperStrategyGranarySupplyOnly.sol#L165 for (uint256 i = 0; i < numSteps; i = i.uncheckedInc()) {
for (uint256 i = 0; i < numStrategists; i = i.uncheckedInc()) {
ReaperVaultV2.sol#L264 for (uint256 i = 0; i < queueLength; i = i.uncheckedInc()) {
ReaperVaultV2.sol#L372 for (uint256 i = 0; i < queueLength; i = i.uncheckedInc()) {
[ReaperBaseStrategy
require()
/revert()
strings longer than 32 bytes cost extra gasNOTE
: None of these findings where found by 4naly3er output - Gas
Each extra memory word of bytes past the original 32 incurs an MSTORE which costs 3 gas
bytes
constants are more efficient than string
constantsIf data can fit into 32 bytes, then you should use bytes32
datatype rather than bytes or strings as it is cheaper in solidity.
This removes an extra SLOAD
block.timestamp
and block.number
are added to event information by default so adding them manually wastes extra gas.
abi.encode()
is less gas efficient than abi.encodePacked()
Changing the abi.encode
function to abi.encodePacked
can save gas since the abi.encode
function pads extra null bytes
at the end of the call data, which is unnecessary. Also, in general, abi.encodePacked
is more gas-efficient.
require()
/ assert()
statements that use &&
saves gasInstead of using the &&
operator in a single require statement to check multiple conditions, consider using multiple require statements with 1 condition per require statement (saving 3 gas per &
).
BorrowerOperations.sol#L301 assert(msg.sender == _borrower || (msg.sender == stabilityPoolAddress && _collTopUp > 0 && _LUSDChange == 0));
BorrowerOperations.sol#L653 require(_maxFeePercentage >= BORROWING_FEE_FLOOR && _maxFeePercentage <= DECIMAL_PRECISION,
TroveManager.sol#L1279 assert(closedStatus != Status.nonExistent && closedStatus != Status.active);
TroveManager.sol#L1342 assert(troveStatus != Status.nonExistent && troveStatus != Status.active);
TroveManager.sol#L1539 require (TroveOwnersArrayLength > 1 && sortedTroves.getSize(_collateral) > 1);
LUSDToken.sol#L348 _recipient != address(0) &&
LUSDToken.sol#L353 !stabilityPools[_recipient] &&
LUSDToken.sol#L354 !troveManagers[_recipient] &&
safeMath
can be avoided to save gasIn other parts of the project solidity version ^0.8
it's being used and has overflow protection by default so it's not needed safeMath
.
require
stringsNOTE
: None of these findings where found by 4naly3er output - Gas
Source Instead of using error strings, to reduce deployment and runtime cost, you should use Custom Errors. This would save both deployment and runtime cost.
address(0)
for gas savesNOTE
: None of these findings where found by 4naly3er output - Gas
Saves 6 gas per instance
NOTE
: None of these findings where found by 4naly3er output - Gas
payable
NOTE
: None of these findings where found by 4naly3er output - Gas
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
unchecked
block can be used to save gasWhere is not expected to overflow, unchecked
can be used
uint8
for convention and to save 1 storage slotdistributionPeriod
can be set as default value to save gas#0 - c4-judge
2023-03-08T12:44:04Z
trust1995 marked the issue as grade-a