Ethos Reserve contest - 0xSmartContract'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: 23/154

Findings: 2

Award: $764.26

QA:
grade-a
Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Summary

Low Risk Issues List

NumberIssues DetailsContext
[L-01]Use ERC-5143: Slippage Protection for Tokenized Vault1
[L-02]Head Overflow Bug in Calldata Tuple ABI-Reencoding1
[L-03]uint type argument was use instead uint256 for permitfunction , this will cause the function selector to be miscalculated1
[L-04]First ERC4626 deposit exploit can break share calculation1
[L-05]initialize() function can be called by anybody1
[L-06]Insufficient coverageAll Contracts
[L-07]Missing Event for initialize1
[L-08]Project Upgrade and Stop Scenario should be1
[L-09]Loss of precision due to rounding in amount / uint256(rewardsPerSecond)1
[L-10]Use Fuzzing Test for complicated code bases
[L-11]Update codes to avoid Compile Errors11
[L-12]Add to Event-Emit for critical functions3
[L-13]Use uint256 instead uint477
[L-14]Signature Malleability of EVM's ecrecover()1
[L-15]sendCollateral event is missing parameters1
[L-16]Lack of Input Validation1
[L-17]Prevent division by 01
[L-18]Project has NPM Dependency which uses a vulnerable version : @openzeppelin1
[L-19]Keccak Constant values should used to immutable rather than constant8
[L-20]Due to the novelty of the ERC4626 standard, it is safer to use as upgradeable
[L-21]In the setAddresses function, there is no return of incorrect address identification1

Total 21 issues

Non-Critical Issues List

NumberIssues DetailsContext
[N-01]Omissions in Events2
[N-02]NatSpec comments should be increased in contractsAll Contracts
[N-03]Function writing that does not comply with the Solidity Style GuideAll Contracts
[N-04]Include return parameters in NatSpec commentsAll Constracs
[N-05]Tokens accidentally sent to the contract cannot be recovered1
[N-06]Repeated validation logic can be converted into a function modifier13
[N-07]Use a more recent version of Solidity14
[N-08]For functions, follow Solidity standard naming conventions (internal function style rule)4
[N-09]Floating pragma4
[N-10]Use descriptive names for Contracts and LibrariesAll Contracts
[N-11]Use SMTCheckerAll Contracts
[N-12]Add NatSpec Mapping comment13
[N-13]Showing the actual values of numbers in NatSpec comments makes checking and reading code easier2
[N-14]Lines are too long16
[N-15]Constants on the left are better36
[N-16]constants should be defined rather than using magic numbers3
[N-17]Missing timelock for critical parameter change1
[N-18]Use the delete keyword instead of assigning a value of 017
[N-19]Not using the named return variables anywhere in the function is confusing2
[N-20]Function Calls in Loop Could Lead to Denial of Service due to Array length not being checked2
[N-21]According to the syntax rules, use mapping () instead of mapping() using spaces as keyword2
[N-22]For modern and more readable code; update import usages110
[N-23]Use require instead of assert20
[N-24]Implement some type of version counter that will be incremented automatically for contract upgrades1
[N-25]Use a single file for all system-wide constants18
[N-26]Assembly Codes Specific – Should Have Comments1
[N-27]Highest risk must be specified in NatSpec comments and documentation1
[N-28]Include proxy contracts to Audit

Total 28 issues

Suggestions

NumberSuggestion Details
[S-01]Generate perfect code headers every time

Total 2 suggestions

[L-01] Use ERC-5143: Slippage Protection for Tokenized Vault

Project use ERC-4626 standart

EIP-4626 is vulnerable to the so-called inflation attacks. This attack results from the possibility to manipulate the exchange rate and front run a victim’s deposit when the vault has low liquidity volume.

Proposed mitigation

The following standard extends the EIP-4626 Tokenized Vault standard with functions dedicated to the safe interaction between EOAs and the vault when price is subject to slippage.

https://eips.ethereum.org/EIPS/eip-5143

[L-02] Head Overflow Bug in Calldata Tuple ABI-Reencoding

On July 5, 2022, Chance Hudson (@vimwitch) from the Ethereum Foundation discovered a bug in the Solidity code generator. The earliest affected version of the compiler is 0.5.8, which introduced ABI-reencoding of calldata arrays and structs. Solidity version 0.8.16, released on August 08, 2022, provides a fix.

 Fix data corruption that affected ABI-encoding of calldata values represented by tuples: structs at any nesting level; argument lists of external functions, events and errors; return value lists of external functions. The 32 leading bytes of the first dynamically-encoded value in the tuple would get zeroed when the last component contained a statically-encoded array.

Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  159       */
  160:     function setHarvestSteps(address[2][] calldata _newSteps) external {
  161:         _atLeastRole(ADMIN);
  162:         delete steps;
  163: 
  164:         uint256 numSteps = _newSteps.length;
  165:         for (uint256 i = 0; i < numSteps; i = i.uncheckedInc()) {
  166:             address[2] memory step = _newSteps[i];
  167:             require(step[0] != address(0));
  168:             require(step[1] != address(0));
  169:             steps.push(step);
  170:         }
  171:     }

Proof of Concept

https://blog.soliditylang.org/2022/08/08/calldata-tuple-reencoding-head-overflow-bug/

Use solidity of version min. 0.8.16

[L-03] uint type argument was use instead uint256 for permitfunction , this will cause the function selector to be miscalculated

LUSDToken.permit() function is important external function and use for external account interaction, when external contract accounts interact with the contract, they trigger the permit function with interface or low level call

However, the best practice of the permit function is to use uint256 type arguments, in the project it cannot interact with the permit function of the contract because it is used with uint type, the operation will be reverted

