Ethos Reserve contest - DeFiHackLabs'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: 62/154

Findings: 2

Award: $103.33

🌟 Selected for report: 0

🚀 Solo Findings: 0

C4_ethos_QA_report

[L1] Solidity outdate version

Proof of concept

pragma solidity 0.6.11;

in all Ethos contract, ex: https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L3

Recommendation:

Update solidity 0.8.18 or higher which has the fix for some vulnerabilites.

[L2] Susceptible to Signature Malleability

In Solidity ecrecover function directly to verify the given signatures. However, the ecrecover EVM opcode allows malleable (non-unique) signatures and thus is susceptible to replay attacks.

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L287

        address recoveredAddress = ecrecover(digest, v, r, s);
        require(recoveredAddress == owner, 'LUSD: invalid signature');

Recommendation:

Use the recover function from OpenZeppelin's ECDSA library for signature verification.

function permit
    (
        address owner, 
        address spender, 
        uint amount, 
        uint deadline, 
        uint8 v, 
        bytes32 r, 
        bytes32 s
    ) 
        external 
        override 
    {
        if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
            revert('LUSD: Invalid s value');
        }

        require(deadline >= block.timestamp, 'LUSD: expired deadline');      
        bytes32 digest = keccak256(abi.encodePacked(
            '\x19\x01',
            domainSeparator(),
            keccak256(abi.encode(
                _PERMIT_TYPEHASH,
                owner,
                spender,
                amount,
                nonces[owner]++,
                deadline
            ))
        ));
        
        address recoveredAddress = recover(digest, r, s);
        require(recoveredAddress == owner, 'LUSD: invalid signature');
        _approve(owner, spender, amount);
    }
}

[L3] Lack of zero address checks on ecrecover

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L287

Recommendation:

add require(recoveredAddress != address(0), "recoveredAddress is zero address!");

[L4] TresuryAddress should be multi-sig.

From code, looks like tressury is a EOA wallet. Better to use multi-sig wallet like Gnosis safe.

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/ActivePool.sol#L93

Recommendation:

Use checkContract to check address exist.

[L5] Owner can renounce Ownership

Use Ownable there is only one owner at a time. Typically, the contract’s owner is the account that deploys the contract. As a result, the owner is able to perform certain privileged activities.

onlyOwner functions;

ActivePool.sol:
function setAddresses(...) external  onlyOwner
function setYieldingPercentage(address _collateral, uint256 _bps) external onlyOwner
function setYieldingPercentageDrift(uint256 _driftBps) external onlyOwner
function setYieldClaimThreshold(address _collateral, uint256 _threshold) external onlyOwner 
function setYieldDistributionParams(uint256 _treasurySplit, uint256 _SPSplit, uint256 _stakingSplit) external onlyOwner 
function manualRebalance(address _collateral, uint256 _simulatedAmountLeavingPool) external onlyOwner

CollateralConfig.sol:
function initialize(...) onlyOwner 
function updateCollateralRatios() onlyOwner 

PriceFeed.sol:
function setAddresses(...) external onlyOwner
function updateChainlinkAggregator(...) external onlyOwner 
function updateTellorCaller(...) external onlyOwner 

/LQTY/CommunityIssuance.sol
function setAddresses(...) external  onlyOwner
function fund(uint amount) external onlyOwner
function updateDistributionPeriod(uint256 _newDistributionPeriod) external onlyOwner 

The Openzeppelin’s Ownable used in this project contract implements renounceOwnership. This can represent a certain risk if the ownership is renounced for any other reason than by design. Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.

Recommendation:

We recommend to either reimplement the function to disable it or to clearly specify if it is part of the contract design.

  • You can create a new contract class called " x " that inherits from the Ownable contract but overwrites the "renounceOwnership" function to throw an exception instead of allowing the owner to renounce ownership.
  • This avoids the risk of losing control of the contract.
import "@openzeppelin/contracts/access/Ownable.sol";

contract x is Ownable {
    function renounceOwnership() public override onlyOwner {
        revert("Ownership cannot be renounced");
    }
}

[L6] Missing emits for declared events

Description:

updateTreasury(address newTreasury) is missing event emits which prevents the intended data from being observed easily by off-chain interfaces.

https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L627-L631

Recommendation:

Add emits when Treasury address is updated.

