-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
331 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,331 @@ | ||
## Summary | ||
|
||
| | Issues | Contexts | Estimated Gas Saved | | ||
| ------ | ---------------------------------------------------------------------------------------------------------- | -------- | ------------------- | | ||
| [G-01] | Setting the constructor to `payable` | 6 | - | | ||
| [G-02] | `<x> += <y>` Costs More Gas Than `<x> = <x> + <y>` For State Variables | 22 | - | | ||
| [G-03] | Empty blocks should be removed or emit something | 2 | - | | ||
| [G-04] | Use a more recent version of solidity | 5 | - | | ||
| [G-05] | Splitting `require()` statements that use `&&` saves gas | 5 | - | | ||
| [G-06] | Instead of emitting multiple events with a single value, emit a single event with multiple values | 38 | - | | ||
| [G-07] | Expressions for constant values such as a call to `keccak256()`, should use immutable rather than constant | 8 | - | | ||
| [G-08] | Use `require` instead of `assert` | 20 | - | | ||
| [G-09] | Internal/Private functions only called once can be inlined to save gas | 22 | - | | ||
| [G-10] | Structs can be packed into fewer storage slots | 8 | - | | ||
| [G-11] | Checking `vars.compositeDebt` to not be zero is redundant | 1 | - | | ||
|
||
## [G-01] 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. | ||
|
||
**Context** | ||
|
||
6 results - 6 files | ||
|
||
Ethos-Core/contracts/LUSDToken.sol: | ||
84: constructor | ||
|
||
Ethos-Core/contracts/TroveManager.sol: | ||
225: constructor() public { | ||
|
||
Ethos-Core/contracts/LQTY/CommunityIssuance.sol: | ||
57: constructor() public { | ||
|
||
Ethos-Vault/contracts/ReaperVaultERC4626.sol: | ||
16: constructor( | ||
|
||
Ethos-Vault/contracts/ReaperVaultV2.sol: | ||
111: constructor( | ||
|
||
Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol: | ||
61: constructor() initializer {} | ||
|
||
## [G-02] `<x> += <y>` Costs More Gas Than `<x> = <x> + <y>` For State Variables | ||
|
||
8 results - 2 files | ||
|
||
Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol: | ||
133: toFree += profit; | ||
|
||
Ethos-Vault/contracts/ReaperVaultV2.sol: | ||
168: totalAllocBPS += _allocBPS; | ||
196: totalAllocBPS += _allocBPS; | ||
391: totalLoss += loss; | ||
450: stratParams.losses += loss; | ||
505: strategy.gains += vars.gain; | ||
520: strategy.allocated += vars.credit; | ||
521: totalAllocated += vars.credit; | ||
|
||
14 results - 3 files | ||
|
||
Ethos-Vault/contracts/ReaperStrategyGranarySupplyOnly.sol: | ||
141: roi -= int256(loss); | ||
|
||
Ethos-Vault/contracts/ReaperVaultV2.sol: | ||
194: totalAllocBPS -= strategies[_strategy].allocBPS; | ||
214: totalAllocBPS -= strategies[_strategy].allocBPS; | ||
390: value -= loss; | ||
395: strategies[stratAddr].allocated -= actualWithdrawn; | ||
396: totalAllocated -= actualWithdrawn; | ||
444: stratParams.allocBPS -= bpsChange; | ||
445: totalAllocBPS -= bpsChange; | ||
451: stratParams.allocated -= loss; | ||
452: totalAllocated -= loss; | ||
514: strategy.allocated -= vars.debtPayment; | ||
515: totalAllocated -= vars.debtPayment; | ||
516: vars.debt -= vars.debtPayment; // tracked for return value | ||
|
||
Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol: | ||
128: repayment -= uint256(-roi); | ||
|
||
## [G-03] Empty blocks should be removed or emit something | ||
|
||
The code should be refactored such that they no longer exist, or the block should do something useful, such as emitting an event or reverting. If the contract is meant to be extended, the contract should be `abstract` and the function signatures be added without any default implementation. | ||
|
||
2 results - 2 files | ||
|
||
Ethos-Vault/contracts/ReaperVaultERC4626.sol: | ||
24: ) ReaperVaultV2(_token, _name, _symbol, _tvlCap, _treasury, _strategists, _multisigRoles) {} | ||
|
||
Ethos-Vault/contracts/abstract/ReaperBaseStrategyv4.sol: | ||
61: constructor() initializer {} | ||
|
||
## [G-04] Use a more recent version of solidity | ||
|
||
Use a solidity version of at least 0.8 to get default underflow/overflow checks, 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. | ||
|
||
For example, we can avoid using the library safeMath in the following files by using version 0.8+: | ||
|
||
5 results - 5 files | ||
|
||
Ethos-Core/contracts/ActivePool.sol: | ||
3: pragma solidity 0.6.11; | ||
|
||
Ethos-Core/contracts/LUSDToken.sol: | ||
3: pragma solidity 0.6.11; | ||
|
||
Ethos-Core/contracts/StabilityPool.sol: | ||
3: pragma solidity 0.6.11; | ||
|
||
Ethos-Core/contracts/LQTY/CommunityIssuance.sol: | ||
3: pragma solidity 0.6.11; | ||
|
||
Ethos-Core/contracts/LQTY/LQTYStaking.sol: | ||
3: pragma solidity 0.6.11; | ||
|
||
## [G-05] Splitting `require()` statements that use `&&` saves gas | ||
|
||
Instead of using the && operator in a single require statement to check multiple conditions,using multiple require statements with 1 condition per require statement will save 8 GAS per &&. | ||
The gas difference would only be realized if the revert condition is realized(met). | ||
|
||
5 results - 3 files | ||
|
||
Ethos-Core/contracts/BorrowerOperations.sol: | ||
653: require(_maxFeePercentage >= BORROWING_FEE_FLOOR && _maxFeePercentage <= DECIMAL_PRECISION, | ||
|
||
Ethos-Core/contracts/LUSDToken.sol: | ||
348: _recipient != address(0) && | ||
353: !stabilityPools[_recipient] && | ||
354: !troveManagers[_recipient] && | ||
|
||
Ethos-Core/contracts/TroveManager.sol: | ||
1539: require (TroveOwnersArrayLength > 1 && sortedTroves.getSize(_collateral) > 1); | ||
|
||
## [G-06] Instead of emitting multiple events with a single value, emit a single event with multiple values | ||
|
||
There are many events which are emitted only once inside a `setAddress()` function with a single address value. Those functions can be called only once because they have `onlyOwner` modifier and call `_renounceOwnership()` at the end of the function. Thus, such events can be combined into a single event which will save additional gas. | ||
|
||
**Context** | ||
|
||
38 results - 4 files | ||
|
||
Ethos-Core/contracts/BorrowerOperations.sol: | ||
155: emit CollateralConfigAddressChanged(_collateralConfigAddress); | ||
156: emit TroveManagerAddressChanged(_troveManagerAddress); | ||
157: emit ActivePoolAddressChanged(_activePoolAddress); | ||
158: emit DefaultPoolAddressChanged(_defaultPoolAddress); | ||
159: emit StabilityPoolAddressChanged(_stabilityPoolAddress); | ||
160: emit GasPoolAddressChanged(_gasPoolAddress); | ||
161: emit CollSurplusPoolAddressChanged(_collSurplusPoolAddress); | ||
162: emit PriceFeedAddressChanged(_priceFeedAddress); | ||
163: emit SortedTrovesAddressChanged(_sortedTrovesAddress); | ||
164: emit LUSDTokenAddressChanged(_lusdTokenAddress); | ||
165: emit LQTYStakingAddressChanged(_lqtyStakingAddress); | ||
|
||
Ethos-Core/contracts/StabilityPool.sol: | ||
293: emit BorrowerOperationsAddressChanged(_borrowerOperationsAddress); | ||
294: emit CollateralConfigAddressChanged(_collateralConfigAddress); | ||
295: emit TroveManagerAddressChanged(_troveManagerAddress); | ||
296: emit ActivePoolAddressChanged(_activePoolAddress); | ||
297: emit LUSDTokenAddressChanged(_lusdTokenAddress); | ||
298: emit SortedTrovesAddressChanged(_sortedTrovesAddress); | ||
299: emit PriceFeedAddressChanged(_priceFeedAddress); | ||
300: emit CommunityIssuanceAddressChanged(_communityIssuanceAddress); | ||
|
||
Ethos-Core/contracts/TroveManager.sol: | ||
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); | ||
|
||
Ethos-Core/contracts/LQTY/LQTYStaking.sol: | ||
93: emit LQTYTokenAddressSet(_lqtyTokenAddress); | ||
94: emit LQTYTokenAddressSet(_lusdTokenAddress); | ||
95: emit TroveManagerAddressSet(_troveManagerAddress); | ||
96: emit BorrowerOperationsAddressSet(_borrowerOperationsAddress); | ||
97: emit ActivePoolAddressSet(_activePoolAddress); | ||
98: emit CollateralConfigAddressSet(_collateralConfigAddress); | ||
|
||
**Proof of Concept** | ||
|
||
The following test was done in Remix to show the difference between calling a function which emits 3 events, and a function which emits 1 event with 3 passed values: | ||
|
||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
contract Events { | ||
|
||
address public add1; | ||
address public add2; | ||
address public add3; | ||
|
||
|
||
// gas 3 events 107290 | ||
// gas 1 event 105390 | ||
event Add1(address _add1, address _add2, address _add3); | ||
|
||
function setAdd(address _add1, address _add2, address _add3) public { | ||
|
||
add1 = _add1; | ||
add2 = _add2; | ||
add3 = _add3; | ||
|
||
emit Add1(_add1, _add2, _add3); | ||
|
||
} | ||
} | ||
|
||
`950 gas is saved per event. | ||
|
||
## [G-07] Expressions for constant values such as a call to `keccak256()`, should use immutable rather than constant | ||
|
||
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"); | ||
|
||
## [G-08] 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. | ||
|
||
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. | ||
|
||
**Context** | ||
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 | ||
|
||
## [G-09] Internal/Private 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. | ||
|
||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/LQTY/LQTYStaking.sol#L265 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Vault/contracts/ReaperVaultV2.sol#L462 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L438 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L455 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L476 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L533 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L537 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L546 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L551 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L555 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L567 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L571 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L624 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L636 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L661 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/TroveManager.sol#L1082 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/TroveManager.sol#L864 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/TroveManager.sol#L785 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/TroveManager.sol#L671 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/TroveManager.sol#L582 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/TroveManager.sol#L478 | ||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/TroveManager.sol#L1538 | ||
|
||
## [G-10] Structs can be packed into fewer storage slots | ||
|
||
Few of the structs across the contracts can be packed in order to save gas. Check this [tweet](https://twitter.com/0xCygaar/status/1630660838460735489) for a reference. | ||
|
||
**Context** | ||
|
||
8 results - 3 files | ||
|
||
Ethos-Core/contracts/BorrowerOperations.sol: | ||
47: struct LocalVariables_adjustTrove { | ||
66: struct LocalVariables_openTrove { | ||
80: struct ContractsCache { | ||
|
||
Ethos-Core/contracts/StabilityPool.sol: | ||
646: struct LocalVariables_getSingularCollateralGain { | ||
|
||
Ethos-Core/contracts/TroveManager.sol: | ||
129: struct LocalVariables_OuterLiquidationFunction { | ||
146: struct LocalVariables_LiquidationSequence { | ||
160: struct LiquidationValues { | ||
172: struct LiquidationTotals { | ||
|
||
## [G-11] Checking `vars.compositeDebt` to not be zero is redundant | ||
|
||
There is an instance where `assert` check on`vars.compositeDebt` to be above zero is redundant. The `composideDebt` is a sum of LUSD amount + LUSD borrowing fee + LUSD gas comp. and it is impossible to be zero. Furthermore, few lines above the `netDebt` which is part of the `compositeDebt` is checked to be above zero. | ||
|
||
**Context** | ||
|
||
https://github.com/code-423n4/2023-02-ethos/blob/73687f32b934c9d697b97745356cdf8a1f264955/Ethos-Core/contracts/BorrowerOperations.sol#L197 |