Platform: Code4rena
Start Date: 16/02/2023
Pot Size: $144,750 USDC
Total HM: 17
Participants: 154
Period: 19 days
Judge: Trust
Total Solo HM: 5
Id: 216
League: ETH
Rank: 62/154
Findings: 2
Award: $103.33
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: GalloDaSballo
Also found by: 0x3b, 0xAgro, 0xSmartContract, 0xTheC0der, 0xackermann, 0xnev, 0xsomeone, ABA, BRONZEDISC, Bjorn_bug, Bnke0x0, Breeje, Co0nan, CodeFoxInc, CodingNameKiki, DadeKuma, DeFiHackLabs, IceBear, Josiah, Kaysoft, Lavishq, MohammedRizwan, PaludoX0, PawelK, Phantasmagoria, Raiders, RaymondFam, Rickard, Rolezn, Sathish9098, SleepingBugs, SuperRayss, UdarTeam, Udsen, Viktor_Cortess, arialblack14, ast3ros, bin2chen, brgltd, btk, catellatech, ch0bu, chaduke, chrisdior4, codeislight, cryptonue, delfin454000, descharre, dontonka, emmac002, fs0c, hacker-dom, hansfriese, imare, lukris02, luxartvinsec, martin, matrix_0wl, peanuts, rbserver, shark, tnevler, trustindistrust, tsvetanovv, vagrant, yongskiws, zzzitron
61.2601 USDC - $61.26
pragma solidity 0.6.11;
in all Ethos contract, ex: https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L3
Update solidity 0.8.18 or higher which has the fix for some vulnerabilites.
In Solidity ecrecover function directly to verify the given signatures. However, the ecrecover EVM opcode allows malleable (non-unique) signatures and thus is susceptible to replay attacks.
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L287
address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress == owner, 'LUSD: invalid signature');
Use the recover function from OpenZeppelin's ECDSA library for signature verification.
function permit ( address owner, address spender, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s ) external override { if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { revert('LUSD: Invalid s value'); } require(deadline >= block.timestamp, 'LUSD: expired deadline'); bytes32 digest = keccak256(abi.encodePacked( '\x19\x01', domainSeparator(), keccak256(abi.encode( _PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline )) )); address recoveredAddress = recover(digest, r, s); require(recoveredAddress == owner, 'LUSD: invalid signature'); _approve(owner, spender, amount); } }
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LUSDToken.sol#L287
add require(recoveredAddress != address(0), "recoveredAddress is zero address!");
From code, looks like tressury is a EOA wallet. Better to use multi-sig wallet like Gnosis safe.
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/ActivePool.sol#L93
Use checkContract to check address exist.
Use Ownable there is only one owner at a time. Typically, the contract’s owner is the account that deploys the contract. As a result, the owner is able to perform certain privileged activities.
onlyOwner
functions;
ActivePool.sol: function setAddresses(...) external onlyOwner function setYieldingPercentage(address _collateral, uint256 _bps) external onlyOwner function setYieldingPercentageDrift(uint256 _driftBps) external onlyOwner function setYieldClaimThreshold(address _collateral, uint256 _threshold) external onlyOwner function setYieldDistributionParams(uint256 _treasurySplit, uint256 _SPSplit, uint256 _stakingSplit) external onlyOwner function manualRebalance(address _collateral, uint256 _simulatedAmountLeavingPool) external onlyOwner CollateralConfig.sol: function initialize(...) onlyOwner function updateCollateralRatios() onlyOwner PriceFeed.sol: function setAddresses(...) external onlyOwner function updateChainlinkAggregator(...) external onlyOwner function updateTellorCaller(...) external onlyOwner /LQTY/CommunityIssuance.sol function setAddresses(...) external onlyOwner function fund(uint amount) external onlyOwner function updateDistributionPeriod(uint256 _newDistributionPeriod) external onlyOwner
The Openzeppelin’s Ownable used in this project contract implements renounceOwnership. This can represent a certain risk if the ownership is renounced for any other reason than by design. Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.
We recommend to either reimplement the function to disable it or to clearly specify if it is part of the contract design.
import "@openzeppelin/contracts/access/Ownable.sol"; contract x is Ownable { function renounceOwnership() public override onlyOwner { revert("Ownership cannot be renounced"); } }
updateTreasury(address newTreasury)
is missing event emits which prevents the intended data from being observed easily by off-chain interfaces.
Add emits when Treasury address is updated.
_newTvlCap
isn't a zero valueAlthough the updateTvlCap()
is checked the msg.sender
has ADMIN role, But still didn't check paramerter _newTvlCap
is not 0
Add a require()
check.
function updateTvlCap(uint256 _newTvlCap) public { _atLeastRole(ADMIN); require(_newTvlCap != 0); tvlCap = _newTvlCap; emit TvlCapUpdated(tvlCap); }
Allowing zero-addresses will lead to contract reverts and force redeployments if there are no setters for such address variables.
constructor( address _token, string memory _name, string memory _symbol, uint256 _tvlCap, address _treasury, address[] memory _strategists, address[] memory _multisigRoles ) ERC20(string(_name), string(_symbol)) { token = IERC20Metadata(_token); constructionTime = block.timestamp; lastReport = block.timestamp; tvlCap = _tvlCap; treasury = _treasury;
// See comments on ReaperVaultV2's constructor constructor( address _token, string memory _name, string memory _symbol, uint256 _tvlCap, address _treasury, address[] memory _strategists, address[] memory _multisigRoles ) ReaperVaultV2(_token, _name, _symbol, _tvlCap, _treasury, _strategists, _multisigRoles) {}
Add zero-address checks for all initializations/setters of all address state variables.
require(_treasury != address(0), "The address cannot be zero"); treasury = _treasury;
To save some gas:
assembly { if iszero(_treasury) { mstore(0x00, "zero address") revert(0x00, 0x20) } } treasury = _treasury;
Missing _token for the zero address check.
function inCaseTokensGetStuck(address _token) external { _atLeastRole(ADMIN); require(_token != address(token), "!token"); uint256 amount = IERC20Metadata(_token).balanceOf(address(this)); IERC20Metadata(_token).safeTransfer(msg.sender, amount); emit InCaseTokensGetStuckCalled(_token, amount); }
function __ReaperBaseStrategy_init( address _vault, address _want, address[] memory _strategists, address[] memory _multisigRoles ) internal onlyInitializing { __UUPSUpgradeable_init(); __AccessControlEnumerable_init(); vault = _vault; want = _want;
function initialize( address _vault, address[] memory _strategists, address[] memory _multisigRoles, IAToken _gWant ) public initializer { gWant = _gWant; want = _gWant.UNDERLYING_ASSET_ADDRESS(); __ReaperBaseStrategy_init(_vault, want, _strategists, _multisigRoles);
Add zero-address checks for all function of all address state variables.
function inCaseTokensGetStuck(address _token) external { _atLeastRole(ADMIN); require(_token != address(0), "The address cannot be zero"); require(_token != address(token), "!token"); uint256 amount = IERC20Metadata(_token).balanceOf(address(this)); IERC20Metadata(_token).safeTransfer(msg.sender, amount); emit InCaseTokensGetStuckCalled(_token, amount); }
To save some gas:
function inCaseTokensGetStuck(address _token) external { _atLeastRole(ADMIN); assembly { if iszero(_token) { mstore(0x00, "zero address") revert(0x00, 0x20) } } require(_token != address(token), "!token"); uint256 amount = IERC20Metadata(_token).balanceOf(address(this)); IERC20Metadata(_token).safeTransfer(msg.sender, amount); emit InCaseTokensGetStuckCalled(_token, amount); }
Since it carries over unclaimed rewards from the previous reward program. It would be safer to keep the reward token immutable as a safeguard against violations of this condition.
address[] public rewardClaimingTokens;
The easiest mitigation would be to pass this variable as immutable
address[] public immutable rewardClaimingTokens;
The default function visibility level in contracts is public, in interfaces - external. Explicitly define function visibility to prevent confusion. https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/BorrowerOperations.sol#L28-L32
address stabilityPoolAddress; address gasPoolAddress; ICollSurplusPool collSurplusPool;
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L31
address gasPoolAddress;
It is recommended to make a conscious decision on which visibility type is appropriate for a function. This can dramatically reduce the attack surface of a contract system.
Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile locally. https://swcregistry.io/docs/SWC-103
Affected instance: https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol#L3
1 result - 1 file File: Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol 1: // SPDX-License-Identifier: BUSL1.1 2: 3: pragma solidity ^0.8.0;
Lock pragmas to specific compiler version. solidity-specific/locking-pragmas
Error message miss caller is redemptionHelper.
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/LQTY/LQTYStaking.sol#L257
function _requireCallerIsTroveManagerOrActivePool() internal view { address redemptionHelper = address(ITroveManager(troveManagerAddress).redemptionHelper()); require( msg.sender == troveManagerAddress || msg.sender == redemptionHelper || msg.sender == activePoolAddress, "LQTYStaking: caller is not TroveM or ActivePool" // @audit error message miss caller is redemptionHelper. );
function _chainID() private pure returns (uint256 chainID) { assembly { chainID := chainid() } }
function _chainID() private pure returns (uint256 chainID) { chainID = 0; assembly { chainID := chainid() } }
_requireNonZeroAmount
instead of if (_amount !=0)
Origin source code:
if (_amount !=0) {_requireNoUnderCollateralizedTroves();}
_requireNonZeroAmount(_amount); _requireNoUnderCollateralizedTroves();
Lots of require check without error messages. some examples below:
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L716
require(_troveArray.length != 0);
require(_troveArray.length != 0, "TroveManager: Calldata address array must not be empty");
https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/TroveManager.sol#L754
require(totals.totalDebtInSequence > 0);
require(totals.totalDebtInSequence > 0, "TroveManager: nothing to liquidate");
Use temporary TODOs as you work on a feature, but make sure to treat them before merging. Either add a link to a proper issue in your TODO, or remove it from the code
File: Ethos-Core/contracts/StabilityPool.sol 335: /* TODO tess3rac7 unused var, but previously included in ETHGainWithdrawn event log. 336: * Doesn't make a lot of sense to include in multiple CollateralGainWithdrawn logs. 337: * If needed could create a separate event just to report this. 338: */
File: Ethos-Core/contracts/StabilityPool.sol 380: /* TODO tess3rac7 unused var, but previously included in ETHGainWithdrawn event log. 381: * Doesn't make a lot of sense to include in multiple CollateralGainWithdrawn logs. 382: * If needed could create a separate event just to report this. 383: */
require
instead of assert
File: Ethos-Core/contracts/BorrowerOperations.sol 301: assert(msg.sender == _borrower || (msg.sender == stabilityPoolAddress && _collTopUp > 0 && _LUSDChange == 0));
File: Ethos-Core/contracts/LUSDToken.sol 321: assert(account != address(0));
File: Ethos-Core/contracts/LUSDToken.sol 312: assert(sender != address(0)); 313: assert(recipient != address(0));
Assert should not be used except for tests, require should be used.
Prior to Solidity 0.8.0, pressing a confirm consumes the remainder of the process’s available gas instead of returning it, as request()/revert() did.
assert() and require(); The big difference between the two is that the assert()function when false, uses up all the remaining gas and reverts all the changes made. Meanwhile, a require() function when false, also reverts back all the changes made to the contract but does refund all the remaining gas fees we offered to pay. This is the most common Solidity function used by developers for debugging and error handling.
Assertion() should be avoided even after solidity version 0.8.0, because its documentation states “The Assert function generates an error of type Panic(uint256).Code that works properly should never Panic, even on invalid external input. If this happens, you need to fix it in your contract. There’s a mistake”.
File: Ethos-Core/contracts/TroveManager.sol 53: uint constant public MINUTE_DECAY_FACTOR = 999037758833783000;
File: Ethos-Vault/contracts/ReaperVaultV2.sol 41: uint256 public constant PERCENT_DIVISOR = 10000;
There is occasions where certain number have been hardcoded, either in variable or in the code itself. Large numbers can become hard to read.
Consider using underscores for number literals to improve its readability.like this
File: Ethos-Vault/contracts/ReaperVaultV2.sol - 41: uint256 public constant PERCENT_DIVISOR = 10000; + uint256 public constant PERCENT_DIVISOR = 10_000
here the commented out portions serves no purpose at all,consider removing these TroveManager.sol#L14
File: Ethos-Core/contracts/TroveManager.sol 14: // import "./Dependencies/Ownable.sol";
File: Ethos-Core/contracts/TroveManager.sol 18: contract TroveManager is LiquityBase, /*Ownable,*/ CheckContract, ITroveManager { 19: // string constant public NAME = "TroveManager";
#0 - c4-judge
2023-03-10T16:56:51Z
trust1995 marked the issue as grade-b
#1 - c4-sponsor
2023-03-29T01:01:20Z
0xBebis marked the issue as sponsor acknowledged
🌟 Selected for report: c3phas
Also found by: 0x3b, 0x6980, 0x73696d616f, 0xSmartContract, 0xackermann, 0xhacksmithh, 0xsomeone, Bnke0x0, Bough, Budaghyan, Darshan, DeFiHackLabs, Deivitto, GalloDaSballo, JCN, LethL, Madalad, MiniGlome, Morraez, P-384, PaludoX0, Phantasmagoria, Praise, RHaO-sec, Rageur, RaymondFam, ReyAdmirado, Rickard, Rolezn, SaeedAlipoor01988, Saintcode_, Sathish9098, TheSavageTeddy, Tomio, Viktor_Cortess, abiih, arialblack14, atharvasama, banky, codeislight, cryptonue, ddimitrov22, dec3ntraliz3d, descharre, dharma09, emmac002, favelanky, hl_, hunter_w3b, kaden, kodyvim, matrix_0wl, oyc_109, pavankv, scokaf, seeu, yamapyblack
42.0697 USDC - $42.07
Issue | Instances | |
---|---|---|
[G-1] | Setting the constructor to payable saves gas (sayan) | 2 |
[G-2] | Mark functions as payable (with discretion) | 49 |
[G-3] | Use assembly for math (add, sub, mul, div) | 2 |
[G-4] | Short Revert Strings | 8 |
[G-5] | Use assembly to write storage values | 13 |
[G-6] | Use multiple require() statments insted of require(expression && expression && …) | 4 |
[G-7] | Optimal Comparison | 8 |
[G-8] | Tightly pack storage variables | 2 |
[G-9] | Instead of if(x), use assembly with iszeroiszero (x)(secoalba) | 12 |
[G-1] Setting the constructor
to payable
saves gas (sayan)
Saves ~13 gas per instance
2 instances File: Ethos-Vault/contracts/ReaperVaultV2.sol 111: constructor( 112: address _token, 113: string memory _name, 114: string memory _symbol, 115: uint256 _tvlCap, 116: address _treasury, 117: address[] memory _strategists, 118: address[] memory _multisigRoles 119: ) ERC20(string(_name), string(_symbol)) { File: Ethos-Vault/contracts/ReaperVaultERC4626.sol 16: constructor( 17: address _token, 18: string memory _name, 19: string memory _symbol, 20: uint256 _tvlCap, 21: address _treasury, 22: address[] memory _strategists, 23: address[] memory _multisigRoles 24: ) ReaperVaultV2(_token, _name, _symbol, _tvlCap, _treasury, _strategists, _multisigRoles) {}
[G-2] Mark functions as payable (with discretion)
You can mark public or external functions as payable to save gas. Functions that are not payable have additional logic to check if there was a value sent with a call, however, making a function payable eliminates this check. This optimization should be carefully considered due to potentially unwanted behavior when a function does not need to accept ether.
Saves ~24 gas per instance
49 instances File; Ethos-Core/contracts/TroveManager.sol 225: constructor() public { 232: function setAddresses( 299: function getTroveOwnersCount(address _collateral) external view override returns (uint) { 303: function getTroveFromTroveOwnersArray(address _collateral, uint _index) external view override returns (address) { 310: function liquidate(address _borrower, address _collateral) external override { 513: function liquidateTroves(address _collateral, uint _n) external override { 715: function batchLiquidateTroves(address _collateral, address[] memory _troveArray) public override { 939: function redeemCloseTrove( 962: function updateDebtAndCollAndStakesPostRedemption( 982: function burnLUSDAndEmitRedemptionEvent( 1016: function redeemCollateral( 1045: function getNominalICR(address _borrower, address _collateral) public view override returns (uint) { 1054: function getCurrentICR( 1076: function applyPendingRewards(address _borrower, address _collateral) external override { 1111: function updateTroveRewardSnapshots(address _borrower, address _collateral) external override { 1123: function getPendingCollateralReward(address _borrower, address _collateral) public view override returns (uint) { 1138: function getPendingLUSDDebtReward(address _borrower, address _collateral) public view override returns (uint) { 1152: function hasPendingRewards(address _borrower, address _collateral) public view override returns (bool) { 1164: function getEntireDebtAndColl( 1183: function removeStake(address _borrower, address _collateral) external override { 1195: function updateStakeAndTotalStakes(address _borrower, address _collateral) external override returns (uint) { 1273: function closeTrove(address _borrower, address _collateral, uint256 _closedStatusNum) external override { 1316: function addTroveOwnerToArray(address _borrower, address _collateral) external override returns (uint index) { 1361: function getTCR(address _collateral, uint _price) external view override returns (uint) { 1366: function checkRecoveryMode(address _collateral, uint _price) external view override returns (bool) { 1397: function updateBaseRateFromRedemption( 1425: function getRedemptionRate() public view override returns (uint) { 1440: function getRedemptionFee(uint _collateralDrawn) public view override returns (uint) { 1444: function getRedemptionFeeWithDecay(uint _collateralDrawn) external view override returns (uint) { 1456: function getBorrowingRate() public view override returns (uint) { 1460: function getBorrowingRateWithDecay() public view override returns (uint) { 1471: function getBorrowingFee(uint _LUSDDebt) external view override returns (uint) { 1475: function getBorrowingFeeWithDecay(uint _LUSDDebt) external view override returns (uint) { 1485: function decayBaseRateFromBorrowing() external override { 1544: function getTroveStatus(address _borrower, address _collateral) external view override returns (uint) { 1556: function getTroveColl(address _borrower, address _collateral) external view override returns (uint) { 1562: function setTroveStatus(address _borrower, address _collateral, uint _num) external override { 1567: function increaseTroveColl(address _borrower, address _collateral, uint _collIncrease) external override returns (uint) { 1574: function decreaseTroveColl(address _borrower, address _collateral, uint _collDecrease) external override returns (uint) { 1588: function decreaseTroveDebt(address _borrower, address _collateral, uint _debtDecrease) external override returns (uint) { File: Ethos-Vault/contracts/abstract/ReaperBaseStrategyV4.sol 95: function withdraw(uint256 _amount) external override returns (uint256 loss) { 109: function harvest() external override returns (int256 roi) { 156: function setEmergencyExit() external { 167: function initiateUpgradeCooldown() external {
[G-3] Use assembly for math (add, sub, mul, div)
Use assembly for math instead of Solidity. You can check for overflow/underflow in assembly to ensure safety. If using Solidity versions < 0.8.0 and you are using Safemath, you can gain significant gas savings by using assembly to calculate values and checking for overflow/underflow.
Saves ~40 gas per instance
2 instances File: Ethos-Core/contracts/TroveManager.sol 54: uint constant public override REDEMPTION_FEE_FLOOR = DECIMAL_PRECISION / 1000 * 5; // 0.5% 55: uint constant public MAX_BORROWING_FEE = DECIMAL_PRECISION / 100 * 5; // 5%
[G-4] Short Revert Strings
Keeping revert strings under 32-bytes prevents the string from being stored in more than one memory slot.
Saves ~3 gas per instance
8 instances File: Ethos-Core/contracts/StabilityPool.sol 849: require( msg.sender == address(activePool), "StabilityPool: Caller is not ActivePool"); 853: require(msg.sender == address(troveManager), "StabilityPool: Caller is not TroveManager"); 865: require(ICR >= collMCR, "StabilityPool: Cannot withdraw while there are troves with ICR < MCR"); 870: require(_initialDeposit > 0, 'StabilityPool: User must have a non-zero deposit'); 874: require(_amount > 0, 'StabilityPool: Amount must be non-zero'); File: Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol 81: require(_multisigRoles.length == 3, "Invalid number of multisig roles"); 98: require(_amount <= balanceOf(), "Ammount must be less than balance"); 193: require( upgradeProposalTime + UPGRADE_TIMELOCK < block.timestamp, "Upgrade cooldown not initiated or still ongoing"
[G-5] Use assembly to write storage values
Saves ~66 gas per instance
13 instances File; Ethos-Core/contracts/TroveManager.sol 227: owner = msg.sender; 266: borrowerOperationsAddress = _borrowerOperationsAddress; 271: gasPoolAddress = _gasPoolAddress; 294: owner = address(0); 1417: baseRate = newBaseRate; 1504: lastFeeOperationTime = block.timestamp; File: Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol 72: vault = _vault; 73: want = _want; 137: lastHarvestTimestamp = block.timestamp; 158: emergencyExit = true; 169: IVault(vault).revokeStrategy(address(this)); 181: upgradeProposalTime = block.timestamp + FUTURE_NEXT_PROPOSAL_TIME;
[G-6] Use multiple require() statments insted of require(expression && expression && …)
You can safe gas by breaking up a require statement with multiple conditions, into multiple require statements with a single condition.
Saves ~16 gas per instance
4 instances File: Ethos-Core/contracts/BorrowerOperations.sol 653: require(_maxFeePercentage >= BORROWING_FEE_FLOOR && _maxFeePercentage <= DECIMAL_PRECISION, File: Ethos-Core/contracts/LUSDToken.sol 347: require( 352: require( File: Ethos-Core/contracts/TroveManager.sol 1539: require (TroveOwnersArrayLength > 1 && sortedTroves.getSize(_collateral) > 1);
[G-7] Optimal Comparison
When comparing integers, it is cheaper to use strict > & < operators over >= & <= operators, even if you must increment or decrement one of the operands. Note: before using this technique, it’s important to consider whether incrementing/decrementing one of the operators could result in an over/underflow. This optimization is applicable when the optimizer is turned off.
Saves ~3 gas per instance
8 instances File: Ethos-Core/contracts/TroveManager.sol 372: if (TroveOwners[_collateral].length <= 1) {return singleLiquidation;} // don't liquidate if last trove 383: if (_ICR <= _100pct) { 415: } else if ((_ICR >= _MCR) && (_ICR < _TCR) && (singleLiquidation.entireTroveDebt <= _LUSDInStabPool)) { 616: if (vars.ICR >= vars.collMCR && vars.remainingLUSDInStabPool == 0) { break; } 817: if (vars.ICR >= vars.collMCR && vars.remainingLUSDInStabPool == 0) { continue; } 1348: assert(index <= idxLast); 1489: assert(decayedBaseRate <= DECIMAL_PRECISION); // The baseRate can decay to 0 1503: if (timePassed >= SECONDS_IN_ONE_MINUTE) {
[G-8] Tightly pack storage variables
When defining storage variables, make sure to declare them in ascending order, according to size. When multiple variables are able to fit into one 256 bit slot, this will save storage size and gas during runtime. For example, if you have a bool, uint256 and a bool, instead of defining the variables in the previously mentioned order, defining the two boolean variables first will pack them both into one storage slot since they only take up one byte of storage.
Saves ~17,298 gas per instance
2 instances File: Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol 19: contract ReaperStrategyGranarySupplyOnly is ReaperBaseStrategyv4, VeloSolidMixin { File: Ethos-Vault/contracts/ReaperVaultV2.sol 21: contract ReaperVaultV2 is ReaperAccessControl, ERC20, IERC4626Events, AccessControlEnumerable, ReentrancyGuard {
[G-9] Instead of if(x), use assembly with iszeroiszero (x)(secoalba)
Saves ~5 gas per instance
12 instances File: Ethos-Core/contracts/BorrowerOperations.sol 189: if (!isRecoveryMode) { 202: if (isRecoveryMode) { 292: if (_isDebtIncrease) { 311: if (_isDebtIncrease && !isRecoveryMode) { 337: if (!_isDebtIncrease && _LUSDChange > 0) { 490: if (_isDebtIncrease) { 496: if (_isCollIncrease) { 597: if (_isDebtIncrease) { 649: if (_isRecoveryMode) { File: Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol 75: if (emergencyExit) { File: Ethos-Vault/contracts/ReaperVaultV2.sol 604: if (_active) {
#0 - c4-judge
2023-03-09T18:19:34Z
trust1995 marked the issue as grade-b