[L7] Missing check _newTvlCap isn't a zero value

Description:

Although the updateTvlCap() is checked the msg.sender has ADMIN role, But still didn't check paramerter _newTvlCap is not 0

https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L578-L582

Recommendation:

Add a require() check.

function updateTvlCap(uint256 _newTvlCap) public {
    _atLeastRole(ADMIN);
    require(_newTvlCap != 0);
    tvlCap = _newTvlCap;
    emit TvlCapUpdated(tvlCap);
}

[L8] Missing zero address check in constructor

Allowing zero-addresses will lead to contract reverts and force redeployments if there are no setters for such address variables.

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L111-L124

    constructor(
        address _token,
        string memory _name,
        string memory _symbol,
        uint256 _tvlCap,
        address _treasury,
        address[] memory _strategists,
        address[] memory _multisigRoles
    ) ERC20(string(_name), string(_symbol)) {
        token = IERC20Metadata(_token);
        constructionTime = block.timestamp;
        lastReport = block.timestamp;
        tvlCap = _tvlCap;
        treasury = _treasury;

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultERC4626.sol#L16-L24

    // See comments on ReaperVaultV2's constructor
    constructor(
        address _token,
        string memory _name,
        string memory _symbol,
        uint256 _tvlCap,
        address _treasury,
        address[] memory _strategists,
        address[] memory _multisigRoles
    ) ReaperVaultV2(_token, _name, _symbol, _tvlCap, _treasury, _strategists, _multisigRoles) {}

Add zero-address checks for all initializations/setters of all address state variables.

require(_treasury != address(0), "The address cannot be zero");
treasury = _treasury;

To save some gas:

assembly {
    if iszero(_treasury) {
        mstore(0x00, "zero address")
        revert(0x00, 0x20)
    }
}
treasury = _treasury;

[L9] Missing zero address check in function

Missing _token for the zero address check.

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L637-L644

    function inCaseTokensGetStuck(address _token) external {
        _atLeastRole(ADMIN);
        require(_token != address(token), "!token");

        uint256 amount = IERC20Metadata(_token).balanceOf(address(this));
        IERC20Metadata(_token).safeTransfer(msg.sender, amount);
        emit InCaseTokensGetStuckCalled(_token, amount);
    }

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol#L63-L73

   function __ReaperBaseStrategy_init(
        address _vault,
        address _want,
        address[] memory _strategists,
        address[] memory _multisigRoles
    ) internal onlyInitializing {
        __UUPSUpgradeable_init();
        __AccessControlEnumerable_init();

        vault = _vault;
        want = _want;

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol#L62-L70

    function initialize(
        address _vault,
        address[] memory _strategists,
        address[] memory _multisigRoles,
        IAToken _gWant
    ) public initializer {
        gWant = _gWant;
        want = _gWant.UNDERLYING_ASSET_ADDRESS();
        __ReaperBaseStrategy_init(_vault, want, _strategists, _multisigRoles);

Add zero-address checks for all function of all address state variables.

function inCaseTokensGetStuck(address _token) external {
    _atLeastRole(ADMIN);
    require(_token != address(0), "The address cannot be zero");
    require(_token != address(token), "!token");

    uint256 amount = IERC20Metadata(_token).balanceOf(address(this));
    IERC20Metadata(_token).safeTransfer(msg.sender, amount);
    emit InCaseTokensGetStuckCalled(_token, amount);
}

To save some gas:

function inCaseTokensGetStuck(address _token) external {
    _atLeastRole(ADMIN);
     assembly {
        if iszero(_token) {
            mstore(0x00, "zero address")
            revert(0x00, 0x20)
        }
    }
    require(_token != address(token), "!token");

    uint256 amount = IERC20Metadata(_token).balanceOf(address(this));
    IERC20Metadata(_token).safeTransfer(msg.sender, amount);
    emit InCaseTokensGetStuckCalled(_token, amount);
}

[L10] making rewardClaimingTokens immutable

Since it carries over unclaimed rewards from the previous reward program. It would be safer to keep the reward token immutable as a safeguard against violations of this condition.

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol#L41

address[] public rewardClaimingTokens;

The easiest mitigation would be to pass this variable as immutable

address[] public immutable rewardClaimingTokens;

[L11] Missing visibility setting

The default function visibility level in contracts is public, in interfaces - external. Explicitly define function visibility to prevent confusion. https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L28-L32

    address stabilityPoolAddress;
    address gasPoolAddress;
    ICollSurplusPool collSurplusPool;

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L31

    address gasPoolAddress;

Recommendation

It is recommended to make a conscious decision on which visibility type is appropriate for a function. This can dramatically reduce the attack surface of a contract system.


[N1] Lock pragmas to Specific Compiler Version

Description:

Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile locally. https://swcregistry.io/docs/SWC-103

Affected instance: https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol#L3

1 result - 1 file
File: Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol
1: // SPDX-License-Identifier: BUSL1.1
2: 
3: pragma solidity ^0.8.0;

Recommendation:

Lock pragmas to specific compiler version. solidity-specific/locking-pragmas

[N2] Incorrect error message

Error message miss caller is redemptionHelper.

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LQTY/LQTYStaking.sol#L257


    function _requireCallerIsTroveManagerOrActivePool() internal view {
        address redemptionHelper = address(ITroveManager(troveManagerAddress).redemptionHelper());
        require(
            msg.sender == troveManagerAddress ||
            msg.sender == redemptionHelper ||
            msg.sender == activePoolAddress,
            "LQTYStaking: caller is not TroveM or ActivePool" // @audit error message miss caller is redemptionHelper.
        );

[N3] Function doesn't initialize return value

https://github.com/code-423n4/2023-02-ethos/blob/d46ede81e35c1ed86242c9ba3c5c57d2ca2b57d8/Ethos-Core/contracts/LUSDToken.sol#L298

function _chainID() private pure returns (uint256 chainID) {
    assembly {
        chainID := chainid()
    }
}

Recommendation:

function _chainID() private pure returns (uint256 chainID) {
    chainID = 0; 
    assembly {
        chainID := chainid()
    }
}

[N4] Use _requireNonZeroAmount instead of if (_amount !=0)

Origin source code:

if (_amount !=0) {_requireNoUnderCollateralizedTroves();}

https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/StabilityPool.sol#L368

This could increases code readability:

_requireNonZeroAmount(_amount);
_requireNoUnderCollateralizedTroves();

Explanation

  • You could use the _requireNonZeroAmount function, which is a function defined in the contract.
  • This is used to check if _amount > 0.

[N5] Without clear error messages

Lots of require check without error messages. some examples below:

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L716

require(_troveArray.length != 0);

Fix:

require(_troveArray.length != 0, "TroveManager: Calldata address array must not be empty");

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L754

 require(totals.totalDebtInSequence > 0);

Fix:

require(totals.totalDebtInSequence > 0, "TroveManager: nothing to liquidate");

[N6] Open TODOs

Use temporary TODOs as you work on a feature, but make sure to treat them before merging. Either add a link to a proper issue in your TODO, or remove it from the code

Context:

StabilityPool.sol#L335-L338

File: Ethos-Core/contracts/StabilityPool.sol 335: /* TODO tess3rac7 unused var, but previously included in ETHGainWithdrawn event log. 336: * Doesn't make a lot of sense to include in multiple CollateralGainWithdrawn logs. 337: * If needed could create a separate event just to report this. 338: */

StabilityPool.sol#L380-L383

File: Ethos-Core/contracts/StabilityPool.sol 380: /* TODO tess3rac7 unused var, but previously included in ETHGainWithdrawn event log. 381: * Doesn't make a lot of sense to include in multiple CollateralGainWithdrawn logs. 382: * If needed could create a separate event just to report this. 383: */

[N7] Use require instead of assert

Context :-

BorrowerOperations.sol#L301

File: Ethos-Core/contracts/BorrowerOperations.sol 301: assert(msg.sender == _borrower || (msg.sender == stabilityPoolAddress && _collTopUp > 0 && _LUSDChange == 0));

LUSDToken.sol#L321

File: Ethos-Core/contracts/LUSDToken.sol 321: assert(account != address(0));

LUSDToken.sol#L312-L313

File: Ethos-Core/contracts/LUSDToken.sol 312: assert(sender != address(0)); 313: assert(recipient != address(0));

Description

Assert should not be used except for tests, require should be used.

Prior to Solidity 0.8.0, pressing a confirm consumes the remainder of the process’s available gas instead of returning it, as request()/revert() did.

assert() and require(); The big difference between the two is that the assert()function when false, uses up all the remaining gas and reverts all the changes made. Meanwhile, a require() function when false, also reverts back all the changes made to the contract but does refund all the remaining gas fees we offered to pay. This is the most common Solidity function used by developers for debugging and error handling.

Assertion() should be avoided even after solidity version 0.8.0, because its documentation states “The Assert function generates an error of type Panic(uint256).Code that works properly should never Panic, even on invalid external input. If this happens, you need to fix it in your contract. There’s a mistake”.

[N8] use underscore for number literals

TroveManager.sol#L53

File: Ethos-Core/contracts/TroveManager.sol 53: uint constant public MINUTE_DECAY_FACTOR = 999037758833783000;

ReaperVaultV2.sol#L41

File: Ethos-Vault/contracts/ReaperVaultV2.sol 41: uint256 public constant PERCENT_DIVISOR = 10000;

Description

There is occasions where certain number have been hardcoded, either in variable or in the code itself. Large numbers can become hard to read.

Recommendation

Consider using underscores for number literals to improve its readability.like this

File: Ethos-Vault/contracts/ReaperVaultV2.sol - 41: uint256 public constant PERCENT_DIVISOR = 10000; + uint256 public constant PERCENT_DIVISOR = 10_000

[S-1] Remove unecessary comments

here the commented out portions serves no purpose at all,consider removing these TroveManager.sol#L14

File: Ethos-Core/contracts/TroveManager.sol 14: // import "./Dependencies/Ownable.sol";

TroveManager.sol#L18-L19

File: Ethos-Core/contracts/TroveManager.sol 18: contract TroveManager is LiquityBase, /*Ownable,*/ CheckContract, ITroveManager { 19: // string constant public NAME = "TroveManager";

#0 - c4-judge

2023-03-10T16:56:51Z

trust1995 marked the issue as grade-b

#1 - c4-sponsor

2023-03-29T01:01:20Z

0xBebis marked the issue as sponsor acknowledged

Report

Gas Optimizations

IssueInstances
[G-1]Setting the constructor to payable saves gas (sayan)2
[G-2]Mark functions as payable (with discretion)49
[G-3]Use assembly for math (add, sub, mul, div)2
[G-4]Short Revert Strings8
[G-5]Use assembly to write storage values13
[G-6]Use multiple require() statments insted of require(expression && expression && …)4
[G-7]Optimal Comparison8
[G-8]Tightly pack storage variables2
[G-9]Instead of if(x), use assembly with iszeroiszero (x)(secoalba)12

[G-1] Setting the constructor to payable saves gas (sayan)

Saves ~13 gas per instance

2 instances

File: Ethos-Vault/contracts/ReaperVaultV2.sol
111:     constructor(
112:         address _token,
113:         string memory _name,
114:         string memory _symbol,
115:         uint256 _tvlCap,
116:         address _treasury,
117:         address[] memory _strategists,
118:         address[] memory _multisigRoles
119:     ) ERC20(string(_name), string(_symbol)) {


File: Ethos-Vault/contracts/ReaperVaultERC4626.sol
16:     constructor(
17:         address _token,
18:         string memory _name,
19:         string memory _symbol,
20:         uint256 _tvlCap,
21:         address _treasury,
22:         address[] memory _strategists,
23:         address[] memory _multisigRoles
24:     ) ReaperVaultV2(_token, _name, _symbol, _tvlCap, _treasury, _strategists, _multisigRoles) {}

[G-2] Mark functions as payable (with discretion)

You can mark public or external functions as payable to save gas. Functions that are not payable have additional logic to check if there was a value sent with a call, however, making a function payable eliminates this check. This optimization should be carefully considered due to potentially unwanted behavior when a function does not need to accept ether.

Saves ~24 gas per instance

49 instances

File; Ethos-Core/contracts/TroveManager.sol

225:     constructor() public {

232:    function setAddresses(

299:    function getTroveOwnersCount(address _collateral) external view override returns (uint) {

303:    function getTroveFromTroveOwnersArray(address _collateral, uint _index) external view override returns (address) {

310:    function liquidate(address _borrower, address _collateral) external override {

513:    function liquidateTroves(address _collateral, uint _n) external override {

715:    function batchLiquidateTroves(address _collateral, address[] memory _troveArray) public override {

939:    function redeemCloseTrove(

962:    function updateDebtAndCollAndStakesPostRedemption(

982:    function burnLUSDAndEmitRedemptionEvent(

1016:    function redeemCollateral(

1045:    function getNominalICR(address _borrower, address _collateral) public view override returns (uint) {

1054:    function getCurrentICR(

1076:    function applyPendingRewards(address _borrower, address _collateral) external override {

1111:    function updateTroveRewardSnapshots(address _borrower, address _collateral) external override {

1123:    function getPendingCollateralReward(address _borrower, address _collateral) public view override returns (uint) {

1138:    function getPendingLUSDDebtReward(address _borrower, address _collateral) public view override returns (uint) {

1152:    function hasPendingRewards(address _borrower, address _collateral) public view override returns (bool) {

1164:    function getEntireDebtAndColl(

1183:    function removeStake(address _borrower, address _collateral) external override {

1195:    function updateStakeAndTotalStakes(address _borrower, address _collateral) external override returns (uint) {

1273:    function closeTrove(address _borrower, address _collateral, uint256 _closedStatusNum) external override {

1316:    function addTroveOwnerToArray(address _borrower, address _collateral) external override returns (uint index) {

1361:    function getTCR(address _collateral, uint _price) external view override returns (uint) {

1366:    function checkRecoveryMode(address _collateral, uint _price) external view override returns (bool) {

1397:    function updateBaseRateFromRedemption(

1425:    function getRedemptionRate() public view override returns (uint) {

1440:    function getRedemptionFee(uint _collateralDrawn) public view override returns (uint) {

1444:    function getRedemptionFeeWithDecay(uint _collateralDrawn) external view override returns (uint) {

1456:     function getBorrowingRate() public view override returns (uint) {

1460:    function getBorrowingRateWithDecay() public view override returns (uint) {

1471:    function getBorrowingFee(uint _LUSDDebt) external view override returns (uint) {

1475:    function getBorrowingFeeWithDecay(uint _LUSDDebt) external view override returns (uint) {

1485:    function decayBaseRateFromBorrowing() external override {

1544:    function getTroveStatus(address _borrower, address _collateral) external view override returns (uint) {

1556:    function getTroveColl(address _borrower, address _collateral) external view override returns (uint) {

1562:    function setTroveStatus(address _borrower, address _collateral, uint _num) external override {

1567:    function increaseTroveColl(address _borrower, address _collateral, uint _collIncrease) external override returns (uint) {

1574:    function decreaseTroveColl(address _borrower, address _collateral, uint _collDecrease) external override returns (uint) {

1588:    function decreaseTroveDebt(address _borrower, address _collateral, uint _debtDecrease) external override returns (uint) {


File: Ethos-Vault/contracts/abstract/ReaperBaseStrategyV4.sol

95:     function withdraw(uint256 _amount) external override returns (uint256 loss) {

109:    function harvest() external override returns (int256 roi) {

156:    function setEmergencyExit() external {

167:    function initiateUpgradeCooldown() external {

[G-3] Use assembly for math (add, sub, mul, div)

Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.

Saves ~40 gas per instance

2 instances

File: Ethos-Core/contracts/TroveManager.sol

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%

[G-4] Short Revert Strings

Keeping revert strings under 32-bytes prevents the string from being stored in more than one memory slot.

Saves ~3 gas per instance

8 instances

File: Ethos-Core/contracts/StabilityPool.sol

849:        require( msg.sender == address(activePool), "StabilityPool: Caller is not ActivePool");

853:        require(msg.sender == address(troveManager), "StabilityPool: Caller is not TroveManager");

865:            require(ICR >= collMCR, "StabilityPool: Cannot withdraw while there are troves with ICR < MCR");

870:        require(_initialDeposit > 0, 'StabilityPool: User must have a non-zero deposit');

874:        require(_amount > 0, 'StabilityPool: Amount must be non-zero');


File:  Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol

81:        require(_multisigRoles.length == 3, "Invalid number of multisig roles");

98:        require(_amount <= balanceOf(), "Ammount must be less than balance");

193:         require(
            upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp,
            "Upgrade cooldown not initiated or still ongoing"

[G-5] Use assembly to write storage values

Saves ~66 gas per instance

13 instances

File; Ethos-Core/contracts/TroveManager.sol

227:        owner = msg.sender;

266:        borrowerOperationsAddress = _borrowerOperationsAddress;

271:        gasPoolAddress = _gasPoolAddress;

294:        owner = address(0);

1417:        baseRate = newBaseRate;

1504:            lastFeeOperationTime = block.timestamp;



File:  Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol

72:        vault = _vault;

73:        want = _want;

137:        lastHarvestTimestamp = block.timestamp;

158:        emergencyExit = true;

169:        IVault(vault).revokeStrategy(address(this));

181:        upgradeProposalTime = block.timestamp + FUTURE_NEXT_PROPOSAL_TIME;

[G-6] Use multiple require() statments insted of require(expression && expression && …)

You can safe gas by breaking up a require statement with multiple conditions, into multiple require statements with a single condition.

Saves ~16 gas per instance

4 instances

File: Ethos-Core/contracts/BorrowerOperations.sol

653:             require(_maxFeePercentage >= BORROWING_FEE_FLOOR && _maxFeePercentage <= DECIMAL_PRECISION,

File: Ethos-Core/contracts/LUSDToken.sol

347:        require(

352:        require(

File: Ethos-Core/contracts/TroveManager.sol

1539:         require (TroveOwnersArrayLength > 1 && sortedTroves.getSize(_collateral) > 1);

[G-7] Optimal Comparison

When comparing integers, it is cheaper to use strict > & < operators over >= & <= operators, even if you must increment or decrement one of the operands. Note: before using this technique, it’s important to consider whether incrementing/decrementing one of the operators could result in an over/underflow. This optimization is applicable when the optimizer is turned off.

Saves ~3 gas per instance

8 instances

File: Ethos-Core/contracts/TroveManager.sol

372:        if (TroveOwners[_collateral].length <= 1) {return singleLiquidation;} // don't liquidate if last trove

383:        if (_ICR <= _100pct) {

415:        } else if ((_ICR >= _MCR) && (_ICR < _TCR) && (singleLiquidation.entireTroveDebt <= _LUSDInStabPool)) {

616:                if (vars.ICR >= vars.collMCR && vars.remainingLUSDInStabPool == 0) { break; }

817:                if (vars.ICR >= vars.collMCR && vars.remainingLUSDInStabPool == 0) { continue; }

1348:        assert(index <= idxLast);

1489:        assert(decayedBaseRate <= DECIMAL_PRECISION);  // The baseRate can decay to 0

1503:        if (timePassed >= SECONDS_IN_ONE_MINUTE) {


[G-8] Tightly pack storage variables

When defining storage variables, make sure to declare them in ascending order, according to size. When multiple variables are able to fit into one 256 bit slot, this will save storage size and gas during runtime. For example, if you have a bool, uint256 and a bool, instead of defining the variables in the previously mentioned order, defining the two boolean variables first will pack them both into one storage slot since they only take up one byte of storage.

Saves ~17,298 gas per instance

2 instances

File: Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol

19: contract ReaperStrategyGranarySupplyOnly is ReaperBaseStrategyv4, VeloSolidMixin {

File: Ethos-Vault/contracts/ReaperVaultV2.sol

21: contract ReaperVaultV2 is ReaperAccessControl, ERC20, IERC4626Events, AccessControlEnumerable, ReentrancyGuard {

[G-9] Instead of if(x), use assembly with iszeroiszero (x)(secoalba)

Saves ~5 gas per instance

12 instances

File: Ethos-Core/contracts/BorrowerOperations.sol

189:        if (!isRecoveryMode) {

202:        if (isRecoveryMode) {

292:        if (_isDebtIncrease) {

311:        if (_isDebtIncrease && !isRecoveryMode) { 

337:        if (!_isDebtIncrease && _LUSDChange > 0) {

490:        if (_isDebtIncrease) {

496:        if (_isCollIncrease) {

597:            if (_isDebtIncrease) {

649:        if (_isRecoveryMode) {

File: Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol

75:         if (emergencyExit) {

File: Ethos-Vault/contracts/ReaperVaultV2.sol

604:        if (_active) {


#0 - c4-judge

2023-03-09T18:19:34Z

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