Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gas Optimizations #111

Open
code423n4 opened this issue May 2, 2022 · 1 comment
Open

Gas Optimizations #111

code423n4 opened this issue May 2, 2022 · 1 comment
Labels
bug Something isn't working G (Gas Optimization)

Comments

@code423n4
Copy link
Contributor

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 high

PROOF OF CONCEPT

Instances include:

AdminInceptionVault.sol

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 loop
AdminInceptionVault.sol:108

scope: claimMimo()

  • _debtNotifier is read (_collateralCount) times:
    number of reads depending on _collateralCount as it is in a for loop
AdminInceptionVault.sol:108

scope: deposit()

  • _a is read twice:
AdminInceptionVault.sol:152
AdminInceptionVault.sol:153

InceptionVaultsCore.sol

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

ChainlinkInceptionPriceFeed.sol

scope: getAssetPrice()

  • _eurOracle is read twice:
ChainlinkInceptionPriceFeed.sol:74
ChainlinkInceptionPriceFeed.sol:82

GenericMinerV2.sol

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

PARMinerV2.sol

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

BalancerV2LPOracle.sol

scope: latestRoundData()

  • pool is read twice:
BalancerV2LPOracle.sol:104
BalancerV2LPOracle.sol:117

SuperVault.sol

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

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

InceptionVaultsCore.sol:42: VaultConfig memory vaultConfig

DemandMinerV2.sol

scope: initialize()

DemandMinerV2.sol:56: FeeConfig memory newFeeConfig

GenericMinerV2.sol

scope: setBoostConfig()

GenericMinerV2.sol:69: BoostConfig memory newBoostConfig

scope: _releaseRewards()

GenericMinerV2.sol:209: UserInfo memory _userInfo

scope: _updateBoost()

GenericMinerV2.sol:231: UserInfo memory _userInfo

PARMinerV2.sol

scope: setBoostConfig()

PARMinerV2.sol:70: BoostConfig memory newBoostConfig

scope: _releaseRewards()

PARMinerV2.sol:306: UserInfo memory _userInfo

scope: _updateBoost()

PARMinerV2.sol:331: UserInfo memory _userInfo

VotingMinerV2.sol

scope: releaseMIMO()

VotingMinerV2.sol:24: UserInfo memory _userInfo

scope: _syncStake()

VotingMinerV2.sol:67: UserInfo memory _userInfo

SuperVault.sol

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

TOOLS USED

Manual Analysis

MITIGATION

Replace memory with calldata

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

InceptionVaultsCore.sol:122

ChainlinkInceptionPriceFeed.sol

ChainlinkInceptionPriceFeed.sol:75
ChainlinkInceptionPriceFeed.sol:79

GenericMinerV2.sol

GenericMinerV2.sol:58
GenericMinerV2.sol:70
GenericMinerV2.sol:175
GenericMinerV2.sol:195

PARMinerV2.sol

PARMinerV2.sol:52
PARMinerV2.sol:71
PARMinerV2.sol:254
PARMinerV2.sol:284

GUniLPOracle.sol

GUniLPOracle.sol:112
GUniLPOracle.sol:112

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

InceptionVaultsCore.sol:138
InceptionVaultsCore.sol:179
InceptionVaultsCore.sol:219
InceptionVaultsCore.sol:226
InceptionVaultsCore.sol:285

GenericMinerV2.sol

GenericMinerV2.sol:58
GenericMinerV2.sol:58
GenericMinerV2.sol:70
GenericMinerV2.sol:70
GenericMinerV2.sol:197
GenericMinerV2.sol:331
GenericMinerV2.sol:331

PARMinerV2.sol

PARMinerV2.sol:52
PARMinerV2.sol:52
PARMinerV2.sol:71
PARMinerV2.sol:71
PARMinerV2.sol:286
PARMinerV2.sol:426
PARMinerV2.sol:426

TOOLS USED

Manual Analysis

MITIGATION

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

PROOF OF CONCEPT

Instances include:

DexAddressProvider.sol

DexAddressProvider.sol:15 _a = a;

InceptionVaultFactory.sol

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

DemandMinerV2.sol:34 _token = token;
DemandMinerV2.sol:35 _feeCollector = feeCollector;
DemandMinerV2.sol:36 _feeConfig = feeConfig;

GenericMinerV2.sol

GenericMinerV2.sol:56 _a = _addresses;
GenericMinerV2.sol:59 _boostConfig = boostConfig;

PARMinerV2.sol

PARMinerV2.sol:53 _a = govAP;
PARMinerV2.sol:54 _dexAP = dexAP;
PARMinerV2.sol:55 _liquidateCallerReward = 200 ether;
PARMinerV2.sol:60 _boostConfig = boostConfig;

SupplyMinerV2.sol

SupplyMinerV2.sol:20 _collateral = collateral;

BalancerV2LPOracle.sol

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

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

SuperVaultFactory.sol:20 base = _base;

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

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

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

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

InceptionVaultDataProvider.sol:64: require(vaultExists(_vaultId), "IV105");

ChainlinkInceptionPriceFeed.sol

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

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

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

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

SupplyMinerV2.sol:50: require(address(collateral) != address(0), "LM000");

VotingMinerV2.sol

VotingMinerV2.sol:44: require(_a.mimo().transfer(_user, pendingMIMO), "LM100");
VotingMinerV2.sol:47: require(_par.transfer(_user, pendingPAR), "LM100");

BalancerV2LPOracle.sol

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

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

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

TOOLS USED

Manual Analysis

MITIGATION

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.

PROOF OF CONCEPT

Instances include:

InceptionVaultFactory.sol

InceptionVaultFactory.sol:218: uint256 insuranceAmount = 0;

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

InceptionVaultFactory.sol:218: emit CumulativeRateUpdated(_timeElapsed, _cumulativeRate);

GenericMinerV2.sol

GenericMinerV2.sol:61: emit BoostConfigSet(_boostConfig);
GenericMinerV2.sol:73: emit BoostConfigSet(_boostConfig);

PARMinerV2.sol

PARMinerV2.sol:74: emit BoostConfigSet(_boostConfig);

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 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.

PROOF OF CONCEPT

Instances include:

GenericMinerV2.sol

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

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

SuperVault.sol:344: require(proxy != address(0) && router != address(0), "SV201");

TOOLS USED

Manual Analysis

MITIGATION

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

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 subtractions

PROOF OF CONCEPT

Instances include:

InceptionVaultsCore.sol

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

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

IInceptionVaultFactory.sol:16:
struct InceptionVault {
    address owner;
    IAdminInceptionVault adminInceptionVault;
    IInceptionVaultsCore inceptionVaultsCore;
    IInceptionVaultsDataProvider inceptionVaultsDataProvider;
    IInceptionVaultPriceFeed inceptionVaultPriceFeed;
    bool isCustomPriceFeed;
  }

TOOLS USED

Manual Analysis

MITIGATION

Place isCustomPriceFeed after owner to save one storage slot

address owner;
+bool isCustomPriceFeed;
IAdminInceptionVault adminInceptionVault;
IInceptionVaultsCore inceptionVaultsCore;
IInceptionVaultsDataProvider inceptionVaultsDataProvider;
IInceptionVaultPriceFeed inceptionVaultPriceFeed;

Unchecked 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 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

@code423n4 code423n4 added bug Something isn't working G (Gas Optimization) labels May 2, 2022
code423n4 added a commit that referenced this issue May 2, 2022
@m19
Copy link
Collaborator

m19 commented May 8, 2022

We appreciate this thorough report on how to optimize gas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working G (Gas Optimization)
Projects
None yet
Development

No branches or pull requests

2 participants