Platform: Code4rena
Start Date: 28/04/2022
Pot Size: $50,000 USDC
Total HM: 7
Participants: 43
Period: 5 days
Judge: gzeon
Total Solo HM: 2
Id: 115
League: ETH
Rank: 14/43
Findings: 3
Award: $693.86
π Selected for report: 0
π Solo Findings: 0
π Selected for report: hyh
Also found by: 0xDjango, berndartmueller, cccz, defsec, delfin454000, joestakey, robee
247.8825 USDC - $247.88
Judge has assessed an item in Issue #114 as Medium risk. The relevant finding follows:
PROBLEM All external functions that can be called by users should have comments
SEVERITY Non-Critical
PROOF OF CONCEPT Instances include:
SuperVaultFactory.sol SuperVaultFactory.sol:23: function clone(bytes calldata _initdata) public TOOLS USED Manual Analysis
MITIGATION Add comments to these functions
#0 - gzeoneth
2022-06-05T15:26:41Z
Duplicate of #145
π Selected for report: Dravee
Also found by: 0x1f8b, 0x4non, 0x52, 0xDjango, AlleyCat, Funen, GalloDaSballo, GimelSec, Hawkeye, MaratCerby, Picodes, berndartmueller, cccz, defsec, delfin454000, dipp, hyh, ilan, joestakey, kebabsec, luduvigo, pauliax, peritoflores, robee, rotcivegaf, samruna, shenwilly, sikorico, simon135, sorrynotsorry, unforgiven, z3s
152.6741 USDC - $152.67
Few vulnerabilities were found examining the contracts. The main concerns are with ERC20 methods calls.
It is not recommended to emit events before the end of the computations, as the function might revert based on conditions ahead of the event emission
Non-Critical
Instances include:
DemandMinerV2.sol:88: emit WithdrawFeeReleased(fee); //ERC20 safeTransfer() is called after the emission of this event
Manual Analysis
Place the event emission in the last position in the function.
Setters should emit an event so that Dapps can detect important changes to storage
Non-Critical
Instances include:
DexAddressProvider.sol:33: setDexMapping()
InceptionVaultsDataProvider.sol:63: setCollateralBalance() InceptionVaultsDataProvider.sol:75: setBaseDebt()
PARMiner.sol:82: setLiquidateCallerReward()
Manual Analysis
Emit an event in all setters.
All external functions that can be called by users should have comments
Non-Critical
Instances include:
SuperVaultFactory.sol:23: function clone(bytes calldata _initdata) public
Manual Analysis
Add comments to these functions
uint
is an alias for uint256
.
It is better to use uint256: it brings readability and consistency in the code, and it future proofs it in case of any changes to the alias of uint
Non-Critical
Instances include:
SuperVault.sol:30: uint dexIndex; SuperVault.sol:127: uint dexIndex; SuperVault.sol:143: uint dexIndex; SuperVault.sol:144: uint ; SuperVault.sol:176: uint dexIndex; SuperVault.sol:194: uint dexIndex; SuperVault.sol:223: uint dexIndex; SuperVault.sol:322: uint dexIndex; SuperVault.sol:324: uint ;
Manual Analysis
replace uint
with
uint256
There should be a zero (address or integer) check in all setters
Non-Critical
Instances include:
DemandMinerV2.sol:181: function setFeeCollector(address feeCollector)
Manual Analysis
Add a zero address check to this function.
Some tokens (like USDT) do not work when changing the allowance from an existing non-zero allowance value. They must first be approved for zero and then the actual allowance must be approved. In case such token is used as collateral, it is safer to add a zero approval before changing the allowance in the contract.
Low
Instances include:
PARMinerV2.sol:125: collateralToken.approve(proxy, collateralToken.balanceOf(address(this)));
Manual Analysis
Approve 0 before approving a new value:
+collateralToken.approve(proxy, 0); collateralToken.approve(proxy, collateralToken.balanceOf(address(this)));
In BalancerV2LPOracle.sol
, _getNormalizedBalance()
uses the decimals()
method of the ERC20 token to compute its output. The problem is that decimals()
is not part of the official ERC20 standard and might fail for tokens that do not implement it. While in practice it is very unlikely, as usually most of the tokens implement it, this should still be considered as a valid concern, as it means there is a risk the oracle returns an incorrect price.
Low
Instances include:
BalancerV2LPOracle.sol:126: function _getNormalizedBalance(address token, uint256 balance) internal view returns (uint256) { uint8 decimals = ERC20(token).decimals(); return balance.mul(MathPow.pow(10, 18 - decimals)); }
GUniLPOracle.sol:45-51: uint256 decimalsA = ERC20(_pool.token0()).decimals(); _tokenDecimalsUnitA = 10**decimalsA; _tokenDecimalsOffsetA = 10**(18 - decimalsA); uint256 decimalsB = ERC20(_pool.token1()).decimals(); _tokenDecimalsUnitB = 10**decimalsB; _tokenDecimalsOffsetB = 10**(18 - decimalsB);
Manual Analysis
While this reduces flexibility, the safest solution is to hardcode the value of decimals
in these functions.
293.3066 USDC - $293.31
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
Instances include:
scope: withdraw()
_a
is read twice:AdminInceptionVault.sol:99 AdminInceptionVault.sol:100
scope: claimMimo()
_collateralCount
is read (_collateralCount
) times:
number of reads depending on _collateralCount
as it is in a for loopAdminInceptionVault.sol:108
scope: claimMimo()
_debtNotifier
is read (_collateralCount
) times:
number of reads depending on _collateralCount
as it is in a for loopAdminInceptionVault.sol:108
scope: deposit()
_a
is read twice:AdminInceptionVault.sol:152 AdminInceptionVault.sol:153
scope: borrow()
_a
is read three times:AdminInceptionVault.sol:137 AdminInceptionVault.sol:151 AdminInceptionVault.sol:158
_adminInceptionVault
is read twice:AdminInceptionVault.sol:138 AdminInceptionVault.sol:162
_inceptionVaultsData
is read three times:AdminInceptionVault.sol:139 AdminInceptionVault.sol:153 AdminInceptionVault.sol:156
_vaultConfig
is read three times:AdminInceptionVault.sol:145 AdminInceptionVault.sol:146 AdminInceptionVault.sol:158
scope: liquidatePartial()
_a
is read three times:AdminInceptionVault.sol:205 AdminInceptionVault.sol:226 AdminInceptionVault.sol:233
_adminInceptionVault
is read twice:AdminInceptionVault.sol:226 AdminInceptionVault.sol:235
_inceptionVaultsData
is read three times:AdminInceptionVault.sol:200 AdminInceptionVault.sol:203 AdminInceptionVault.sol:238
_inceptionPriceFeed
is read twice:AdminInceptionVault.sol:202 AdminInceptionVault.sol:237
_vaultConfig
is read four times:AdminInceptionVault.sol:205 AdminInceptionVault.sol:208 AdminInceptionVault.sol:217 AdminInceptionVault.sol:222
scope: _removeCollateralFromVault()
_inceptionVaultsData
is read three times:AdminInceptionVault.sol:284 AdminInceptionVault.sol:287 AdminInceptionVault.sol:294
scope: _addCollateralToVaultById()
_inceptionVaultsData
is read twice:AdminInceptionVault.sol:305 AdminInceptionVault.sol:306
scope: _refreshCumulativeRate()
_cumulativeRat
is read three times:AdminInceptionVault.sol:313 AdminInceptionVault.sol:313 AdminInceptionVault.sol:315
scope: _reduceVaultDebt()
_inceptionVaultsData
is read twice:AdminInceptionVault.sol:326 AdminInceptionVault.sol:330 AdminInceptionVault.sol:333
scope: getAssetPrice()
_eurOracle
is read twice:ChainlinkInceptionPriceFeed.sol:74 ChainlinkInceptionPriceFeed.sol:82
scope: _pendingMIMO()
_totalStakeWithBoost
is read twice:GenericMinerV2.sol:281 GenericMinerV2.sol:286
scope: _pendingPAR()
_totalStakeWithBoost
is read twice:GenericMinerV2.sol:295 GenericMinerV2.sol:300
scope: _getBoostMultiplier()
_boostConfig
is read six times:GenericMinerV2.sol:316 GenericMinerV2.sol:317 GenericMinerV2.sol:318 GenericMinerV2.sol:319 GenericMinerV2.sol:322 GenericMinerV2.sol:331
scope: liquidate()
_par
is read three times:PARMinerV2.sol:118 PARMinerV2.sol:127 PARMinerV2.sol:128
_a
is read twice:PARMinerV2.sol:120 PARMinerV2.sol:122
scope: _increaseStake()
__accParAmountPerShare
is read twice:PARMinerV2.sol:261 PARMinerV2.sol:365
scope: _pendingMIMO()
_totalStakeWithBoost
is read twice:PARMinerV2.sol:376 PARMinerV2.sol:381
scope: _getBoostMultiplier()
_boostConfig
is read six times:PARMinerV2.sol:411 PARMinerV2.sol:412 PARMinerV2.sol:413 PARMinerV2.sol:414 PARMinerV2.sol:417 PARMinerV2.sol:426
scope: latestRoundData()
pool
is read twice:BalancerV2LPOracle.sol:104 BalancerV2LPOracle.sol:117
scope: executeOperation()
lendingPool
is read twice:SuperVault.sol:83 SuperVault.sol:97
scope: emptyVaultOperation()
a
is read five times:SuperVault.sol:198 SuperVault.sol:199 SuperVault.sol:202 SuperVault.sol:203 SuperVault.sol:205
scope: emptyVault()
a
is read twice:SuperVault.sol:233 SuperVault.sol:233
scope: withdrawFromVault()
a
is read twice:SuperVault.sol:245 SuperVault.sol:246
scope: borrowFromVault()
a
is read three times:SuperVault.sol:254 SuperVault.sol:255 SuperVault.sol:255
scope: depositToVault()
a
is read twice:SuperVault.sol:273 SuperVault.sol:275
scope: depositAndBorrowFromVault()
a
is read four times:SuperVault.sol:289 SuperVault.sol:291 SuperVault.sol:292 SuperVault.sol:292
scope: depositETHAndBorrowFromVault()
a
is read three times:SuperVault.sol:312 SuperVault.sol:313 SuperVault.sol:313
scope: leverageSwap()
a
is read three times:SuperVault.sol:326 SuperVault.sol:327 SuperVault.sol:328
scope: checkAndSendMIMO()
ga
is read three times:SuperVault.sol:369 SuperVault.sol:370 SuperVault.sol:370
Manual Analysis
cache these storage variables in memory
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.
Instances include:
scope: initialize()
InceptionVaultsCore.sol:42: VaultConfig memory vaultConfig
scope: initialize()
DemandMinerV2.sol:56: FeeConfig memory newFeeConfig
scope: setBoostConfig()
GenericMinerV2.sol:69: BoostConfig memory newBoostConfig
scope: _releaseRewards()
GenericMinerV2.sol:209: UserInfo memory _userInfo
scope: _updateBoost()
GenericMinerV2.sol:231: UserInfo memory _userInfo
scope: setBoostConfig()
PARMinerV2.sol:70: BoostConfig memory newBoostConfig
scope: _releaseRewards()
PARMinerV2.sol:306: UserInfo memory _userInfo
scope: _updateBoost()
PARMinerV2.sol:331: UserInfo memory _userInfo
scope: releaseMIMO()
VotingMinerV2.sol:24: UserInfo memory _userInfo
scope: _syncStake()
VotingMinerV2.sol:67: UserInfo memory _userInfo
scope: leverageOperation()
SuperVault.sol:105: bytes memory params
scope: rebalanceOperation()
SuperVault.sol:141: bytes memory params
scope: emptyVaultOperation()
SuperVault.sol:191: bytes memory params
scope: leverageSwap()
SuperVault.sol:321: bytes memory params
scope: aggregatorSwap()
SuperVault.sol:341: bytes memory dexTXData
scope: takeFlashLoan()
SuperVault.sol:357: bytes memory params
Manual Analysis
Replace memory
with calldata
>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
Instances include:
InceptionVaultsCore.sol:122
ChainlinkInceptionPriceFeed.sol:75 ChainlinkInceptionPriceFeed.sol:79
GenericMinerV2.sol:58 GenericMinerV2.sol:70 GenericMinerV2.sol:175 GenericMinerV2.sol:195
PARMinerV2.sol:52 PARMinerV2.sol:71 PARMinerV2.sol:254 PARMinerV2.sol:284
GUniLPOracle.sol:112 GUniLPOracle.sol:112
Manual Analysis
Replace > 0
with != 0
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
Instances include:
InceptionVaultsCore.sol:138 InceptionVaultsCore.sol:179 InceptionVaultsCore.sol:219 InceptionVaultsCore.sol:226 InceptionVaultsCore.sol:285
GenericMinerV2.sol:58 GenericMinerV2.sol:58 GenericMinerV2.sol:70 GenericMinerV2.sol:70 GenericMinerV2.sol:197 GenericMinerV2.sol:331 GenericMinerV2.sol:331
PARMinerV2.sol:52 PARMinerV2.sol:52 PARMinerV2.sol:71 PARMinerV2.sol:71 PARMinerV2.sol:286 PARMinerV2.sol:426 PARMinerV2.sol:426
Manual Analysis
Replace <=
with <
, and >=
with >
. Do not forget to increment/decrement the compared variable
example:
-collateralValueToReceive >= collateralValue +collateralValueToReceive > collateralValue - 1;
However, if 1
is negligible compared to the value of the variable, we can omit the increment.
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.
Instances include:
BalancerV2LPOracle.sol:18: uint256 public override version = 3;
GUniLPOracle.sol:16: uint256 public override version = 3;
Manual Analysis
Add the constant
modifier to these variables.
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).
Instances include:
DexAddressProvider.sol:15 _a = a;
InceptionVaultFactory.sol:54 _adminInceptionVaultBase = adminInceptionVaultBase; InceptionVaultFactory.sol:55 _inceptionVaultsCoreBase = inceptionVaultsCoreBase; InceptionVaultFactory.sol:56 _inceptionVaultsDataProviderBase = inceptionVaultsDataProviderBase; InceptionVaultFactory.sol:57 _a = addressProvider; InceptionVaultFactory.sol:58 _debtNotifier = debtNotifier; InceptionVaultFactory.sol:59 _weth = weth; InceptionVaultFactory.sol:60 _mimo = mimo;
DemandMinerV2.sol:34 _token = token; DemandMinerV2.sol:35 _feeCollector = feeCollector; DemandMinerV2.sol:36 _feeConfig = feeConfig;
GenericMinerV2.sol:56 _a = _addresses; GenericMinerV2.sol:59 _boostConfig = boostConfig;
PARMinerV2.sol:53 _a = govAP; PARMinerV2.sol:54 _dexAP = dexAP; PARMinerV2.sol:55 _liquidateCallerReward = 200 ether; PARMinerV2.sol:60 _boostConfig = boostConfig;
SupplyMinerV2.sol:20 _collateral = collateral;
BalancerV2LPOracle.sol:39 vault = _vault; BalancerV2LPOracle.sol:40 poolId = _poolId; BalancerV2LPOracle.sol:44 decimals = _decimals; BalancerV2LPOracle.sol:45 description = _description; BalancerV2LPOracle.sol:46 pool = IBalancerPool(_pool); BalancerV2LPOracle.sol:47 oracleA = _oracleA; BalancerV2LPOracle.sol:48 oracleB = _oracleB;
GUniLPOracle.sol:39 decimals = _decimals; GUniLPOracle.sol:40 description = _description; GUniLPOracle.sol:41 pool = _pool; GUniLPOracle.sol:42 oracleA = _oracleA; GUniLPOracle.sol:43 oracleB = _oracleB;
SuperVaultFactory.sol:20 base = _base;
Manual Analysis
Hardcode these storage variables with their value instead of writing them during contract deployment with constructor parameters.
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
Instances include:
DexAddressProvider.sol:14: require(address(a) != address(0), "LM000"); DexAddressProvider.sol:38: require(_proxy != address(0), "LM000"); DexAddressProvider.sol:39: require(_router != address(0), "LM000");
InceptionVaultFactory.sol:47: require(adminInceptionVaultBase != address(0), "IV000"); InceptionVaultFactory.sol:48: require(inceptionVaultsCoreBase != address(0), "IV000"); InceptionVaultFactory.sol:49: require(inceptionVaultsDataProviderBase != address(0), "IV000"); InceptionVaultFactory.sol:50: require(address(addressProvider) != address(0), "IV000"); InceptionVaultFactory.sol:51: require(address(debtNotifier) != address(0), "IV000"); InceptionVaultFactory.sol:52: require(address(weth) != address(0), "IV000");; InceptionVaultFactory.sol:53: require(address(mimo) != address(0), "IV000"); InceptionVaultFactory.sol:74: require(address(_inceptionCollateral) != address(0), "IV000"); InceptionVaultFactory.sol:75: require(_inceptionVaultPriceFeed != address(0), "IV000"); InceptionVaultFactory.sol:85: require(address(_assetOracle) != address(0), "IV000"); InceptionVaultFactory.sol:89: require(address(_assetOracle) == address(0), "IV001"); InceptionVaultFactory.sol:130: require(_address != address(0), "IV000"); InceptionVaultFactory.sol:130: require(_priceFeedIds[_address] == 0, "IV002");
InceptionVaultFactory.sol:122 require(_amount > 0, "IV100"); InceptionVaultFactory.sol:138 require(_amount <= stablex.balanceOf(address(_adminInceptionVault)), "IV104"); InceptionVaultFactory.sol:160 require(isHealthy, "IV102"); InceptionVaultFactory.sol:204 require( !_a.liquidationManager().isHealthy(collateralValue, currentVaultDebt, _vaultConfig.liquidationRatio), "IV103" ); InceptionVaultFactory.sol:226 require(_a.stablex().balanceOf(address(_adminInceptionVault)) >= insuranceAmount, "IV104"); InceptionVaultFactory.sol:285 require(_amount <= v.collateralBalance, "IV101"); InceptionVaultFactory.sol:291 require( _a.liquidationManager().isHealthy( newCollateralValue, _inceptionVaultsData.vaults(_vaultId).baseDebt, _vaultConfig.minCollateralRatio ), "IV102" );
InceptionVaultDataProvider.sol:64: require(vaultExists(_vaultId), "IV105");
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");
DemandMinerV2.sol:31: require(address(token) != address(0), "LM000"); DemandMinerV2.sol:32: require(address(token) != address(_addresses.mimo()), "LM001"); DemandMinerV2.sol:33: require(feeCollector != address(0), "LM000");
GenericMinerV2.sol:55: require(address(_addresses) != address(0), "LM000"); GenericMinerV2.sol:58: require(boostConfig.a >= 1 && boostConfig.d > 0 && boostConfig.maxBoost >= 1, "LM004"); GenericMinerV2.sol:70: require(newBoostConfig.a >= 1 && newBoostConfig.d > 0 && newBoostConfig.maxBoost >= 1, "LM004"); GenericMinerV2.sol:175: require(value > 0, "LM101"); GenericMinerV2.sol:195: require(value > 0, "LM101"); GenericMinerV2.sol:197: require(_userInfo.stake >= value, "LM102"); GenericMinerV2.sol:220: require(_a.mimo().transfer(_user, pendingMIMO), "LM100"); GenericMinerV2.sol:223: require(_par.transfer(_user, pendingPAR), "LM100"); GenericMinerV2.sol:331: require(multiplier >= 1e18 && multiplier <= _boostConfig.maxBoost, "LM103");
PARMinerV2.sol:50: require(address(govAP) != address(0), "LM000"); PARMinerV2.sol:51: require(address(dexAP) != address(0), "LM000"); PARMinerV2.sol:52: require(boostConfig.a >= 1 && boostConfig.d > 0 && boostConfig.maxBoost >= 1, "LM004"); PARMinerV2.sol:71: require(boostConfig.a >= 1 && boostConfig.d > 0 && boostConfig.maxBoost >= 1, "LM004"); PARMinerV2.sol:128: require(_par.balanceOf(address(this)) > parBalanceBefore, "LM104");; PARMinerV2.sol:254: require(value > 0, "LM101"); PARMinerV2.sol:284: require(value > 0, "LM101"); PARMinerV2.sol:286: require(_userInfo.stake >= value, "LM102"); PARMinerV2.sol:320: require(_par.transfer(_user, pendingPAR), "LM100"); PARMinerV2.sol:323: require(_a.mimo().transfer(_user, pendingMIMO), "LM100"); PARMinerV2.sol:426: require(multiplier >= 1e18 && multiplier <= _boostConfig.maxBoost, "LM103");
SupplyMinerV2.sol:50: require(address(collateral) != address(0), "LM000");
VotingMinerV2.sol:44: require(_a.mimo().transfer(_user, pendingMIMO), "LM100"); VotingMinerV2.sol:47: require(_par.transfer(_user, pendingPAR), "LM100");
BalancerV2LPOracle.sol:35: require(address(_vault) != address(0), "C000"); BalancerV2LPOracle.sol:36: require(address(_oracleA) != address(0), "C000"); BalancerV2LPOracle.sol:37: require(address(_oracleB) != address(0), "C000"); BalancerV2LPOracle.sol:38: require(tokensNum == IBalancerVault.PoolSpecialization.TWO_TOKEN, "C001");
GUniLPOracle.sol:35: require(address(_pool) != address(0), "C000"); GUniLPOracle.sol:36: require(address(_oracleA) != address(0), "C000"); GUniLPOracle.sol:37: require(address(_oracleB) != address(0), "C000"); GUniLPOracle.sol:112: require(rA > 0 || rB > 0, "C100"); GUniLPOracle.sol:114: require(totalSupply >= 1e9, "C101");
SuperVault.sol:56: require(address(_a) != address(0)); SuperVault.sol:57: require(address(_ga) != address(0)); SuperVault.sol:58: require(address(_lendingPool) != address(0)); SuperVault.sol:59: require(address(dexAP) != address(0)); SuperVault.sol:83: require(msg.sender == address(lendingPool), "SV002"); SuperVault.sol:109: require(token.balanceOf(address(this)) >= flashloanRepayAmount, "SV101"); SuperVault.sol:156: require(fromCollateral.balanceOf(address(this)) >= flashloanRepayAmount, "SV101"); SuperVault.sol:207: require(vaultCollateral.balanceOf(address(this)) >= flashloanRepayAmount, "SV101"); SuperVault.sol:233: require(IERC20(a.stablex()).transfer(msg.sender, IERC20(a.stablex()).balanceOf(address(this)))); SuperVault.sol:247: require(asset.transfer(msg.sender, amount)); SuperVault.sol:255: require(IERC20(a.stablex()).transfer(msg.sender, IERC20(a.stablex()).balanceOf(address(this)))); SuperVault.sol:264: require(token.transfer(msg.sender, token.balanceOf(address(this)))); SuperVault.sol:292: require(IERC20(a.stablex()).transfer(msg.sender, IERC20(a.stablex()).balanceOf(address(this)))); SuperVault.sol:313: require(IERC20(a.stablex()).transfer(msg.sender, IERC20(a.stablex()).balanceOf(address(this)))); SuperVault.sol:344: require(proxy != address(0) && router != address(0), "SV201"); SuperVault.sol:370: require(ga.mimo().transfer(msg.sender, ga.mimo().balanceOf(address(this))));
Manual Analysis
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);
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.
Instances include:
InceptionVaultFactory.sol:218: uint256 insuranceAmount = 0;
Manual Analysis
Remove explicit initialization for default values.
When emitting an event, using a local variable instead of a storage variable saves gas.
Instances include:
InceptionVaultFactory.sol:218: emit CumulativeRateUpdated(_timeElapsed, _cumulativeRate);
GenericMinerV2.sol:61: emit BoostConfigSet(_boostConfig); GenericMinerV2.sol:73: emit BoostConfigSet(_boostConfig);
PARMinerV2.sol:74: emit BoostConfigSet(_boostConfig);
Manual Analysis
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 are cheaper than postfix increments.
Instances include:
DexAddressProvider.sol:16: i++
AdminInceptionVault.sol:108: i++
Manual Analysis
change variable++
to ++variable
.
Require statements including conditions with the &&
operator can be broken down in multiple require statements to save gas.
Instances include:
GenericMinerV2.sol:58: require(boostConfig.a >= 1 && boostConfig.d > 0 && boostConfig.maxBoost >= 1, "LM004"); GenericMinerV2.sol:70: require(newBoostConfig.a >= 1 && newBoostConfig.d > 0 && newBoostConfig.maxBoost >= 1, "LM004"); GenericMinerV2.sol:331: require(multiplier >= 1e18 && multiplier <= _boostConfig.maxBoost, "LM103");
PARMinerV2.sol:52: require(boostConfig.a >= 1 && boostConfig.d > 0 && boostConfig.maxBoost >= 1, "LM004"); PARMinerV2.sol:71: require(boostConfig.a >= 1 && boostConfig.d > 0 && boostConfig.maxBoost >= 1, "LM004"); PARMinerV2.sol:426: require(multiplier >= 1e18 && multiplier <= _boostConfig.maxBoost, "LM103");
SuperVault.sol:344: require(proxy != address(0) && router != address(0), "SV201");
Manual Analysis
Break down the single require statement in multiple require statements
e.g:
GenericMinerV2.sol:331: -require(multiplier >= 1e18 && multiplier <= _boostConfig.maxBoost, "LM103"); +require(multiplier >= 1e18) +require(multiplier <= _boostConfig.maxBoost, "LM103")
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
Instances include:
InceptionVaultsCore.sol:217: uint256 collateralValueToReceive = _amount.add(_amount.wadMul(_vaultConfig.liquidationBonus)); InceptionVaultsCore.sol:222: uint256 discountedCollateralValue = collateralValue.wadDiv(_vaultConfig.liquidationBonus.add(WadRayMath.wad())); InceptionVaultsCore.sol:232: _reduceVaultDebt(_vaultId, repayAmount.add(insuranceAmount));
Manual Analysis
Do not use this library for additions and subtractions
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.
Instances include:
IInceptionVaultFactory.sol:16: struct InceptionVault { address owner; IAdminInceptionVault adminInceptionVault; IInceptionVaultsCore inceptionVaultsCore; IInceptionVaultsDataProvider inceptionVaultsDataProvider; IInceptionVaultPriceFeed inceptionVaultPriceFeed; bool isCustomPriceFeed; }
Manual Analysis
Place isCustomPriceFeed
after owner
to save one storage slot
address owner; +bool isCustomPriceFeed; IAdminInceptionVault adminInceptionVault; IInceptionVaultsCore inceptionVaultsCore; IInceptionVaultsDataProvider inceptionVaultsDataProvider; IInceptionVaultPriceFeed inceptionVaultPriceFeed;
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
Instances include:
SuperVault.sol:110: token.balanceOf(address(this)) is greater than flashloanRepayAmount (see condition one line above), underflow check unnecessary
Manual Analysis
Place the arithmetic operations in an unchecked
block
#0 - m19
2022-05-08T05:38:32Z
We appreciate this thorough report on how to optimize gas.