Ethos-Core/contracts/LUSDToken.sol:
  261  
  262:     function permit
  263:     (
  264:         address owner, 
  265:         address spender, 
  266:         uint amount, 
  267:         uint deadline, 
  268:         uint8 v, 
  269:         bytes32 r, 
  270:         bytes32 s
  271:     ) 
  272:         external 
  273:         override 
  274:     {

Proof of Concept


contract CheckFunctionSelector {

    // 0x97a6e84a
    function functionSelector_uint() external pure returns (bytes4 fourbyte) {
       return bytes4(keccak256(bytes("permit(address,address,uint,uint,uint8,bytes32,bytes32)")));
    }

    // 0xd505accf
    function functionSelector_uint256() external pure returns (bytes4 fourbyte) {
       return bytes4(keccak256(bytes("permit(address,address,uint256,uint256,uint8,bytes32,bytes32)")));
    }

}
Ethos-Core/contracts/LUSDToken.sol:
  261  
  262:     function permit
  263:     (
  264:         address owner, 
  265:         address spender, 
- 266:         uint amount, 
+ 266:         uint256 amount, 
- 267:         uint deadline, 
+ 267:         uint256 deadline,
  268:         uint8 v, 
  269:         bytes32 r, 
  270:         bytes32 s
  271:     ) 
  272:         external 
  273:         override 
  274:     {

[L-04] First ERC4626 deposit exploit can break share calculation

Impact

A well known attack vector for almost all shares based liquidity pool contracts, where an early user can manipulate the price per share and profit from late users' deposits because of the precision loss caused by the rather large value of price per share.


Ethos-Vault/contracts/ReaperVaultERC4626.sol:
  50      // the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.
  51:     function convertToShares(uint256 assets) public view override returns (uint256 shares) {
  52:         if (totalSupply() == 0 || _freeFunds() == 0) return assets;
  53:         return (assets * totalSupply()) / _freeFunds();
  54:     }

Proof Of Concept

1 - A malicious early user can deposit() with 1 wei of asset token as the first depositor of the LToken, and get 1 wei of shares

2 - Then the attacker can send 10000e18 - 1 of asset tokens and inflate the price per share from 1.0000 to an extreme value of 1.0000e22 ( from (1 + 10000e18 - 1) / 1)

3 - As a result, the future user who deposits 19999e18 will only receive 1 wei (from 19999e18 * 1 / 10000e18) of shares token

4 - They will immediately lose 9999e18 or half of their deposits if they redeem() right after the deposit()

The attacker can profit from future users' deposits. While the late users will lose part of their funds to the attacker.

Consider sending the first 1000 shares to the address 0, a mitigation used in Uniswap V2

[L-05] initialize() function can be called by anybody

initialize() function can be called anybody when the contract is not initialized.

More importantly, if someone else runs this function, they will have full authority because of the __ReaperBaseStrategy_init() function and __ReaperBaseStrategy_init() function sets DEFAULT_ADMIN_ROLE , so this is very important risk

Here is a definition of initialize() function.


Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  61       */
  62:     function initialize(
  63:         address _vault,
  64:         address[] memory _strategists,
  65:         address[] memory _multisigRoles,
  66:         IAToken _gWant
  67:     ) public initializer {
  68:         gWant = _gWant;
  69:         want = _gWant.UNDERLYING_ASSET_ADDRESS();
  70:         __ReaperBaseStrategy_init(_vault, want, _strategists, _multisigRoles);
  71:         rewardClaimingTokens = [address(_gWant)];
  72:     }

Add a control that makes initialize() only call the Deployer Contract or EOA;

if (msg.sender != DEPLOYER_ADDRESS) {
            revert NotDeployer();
        }

[L-06] Insufficient coverage

Description: The test coverage rate of the project is ~93%. Testing all functions is best practice in terms of security criteria. Due to its capacity, test coverage is expected to be 100%.


1 result - 1 file

README.md:
  91: - What is the overall line coverage percentage provided by your tests?:  93

[L-07] Missing Event for initialize

Context:


Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  61       */
  62:     function initialize(
  63:         address _vault,
  64:         address[] memory _strategists,
  65:         address[] memory _multisigRoles,
  66:         IAToken _gWant
  67:     ) public initializer {
  68:         gWant = _gWant;
  69:         want = _gWant.UNDERLYING_ASSET_ADDRESS();
  70:         __ReaperBaseStrategy_init(_vault, want, _strategists, _multisigRoles);
  71:         rewardClaimingTokens = [address(_gWant)];
  72:     }

Description: Events help non-contract tools to track changes, and events prevent users from being surprised by changes Issuing event-emit during initialization is a detail that many projects skip

Recommendation: Add Event-Emit

[L-08] Project Upgrade and Stop Scenario should be

At the start of the project, the system may need to be stopped or upgraded, I suggest you have a script beforehand and add it to the documentation. This can also be called an " EMERGENCY STOP (CIRCUIT BREAKER) PATTERN ".

https://github.com/maxwoe/solidity_patterns/blob/master/security/EmergencyStop.sol

[L-09] Loss of precision due to rounding in amount / uint256(rewardsPerSecond)

If totalSupply is too low then the rounding problem is big

Add scalar so roundings are negligible

Ethos-Vault/contracts/ReaperVaultV2.sol:
  365:         value = (_freeFunds() * _shares) / totalSupply();

[L-10] Use Fuzzing Test for complicated code bases

I recommend fuzzing testing in complex code structures, especially Ethos Reserve, where there is an innovative code base and a lot of computation.

Recommendation:

Use should fuzzing test like Echidna.

As Alberto Cuesta Canada said:

Fuzzing is not easy, the tools are rough, and the math is hard, but it is worth it. Fuzzing gives me a level of confidence in my smart contracts that I didn’t have before. Relying just on unit testing anymore and poking around in a testnet seems reckless now.

https://medium.com/coinmonks/smart-contract-fuzzing-d9b88e0b0a05

[L-11] Update codes to avoid Compile Errors

contracts/StabilityPool.sol:413:1: Warning: Unused local variable.
uint LUSDLoss = initialDeposit.sub(compoundedLUSDDeposit); // Needed only for event log
^-----------^

contracts/StabilityPool.sol:502:1: Warning: Unused local variable.
uint LUSDLoss = initialDeposit.sub(compoundedLUSDDeposit); // Needed only for event log
^-----------^

contracts/ActivePool.sol:26:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract ActivePool is Ownable, CheckContract, IActivePool {
^ (Relevant source part starts here and spans across multiple lines).

contracts/BorrowerOperations.sol:18:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract BorrowerOperations is LiquityBase, Ownable, CheckContract, IBorrowerOperations {
^ (Relevant source part starts here and spans across multiple lines).

contracts/LQTY/LQTYStaking.sol:18:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract LQTYStaking is ILQTYStaking, Ownable, CheckContract, BaseMath {
^ (Relevant source part starts here and spans across multiple lines).

contracts/TroveManager.sol:18:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract TroveManager is LiquityBase, /*Ownable,*/ CheckContract, ITroveManager {
^ (Relevant source part starts here and spans across multiple lines).

contracts/SortedTroves.sol:46:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract SortedTroves is Ownable, CheckContract, ISortedTroves {
^ (Relevant source part starts here and spans across multiple lines).

contracts/PriceFeed.sol:24:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract PriceFeed is Ownable, CheckContract, BaseMath, IPriceFeed {
^ (Relevant source part starts here and spans across multiple lines).

contracts/Proxy/BorrowerWrappersScript.sol:21:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract BorrowerWrappersScript is BorrowerOperationsScript, ERC20TransferScript, LQTYStakingScript {
^ (Relevant source part starts here and spans across multiple lines).

contracts/RedemptionHelper.sol:16:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract RedemptionHelper is LiquityBase, Ownable, IRedemptionHelper {
^ (Relevant source part starts here and spans across multiple lines).

contracts/StabilityPool.sol:146:1: Warning: Contract code size exceeds 24576 bytes (a limit introduced in Spurious Dragon). This contract may not be deployable on mainnet. Consider enabling the optimizer (with a low "runs" value!), turning off revert strings, or using libraries.
contract StabilityPool is LiquityBase, Ownable, CheckContract, IStabilityPool {
^ (Relevant source part starts here and spans across multiple lines).


[L-12] Add to Event-Emit for critical functions

3 functions 


Ethos-Core/contracts/LUSDToken.sol:
  132  
  133:     function pauseMinting() external {
  134:         require(
  135:             msg.sender == guardianAddress || msg.sender == governanceAddress,
  136:             "LUSD: Caller is not guardian or governance"
  137:         );
  138:         mintingPaused = true;
  139:     }
  140: 
  141:     function unpauseMinting() external {
  142:         _requireCallerIsGovernance();
  143:         mintingPaused = false;
  144:     }
  145 

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  166       */
  167:     function initiateUpgradeCooldown() external {
  168:         _atLeastRole(STRATEGIST);
  169:         upgradeProposalTime = block.timestamp;
  170:     }

[L-13] Use uint256 instead uint

Project use uint and uint256

Number of uses: uint = 477 results – 8 files uint256 = 281 results - 11 files

Some developers prefer to use uint256 because it is consistent with other uint data types, which also specify their size, and also because making the size of the data explicit reminds the developer and the reader how much data they've got to play with, which may help prevent or detect bugs.

For example if doing bytes4(keccak('transfer(address, uint)’)), you'll get a different method sig ID than bytes4(keccak('transfer(address, uint256)’)) and smart contracts will only understand the latter when comparing method sig IDs

[L-14] Signature Malleability of EVM's ecrecover()

Context:


Ethos-Core/contracts/LUSDToken.sol:
  261  
  262:     function permit
           // ...

  283:         bytes32 digest = keccak256(abi.encodePacked('\x19\x01', 
  284:                          domainSeparator(), keccak256(abi.encode(
  285:                          _PERMIT_TYPEHASH, owner, spender, amount, 
  286:                          _nonces[owner]++, deadline))));
  287:         address recoveredAddress = ecrecover(digest, v, r, s);

Description: Description: The function calls the 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.

Although a replay attack seems not possible for this contract, I recommend using the battle-tested OpenZeppelin's ECDSA library.

Recommendation:* Use the ecrecover function from OpenZeppelin's ECDSA library for signature verification. (Ensure using a version > 4.7.3 for there was a critical bug >= 4.1.0 < 4.7.3).

[L-15] sendCollateral event is missing parameters

The ActivePool.sol contract has very important function; sendCollateral

However, only amounts are published in emits, whereas msg.sender must be specified in every transaction, a contract or web page listening to events cannot react to users, emit does not serve the purpose. Basically, this event cannot be used

Ethos-Core/contracts/ActivePool.sol:
  170  
  171:     function sendCollateral(address _collateral, address _account, uint _amount) external override {
  172:         _requireValidCollateralAddress(_collateral);
  173:         _requireCallerIsBOorTroveMorSP();
  174:         _rebalance(_collateral, _amount);
  175:         collAmount[_collateral] = collAmount[_collateral].sub(_amount);
  176:         emit ActivePoolCollateralBalanceUpdated(_collateral, collAmount[_collateral]);
- 177:         emit CollateralSent(_collateral, _account, _amount);
+ 177:         emit CollateralSent(msg.sender_collateral, _account, _amount);


add msg.sender parameter in event-emit

[L-16] Lack of Input Validation

For defence-in-depth purpose, it is recommended to perform additional validation against the amount that the user is attempting to deposit, mint, withdraw and redeem to ensure that the submitted amount is valid.

OpenZeppelinTokenizedVault.sol#L9


Ethos-Vault/contracts/ReaperVaultV2.sol:
  318      // shares to {_receiver}. Returns the number of shares that were minted.
  319:     function _deposit(uint256 _amount, address _receiver) internal nonReentrant returns (uint256 shares) {
  320:         _atLeastRole(DEPOSITOR);
  321:         require(!emergencyShutdown, "Cannot deposit during emergency shutdown");
  322:         require(_amount != 0, "Invalid amount");
+                 require(shares <= maxMint(receiver), "mint more than max");
  323:         uint256 pool = balance();
  324:         require(pool + _amount <= tvlCap, "Vault is full");
  325: 
  326:         uint256 freeFunds = _freeFunds();
  327:         uint256 balBefore = token.balanceOf(address(this));
  328:         token.safeTransferFrom(msg.sender, address(this), _amount);
  329:         uint256 balAfter = token.balanceOf(address(this));
  330:         _amount = balAfter - balBefore;
  331:         if (totalSupply() == 0) {
  332:             shares = _amount;
  333:         } else {
  334:             shares = (_amount * totalSupply()) / freeFunds; // use "freeFunds" instead of "pool"
  335:         }
  336:         _mint(_receiver, shares);
  337:         emit Deposit(msg.sender, _receiver, _amount, shares);
  338:     }



[L-17] Prevent division by 0

On several locations in the code precautions are not being taken for not dividing by 0, this will revert the code. These functions can be calledd with 0 value in the input, this value is not checked for being bigger than 0, that means in some scenarios this can potentially trigger a division by zero.


Ethos-Vault/contracts/ReaperVaultV2.sol:
  358      // and return corresponding assets to {_receiver}. Returns the number of assets that were returned.
  359:     function _withdraw(
  360:         uint256 _shares,
  361:         address _receiver,
  362:         address _owner
  363:     ) internal nonReentrant returns (uint256 value) {
  364:         require(_shares != 0, "Invalid amount");
- 365:         value = (_freeFunds() * _shares) / totalSupply();
+	  if (totalSupply() == 0) {
+  	      revert Unauthorized("total supply is zero");
+	  } else {
+  	       value = (_freeFunds * _shares) / totalSupply();
+	 }

  366:         _burn(_owner, _shares);

[L-18] Project has NPM Dependency which uses a vulnerable version : @openzeppelin

Ethos-Vault/package.json:
  39    },
  40:   "dependencies": {
  41:     "@openzeppelin/contracts": "^4.7.3",
  42:     "@openzeppelin/contracts-upgradeable": "^4.7.3",
 

Proof Of Concept

https://security.snyk.io/package/npm/@openzeppelin%2Fcontracts/

Upgrade OZ to version 4.8.0 or higher

[L-19] Keccak Constant values should used to immutable rather than constant

There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts.

While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use the right tool for the task at hand.


8 results - 2 files

Ethos-Vault/contracts/ReaperVaultV2.sol:
  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:
  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");

[L-20] Due to the novelty of the ERC4626 standard, it is safer to use as upgradeable

ERC-4626 is a standard to optimize and unify the technical parameters of yield-bearing vaults. It provides a standard API for tokenized yield-bearing vaults that represent shares of a single underlying ERC-20 token. ERC-4626 also outlines an optional extension for tokenized vaults utilizing ERC-20, offering basic functionality for depositing, withdrawing tokens and reading balances.

ERC4626 Tokenized Vauldt was created 2021-12-22 so this standart is safer to use as upgrade

[L-21] In the setAddresses function, there is no return of incorrect address identification

In case of incorrect address definition in the setAddresses function, there is no way to fix it because of the owner = address(0) at the end of the function

It is recommended to fix the architecture 1- Address definitions can be defined in the constructor 2- Because of owner = address(0) at the end of the function, there is no way to fix it, so the owner's authority can be maintained.

Ethos-Core/contracts/TroveManager.sol:
  231  
  232:     function setAddresses(
  233:         address _borrowerOperationsAddress,
  234:         address _collateralConfigAddress,
  235:         address _activePoolAddress,
  236:         address _defaultPoolAddress,
  237:         address _stabilityPoolAddress,
  238:         address _gasPoolAddress,
  239:         address _collSurplusPoolAddress,
  240:         address _priceFeedAddress,
  241:         address _lusdTokenAddress,
  242:         address _sortedTrovesAddress,
  243:         address _lqtyTokenAddress,
  244:         address _lqtyStakingAddress,
  245:         address _redemptionHelperAddress
  246:     )
  247:         external
  248:         override
  249:     {
  250:         require(msg.sender == owner);
  251: 
  252:         checkContract(_borrowerOperationsAddress);
  253:         checkContract(_collateralConfigAddress);
  254:         checkContract(_activePoolAddress);
  255:         checkContract(_defaultPoolAddress);
  256:         checkContract(_stabilityPoolAddress);
  257:         checkContract(_gasPoolAddress);
  258:         checkContract(_collSurplusPoolAddress);
  259:         checkContract(_priceFeedAddress);
  260:         checkContract(_lusdTokenAddress);
  261:         checkContract(_sortedTrovesAddress);
  262:         checkContract(_lqtyTokenAddress);
  263:         checkContract(_lqtyStakingAddress);
  264:         checkContract(_redemptionHelperAddress);
  265: 
  266:         borrowerOperationsAddress = _borrowerOperationsAddress;
  267:         collateralConfig = ICollateralConfig(_collateralConfigAddress);
  268:         activePool = IActivePool(_activePoolAddress);
  269:         defaultPool = IDefaultPool(_defaultPoolAddress);
  270:         stabilityPool = IStabilityPool(_stabilityPoolAddress);
  271:         gasPoolAddress = _gasPoolAddress;
  272:         collSurplusPool = ICollSurplusPool(_collSurplusPoolAddress);
  273:         priceFeed = IPriceFeed(_priceFeedAddress);
  274:         lusdToken = ILUSDToken(_lusdTokenAddress);
  275:         sortedTroves = ISortedTroves(_sortedTrovesAddress);
  276:         lqtyToken = IERC20(_lqtyTokenAddress);
  277:         lqtyStaking = ILQTYStaking(_lqtyStakingAddress);
  278:         redemptionHelper = IRedemptionHelper(_redemptionHelperAddress);
  279: 
  280:         emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress);
  281:         emit CollateralConfigAddressChanged(_collateralConfigAddress);
  282:         emit ActivePoolAddressChanged(_activePoolAddress);
  283:         emit DefaultPoolAddressChanged(_defaultPoolAddress);
  284:         emit StabilityPoolAddressChanged(_stabilityPoolAddress);
  285:         emit GasPoolAddressChanged(_gasPoolAddress);
  286:         emit CollSurplusPoolAddressChanged(_collSurplusPoolAddress);
  287:         emit PriceFeedAddressChanged(_priceFeedAddress);
  288:         emit LUSDTokenAddressChanged(_lusdTokenAddress);
  289:         emit SortedTrovesAddressChanged(_sortedTrovesAddress);
  290:         emit LQTYTokenAddressChanged(_lqtyTokenAddress);
  291:         emit LQTYStakingAddressChanged(_lqtyStakingAddress);
  292:         emit RedemptionHelperAddressChanged(_redemptionHelperAddress);
  293: 
  294:         owner = address(0);
  295:     }
  296  

[N-01] Omissions in Events

Throughout the codebase, events are generally emitted when sensitive changes are made to the contracts. However, some events are missing important parameters

The events should include the new value and old value where possible:


Ethos-Core/contracts/LQTY/CommunityIssuance.sol:
  60  
  61:     function setAddresses
  62:     (
  63:         address _oathTokenAddress, 
  64:         address _stabilityPoolAddress
  65:     ) 
  66:         external 
  67:         onlyOwner 
  68:         override 
  69:     {
  70:         require(!initialized, "issuance has been initialized");
  71:         checkContract(_oathTokenAddress);
  72:         checkContract(_stabilityPoolAddress);
  73: 
  74:         OathToken = IERC20(_oathTokenAddress);
  75:         stabilityPoolAddress = _stabilityPoolAddress;
  76: 
  77:         initialized = true;
  78: 
- 79:         emit OathTokenAddressSet(_oathTokenAddress);
- 80:         emit StabilityPoolAddressSet(_stabilityPoolAddress);
+ 79:         emit OathTokenAddressSet(old_oathTokenAddress , _oathTokenAddress);
+ 80:         emit StabilityPoolAddressSet(old_ stabilityPoolAddress, _stabilityPoolAddress);

  81:     }


Ethos-Vault/contracts/ReaperVaultV2.sol:
  257       */
  258:     function setWithdrawalQueue(address[] calldata _withdrawalQueue) external {
  259:         _atLeastRole(ADMIN);
  260:         uint256 queueLength = _withdrawalQueue.length;
  261:         require(queueLength != 0, "Queue must not be empty");
  262: 
  263:         delete withdrawalQueue;
  264:         for (uint256 i = 0; i < queueLength; i = i.uncheckedInc()) {
  265:             address strategy = _withdrawalQueue[i];
  266:             StrategyParams storage params = strategies[strategy];
  267:             require(params.activation != 0, "Invalid strategy address");
  268:             withdrawalQueue.push(strategy);
  269:         }
- 270:         emit UpdateWithdrawalQueue(_withdrawalQueue);
+ 270:         emit UpdateWithdrawalQueue(oldWithdrawalQueue, _withdrawalQueue);

  271:     }


Ethos-Core/contracts/ActivePool.sol:
  137  
  138:     function setYieldClaimThreshold(address _collateral, uint256 _threshold) external onlyOwner {
  139:         _requireValidCollateralAddress(_collateral);
  140:         yieldClaimThreshold[_collateral] = _threshold;
- 141:         emit YieldClaimThresholdUpdated(_collateral, _threshold);
+ 141:         emit YieldClaimThresholdUpdated(oldCollateral, _collateral, _threshold);
 142      }

[N-02] NatSpec comments should be increased in contracts

Context: All project have 226 functions (without interfaces) but they have only 95 NatSpec’s

Description: It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability. https://docs.soliditylang.org/en/v0.8.15/natspec-format.html

Recommendation: NatSpec comments should be increased in contracts

[N-03] Function writing that does not comply with the Solidity Style Guide

Context: All Contracts

Description: Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.

https://docs.soliditylang.org/en/v0.8.17/style-guide.html

Functions should be grouped according to their visibility and ordered:

constructor receive function (if exists) fallback function (if exists) external public internal private within a grouping, place the view and pure functions last

[N-04] Include return parameters in NatSpec comments

Context: All project have 118 returns variable (without interfaces) but they have only 20 @return NatSpec’s

Description: It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.

https://docs.soliditylang.org/en/v0.8.15/natspec-format.html

Recommendation: Include return parameters in NatSpec comments

Recommendation Code Style: (from Uniswap3)

    /// @notice Adds liquidity for the given recipient/tickLower/tickUpper position
    /// @dev The caller of this method receives a callback in the form of IUniswapV3MintCallback#uniswapV3MintCallback
    /// in which they must pay any token0 or token1 owed for the liquidity. The amount of token0/token1 due depends
    /// on tickLower, tickUpper, the amount of liquidity, and the current price.
    /// @param recipient The address for which the liquidity will be created
    /// @param tickLower The lower tick of the position in which to add liquidity
    /// @param tickUpper The upper tick of the position in which to add liquidity
    /// @param amount The amount of liquidity to mint
    /// @param data Any data that should be passed through to the callback
    /// @return amount0 The amount of token0 that was paid to mint the given amount of liquidity. Matches the value in the callback
    /// @return amount1 The amount of token1 that was paid to mint the given amount of liquidity. Matches the value in the callback
    function mint(
        address recipient,
        int24 tickLower,
        int24 tickUpper,
        uint128 amount,
        bytes calldata data
    ) external returns (uint256 amount0, uint256 amount1);

[N-05] Tokens accidentally sent to the contract cannot be recovered

Using balanceOf for reward distribution means accidentally sent reward tokens to the contract will be distributed to all users who are eligible for rewards.

Examine and consider the trade-offs of using internal accounting instead of balanceOf(address(this) . Implement functions that allow to withdraw the accidentally sent tokens from the contracts.

Contex Ethos-Vault/contracts/ReaperVaultERC4626.sol

It can't be recovered if the tokens accidentally arrive at the contract address, which has happened to many popular projects, so I recommend adding a recovery code to your critical contracts.

Add this code:

 /**
  * @notice Sends ERC20 tokens trapped in contract to external address
  * @dev Onlyowner is allowed to make this function call
  * @param account is the receiving address
  * @param externalToken is the token being sent
  * @param amount is the quantity being sent
  * @return boolean value indicating whether the operation succeeded.
  *
 */
  function rescueERC20(address account, address externalToken, uint256 amount) public onlyOwner returns (bool) {
    IERC20(externalToken).transfer(account, amount);
    return true;
  }
}

[N-06] Repeated validation logic can be converted into a function modifier

If a query or logic is repeated over many lines, using a modifier improves the readability and reusability of the code


13 results - 5 files

Ethos-Core/contracts/ActivePool.sol:
  92          checkContract(_collSurplusPoolAddress);
  93:         require(_treasuryAddress != address(0), "Treasury cannot be 0 address");

Ethos-Core/contracts/LUSDToken.sol:
  311      function _transfer(address sender, address recipient, uint256 amount) internal {
  312:         assert(sender != address(0));
  313:         assert(recipient != address(0));
  314  

  320      function _mint(address account, uint256 amount) internal {
  321:         assert(account != address(0));
  322  

  328      function _burn(address account, uint256 amount) internal {
  329:         assert(account != address(0));
  330          

  336      function _approve(address owner, address spender, uint256 amount) internal {
  337:         assert(owner != address(0));
  338:         assert(spender != address(0));
  339  

  347          require(
  348:             _recipient != address(0) && 

Ethos-Core/contracts/TroveManager.sol:
  293  
  294:         owner = address(0);
  295      }

Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  167:             require(step[0] != address(0));
  168:             require(step[1] != address(0));


Ethos-Vault/contracts/ReaperVaultV2.sol:

  151:         require(_strategy != address(0), "Invalid strategy address");
  629:         require(newTreasury != address(0), "Invalid address");

[N-07] Use a more recent version of Solidity

Context:

14 results - 12 files

Ethos-Core/contracts/ActivePool.sol:
  2  
  3: pragma solidity 0.6.11;
  4  

Ethos-Core/contracts/BorrowerOperations.sol:
  2  
  3: pragma solidity 0.6.11;
  4  

Ethos-Core/contracts/CollateralConfig.sol:
  2  
  3: pragma solidity 0.6.11;
  4  

Ethos-Core/contracts/LUSDToken.sol:
  2  
  3: pragma solidity 0.6.11;
  4  

Ethos-Core/contracts/StabilityPool.sol:
   2  
   3: pragma solidity 0.6.11;
   4  

Ethos-Core/contracts/TroveManager.sol:
  2  
  3: pragma solidity 0.6.11;
  4  

Ethos-Core/contracts/LQTY/CommunityIssuance.sol:
  2  
  3: pragma solidity 0.6.11;
  4  

Ethos-Core/contracts/LQTY/LQTYStaking.sol:
  2  
  3: pragma solidity 0.6.11;
  4  

Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  2  
  3: pragma solidity ^0.8.0;
  4  

Ethos-Vault/contracts/ReaperVaultERC4626.sol:
  2  
  3: pragma solidity ^0.8.0;
  4  

Ethos-Vault/contracts/ReaperVaultV2.sol:
  2  
  3: pragma solidity ^0.8.0;
  4  

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  2  
  3: pragma solidity ^0.8.0;
  4 

Description: For security, it is best practice to use the latest Solidity version. For the security fix list in the versions; https://github.com/ethereum/solidity/blob/develop/Changelog.md

Recommendation: Old version of Solidity is used (0.8.0 / 0.6.11), newer version can be used (0.8.17)

[N-08] For functions, follow Solidity standard naming conventions (internal function style rule)

Context:


4 results - 2 files

Ethos-Core/contracts/ActivePool.sol:
  41:     mapping (address => uint256) internal collAmount;  // collateral => amount tracker
  42:     mapping (address => uint256) internal LUSDDebt;  // collateral => corresponding debt tracker

Ethos-Core/contracts/StabilityPool.sol:
  167:     mapping (address => uint256) internal collAmounts;  // deposited collateral tracker
  170:     uint256 internal totalLUSDDeposits;

Description: The above codes don't follow Solidity's standard naming convention,

internal and private functions : the mixedCase format starting with an underscore (_mixedCase starting with an underscore)

[N-09] Floating pragma

Description: Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively. https://swcregistry.io/docs/SWC-103

Floating Pragma List:

4 results 

Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  2  
  3: pragma solidity ^0.8.0;
  4  

Ethos-Vault/contracts/ReaperVaultERC4626.sol:
  2  
  3: pragma solidity ^0.8.0;
  4  

Ethos-Vault/contracts/ReaperVaultV2.sol:
  2  
  3: pragma solidity ^0.8.0;
  4  

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  2  
  3: pragma solidity ^0.8.0;
  4 

Recommendation: Lock the pragma version and also consider known bugs (https://github.com/ethereum/solidity/releases) for the compiler version that is chosen.

[N-10] Use descriptive names for Contracts and Libraries

This codebase will be difficult to navigate, as there are no descriptive naming conventions that specify which files should contain meaningful logic.

Prefixes should be added like this by filing:

Interface I_ absctract contracts Abs_ Libraries Lib_

We recommend that you implement this or a similar agreement.

[N-11] Use SMTChecker

The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification.

https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19

[N-12]  Add NatSpec Mapping comment

Description: Add NatSpec comments describing mapping keys and values

Context:

13 results - 4 files

Ethos-Core/contracts/CollateralConfig.sol:
  35:     mapping (address => Config) public collateralConfig; 

Ethos-Core/contracts/LUSDToken.sol:
  54:     mapping (address => uint256) private _nonces;
  57:     mapping (address => uint256) private _balances;
  58:     mapping (address => mapping (address => uint256)) private _allowances;  
  62:     mapping (address => bool) public troveManagers;
  63:     mapping (address => bool) public stabilityPools;
  64:     mapping (address => bool) public borrowerOperations;

Ethos-Core/contracts/StabilityPool.sol:
  167:     mapping (address => uint256) internal collAmounts;  // deposited collateral tracker
  179:         mapping (address => uint) S;
  223:     mapping (uint128 => mapping(uint128 => uint)) public epochToScaleToG;
  228:     mapping (address => uint) public lastCollateralError_Offset;

Ethos-Core/contracts/LQTY/LQTYStaking.sol:
  35:         mapping (address => uint) F_Collateral_Snapshot;

Recommendation:


 /// @dev    address(user) -> address(ERC0 Contract Address) -> uint256(allowance amount from user)
    mapping(address => mapping(address => uint256)) public allowance;

[N-13] Showing the actual values of numbers in NatSpec comments makes checking and reading code easier


2 results - 2 files

Ethos-Core/contracts/LQTY/CommunityIssuance.sol:
  57      constructor() public {
- 58:         distributionPeriod = 14 days;
+ 58:         distributionPeriod = 14 days; // 1_209_600 (14*24*60*60)

  59      }

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
-  24      uint256 public constant UPGRADE_TIMELOCK = 48 hours; // minimum 48 hours for RF
+            //minimum 48 hours for RF
+ 24      uint256 public constant UPGRADE_TIMELOCK = 48 hours; // 172_800  (48*60*60)

- 25:     uint256 public constant FUTURE_NEXT_PROPOSAL_TIME = 365 days * 100; //  3_153_600_000 (365*24*60*60*100)

[N-14] Lines are too long

TroveManager.sol#L102

TroveManager.sol#L97

TroveManager.sol#L200-L202

TroveManager.sol#L351

TroveManager.sol#L393

TroveManager.sol#L407

TroveManager.sol#L428

TroveManager.sol#L572

TroveManager.sol#L775

TroveManager.sol#L926

Description: It is generally recommended that lines in the source code should not exceed 80-120 characters. Today's screens are much larger, so in some cases it makes sense to expand that.The lines above should be split when they reach that length, as the files will most likely be on GitHub and GitHub always uses a scrollbar when the length is more than 164 characters.

(why-is-80-characters-the-standard-limit-for-code-width)[https://softwareengineering.stackexchange.com/questions/148677/why-is-80-characters-the-standard-limit-for-code-width]

Recommendation: Multiline output parameters and return statements should follow the same style recommended for wrapping long lines found in the Maximum Line Length section.

https://docs.soliditylang.org/en/v0.8.17/style-guide.html#introduction

thisFunctionCallIsReallyLong(
    longArgument1,
    longArgument2,
    longArgument3
);

[N-15] Constants on the left are better

If you use the constant first you support structures that veil programming errors. And one should only produce code either to add functionality, fix an programming error or trying to support structures to avoid programming errors (like design patterns).

https://www.moserware.com/2008/01/constants-on-left-are-better-but-this.html

36 results - 6 files

Ethos-Core/contracts/BorrowerOperations.sol:
  534:         require(_collTopUp == 0 || _collWithdrawal == 0, "BorrowerOperations: Cannot withdraw and add coll");
  568:         require(_collWithdrawal == 0, "BorrowerOps: Collateral withdrawal not permitted Recovery Mode");

Ethos-Core/contracts/StabilityPool.sol:
  428:         if (totalLUSD == 0 || _LQTYIssuance == 0) {return;}
  469:         if (totalLUSD == 0 || _debtToOffset == 0) { return; }
  489:         if (totalLUSD == 0) { return; }
  575:         if (newProductFactor == 0) {
  685:         if (initialDeposit == 0) {return 0;}
  720:         if (initialDeposit == 0) { return 0; }
  751:         if (scaleDiff == 0) {
  784:         if (_amount == 0) {return;}
  795:         if (LUSDWithdrawal == 0) {return;}
  809:         if (_newValue == 0) {

Ethos-Core/contracts/TroveManager.sol:
   616:                 if (vars.ICR >= vars.collMCR && vars.remainingLUSDInStabPool == 0) { break; }
   817:                 if (vars.ICR >= vars.collMCR && vars.remainingLUSDInStabPool == 0) { continue; }
  1127:         if ( rewardPerUnitStaked == 0 || Troves[_borrower][_collateral].status != Status.active) { return 0; }
  1142:         if ( rewardPerUnitStaked == 0 || Troves[_borrower][_collateral].status != Status.active) { return 0; }
  1215:         if (totalCollateralSnapshot[_collateral] == 0) {
  1238:         if (_debt == 0) { return; }

Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  121:             if (amount == 0) {

Ethos-Vault/contracts/ReaperVaultERC4626.sol:
   52:         if (totalSupply() == 0 || _freeFunds() == 0) return assets;
   67:         if (totalSupply() == 0) return shares;
  140:         if (totalSupply() == 0) return shares;
  184:         if (totalSupply() == 0 || _freeFunds() == 0) return 0;

Ethos-Vault/contracts/ReaperVaultV2.sol:
  152:         require(strategies[_strategy].activation == 0, "Strategy already added");
  210:         if (strategies[_strategy].allocBPS == 0) {
  227:         if (totalAllocBPS == 0 || emergencyShutdown) {
  296:         return totalSupply() == 0 ? 10**decimals() : (_freeFunds() * 10**decimals()) / totalSupply();
  331:         if (totalSupply() == 0) {
  380:                 if (strategyBal == 0) {
  466:             uint256 shares = supply == 0 ? performanceFee : (performanceFee * supply) / _freeFunds();
  555:         if (strategy.allocBPS == 0 || emergencyShutdown) {

[N-16] constants should be defined rather than using magic numbers

A magic number is a numeric literal that is used in the code without any explanation of its meaning. The use of magic numbers makes programs less readable and hence more difficult to maintain and update. Even assembly can benefit from using readable constants instead of hex/numeric literals

3 result 

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%

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  25:     uint256 public constant FUTURE_NEXT_PROPOSAL_TIME = 365 days * 100;

[N-17] Missing timelock for critical parameter change


1 result - 1 file

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  155       */
  156:     function setEmergencyExit() external {
  157:         _atLeastRole(GUARDIAN);
  158:         emergencyExit = true;
  159:         IVault(vault).revokeStrategy(address(this));
  160:     }

[N-18] Use the delete keyword instead of assigning a value of 0

Using the 'delete' keyword instead of assigning a '0' value is a detailed optimization that increases code readability and audit quality, and clearly indicates the intent.

Other hand, if use delete instead 0 value assign , it will be gas saved

17 results - 4 files

Ethos-Core/contracts/ActivePool.sol:
  253:             vars.profit = 0;

Ethos-Core/contracts/StabilityPool.sol:
  529:             lastLUSDLossError_Offset = 0;
  578:             currentScale = 0;
  756:             compoundedDeposit = 0;

Ethos-Core/contracts/TroveManager.sol:
   387:             singleLiquidation.debtToOffset = 0;
   388:             singleLiquidation.collToSendToSP = 0;
   468:             debtToOffset = 0;
   469:             collToSendToSP = 0;
   505:         singleLiquidation.debtToRedistribute = 0;
   506:         singleLiquidation.collToRedistribute = 0;
  1192:         Troves[_borrower][_collateral].stake = 0;
  1285:         Troves[_borrower][_collateral].coll = 0;
  1286:         Troves[_borrower][_collateral].debt = 0;
  1288:         rewardSnapshots[_borrower][_collateral].collAmount = 0;
  1289:         rewardSnapshots[_borrower][_collateral].LUSDDebt = 0;

Ethos-Vault/contracts/ReaperVaultV2.sol:
  215:         strategies[_strategy].allocBPS = 0;
  537:             lockedProfit = 0;

[N-19] Not using the named return variables anywhere in the function is confusing


2 results

Ethos-Core/contracts/StabilityPool.sol:
  626:     function getDepositorCollateralGain(address _depositor) public view override returns (address[] memory assets, uint[] memory amounts) {
  627:         uint initialDeposit = deposits[_depositor].initialValue;
  628: 
  629:         if (initialDeposit != 0) { 
  630:             Snapshots storage snapshots = depositSnapshots[_depositor];
  631: 
  632:             return _getCollateralGainFromSnapshots(initialDeposit, snapshots);
  633:         }

Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
  103: 
  104:     function _liquidateAllPositions() internal override returns (uint256 amountFreed) {
  105:         _withdrawUnderlying(balanceOfPool());
  106:         return balanceOfWant();
  107:     }

Recommendation: Consider adopting a consistent approach to return values throughout the codebase by removing all named return variables, explicitly declaring them as local variables, and adding the necessary return statements where appropriate. This would improve both the explicitness and readability of the code, and it may also help reduce regressions during future code refactors.

[N-20] Function Calls in Loop Could Lead to Denial of Service due to Array length not being checked


Ethos-Core/contracts/TroveManager.sol:
  715:     function batchLiquidateTroves(address _collateral, address[] memory _troveArray) public override {
+                require(memory _troveArray.length< max troveArrayLengt, "max length");

  716:         require(_troveArray.length != 0);
  717: 
  718:         IActivePool activePoolCached = activePool;
  719:         IDefaultPool defaultPoolCached = defaultPool;
  720:         IStabilityPool stabilityPoolCached = stabilityPool;
  721: 
  722:         LocalVariables_OuterLiquidationFunction memory vars;
  723:         LiquidationTotals memory totals;

Recommendation: Function calls made in unbounded loop are error-prone with potential resource exhaustion as it can trap the contract due to gas limitations or failed transactions. Consider bounding the loop length if the array is expected to be growing and/or handling a huge list of elements to avoid unnecessary gas wastage and denial of service.

[N-21] According to the syntax rules, use mapping () instead of mapping() using spaces as keyword

2 results - 2 files

Ethos-Core/contracts/LQTY/LQTYStaking.sol:
- 25:     mapping( address => uint) public stakes;
+ 25:     mapping ( address => uint) public stakes;

Ethos-Vault/contracts/ReaperVaultV2.sol:
- 35:     mapping(address => StrategyParams) public strategies;
+ 35:     mapping (address => StrategyParams) public strategies;

[N-22] For modern and more readable code; update import usages

Context:


110 results - 12 files

Ethos-Core/contracts/ActivePool.sol:
   4  
   5: import './Interfaces/IActivePool.sol';
   6: import "./Interfaces/ICollateralConfig.sol";
   7: import './Interfaces/IDefaultPool.sol';
   8: import "./Interfaces/ICollSurplusPool.sol";
   9: import "./Interfaces/ILQTYStaking.sol";
  10: import "./Interfaces/IStabilityPool.sol";
  11: import "./Interfaces/ITroveManager.sol";
  12: import "./Dependencies/SafeMath.sol";
  13: import "./Dependencies/Ownable.sol";
  14: import "./Dependencies/CheckContract.sol";
  15: import "./Dependencies/console.sol";
  16: import "./Dependencies/SafeERC20.sol";
  17: import "./Dependencies/IERC4626.sol";
  18  

Ethos-Core/contracts/BorrowerOperations.sol:
   4  
   5: import "./Interfaces/IBorrowerOperations.sol";
   6: import "./Interfaces/ICollateralConfig.sol";
   7: import "./Interfaces/ITroveManager.sol";
   8: import "./Interfaces/ILUSDToken.sol";
   9: import "./Interfaces/ICollSurplusPool.sol";
  10: import "./Interfaces/ISortedTroves.sol";
  11: import "./Interfaces/ILQTYStaking.sol";
  12: import "./Dependencies/LiquityBase.sol";
  13: import "./Dependencies/Ownable.sol";
  14: import "./Dependencies/CheckContract.sol";
  15: import "./Dependencies/console.sol";
  16: import "./Dependencies/SafeERC20.sol";
  17  

Ethos-Core/contracts/CollateralConfig.sol:
  4  
  5: import "./Dependencies/CheckContract.sol";
  6: import "./Dependencies/Ownable.sol";
  7: import "./Dependencies/SafeERC20.sol";
  8: import "./Interfaces/ICollateralConfig.sol";
  9  

Ethos-Core/contracts/LUSDToken.sol:
  4  
  5: import "./Interfaces/ILUSDToken.sol";
  6: import "./Interfaces/ITroveManager.sol";
  7: import "./Dependencies/SafeMath.sol";
  8: import "./Dependencies/CheckContract.sol";
  9: import "./Dependencies/console.sol";
  

Ethos-Core/contracts/StabilityPool.sol:
   4  
   5: import './Interfaces/IBorrowerOperations.sol';
   6: import "./Interfaces/ICollateralConfig.sol";
   7: import './Interfaces/IStabilityPool.sol';
   8: import './Interfaces/IBorrowerOperations.sol';
   9: import './Interfaces/ITroveManager.sol';
  10: import './Interfaces/ILUSDToken.sol';
  11: import './Interfaces/ISortedTroves.sol';
  12: import "./Interfaces/ICommunityIssuance.sol";
  13: import "./Dependencies/LiquityBase.sol";
  14: import "./Dependencies/SafeMath.sol";
  15: import "./Dependencies/LiquitySafeMath128.sol";
  16: import "./Dependencies/Ownable.sol";
  17: import "./Dependencies/CheckContract.sol";
  18: import "./Dependencies/console.sol";
  19: import "./Dependencies/SafeERC20.sol";
  20  

Ethos-Core/contracts/TroveManager.sol:
   4  
   5: import "./Interfaces/ICollateralConfig.sol";
   6: import "./Interfaces/ITroveManager.sol";
   7: import "./Interfaces/IStabilityPool.sol";
   8: import "./Interfaces/ICollSurplusPool.sol";
   9: import "./Interfaces/ILUSDToken.sol";
  10: import "./Interfaces/ISortedTroves.sol";
  11: import "./Interfaces/ILQTYStaking.sol";
  12: import "./Interfaces/IRedemptionHelper.sol";
  13: import "./Dependencies/LiquityBase.sol";
  14: // import "./Dependencies/Ownable.sol";
  15: import "./Dependencies/CheckContract.sol";
  16: import "./Dependencies/IERC20.sol";
  17  

Ethos-Core/contracts/LQTY/CommunityIssuance.sol:
   4  
   5: import "../Dependencies/IERC20.sol";
   6: import "../Interfaces/ICommunityIssuance.sol";
   7: import "../Dependencies/BaseMath.sol";
   8: import "../Dependencies/LiquityMath.sol";
   9: import "../Dependencies/Ownable.sol";
  10: import "../Dependencies/CheckContract.sol";
  11: import "../Dependencies/SafeMath.sol";
  12  

Ethos-Core/contracts/LQTY/LQTYStaking.sol:
   4  
   5: import "../Dependencies/BaseMath.sol";
   6: import "../Dependencies/SafeMath.sol";
   7: import "../Dependencies/Ownable.sol";
   8: import "../Dependencies/CheckContract.sol";
   9: import "../Dependencies/console.sol";
  10: import "../Dependencies/IERC20.sol";
  11: import "../Interfaces/ICollateralConfig.sol";
  12: import "../Interfaces/ILQTYStaking.sol";
  13: import "../Interfaces/ITroveManager.sol";
  14: import "../Dependencies/LiquityMath.sol";
  15: import "../Interfaces/ILUSDToken.sol";
  16: import "../Dependencies/SafeERC20.sol";
  17  

Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol:
   4  
   5: import "./abstract/ReaperBaseStrategyv4.sol";
   6: import "./interfaces/IAToken.sol";
   7: import "./interfaces/IAaveProtocolDataProvider.sol";
   8: import "./interfaces/ILendingPool.sol";
   9: import "./interfaces/ILendingPoolAddressesProvider.sol";
  10: import "./interfaces/IRewardsController.sol";
  11: import "./libraries/ReaperMathUtils.sol";
  12: import "./mixins/VeloSolidMixin.sol";
  13: import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
  14: import "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol";
  15  

Ethos-Vault/contracts/ReaperVaultERC4626.sol:
  4  
  5: import "./ReaperVaultV2.sol";
  6: import "./interfaces/IERC4626Functions.sol";
  7  

Ethos-Vault/contracts/ReaperVaultV2.sol:
   4  
   5: import "./interfaces/IERC4626Events.sol";
   6: import "./interfaces/IStrategy.sol";
   7: import "./libraries/ReaperMathUtils.sol";
   8: import "./mixins/ReaperAccessControl.sol";
   9: import "@openzeppelin/contracts/access/AccessControlEnumerable.sol";
  10: import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
  11: import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
  12: import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
  13: import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
  14: import "@openzeppelin/contracts/utils/math/Math.sol";
  15  

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
   4  
   5: import "../interfaces/IStrategy.sol";
   6: import "../interfaces/IVault.sol";
   7: import "../libraries/ReaperMathUtils.sol";
   8: import "../mixins/ReaperAccessControl.sol";
   9: import "@openzeppelin/contracts-upgradeable/access/AccessControlEnumerableUpgradeable.sol";
  10: import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
  11: import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
  12: import "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
  13 

Description: Solidity code is also cleaner in another way that might not be noticeable: the struct Point. We were importing it previously with global import but not using it. The Point struct polluted the source code with an unnecessary object we were not using because we did not need it. This was breaking the rule of modularity and modular programming: only import what you need Specific imports with curly braces allow us to apply this rule better.

Recommendation: import {contract1 , contract2} from "filename.sol";

A good example from the ArtGobblers project;

import {Owned} from "solmate/auth/Owned.sol";
import {ERC721} from "solmate/tokens/ERC721.sol";
import {LibString} from "solmate/utils/LibString.sol";
import {MerkleProofLib} from "solmate/utils/MerkleProofLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {ERC1155, ERC1155TokenReceiver} from "solmate/tokens/ERC1155.sol";
import {toWadUnsafe, toDaysWadUnsafe} from "solmate/utils/SignedWadMath.sol";

[N-23] Use require instead of assert


20 results - 4 files

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

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 ruqire(); 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".

[N-24] Implement some type of version counter that will be incremented automatically for contract upgrades

As part of the upgradeability of Proxies , the contract can be upgraded multiple times, where it is a systematic approach to record the version of each upgrade.

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  189:     function _authorizeUpgrade(address) internal override {
  190:         _atLeastRole(DEFAULT_ADMIN_ROLE);
  191:         require(
  192:             upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp,
  193:             "Upgrade cooldown not initiated or still ongoing"
  194:         );
  195:         clearUpgradeCooldown();
  196:     }

I suggest implementing some kind of version counter that will be incremented automatically when you upgrade the contract.

Recommendation:


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

+	uint256 public authorizeUpgradeCounter;

  189:     function _authorizeUpgrade(address) internal override {
  190:         _atLeastRole(DEFAULT_ADMIN_ROLE);
  191:         require(
  192:             upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp,
  193:             "Upgrade cooldown not initiated or still ongoing"
  194:         );
+	authorizeUpgradeCounter+=1;
  195:         clearUpgradeCooldown();
  196:     }

[N-25] Use a single file for all system-wide constants

There are many addresses and constants used in the system. It is recommended to put the most used ones in one file (for example constants.sol, use inheritance to access these values)

This will help with readability and easier maintenance for future changes. This also helps with any issues, as some of these hard-coded values are admin addresses.

constants.sol Use and import this file in contracts that require access to these values. This is just a suggestion, in some use cases this may result in higher gas usage in the distribution.


18 results - 4 files

Ethos-Core/contracts/StabilityPool.sol:
  197:     uint public constant SCALE_FACTOR = 1e9; 

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;
  42      uint256 public tvlCap;
  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");
  77  

Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  22  
  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");

[N-26] Assembly Codes Specific – Should Have Comments

Since this is a low level language that is more difficult to parse by readers, include extensive documentation, comments on the rationale behind its use, clearly explaining what each assembly instruction does

This will make it easier for users to trust the code, for reviewers to validate the code, and for developers to build on or update the code.

Note that using Aseembly removes several important security features of Solidity, which can make the code more insecure and more error-prone.


Ethos-Core/contracts/LUSDToken.sol:
  297  
  298:     function _chainID() private pure returns (uint256 chainID) {
  299:         assembly {
  300:             chainID := chainid()
  301:         }
  302:     }

[N-27] Highest risk must be specified in NatSpec comments and documentation


Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol:
  189:     function _authorizeUpgrade(address) internal override {
  190:         _atLeastRole(DEFAULT_ADMIN_ROLE);
  191:         require(
  192:             upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp,
  193:             "Upgrade cooldown not initiated or still ongoing"
  194:         );
  195:         clearUpgradeCooldown();
  196:     }

Description: Although the UUPS model has many advantages and the proposed proxy model is currently the UUPS model, there are some caveats we should be aware of before applying it to a real-world project.

One of the main attention: Since upgrades are done through the implementation contract with the help of the _authorizeUpgrade method, there is a high risk of new implementations such as excluding the _authorizeUpgrade method, which can permanently kill the smart contract's ability to upgrade. Also, this pattern is somewhat complicated to implement compared to other proxy patterns.

Recommendation: Therefore, this highest risk must be found in NatSpec comments and documents.

[N-28] Include proxy contracts to Audit

The Proxy contract could not be seen in the checklist because it is an upgradable contract, only the implementation contracts are visible, I recommend including the Proxy contract in the audit for integrity in the audit.

[S-01] Generate perfect code headers every time

Description: I recommend using header for Solidity code layout and readability

https://github.com/transmissions11/headers

/*//////////////////////////////////////////////////////////////
                           TESTING 123
//////////////////////////////////////////////////////////////*/

#0 - c4-judge

2023-03-09T18:09:25Z

trust1995 marked the issue as grade-a

#1 - trust1995

2023-03-10T17:42:49Z

Excellent report, top 3 QA

#2 - c4-sponsor

2023-03-28T21:14:01Z

0xBebis marked the issue as sponsor confirmed

Gas Optimizations List

NumberOptimization DetailsContext
[G-01]Remove the initializer modifier2
[G-02]Use hardcode address instead address(this)36
[G-03]Structs can be packed into fewer storage slots2
[G-04]Pack state variables4
[G-05]Remove or replace unused state variables1
[G-06]Deposit struct can be removed1
[G-07]Using delete instead of setting mapping/state variable 0 saves gas9
[G-08]Using delete instead of setting address(0) saves gas1
[G-09]Using delete to set bool values to their default values saves gas3
[G-10]Using storage instead of memory for structs/arrays saves gas7
[G-11]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate7
[G-12]Multiple accesses of a mapping/array should use a local variable cache30
[G-13]The result of function calls should be cached rather than re-calling the function4
[G-14]Avoid using state variable in emit1
[G-15]Superfluous event fields1
[G-16]Avoid contract existence checks by using low level calls97
[G-17]bytes constants are more eficient than string constans8
[G-18]Change public function visibility to external3
[G-19]Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead6
[G-20]internal functions only called once can be inlined to save gas45
[G-21]Do not calculate constants variables11
[G-22]Use assembly to write address storage values18
[G-23]Setting the constructor to payable6
[G-24]Add unchecked {} for subtractions where the operands cannot underflow because of a previous require or if statement10
[G-25]++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops13
[G-26]x += y (x -= y) costs more gas than x = x + y (x = x - y) for state variables9
[G-27]Use require instead of assert20
[G-28]Use nested if and, avoid multiple check combinations3
[G-29]Use double require instead of using &&4
[G-30]Sort Solidity operations using short-circuit mode14
[G-31]Optimize names to save gasAll contracts
[G-32]Use a more recent version of solidity12
[G-33]>= costs less gas than >1
[G-34]Upgrade Solidity's optimizer

Total 34 issues

[G-01] Remove the initializer modifier

if we can just ensure that the initialize() function could only be called from within the constructor, we shouldn't need to worry about it getting called again.

2 results - 2 files:

Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:

  67:     ) public initializer {
Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:

  61:     constructor() initializer {}

In the EVM, the constructor's job is actually to return the bytecode that will live at the contract's address. So, while inside a constructor, your address (address(this)) will be the deployment address, but there will be no bytecode at that address! So if we check address(this).code.length before the constructor has finished, even from within a delegatecall, we will get 0. So now let's update our initialize() function to only run if we are inside a constructor:


Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:

- 67:     ) public initializer {
+        require(address(this).code.length == 0, 'not in constructor’);

Now the Proxy contract's constructor can still delegatecall initialize(), but if anyone attempts to call it again (after deployment) through the Proxy instance, or tries to call it directly on the above instance, it will revert because address(this).code.length will be nonzero.

Also, because we no longer need to write to any state to track whether initialize() has been called, we can avoid the 20k storage gas cost. In fact, the cost for checking our own code size is only 2 gas, which means we have a 10,000x gas savings over the standard version. Pretty neat!

[G-02] Use hardcode address instead address(this)

Instead of address(this), it is more gas-efficient to pre-calculate and use the address with a hardcode. Foundry's script.sol and solmate````LibRlp.sol`` contracts can do this.

Reference: https://book.getfoundry.sh/reference/forge-std/compute-create-address https://twitter.com/transmissions11/status/1518507047943245824

36 results - 9 files:

Ethos-Core\contracts\ActivePool.sol:

  210:    IERC20(_collateral).safeTransferFrom(msg.sender, address(this), _amount);

  247:    vars.ownedShares = vars.yieldGenerator.balanceOf(address(this));

  280:    IERC4626(yieldGenerator[_collateral]).deposit(uint256(vars.netAssetMovement), address(this));

  282:    IERC4626(yieldGenerator[_collateral]).withdraw(uint256(-vars.netAssetMovement), address(this), address(this));
 
  288:    vars.profit = IERC20(_collateral).balanceOf(address(this)).add(vars.yieldingAmount).sub(collAmount[_collateral]);

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

Ethos-Core\contracts\BorrowerOperations.sol:

  231:    IERC20(_collateral).safeTransferFrom(msg.sender, address(this), _collAmount);

  497:    IERC20(_collateral).safeTransferFrom(msg.sender, address(this), _collChange);

  530:    require(IERC20(_collateral).allowance(_user, address(this)) >= _collAmount, "BorrowerOperations: Insufficient collateral allowance");

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

Ethos-Core\contracts\LUSDToken.sol:

  305:    return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));

  349:    _recipient != address(this),

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

Ethos-Core\contracts\StabilityPool.sol:

  605:    lusdToken.burn(address(this), _debtToOffset);
  606  
  607:    activePoolCached.sendCollateral(_collateral, address(this), _collToAdd);

  777:    lusdToken.sendToPool(_address, address(this), _amount);

  797:    lusdToken.returnFromPool(address(this), _depositor, LUSDWithdrawal);

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

Ethos-Core\contracts\LQTY\CommunityIssuance.sol:

  103:    OathToken.transferFrom(msg.sender, address(this), amount);

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

Ethos-Core\contracts\LQTY\LQTYStaking.sol:

  128:    lqtyToken.safeTransferFrom(msg.sender, address(this), _LQTYamount);

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

Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:

  120:    uint256 amount = startToken.balanceOf(address(this));

  127:    uint256 allocated = IVault(vault).strategies(address(this)).allocated;

  180:    ILendingPool(lendingPoolAddress).deposit(want, toReinvest, address(this), LENDER_REFERRAL_CODE_NONE);

  200:    ILendingPool(ADDRESSES_PROVIDER.getLendingPool()).withdraw(address(want), _withdrawAmount, address(this));

  227:    return IERC20Upgradeable(want).balanceOf(address(this));

  233:    address(this)

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  153:    require(address(this) == IStrategy(_strategy).vault(), "Strategy's vault does not match");

  246:    available = Math.min(available, token.balanceOf(address(this)));

  279:    return token.balanceOf(address(this)) + totalAllocated;

  327:    uint256 balBefore = token.balanceOf(address(this));

  328:    token.safeTransferFrom(msg.sender, address(this), _amount);

  329:    uint256 balAfter = token.balanceOf(address(this));

  368:    if (value > token.balanceOf(address(this))) {

  373:    vaultBalance = token.balanceOf(address(this));

  386:    uint256 actualWithdrawn = token.balanceOf(address(this)) - vaultBalance;
 
  399:    vaultBalance = token.balanceOf(address(this));

  528:    token.safeTransferFrom(vars.stratAddr, address(this), vars.freeWantInStrat - vars.credit);
 
  641:    uint256 amount = IERC20Metadata(_token).balanceOf(address(this));

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

Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:

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

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

[G-03] Structs can be packed into fewer storage slots

  1. The "StrategyParams" construct can be packaged as suggested below. Here, uint64 is suggested for uint256 activation and```uint256 lastReport`` considering that it will be valid for the next 532 years.

1 slot win 1 * 2k = 2k gas saved

Ethos-Vault\contracts\ReaperVaultV2.sol:

  25:     struct StrategyParams {
  26:         uint256 activation; // Activation block.timestamp
  27:         uint256 feeBPS; // Performance fee taken from profit, in BPS
  28:         uint256 allocBPS; // Allocation in BPS of vault's total assets
  29:         uint256 allocated; // Amount of capital allocated to this strategy
  30:         uint256 gains; // Total returns that Strategy has realized for Vault
  31:         uint256 losses; // Total losses that Strategy has realized for Vault
  32:         uint256 lastReport; // block.timestamp of the last time a report occured
  33:     }

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

Ethos-Vault\contracts\ReaperVaultV2.sol:
  25:     struct StrategyParams {
- 26:         uint256 activation; // Activation block.timestamp
+ 26:         uint64 activation; // Activation block.timestamp
+ 32:         uint64 lastReport; // block.timestamp of the last time a report occured
  27:         uint256 feeBPS; // Performance fee taken from profit, in BPS
  28:         uint256 allocBPS; // Allocation in BPS of vault's total assets
  29:         uint256 allocated; // Amount of capital allocated to this strategy
  30:         uint256 gains; // Total returns that Strategy has realized for Vault
  31:         uint256 losses; // Total losses that Strategy has realized for Vault
- 32:         uint256 lastReport; // block.timestamp of the last time a report occured
  33:     }
  1. The "Config" structure can be packaged as suggested below.

1 slot win 1 * 2k = 2k gas saved

Ethos-Core\contracts\CollateralConfig.sol:

  27:     struct Config {
  28:         bool allowed;
  29:         uint256 decimals;
  30:         uint256 MCR;
  31:         uint256 CCR;
  32:     }

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

Ethos-Core\contracts\CollateralConfig.sol:

  27:     struct Config {
  28:         bool allowed;
- 29:         uint256 decimals;
+ 29:         uint128 decimals;  
  30:         uint256 MCR;
  31:         uint256 CCR;
  32:     }

[G-04] Pack state variables

  1. State variable packaging in ReaperBaseStrategyv4.sol contract (1 * 2k = 2k gas saved)
Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:

  31:     uint256 public lastHarvestTimestamp;

  33:     uint256 public upgradeProposalTime;

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

Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:

- 31:     uint256 public lastHarvestTimestamp;
+ 31:     uint64 public lastHarvestTimestamp;

- 33:     uint256 public upgradeProposalTime;
+ 33:     uint64 public upgradeProposalTime;
  1. State variable packaging in ActivePool.sol contract (3 * 2.1k = 6.3k Gas saved)
Ethos-Core\contracts\ActivePool.sol:
   
  49:     uint256 public yieldingPercentageDrift = 100; // rebalance iff % is off by more than 100 BPS
  50: 
  51:     // Yield distribution params, must add up to 10k
  52:     uint256 public yieldSplitTreasury = 20_00; // amount of yield to direct to treasury in BPS
  53:     uint256 public yieldSplitSP = 40_00; // amount of yield to direct to stability pool in BPS
  54:     uint256 public yieldSplitStaking = 40_00; // amount of yield to direct to OATH Stakers in BPS

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

The state variable yieldingPercentageDrift can be updated to a maximum value of 500 as seen in function setYieldingPercentageDrift.

Ethos-Core\contracts\ActivePool.sol:   
132 function setYieldingPercentageDrift(uint256 _driftBps) external onlyOwner {   
133: require(_driftBps <= 500, "Exceeds max allowed value of 500 BPS");   
134: yieldingPercentageDrift = _driftBps;

On the other hand, `yieldSplitTreasury, yieldSplitSPand TheyieldSplitStaking`` state variables can total up to 10k.

51: // Yield distribution params, must add up to 10k

Given this information, these state variables can be tightly packed into 1 slot instead of 4 if updated as shown below.

Ethos-Core\contracts\ActivePool.sol:
     
- 49:     uint256 public yieldingPercentageDrift = 100; // rebalance iff % is off by more than 100 BPS
+ 49:     uint64 public yieldingPercentageDrift = 100; // rebalance iff % is off by more than 100 BPS
  
  51:     // Yield distribution params, must add up to 10k
- 52:     uint256 public yieldSplitTreasury = 20_00; // amount of yield to direct to treasury in BPS
+ 52:     uint64 public yieldSplitTreasury = 20_00; // amount of yield to direct to treasury in BPS
- 53:     uint256 public yieldSplitSP = 40_00; // amount of yield to direct to stability pool in BPS
+ 53:     uint64 public yieldSplitSP = 40_00; // amount of yield to direct to stability pool in BPS
- 54:     uint256 public yieldSplitStaking = 40_00; // amount of yield to direct to OATH Stakers in BPS
+ 54:     uint64 public yieldSplitStaking = 40_00; // amount of yield to direct to OATH Stakers in BPS
  1. State variable packaging in ReaperVaultV2.sol contract (3 * 2.1k = 6.3k Gas saved)
Ethos-Vault\contracts\ReaperVaultV2.sol:
 
  35:     mapping(address => StrategyParams) public strategies;

  38:     address[] public withdrawalQueue;

  42:     uint256 public tvlCap;
  43: 
  44:     uint256 public totalAllocBPS; // Sum of allocBPS across all strategies (in BPS, <= 10k)
  45:     uint256 public totalAllocated; // Amount of tokens that have been allocated to all strategies
  46:     uint256 public lastReport; // block.timestamp of last report from any strategy

  49:     bool public emergencyShutdown;

  54:     // Max slippage(loss) allowed when withdrawing, in BPS (0.01%)
  55:     uint256 public withdrawMaxLoss = 1;
  56:     uint256 public lockedProfitDegradation; // rate per block of degradation. DEGRADATION_COEFFICIENT is 100% per block
  57:     uint256 public lockedProfit; // how much profit is locked and cant be withdrawn

  78:     address public treasury; // address to whom performance fee is remitted in the form of vault shares

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  35:     mapping(address => StrategyParams) public strategies;

  38:     address[] public withdrawalQueue;

  42:     uint256 public tvlCap;

+ 49:     bool public emergencyShutdown;
- 44:     uint256 public totalAllocBPS; // Sum of allocBPS across all strategies (in BPS, <= 10k)
+ 44:     uint192 public totalAllocBPS; // Sum of allocBPS across all strategies (in BPS, <= 10k)
  45:     uint256 public totalAllocated; // Amount of tokens that have been allocated to all strategies
- 46:     uint256 public lastReport; // block.timestamp of last report from any strategy
+ 46:     uint64 public lastReport; // block.timestamp of last report from any strategy

- 49:     bool public emergencyShutdown;

- 55:     uint256 public withdrawMaxLoss = 1;
+ 55:     uint64 public withdrawMaxLoss = 1;
- 56:     uint256 public lockedProfitDegradation; // rate per block of degradation. DEGRADATION_COEFFICIENT is 100% per block
+ 56:     uint128 public lockedProfitDegradation; // rate per block of degradation. DEGRADATION_COEFFICIENT is 100% per block
  57:     uint256 public lockedProfit; // how much profit is locked and cant be withdrawn

  78:     address public treasury; // address to whom performance fee is remitted in the form of vault shares
  1. State variable packaging in ReaperBaseStrategyv4.sol contract (2 * 2.1k = 4.2k Gas saved)
Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:

  28:     address public want;

  30:     bool public emergencyExit;
  31:     uint256 public lastHarvestTimestamp;
 
  33:     uint256 public upgradeProposalTime;

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

- 28:     address public want;

  30:     bool public emergencyExit;
- 31:     uint256 public lastHarvestTimestamp;
+ 31:     uint64 public lastHarvestTimestamp; 

- 33:     uint256 public upgradeProposalTime;
+ 33:     uint64 public upgradeProposalTime;
+ 28:     address public want;

[G-05] Remove or replace unused state variables

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

1 result - 1 file:

Ethos-Core\contracts\StabilityPool.sol:

  160:     address public lqtyTokenAddress;

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

[G-06] Deposit struct can be removed

Ethos-Core\contracts\StabilityPool.sol:
   174: struct Deposit {
   175: uint initialValue;
   176: }

   186: mapping (address => Deposit) public deposits; // depositor address -> deposit struct

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

Using a direct type when accessing mapping values is more gas-optimized than using a single-element structure. Because the first requires a storage slot to be read or written. The second requires reading or writing two storage slots. (one for the struct and one for the value inside the struct)

Here, removing struct Deposit and accessing directly from mapping is gas-optimized.

Ethos-Core\contracts\StabilityPool.sol:
- 174: struct Deposit {
- 175: uint initialValue;
- 176: }

- 186: mapping (address => Deposit) public deposits; // depositor address -> deposit struct
// address depositor -> uint256 initialValue
+ 186: mapping (address => uint256) public deposits;

[G-07] Using delete instead of setting mapping/state variable 0 saves gas

9 results - 3 files:

Ethos-Core\contracts\StabilityPool.sol:

  529:     lastLUSDLossError_Offset = 0;

  578:     currentScale = 0;

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

Ethos-Core\contracts\TroveManager.sol:

  1192:    Troves[_borrower][_collateral].stake = 0;

  1285:    Troves[_borrower][_collateral].coll = 0;

  1286:    Troves[_borrower][_collateral].debt = 0;

  1288:    rewardSnapshots[_borrower][_collateral].collAmount = 0;

  1289:    rewardSnapshots[_borrower][_collateral].LUSDDebt = 0;

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  215:     strategies[_strategy].allocBPS = 0;

  537:     lockedProfit = 0;

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

Recommendation code:

Ethos-Vault\contracts\ReaperVaultV2.sol:

- 215:     strategies[_strategy].allocBPS = 0;
+ 215:     delete strategies[_strategy].allocBPS;

[G-08] Using delete instead of setting address(0) saves gas

1 result - 1 file:

Ethos-Core\contracts\TroveManager.sol:

  294:         owner = address(0);

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

Proof Of Concepts:

contract Example {
    address public myAddress = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;

    //  use for empty value Slot:     23450 gas
    //  use for non empty value Slot: 21450 gas
    function reset() public {
        delete myAddress;
    }


    // use for empty value Slot:     23497 gas
    // use for non empty value Slot: 23497 gas
    function setToZero() public {
        myAddress = address(0);
    }
}

[G-09] Using delete to set bool values to their default values saves gas

3 result - 2 files:

Ethos-Core\contracts\LUSDToken.sol:

  143:         mintingPaused = false;

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

Ethos-Core\contracts\TroveManager.sol:

  602:         vars.backToNormalMode = false;

  804:         vars.backToNormalMode = false;

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

[G-10] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

7 results - 3 files:


Ethos-Core\contracts\BorrowerOperations.sol:

  175:    ContractsCache memory contractsCache = ContractsCache(troveManager, activePool, lusdToken);

  283:    ContractsCache memory contractsCache = ContractsCache(troveManager, activePool, lusdToken);

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

Ethos-Core\contracts\StabilityPool.sol:

  687:    Snapshots memory snapshots = depositSnapshots[_depositor];

  722:    Snapshots memory snapshots = depositSnapshots[_depositor];

  806:    address[] memory collaterals = collateralConfig.getAllowedCollaterals();

  857:    address[] memory collaterals = collateralConfig.getAllowedCollaterals();

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

Ethos-Core\contracts\LQTY\LQTYStaking.sol:

  226:    address[] memory collaterals = collateralConfig.getAllowedCollaterals();

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

[G-11] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

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.

7 results 2 files:

Ethos-Core\contracts\LUSDToken.sol:

  62:     mapping (address => bool) public troveManagers;
  63:     mapping (address => bool) public stabilityPools;
  64:     mapping (address => bool) public borrowerOperations;

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

Ethos-Core\contracts\TroveManager.sol:

  104:     mapping (address => uint) public L_Collateral;
  105:     mapping (address => uint) public L_LUSDDebt;


  119:     mapping (address => uint) public lastCollateralError_Redistribution;
  120:     mapping (address => uint) public lastLUSDDebtError_Redistribution;

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

[G-12] Multiple accesses of a mapping/array should use a local variable cache

The instances below point to the second+ access of a value inside a mapping/array, within a function. Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~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. Caching an array's struct avoids recalculating the array offsets into memory/calldata.

30 results - 4 files:

Ethos-Core\contracts\ActivePool.sol:

  // @audit LUSDDebt[_collateral]
  
  193:    LUSDDebt[_collateral] = LUSDDebt[_collateral].add(_amount);

  194:    ActivePoolLUSDDebtUpdated(_collateral, LUSDDebt[_collateral]);

  200:    LUSDDebt[_collateral] = LUSDDebt[_collateral].sub(_amount);

  201:    ActivePoolLUSDDebtUpdated(_collateral, LUSDDebt[_collateral]);


// @audit yieldGenerator[_collateral] 

  245:    vars.yieldGenerator = IERC4626(yieldGenerator[_collateral]);

  278:    IERC20(_collateral).safeIncreaseAllowance(yieldGenerator[_collateral], uint256(vars.netAssetMovement));

  279:    IERC4626(yieldGenerator[_collateral]).deposit(uint256(vars.netAssetMovement), address(this));

  281:    IERC4626(yieldGenerator[_collateral]).withdraw(uint256(-vars.netAssetMovement), address(this), address(this));

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

Ethos-Core\contracts\StabilityPool.sol:

  // @audit depositSnapshots[_depositor]

  825:    depositSnapshots[_depositor].P = currentP;

  826:    depositSnapshots[_depositor].G = currentG;

  827:    depositSnapshots[_depositor].scale = currentScaleCached;

  828:    depositSnapshots[_depositor].epoch = currentEpochCached;

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

Ethos-Core\contracts\TroveManager.sol:

  // @audit Troves[_borrower][_collateral]

  969:    Troves[_borrower][_collateral].debt = _newDebt;

  970:    Troves[_borrower][_collateral].coll = _newColl;

  977:    Troves[_borrower][_collateral].stake,


  //@audit Troves[_borrower][_collateral]

  1091:   Troves[_borrower][_collateral].coll = Troves[_borrower][_collateral].coll.add(pendingCollateralReward);

  1092:   Troves[_borrower][_collateral].debt = Troves[_borrower][_collateral].debt.add(pendingLUSDDebtReward);

  1102:   Troves[_borrower][_collateral].debt,

  1103:   Troves[_borrower][_collateral].coll,

  1104:   Troves[_borrower][_collateral].stake,


  // @audit Troves[_borrower][_collateral]

  1284:   Troves[_borrower][_collateral].status = closedStatus;

  1285:   Troves[_borrower][_collateral].coll = 0;

  1286:   Troves[_borrower][_collateral].debt = 0;


  // @audit rewardSnapshots[_borrower][_collateral]

  1288:   rewardSnapshots[_borrower][_collateral].collAmount = 0;

  1289:   rewardSnapshots[_borrower][_collateral].LUSDDebt = 0;

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  // @audit strategies[_strategy]

  180:    require(strategies[_strategy].activation != 0, "Invalid strategy address");

  182:    strategies[_strategy].feeBPS = _feeBPS;

  183:    emit StrategyFeeBPSUpdated(_strategy, _feeBPS);


  // @audit strategies[_strategy]

  210:    if (strategies[_strategy].allocBPS == 0) {

  214:    totalAllocBPS -= strategies[_strategy].allocBPS;

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

[G-13] The result of function calls should be cached rather than re-calling the function

4 result - 1 file:

Ethos-Vault\contracts\ReaperVaultV2.sol:

  //@audit token.balanceOf()
  368:     if (value > token.balanceOf(address(this))) {
  
  //@audit token.balanceOf()
  373:     vaultBalance = token.balanceOf(address(this));
  
  //@audit token.balanceOf()
  386:     uint256 actualWithdrawn = token.balanceOf(address(this)) - vaultBalance;
 
  //@audit token.balanceOf()
  399:     vaultBalance = token.balanceOf(address(this));

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

[G-14] Avoid using state variable in emit

Using the current memory value here will be more gas-optimized.

1 result - 1 file:

Ethos-Vault\contracts\ReaperVaultV2.sol:

  578:     function updateTvlCap(uint256 _newTvlCap) public {
  579:         _atLeastRole(ADMIN);
  580:         tvlCap = _newTvlCap;
  581:         emit TvlCapUpdated(tvlCap);
  582:     }

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  578:     function updateTvlCap(uint256 _newTvlCap) public {
  579:         _atLeastRole(ADMIN);
  580:         tvlCap = _newTvlCap;
- 581:         emit TvlCapUpdated(tvlCap);
+ 581:         emit TvlCapUpdated(_newTvlCap);
  582:     }

[G-15] Superfluous event fields

block.timestamp and block.number are added to event information by default so adding them manually wastes gas.

1 result - 1 file:

Ethos-Core\contracts\TroveManager.sol:

  1500     function _updateLastFeeOpTime() internal {
  1501         uint timePassed = block.timestamp.sub(lastFeeOperationTime);
  1502 
  1503         if (timePassed >= SECONDS_IN_ONE_MINUTE) {
  1504             lastFeeOperationTime = block.timestamp;
  1505:             emit LastFeeOpTimeUpdated(block.timestamp);
  1506         }
  1507     }

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

Ethos-Core\contracts\TroveManager.sol:

 1500     function _updateLastFeeOpTime() internal {
 1501         uint timePassed = block.timestamp.sub(lastFeeOperationTime);
 1502 
 1503         if (timePassed >= SECONDS_IN_ONE_MINUTE) {
 1504             lastFeeOperationTime = block.timestamp;
-1505:             emit LastFeeOpTimeUpdated(block.timestamp);
+ 1505:             emit LastFeeOpTimeUpdated(lastFeeOperationTime);
 1506         }
 1507     }

[G-16] Avoid contract existence checks by using low level calls

Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external function calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence.

97 results - 9 files:

Ethos-Core\contracts\BorrowerOperations.sol:

  //@audit getCollateralCCR()
  178:      vars.collCCR = collateralConfig.getCollateralCCR(_collateral);
  286:      vars.collCCR = collateralConfig.getCollateralCCR(_collateral);

  //@audit getCollateralMCR()
  179:      vars.collMCR = collateralConfig.getCollateralMCR(_collateral);
  287:      vars.collMCR = collateralConfig.getCollateralMCR(_collateral);

  //@audit getCollateralDecimals()
  180:      vars.collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  288:      vars.collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  382:      uint256 collDecimals = collateralConfig.getCollateralDecimals(_collateral);

  //@audit getTroveColl()
  388:      uint coll = troveManagerCached.getTroveColl(msg.sender, _collateral);

  //@audit getTroveDebt()
  389:      uint debt = troveManagerCached.getTroveDebt(msg.sender, _collateral);
 
  //@audit getBorrowingFee()
  421:      uint LUSDFee = _troveManager.getBorrowingFee(_LUSDAmount);

  //@audit increaseTroveColl(), decreaseTroveColl()
  468:      uint newColl = (_isCollIncrease) ? _troveManager.increaseTroveColl(_borrower, _collateral, _collChange)
  469:                                     : _troveManager.decreaseTroveColl(_borrower, _collateral, _collChange);
  470:      uint newDebt = (_isDebtIncrease) ? _troveManager.increaseTroveDebt(_borrower, _collateral, _debtChange)
  471:                                     : _troveManager.decreaseTroveDebt(_borrower, _collateral, _debtChange);

  //@audit isCollateralAllowed()
  525:      require(collateralConfig.isCollateralAllowed(_collateral), "BorrowerOps: Invalid collateral address");

  //@audit balanceOf()
  529:      require(IERC20(_collateral).balanceOf(_user) >= _collAmount, "BorrowerOperations: Insufficient user collateral balance");
  645:      require(_lusdToken.balanceOf(_borrower) >= _debtRepayment, "BorrowerOps: Caller doesnt have enough LUSD to make repayment");
  
  //@audit allowance()
  530:      require(IERC20(_collateral).allowance(_user, address(this)) >= _collAmount, "BorrowerOperations: Insufficient collateral allowance");

  //@audit getTroveStatus()
  542:      uint status = _troveManager.getTroveStatus(_borrower, _collateral);
  547:      uint status = _troveManager.getTroveStatus(_borrower, _collateral);

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

Ethos-Core\contracts\TroveManager.sol:

  //@audit getCollateralDecimals()
  420:      uint collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  522:      vars.collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  596:      vars.collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  725:      vars.collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  798:      vars.collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  1048:     uint256 collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  1061:     uint256 collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  1131:     uint256 collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  1146:     uint256 collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  1362:     uint256 collDecimals = collateralConfig.getCollateralDecimals(_collateral);
  1368:     uint256 collDecimals = collateralConfig.getCollateralDecimals(_collateral);

  //@audit getCollateralMCR()
  523:      vars.collMCR = collateralConfig.getCollateralMCR(_collateral);
  598:      vars.collMCR = collateralConfig.getCollateralMCR(_collateral);
  727:      vars.collMCR = collateralConfig.getCollateralMCR(_collateral);
  800:      vars.collMCR = collateralConfig.getCollateralMCR(_collateral);

  //@audit fetchPrice()
  524:      vars.price = priceFeed.fetchPrice(_collateral);
  728:      vars.price = priceFeed.fetchPrice(_collateral);
  
  //@audit getCollateralCCR()
  521:      vars.collCCR = collateralConfig.getCollateralCCR(_collateral);
  597:      vars.collCCR = collateralConfig.getCollateralCCR(_collateral);
  726:      vars.collCCR = collateralConfig.getCollateralCCR(_collateral);
  799:      vars.collCCR = collateralConfig.getCollateralCCR(_collateral);
  
  //@audit getLast()
  606:      vars.user = _sortedTroves.getLast(_collateral);
  691:      vars.user = sortedTrovesCached.getLast(_collateral);

  //@audit getFirst()
  607:      address firstUser = _sortedTroves.getFirst(_collateral);

  //@audit getPrev()
  610:      address nextUser = _sortedTroves.getPrev(_collateral, vars.user);  

   //@audit getTotalLUSDDeposits()
  729:      vars.LUSDInStabPool = stabilityPoolCached.getTotalLUSDDeposits();

  //@audit getCollateral()
  1308:     uint activeColl = _activePool.getCollateral(_collateral);
  1309:     uint liquidatedColl = defaultPool.getCollateral(_collateral);

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

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

Ethos-Core\contracts\ActivePool.sol:

  //@audit getAllowedCollaterals()
  105:      address[] memory collaterals = ICollateralConfig(collateralConfigAddress).getAllowedCollaterals();

  //@audit asset()
  111:      require(IERC4626(vault).asset() == collateral, "Vault asset must be collateral");

  //@audit deposit()
  280:      IERC4626(yieldGenerator[_collateral]).deposit(uint256(vars.netAssetMovement), address(this));
  
  //@audit withdraw()
  282:      IERC4626(yieldGenerator[_collateral]).withdraw(uint256(-vars.netAssetMovement), address(this), address(this));

  //@audit balanceOf()
  288:      vars.profit = IERC20(_collateral).balanceOf(address(this)).add(vars.yieldingAmount).sub(collAmount[_collateral]);

  //@audit isCollateralAllowed()
  315:      ICollateralConfig(collateralConfigAddress).isCollateralAllowed(_collateral),

  //@audit redemptionHelper()
  328:      address redemptionHelper = address(ITroveManager(troveManagerAddress).redemptionHelper());

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

Ethos-Core\contracts\StabilityPool.sol:

  //@audit redemptionHelper()
  328:      address redemptionHelper = address(ITroveManager(troveManagerAddress).redemptionHelper());

  //@audit issueOath()
  417:      uint LQTYIssuance = _communityIssuance.issueOath();

  //@audit getAllowedCollaterals()
  638:      assets = collateralConfig.getAllowedCollaterals();
  806:      address[] memory collaterals = collateralConfig.getAllowedCollaterals();
  857:      address[] memory collaterals = collateralConfig.getAllowedCollaterals();

  //@audit getCollateralDecimals()
  664:      vars.collDecimals = collateralConfig.getCollateralDecimals(_collateral);

  //@audit fetchPrice()
  861:      uint price = priceFeed.fetchPrice(collateral);

  //@audit getLast()
  862:      address lowestTrove = sortedTroves.getLast(collateral);

  //@audit getCollateralMCR()
  863:      uint256 collMCR = collateralConfig.getCollateralMCR(collateral);

  //@audit getCurrentICR()
  864:      uint ICR = troveManager.getCurrentICR(lowestTrove, collateral, price);

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

Ethos-Core\contracts\LQTY\CommunityIssuance.sol:

  //@audit transferFrom()
  103:      OathToken.transferFrom(msg.sender, address(this), amount);

  //@audit transfer()
  127:      OathToken.transfer(_account, _OathAmount);

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

Ethos-Core\contracts\LQTY\LQTYStaking.sol:

  //@audit transfer()
  135:      lusdToken.transfer(msg.sender, LUSDGain);
  171:      lusdToken.transfer(msg.sender, LUSDGain);

  //@audit getAllowedCollaterals()
  204:      assets = collateralConfig.getAllowedCollaterals();
  226:      address[] memory collaterals = collateralConfig.getAllowedCollaterals();

  //@audit redemptionHelper()
  252:      address redemptionHelper = address(ITroveManager(troveManagerAddress).redemptionHelper());

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  //@audit vault()
  153:      require(address(this) == IStrategy(_strategy).vault(), "Strategy's vault does not match");
  
  //@audit want()
  154:      require(address(token) == IStrategy(_strategy).want(), "Strategy's want does not match");

  //@audit balanceOf()
  246:      available = Math.min(available, token.balanceOf(address(this)));
  279:      return token.balanceOf(address(this)) + totalAllocated;
  303:      _deposit(token.balanceOf(msg.sender), msg.sender);
  327:      uint256 balBefore = token.balanceOf(address(this));
  329:      uint256 balAfter = token.balanceOf(address(this));
  368:      if (value > token.balanceOf(address(this))) {
  373:      vaultBalance = token.balanceOf(address(this));
  386:      uint256 actualWithdrawn = token.balanceOf(address(this)) - vaultBalance;
  556:      return IStrategy(vars.stratAddr).balanceOf();
  641:      uint256 amount = IERC20Metadata(_token).balanceOf(address(this));

  //@audit withdraw()
  385:      uint256 loss = IStrategy(stratAddr).withdraw(Math.min(remaining, strategyBal));

  //@audit decimals()
  651:      return token.decimals();

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

Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:

  //@audit availableCapital()
  111:      int256 availableCapital = IVault(vault).availableCapital();

  //@audit report()
  134:      debt = IVault(vault).report(roi, repayment);

Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:

  //@audit UNDERLYING_ASSET_ADDRESS()
  69:       want = _gWant.UNDERLYING_ASSET_ADDRESS();

  //@audit balanceOf()
  120:      uint256 amount = startToken.balanceOf(address(this));
  227:      return IERC20Upgradeable(want).balanceOf(address(this));

  //@audit strategies()
  127:      uint256 allocated = IVault(vault).strategies(address(this)).allocated;

  //@audit getLendingPool()
  178:      address lendingPoolAddress = ADDRESSES_PROVIDER.getLendingPool();
  200:      ILendingPool(ADDRESSES_PROVIDER.getLendingPool()).withdraw(address(want), _withdrawAmount, address(this));

  //@audit claimAllRewardsToSelf()
  207:      IRewardsController(REWARDER).claimAllRewardsToSelf(rewardClaimingTokens);

  //@audit getUserReserveData(
  231:      (uint256 supply, , , , , , , , ) = IAaveProtocolDataProvider(DATA_PROVIDER).getUserReserveData(

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

[G-17] bytes constants are more eficient than string constans

If the data can fit in 32 bytes, the bytes32 data type can be used instead of bytes or strings, as it is less robust in terms of robustness.

8 results - 6 files:

Ethos-Core\contracts\ActivePool.sol:

  30:     string constant public NAME = "ActivePool";

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

Ethos-Core\contracts\BorrowerOperations.sol:

  21:     string constant public NAME = "BorrowerOperations";

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

Ethos-Core\contracts\LUSDToken.sol:

  32:     string constant internal _NAME = "LUSD Stablecoin";

  33:     string constant internal _SYMBOL = "LUSD";

  34:     string constant internal _VERSION = "1";

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

Ethos-Core\contracts\StabilityPool.sol:

  150:    string constant public NAME = "StabilityPool";

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

Ethos-Core\contracts\LQTY\CommunityIssuance.sol:

  19:     string constant public NAME = "CommunityIssuance";

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

Ethos-Core\contracts\LQTY\LQTYStaking.sol:

  23:     string constant public NAME = "LQTYStaking";

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

[G-18] Change public function visibility to external

Since the following public functions are not called from within the contract, their visibility can be made external for gas optimization.

3 results - 2 files:

Ethos-Core\contracts\TroveManager.sol:

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

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

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  295:     function getPricePerFullShare() public view returns (uint256) {

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

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

When using elements that are smaller than 32 bytes, your contracts gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html

Use a larger size then downcast where needed.

6 results 3 files:

Ethos-Vault\contracts\ReaperVaultV2.sol:

  // @audit uint8 decimals()
  651:     return token.decimals();

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

Ethos-Core\contracts\TroveManager.sol:

  // @audit uint128 index
  1329:    index = uint128(TroveOwners[_collateral].length.sub(1));
  1332:    return index;

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

Ethos-Core\contracts\StabilityPool.sol:

  // @audit uint128 currentScaleCached
  585:     currentScale = currentScaleCached.add(1);

  // @audit uint128 currentEpochCached
  576:     currentEpoch = currentEpochCached.add(1);

  // @audit uint128 scaleSnapshot
  745:     uint128 scaleDiff = currentScale.sub(scaleSnapshot);

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

[G-20] internal functions only called once can be inlined to save gas

Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.

45 results - 9 files:

Ethos-Core\contracts\ActivePool.sol:

  320:    function _requireCallerIsBorrowerOperationsOrDefaultPool() internal view {

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

Ethos-Core\contracts\BorrowerOperations.sol:

  524:    function _requireValidCollateralAddress(address _collateral) internal view {

  533:    function _requireSingularCollChange(uint _collTopUp, uint _collWithdrawal) internal pure {

  537:    function _requireNonZeroAdjustment(uint _collTopUp, uint _collWithdrawal, uint _LUSDChange) internal pure {

  546:    function _requireTroveisNotActive(ITroveManager _troveManager, address _borrower, address _collateral) internal view {

  551:    function _requireNonZeroDebtChange(uint _LUSDChange) internal pure {
  
  555     function _requireNotInRecoveryMode(
  556         address _collateral,
  557         uint _price,
  558         uint256 _CCR,
  559         uint256 _collateralDecimals
  560:     ) internal view {
  
  567:    function _requireNoCollWithdrawal(uint _collWithdrawal) internal pure {
  
  571     function _requireValidAdjustmentInCurrentMode 
  572     (
  573         bool _isRecoveryMode,
  574         address _collateral,
  575         uint _collWithdrawal,
  576         bool _isDebtIncrease, 
  577         LocalVariables_adjustTrove memory _vars
  578     ) 
  579:         internal 
  
  624:    function _requireNewICRisAboveOldICR(uint _newICR, uint _oldICR) internal pure {

  636:    function _requireValidLUSDRepayment(uint _currentDebt, uint _debtRepayment) internal pure {

  640:    function _requireCallerIsStabilityPool() internal view {

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

Ethos-Core\contracts\LUSDToken.sol:

  320:    function _mint(address account, uint256 amount) internal {

  328:    function _burn(address account, uint256 amount) internal {

  360:    function _requireCallerIsBorrowerOperations() internal view {

  365:    function _requireCallerIsBOorTroveMorSP() internal view {

  375:    function _requireCallerIsStabilityPool() internal view {

  380:    function _requireCallerIsTroveMorSP() internal view {

  393:    function _requireMintingNotPaused() internal view {

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

Ethos-Core\contracts\StabilityPool.sol:

  421:    function _updateG(uint _LQTYIssuance) internal {

  439:    function _computeLQTYPerUnitStaked(uint _LQTYIssuance, uint _totalLUSDDeposits) internal returns (uint) {

  597:    function _moveOffsetCollAndDebt(address _collateral, uint _collToAdd, uint _debtToOffset) internal {

  637:    function _getCollateralGainFromSnapshots(uint initialDeposit, Snapshots storage snapshots) internal view returns (address[] memory assets, uint[] memory amounts) {
  
  693:    function _getLQTYGainFromSnapshots(uint initialDeposit, Snapshots memory snapshots) internal view returns (uint) {
  
  776:    function _sendLUSDtoStabilityPool(address _address, uint _amount) internal {

  794:    function _sendLUSDToDepositor(address _depositor, uint LUSDWithdrawal) internal {

  848:    function _requireCallerIsActivePool() internal view {

  852:    function _requireCallerIsTroveManager() internal view {

  856:    function _requireNoUnderCollateralizedTroves() internal {

  869:    function _requireUserHasDeposit(uint _initialDeposit) internal pure {

  873:    function _requireNonZeroAmount(uint _amount) internal pure {
Ethos-Core\contracts\TroveManager.sol:

  1082:   function _applyPendingRewards(IActivePool _activePool, IDefaultPool _defaultPool, address _borrower, address _collateral) internal {
  
  1213:   function _computeNewStake(address _collateral, uint _coll) internal view returns (uint) {
  
  1321:   function _addTroveOwnerToArray(address _borrower, address _collateral) internal returns (uint128 index) {
  
  1339:   function _removeTroveOwner(address _borrower, address _collateral, uint TroveOwnersArrayLength) internal {
  
  1516:   function _minutesPassedSinceLastFeeOp() internal view returns (uint) {
  
  1538:   function _requireMoreThanOneTroveInSystem(uint TroveOwnersArrayLength, address _collateral) internal view {

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

Ethos-Core\contracts\LQTY\LQTYStaking.sol:
  
  251:    function _requireCallerIsTroveManagerOrActivePool() internal view {
  
  261:    function _requireCallerIsBorrowerOperations() internal view {
  
  265:    function _requireUserHasStake(uint currentStake) internal pure {  
  
  269:    function _requireNonZeroAmount(uint _amount) internal pure {

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

Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:
  
  176:    function _deposit(uint256 toReinvest) internal {
  
  206:    function _claimRewards() internal {

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

Ethos-Vault\contracts\ReaperVaultV2.sol:
  
  462:    function _chargeFees(address strategy, uint256 gain) internal returns (uint256) {

Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:
  
  250:    function _liquidateAllPositions() internal virtual returns (uint256 amountFreed);

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

[G-21] Do not calculate constants variables

Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.

11 results - 3 files:

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%
Ethos-Vault\contracts\ReaperVaultV2.sol:

  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:
  
  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");

Recommendation Code:


Ethos-Core\contracts\TroveManager.sol:

- 54:     uint constant public override REDEMPTION_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%

+            // REDEMPTION_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5%
+ 54:     uint constant public override REDEMPTION_FEE_FLOOR = 5e15;

Ethos-Vault\contracts\ReaperVaultV2.sol:

- 73:     bytes32 public constant DEPOSITOR = keccak256("DEPOSITOR");
+ 73:     bytes32 public constant DEPOSITOR = 0xe16b3d8fc79140c62874442c8b523e98592b429e73c0db67686a5b378b29f336;

[G-22] Use assembly to write address storage values

18 results - 6 files:

Ethos-Core\contracts\LUSDToken.sol:

   84     constructor
  102:         troveManagerAddress = _troveManagerAddress;
  106:         stabilityPoolAddress = _stabilityPoolAddress;
  110:         borrowerOperationsAddress = _borrowerOperationsAddress;
  114:         governanceAddress = _governanceAddress;
  117:         guardianAddress = _guardianAddress;

  146     function updateGovernance(address _newGovernanceAddress) external {
  149:         governanceAddress = _newGovernanceAddress;

  153     function updateGuardian(address _newGuardianAddress) external {
  156:         guardianAddress = _newGuardianAddress;

  160     function upgradeProtocol(
  170:         troveManagerAddress = _newTroveManagerAddress;
  174:         stabilityPoolAddress = _newStabilityPoolAddress;
  178:         borrowerOperationsAddress = _newBorrowerOperationsAddress;

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

Ethos-Core\contracts\TroveManager.sol:
  
  232     function setAddresses(
  266:         borrowerOperationsAddress = _borrowerOperationsAddress;

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

Ethos-Core\contracts\LQTY\CommunityIssuance.sol:

  61     function setAddresses
  75:         stabilityPoolAddress = _stabilityPoolAddress;

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

Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:

  62:    function initialize(
  68:         gWant = _gWant;

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  111     constructor(
  123:         tvlCap = _tvlCap;
  124:         treasury = _treasury;

  627     function updateTreasury(address newTreasury) external {
  630:         treasury = newTreasury;

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

Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:
 
  63     function __ReaperBaseStrategy_init(
  72:         vault = _vault;
  73:         want = _want;

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

Recommendation Code:


Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:
 
  63     function __ReaperBaseStrategy_init(
- 72:         vault = _vault;
+                  assembly {                      
+                      sstore(vault.slot, _vault )
+                  }                               
          

[G-23] Setting the constructor to payable

You can cut out 10 opcodes in the creation-time EVM bytecode if you declare a constructor payable. Making the constructor payable eliminates the need for an initial check of msg.value == 0 and saves 13 gas on deployment with no security risks.

6 results - 6 files:

Ethos-Core\contracts\LUSDToken.sol:

  84:     constructor

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

Ethos-Core\contracts\TroveManager.sol:

  225:     constructor() public {

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

Ethos-Core\contracts\LQTY\CommunityIssuance.sol:

  57:     constructor() public {

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

Ethos-Vault\contracts\ReaperVaultERC4626.sol:

  16:     constructor(

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  111:     constructor(

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

Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:

  61:     constructor() initializer {}

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

Recommendation: Set the constructor to payable

[G-24] Add unchecked {} for subtractions where the operands cannot underflow because of a previous require or if statement

require(a <= b); x = b - a => require(a <= b); unchecked { x = b - a } if(a <= b); x = b - a => if(a <= b); unchecked { x = b - a }

This will stop the check for overflow and underflow so it will save gas.

10 results - 2 files:

Ethos-Vault\contracts\ReaperVaultV2.sol:

  144     function addStrategy(
  156         require(_allocBPS + totalAllocBPS <= PERCENT_DIVISOR, "Invalid allocBPS value");
  168:         totalAllocBPS += _allocBPS;

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

Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:

   99         if (_amountNeeded > liquidatedAmount) {
  100:             loss = _amountNeeded - liquidatedAmount;

  131         if (totalAssets > allocated) {
  132:             uint256 profit = totalAssets - allocated;
  135         } else if (totalAssets < allocated) {
  136:             roi = -int256(allocated - totalAssets);
  137         }


  225     function availableCapital() public view returns (int256) {
  234         if (stratCurrentAllocation > stratMaxAllocation) {
  235:             return -int256(stratCurrentAllocation - stratMaxAllocation);
  236:         } else if (stratCurrentAllocation < stratMaxAllocation) {
  244:             uint256 available = stratMaxAllocation - stratCurrentAllocation;


  359     function _withdraw(
  374                 if (value <= vaultBalance) {
  384:                 uint256 remaining = value - vaultBalance;


  493     function report(int256 _roi, uint256 _repayment) external returns (uint256) {
  534         if (vars.lockedProfitBeforeLoss > vars.loss) {
  535:             lockedProfit = vars.lockedProfitBeforeLoss - vars.loss;


  109     function harvest() external override returns (int256 roi) {
  120             if (amountFreed < debt) {
  121:                 roi = -int256(debt - amountFreed);
  122             } else if (amountFreed > debt) {
  123:                 roi = int256(amountFreed - debt);
  124             }

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

[G-25] ++i/i++ should be unchecked{++i}/unchecked{i++} when it is not possible for them to overflow, as is the case when used in for- and while-loops

In the use of for loops, this structure, which will reduce gas costs. This saves 30-40 gas per loop.

13 results - 3 files:


Ethos-Core\contracts\StabilityPool.sol:

  351:    for (uint i = 0; i < numCollaterals; i++) {

  397:    for (uint i = 0; i < numCollaterals; i++) {

  640:    for (uint i = 0; i < assets.length; i++) {

  810:    for (uint i = 0; i < collaterals.length; i++) {

  831:    for (uint i = 0; i < collaterals.length; i++) {

  859:    for (uint i = 0; i < numCollaterals; i++) {

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

Ethos-Core\contracts\TroveManager.sol:

  608:    for (vars.i = 0; vars.i < _n && vars.user != firstUser; vars.i++) {

  690:    for (vars.i = 0; vars.i < _n; vars.i++) {

  808:    for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {

  882:    for (vars.i = 0; vars.i < _troveArray.length; vars.i++) {

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

Ethos-Core\contracts\LQTY\LQTYStaking.sol:

  206:    for (uint i = 0; i < assets.length; i++) {

  228:    for (uint i = 0; i < collaterals.length; i++) {

  240:    for (uint i = 0; i < numCollaterals; i++) {

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

Recommendation Code:

- 206:    for (uint i = 0; i < assets.length; i++) {
+ 206:    for (uint i = 0; i < assets.length; i = unchecked_inc(i)) {

+ function unchecked_inc(uint i) internal pure returns (uint) {
+       unchecked {
+           return i + 1;
+        }
+    }

[G-26] x += y (x -= y) costs more gas than x = x + y (x = x - y) for state variables

9 results - 1 file:

Ethos-Vault\contracts\ReaperVaultV2.sol:

  168:    totalAllocBPS += _allocBPS;

  194:    totalAllocBPS -= strategies[_strategy].allocBPS;

  196:    totalAllocBPS += _allocBPS;

  214:    totalAllocBPS -= strategies[_strategy].allocBPS;

  396:    totalAllocated -= actualWithdrawn;

  445:    totalAllocBPS -= bpsChange;

  452:    totalAllocated -= loss;
  
  515:    totalAllocated -= vars.debtPayment;

  521:    totalAllocated += vars.credit;

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

x += y (x -= y) costs more gas than x = x + y (x = x - y) for state variables.

[G-27] Use require instead of assert

The assert() and require() functions are a part of the error handling aspect in Solidity. Solidity makes use of state-reverting error handling exceptions. This means all changes made to the contract on that call or any sub-calls are undone if an error is thrown. It also flags an error.

They are quite similar as both check for conditions and if they are not met, would throw an error.

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.

20 results - 4 files:

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); 

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

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));

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

Ethos-Core\contracts\StabilityPool.sol:

  526:     assert(_debtToOffset <= _totalLUSDDeposits);

  551:     assert(_LUSDLossPerUnitStaked <= DECIMAL_PRECISION);

  591:     assert(newP > 0);

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

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

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

[G-28] Use nested if and, avoid multiple check combinations

Using nested is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.

3 results - 2 files:

Ethos-Core\contracts\ActivePool.sol:

  264:         if (vars.percentOfFinalBal > vars.yieldingPercentage && vars.percentOfFinalBal.sub(vars.yieldingPercentage) > yieldingPercentageDrift) {

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

Ethos-Core\contracts\BorrowerOperations.sol:

  311:         if (_isDebtIncrease && !isRecoveryMode) { 

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

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

Recomendation Code:

Ethos-Core\contracts\BorrowerOperations.sol:

- 311:         if (_isDebtIncrease && !isRecoveryMode) {
+                 if (_isDebtIncrease) { 
+                 if (!isRecoveryMode) { 
+              }
+          }

[G-29] Use double require instead of using &&

Using double require instead of operator && can save more gas When having a require statement with 2 or more expressions needed, place the expression that cost less gas first.

4 results - 3 files:

Ethos-Core\contracts\BorrowerOperations.sol:

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

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

Ethos-Core\contracts\LUSDToken.sol:

  347:     require(
  348:         _recipient != address(0) && 
  349:         _recipient != address(this),
  350:         "LUSD: Cannot transfer tokens directly to the LUSD token contract or the zero address"
  351:     );

  352:     require(
  353:         !stabilityPools[_recipient] &&
  354:         !troveManagers[_recipient] &&
  355:         !borrowerOperations[_recipient],
  356:         "LUSD: Cannot transfer tokens directly to the StabilityPool, TroveManager or BorrowerOps"
  357:     );

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

Ethos-Core\contracts\TroveManager.sol:

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

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

Recommendation Code:

Ethos-Core\contracts\TroveManager.sol:

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

[G-30] Sort Solidity operations using short-circuit mode

Short-circuiting is a solidity contract development model that uses OR/AND logic to sequence different cost operations. It puts low gas cost operations in the front and high gas cost operations in the back, so that if the front is low If the cost operation is feasible, you can skip (short-circuit) the subsequent high-cost Ethereum virtual machine operation.

//f(x) is a low gas cost operation //g(y) is a high gas cost operation //Sort operations with different gas costs as follows f(x) || g(y) f(x) && g(y)

14 results - 5 files:

Ethos-Core\contracts\TroveManager.sol:

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

   817:    if (vars.ICR >= vars.collMCR && vars.remainingLUSDInStabPool == 0) { continue; }
   
  1539:   require (TroveOwnersArrayLength > 1 && sortedTroves.getSize(_collateral) > 1);

  1531:   require(msg.sender == borrowerOperationsAddress || msg.sender == address(redemptionHelper));

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

Ethos-Vault\contracts\ReaperVaultERC4626.sol:

   52:      if (totalSupply() == 0 || _freeFunds() == 0) return assets;

   80:      if (emergencyShutdown || balance() >= tvlCap) return 0;

  123:     if (emergencyShutdown || balance() >= tvlCap) return 0;

  184:     if (totalSupply() == 0 || _freeFunds() == 0) return 0;

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

Ethos-Vault\contracts\ReaperVaultV2.sol:

  227:     if (totalAllocBPS == 0 || emergencyShutdown) {

  555:     if (strategy.allocBPS == 0 || emergencyShutdown) {

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

Ethos-Core\contracts\BorrowerOperations.sol:

  311:     if (_isDebtIncrease && !isRecoveryMode) { 

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

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

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

Ethos-Core\contracts\LUSDToken.sol:

  347:     require(
  348         _recipient != address(0) && 
  349         _recipient != address(this),

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

[G-31] Optimize names to save gas (22 gas)

Contracts most called functions could simply save gas by function ordering via Method ID. Calling a function at runtime will be cheaper if the function is positioned earlier in the order (has a relatively lower Method ID) because 22 gas are added to the cost of a function for every position that came before it. The caller can save on gas if you prioritize most called functions. 

Context:  All Contracts

Recommendation:  Find a lower method ID name for the most called functions for example Call() vs. Call1() is cheaper by 22 gas For example, the function IDs in the ActivePool.sol contract will be the most used; A lower method ID may be given.

Proof of Consept: https://medium.com/joyso/solidity-how-does-function-name-affect-gas-consumption-in-smart-contract-47d270d8ac92

ActivePool.sol function names can be named and sorted according to METHOD ID

Sighash   |   Function Signature
========================
31585161  =>  manualRebalance(address,uint256)
c707f625  =>  setAddresses(address,address,address,address,address,address,address,address,address[])
8fca46f4  =>  setYieldingPercentage(address,uint256)
6dc85da8  =>  setYieldingPercentageDrift(uint256)
2bc75d35  =>  setYieldClaimThreshold(address,uint256)
46a80441  =>  setYieldDistributionParams(uint256,uint256,uint256)
9b56d6c9  =>  getCollateral(address)
4b14fa87  =>  getLUSDDebt(address)
715aa61b  =>  sendCollateral(address,address,uint256)
5fc7b5e8  =>  increaseLUSDDebt(address,uint256)
3aacc815  =>  decreaseLUSDDebt(address,uint256)
eb13cd10  =>  pullCollateralFromBorrowerOperationsOrDefaultPool(address,uint256)
e07a32fa  =>  _rebalance(address,uint256)
a388c48f  =>  _requireValidCollateralAddress(address)
cd0a39b5  =>  _requireCallerIsBorrowerOperationsOrDefaultPool()
9cfa3bca  =>  _requireCallerIsBOorTroveMorSP()
8daa3501  =>  _requireCallerIsBOorTroveM()

[G-32] Use a more recent version of solidity

Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath Use a solidity version of at least 0.8.2 to get simple compiler automatic inlining Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value In 0.8.15 the conditions necessary for inlining are relaxed. Benchmarks show that the change significantly decreases the bytecode size (which impacts the deployment cost) while the effect on the runtime gas usage is smaller.

In 0.8.17 prevent the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call; Simplify the starting offset of zero-length operations to zero. More efficient overflow checks for multiplication.

12 results - 12 files:

Ethos-Core\contracts\ActivePool.sol:
 
  3: pragma solidity 0.6.11;

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

Ethos-Core\contracts\BorrowerOperations.sol:

  3: pragma solidity 0.6.11;
 

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

Ethos-Core\contracts\CollateralConfig.sol:
 
  3: pragma solidity 0.6.11;
 

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

Ethos-Core\contracts\LUSDToken.sol:
 
  3: pragma solidity 0.6.11;

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

Ethos-Core\contracts\StabilityPool.sol:
 
  3: pragma solidity 0.6.11;

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

Ethos-Core\contracts\TroveManager.sol:

  3: pragma solidity 0.6.11;

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

Ethos-Core\contracts\LQTY\CommunityIssuance.sol:
 
  3: pragma solidity 0.6.11;

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

Ethos-Core\contracts\LQTY\LQTYStaking.sol:
 
  3: pragma solidity 0.6.11;

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

Ethos-Vault\contracts\ReaperStrategyGranarySupplyOnly.sol:

 3: pragma solidity ^0.8.0;

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

Ethos-Vault\contracts\ReaperVaultERC4626.sol:
 
  3: pragma solidity ^0.8.0;

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

Ethos-Vault\contracts\ReaperVaultV2.sol:
 
  3: pragma solidity ^0.8.0;

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

Ethos-Vault\contracts\abstract\ReaperBaseStrategyv4.sol:
  
  3: pragma solidity ^0.8.0;

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

[G-33] >= costs less gas than >

The compiler uses opcodes GT and ISZERO for solidity code that uses >, but only requires LT for >=, which saves 3 gas

1 result - 1 file:

Ethos-Core\contracts\LQTY\CommunityIssuance.sol:

  87:             uint256 endTimestamp = block.timestamp > lastDistributionTime ? lastDistributionTime : block.timestamp;

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

[G-34] Upgrade Solidity's optimizer

Make sure Solidity’s optimizer is enabled. It reduces gas costs. If you want to gas optimize for contract deployment (costs less to deploy a contract) then set the Solidity optimizer at a low number. If you want to optimize for run-time gas costs (when functions are called on a contract) then set the optimizer to a high number.

Ethos-Core\hardhat.config.js:
  38:     solidity: {
  39:         compilers: [
  40:             {
  41:                 version: "0.4.23",
  42:                 settings: {
  43:                     optimizer: {
  44:                         enabled: true,
  45:                         runs: 100
  46:                     }
  47:                 }
  48:             },
  49:             {
  50:                 version: "0.5.17",
  51:                 settings: {
  52:                     optimizer: {
  53:                         enabled: true,
  54:                         runs: 100
  55:                     }
  56:                 }
  57:             },
  58:             {
  59:                 version: "0.6.11",
  60:                 settings: {
  61:                     optimizer: {
  62:                         enabled: true,
  63:                         runs: 100
  64:                     }
  65:                 }
  66:             },
  67:         ]
  68:     },
Ethos-Vault\hardhat.config.js:
  10: module.exports = {
  11:   solidity: {
  12:     compilers: [
  13:       {
  14:         version: "0.8.11",
  15:         settings: {
  16:           optimizer: {
  17:             enabled: true,
  18:             runs: 200,
  19:           },
  20:         },
  21:       },
  22:     ],
  23:   },
  24:   networks: {
  25:     mainnet: {
  26:       url: `https://rpc.ftm.tools`,
  27:       chainId: 250,
  28:       accounts: [`0x${PRIVATE_KEY}`],
  29:     },
  30:     testnet: {
  31:       url: `https://rpcapi-tracing.testnet.fantom.network`,
  32:       chainId: 4002,
  33:       accounts: [`0x${PRIVATE_KEY}`],
  34:     },
  35:   },
  36:   etherscan: {
  37:     apiKey: FTMSCAN_KEY,
  38:   },
  39:   mocha: {
  40:     timeout: 1200000,
  41:   },
  42:   gasReporter: {
  43:       enabled: true
  44:   }
  45: };

#0 - c4-judge

2023-03-08T15:26:12Z

trust1995 marked the issue as grade-a

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