Ethos Reserve contest - seeu's results

A CDP-backed stablecoin platform designed to generate yield on underlying assets to establish a sustainable DeFi stable interest rate.

General Information

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

Ethos Reserve

Findings Distribution

Researcher Performance

Rank: 151/154

Findings: 1

Award: $42.07

Gas:
grade-b

๐ŸŒŸ Selected for report: 0

๐Ÿš€ Solo Findings: 0

Gas Optimization Issues

IDIssueContextsInstancesTotal Gas Saved
G-01Using bools for storage incurs overhead49153900
G-02Array length not cached outside of loop4824
G-03Variables initialized with default value722-
G-04Unsigned integer comparison with > 0626-
G-05Long revert/require strings37-
G-06Postfix increment/decrease used717-
G-07Mark payable functions guaranteed to revert when called by normal users27147
G-08Use assembly to check for address(0)51590
G-09Use "require" instead of "assert" when possible420-
G-10Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead22-
G-11Split require() statements that use && to save gas11-
G-12x += y costs more gas than x = x + y for state variables3222486
G-13When possible, use non-strict comparison >= and/or =< instead of > <964960
G-14If possible, use private rather than public for constants1030102180
G-15Use a more recent version of Solidity to save gas1212-
Total issuesTotal contextsTotal instancesTotal minimum gas saved
1580262259787

[G-01] Using bools for storage incurs overhead

Description

Use uint256 for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from โ€˜falseโ€™ to โ€˜trueโ€™, after having been โ€˜trueโ€™ in the past.

Findings

References

[G-02] Array length not cached outside of loop

Description

Caching the length eliminates the additional DUP<N> required to store the stack offset and converts each of them to a DUP<N> (3 gas).

Findings

References

[G-03] Variables initialized with default value

Description

Explicitly initialize a variable with its default value wastes gas

Findings

References

[G-04] Unsigned integer comparison with > 0

Description

Comparisons done using != 0 are cheaper than > 0 when dealing with unsigned integer types

Findings

  • Ethos-Core/contracts/ActivePool.sol
    ::278 =>         if (vars.netAssetMovement > 0) {
  • Ethos-Core/contracts/BorrowerOperations.sol
    ::128 =>         assert(MIN_NET_DEBT > 0);
    ::197 =>         assert(vars.compositeDebt > 0);
    ::301 =>         assert(msg.sender == _borrower || (msg.sender == stabilityPoolAddress && _collTopUp > 0 && _LUSDChange == 0));
    ::337 =>         if (!_isDebtIncrease && _LUSDChange > 0) {
    ::552 =>         require(_LUSDChange > 0, "BorrowerOps: Debt increase requires non-zero debtChange");
  • Ethos-Core/contracts/LQTY/LQTYStaking.sol
    ::152 =>         if (_LQTYamount > 0) {
    ::181 =>         if (totalLQTYStaked > 0) {collFeePerLQTYStaked = _collFee.mul(DECIMAL_PRECISION).div(totalLQTYStaked);}
    ::191 =>         if (totalLQTYStaked > 0) {LUSDFeePerLQTYStaked = _LUSDFee.mul(DECIMAL_PRECISION).div(totalLQTYStaked);}
    ::266 =>         require(currentStake > 0, 'LQTYStaking: User must have a non-zero stake');
    ::270 =>         require(_amount > 0, 'LQTYStaking: Amount must be non-zero');
  • Ethos-Core/contracts/StabilityPool.sol
    ::591 =>         assert(newP > 0);
    ::870 =>         require(_initialDeposit > 0, 'StabilityPool: User must have a non-zero deposit');
    ::874 =>         require(_amount > 0, 'StabilityPool: Amount must be non-zero');
  • Ethos-Core/contracts/TroveManager.sol
    ::424 =>             if (singleLiquidation.collSurplus > 0) {
    ::452 =>         if (_LUSDInStabPool > 0) {
    ::551 =>         require(totals.totalDebtInSequence > 0);
    ::563 =>         if (totals.totalCollSurplus > 0) {
    ::754 =>         require(totals.totalDebtInSequence > 0);
    ::766 =>         if (totals.totalCollSurplus > 0) {
    ::916 =>         if (_LUSD > 0) {
    ::920 =>         if (_collAmount > 0) {
    ::1224 =>             assert(totalStakesSnapshot[_collateral] > 0);
    ::1414 =>         assert(newBaseRate > 0); // Base rate is always non-zero after redemption
  • Ethos-Vault/contracts/ReaperVaultV2.sol
    ::502 =>         } else if (_roi > 0) {
    ::518 =>         } else if (vars.available > 0) {

References

[G-05] Long revert/require strings

Description

Strings in require() / revert() longer than 32 bytes cost extra gas

Findings

  • Ethos-Core/contracts/ActivePool.sol
    ::107 =>         require(numCollaterals == _erc4626vaults.length, "Vaults array length must match number of collaterals");
    ::133 =>         require(_driftBps <= 500, "Exceeds max allowed value of 500 BPS");
  • Ethos-Vault/contracts/ReaperVaultV2.sol
    ::150 =>         require(!emergencyShutdown, "Cannot add strategy during emergency shutdown");
    ::321 =>         require(!emergencyShutdown, "Cannot deposit during emergency shutdown");
    ::436 =>         require(loss <= allocation, "Strategy loss cannot be greater than allocation");
  • Ethos-Vault/contracts/abstract/ReaperBaseStrategyV4.sol
    ::98 =>         require(_amount <= balanceOf(), "Ammount must be less than balance");
    ::193 =>             "Upgrade cooldown not initiated or still ongoing"

References

[G-06] Postfix increment/decrease used

Description

The prefix increment / decrease expression returns the updated value after it's incremented while the postfix increment / decrease expression returns the original value.

Be careful when employing this optimization anytime the return value of the expression is utilized later; for instance, uint a = i++ and uint a = ++i produce different values for a.

Findings

References

[G-07] Mark payable functions guaranteed to revert when called by normal users

Description

If a function modifier, like onlyOwner, is applied, the function will revert if a regular user attempts to pay it. Making the function payable will save valid callers money on gas since the compiler won't do checks to see if a payment was made.

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.

Findings

  • Ethos-Core/contracts/ActivePool.sol
    ::125 =>     function setYieldingPercentage(address _collateral, uint256 _bps) external onlyOwner {
    ::132 =>     function setYieldingPercentageDrift(uint256 _driftBps) external onlyOwner {
    ::138 =>     function setYieldClaimThreshold(address _collateral, uint256 _threshold) external onlyOwner {
    ::144 =>     function setYieldDistributionParams(uint256 _treasurySplit, uint256 _SPSplit, uint256 _stakingSplit) external onlyOwner {
    ::214 =>     function manualRebalance(address _collateral, uint256 _simulatedAmountLeavingPool) external onlyOwner {
  • Ethos-Core/contracts/LQTY/CommunityIssuance.sol
    ::101 =>     function fund(uint amount) external onlyOwner {
    ::120 =>     function updateDistributionPeriod(uint256 _newDistributionPeriod) external onlyOwner {

References

[G-08] Use assembly to check for address(0)

Description

By checking for address(0) using assembly language, you can avoid the use of more gas-expensive operations such as calling a smart contract or reading from storage. This can save 6 gas per instance.

Findings

References

[G-09] Use "require" instead of "assert" when possible

Description

If assert() returns a false assertion, it compiles to the invalid opcode 0xfe, which eats up all the gas left and completely undoes the changes. require() compiles to 0xfd, which is the opcode for a REVERT, indicating that it will return the remaining gas if it returns a false assertion.

Findings

  • Ethos-Core/contracts/BorrowerOperations.sol
    ::128 =>         assert(MIN_NET_DEBT > 0);
    ::197 =>         assert(vars.compositeDebt > 0);
    ::301 =>         assert(msg.sender == _borrower || (msg.sender == stabilityPoolAddress && _collTopUp > 0 && _LUSDChange == 0));
    ::331 =>         assert(_collWithdrawal <= vars.coll);
  • Ethos-Core/contracts/LUSDToken.sol
    ::312 =>         assert(sender != address(0));
    ::313 =>         assert(recipient != address(0));
    ::321 =>         assert(account != address(0));
    ::329 =>         assert(account != address(0));
    ::337 =>         assert(owner != address(0));
    ::338 =>         assert(spender != address(0));
  • Ethos-Core/contracts/StabilityPool.sol
    ::526 =>         assert(_debtToOffset <= _totalLUSDDeposits);
    ::551 =>         assert(_LUSDLossPerUnitStaked <= DECIMAL_PRECISION);
    ::591 =>         assert(newP > 0);
  • Ethos-Core/contracts/TroveManager.sol
    ::417 =>             assert(_LUSDInStabPool != 0);
    ::1224 =>             assert(totalStakesSnapshot[_collateral] > 0);
    ::1279 =>         assert(closedStatus != Status.nonExistent && closedStatus != Status.active);
    ::1342 =>         assert(troveStatus != Status.nonExistent && troveStatus != Status.active);
    ::1348 =>         assert(index <= idxLast);
    ::1414 =>         assert(newBaseRate > 0); // Base rate is always non-zero after redemption
    ::1489 =>         assert(decayedBaseRate <= DECIMAL_PRECISION);  // The baseRate can decay to 0

References

[G-10] Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

Description

Gas consumption can be greater if you use items that are less than 32 bytes in size. This is such that the EVM can only handle 32 bytes at once. In order to increase the element's size from 32 bytes to the necessary amount, the EVM must do extra operations if it is lower than that. When necessary, it is advised to utilize a bigger size and then downcast.

Findings

References

[G-11] Split require() statements that use && to save gas

Description

It is advised to use two require instead of using one require with the operator && to save gas.

Findings

References

[G-12] x += y costs more gas than x = x + y for state variables

Description

Gas can be saved by substituting the addition operator with plus-equals, same for minus. Saves 113 gas per instance.

Findings

  • Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol
    ::133 =>             toFree += profit;
    ::141 =>         roi -= int256(loss);
  • Ethos-Vault/contracts/ReaperVaultV2.sol
    ::168 =>         totalAllocBPS += _allocBPS;
    ::194 =>         totalAllocBPS -= strategies[_strategy].allocBPS;
    ::196 =>         totalAllocBPS += _allocBPS;
    ::214 =>         totalAllocBPS -= strategies[_strategy].allocBPS;
    ::390 =>                     value -= loss;
    ::391 =>                     totalLoss += loss;
    ::395 =>                 strategies[stratAddr].allocated -= actualWithdrawn;
    ::396 =>                 totalAllocated -= actualWithdrawn;
    ::444 =>                 stratParams.allocBPS -= bpsChange;
    ::445 =>                 totalAllocBPS -= bpsChange;
    ::450 =>         stratParams.losses += loss;
    ::451 =>         stratParams.allocated -= loss;
    ::452 =>         totalAllocated -= loss;
    ::505 =>             strategy.gains += vars.gain;
    ::514 =>                 strategy.allocated -= vars.debtPayment;
    ::515 =>                 totalAllocated -= vars.debtPayment;
    ::516 =>                 vars.debt -= vars.debtPayment; // tracked for return value
    ::520 =>             strategy.allocated += vars.credit;
    ::521 =>             totalAllocated += vars.credit;
  • Ethos-Vault/contracts/abstract/ReaperBaseStrategyV4.sol
    ::128 =>                 repayment -= uint256(-roi);

References

[G-13] When possible, use non-strict comparison >= and/or =< instead of > <

Description

Non-strict inequalities are cheaper than strict ones due to some supplementary checks (ISZERO, 3 gas). It will save 15โ€“20 gas.

Findings

  • Ethos-Core/contracts/ActivePool.sol
    ::252 =>         if (vars.profit < yieldClaimThreshold[_collateral]) {
    ::264 =>         if (vars.percentOfFinalBal > vars.yieldingPercentage && vars.percentOfFinalBal.sub(vars.yieldingPercentage) > yieldingPercentageDrift) {
    ::269 =>         } else if(vars.percentOfFinalBal < vars.yieldingPercentage && vars.yieldingPercentage.sub(vars.percentOfFinalBal) > yieldingPercentageDrift) {
    ::278 =>         if (vars.netAssetMovement > 0) {
    ::281 =>         } else if (vars.netAssetMovement < 0) {
  • Ethos-Core/contracts/BorrowerOperations.sol
    ::128 =>         assert(MIN_NET_DEBT > 0);
    ::197 =>         assert(vars.compositeDebt > 0);
    ::337 =>         if (!_isDebtIncrease && _LUSDChange > 0) {
    ::552 =>         require(_LUSDChange > 0, "BorrowerOps: Debt increase requires non-zero debtChange");
  • Ethos-Core/contracts/LQTY/CommunityIssuance.sol
    ::86 =>         if (lastIssuanceTimestamp < lastDistributionTime) {
    ::106 =>         if (lastIssuanceTimestamp < lastDistributionTime) {
  • Ethos-Core/contracts/LQTY/LQTYStaking.sol
    ::152 =>         if (_LQTYamount > 0) {
    ::266 =>         require(currentStake > 0, 'LQTYStaking: User must have a non-zero stake');
    ::270 =>         require(_amount > 0, 'LQTYStaking: Amount must be non-zero');
  • Ethos-Core/contracts/LUSDToken.sol
    ::279 =>         if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
  • Ethos-Core/contracts/StabilityPool.sol
    ::583 =>         } else if (currentP.mul(newProductFactor).div(DECIMAL_PRECISION) < SCALE_FACTOR) {
    ::591 =>         assert(newP > 0);
    ::742 =>         if (epochSnapshot < currentEpoch) { return 0; }
    ::768 =>         if (compoundedDeposit < initialDeposit.div(1e9)) {return 0;}
    ::870 =>         require(_initialDeposit > 0, 'StabilityPool: User must have a non-zero deposit');
    ::874 =>         require(_amount > 0, 'StabilityPool: Amount must be non-zero');
  • Ethos-Core/contracts/TroveManager.sol
    ::397 =>         } else if ((_ICR > _100pct) && (_ICR < _MCR)) {
    ::424 =>             if (singleLiquidation.collSurplus > 0) {
    ::452 =>         if (_LUSDInStabPool > 0) {
    ::493 =>         if (_collDecimals < LiquityMath.CR_CALCULATION_DECIMALS) {
    ::495 =>         } else if (_collDecimals > LiquityMath.CR_CALCULATION_DECIMALS) {
    ::551 =>         require(totals.totalDebtInSequence > 0);
    ::563 =>         if (totals.totalCollSurplus > 0) {
    ::651 =>             else if (vars.backToNormalMode && vars.ICR < vars.collMCR) {
    ::694 =>             if (vars.ICR < _MCR) {
    ::754 =>         require(totals.totalDebtInSequence > 0);
    ::766 =>         if (totals.totalCollSurplus > 0) {
    ::853 =>             else if (vars.backToNormalMode && vars.ICR < vars.collMCR) {
    ::886 =>             if (vars.ICR < _MCR) {
    ::916 =>         if (_LUSD > 0) {
    ::920 =>         if (_collAmount > 0) {
    ::1160 =>         return (rewardSnapshots[_borrower][_collateral].collAmount < L_Collateral[_collateral]);
    ::1224 =>             assert(totalStakesSnapshot[_collateral] > 0);
    ::1386 =>         return TCR < _CCR;
    ::1414 =>         assert(newBaseRate > 0); // Base rate is always non-zero after redemption
    ::1450 =>         require(redemptionFee < _collateralDrawn);
    ::1539 =>         require (TroveOwnersArrayLength > 1 && sortedTroves.getSize(_collateral) > 1);
  • Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol
    ::80 =>         if (wantBalance > _debt) {
    ::92 =>         if (wantBal < _amountNeeded) {
    ::99 =>         if (_amountNeeded > liquidatedAmount) {
    ::131 =>         if (totalAssets > allocated) {
    ::135 =>         } else if (totalAssets < allocated) {
  • Ethos-Vault/contracts/ReaperVaultV2.sol
    ::234 =>         if (stratCurrentAllocation > stratMaxAllocation) {
    ::236 =>         } else if (stratCurrentAllocation < stratMaxAllocation) {
    ::368 =>         if (value > token.balanceOf(address(this))) {
    ::400 =>             if (value > vaultBalance) {
    ::420 =>         if (lockedFundsRatio < DEGRADATION_COEFFICIENT) {
    ::499 =>         if (_roi < 0) {
    ::502 =>         } else if (_roi > 0) {
    ::509 =>         if (vars.available < 0) {
    ::518 =>         } else if (vars.available > 0) {
    ::525 =>         if (vars.credit > vars.freeWantInStrat) {
    ::527 =>         } else if (vars.credit < vars.freeWantInStrat) {
    ::534 =>         if (vars.lockedProfitBeforeLoss > vars.loss) {
  • Ethos-Vault/contracts/abstract/ReaperBaseStrategyV4.sol
    ::113 =>         if (availableCapital < 0) {
    ::120 =>             if (amountFreed < debt) {
    ::122 =>             } else if (amountFreed > debt) {
    ::127 =>             if (roi < 0) {
    ::192 =>             upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp,

References

[G-14] If possible, use private rather than public for constants

Description

If necessary, the values can be obtained from the verified contract source code; alternatively, if there are many values, a single getter function that returns a tuple containing the values of all currently-public constants can be used. An example: FraxlendPair.sol#L156-L178.

The compiler doesn't have to write non-payable getter functions for deployment calldata, store the value's bytes somewhere other than where it will be used, or add another entry to the method ID table, which saves 3406-3606 gas in deployment gas.

Findings

  • Ethos-Core/contracts/ActivePool.sol
    ::30 =>     string constant public NAME = "ActivePool";
  • Ethos-Core/contracts/BorrowerOperations.sol
    ::21 =>     string constant public NAME = "BorrowerOperations";
  • Ethos-Core/contracts/CollateralConfig.sol
    ::21 =>     uint256 constant public MIN_ALLOWED_MCR = 1.1 ether; // 110%
    ::25 =>     uint256 constant public MIN_ALLOWED_CCR = 1.5 ether; // 150%
  • Ethos-Core/contracts/LQTY/CommunityIssuance.sol
    ::19 =>     string constant public NAME = "CommunityIssuance";
  • Ethos-Core/contracts/LQTY/LQTYStaking.sol
    ::23 =>     string constant public NAME = "LQTYStaking";
  • Ethos-Core/contracts/StabilityPool.sol
    ::150 =>     string constant public NAME = "StabilityPool";
    ::197 =>     uint public constant SCALE_FACTOR = 1e9;
  • Ethos-Core/contracts/TroveManager.sol
    ::48 =>     uint constant public SECONDS_IN_ONE_MINUTE = 60;
    ::53 =>     uint constant public MINUTE_DECAY_FACTOR = 999037758833783000;
    ::54 =>     uint constant public override REDEMPTION_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%
    ::55 =>     uint constant public MAX_BORROWING_FEE = DECIMAL_PRECISION / 100 * 5; // 5%
    ::61 =>     uint constant public BETA = 2;
  • Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol
    ::24 =>     address public constant VELO_ROUTER = 0xa132DAB612dB5cB9fC9Ac426A0Cc215A3423F9c9;
    ::25 =>     ILendingPoolAddressesProvider public constant ADDRESSES_PROVIDER =
    ::27 =>     IAaveProtocolDataProvider public constant DATA_PROVIDER =
    ::29 =>     IRewardsController public constant REWARDER = IRewardsController(0x6A0406B8103Ec68EE9A713A073C7bD587c5e04aD);
  • Ethos-Vault/contracts/ReaperVaultV2.sol
    ::40 =>     uint256 public constant DEGRADATION_COEFFICIENT = 10**18; // The unit for calculating profit degradation.
    ::41 =>     uint256 public constant PERCENT_DIVISOR = 10000;
    ::73 =>     bytes32 public constant DEPOSITOR = keccak256("DEPOSITOR");
    ::74 =>     bytes32 public constant STRATEGIST = keccak256("STRATEGIST");
    ::75 =>     bytes32 public constant GUARDIAN = keccak256("GUARDIAN");
    ::76 =>     bytes32 public constant ADMIN = keccak256("ADMIN");
  • Ethos-Vault/contracts/abstract/ReaperBaseStrategyV4.sol
    ::23 =>     uint256 public constant PERCENT_DIVISOR = 10_000;
    ::24 =>     uint256 public constant UPGRADE_TIMELOCK = 48 hours; // minimum 48 hours for RF
    ::25 =>     uint256 public constant FUTURE_NEXT_PROPOSAL_TIME = 365 days * 100;
    ::49 =>     bytes32 public constant KEEPER = keccak256("KEEPER");
    ::50 =>     bytes32 public constant STRATEGIST = keccak256("STRATEGIST");
    ::51 =>     bytes32 public constant GUARDIAN = keccak256("GUARDIAN");
    ::52 =>     bytes32 public constant ADMIN = keccak256("ADMIN");

References

[Gโ€‘15] Use a more recent version of Solidity to save gas

Description

Use a version of Solidity at least 0.8.10 to:

  • have external calls skip contract existence checks if the external call has a return value (from v 0.8.10);
  • get custom errors, which are cheaper at deployment than revert()/require() strings (from v 0.8.4);
  • get better struct packing and cheaper multiple storage reads (from v 0.8.3);
  • get simple compiler automatic inlining (from v 0.8.2).

Findings

References

#0 - c4-judge

2023-03-09T17:42:00Z

trust1995 marked the issue as grade-b

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax ยฉ 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter