You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable in memory: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.
In particular, in for loops, when using the length of a storage array as the condition being checked after each loop, caching the array length in memory can yield significant gas savings if the array length is high
Calldata instead of memory for RO function parameters
PROBLEM
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory.
Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
Try to use calldata as a data location because it will avoid copies and also makes sure that the data cannot be modified.
However, if 1 is negligible compared to the value of the variable, we can omit the increment.
Constant should be used for storage variables that are not modified
IMPACT
Constant variables are replaced at compile time by their values.
If a state variable is never modified, it should be specified as constant to save a SLOAD operation when it is read, saving 97 gas.
PROOF OF CONCEPT
Instances include:
BalancerV2LPOracle.sol
BalancerV2LPOracle.sol:18: uint256 public override version = 3;
GUniLPOracle.sol
GUniLPOracle.sol:16: uint256 public override version = 3;
TOOLS USED
Manual Analysis
MITIGATION
Add the constant modifier to these variables.
Constructor parameters should be avoided when possible
IMPACT
Constructor parameters are expensive. The contract deployment will be cheaper in gas if storage variables are hard coded instead of using constructor parameters. It can save approximately 670 gas per storage variable concerned upon deployment (~400 if the optimizer is enabled).
Hardcode these storage variables with their value instead of writing them during contract deployment with constructor parameters.
Custom Errors
IMPACT
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained here
Custom errors are defined using the error statement
ChainlinkInceptionPriceFeed.sol:75: require(eurAnswer > 0, "EUR price data not valid");
ChainlinkInceptionPriceFeed.sol:76: require(block.timestamp - eurUpdatedAt < _PRICE_ORACLE_STALE_THRESHOLD, "EUR price data is stale");
ChainlinkInceptionPriceFeed.sol:79: require(answer > 0, "Price data not valid");
ChainlinkInceptionPriceFeed.sol:80: require(block.timestamp - assetUpdatedAt < _PRICE_ORACLE_STALE_THRESHOLD, "Price data is stale");
Replace require and revert statements with custom errors.
For instance, in DexAddressProvider.sol:
Replace
require(_proxy_ != address(0), "LM000");
with
if (_proxy == address(0)) {
revert ErrorNullAddress(_proxy);
}
and define the custom error in the contract
error ErrorNullAddress(address _address);
Default value initialization
IMPACT
If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type).
Explicitly initializing it with its default value is an anti-pattern and wastes gas.
When possible, emit the function parameter that is written in the storage variable instead of the storage variable itself to save gas.
In cases where the storage variable is read multiple times in the function, it is recommended to cache it into memory, then passing these cached variable in the emit statement, as explained in the cache paragraph
Prefix increments
IMPACT
Prefix increments are cheaper than postfix increments.
PROOF OF CONCEPT
Instances include:
DexAddressProvider.sol
DexAddressProvider.sol:16: i++
AdminInceptionVault.sol
AdminInceptionVault.sol:108: i++
TOOLS USED
Manual Analysis
MITIGATION
change variable++ to ++variable.
Require instead of AND
IMPACT
Require statements including conditions with the && operator can be broken down in multiple require statements to save gas.
Since the version 0.8.0 update, overflow and underflow checks are performed automatically in Solidity. It is not necessary and costs additional gas to use the external library SafeMath to perform additions and subtractions
Do not use this library for additions and subtractions
Tight Variable Packing
PROBLEM
Solidity contracts have contiguous 32 bytes (256 bits) slots used in storage.
By arranging the variables, it is possible to minimize the number of slots used within a contract's storage and therefore reduce deployment costs.
string type variables are each of 20 bytes size (way less than 32 bytes). However, they here take up a whole 32 bytes slot (they are contiguous).
As bool type variables are of size 1 byte, there's a slot here that can get saved by moving them.
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an unchecked block will save gas
PROOF OF CONCEPT
Instances include:
SuperVault.sol
SuperVault.sol:110: token.balanceOf(address(this)) is greater than flashloanRepayAmount (see condition one line above), underflow check unnecessary
TOOLS USED
Manual Analysis
MITIGATION
Place the arithmetic operations in an unchecked block
The text was updated successfully, but these errors were encountered:
Gas Report
Table of Contents
Caching storage variables in memory to save gas
IMPACT
Anytime you are reading from storage more than once, it is cheaper in gas cost to cache the variable in memory: a SLOAD cost 100gas, while MLOAD and MSTORE cost 3 gas.
In particular, in
for
loops, when using the length of a storage array as the condition being checked after each loop, caching the array length in memory can yield significant gas savings if the array length is highPROOF OF CONCEPT
Instances include:
AdminInceptionVault.sol
scope:
withdraw()
_a
is read twice:scope:
claimMimo()
_collateralCount
is read (_collateralCount
) times:number of reads depending on
_collateralCount
as it is in a for loopscope:
claimMimo()
_debtNotifier
is read (_collateralCount
) times:number of reads depending on
_collateralCount
as it is in a for loopscope:
deposit()
_a
is read twice:InceptionVaultsCore.sol
scope:
borrow()
_a
is read three times:_adminInceptionVault
is read twice:_inceptionVaultsData
is read three times:_vaultConfig
is read three times:scope:
liquidatePartial()
_a
is read three times:_adminInceptionVault
is read twice:_inceptionVaultsData
is read three times:_inceptionPriceFeed
is read twice:_vaultConfig
is read four times:scope:
_removeCollateralFromVault()
_inceptionVaultsData
is read three times:scope:
_addCollateralToVaultById()
_inceptionVaultsData
is read twice:scope:
_refreshCumulativeRate()
_cumulativeRat
is read three times:scope:
_reduceVaultDebt()
_inceptionVaultsData
is read twice:ChainlinkInceptionPriceFeed.sol
scope:
getAssetPrice()
_eurOracle
is read twice:GenericMinerV2.sol
scope:
_pendingMIMO()
_totalStakeWithBoost
is read twice:scope:
_pendingPAR()
_totalStakeWithBoost
is read twice:scope:
_getBoostMultiplier()
_boostConfig
is read six times:PARMinerV2.sol
scope:
liquidate()
_par
is read three times:_a
is read twice:scope:
_increaseStake()
__accParAmountPerShare
is read twice:scope:
_pendingMIMO()
_totalStakeWithBoost
is read twice:scope:
_getBoostMultiplier()
_boostConfig
is read six times:BalancerV2LPOracle.sol
scope:
latestRoundData()
pool
is read twice:SuperVault.sol
scope:
executeOperation()
lendingPool
is read twice:scope:
emptyVaultOperation()
a
is read five times:scope:
emptyVault()
a
is read twice:scope:
withdrawFromVault()
a
is read twice:scope:
borrowFromVault()
a
is read three times:scope:
depositToVault()
a
is read twice:scope:
depositAndBorrowFromVault()
a
is read four times:scope:
depositETHAndBorrowFromVault()
a
is read three times:scope:
leverageSwap()
a
is read three times:scope:
checkAndSendMIMO()
ga
is read three times:TOOLS USED
Manual Analysis
MITIGATION
cache these storage variables in memory
Calldata instead of memory for RO function parameters
PROBLEM
If a reference type function parameter is read-only, it is cheaper in gas to use calldata instead of memory.
Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory.
Try to use calldata as a data location because it will avoid copies and also makes sure that the data cannot be modified.
PROOF OF CONCEPT
Instances include:
InceptionVaultsCore.sol
scope:
initialize()
DemandMinerV2.sol
scope:
initialize()
GenericMinerV2.sol
scope:
setBoostConfig()
scope:
_releaseRewards()
scope:
_updateBoost()
PARMinerV2.sol
scope:
setBoostConfig()
scope:
_releaseRewards()
scope:
_updateBoost()
VotingMinerV2.sol
scope:
releaseMIMO()
scope:
_syncStake()
SuperVault.sol
scope:
leverageOperation()
scope:
rebalanceOperation()
scope:
emptyVaultOperation()
scope:
leverageSwap()
scope:
aggregatorSwap()
scope:
takeFlashLoan()
TOOLS USED
Manual Analysis
MITIGATION
Replace
memory
withcalldata
Comparisons with zero for unsigned integers
IMPACT
>0
is less gas efficient than!= 0
if you enable the optimizer at 10k AND you’re in a require statement.Detailed explanation with the opcodes here
PROOF OF CONCEPT
Instances include:
InceptionVaultsCore.sol
ChainlinkInceptionPriceFeed.sol
GenericMinerV2.sol
PARMinerV2.sol
GUniLPOracle.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace
> 0
with!= 0
Comparison Operators
IMPACT
In the EVM, there is no opcode for
>=
or<=
.When using greater than or equal, two operations are performed:
>
and=
.Using strict comparison operators hence saves gas
PROOF OF CONCEPT
Instances include:
InceptionVaultsCore.sol
GenericMinerV2.sol
PARMinerV2.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace
<=
with<
, and>=
with>
. Do not forget to increment/decrement the compared variableexample:
However, if
1
is negligible compared to the value of the variable, we can omit the increment.Constant should be used for storage variables that are not modified
IMPACT
Constant variables are replaced at compile time by their values.
If a state variable is never modified, it should be specified as
constant
to save aSLOAD
operation when it is read, saving 97 gas.PROOF OF CONCEPT
Instances include:
BalancerV2LPOracle.sol
GUniLPOracle.sol
TOOLS USED
Manual Analysis
MITIGATION
Add the
constant
modifier to these variables.Constructor parameters should be avoided when possible
IMPACT
Constructor parameters are expensive. The contract deployment will be cheaper in gas if storage variables are hard coded instead of using constructor parameters. It can save approximately 670 gas per storage variable concerned upon deployment (~400 if the optimizer is enabled).
PROOF OF CONCEPT
Instances include:
DexAddressProvider.sol
InceptionVaultFactory.sol
DemandMinerV2.sol
GenericMinerV2.sol
PARMinerV2.sol
SupplyMinerV2.sol
BalancerV2LPOracle.sol
GUniLPOracle.sol
SuperVaultFactory.sol
TOOLS USED
Manual Analysis
MITIGATION
Hardcode these storage variables with their value instead of writing them during contract deployment with constructor parameters.
Custom Errors
IMPACT
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met) while providing the same amount of information, as explained here
Custom errors are defined using the error statement
PROOF OF CONCEPT
Instances include:
DexAddressProvider.sol
InceptionVaultFactory.sol
InceptionVaultFactory.sol
InceptionVaultDataProvider.sol
ChainlinkInceptionPriceFeed.sol
DemandMinerV2.sol
GenericMinerV2.sol
PARMinerV2.sol
SupplyMinerV2.sol
VotingMinerV2.sol
BalancerV2LPOracle.sol
GUniLPOracle.sol
SuperVault.sol
TOOLS USED
Manual Analysis
MITIGATION
Replace require and revert statements with custom errors.
For instance, in
DexAddressProvider.sol
:Replace
with
and define the custom error in the contract
Default value initialization
IMPACT
If a variable is not set/initialized, it is assumed to have the default value (0, false, 0x0 etc depending on the data type).
Explicitly initializing it with its default value is an anti-pattern and wastes gas.
PROOF OF CONCEPT
Instances include:
InceptionVaultFactory.sol
TOOLS USED
Manual Analysis
MITIGATION
Remove explicit initialization for default values.
Event emitting of local variable
PROBLEM
When emitting an event, using a local variable instead of a storage variable saves gas.
PROOF OF CONCEPT
Instances include:
InceptionVaultFactory.sol
GenericMinerV2.sol
PARMinerV2.sol
TOOLS USED
Manual Analysis
MITIGATION
When possible, emit the function parameter that is written in the storage variable instead of the storage variable itself to save gas.
In cases where the storage variable is read multiple times in the function, it is recommended to cache it into memory, then passing these cached variable in the
emit
statement, as explained in the cache paragraphPrefix increments
IMPACT
Prefix increments are cheaper than postfix increments.
PROOF OF CONCEPT
Instances include:
DexAddressProvider.sol
AdminInceptionVault.sol
TOOLS USED
Manual Analysis
MITIGATION
change
variable++
to++variable
.Require instead of AND
IMPACT
Require statements including conditions with the
&&
operator can be broken down in multiple require statements to save gas.PROOF OF CONCEPT
Instances include:
GenericMinerV2.sol
PARMinerV2.sol
SuperVault.sol
TOOLS USED
Manual Analysis
MITIGATION
Break down the single require statement in multiple require statements
e.g:
SafeMath not necessary everywhere
IMPACT
Since the version 0.8.0 update, overflow and underflow checks are performed automatically in Solidity. It is not necessary and costs additional gas to use the external library
SafeMath
to perform additions and subtractionsPROOF OF CONCEPT
Instances include:
InceptionVaultsCore.sol
TOOLS USED
Manual Analysis
MITIGATION
Do not use this library for additions and subtractions
Tight Variable Packing
PROBLEM
Solidity contracts have contiguous 32 bytes (256 bits) slots used in storage.
By arranging the variables, it is possible to minimize the number of slots used within a contract's storage and therefore reduce deployment costs.
string type variables are each of 20 bytes size (way less than 32 bytes). However, they here take up a whole 32 bytes slot (they are contiguous).
As bool type variables are of size 1 byte, there's a slot here that can get saved by moving them.
PROOF OF CONCEPT
Instances include:
IInceptionVaultFactory.sol
TOOLS USED
Manual Analysis
MITIGATION
Place
isCustomPriceFeed
afterowner
to save one storage slotUnchecked arithmetic
IMPACT
The default "checked" behavior costs more gas when adding/diving/multiplying, because under-the-hood those checks are implemented as a series of opcodes that, prior to performing the actual arithmetic, check for under/overflow and revert if it is detected.
if it can statically be determined there is no possible way for your arithmetic to under/overflow (such as a condition in an if statement), surrounding the arithmetic in an
unchecked
block will save gasPROOF OF CONCEPT
Instances include:
SuperVault.sol
TOOLS USED
Manual Analysis
MITIGATION
Place the arithmetic operations in an
unchecked
blockThe text was updated successfully, but these errors were encountered: