Badger eBTC Audit + Certora Formal Verification Competition - Madalad's results

Use stETH to borrow Bitcoin with 0% fees | The only smart contract based #BTC.

General Information

Platform: Code4rena

Start Date: 24/10/2023

Pot Size: $149,725 USDC

Total HM: 7

Participants: 52

Period: 21 days

Judge: ronnyx2017

Total Solo HM: 2

Id: 300

League: ETH

eBTC Protocol

Findings Distribution

Researcher Performance

Rank: 18/52

Findings: 1

Award: $162.76

Gas:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

162.763 USDC - $162.76

Labels

bug
G (Gas Optimization)
grade-a
sufficient quality report
G-18

External Links

Gas Report

Gas Optimizations

IssueInstancesGas Saved
[G-01]abi.encodePacked is more gas efficient than abi.encode5-
[G-02]Use assembly to emit events883344
[G-03]Use assembly to check for address(0)24432
[G-04]Use assembly to calculate hashes114114
[G-05]Use assembly to write storage values1128400
[G-06]Use require instead of assert1-
[G-07]Avoid zero transfer to save gas101000
[G-08]Using bool for storage incurs overhead468400
[G-09]Do not compare boolean expressions to boolean literals436
[G-10]Cache multiple accesses of mapping/array values391638
[G-11]Using constants directly, rather than caching the value, saves gas2-
[G-12]Function result should be cached90-
[G-13]Cache storage variables read from more than once373589
[G-14]Use calldata instead of memory for function arguments that are read only26-
[G-15]Division/Multiplication by powers of 2 should use bit shifting480
[G-16]Initializing variable to default value costs unnecessary gas742
[G-17]Same cast is done multiple times96-
[G-18]Avoid emitting events within loops1750
[G-19]Multiple consecutively emitted events should be combined43304
[G-20]require/revert strings longer than 32 bytes cost extra gas1152070
[G-21]Cache length outside of for loop4388
[G-22]Using >= is cheaper than >56168
[G-23]Inline modifiers that are only used once, to save gas10-
[G-24]Inline internal functions that are only called once782340
[G-25]Expressions for constant values such as a call to keccak256 should use immutable rather than constant8800
[G-26]Avoid contract existence checks by using low level calls12512500
[G-27]Using storage instead of memory for structs/arrays saves gas28-
[G-28]Refactor modifiers to call a local function55000
[G-29]Combine multiple mappings with the same key type where appropriate360126
[G-30]Nesting if-statements is cheaper than using &&1378
[G-31]Use unchecked for operations that cannot overflow/underflow13780
[G-32]Function names can be optimized to save gas17374
[G-33]Pack state variables into fewer storage slots12000
[G-34]Use payable for constructor19399
[G-35]Pre-compute hashes3108
[G-36]Use private rather than public for constants31105400
[G-37]require() or revert() statements that check input arguments should be at the top of the function5-
[G-38]Split require statements using &&33297
[G-39]Not using the named return variable is confusing and can waste gas21-
[G-40]Use Solidity version 0.8.19 for gas savings19-
[G-41]State variable read in a loop8776
[G-42]Structs can be packed into fewer storage slots3-
[G-43]Pack structs into fewer storage slots by truncating uint256 values referencing time24000
[G-44]Remove superfluous event fields3102
[G-45]Use != 0 instead of > 0 for uints50300
[G-46]Usage of uint smaller than 32 bytes (256 bits) incurs overhead636
[G-47]Remove unused state variables9-
[G-48]Avoid updating storage when the value hasn't changed64800
[G-49]Use assembly for integer zero checks26156
[G-50]Use custom errors1631956
[G-51]Using storage instead of memory for state variables saves gas714700
[G-52]Use uint(1)/uint(2) instead of true/false420000
[G-53]++i costs less gas than i++1272
[G-54]State variables only assigned to in the constructor should be immutable24194
[G-55]State variables that do not change should be constant or immutable17-
[G-56]Use via-ir for deployment1-

Total issues: 56

Total instances: 1491

Total gas saved: 339,049

Gas Optimizations

[G-01] abi.encodePacked is more gas efficient than abi.encode

abi.encode pads all elementary types to 32 bytes, whereas abi.encodePacked will only use the minimal required memory to encode the data. See here for more info.

<details> <summary>Instances: 5</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

682:         return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));

L682

File: packages/contracts/contracts/EBTCToken.sol

214:                     abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner]++, deadline)

240:         return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));

L214, L240

File: packages/contracts/contracts/LeverageMacroBase.sol

152:                 abi.encode(operation)

159:                 abi.encode(operation)

L152, L159

</details>

 

[G-02] Use assembly to emit events

For example emit ExampleEvent(amount) (amount is uint256) can be re-written as

assembly {
    let memptr := mload(0x40)
    mstore(0x00, calldataload(0x44))
    mstore(0x20, calldataload(0xa4))
    mstore(0x40, amount)
    log1(
        0x00,
        0x60,
        // keccak256("ExampleEvent(uint256)")
        0x12210f92675543a3eee7d9f6cc64eaca8eb1431502f685da3f48e7593e2b7f1e
    )
    mstore(0x40, memptr)
}
<details> <summary>Instances: 88</summary>
File: packages/contracts/contracts/ActivePool.sol

65:         emit FeeRecipientAddressChanged(_feeRecipientAddress);

112:         emit SystemCollSharesUpdated(cachedSystemCollShares);
113:         emit CollSharesTransferred(_account, _shares);

145:         emit SystemCollSharesUpdated(cachedSystemCollShares);
146:         emit CollSharesTransferred(_account, totalShares);

173:         emit SystemCollSharesUpdated(cachedSystemCollShares);
174:         emit FeeRecipientClaimableCollSharesIncreased(cachedFeeRecipientCollShares, _shares);

200:         emit ActivePoolEBTCDebtUpdated(cachedSystemDebt);

213:         emit ActivePoolEBTCDebtUpdated(cachedSystemDebt);

247:         emit SystemCollSharesUpdated(cachedSystemCollShares);

307:         emit FlashLoanSuccess(address(receiver), token, amount, fee);

360:         emit FeeRecipientClaimableCollSharesDecreased(cachedFeeRecipientCollShares, _shares);

383:         emit SweepTokenSuccess(token, amount, cachedFeeRecipientAddress);

399:         emit FeeRecipientAddressChanged(_feeRecipientAddress);

412:         emit FlashFeeSet(msg.sender, _oldFee, _newFee);

421:         emit FlashLoansPaused(msg.sender, _paused);

L65, L112, L145, L173, L200, L213, L247, L307, L360, L383, L399, L412, L421

File: packages/contracts/contracts/BorrowerOperations.sol

136:         emit FeeRecipientAddressChanged(_feeRecipientAddress);

641:         emit PositionManagerApprovalSet(_borrower, _positionManager, _approval);

1105:         emit FlashLoanSuccess(address(receiver), token, amount, fee);

1149:         emit FeeRecipientAddressChanged(_feeRecipientAddress);

1162:         emit FlashFeeSet(msg.sender, _oldFee, _newFee);

1171:         emit FlashLoansPaused(msg.sender, _paused);

L136, L641, L1105, L1149, L1162, L1171

File: packages/contracts/contracts/CdpManager.sol

55:         emit StakingRewardSplitSet(stakingRewardSplit);

179:                 emit CdpUpdated(
180:                     _redeemColFromCdp.cdpId,
181:                     _borrower,
182:                     msg.sender,
183:                     _oldDebtAndColl.debt,
184:                     _oldDebtAndColl.collShares,
185:                     0,
186:                     0,
187:                     0,
188:                     CdpOperation.redeemCollateral
189:                 );

220:             emit CdpUpdated(
221:                 _redeemColFromCdp.cdpId,
222:                 ISortedCdps(sortedCdps).getOwnerAddress(_redeemColFromCdp.cdpId),
223:                 msg.sender,
224:                 _oldDebtAndColl.debt,
225:                 _oldDebtAndColl.collShares,
226:                 newDebt,
227:                 newColl,
228:                 Cdps[_redeemColFromCdp.cdpId].stake,
229:                 CdpOperation.redeemCollateral
230:             );

466:         emit Redemption(
467:             _debt,
468:             totals.debtToRedeem,
469:             totals.collSharesDrawn,
470:             totals.feeCollShares,
471:             msg.sender
472:         );

545:         emit CdpUpdated(_cdpId, _borrower, msg.sender, _debt, _coll, 0, 0, 0, CdpOperation.closeCdp);

630:         emit BaseRateUpdated(newBaseRate);

681:         emit BaseRateUpdated(decayedBaseRate);

698:             emit LastRedemptionTimestampUpdated(block.timestamp);

782:         emit StakingRewardSplitSet(_stakingRewardSplit);

801:         emit RedemptionFeeFloorSet(_redemptionFeeFloor);

824:         emit MinuteDecayFactorSet(_minuteDecayFactor);

836:         emit BetaSet(_beta);

848:         emit RedemptionsPaused(_paused);

925:         emit CdpUpdated(
926:             _cdpId,
927:             _borrower,
928:             msg.sender,
929:             0,
930:             0,
931:             _debt,
932:             _coll,
933:             stake,
934:             CdpOperation.openCdp
935:         );

961:         emit CdpUpdated(
962:             _cdpId,
963:             _borrower,
964:             msg.sender,
965:             _debt,
966:             _coll,
967:             _newDebt,
968:             _newColl,
969:             stake,
970:             CdpOperation.adjustCdp
971:         );

L55, L179, L220, L466, L545, L630, L681, L698, L782, L801, L824, L836, L848, L925, L961

File: packages/contracts/contracts/LiquidationLibrary.sol

238:         emit CdpUpdated(
239:             _liqState.cdpId,
240:             _borrower,
241:             msg.sender,
242:             _totalDebtToBurn,
243:             _totalColToSend,
244:             0,
245:             0,
246:             0,
247:             CdpOperation.liquidateInNormalMode
248:         );

281:         emit CdpLiquidated(
282:             _liqState.cdpId,
283:             _borrower,
284:             _totalDebtToBurn,
285:             // please note this is the collateral share of the liquidated CDP
286:             _cappedColPortion,
287:             CdpOperation.liquidateInNormalMode,
288:             msg.sender,
289:             _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
290:         );

312:         emit CdpUpdated(
313:             _recoveryState.cdpId,
314:             _borrower,
315:             msg.sender,
316:             _totalDebtToBurn,
317:             _totalColToSend,
318:             0,
319:             0,
320:             0,
321:             CdpOperation.liquidateInRecoveryMode
322:         );

367:         emit CdpLiquidated(
368:             _recoveryState.cdpId,
369:             _borrower,
370:             _totalDebtToBurn,
371:             // please note this is the collateral share of the liquidated CDP
372:             _cappedColPortion,
373:             CdpOperation.liquidateInRecoveryMode,
374:             msg.sender,
375:             _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
376:         );

437:             emit CdpPartiallyLiquidated(
438:                 _cdpId,
439:                 sortedCdps.getOwnerAddress(_cdpId),
440:                 _partialDebt,
441:                 _partialColl,
442:                 CdpOperation.partiallyLiquidate,
443:                 msg.sender,
444:                 _cappedColl > _debtToColl ? (_cappedColl - _debtToColl) : 0
445:             );

490:         emit CdpUpdated(
491:             _cdpId,
492:             sortedCdps.getOwnerAddress(_cdpId),
493:             msg.sender,
494:             _oldDebt,
495:             _oldColl,
496:             Cdps[_cdpId].debt,
497:             Cdps[_cdpId].coll,
498:             Cdps[_cdpId].stake,
499:             CdpOperation.partiallyLiquidate
500:         );

516:         emit Liquidation(totalDebtToBurn, totalCollSharesToSend, totalLiquidatorRewardCollShares);

891:         emit SystemDebtRedistributionIndexUpdated(systemDebtRedistributionIndex);

L238, L281, L312, L367, L437, L490, L516, L891

File: packages/contracts/contracts/CdpManagerStorage.sol

50:         emit TCRNotified(_tcr);

55:             emit GracePeriodStart();

64:         emit TCRNotified(_tcr);

69:             emit GracePeriodEnd();

119:         emit GracePeriodDurationSet(_gracePeriod);

300:         emit SystemSnapshotsUpdated(_totalStakesSnapshot, _totalCollateralSnapshot);

340:         emit CdpDebtRedistributionIndexUpdated(_cdpId, _systemDebtRedistributionIndex);

390:             emit CdpUpdated(
391:                 _cdpId,
392:                 ISortedCdps(sortedCdps).getOwnerAddress(_cdpId),
393:                 msg.sender,
394:                 prevDebt,
395:                 prevCollShares,
396:                 _newDebt,
397:                 _newColl,
398:                 _cdp.stake,
399:                 CdpOperation.syncAccounting
400:             );

414:         emit TotalStakesUpdated(_newTotalStakes);

425:         emit TotalStakesUpdated(_newTotalStakes);

481:         emit CdpArrayIndexUpdated(idToMove, index);

542:             emit StEthIndexUpdated(_oldIndex, _newIndex, block.timestamp);

587:         emit CollateralFeePerUnitUpdated(_oldPerUnit, _newPerUnit, _feeTaken);

602:         emit CdpFeeSplitApplied(
603:             _cdpId,
604:             _oldPerUnitCdp,
605:             _systemStEthFeePerUnitIndex,
606:             _feeSplitDistributed,
607:             _newColl
608:         );

L50, L55, L64, L69, L119, L300, L340, L390, L414, L425, L481, L542, L587, L602

File: packages/contracts/contracts/CollSurplusPool.sol

83:         emit SurplusCollSharesUpdated(_account, newAmount);

95:         emit SurplusCollSharesUpdated(_account, 0);

104:         emit CollSharesTransferred(_account, claimableColl);

150:         emit SweepTokenSuccess(token, amount, feeRecipientAddress);

L83, L95, L104, L150

File: packages/contracts/contracts/EBTCToken.sol

259:         emit Transfer(sender, recipient, amount);

267:         emit Transfer(address(0), account, amount);

282:         emit Transfer(account, address(0), amount);

290:         emit Approval(owner, spender, amount);

L259, L267, L282, L290

File: packages/contracts/contracts/Governor.sol

157:         emit RoleNameSet(role, roleName);

L157

File: packages/contracts/contracts/PriceFeed.sol

67:         emit FallbackCallerChanged(address(0), _fallbackCallerAddress);

378:                     emit FallbackCallerChanged(oldFallbackCaller, _fallbackCaller);

381:                 emit UnhealthyFallbackCaller(_fallbackCaller, block.timestamp);

387:             emit FallbackCallerChanged(oldFallbackCaller, _fallbackCaller);

548:         emit PriceFeedStatusChanged(_status);

555:         emit LastGoodPriceUpdated(_currentPrice);

L67, L378, L381, L387, L548, L555

File: packages/contracts/contracts/SortedCdps.sol

405:         emit NodeAdded(_id, _NICR);

451:             emit NodeRemoved(_ids[i]);

490:         emit NodeRemoved(_id);

L405, L451, L490

File: packages/contracts/contracts/LeverageMacroFactory.sol

56:         emit DeployNewMacro(_owner, addy);

L56

File: packages/contracts/contracts/Dependencies/Auth.sol

22:         emit OwnershipTransferred(msg.sender, _owner);
23:         emit AuthorityUpdated(msg.sender, _authority);

49:         emit AuthorityUpdated(msg.sender, newAuthority);

55:         emit OwnershipTransferred(msg.sender, newOwner);

L22, L49, L55

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

50:         emit AuthorityUpdated(msg.sender, Authority(newAuthority));

62:         emit AuthorityUpdated(address(this), Authority(newAuthority));

L50, L62

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

101:         emit PublicCapabilityUpdated(target, functionSig, enabled);

129:         emit RoleCapabilityUpdated(role, target, functionSig, enabled);

140:         emit CapabilityBurned(target, functionSig);

163:         emit UserRoleUpdated(user, role, enabled);

L101, L129, L140, L163

</details>

 

[G-03] Use assembly to check for address(0)

Saves 16000 deployment gas per instance and 6 runtime gas per instance.

assembly {
  if iszero(_addr) {
  mstore(0x00, "zero address")
  revert(0x00, 0x20)
  }
}
<details> <summary>Instances: 24</summary>
File: packages/contracts/contracts/ActivePool.sol

61:         if (_authorityAddress != address(0)) {

394:             _feeRecipientAddress != address(0),

L61, L394

File: packages/contracts/contracts/BorrowerOperations.sol

124:         if (_authorityAddress != address(0)) {

735:             recoveredAddress != address(0) && recoveredAddress == _borrower,

1142:             _feeRecipientAddress != address(0),

L124, L735, L1142

File: packages/contracts/contracts/CdpManager.sol

369:             while (currentBorrower != address(0) && getSyncedICR(_cId, totals.price) < MCR) {

389:             currentBorrower != address(0) && totals.remainingDebtToRedeem > 0 && _maxIterations > 0

L369, L389

File: packages/contracts/contracts/CollSurplusPool.sol

55:         if (_authorityAddress != address(0)) {

L55

File: packages/contracts/contracts/EBTCToken.sol

247:         require(sender != address(0), "EBTCToken: zero sender!");

248:         require(recipient != address(0), "EBTCToken: zero recipient!");

263:         require(account != address(0), "EBTCToken: mint to zero recipient!");

271:         require(account != address(0), "EBTCToken: burn from zero account!");

286:         require(owner != address(0), "EBTCToken: zero approve owner!");

287:         require(spender != address(0), "EBTCToken: zero approve spender!");

297:             _recipient != address(0) && _recipient != address(this),

L247, L248, L263, L271, L286, L287, L297

File: packages/contracts/contracts/PriceFeed.sol

224:             if (address(fallbackCaller) == address(0)) {

363:         if (_fallbackCaller != address(0)) {

587:         if (address(fallbackCaller) != address(0)) {

L224, L363, L587

File: packages/contracts/contracts/HintHelpers.sol

69:                 vars.currentCdpUser != address(0) &&

85:                 vars.currentCdpUser != address(0) &&

L69, L85

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

187:         require(facet != address(0), "Diamond: Function does not exist");

L187

File: packages/contracts/contracts/Dependencies/Auth.sol

38:             (address(auth) != address(0) && auth.canCall(user, address(this), functionSig)) ||

L38

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

35:         return (address(auth) != address(0) && auth.canCall(user, address(this), functionSig));

56:         require(address(_authority) == address(0), "Auth: authority is non-zero");

L35, L56

</details>

 

[G-04] Use assembly to calculate hashes

Saves 5000 deployment gas per instance and 374 runtime gas per instance.

Unoptimized

function solidityHash(uint256 a, uint256 b) public view {
	//unoptimized
	keccak256(abi.encodePacked(a, b));
}

Optimized

function assemblyHash(uint256 a, uint256 b) public view {
	//optimized
	assembly {
		mstore(0x00, a)
		mstore(0x20, b)
		let hashedVal := keccak256(0x00, 0x40)
	}
}
<details> <summary>Instances: 11</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

128:         bytes32 hashedName = keccak256(bytes(NAME));

129:         bytes32 hashedVersion = keccak256(bytes(_VERSION));

682:         return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));

717:         bytes32 digest = keccak256(

721:                 keccak256(

L128, L129, L682, L717, L721

File: packages/contracts/contracts/EBTCToken.sol

71:         bytes32 hashedName = keccak256(bytes(_NAME));

72:         bytes32 hashedVersion = keccak256(bytes(_VERSION));

209:         bytes32 digest = keccak256(

213:                 keccak256(

240:         return keccak256(abi.encode(typeHash, name, version, _chainID(), address(this)));

L71, L72, L209, L213, L240

File: packages/contracts/contracts/HintHelpers.sol

182:             latestRandomSeed = uint256(keccak256(abi.encodePacked(latestRandomSeed)));

L182

</details>

 

[G-05] Use assembly to write storage values

// unoptimized owner = _newOwner // optimized assembly { sstore(owner.slot, _newOwner) }
<details> <summary>Instances: 112</summary>
File: packages/contracts/contracts/ActivePool.sol

53:         borrowerOperationsAddress = _borrowerOperationsAddress;
54:         cdpManagerAddress = _cdpManagerAddress;
55:         collateral = ICollateralToken(_collTokenAddress);
56:         collSurplusPoolAddress = _collSurplusAddress;
57:         feeRecipientAddress = _feeRecipientAddress;

110:         systemCollShares = cachedSystemCollShares;

143:         systemCollShares = cachedSystemCollShares;

168:         systemCollShares = cachedSystemCollShares;

171:         feeRecipientCollShares = cachedFeeRecipientCollShares;

199:         systemDebt = cachedSystemDebt;

212:         systemDebt = cachedSystemDebt;

246:         systemCollShares = cachedSystemCollShares;

359:         feeRecipientCollShares = cachedFeeRecipientCollShares;

398:         feeRecipientAddress = _feeRecipientAddress;

L53, L110, L143, L168, L171, L199, L212, L246, L359, L398

File: packages/contracts/contracts/BorrowerOperations.sol

117:         cdpManager = ICdpManager(_cdpManagerAddress);
118:         collSurplusPool = ICollSurplusPool(_collSurplusPoolAddress);
119:         sortedCdps = ISortedCdps(_sortedCdpsAddress);
120:         ebtcToken = IEBTCToken(_ebtcTokenAddress);
121:         feeRecipientAddress = _feeRecipientAddress;

131:         _HASHED_NAME = hashedName;
132:         _HASHED_VERSION = hashedVersion;
133:         _CACHED_CHAIN_ID = _chainID();
134:         _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion);

640:         positionManagers[_borrower][_positionManager] = _approval;

1148:         feeRecipientAddress = _feeRecipientAddress;

L117, L131, L640, L1148

File: packages/contracts/contracts/CdpManagerStorage.sol

53:             lastGracePeriodStartTimestamp = uint128(block.timestamp);

67:             lastGracePeriodStartTimestamp = UNSET_TIMESTAMP;

118:         recoveryModeGracePeriodDuration = _gracePeriod;

228:         deploymentStartTime = block.timestamp;
229:         liquidationLibrary = _liquidationLibraryAddress;

233:         borrowerOperationsAddress = _borrowerOperationsAddress;
234:         collSurplusPool = ICollSurplusPool(_collSurplusPool);
235:         ebtcToken = IEBTCToken(_ebtcToken);
236:         sortedCdps = ISortedCdps(_sortedCdps);

276:         cdpDebtRedistributionIndex[_cdpId] = 0;
277:         cdpStEthFeePerUnitIndex[_cdpId] = 0;

295:         totalStakesSnapshot = _totalStakesSnapshot;

298:         totalCollateralSnapshot = _totalCollateralSnapshot;

339:         cdpDebtRedistributionIndex[_cdpId] = _systemDebtRedistributionIndex;

405:             cdpStEthFeePerUnitIndex[_cdpId] = _systemStEthFeePerUnitIndex;

412:         totalStakes = _newTotalStakes;

423:         totalStakes = _newTotalStakes;

541:             stEthIndex = _newIndex;

581:         systemStEthFeePerUnitIndex = _newPerUnit;
582:         systemStEthFeePerUnitIndexError = _newErrorPerUnit;

L53, L67, L118, L228, L233, L276, L295, L298, L339, L405, L412, L423, L541, L581

File: packages/contracts/contracts/CollSurplusPool.sol

48:         borrowerOperationsAddress = _borrowerOperationsAddress;
49:         cdpManagerAddress = _cdpManagerAddress;
50:         activePoolAddress = _activePoolAddress;
51:         collateral = ICollateralToken(_collTokenAddress);
52:         feeRecipientAddress = IActivePool(activePoolAddress).feeRecipientAddress();

81:         balances[_account] = newAmount;

94:         balances[_account] = 0;

102:             totalSurplusCollShares = cachedTotalSurplusCollShares - claimableColl;

132:         totalSurplusCollShares = totalSurplusCollShares + _value;

L48, L81, L94, L102, L132

File: packages/contracts/contracts/EBTCToken.sol

68:         cdpManagerAddress = _cdpManagerAddress;
69:         borrowerOperationsAddress = _borrowerOperationsAddress;

74:         _HASHED_NAME = hashedName;
75:         _HASHED_VERSION = hashedVersion;
76:         _CACHED_CHAIN_ID = _chainID();
77:         _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(_TYPE_HASH, hashedName, hashedVersion);

255:             _balances[sender] = cachedSenderBalances - amount;

258:         _balances[recipient] = _balances[recipient] + amount;

265:         _totalSupply = _totalSupply + amount;
266:         _balances[account] = _balances[account] + amount;

278:             _balances[account] = cachedBalance - amount;

281:         _totalSupply = _totalSupply - amount;

289:         _allowances[owner][spender] = amount;

L68, L74, L255, L258, L265, L278, L281, L289

File: packages/contracts/contracts/Governor.sol

155:         roleNames[role] = roleName;

L155

File: packages/contracts/contracts/PriceFeed.sol

63:         fallbackCaller = IFallbackCaller(_fallbackCallerAddress);

69:         ETH_BTC_CL_FEED = AggregatorV3Interface(_ethBtcCLFeed);
70:         STETH_ETH_CL_FEED = AggregatorV3Interface(_collEthCLFeed);

88:         status = Status.chainlinkWorking;

377:                     fallbackCaller = newFallbackCaler;

386:             fallbackCaller = newFallbackCaler;

547:         status = _status;

554:         lastGoodPrice = _currentPrice;

L63, L69, L88, L377, L386, L547, L554

File: packages/contracts/contracts/SortedCdps.sol

93:         maxSize = _size;

95:         cdpManager = ICdpManager(_cdpManagerAddress);
96:         borrowerOperationsAddress = _borrowerOperationsAddress;

L93, L95

File: packages/contracts/contracts/HintHelpers.sol

34:         sortedCdps = ISortedCdps(_sortedCdpsAddress);
35:         cdpManager = ICdpManager(_cdpManagerAddress);

L34

File: packages/contracts/contracts/LeverageMacroBase.sol

60:         borrowerOperations = IBorrowerOperations(_borrowerOperationsAddress);
61:         activePool = IActivePool(_activePool);
62:         cdpManager = ICdpCdps(_cdpManager);
63:         ebtcToken = IEBTCToken(_ebtc);
64:         stETH = ICollateralToken(_coll);
65:         sortedCdps = ISortedCdps(_sortedCdps);

67:         willSweep = _sweepToCaller;

L60, L67

File: packages/contracts/contracts/LeverageMacroFactory.sol

29:         borrowerOperations = _borrowerOperationsAddress;
30:         activePool = _activePool;
31:         cdpManager = _cdpManager;
32:         ebtcToken = _ebtc;
33:         stETH = _coll;
34:         sortedCdps = _sortedCdps;

L29

File: packages/contracts/contracts/LeverageMacroReference.sol

36:         theOwner = _owner;

L36

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

45:         owner = _owner;

L45

File: packages/contracts/contracts/Dependencies/Auth.sol

19:         owner = _owner;
20:         authority = _authority;

47:         authority = newAuthority;

53:         owner = newOwner;

L19, L47, L53

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

43:         _authority = Authority(newAuthority);

47:             _authorityInitialized = true;

59:         _authority = Authority(newAuthority);
60:         _authorityInitialized = true;

L43, L47, L59

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

53:         activePool = IActivePool(_activePoolAddress);
54:         priceFeed = IPriceFeed(_priceFeedAddress);
55:         collateral = ICollateralToken(_collateralAddress);

L53

File: packages/contracts/contracts/Dependencies/ReentrancyGuard.sol

16:         locked = LOCKED;

20:         locked = OPEN;

L16, L20

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

96:             capabilityFlag[target][functionSig] = CapabilityFlag.Public;

98:             capabilityFlag[target][functionSig] = CapabilityFlag.None;

138:         capabilityFlag[target][functionSig] = CapabilityFlag.Burned;

L96, L98, L138

</details>

 

[G-06] Use require instead of assert

Prior to solc 0.8.0, assert used the invalid opcode which used up all the remaining gas while require used the revert opcode which refunded the gas and therefore the importance of using require instead of assert was greater.

However, after 0.8.0, assert uses revert opcode just like require but creates a Panic(uint256) error instead of Error(string) created by require.

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

<details> <summary>Instances: 1</summary>
File: packages/contracts/contracts/LiquidationLibrary.sol

564:         assert(toLiquidator < _totalColToSend); // Assert is correct here for Echidna

L564

</details>

 

[G-07] Avoid zero transfer to save gas

In Solidity, unnecessary operations can waste gas. For example, a transfer function without a zero amount check uses gas even if called with a zero amount, since the contract state remains unchanged. Implementing a zero amount check avoids these unnecessary function calls, saving gas and improving efficiency.

<details> <summary>Instances: 10</summary>
File: packages/contracts/contracts/ActivePool.sol

274:         collateral.transfer(address(receiver), amount);

283:         collateral.transferFrom(address(receiver), address(this), amountWithFee);

286:         collateral.transfer(feeRecipientAddress, fee);

381:         IERC20(token).safeTransfer(cachedFeeRecipientAddress, amount);

L274, L283, L286, L381

File: packages/contracts/contracts/BorrowerOperations.sol

785:         collateral.transferFrom(msg.sender, address(activePool), _stEthBalance);

1100:         ebtcToken.transferFrom(address(receiver), feeRecipientAddress, fee + amount);

L785, L1100

File: packages/contracts/contracts/CollSurplusPool.sol

148:         IERC20(token).safeTransfer(feeRecipientAddress, amount);

L148

File: packages/contracts/contracts/LeverageMacroBase.sol

129:             IERC20(operation.tokenToTransferIn).safeTransferFrom(
130:                 msg.sender,
131:                 address(this),
132:                 operation.amountToTransferIn
133:             );

224:             ebtcToken.transfer(msg.sender, ebtcBal);

237:         IERC20(token).safeTransfer(msg.sender, amount);

L129, L224, L237

</details>

 

[G-08] Using bool for storage incurs overhead

Booleans are more expensive than uint256 or any type that takes up a full word because each write operation emits an extra SLOAD to first read the slot's contents, replace the bits taken up by the boolean, and then write back. This is the compiler's defense against contract upgrades and pointer aliasing, and it cannot be disabled.

Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas) for the extra SLOAD, and to avoid Gsset (20000 gas) when changing from false to true, after having been true in the past (see OpenZeppelin's ReentrancyGuard for reference).

<details> <summary>Instances: 4</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

143:     bool public redemptionsPaused;

L143

File: packages/contracts/contracts/LeverageMacroBase.sol

35:     bool internal immutable willSweep;

L35

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

14:     bool private _authorityInitialized;

L14

File: packages/contracts/contracts/Dependencies/ERC3156FlashLender.sol

15:     bool public flashLoansPaused;

L15

</details>

 

[G-09] Do not compare boolean expressions to boolean literals

<x> == true <=> <x>, also <x> == false <=> !<x>

<details> <summary>Instances: 4</summary>
File: packages/contracts/contracts/CdpManager.sol

332:         require(redemptionsPaused == false, "CdpManager: Redemptions Paused");

L332

File: packages/contracts/contracts/LiquidationLibrary.sol

100:         bool _recoveryModeAtStart = _TCR < CCR ? true : false;

695:         vars.recoveryModeAtStart = _TCR < CCR ? true : false;

788:                     vars.backToNormalMode = _TCR < CCR ? false : true;

L100, L695, L788

</details>

 

[G-10] Cache multiple accesses of mapping/array values

Caching a mapping's value in a local storage or calldata variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations. Caching an array's struct avoids recalculating the array offsets into memory/calldata.

<details> <summary>Instances: 39</summary>
File: packages/contracts/contracts/CdpManager.sol

914:         Cdps[_cdpId].debt = _debt;
915:         Cdps[_cdpId].coll = _coll;
916:         Cdps[_cdpId].status = Status.active;
917:         Cdps[_cdpId].liquidatorRewardShares = _liquidatorRewardShares;

L914

File: packages/contracts/contracts/LiquidationLibrary.sol

490:         emit CdpUpdated(
491:             _cdpId,
492:             sortedCdps.getOwnerAddress(_cdpId),
493:             msg.sender,
494:             _oldDebt,
495:             _oldColl,
496:             Cdps[_cdpId].debt,
497:             Cdps[_cdpId].coll,
498:             Cdps[_cdpId].stake,
499:             CdpOperation.partiallyLiquidate
500:         );

490:         emit CdpUpdated(
491:             _cdpId,
492:             sortedCdps.getOwnerAddress(_cdpId),
493:             msg.sender,
494:             _oldDebt,
495:             _oldColl,
496:             Cdps[_cdpId].debt,
497:             Cdps[_cdpId].coll,
498:             Cdps[_cdpId].stake,
499:             CdpOperation.partiallyLiquidate
500:         );

L490

File: packages/contracts/contracts/CdpManagerStorage.sol

271:         Cdps[_cdpId].status = closedStatus;
272:         Cdps[_cdpId].coll = 0;
273:         Cdps[_cdpId].debt = 0;
274:         Cdps[_cdpId].liquidatorRewardShares = 0;

350:         uint256 _oldPerUnitCdp = cdpStEthFeePerUnitIndex[_cdpId];

405:             cdpStEthFeePerUnitIndex[_cdpId] = _systemStEthFeePerUnitIndex;

411:         uint256 _newTotalStakes = totalStakes - Cdps[_cdpId].stake;

413:         Cdps[_cdpId].stake = 0;

464:         Status cdpStatus = Cdps[_cdpId].status;

471:         uint128 index = Cdps[_cdpId].arrayIndex;

621:         uint256 _cdpCol = Cdps[_cdpId].coll;

631:         uint256 _feeSplitDistributed = Cdps[_cdpId].stake *
632:             (_systemStEthFeePerUnitIndex - _cdpStEthFeePerUnitIndex);

L271, L350, L405, L411, L413, L464, L471, L621, L631

File: packages/contracts/contracts/CollSurplusPool.sol

80:         uint256 newAmount = balances[_account] + _amount;
81:         balances[_account] = newAmount;

91:         uint256 claimableColl = balances[_account];

94:         balances[_account] = 0;

L80, L91, L94

File: packages/contracts/contracts/EBTCToken.sol

250:         uint256 cachedSenderBalances = _balances[sender];

255:             _balances[sender] = cachedSenderBalances - amount;

258:         _balances[recipient] = _balances[recipient] + amount;
258:         _balances[recipient] = _balances[recipient] + amount;

266:         _balances[account] = _balances[account] + amount;
266:         _balances[account] = _balances[account] + amount;

273:         uint256 cachedBalance = _balances[account];

278:             _balances[account] = cachedBalance - amount;

L250, L255, L258, L266, L273, L278

File: packages/contracts/contracts/SortedCdps.sol

388:             data.nodes[_id].nextId = data.head;

393:             data.nodes[_id].prevId = data.tail;

398:             data.nodes[_id].nextId = nextId;
399:             data.nodes[_id].prevId = prevId;

433:             require(contains(_ids[i]), "SortedCdps: List does not contain the id");

450:             delete data.nodes[_ids[i]];
451:             emit NodeRemoved(_ids[i]);

465:                 data.head = data.nodes[_id].nextId;

471:                 data.tail = data.nodes[_id].prevId;

477:                 data.nodes[data.nodes[_id].prevId].nextId = data.nodes[_id].nextId;

479:                 data.nodes[data.nodes[_id].nextId].prevId = data.nodes[_id].prevId;

488:         delete data.nodes[_id];

626:         bytes32 nextId = data.nodes[prevId].nextId;

630:             prevId = data.nodes[prevId].nextId;
631:             nextId = data.nodes[prevId].nextId;

649:         bytes32 prevId = data.nodes[nextId].prevId;

653:             nextId = data.nodes[nextId].prevId;
654:             prevId = data.nodes[nextId].prevId;

L388, L393, L398, L433, L450, L465, L471, L477, L479, L488, L626, L630, L649, L653

File: packages/contracts/contracts/LeverageMacroBase.sol

440:                 require(
441:                     IERC20(swapChecks[i].tokenToCheck).balanceOf(address(this)) >
442:                         swapChecks[i].expectedMinOut,
443:                     "LeverageMacroReference: swap check failure!"
444:                 );

440:                 require(
441:                     IERC20(swapChecks[i].tokenToCheck).balanceOf(address(this)) >
442:                         swapChecks[i].expectedMinOut,
443:                     "LeverageMacroReference: swap check failure!"
444:                 );

L440

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

90:         require(
91:             capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
92:             "RolesAuthority: Capability Burned"
93:         );

96:             capabilityFlag[target][functionSig] = CapabilityFlag.Public;

98:             capabilityFlag[target][functionSig] = CapabilityFlag.None;

113:             getRolesWithCapability[target][functionSig] |= bytes32(1 << role);

120:             getRolesWithCapability[target][functionSig] &= ~bytes32(1 << role);

114:             enabledFunctionSigsByTarget[target].add(bytes32(functionSig));

121:             enabledFunctionSigsByTarget[target].remove(bytes32(functionSig));

124:             if (enabledFunctionSigsByTarget[target].length() == 0) {

134:         require(
135:             capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
136:             "RolesAuthority: Capability Burned"
137:         );
138:         capabilityFlag[target][functionSig] = CapabilityFlag.Burned;

149:             getUserRoles[user] |= bytes32(1 << role);

155:             getUserRoles[user] &= ~bytes32(1 << role);

158:             if (getUserRoles[user] == bytes32(0)) {

L90, L96, L98, L113, L120, L121, L124, L134, L149, L155, L158

</details>

 

[G-11] Using constants directly, rather than caching the value, saves gas

<details> <summary>Instances: 2</summary>
File: packages/contracts/contracts/SimplifiedDiamondLike.sol

84:         bytes32 position = DIAMOND_STORAGE_POSITION;

L84

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

68:         uint256 y = DECIMAL_PRECISION;

L68

</details>

 

[G-12] Function result should be cached

The instances below show multiple calls to a single function within the same function.

<details> <summary>Instances: 90</summary>
File: packages/contracts/contracts/ActivePool.sol

261:     function flashLoan(
262:         IERC3156FlashBorrower receiver,
263:         address token,
264:         uint256 amount,
265:         bytes calldata data
266:     ) external override returns (bool) {

272:         uint256 oldRate = collateral.getPooledEthByShares(DECIMAL_PRECISION);

302:         require(
303:             collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
304:             "ActivePool: Should keep same collateral share rate"
305:         );

371:     function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {

376:         uint256 balance = IERC20(token).balanceOf(address(this));

381:         IERC20(token).safeTransfer(cachedFeeRecipientAddress, amount);

L261, L272, L302, L371, L376, L381

File: packages/contracts/contracts/BorrowerOperations.sol

439:     function _openCdp(
440:         uint256 _debt,
441:         bytes32 _upperHint,
442:         bytes32 _lowerHint,
443:         uint256 _stEthBalance,
444:         address _borrower
445:     ) internal returns (bytes32) {

500:                 cdpManager.notifyEndGracePeriod(newTCR);

509:             cdpManager.notifyEndGracePeriod(newTCR);

852:     function _requireValidAdjustmentInCurrentMode(
853:         bool _isRecoveryMode,
854:         uint256 _stEthBalanceDecrease,
855:         bool _isDebtIncrease,
856:         AdjustCdpLocals memory _vars
857:     ) internal {

896:                 cdpManager.notifyEndGracePeriod(_vars.newTCR);

906:             cdpManager.notifyEndGracePeriod(_vars.newTCR);

L439, L500, L509, L852, L896, L906

File: packages/contracts/contracts/CdpManager.sol

320:     function redeemCollateral(
321:         uint256 _debt,
322:         bytes32 _firstRedemptionHint,
323:         bytes32 _upperPartialRedemptionHint,
324:         bytes32 _lowerPartialRedemptionHint,
325:         uint256 _partialRedemptionHintNICR,
326:         uint256 _maxIterations,
327:         uint256 _maxFeePercentage
328:     ) external override nonReentrantSelfAndBOps {

367:             currentBorrower = sortedCdps.getOwnerAddress(_cId);

371:                 currentBorrower = sortedCdps.getOwnerAddress(_cId);

370:                 _cId = sortedCdps.getPrev(_cId);

424:                 bytes32 _nextId = sortedCdps.getPrev(_cId);

L320, L367, L371, L424

File: packages/contracts/contracts/LiquidationLibrary.sol

135:     function _liquidateIndividualCdpSetupCDP(
136:         LiquidationLocals memory _liqState,
137:         LiquidationRecoveryModeLocals memory _recoveryState
138:     ) internal {

145:             (
146:                 liquidationValues.debtToBurn,
147:                 liquidationValues.totalCollToSendToLiquidator,
148:                 liquidationValues.debtToRedistribute,
149:                 liquidationValues.liquidatorCollSharesReward,
150:                 liquidationValues.collSurplus
151:             ) = _liquidateCdpInGivenMode(_liqState, _recoveryState);

162:                 (
163:                     liquidationValues.debtToBurn,
164:                     liquidationValues.totalCollToSendToLiquidator,
165:                     liquidationValues.debtToRedistribute,
166:                     liquidationValues.liquidatorCollSharesReward,
167:                     liquidationValues.collSurplus
168:                 ) = _liquidateCdpInGivenMode(_liqState, _recoveryState);

186:     function _liquidateCdpInGivenMode(
187:         LiquidationLocals memory _liqState,
188:         LiquidationRecoveryModeLocals memory _recoveryState
189:     ) private returns (uint256, uint256, uint256, uint256, uint256) {

202:             return (
203:                 _outputState.totalDebtToBurn,
204:                 _outputState.totalCollSharesToSend,
205:                 _outputState.totalDebtToRedistribute,
206:                 _outputState.totalLiquidatorRewardCollShares,
207:                 _outputState.totalSurplusCollShares
208:             );

213:             return (
214:                 _outputState.totalDebtToBurn,
215:                 _outputState.totalCollSharesToSend,
216:                 _outputState.totalDebtToRedistribute,
217:                 _outputState.totalLiquidatorRewardCollShares,
218:                 _outputState.totalSurplusCollShares
219:             );

733:     function _getTotalFromBatchLiquidate_RecoveryMode(
734:         uint256 _price,
735:         uint256 _systemCollShares,
736:         uint256 _systemDebt,
737:         bytes32[] memory _cdpArray
738:     ) internal returns (LiquidationTotals memory totals) {

745:         uint256 _TCR = _computeTCRWithGivenSystemValues(
746:             vars.entireSystemColl,
747:             vars.entireSystemDebt,
748:             _price
749:         );

783:                     _TCR = _computeTCRWithGivenSystemValues(
784:                         vars.entireSystemColl,
785:                         vars.entireSystemDebt,
786:                         _price
787:                     );

759:                 if (
760:                     !vars.backToNormalMode &&
761:                     (_checkICRAgainstMCR(vars.ICR) || canLiquidateRecoveryMode(vars.ICR, _TCR))
762:                 ) {

790:                 } else if (vars.backToNormalMode && _checkICRAgainstMCR(vars.ICR)) {

764:                     _syncAccounting(vars.cdpId);

791:                     _syncAccounting(vars.cdpId);

781:                     totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

795:                     totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

L135, L145, L162, L186, L202, L213, L733, L745, L783, L790, L791, L795

File: packages/contracts/contracts/CdpManagerStorage.sol

304:     function _getPendingRedistributedDebt(
305:         bytes32 _cdpId
306:     ) internal view returns (uint256 pendingEBTCDebtReward, uint256 _debtIndexDiff) {

310:             return (0, 0);

318:             return (0, 0);

616:     function getAccumulatedFeeSplitApplied(
617:         bytes32 _cdpId,
618:         uint256 _systemStEthFeePerUnitIndex
619:     ) public view returns (uint256, uint256) {

628:             return (0, _cdpCol);

643:             return (0, _cdpCol);

L304, L310, L318, L616, L628, L643

File: packages/contracts/contracts/CollSurplusPool.sol

142:     function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {

145:         uint256 balance = IERC20(token).balanceOf(address(this));

148:         IERC20(token).safeTransfer(feeRecipientAddress, amount);

L142, L145, L148

File: packages/contracts/contracts/Governor.sol

43:     function getUsersByRole(uint8 role) external view returns (address[] memory usersWithRole) {

48:             bool _canCall = doesUserHaveRole(user, role);

59:                 bool _canCall = doesUserHaveRole(user, role);

49:             if (_canCall) {

60:                 if (_canCall) {

73:     function getRolesForUser(address user) external view returns (uint8[] memory rolesForUser) {

77:             if (doesUserHaveRole(user, i)) {

85:                 if (doesUserHaveRole(user, i)) {

96:     function getRolesFromByteMap(bytes32 byteMap) public pure returns (uint8[] memory roleIds) {

100:             if (roleEnabled) {

109:                 if (roleEnabled) {

L43, L48, L59, L60, L73, L77, L85, L96, L100, L109

File: packages/contracts/contracts/PriceFeed.sol

98:     function fetchPrice() external override returns (uint256) {

121:                     _changeStatus(Status.usingFallbackChainlinkUntrusted);

126:                 _changeStatus(Status.usingFallbackChainlinkUntrusted);

112:                 if (_fallbackIsBroken(fallbackResponse)) {

133:                 if (_fallbackIsBroken(fallbackResponse)) {

120:                 if (_fallbackIsFrozen(fallbackResponse)) {

141:                 if (_fallbackIsFrozen(fallbackResponse)) {

127:                 return _storeFallbackPrice(fallbackResponse);

146:                 return _storeFallbackPrice(fallbackResponse);

153:                 if (_fallbackIsBroken(fallbackResponse)) {

113:                     _changeStatus(Status.bothOraclesUntrusted);

154:                     _changeStatus(Status.bothOraclesUntrusted);

160:                 if (_fallbackIsFrozen(fallbackResponse)) {
161:                     _changeStatus(Status.usingFallbackChainlinkUntrusted);

175:                 _changeStatus(Status.usingFallbackChainlinkUntrusted);
176:                 return _storeFallbackPrice(fallbackResponse);

180:             if (_fallbackIsBroken(fallbackResponse)) {

134:                     _changeStatus(Status.usingChainlinkFallbackUntrusted);

181:                 _changeStatus(Status.usingChainlinkFallbackUntrusted);

170:                     return _storeChainlinkPrice(chainlinkResponse.answer);

185:             return _storeChainlinkPrice(chainlinkResponse.answer);

199:                 return _storeChainlinkPrice(chainlinkResponse.answer);

202:             if (_fallbackIsBroken(fallbackResponse)) {
203:                 _changeStatus(Status.bothOraclesUntrusted);

211:             if (_fallbackIsFrozen(fallbackResponse)) {

216:             return _storeFallbackPrice(fallbackResponse);

110:             if (_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse)) {

226:                 if (
227:                     !_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) &&
228:                     !_chainlinkIsFrozen(chainlinkResponse)
229:                 ) {

131:             if (_chainlinkIsFrozen(chainlinkResponse)) {

226:                 if (
227:                     !_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) &&
228:                     !_chainlinkIsFrozen(chainlinkResponse)
229:                 ) {
230:                     _changeStatus(Status.usingChainlinkFallbackUntrusted);
231:                     return _storeChainlinkPrice(chainlinkResponse.answer);

191:             if (
192:                 _bothOraclesLiveAndUnbrokenAndSimilarPrice(
193:                     chainlinkResponse,
194:                     prevChainlinkResponse,
195:                     fallbackResponse
196:                 )
197:             ) {

239:             if (
240:                 _bothOraclesLiveAndUnbrokenAndSimilarPrice(
241:                     chainlinkResponse,
242:                     prevChainlinkResponse,
243:                     fallbackResponse
244:                 )
245:             ) {

198:                 _changeStatus(Status.chainlinkWorking);

246:                 _changeStatus(Status.chainlinkWorking);
247:                 return _storeChainlinkPrice(chainlinkResponse.answer);

256:             if (_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse)) {

258:                 if (_fallbackIsBroken(fallbackResponse)) {
259:                     _changeStatus(Status.bothOraclesUntrusted);

264:                 _changeStatus(Status.usingFallbackChainlinkUntrusted);

266:                 if (_fallbackIsFrozen(fallbackResponse)) {

271:                 return _storeFallbackPrice(fallbackResponse);

274:             if (_chainlinkIsFrozen(chainlinkResponse)) {

276:                 if (_fallbackIsBroken(fallbackResponse)) {
277:                     _changeStatus(Status.usingChainlinkFallbackUntrusted);

282:                 if (_fallbackIsFrozen(fallbackResponse)) {

287:                 return _storeFallbackPrice(fallbackResponse);

291:             if (_fallbackIsBroken(fallbackResponse)) {
292:                 _changeStatus(Status.usingChainlinkFallbackUntrusted);
293:                 return _storeChainlinkPrice(chainlinkResponse.answer);

297:             if (_fallbackIsFrozen(fallbackResponse)) {

169:                 if (_bothOraclesSimilarPrice(chainlinkResponse, fallbackResponse)) {

303:             if (_bothOraclesSimilarPrice(chainlinkResponse, fallbackResponse)) {
304:                 _changeStatus(Status.chainlinkWorking);
305:                 return _storeChainlinkPrice(chainlinkResponse.answer);

309:             _changeStatus(Status.usingFallbackChainlinkUntrusted);
310:             return _storeFallbackPrice(fallbackResponse);

316:             if (_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse)) {
317:                 _changeStatus(Status.bothOraclesUntrusted);

322:             if (_chainlinkIsFrozen(chainlinkResponse)) {

327:             if (
328:                 _bothOraclesLiveAndUnbrokenAndSimilarPrice(
329:                     chainlinkResponse,
330:                     prevChainlinkResponse,
331:                     fallbackResponse
332:                 )
333:             ) {
334:                 _changeStatus(Status.chainlinkWorking);
335:                 return _storeChainlinkPrice(chainlinkResponse.answer);

150:             if (_chainlinkPriceChangeAboveMax(chainlinkResponse, prevChainlinkResponse)) {

340:             if (_chainlinkPriceChangeAboveMax(chainlinkResponse, prevChainlinkResponse)) {
341:                 _changeStatus(Status.bothOraclesUntrusted);

347:             return _storeChainlinkPrice(chainlinkResponse.answer);

606:     function _getCurrentChainlinkResponse()
607:         internal
608:         view
609:         returns (ChainlinkResponse memory chainlinkResponse)
610:     {

615:         try ETH_BTC_CL_FEED.decimals() returns (uint8 decimals) {

623:         try STETH_ETH_CL_FEED.decimals() returns (uint8 decimals) {

687:     function _getPrevChainlinkResponse(
688:         uint80 _currentRoundEthBtcId,
689:         uint80 _currentRoundStEthEthId
690:     ) internal view returns (ChainlinkResponse memory prevChainlinkResponse) {

703:         try ETH_BTC_CL_FEED.decimals() returns (uint8 decimals) {

711:         try STETH_ETH_CL_FEED.decimals() returns (uint8 decimals) {

L98, L121, L126, L133, L141, L146, L153, L154, L160, L175, L180, L181, L185, L199, L202, L211, L216, L226, L226, L239, L246, L256, L258, L264, L266, L271, L274, L276, L282, L287, L291, L297, L303, L309, L316, L322, L327, L340, L347, L606, L615, L623, L687, L703, L711

File: packages/contracts/contracts/SortedCdps.sol

591:     function _validInsertPosition(
592:         uint256 _NICR,
593:         bytes32 _prevId,
594:         bytes32 _nextId
595:     ) internal view returns (bool) {

604:             return data.tail == _prevId && _NICR <= cdpManager.getCachedNominalICR(_prevId);

607:             return
608:                 data.nodes[_prevId].nextId == _nextId &&
609:                 cdpManager.getCachedNominalICR(_prevId) >= _NICR &&
610:                 _NICR >= cdpManager.getCachedNominalICR(_nextId);

601:             return data.head == _nextId && _NICR >= cdpManager.getCachedNominalICR(_nextId);

607:             return
608:                 data.nodes[_prevId].nextId == _nextId &&
609:                 cdpManager.getCachedNominalICR(_prevId) >= _NICR &&
610:                 _NICR >= cdpManager.getCachedNominalICR(_nextId);

674:     function _findInsertPosition(
675:         uint256 _NICR,
676:         bytes32 _prevId,
677:         bytes32 _nextId
678:     ) internal view returns (bytes32, bytes32) {

704:             return _descendList(_NICR, prevId);

707:             return _descendList(_NICR, prevId);

L591, L604, L607, L607, L674, L704, L707

File: packages/contracts/contracts/HintHelpers.sol

48:     function getRedemptionHints(
49:         uint256 _EBTCamount,
50:         uint256 _price,
51:         uint256 _maxIterations
52:     )
53:         external
54:         view
55:         returns (
56:             bytes32 firstRedemptionHint,
57:             uint256 partialRedemptionHintNICR,
58:             uint256 truncatedEBTCamount,
59:             uint256 partialRedemptionNewColl
60:         )
61:     {

66:             vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

73:                 vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

72:                 vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);

116:                 vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
117:                 vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

L48, L66, L73, L116

File: packages/contracts/contracts/LeverageMacroBase.sol

118:     function doOperation(
119:         FlashLoanType flType,
120:         uint256 borrowAmount,
121:         LeverageMacroOperation calldata operation,
122:         PostOperationCheck postCheckType,
123:         PostCheckParams calldata checkParams
124:     ) external {

177:             _doCheckValueType(checkParams.expectedDebt, cdpInfo.debt);

189:             _doCheckValueType(checkParams.expectedDebt, cdpInfo.debt);

178:             _doCheckValueType(checkParams.expectedCollateral, cdpInfo.coll);

190:             _doCheckValueType(checkParams.expectedCollateral, cdpInfo.coll);

187:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(checkParams.cdpId);

199:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(checkParams.cdpId);

398:     function _doSwap(SwapOperation memory swapData) internal {

405:         IERC20(swapData.tokenForSwap).safeApprove(
406:             swapData.addressForApprove,
407:             swapData.exactApproveAmount
408:         );

427:         IERC20(swapData.tokenForSwap).safeApprove(swapData.addressForApprove, 0);

L118, L177, L189, L190, L199, L398, L405, L427

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

134:     function _executeOne(Operation calldata op) internal {

144:                 success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)

149:                 success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
149:                 success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)

L134, L144, L149

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

38:     function setAuthority(address newAuthority) public virtual {

43:         _authority = Authority(newAuthority);

50:         emit AuthorityUpdated(msg.sender, Authority(newAuthority));

L38, L43, L50

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

59:     function _decPow(uint256 _base, uint256 _minutes) internal pure returns (uint256) {

75:                 x = decMul(x, x);

80:                 x = decMul(x, x);

79:                 y = decMul(x, y);

85:         return decMul(x, y);

L59, L75, L80, L85

</details>

 

[G-13] Cache storage variables read from more than once

If the variable is only accessed once, it's cheaper to use the state variable directly that one time, and save the 3 gas the extra stack assignment would spend.

<details> <summary>Instances: 37</summary>
File: packages/contracts/contracts/ActivePool.sol

261:     function flashLoan(
262:         IERC3156FlashBorrower receiver,
263:         address token,
264:         uint256 amount,
265:         bytes calldata data
266:     ) external override returns (bool) {

294:         require(
295:             collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),
296:             "ActivePool: Must repay Balance"
297:         );
298:         require(
299:             collateral.sharesOf(address(this)) >= systemCollShares,
300:             "ActivePool: Must repay Share"
301:         );

L261, L294

File: packages/contracts/contracts/BorrowerOperations.sol

1077:     function flashLoan(
1078:         IERC3156FlashBorrower receiver,
1079:         address token,
1080:         uint256 amount,
1081:         bytes calldata data
1082:     ) external override returns (bool) {

1100:         ebtcToken.transferFrom(address(receiver), feeRecipientAddress, fee + amount);

1103:         ebtcToken.burn(feeRecipientAddress, amount);

L1077, L1100, L1103

File: packages/contracts/contracts/CdpManagerStorage.sol

441:     function _computeNewStake(uint256 _coll) internal view returns (uint256) {

453:             require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");
454:             stake = (_coll * totalStakesSnapshot) / totalCollateralSnapshot;

761:     function _calcSyncedGlobalAccounting(
762:         uint256 _newIndex,
763:         uint256 _oldIndex
764:     ) internal view returns (uint256, uint256, uint256) {

774:             uint256 _newPerUnit = systemStEthFeePerUnitIndex + _deltaFeePerUnit;

777:             return (0, systemStEthFeePerUnitIndex, systemStEthFeePerUnitIndexError);

L441, L453, L761, L774, L777

File: packages/contracts/contracts/PriceFeed.sol

98:     function fetchPrice() external override returns (uint256) {

114:                     return lastGoodPrice;

122:                     return lastGoodPrice;

135:                     return lastGoodPrice;

142:                     return lastGoodPrice;

155:                     return lastGoodPrice;

162:                     return lastGoodPrice;

108:         if (status == Status.chainlinkWorking) {

189:         if (status == Status.usingFallbackChainlinkUntrusted) {

204:                 return lastGoodPrice;

212:                 return lastGoodPrice;

220:         if (status == Status.bothOraclesUntrusted) {

251:             return lastGoodPrice;

255:         if (status == Status.usingFallbackChainlinkFrozen) {

260:                     return lastGoodPrice;

267:                     return lastGoodPrice;

278:                     return lastGoodPrice;

283:                     return lastGoodPrice;

298:                 return lastGoodPrice;

314:         if (status == Status.usingChainlinkFallbackUntrusted) {

318:                 return lastGoodPrice;

323:                 return lastGoodPrice;

342:                 return lastGoodPrice;

351:         return lastGoodPrice;

358:     function setFallbackCaller(address _fallbackCaller) external requiresAuth {

376:                     address oldFallbackCaller = address(fallbackCaller);

384:             address oldFallbackCaller = address(fallbackCaller);

L98, L114, L122, L135, L142, L155, L162, L189, L204, L212, L220, L251, L255, L260, L267, L278, L283, L298, L314, L318, L323, L342, L351, L358, L376, L384

File: packages/contracts/contracts/SortedCdps.sol

344:     function insert(
345:         address owner,
346:         uint256 _NICR,
347:         bytes32 _prevId,
348:         bytes32 _nextId
349:     ) external override returns (bytes32) {

351:         bytes32 _id = toCdpId(owner, block.number, nextCdpNonce);

357:             ++nextCdpNonce;

363:     function _insert(bytes32 _id, uint256 _NICR, bytes32 _prevId, bytes32 _nextId) internal {

388:             data.nodes[_id].nextId = data.head;
389:             data.nodes[data.head].prevId = _id;

393:             data.nodes[_id].prevId = data.tail;
394:             data.nodes[data.tail].nextId = _id;

419:     function batchRemove(bytes32[] memory _ids) external override {

424:         bytes32 _firstPrev = data.nodes[_ids[0]].prevId;
425:         bytes32 _lastNext = data.nodes[_ids[_len - 1]].nextId;

456:     function _remove(bytes32 _id) internal {

465:                 data.head = data.nodes[_id].nextId;

471:                 data.tail = data.nodes[_id].prevId;

477:                 data.nodes[data.nodes[_id].prevId].nextId = data.nodes[_id].nextId;

479:                 data.nodes[data.nodes[_id].nextId].prevId = data.nodes[_id].prevId;

619:     function _descendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {

626:         bytes32 nextId = data.nodes[prevId].nextId;

630:             prevId = data.nodes[prevId].nextId;
631:             nextId = data.nodes[prevId].nextId;

642:     function _ascendList(uint256 _NICR, bytes32 _startId) internal view returns (bytes32, bytes32) {

649:         bytes32 prevId = data.nodes[nextId].prevId;

653:             nextId = data.nodes[nextId].prevId;
654:             prevId = data.nodes[nextId].prevId;

L344, L351, L357, L363, L388, L393, L419, L424, L456, L465, L471, L477, L479, L619, L626, L630, L642, L649, L653

</details>

 

[G-14] Use calldata instead of memory for function arguments that are read only

When a function with a memory array is called externally, the abi.decode() step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.

If the array is passed to an internal function which passes the array to another internal function where the array is modified and therefore memory is used in the external call, it's still more gas-efficient to use calldata when the external function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one

<details> <summary>Instances: 26</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

760:     function _processTokenMovesFromAdjustment(MoveTokensParams memory _varMvTokens) internal {

986:     function _getNewNominalICRFromCdpChange(
987:         AdjustCdpLocals memory vars,
988:         bool _isDebtIncrease
989:     ) internal pure returns (uint256) {

L760, L986

File: packages/contracts/contracts/CdpManager.sol

135:     function _redeemCollateralFromCdp(
136:         SingleRedemptionInputs memory _redeemColFromCdp
137:     ) internal returns (SingleRedemptionValues memory singleRedemption) {

L135

File: packages/contracts/contracts/LiquidationLibrary.sol

135:     function _liquidateIndividualCdpSetupCDP(
136:         LiquidationLocals memory _liqState,
137:         LiquidationRecoveryModeLocals memory _recoveryState
138:     ) internal {

186:     function _liquidateCdpInGivenMode(
187:         LiquidationLocals memory _liqState,
188:         LiquidationRecoveryModeLocals memory _recoveryState
189:     ) private returns (uint256, uint256, uint256, uint256, uint256) {

397:     function _liquidateCDPPartially(
398:         LiquidationLocals memory _partialState
399:     ) private returns (uint256, uint256) {

466:     function _reInsertPartialLiquidation(
467:         LiquidationLocals memory _partialState,
468:         uint256 _newNICR,
469:         uint256 _oldDebt,
470:         uint256 _oldColl
471:     ) internal {

608:     function _getLiquidationValuesNormalMode(
609:         uint256 _price,
610:         uint256 _TCR,
611:         LocalVariables_LiquidationSequence memory vars,
612:         LiquidationValues memory singleLiquidation
613:     ) internal {

642:     function _getLiquidationValuesRecoveryMode(
643:         uint256 _price,
644:         uint256 _systemDebt,
645:         uint256 _systemCollShares,
646:         LocalVariables_LiquidationSequence memory vars,
647:         LiquidationValues memory singleLiquidation
648:     ) internal {

839:     function _addLiquidationValuesToTotals(
840:         LiquidationTotals memory oldTotals,
841:         LiquidationValues memory singleLiquidation
842:     ) internal pure returns (LiquidationTotals memory newTotals) {

L135, L186, L397, L466, L608, L642, L839

File: packages/contracts/contracts/Governor.sol

120:     function getByteMapFromRoles(uint8[] memory roleIds) public pure returns (bytes32) {

154:     function setRoleName(uint8 role, string memory roleName) external requiresAuth {

L120, L154

File: packages/contracts/contracts/PriceFeed.sol

400:     function _chainlinkIsBroken(
401:         ChainlinkResponse memory _currentResponse,
402:         ChainlinkResponse memory _prevResponse
403:     ) internal view returns (bool) {

412:     function _badChainlinkResponse(ChainlinkResponse memory _response) internal view returns (bool) {

435:     function _chainlinkIsFrozen(ChainlinkResponse memory _response) internal view returns (bool) {

445:     function _chainlinkPriceChangeAboveMax(
446:         ChainlinkResponse memory _currentResponse,
447:         ChainlinkResponse memory _prevResponse
448:     ) internal pure returns (bool) {

465:     function _fallbackIsBroken(FallbackResponse memory _response) internal view returns (bool) {

485:     function _fallbackIsFrozen(
486:         FallbackResponse memory _fallbackResponse
487:     ) internal view returns (bool) {

503:     function _bothOraclesLiveAndUnbrokenAndSimilarPrice(
504:         ChainlinkResponse memory _chainlinkResponse,
505:         ChainlinkResponse memory _prevChainlinkResponse,
506:         FallbackResponse memory _fallbackResponse
507:     ) internal view returns (bool) {

526:     function _bothOraclesSimilarPrice(
527:         ChainlinkResponse memory _chainlinkResponse,
528:         FallbackResponse memory _fallbackResponse
529:     ) internal pure returns (bool) {

561:     function _storeFallbackPrice(
562:         FallbackResponse memory _fallbackResponse
563:     ) internal returns (uint256) {

L400, L412, L435, L445, L465, L485, L503, L526, L561

File: packages/contracts/contracts/LeverageMacroBase.sol

244:     function _doCheckValueType(CheckValueAndType memory check, uint256 valueToCheck) internal {

291:     function _handleOperation(LeverageMacroOperation memory operation) internal {

382:     function _doSwaps(SwapOperation[] memory swapData) internal {

398:     function _doSwap(SwapOperation memory swapData) internal {

435:     function _doSwapChecks(SwapCheck[] memory swapChecks) internal {

L244, L291, L382, L398, L435

</details>

 

[G-15] Division/Multiplication by powers of 2 should use bit shifting

<x> * 2 is equivalent to <x> << 1 and <x> / 2 is equivalent to <x> >> 1. In general, <x> * 2 ** n is equivalent to <x> << n. The MUL and DIV opcodes cost 5 gas, whereas SHL and SHR only cost 3 gas.

Note that using left shifting for multiplication does not protect against overflow and as such it should be implemented with caution.

<details> <summary>Instances: 4</summary>
File: packages/contracts/contracts/PriceFeed.sol

800:         return
801:             (_scaledDecimal *
802:                 uint256(_ethBtcAnswer) *
803:                 uint256(_stEthEthAnswer) *
804:                 EbtcMath.DECIMAL_PRECISION) / 10 ** (_decimalDenominator * 2);

L800

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

38:         decProd = (prod_xy + (DECIMAL_PRECISION / 2)) / DECIMAL_PRECISION;

76:                 n = n / 2;

81:                 n = (n - 1) / 2;

L38, L76, L81

</details>

 

[G-16] Initializing variable to default value costs unnecessary gas

<details> <summary>Instances: 7</summary>
File: packages/contracts/contracts/Governor.sol

54:             uint256 j = 0;

82:             uint256 j = 0;

105:             uint256 j = 0;

L54, L82, L105

File: packages/contracts/contracts/SortedCdps.sol

182:         uint _currentIndex = 0;

245:         uint _ownedCount = 0;

246:         uint i = 0;

311:         uint i = 0;

L182, L245, L246, L311

</details>

 

[G-17] Same cast is done multiple times

It's cheaper to do it once, and store the result to a variable.

<details> <summary>Instances: 96</summary>
File: packages/contracts/contracts/ActivePool.sol

274:         collateral.transfer(address(receiver), amount);

283:         collateral.transferFrom(address(receiver), address(this), amountWithFee);

294:         require(
295:             collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),
296:             "ActivePool: Must repay Balance"
297:         );
298:         require(
299:             collateral.sharesOf(address(this)) >= systemCollShares,
300:             "ActivePool: Must repay Share"
301:         );

272:         uint256 oldRate = collateral.getPooledEthByShares(DECIMAL_PRECISION);

302:         require(
303:             collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
304:             "ActivePool: Should keep same collateral share rate"
305:         );

307:         emit FlashLoanSuccess(address(receiver), token, amount, fee);

376:         uint256 balance = IERC20(token).balanceOf(address(this));

381:         IERC20(token).safeTransfer(cachedFeeRecipientAddress, amount);

L274, L283, L294, L302, L307, L376, L381

File: packages/contracts/contracts/BorrowerOperations.sol

500:                 cdpManager.notifyEndGracePeriod(newTCR);

509:             cdpManager.notifyEndGracePeriod(newTCR);

896:                 cdpManager.notifyEndGracePeriod(_vars.newTCR);

906:             cdpManager.notifyEndGracePeriod(_vars.newTCR);

1088:         ebtcToken.mint(address(receiver), amount);

1100:         ebtcToken.transferFrom(address(receiver), feeRecipientAddress, fee + amount);

1105:         emit FlashLoanSuccess(address(receiver), token, amount, fee);

L500, L509, L896, L906, L1088, L1100, L1105

File: packages/contracts/contracts/CdpManager.sol

163:                 address _borrower = sortedCdps.getOwnerAddress(_redeemColFromCdp.cdpId);

220:             emit CdpUpdated(
221:                 _redeemColFromCdp.cdpId,
222:                 ISortedCdps(sortedCdps).getOwnerAddress(_redeemColFromCdp.cdpId),
223:                 msg.sender,
224:                 _oldDebtAndColl.debt,
225:                 _oldDebtAndColl.collShares,
226:                 newDebt,
227:                 newColl,
228:                 Cdps[_redeemColFromCdp.cdpId].stake,
229:                 CdpOperation.redeemCollateral
230:             );

367:             currentBorrower = sortedCdps.getOwnerAddress(_cId);

371:                 currentBorrower = sortedCdps.getOwnerAddress(_cId);

369:             while (currentBorrower != address(0) && getSyncedICR(_cId, totals.price) < MCR) {

388:         while (
389:             currentBorrower != address(0) && totals.remainingDebtToRedeem > 0 && _maxIterations > 0
390:         ) {

370:                 _cId = sortedCdps.getPrev(_cId);

424:                 bytes32 _nextId = sortedCdps.getPrev(_cId);

L163, L220, L367, L371, L388, L424

File: packages/contracts/contracts/LiquidationLibrary.sol

759:                 if (
760:                     !vars.backToNormalMode &&
761:                     (_checkICRAgainstMCR(vars.ICR) || canLiquidateRecoveryMode(vars.ICR, _TCR))
762:                 ) {

790:                 } else if (vars.backToNormalMode && _checkICRAgainstMCR(vars.ICR)) {

764:                     _syncAccounting(vars.cdpId);

791:                     _syncAccounting(vars.cdpId);

L759, L790, L791

File: packages/contracts/contracts/CollSurplusPool.sol

145:         uint256 balance = IERC20(token).balanceOf(address(this));

148:         IERC20(token).safeTransfer(feeRecipientAddress, amount);

L145, L148

File: packages/contracts/contracts/EBTCToken.sol

247:         require(sender != address(0), "EBTCToken: zero sender!");
248:         require(recipient != address(0), "EBTCToken: zero recipient!");

263:         require(account != address(0), "EBTCToken: mint to zero recipient!");

267:         emit Transfer(address(0), account, amount);

271:         require(account != address(0), "EBTCToken: burn from zero account!");

282:         emit Transfer(account, address(0), amount);

286:         require(owner != address(0), "EBTCToken: zero approve owner!");
287:         require(spender != address(0), "EBTCToken: zero approve spender!");

L247, L263, L267, L271, L282, L286

File: packages/contracts/contracts/Governor.sol

49:             if (_canCall) {

60:                 if (_canCall) {

76:         for (uint8 i = 0; i < type(uint8).max; i++) {

84:             for (uint8 i = 0; i < type(uint8).max; i++) {

98:         for (uint8 i = 0; i < type(uint8).max; i++) {

107:             for (uint8 i = 0; i < type(uint8).max; i++) {

100:             if (roleEnabled) {

109:                 if (roleEnabled) {

L49, L60, L76, L84, L98, L107, L109

File: packages/contracts/contracts/PriceFeed.sol

121:                     _changeStatus(Status.usingFallbackChainlinkUntrusted);

126:                 _changeStatus(Status.usingFallbackChainlinkUntrusted);

112:                 if (_fallbackIsBroken(fallbackResponse)) {

133:                 if (_fallbackIsBroken(fallbackResponse)) {

120:                 if (_fallbackIsFrozen(fallbackResponse)) {

141:                 if (_fallbackIsFrozen(fallbackResponse)) {

127:                 return _storeFallbackPrice(fallbackResponse);

146:                 return _storeFallbackPrice(fallbackResponse);

153:                 if (_fallbackIsBroken(fallbackResponse)) {

113:                     _changeStatus(Status.bothOraclesUntrusted);

154:                     _changeStatus(Status.bothOraclesUntrusted);

160:                 if (_fallbackIsFrozen(fallbackResponse)) {
161:                     _changeStatus(Status.usingFallbackChainlinkUntrusted);

175:                 _changeStatus(Status.usingFallbackChainlinkUntrusted);
176:                 return _storeFallbackPrice(fallbackResponse);

180:             if (_fallbackIsBroken(fallbackResponse)) {

134:                     _changeStatus(Status.usingChainlinkFallbackUntrusted);

181:                 _changeStatus(Status.usingChainlinkFallbackUntrusted);

170:                     return _storeChainlinkPrice(chainlinkResponse.answer);

185:             return _storeChainlinkPrice(chainlinkResponse.answer);

199:                 return _storeChainlinkPrice(chainlinkResponse.answer);

202:             if (_fallbackIsBroken(fallbackResponse)) {
203:                 _changeStatus(Status.bothOraclesUntrusted);

211:             if (_fallbackIsFrozen(fallbackResponse)) {

216:             return _storeFallbackPrice(fallbackResponse);

131:             if (_chainlinkIsFrozen(chainlinkResponse)) {

226:                 if (
227:                     !_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) &&
228:                     !_chainlinkIsFrozen(chainlinkResponse)
229:                 ) {
230:                     _changeStatus(Status.usingChainlinkFallbackUntrusted);
231:                     return _storeChainlinkPrice(chainlinkResponse.answer);

198:                 _changeStatus(Status.chainlinkWorking);

246:                 _changeStatus(Status.chainlinkWorking);
247:                 return _storeChainlinkPrice(chainlinkResponse.answer);

258:                 if (_fallbackIsBroken(fallbackResponse)) {
259:                     _changeStatus(Status.bothOraclesUntrusted);

264:                 _changeStatus(Status.usingFallbackChainlinkUntrusted);

266:                 if (_fallbackIsFrozen(fallbackResponse)) {

271:                 return _storeFallbackPrice(fallbackResponse);

274:             if (_chainlinkIsFrozen(chainlinkResponse)) {

276:                 if (_fallbackIsBroken(fallbackResponse)) {
277:                     _changeStatus(Status.usingChainlinkFallbackUntrusted);

282:                 if (_fallbackIsFrozen(fallbackResponse)) {

287:                 return _storeFallbackPrice(fallbackResponse);

291:             if (_fallbackIsBroken(fallbackResponse)) {
292:                 _changeStatus(Status.usingChainlinkFallbackUntrusted);
293:                 return _storeChainlinkPrice(chainlinkResponse.answer);

297:             if (_fallbackIsFrozen(fallbackResponse)) {

304:                 _changeStatus(Status.chainlinkWorking);
305:                 return _storeChainlinkPrice(chainlinkResponse.answer);

309:             _changeStatus(Status.usingFallbackChainlinkUntrusted);
310:             return _storeFallbackPrice(fallbackResponse);

317:                 _changeStatus(Status.bothOraclesUntrusted);

322:             if (_chainlinkIsFrozen(chainlinkResponse)) {

334:                 _changeStatus(Status.chainlinkWorking);
335:                 return _storeChainlinkPrice(chainlinkResponse.answer);

341:                 _changeStatus(Status.bothOraclesUntrusted);

347:             return _storeChainlinkPrice(chainlinkResponse.answer);

376:                     address oldFallbackCaller = address(fallbackCaller);

384:             address oldFallbackCaller = address(fallbackCaller);

615:         try ETH_BTC_CL_FEED.decimals() returns (uint8 decimals) {

623:         try STETH_ETH_CL_FEED.decimals() returns (uint8 decimals) {

703:         try ETH_BTC_CL_FEED.decimals() returns (uint8 decimals) {

711:         try STETH_ETH_CL_FEED.decimals() returns (uint8 decimals) {

L121, L126, L133, L141, L146, L153, L154, L160, L175, L180, L181, L185, L199, L202, L211, L216, L226, L246, L258, L264, L266, L271, L274, L276, L282, L287, L291, L297, L304, L309, L317, L322, L334, L341, L347, L376, L384, L615, L623, L703, L711

File: packages/contracts/contracts/SortedCdps.sol

604:             return data.tail == _prevId && _NICR <= cdpManager.getCachedNominalICR(_prevId);

607:             return
608:                 data.nodes[_prevId].nextId == _nextId &&
609:                 cdpManager.getCachedNominalICR(_prevId) >= _NICR &&
610:                 _NICR >= cdpManager.getCachedNominalICR(_nextId);

601:             return data.head == _nextId && _NICR >= cdpManager.getCachedNominalICR(_nextId);

607:             return
608:                 data.nodes[_prevId].nextId == _nextId &&
609:                 cdpManager.getCachedNominalICR(_prevId) >= _NICR &&
610:                 _NICR >= cdpManager.getCachedNominalICR(_nextId);

L604, L607, L607

File: packages/contracts/contracts/HintHelpers.sol

66:             vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

73:                 vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

68:             while (
69:                 vars.currentCdpUser != address(0) &&
70:                 cdpManager.getSyncedICR(vars.currentCdpId, _price) < MCR
71:             ) {

84:             while (
85:                 vars.currentCdpUser != address(0) &&
86:                 vars.remainingEbtcToRedeem > 0 &&
87:                 _maxIterations-- > 0
88:             ) {

72:                 vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);

116:                 vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
117:                 vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

L66, L73, L84, L116

File: packages/contracts/contracts/LeverageMacroBase.sol

129:             IERC20(operation.tokenToTransferIn).safeTransferFrom(
130:                 msg.sender,
131:                 address(this),
132:                 operation.amountToTransferIn
133:             );

143:             initialCdpIndex = sortedCdps.cdpCountOf(address(this));

148:             IERC3156FlashLender(address(borrowerOperations)).flashLoan(
149:                 IERC3156FlashBorrower(address(this)),
150:                 address(ebtcToken),
151:                 borrowAmount,
152:                 abi.encode(operation)
153:             );

155:             IERC3156FlashLender(address(activePool)).flashLoan(
156:                 IERC3156FlashBorrower(address(this)),
157:                 address(stETH),
158:                 borrowAmount,
159:                 abi.encode(operation)
160:             );

155:             IERC3156FlashLender(address(activePool)).flashLoan(
156:                 IERC3156FlashBorrower(address(this)),
157:                 address(stETH),
158:                 borrowAmount,
159:                 abi.encode(operation)
160:             );

173:             bytes32 cdpId = sortedCdps.cdpOfOwnerByIndex(address(this), initialCdpIndex);

187:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(checkParams.cdpId);

199:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(checkParams.cdpId);

220:         uint256 ebtcBal = ebtcToken.balanceOf(address(this));
221:         uint256 collateralBal = stETH.sharesOf(address(this));

405:         IERC20(swapData.tokenForSwap).safeApprove(
406:             swapData.addressForApprove,
407:             swapData.exactApproveAmount
408:         );

427:         IERC20(swapData.tokenForSwap).safeApprove(swapData.addressForApprove, 0);

L129, L143, L148, L155, L173, L187, L199, L220, L405, L427

File: packages/contracts/contracts/LeverageMacroReference.sol

53:         ebtcToken.approve(address(borrowerOperations), type(uint256).max);
54:         stETH.approve(address(borrowerOperations), type(uint256).max);
54:         stETH.approve(address(borrowerOperations), type(uint256).max);
55:         stETH.approve(address(activePool), type(uint256).max);

L53

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

144:                 success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)

149:                 success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)

L144, L149

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

43:         _authority = Authority(newAuthority);

50:         emit AuthorityUpdated(msg.sender, Authority(newAuthority));

59:         _authority = Authority(newAuthority);

62:         emit AuthorityUpdated(address(this), Authority(newAuthority));

L43, L50, L59, L62

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

114:             enabledFunctionSigsByTarget[target].add(bytes32(functionSig));

121:             enabledFunctionSigsByTarget[target].remove(bytes32(functionSig));

L114, L121

</details>

 

[G-18] Avoid emitting events within loops

Emitting an event costs at least 375 base gas plus 375 gas for each indexed parameter. Instead of emitting one event for each iteration of a loop, emit one event after the loop containing all of the relevant information instead. See here for more information about the gas cost of events.

<details> <summary>Instances: 1</summary>
File: packages/contracts/contracts/SortedCdps.sol

449:         for (uint i = 0; i < _len; ++i) {

451:             emit NodeRemoved(_ids[i]);

L449, L451

</details>

 

[G-19] Multiple consecutively emitted events should be combined

Emitting multiple different events back to back wastes gas. It is more efficient to combine all of the desired information into a single event and emit that instead.

<details> <summary>Instances: 4</summary>
File: packages/contracts/contracts/ActivePool.sol

112:         emit SystemCollSharesUpdated(cachedSystemCollShares);
113:         emit CollSharesTransferred(_account, _shares);

145:         emit SystemCollSharesUpdated(cachedSystemCollShares);
146:         emit CollSharesTransferred(_account, totalShares);

173:         emit SystemCollSharesUpdated(cachedSystemCollShares);
174:         emit FeeRecipientClaimableCollSharesIncreased(cachedFeeRecipientCollShares, _shares);

L112, L145, L173

File: packages/contracts/contracts/Dependencies/Auth.sol

22:         emit OwnershipTransferred(msg.sender, _owner);
23:         emit AuthorityUpdated(msg.sender, _authority);

L22

</details>

 

[G-20] require/revert strings longer than 32 bytes cost extra gas

Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met. If the contract(s) in scope allow using Solidity >=0.8.4, consider using Custom Errors as they are more gas efficient while allowing developers to describe the error in detail using NatSpec.

<details> <summary>Instances: 115</summary>
File: packages/contracts/contracts/ActivePool.sol

137:         require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");

162:         require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");

220:         require(
221:             msg.sender == borrowerOperationsAddress,
222:             "ActivePool: Caller is not BorrowerOperations"
223:         );

228:         require(
229:             msg.sender == borrowerOperationsAddress || msg.sender == cdpManagerAddress,
230:             "ActivePool: Caller is neither BorrowerOperations nor CdpManager"
231:         );

236:         require(msg.sender == cdpManagerAddress, "ActivePool: Caller is not CdpManager");

277:         require(
278:             receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
279:             "ActivePool: IERC3156: Callback failed"
280:         );

302:         require(
303:             collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
304:             "ActivePool: Should keep same collateral share rate"
305:         );

350:         require(
351:             cachedFeeRecipientCollShares >= _shares,
352:             "ActivePool: Insufficient fee recipient coll"
353:         );

374:         require(token != address(collateral), "ActivePool: Cannot Sweep Collateral");

377:         require(amount <= balance, "ActivePool: Attempt to sweep more than balance");

393:         require(
394:             _feeRecipientAddress != address(0),
395:             "ActivePool: Cannot set fee recipient to zero address"
396:         );

407:         require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");

L137, L162, L220, L228, L236, L277, L302, L350, L374, L377, L393, L407

File: packages/contracts/contracts/BorrowerOperations.sol

145:         require(locked == OPEN, "BorrowerOperations: Reentrancy in nonReentrant call");
146:         require(
147:             ReentrancyGuard(address(cdpManager)).locked() == OPEN,
148:             "CdpManager: Reentrancy in nonReentrant call"
149:         );

368:         require(
369:             _stEthBalanceDecrease <= _cdpStEthBalance,
370:             "BorrowerOperations: Cannot withdraw greater stEthBalance than the value in Cdp"
371:         );

463:         require(vars.netStEthBalance > 0, "BorrowerOperations: zero collateral for openCdp()!");

541:         require(
542:             vars.netStEthBalance + LIQUIDATOR_REWARD == _stEthBalance,
543:             "BorrowerOperations: deposited collateral mismatch!"
544:         );

715:         require(_deadline >= block.timestamp, "BorrowerOperations: Position manager permit expired");

734:         require(
735:             recoveredAddress != address(0) && recoveredAddress == _borrower,
736:             "BorrowerOperations: Invalid signature"
737:         );

807:         require(
808:             _stEthBalanceIncrease == 0 || _stEthBalanceDecrease == 0,
809:             "BorrowerOperations: Cannot add and withdraw collateral in same operation"
810:         );

818:         require(
819:             _stEthBalanceIncrease != 0 || _stEthBalanceDecrease != 0 || _debtChange != 0,
820:             "BorrowerOperations: There must be either a collateral change or a debt change"
821:         );

826:         require(status == 1, "BorrowerOperations: Cdp does not exist or is closed");

831:         require(status == 0, "BorrowerOperations: Cdp is active or has been previously closed");

835:         require(_debtChange > 0, "BorrowerOperations: Debt increase requires non-zero debtChange");

839:         require(
840:             !_checkRecoveryModeForTCR(_tcr),
841:             "BorrowerOperations: Operation not permitted during Recovery Mode"
842:         );

846:         require(
847:             _stEthBalanceDecrease == 0,
848:             "BorrowerOperations: Collateral withdrawal not permitted during Recovery Mode"
849:         );

911:         require(
912:             _newICR >= MCR,
913:             "BorrowerOperations: An operation that would result in ICR < MCR is not permitted"
914:         );

918:         require(_newICR >= CCR, "BorrowerOperations: Operation must leave cdp with ICR >= CCR");

922:         require(
923:             _newICR >= _oldICR,
924:             "BorrowerOperations: Cannot decrease your Cdp's ICR in Recovery Mode"
925:         );

929:         require(
930:             _newTCR >= CCR,
931:             "BorrowerOperations: An operation that would result in TCR < CCR is not permitted"
932:         );

936:         require(_debt > 0, "BorrowerOperations: Debt must be non-zero");

940:         require(
941:             _stEthBalance >= MIN_NET_STETH_BALANCE,
942:             "BorrowerOperations: Cdp's net stEth balance must not fall below minimum"
943:         );

947:         require(
948:             _debtRepayment <= _currentDebt,
949:             "BorrowerOperations: Amount repaid must not be larger than the Cdp's debt"
950:         );

957:         require(
958:             ebtcToken.balanceOf(_account) >= _debtRepayment,
959:             "BorrowerOperations: Caller doesnt have enough eBTC to make repayment"
960:         );

970:         require(
971:             _approval != PositionManagerApproval.None,
972:             "BorrowerOperations: Only borrower account or approved position manager can OpenCdp on borrower's behalf"
973:         );

1116:         require(!flashLoansPaused, "BorrowerOperations: Flash Loans Paused");

1141:         require(
1142:             _feeRecipientAddress != address(0),
1143:             "BorrowerOperations: Cannot set feeRecipient to zero address"
1144:         );

1155:         require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");

L145, L368, L463, L541, L715, L734, L807, L818, L826, L831, L835, L839, L846, L911, L918, L922, L929, L936, L940, L947, L957, L970, L1116, L1141, L1155

File: packages/contracts/contracts/CdpManager.sol

431:         require(totals.collSharesDrawn > 0, "CdpManager: Unable to redeem any amount");

502:         require(_toRemoveIds[0] == _start, "CdpManager: batchRemoveSortedCdpIds check start error");
503:         require(
504:             _toRemoveIds[_total - 1] == _end,
505:             "CdpManager: batchRemoveSortedCdpIds check end error"
506:         );

626:         require(newBaseRate > 0, "CdpManager: new baseRate is zero!"); // Base rate is always non-zero after redemption

672:         require(redemptionFee < _ETHDrawn, "CdpManager: Fee would eat up all returned collateral");

743:         require(
744:             callerBalance >= _amount,
745:             "CdpManager: Requested redemption amount must be <= user's EBTC token balance"
746:         );
747:         require(
748:             callerBalance <= _totalSupply,
749:             "CdpManager: redeemer's EBTC balance exceeds total supply!"
750:         );

754:         require(_amount > 0, "CdpManager: Amount must be greater than zero");

758:         require(_TCR >= MCR, "CdpManager: Cannot redeem when TCR < MCR");

762:         require(
763:             _maxFeePercentage >= redemptionFeeFloor && _maxFeePercentage <= DECIMAL_PRECISION,
764:             "Max fee percentage must be between redemption fee floor and 100%"
765:         );

774:         require(
775:             _stakingRewardSplit <= MAX_REWARD_SPLIT,
776:             "CDPManager: new staking reward split exceeds max"
777:         );

789:         require(
790:             _redemptionFeeFloor >= MIN_REDEMPTION_FEE_FLOOR,
791:             "CDPManager: new redemption fee floor is lower than minimum"
792:         );
793:         require(
794:             _redemptionFeeFloor <= DECIMAL_PRECISION,
795:             "CDPManager: new redemption fee floor is higher than maximum"
796:         );

808:         require(
809:             _minuteDecayFactor >= MIN_MINUTE_DECAY_FACTOR,
810:             "CDPManager: new minute decay factor out of range"
811:         );
812:         require(
813:             _minuteDecayFactor <= MAX_MINUTE_DECAY_FACTOR,
814:             "CDPManager: new minute decay factor out of range"
815:         );

L431, L502, L626, L672, L743, L754, L758, L762, L774, L789, L808

File: packages/contracts/contracts/LiquidationLibrary.sol

56:         require(_partialAmount != 0, "LiquidationLibrary: use `liquidate` for 100%");

82:             require(
83:                 _checkICRAgainstLiqThreshold(_ICR, _TCR),
84:                 "LiquidationLibrary: ICR is not below liquidation threshold in current mode"
85:             );

89:             require(
90:                 cachedLastGracePeriodStartTimestamp != UNSET_TIMESTAMP,
91:                 "LiquidationLibrary: Recovery Mode grace period not started"
92:             );
93:             require(
94:                 block.timestamp >
95:                     cachedLastGracePeriodStartTimestamp + recoveryModeGracePeriodDuration,
96:                 "LiquidationLibrary: Recovery mode grace period still in effect"
97:             );

477:             require(
478:                 getCachedICR(_cdpId, _partialState.price) >= _partialState.ICR,
479:                 "LiquidationLibrary: !_newICR>=_ICR"
480:             );

680:         require(
681:             _cdpArray.length != 0,
682:             "LiquidationLibrary: Calldata address array must not be empty"
683:         );

710:         require(totals.totalDebtInSequence > 0, "LiquidationLibrary: nothing to liquidate");

901:         require(
902:             (_partialDebt + _convertDebtDenominationToBtc(MIN_NET_STETH_BALANCE, _price)) <=
903:                 _entireDebt,
904:             "LiquidationLibrary: Partial debt liquidated must be less than total debt"
905:         );

909:         require(
910:             _entireColl >= MIN_NET_STETH_BALANCE,
911:             "LiquidationLibrary: Coll remaining in partially liquidated CDP must be >= minimum"
912:         );

L56, L82, L89, L477, L680, L710, L901, L909

File: packages/contracts/contracts/CdpManagerStorage.sol

112:         require(
113:             _gracePeriod >= MINIMUM_GRACE_PERIOD,
114:             "CdpManager: Grace period below minimum duration"
115:         );

242:         require(locked == OPEN, "CdpManager: Reentrancy in nonReentrant call");
243:         require(
244:             ReentrancyGuard(borrowerOperationsAddress).locked() == OPEN,
245:             "BorrowerOperations: Reentrancy in nonReentrant call"
246:         );

261:         require(
262:             closedStatus != Status.nonExistent && closedStatus != Status.active,
263:             "CdpManagerStorage: close non-exist or non-active CDP!"
264:         );

453:             require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");

466:         require(
467:             cdpStatus != Status.nonExistent && cdpStatus != Status.active,
468:             "CdpManagerStorage: remove non-exist or non-active CDP!"
469:         );

475:         require(index <= idxLast, "CdpManagerStorage: CDP indexing overflow!");

556:         require(_newIndex > _prevIndex, "CDPManager: only take fee with bigger new index");

649:         require(Cdps[_cdpId].status == Status.active, "CdpManager: Cdp does not exist or is closed");

653:         require(
654:             CdpOwnersArrayLength > 1 && sortedCdps.getSize() > 1,
655:             "CdpManager: Only one cdp in the system"
656:         );

660:         require(
661:             msg.sender == borrowerOperationsAddress,
662:             "CdpManager: Caller is not the BorrowerOperations contract"
663:         );

L112, L242, L261, L453, L466, L475, L556, L649, L653, L660

File: packages/contracts/contracts/CollSurplusPool.sol

92:         require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");

113:         require(
114:             msg.sender == borrowerOperationsAddress,
115:             "CollSurplusPool: Caller is not Borrower Operations"
116:         );

120:         require(msg.sender == cdpManagerAddress, "CollSurplusPool: Caller is not CdpManager");

124:         require(msg.sender == activePoolAddress, "CollSurplusPool: Caller is not Active Pool");

143:         require(token != address(collateral), "CollSurplusPool: Cannot Sweep Collateral");

146:         require(amount <= balance, "CollSurplusPool: Attempt to sweep more than balance");

L92, L113, L120, L124, L143, L146

File: packages/contracts/contracts/EBTCToken.sol

144:             require(cachedAllowance >= amount, "ERC20: transfer amount exceeds allowance");

165:         require(cachedAllowances >= subtractedValue, "ERC20: decreased allowance below zero");

251:         require(cachedSenderBalances >= amount, "ERC20: transfer amount exceeds balance");

263:         require(account != address(0), "EBTCToken: mint to zero recipient!");

271:         require(account != address(0), "EBTCToken: burn from zero account!");

274:         require(cachedBalance >= amount, "ERC20: burn amount exceeds balance");

296:         require(
297:             _recipient != address(0) && _recipient != address(this),
298:             "EBTC: Cannot transfer tokens directly to the EBTC token contract or the zero address"
299:         );
300:         require(
301:             _recipient != cdpManagerAddress && _recipient != borrowerOperationsAddress,
302:             "EBTC: Cannot transfer tokens directly to the CdpManager or BorrowerOps"
303:         );

307:         require(
308:             msg.sender == borrowerOperationsAddress,
309:             "EBTCToken: Caller is not BorrowerOperations"
310:         );

315:         require(
316:             msg.sender == borrowerOperationsAddress ||
317:                 msg.sender == cdpManagerAddress ||
318:                 isAuthorized(msg.sender, msg.sig),
319:             "EBTC: Caller is neither BorrowerOperations nor CdpManager nor authorized"
320:         );

L144, L165, L251, L263, L271, L274, L296, L307, L315

File: packages/contracts/contracts/PriceFeed.sol

79:         require(
80:             !_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) &&
81:                 !_chainlinkIsFrozen(chainlinkResponse),
82:             "PriceFeed: Chainlink must be working and current"
83:         );

L79

File: packages/contracts/contracts/SortedCdps.sol

352:         require(cdpManager.getCdpStatus(_id) == 0, "SortedCdps: new id is NOT nonExistent!");

367:         require(!contains(_id), "SortedCdps: List already contains the node");

371:         require(_NICR > 0, "SortedCdps: NICR must be positive");

422:         require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");

427:         require(
428:             _firstPrev != dummyId || _lastNext != dummyId,
429:             "SortedCdps: batchRemove() leave ZERO node left!"
430:         );

433:             require(contains(_ids[i]), "SortedCdps: List does not contain the id");

458:         require(contains(_id), "SortedCdps: List does not contain the id");

506:         require(contains(_id), "SortedCdps: List does not contain the id");

508:         require(_newNICR > 0, "SortedCdps: NICR must be positive");

715:         require(msg.sender == address(cdpManager), "SortedCdps: Caller is not the CdpManager");

720:         require(
721:             msg.sender == borrowerOperationsAddress || msg.sender == address(cdpManager),
722:             "SortedCdps: Caller is neither BO nor CdpM"
723:         );

L352, L367, L371, L422, L427, L433, L458, L506, L508, L715, L720

File: packages/contracts/contracts/LeverageMacroBase.sol

179:             require(
180:                 cdpInfo.status == checkParams.expectedStatus,
181:                 "!LeverageMacroReference: openCDP status check"
182:             );

191:             require(
192:                 cdpInfo.status == checkParams.expectedStatus,
193:                 "!LeverageMacroReference: adjustCDP status check"
194:             );

201:             require(
202:                 cdpInfo.status == checkParams.expectedStatus,
203:                 "!LeverageMacroReference: closeCDP status check"
204:             );

249:             require(check.value >= valueToCheck, "!LeverageMacroReference: gte post check");

251:             require(check.value <= valueToCheck, "!LeverageMacroReference: let post check");

253:             require(check.value == valueToCheck, "!LeverageMacroReference: equal post check");

352:         require(initiator == address(this), "LeverageMacroReference: wrong initiator for flashloan");

356:             require(
357:                 msg.sender == address(borrowerOperations),
358:                 "LeverageMacroReference: wrong lender for eBTC flashloan"
359:             );

362:             require(
363:                 msg.sender == address(activePool),
364:                 "LeverageMacroReference: wrong lender for stETH flashloan"
365:             );

440:                 require(
441:                     IERC20(swapChecks[i].tokenToCheck).balanceOf(address(this)) >
442:                         swapChecks[i].expectedMinOut,
443:                     "LeverageMacroReference: swap check failure!"
444:                 );

L179, L191, L201, L249, L251, L253, L352, L356, L362, L440

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

57:         require(!_authorityInitialized, "Auth: authority already initialized");

L57

File: packages/contracts/contracts/Dependencies/ReentrancyGuard.sol

14:         require(locked == OPEN, "ReentrancyGuard: Reentrancy in nonReentrant call");

L14

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

90:         require(
91:             capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
92:             "RolesAuthority: Capability Burned"
93:         );

134:         require(
135:             capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
136:             "RolesAuthority: Capability Burned"
137:         );

L90, L134

</details>

 

[G-21] Cache length outside of for loop

Currently, the solidity compiler will always read the length of the array during each iteration.

That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).

<details> <summary>Instances: 4</summary>
File: packages/contracts/contracts/Governor.sol

57:             for (uint256 i = 0; i < _usrs.length; i++) {

122:         for (uint8 i = 0; i < roleIds.length; i++) {

137:             for (uint256 i = 0; i < _sigs.length; ++i) {

46:         for (uint256 i = 0; i < users.length(); i++) {

L57, L122, L137, L46

</details>

 

[G-22] Using >= is cheaper than >

The compiler uses opcodes GT and ISZERO for solidity code that uses >, but only requires LT for >=, which saves 3 gas. It should be converted to the <=/>= equivalent when comparing against integer literals.

<details> <summary>Instances: 56</summary>
File: packages/contracts/contracts/ActivePool.sol

267:         require(amount > 0, "ActivePool: 0 Amount");

L267

File: packages/contracts/contracts/BorrowerOperations.sol

393:         if (!_isDebtIncrease && _debtChange > 0) {

463:         require(vars.netStEthBalance > 0, "BorrowerOperations: zero collateral for openCdp()!");

835:         require(_debtChange > 0, "BorrowerOperations: Debt increase requires non-zero debtChange");

936:         require(_debt > 0, "BorrowerOperations: Debt must be non-zero");

1083:         require(amount > 0, "BorrowerOperations: 0 Amount");

L393, L463, L835, L936, L1083

File: packages/contracts/contracts/CdpManager.sol

388:         while (
389:             currentBorrower != address(0) && totals.remainingDebtToRedeem > 0 && _maxIterations > 0
390:         ) {

431:         require(totals.collSharesDrawn > 0, "CdpManager: Unable to redeem any amount");

436:         } else if (_numCdpsFullyRedeemed > 1) {

497:         while (_cnt > 0 && _id != bytes32(0)) {

626:         require(newBaseRate > 0, "CdpManager: new baseRate is zero!"); // Base rate is always non-zero after redemption

754:         require(_amount > 0, "CdpManager: Amount must be greater than zero");

L388, L431, L436, L497, L626, L754

File: packages/contracts/contracts/LiquidationLibrary.sol

195:             if (_outputState.totalSurplusCollShares > 0) {

261:             if (_collSurplus > 0) {

266:             if (_debtToRedistribute > 0) {

336:             if (_collSurplus > 0) {

342:             if (_debtToRedistribute > 0) {

525:         if (totalDebtToRedistribute > 0) {

710:         require(totals.totalDebtInSequence > 0, "LiquidationLibrary: nothing to liquidate");

713:         if (totals.totalCollSurplus > 0) {

L195, L261, L266, L336, L342, L525, L710, L713

File: packages/contracts/contracts/CdpManagerStorage.sol

315:         if (_debtIndexDiff > 0) {

362:         if (_feeSplitDistributed > 0 || _debtIndexDelta > 0) {

369:             if (_feeSplitDistributed > 0) {

380:             if (_debtIndexDelta > 0) {

453:             require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");

512:         if (_newIndex > _oldIndex && totalStakes > 0) {

653:         require(
654:             CdpOwnersArrayLength > 1 && sortedCdps.getSize() > 1,
655:             "CdpManager: Only one cdp in the system"
656:         );

765:         if (_newIndex > _oldIndex && totalStakes > 0) {

796:         if (_cdpPerUnitIdx != _systemStEthFeePerUnitIndex && _cdpPerUnitIdx > 0) {

829:         if (pendingDebtRedistributed > 0) {

879:         if (_feeTaken > 0) {

L315, L362, L369, L380, L453, L512, L653, L765, L796, L829, L879

File: packages/contracts/contracts/CollSurplusPool.sol

92:         require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");

L92

File: packages/contracts/contracts/Governor.sol

53:         if (count > 0) {

81:         if (count > 0) {

104:         if (count > 0) {

135:         if (_sigs.length > 0) {

L53, L81, L104, L135

File: packages/contracts/contracts/PriceFeed.sol

457:         uint256 percentDeviation = maxPrice > 0
458:             ? ((maxPrice - minPrice) * EbtcMath.DECIMAL_PRECISION) / maxPrice
459:             : 0;

488:         return
489:             _fallbackResponse.timestamp > 0 &&
490:             _responseTimeout(_fallbackResponse.timestamp, fallbackCaller.fallbackTimeout());

L457, L488

File: packages/contracts/contracts/SortedCdps.sol

202:             if (maxNodes > 0 && i >= maxNodes) {

259:             if (maxNodes > 0 && i >= maxNodes) {

274:         if (_ownedCount > 0) {

330:             if (maxNodes > 0 && i >= maxNodes) {

371:         require(_NICR > 0, "SortedCdps: NICR must be positive");

422:         require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");

460:         if (data.size > 1) {

508:         require(_newNICR > 0, "SortedCdps: NICR must be positive");

L202, L259, L274, L330, L371, L422, L460, L508

File: packages/contracts/contracts/HintHelpers.sol

84:             while (
85:                 vars.currentCdpUser != address(0) &&
86:                 vars.remainingEbtcToRedeem > 0 &&
87:                 _maxIterations-- > 0
88:             ) {

L84

File: packages/contracts/contracts/LeverageMacroBase.sol

128:         if (operation.amountToTransferIn > 0) {

223:         if (ebtcBal > 0) {

227:         if (collateralBal > 0) {

293:         if (beforeSwapsLength > 0) {

307:         if (afterSwapsLength > 0) {

L128, L223, L227, L293, L307

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

60:         if (_minutes > 525600000) {

73:         while (n > 1) {

93:         if (_debt > 0) {

109:         if (_debt > 0) {

L60, L73, L93, L109

</details>

 

[G-23] Inline modifiers that are only used once, to save gas

<details> <summary>Instances: 10</summary>
File: packages/contracts/contracts/ActivePool.sol

371:     function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {

L371

File: packages/contracts/contracts/CdpManager.sol

320:     function redeemCollateral(
321:         uint256 _debt,
322:         bytes32 _firstRedemptionHint,
323:         bytes32 _upperPartialRedemptionHint,
324:         bytes32 _lowerPartialRedemptionHint,
325:         uint256 _partialRedemptionHintNICR,
326:         uint256 _maxIterations,
327:         uint256 _maxFeePercentage
328:     ) external override nonReentrantSelfAndBOps {

L320

File: packages/contracts/contracts/CdpManagerStorage.sol

111:     function setGracePeriod(uint128 _gracePeriod) external requiresAuth {

L111

File: packages/contracts/contracts/CollSurplusPool.sol

142:     function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {
142:     function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {

L142

File: packages/contracts/contracts/EBTCToken.sol

225:     function nonces(address owner) external view override(IERC2612, PermitNonce) returns (uint256) {
225:     function nonces(address owner) external view override(IERC2612, PermitNonce) returns (uint256) {

L225

File: packages/contracts/contracts/Governor.sol

154:     function setRoleName(uint8 role, string memory roleName) external requiresAuth {

L154

File: packages/contracts/contracts/PriceFeed.sol

358:     function setFallbackCaller(address _fallbackCaller) external requiresAuth {

L358

File: packages/contracts/contracts/Dependencies/Auth.sol

52:     function transferOwnership(address newOwner) public virtual requiresAuth {

L52

</details>

 

[G-24] Inline internal functions that are only called once

Saves 20-40 gas per instance. See https://blog.soliditylang.org/2021/03/02/saving-gas-with-simple-inliner/ for more details.

<details> <summary>Instances: 78</summary>
File: packages/contracts/contracts/ActivePool.sol

158:         _requireCallerIsCdpManager();

243:         _requireCallerIsBorrowerOperations();

L158, L243

File: packages/contracts/contracts/BorrowerOperations.sol

350:             _requireNonZeroDebtChange(_debtChange);

352:         _requireSingularCollChange(_stEthBalanceIncrease, _stEthBalanceDecrease);

353:         _requireNonZeroAdjustment(_stEthBalanceIncrease, _stEthBalanceDecrease, _debtChange);

356:         (vars.collSharesChange, vars.isCollIncrease) = _getCollSharesChangeFromStEthChange(

373:         vars.newICR = _getNewICRFromCdpChange(

385:         _requireValidAdjustmentInCurrentMode(

394:             _requireValidDebtRepayment(vars.debt, vars.netDebtChange);

421:             uint256 newNICR = _getNewNominalICRFromCdpChange(vars, _isDebtIncrease);

435:             _processTokenMovesFromAdjustment(_varMvTokens);

517:         _requireCdpIsNonExistent(_cdpId);

562:         _requireNotInRecoveryMode(_getCachedTCR(price));

882:             _requireNoStEthBalanceDecrease(_stEthBalanceDecrease);

885:                 _requireNoDecreaseOfICR(_vars.newICR, _vars.oldICR);

L350, L352, L353, L356, L373, L385, L394, L421, L435, L517, L562, L882, L885

File: packages/contracts/contracts/CdpManager.sol

171:                 _closeCdpByRedemption(

334:         _requireValidMaxFeePercentage(_maxFeePercentage);

350:         _requireTCRisNotBelowMCR(totals.price, totals.tcrAtStart);

351:         _requireAmountGreaterThanZero(_debt);

353:         _requireEbtcBalanceCoversRedemptionAndWithinSupply(

363:         if (_isValidFirstRedemptionHint(_firstRedemptionHint, totals.price)) {

403:                 SingleRedemptionValues memory singleRedemption = _redeemCollateralFromCdp(

437:             bytes32[] memory _toRemoveIds = _getCdpIdsToRemove(

447:         _updateBaseRateFromRedemption(

454:         totals.feeCollShares = _getRedemptionFee(totals.collSharesDrawn);

732:         return _checkPotentialRecoveryMode(_systemCollShares, _systemDebt, _price);

922:         uint256 index = _addCdpIdToArray(_cdpId);

956:         _setCdpCollShares(_cdpId, _newColl);

957:         _setCdpDebt(_cdpId, _newDebt);

L171, L334, L350, L351, L353, L363, L403, L437, L447, L454, L732, L922, L956, L957

File: packages/contracts/contracts/LiquidationLibrary.sol

130:         _liquidateIndividualCdpSetupCDP(_liqState, _rs);

405:         _requirePartialLiqDebtSize(_partialDebt, _debtAndColl.debt, _partialState.price);

422:         _requirePartialLiqCollSize(collateral.getPooledEthByShares(newColl));

425:         _partiallyReduceCdpDebt(_cdpId, _partialDebt, _partialColl);

429:             _reInsertPartialLiquidation(

526:             _redistributeDebt(totalDebtToRedistribute);

699:             totals = _getTotalFromBatchLiquidate_RecoveryMode(

707:             totals = _getTotalsFromBatchLiquidate_NormalMode(vars.price, _TCR, _cdpArray);

765:                     _getLiquidationValuesRecoveryMode(

L130, L405, L422, L425, L429, L526, L699, L707, L765

File: packages/contracts/contracts/CdpManagerStorage.sol

256:         _closeCdpWithoutRemovingSortedCdps(_cdpId, closedStatus);

267:         _requireMoreThanOneCdpInSystem(cdpIdsArrayLength);

269:         _removeStake(_cdpId);

279:         _removeCdp(_cdpId, cdpIdsArrayLength);

370:                 _applyAccumulatedFeeSplit(

381:                 _updateRedistributedDebtIndex(_cdpId);

420:         (uint256 newStake, uint256 oldStake) = _updateStakeForCdp(_cdpId);

433:         uint256 newStake = _computeNewStake(_cdp.coll);

511:         _syncStEthIndex(_oldIndex, _newIndex);

518:             _takeSplitAndUpdateFeePerUnit(_feeTaken, _newFeePerUnit, _perUnitError);

519:             _updateSystemSnapshotsExcludeCollRemainder(0);

529:         _syncGracePeriod(); // Synch Grace Period

729:         return _hasRedistributedDebt(_cdpId);

891:         return _checkICRAgainstTCR(icr, tcr) && _recoveryModeGracePeriodPassed();

L256, L267, L269, L279, L370, L381, L420, L433, L511, L518, L519, L529, L729, L891

File: packages/contracts/contracts/CollSurplusPool.sol

78:         _requireCallerIsCdpManager();

90:         _requireCallerIsBorrowerOperations();

131:         _requireCallerIsActivePool();

L78, L90, L131

File: packages/contracts/contracts/EBTCToken.sol

87:         _mint(_account, _amount);

L87

File: packages/contracts/contracts/PriceFeed.sol

105:         FallbackResponse memory fallbackResponse = _getCurrentFallbackResponse();

404:         return _badChainlinkResponse(_currentResponse) || _badChainlinkResponse(_prevResponse);

L105, L404

File: packages/contracts/contracts/SortedCdps.sol

588:         return _validInsertPosition(_NICR, _prevId, _nextId);

701:             return _ascendList(_NICR, nextId);

L588, L701

File: packages/contracts/contracts/HintHelpers.sol

98:                     ) = _calculateCdpStateAfterPartialRedemption(vars, currentCdpDebt, _price);

L98

File: packages/contracts/contracts/LeverageMacroBase.sol

299:             _openCdpCallback(operation.OperationData);

301:             _closeCdpCallback(operation.OperationData);

303:             _adjustCdpCallback(operation.OperationData);

386:             _doSwap(swapData[i]);

401:         _ensureNotSystem(swapData.addressForSwap);

414:         (bool success, ) = excessivelySafeCall(

430:         _doSwapChecks(swapData.swapChecks);

L299, L301, L303, L386, L401, L414, L430

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

115:             _executeOne(ops[i]);

165:         _fallback();

L115, L165

File: packages/contracts/contracts/Dependencies/Auth.sol

27:         require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");

L27

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

17:         require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");

L17

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

80:         (TCR, , ) = _getTCRWithSystemDebtAndCollShares(_price);

87:         uint256 systemDebt = _getSystemDebt();

96:         return _checkRecoveryModeForTCR(_getCachedTCR(_price));

129:             _checkICRAgainstMCR(_icr) ||

130:             (_checkICRAgainstTCR(_icr, _tcr) && _checkRecoveryModeForTCR(_tcr));

L80, L87, L96, L129, L130

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

33:      * Used only inside the exponentiation, _decPow().

L33

</details>

 

[G-25] Expressions for constant values such as a call to keccak256 should use immutable rather than constant

When left as constant, the value is re-calculated each time it is used instead of being converted to a constant at compile time. This costs an extra ~100 gas for each access.

Using immutable only incurs the gas costs for the computation at deploy time.

See here for a detailed description of the issue.

<details> <summary>Instances: 8</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

21:     uint128 public constant UNSET_TIMESTAMP = type(uint128).max;

141:     uint256 public constant MIN_REDEMPTION_FEE_FLOOR = (DECIMAL_PRECISION * 5) / 1000; // 0.5%

L21, L141

File: packages/contracts/contracts/LeverageMacroBase.sol

37:     bytes32 constant FLASH_LOAN_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

37:     bytes32 constant FLASH_LOAN_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

L37, L37

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

39:     bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

39:     bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

L39, L39

File: packages/contracts/contracts/Dependencies/ERC3156FlashLender.sol

11:     bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

11:     bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

L11, L11

</details>

 

[G-26] Avoid contract existence checks by using low level calls

Prior to 0.8.10 the compiler inserted extra code, including EXTCODESIZE (100 gas), to check for contract existence for external function calls. In more recent solidity versions, the compiler will not insert these checks if the external call has a return value. Similar behavior can be achieved in earlier versions by using low-level calls, since low level calls never check for contract existence.

<details> <summary>Instances: 125</summary>
File: packages/contracts/contracts/ActivePool.sol

60:         address _authorityAddress = address(AuthNoOwner(cdpManagerAddress).authority());

272:         uint256 oldRate = collateral.getPooledEthByShares(DECIMAL_PRECISION);

294:         require(
295:             collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),
296:             "ActivePool: Must repay Balance"
297:         );

337:         return collateral.balanceOf(address(this));

376:         uint256 balance = IERC20(token).balanceOf(address(this));

L60, L272, L294, L337, L376

File: packages/contracts/contracts/BorrowerOperations.sol

123:         address _authorityAddress = address(AuthNoOwner(_cdpManagerAddress).authority());

338:         address _borrower = sortedCdps.getOwnerAddress(_cdpId);

347:         vars.price = priceFeed.fetchPrice();

363:         vars.debt = cdpManager.getCdpDebt(_cdpId);
364:         vars.collShares = cdpManager.getCdpCollShares(_cdpId);

367:         uint256 _cdpStEthBalance = collateral.getPooledEthByShares(vars.collShares);

372:         vars.oldICR = EbtcMath._computeCR(_cdpStEthBalance, vars.debt, vars.price);

459:         vars.price = priceFeed.fetchPrice();

465:         uint256 _netCollAsShares = collateral.getSharesByPooledEth(vars.netStEthBalance);
466:         uint256 _liquidatorRewardShares = collateral.getSharesByPooledEth(LIQUIDATOR_REWARD);

469:         vars.ICR = EbtcMath._computeCR(vars.netStEthBalance, vars.debt, vars.price);

472:         vars.NICR = EbtcMath._computeNominalCR(_netCollAsShares, vars.debt);

513:         bytes32 _cdpId = sortedCdps.insert(_borrower, vars.NICR, _upperHint, _lowerHint);

554:         address _borrower = sortedCdps.getOwnerAddress(_cdpId);

561:         uint256 price = priceFeed.fetchPrice();

564:         uint256 collShares = cdpManager.getCdpCollShares(_cdpId);
565:         uint256 debt = cdpManager.getCdpDebt(_cdpId);
566:         uint256 liquidatorRewardShares = cdpManager.getCdpLiquidatorRewardShares(_cdpId);

570:         uint256 newTCR = _getNewTCRFromCdpChange(
571:             collateral.getPooledEthByShares(collShares),
572:             false,
573:             debt,
574:             false,
575:             price
576:         );

749:             collSharesChange = collateral.getSharesByPooledEth(_collReceived);

752:             collSharesChange = collateral.getSharesByPooledEth(_requestedCollWithdrawal);

825:         uint256 status = _cdpManager.getCdpStatus(_cdpId);

830:         uint status = cdpManager.getCdpStatus(_cdpId);

873:         _vars.newTCR = _getNewTCRFromCdpChange(
874:             collateral.getPooledEthByShares(_vars.collSharesChange),
875:             _vars.isCollIncrease,
876:             _vars.netDebtChange,
877:             _isDebtIncrease,
878:             _vars.price
879:         );

999:         uint256 newNICR = EbtcMath._computeNominalCR(newCollShares, newDebt);

1022:         uint256 newICR = EbtcMath._computeCR(
1023:             collateral.getPooledEthByShares(newCollShares),
1024:             newDebt,
1025:             _price
1026:         );

1057:         uint256 systemStEthBalance = collateral.getPooledEthByShares(_systemCollShares);

1065:         uint256 newTCR = EbtcMath._computeCR(systemStEthBalance, systemDebt, _price);

L123, L338, L347, L363, L367, L372, L459, L465, L469, L472, L513, L554, L561, L564, L570, L749, L752, L825, L830, L873, L999, L1022, L1057, L1065

File: packages/contracts/contracts/CdpManager.sol

140:         singleRedemption.debtToRedeem = EbtcMath._min(
141:             _redeemColFromCdp.maxEBTCamount,
142:             Cdps[_redeemColFromCdp.cdpId].debt /// @audit Redeem everything
143:         );

145:         singleRedemption.collSharesDrawn = collateral.getSharesByPooledEth(
146:             (singleRedemption.debtToRedeem * DECIMAL_PRECISION) / _redeemColFromCdp.price
147:         );

163:                 address _borrower = sortedCdps.getOwnerAddress(_redeemColFromCdp.cdpId);

193:             uint256 newNICR = EbtcMath._computeNominalCR(newColl, newDebt);

284:         bytes32 nextCdp = sortedCdps.getNext(_firstRedemptionHint);

338:         totals.price = priceFeed.fetchPrice();

364:             currentBorrower = sortedCdps.getOwnerAddress(_firstRedemptionHint);

366:             _cId = sortedCdps.getLast();
367:             currentBorrower = sortedCdps.getOwnerAddress(_cId);

370:                 _cId = sortedCdps.getPrev(_cId);
371:                 currentBorrower = sortedCdps.getOwnerAddress(_cId);

424:                 bytes32 _nextId = sortedCdps.getPrev(_cId);
425:                 address nextUserToCheck = sortedCdps.getOwnerAddress(_nextId);

500:             _id = sortedCdps.getNext(_id);

621:         uint256 redeemedEBTCFraction = (collateral.getPooledEthByShares(_ETHDrawn) * _price) /
622:             _totalEBTCSupply;

625:         newBaseRate = EbtcMath._min(newBaseRate, DECIMAL_PRECISION); // cap baseRate at a maximum of 100%

648:         return
649:             EbtcMath._min(
650:                 redemptionFeeFloor + _baseRate,
651:                 DECIMAL_PRECISION // cap at a maximum of 100%
652:             );

704:         uint256 decayFactor = EbtcMath._decPow(minuteDecayFactor, minutesPassed);

742:         uint256 callerBalance = ebtcToken.balanceOf(_redeemer);

L140, L145, L163, L193, L284, L338, L364, L366, L370, L424, L500, L621, L625, L648, L704, L742

File: packages/contracts/contracts/LiquidationLibrary.sol

71:         uint256 _price = priceFeed.fetchPrice();

235:         address _borrower = sortedCdps.getOwnerAddress(_liqState.cdpId);

279:         uint _cappedColl = collateral.getPooledEthByShares(_cappedColPortion + _liquidatorReward);

309:         address _borrower = sortedCdps.getOwnerAddress(_recoveryState.cdpId);

366:         uint _cappedColl = collateral.getPooledEthByShares(_cappedColPortion + _liquidatorReward);

436:             uint _cappedColl = collateral.getPooledEthByShares(_partialColl);

561:         toLiquidator = collateral.getSharesByPooledEth(_incentiveColl);

582:             toLiquidator = collateral.getSharesByPooledEth(_incentiveColl);

585:             _incentiveColl = collateral.getPooledEthByShares(_totalColToSend);

691:         vars.price = priceFeed.fetchPrice();

L71, L235, L279, L309, L366, L436, L561, L582, L585, L691

File: packages/contracts/contracts/CdpManagerStorage.sol

74:         uint256 price = priceFeed.fetchPrice();

93:         uint256 newTCR = EbtcMath._computeCR(
94:             collateral.getPooledEthByShares(systemCollShares),
95:             systemDebt,
96:             price
97:         );

297:         uint256 _totalCollateralSnapshot = activePool.getSystemCollShares() - _collRemainder;

494:         uint256 _totalColl = collateral.getPooledEthByShares(_systemCollShares);
495:         return EbtcMath._computeCR(_totalColl, _systemDebt, _price);

564:         uint256 _feeTaken = collateral.getSharesByPooledEth(_deltaFeeSplit) / DECIMAL_PRECISION;

676:         uint256 NICR = EbtcMath._computeNominalCR(currentCollShares, currentEBTCDebt);

693:         uint256 NICR = EbtcMath._computeNominalCR(_newColl, _newDebt);

712:         uint256 _underlyingCollateral = collateral.getPooledEthByShares(currentCollShare);
713:         return EbtcMath._computeCR(_underlyingCollateral, currentDebt, _price);

878:         uint256 _systemCollShare = activePool.getSystemCollShares();

882:         uint256 _systemDebt = activePool.getSystemDebt();

L74, L93, L297, L494, L564, L676, L693, L712, L878, L882

File: packages/contracts/contracts/CollSurplusPool.sol

52:         feeRecipientAddress = IActivePool(activePoolAddress).feeRecipientAddress();

54:         address _authorityAddress = address(AuthNoOwner(cdpManagerAddress).authority());

145:         uint256 balance = IERC20(token).balanceOf(address(this));

L52, L54, L145

File: packages/contracts/contracts/Governor.sol

47:             address user = users.at(i);

56:             address[] memory _usrs = users.values();

134:         bytes32[] memory _sigs = enabledFunctionSigsByTarget[_target].values();

L47, L56, L134

File: packages/contracts/contracts/PriceFeed.sol

449:         uint256 minPrice = EbtcMath._min(_currentResponse.answer, _prevResponse.answer);
450:         uint256 maxPrice = EbtcMath._max(_currentResponse.answer, _prevResponse.answer);

531:         uint256 minPrice = EbtcMath._min(_fallbackResponse.answer, _chainlinkResponse.answer);

533:         uint256 maxPrice = EbtcMath._max(_fallbackResponse.answer, _chainlinkResponse.answer);

L449, L531, L533

File: packages/contracts/contracts/SortedCdps.sol

601:             return data.head == _nextId && _NICR >= cdpManager.getCachedNominalICR(_nextId);

604:             return data.tail == _prevId && _NICR <= cdpManager.getCachedNominalICR(_prevId);

607:             return
608:                 data.nodes[_prevId].nextId == _nextId &&
609:                 cdpManager.getCachedNominalICR(_prevId) >= _NICR &&
610:                 _NICR >= cdpManager.getCachedNominalICR(_nextId);

621:         if (data.head == _startId && _NICR >= cdpManager.getCachedNominalICR(_startId)) {

644:         if (data.tail == _startId && _NICR <= cdpManager.getCachedNominalICR(_startId)) {

L601, L604, L607, L621, L644

File: packages/contracts/contracts/HintHelpers.sol

65:             vars.currentCdpId = sortedCdps.getLast();
66:             vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

72:                 vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
73:                 vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

90:                 uint256 currentCdpDebt = cdpManager.getSyncedCdpDebt(vars.currentCdpId);

116:                 vars.currentCdpId = sortedCdps.getPrev(vars.currentCdpId);
117:                 vars.currentCdpUser = sortedCdps.getOwnerAddress(vars.currentCdpId);

139:         uint256 maxRedeemableEBTC = EbtcMath._min(vars.remainingEbtcToRedeem, currentCdpDebt);

141:         uint256 newCollShare = cdpManager.getSyncedCdpCollShares(vars.currentCdpId);

144:         uint256 collShareToReceive = collateral.getSharesByPooledEth(
145:             (maxRedeemableEBTC * DECIMAL_PRECISION) / _price
146:         );

169:         uint256 arrayLength = cdpManager.getActiveCdpsCount();

172:             return (sortedCdps.nonExistId(), 0, _inputRandomSeed);

175:         hint = sortedCdps.getLast();
176:         diff = EbtcMath._getAbsoluteDifference(_CR, cdpManager.getSyncedNominalICR(hint));

185:             bytes32 _cId = cdpManager.getIdFromCdpIdsArray(arrayIndex);
186:             uint256 currentNICR = cdpManager.getSyncedNominalICR(_cId);

189:             uint256 currentDiff = EbtcMath._getAbsoluteDifference(currentNICR, _CR);

204:         return EbtcMath._computeNominalCR(_coll, _debt);

217:         return EbtcMath._computeCR(_coll, _debt, _price);

L65, L72, L90, L116, L139, L141, L144, L169, L172, L175, L185, L189, L204, L217

File: packages/contracts/contracts/LeverageMacroBase.sol

143:             initialCdpIndex = sortedCdps.cdpCountOf(address(this));

173:             bytes32 cdpId = sortedCdps.cdpOfOwnerByIndex(address(this), initialCdpIndex);

176:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(cdpId);

187:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(checkParams.cdpId);

199:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(checkParams.cdpId);

220:         uint256 ebtcBal = ebtcToken.balanceOf(address(this));
221:         uint256 collateralBal = stETH.sharesOf(address(this));

339:         LeverageMacroOperation memory leverageMacroData = abi.decode(data, (LeverageMacroOperation));

461:         OpenCdpOperation memory flData = abi.decode(data, (OpenCdpOperation));

465:         bytes32 _cdpId = borrowerOperations.openCdp(
466:             flData.eBTCToMint,
467:             flData._upperHint,
468:             flData._lowerHint,
469:             flData.stETHToDeposit
470:         );

475:         CloseCdpOperation memory flData = abi.decode(data, (CloseCdpOperation));

483:         AdjustCdpOperation memory flData = abi.decode(data, (AdjustCdpOperation));

L143, L173, L176, L187, L199, L220, L339, L461, L465, L475, L483

File: packages/contracts/contracts/LeverageMacroDelegateTarget.sol

64:         address _owner = IOwnerLike(address(this)).owner();

L64

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

68:         return (activePool.getSystemCollShares());

76:         return (activePool.getSystemDebt());

89:         uint256 _systemStEthBalance = collateral.getPooledEthByShares(systemCollShares);
90:         TCR = EbtcMath._computeCR(_systemStEthBalance, systemDebt, _price);

L68, L76, L89

</details>

 

[G-27] Using storage instead of memory for structs/arrays saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read.

Instead of declaring the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct.

<details> <summary>Instances: 28</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

427:             MoveTokensParams memory _varMvTokens = MoveTokensParams(
428:                 msg.sender,
429:                 vars.collSharesChange,
430:                 (vars.isCollIncrease ? _stEthBalanceIncrease : 0),
431:                 vars.isCollIncrease,
432:                 _debtChange,
433:                 _isDebtIncrease
434:             );

L427

File: packages/contracts/contracts/CdpManager.sol

150:         CdpDebtAndCollShares memory _oldDebtAndColl = CdpDebtAndCollShares(
151:             Cdps[_redeemColFromCdp.cdpId].debt,
152:             Cdps[_redeemColFromCdp.cdpId].coll
153:         );

395:                 SingleRedemptionInputs memory _redeemColFromCdp = SingleRedemptionInputs(
396:                     _cId,
397:                     totals.remainingDebtToRedeem,
398:                     totals.price,
399:                     _upperPartialRedemptionHint,
400:                     _lowerPartialRedemptionHint,
401:                     _partialRedemptionHintNICR
402:                 );
403:                 SingleRedemptionValues memory singleRedemption = _redeemCollateralFromCdp(
404:                     _redeemColFromCdp
405:                 );

437:             bytes32[] memory _toRemoveIds = _getCdpIdsToRemove(
438:                 _lastRedeemed,
439:                 _numCdpsFullyRedeemed,
440:                 _firstRedeemed
441:             );

496:         bytes32[] memory _toRemoveIds = new bytes32[](_total);

L150, L395, L437, L496

File: packages/contracts/contracts/LiquidationLibrary.sol

101:         LiquidationLocals memory _liqState = LiquidationLocals(
102:             _cdpId,
103:             _partialAmount,
104:             _price,
105:             _ICR,
106:             _upperPartialHint,
107:             _lowerPartialHint,
108:             (_recoveryModeAtStart),
109:             _TCR,
110:             0,
111:             0,
112:             0,
113:             0,
114:             0
115:         );

117:         LiquidationRecoveryModeLocals memory _rs = LiquidationRecoveryModeLocals(
118:             systemDebt,
119:             systemColl,
120:             0,
121:             0,
122:             0,
123:             _cdpId,
124:             _price,
125:             _ICR,
126:             0,
127:             0
128:         );

210:             LiquidationLocals memory _outputState = _liquidateIndividualCdpSetupCDPInNormalMode(
211:                 _liqState
212:             );

404:         CdpDebtAndCollShares memory _debtAndColl = _getSyncedDebtAndCollShares(_cdpId);

614:         LiquidationLocals memory _liqState = LiquidationLocals(
615:             vars.cdpId,
616:             0,
617:             _price,
618:             vars.ICR,
619:             vars.cdpId,
620:             vars.cdpId,
621:             (false),
622:             _TCR,
623:             0,
624:             0,
625:             0,
626:             0,
627:             0
628:         );

630:         LiquidationLocals memory _outputState = _liquidateIndividualCdpSetupCDPInNormalMode(
631:             _liqState
632:         );

649:         LiquidationRecoveryModeLocals memory _recState = LiquidationRecoveryModeLocals(
650:             _systemDebt,
651:             _systemCollShares,
652:             0,
653:             0,
654:             0,
655:             vars.cdpId,
656:             _price,
657:             vars.ICR,
658:             0,
659:             0
660:         );

662:         LiquidationRecoveryModeLocals
663:             memory _outputState = _liquidateIndividualCdpSetupCDPInRecoveryMode(_recState);

751:         bool[] memory _liqFlags = new bool[](_cnt);

L101, L117, L210, L404, L614, L630, L649, L662, L751

File: packages/contracts/contracts/Governor.sol

56:             address[] memory _usrs = users.values();

134:         bytes32[] memory _sigs = enabledFunctionSigsByTarget[_target].values();

L56, L134

File: packages/contracts/contracts/PriceFeed.sol

100:         ChainlinkResponse memory chainlinkResponse = _getCurrentChainlinkResponse();
101:         ChainlinkResponse memory prevChainlinkResponse = _getPrevChainlinkResponse(
102:             chainlinkResponse.roundEthBtcId,
103:             chainlinkResponse.roundStEthEthId
104:         );
105:         FallbackResponse memory fallbackResponse = _getCurrentFallbackResponse();

L100

File: packages/contracts/contracts/SortedCdps.sol

310:         bytes32[] memory userCdps = new bytes32[](maxArraySize);

522:             Node memory _node = data.nodes[_id];

L310, L522

File: packages/contracts/contracts/LeverageMacroBase.sol

199:             ICdpManagerData.Cdp memory cdpInfo = cdpManager.Cdps(checkParams.cdpId);

339:         LeverageMacroOperation memory leverageMacroData = abi.decode(data, (LeverageMacroOperation));

374:         LeverageMacroOperation memory operation = decodeFLData(data);

461:         OpenCdpOperation memory flData = abi.decode(data, (OpenCdpOperation));

475:         CloseCdpOperation memory flData = abi.decode(data, (CloseCdpOperation));

483:         AdjustCdpOperation memory flData = abi.decode(data, (AdjustCdpOperation));

L199, L339, L374, L461, L475, L483

</details>

 

[G-28] Refactor modifiers to call a local function

Modifiers code is copied in all instances where it's used, increasing bytecode size. By doing a refractor to the internal function, one can reduce bytecode size significantly at the cost of one JUMP.

<details> <summary>Instances: 5</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

144:     modifier nonReentrantSelfAndCdpM() {

L144

File: packages/contracts/contracts/CdpManagerStorage.sol

241:     modifier nonReentrantSelfAndBOps() {

L241

File: packages/contracts/contracts/Dependencies/Auth.sol

26:     modifier requiresAuth() virtual {

L26

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

16:     modifier requiresAuth() virtual {

L16

File: packages/contracts/contracts/Dependencies/ReentrancyGuard.sol

13:     modifier nonReentrant() virtual {

L13

</details>

 

[G-29] Combine multiple mappings with the same key type where appropriate

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.

<details> <summary>Instances: 3</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

168:     mapping(bytes32 => Cdp) public Cdps;

190:     mapping(bytes32 => uint256) public cdpDebtRedistributionIndex;

202:     mapping(bytes32 => uint256) public cdpStEthFeePerUnitIndex;

L168, L190, L202

File: packages/contracts/contracts/EBTCToken.sol

51:     mapping(address => uint256) private _balances;
52:     mapping(address => mapping(address => uint256)) private _allowances;

L51

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

28:     mapping(address => EnumerableSet.Bytes32Set) internal enabledFunctionSigsByTarget;

32:     mapping(address => bytes32) public getUserRoles;

34:     mapping(address => mapping(bytes4 => CapabilityFlag)) public capabilityFlag;

36:     mapping(address => mapping(bytes4 => bytes32)) public getRolesWithCapability;

L28, L32, L34, L36

</details>

 

[G-30] Nesting if-statements is cheaper than using &&

Nesting if-statements avoids the stack operations of setting up and using an extra jumpdest, and saves 6 gas. Note that if an else statement is present, then nesting would use more gas, not less.

<details> <summary>Instances: 13</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

393:         if (!_isDebtIncrease && _debtChange > 0) {
394:             _requireValidDebtRepayment(vars.debt, vars.netDebtChange);
395:             _requireSufficientEbtcTokenBalance(msg.sender, vars.netDebtChange);
396:             _requireNonZeroDebt(vars.debt - vars.netDebtChange);
397:         }

L393

File: packages/contracts/contracts/LiquidationLibrary.sol

157:             if (
158:                 liquidationValues.totalCollToSendToLiquidator == 0 &&
159:                 liquidationValues.debtToBurn == 0
160:             ) {
161:                 // retry with fully liquidation
162:                 (
163:                     liquidationValues.debtToBurn,
164:                     liquidationValues.totalCollToSendToLiquidator,
165:                     liquidationValues.debtToRedistribute,
166:                     liquidationValues.liquidatorCollSharesReward,
167:                     liquidationValues.collSurplus
168:                 ) = _liquidateCdpInGivenMode(_liqState, _recoveryState);
169:             }

756:             if (vars.cdpId != bytes32(0) && Cdps[vars.cdpId].status == Status.active) {
757:                 vars.ICR = getSyncedICR(vars.cdpId, _price);

759:                 if (
760:                     !vars.backToNormalMode &&
761:                     (_checkICRAgainstMCR(vars.ICR) || canLiquidateRecoveryMode(vars.ICR, _TCR))
762:                 ) {
763:                     vars.price = _price;
764:                     _syncAccounting(vars.cdpId);
765:                     _getLiquidationValuesRecoveryMode(
766:                         _price,
767:                         vars.entireSystemDebt,
768:                         vars.entireSystemColl,
769:                         vars,
770:                         singleLiquidation
771:                     );

773:                     // Update aggregate trackers
774:                     vars.entireSystemDebt = vars.entireSystemDebt - singleLiquidation.debtToBurn;
775:                     vars.entireSystemColl =
776:                         vars.entireSystemColl -
777:                         singleLiquidation.totalCollToSendToLiquidator -
778:                         singleLiquidation.collSurplus;

780:                     // Add liquidation values to their respective running totals
781:                     totals = _addLiquidationValuesToTotals(totals, singleLiquidation);

783:                     _TCR = _computeTCRWithGivenSystemValues(
784:                         vars.entireSystemColl,
785:                         vars.entireSystemDebt,
786:                         _price
787:                     );
788:                     vars.backToNormalMode = _TCR < CCR ? false : true;
789:                     _liqFlags[vars.i] = true;
790:                 } else if (vars.backToNormalMode && _checkICRAgainstMCR(vars.ICR)) {
791:                     _syncAccounting(vars.cdpId);
792:                     _getLiquidationValuesNormalMode(_price, _TCR, vars, singleLiquidation);

794:                     // Add liquidation values to their respective running totals
795:                     totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
796:                     _liqFlags[vars.i] = true;
797:                 }

819:             if (vars.cdpId != bytes32(0) && Cdps[vars.cdpId].status == Status.active) {
820:                 vars.ICR = getSyncedICR(vars.cdpId, _price);

822:                 if (_checkICRAgainstMCR(vars.ICR)) {
823:                     _syncAccounting(vars.cdpId);
824:                     _getLiquidationValuesNormalMode(_price, _TCR, vars, singleLiquidation);

826:                     // Add liquidation values to their respective running totals
827:                     totals = _addLiquidationValuesToTotals(totals, singleLiquidation);
828:                 }
829:             }

L157, L756, L759, L773, L780, L783, L794, L819, L822, L826

File: packages/contracts/contracts/CdpManagerStorage.sol

512:         if (_newIndex > _oldIndex && totalStakes > 0) {
513:             (
514:                 uint256 _feeTaken,
515:                 uint256 _newFeePerUnit,
516:                 uint256 _perUnitError
517:             ) = _calcSyncedGlobalAccounting(_newIndex, _oldIndex);
518:             _takeSplitAndUpdateFeePerUnit(_feeTaken, _newFeePerUnit, _perUnitError);
519:             _updateSystemSnapshotsExcludeCollRemainder(0);
520:         }

796:         if (_cdpPerUnitIdx != _systemStEthFeePerUnitIndex && _cdpPerUnitIdx > 0) {
797:             (
798:                 uint256 _feeSplitDistributed,
799:                 uint256 _newCollShareAfter
800:             ) = getAccumulatedFeeSplitApplied(_cdpId, _systemStEthFeePerUnitIndex);
801:             _feeSplitApplied = _feeSplitDistributed;
802:             _newCollShare = _newCollShareAfter;
803:         }

L512, L796

File: packages/contracts/contracts/PriceFeed.sol

226:                 if (
227:                     !_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) &&
228:                     !_chainlinkIsFrozen(chainlinkResponse)
229:                 ) {
230:                     _changeStatus(Status.usingChainlinkFallbackUntrusted);
231:                     return _storeChainlinkPrice(chainlinkResponse.answer);
232:                 }

372:                 if (
373:                     !_fallbackIsBroken(fallbackResponse) &&
374:                     !_responseTimeout(fallbackResponse.timestamp, newFallbackCaler.fallbackTimeout())
375:                 ) {
376:                     address oldFallbackCaller = address(fallbackCaller);
377:                     fallbackCaller = newFallbackCaler;
378:                     emit FallbackCallerChanged(oldFallbackCaller, _fallbackCaller);
379:                 }

L226, L372

File: packages/contracts/contracts/SortedCdps.sol

202:             if (maxNodes > 0 && i >= maxNodes) {
203:                 break;
204:             }

259:             if (maxNodes > 0 && i >= maxNodes) {
260:                 break;
261:             }

330:             if (maxNodes > 0 && i >= maxNodes) {
331:                 break;
332:             }

621:         if (data.head == _startId && _NICR >= cdpManager.getCachedNominalICR(_startId)) {
622:             return (dummyId, _startId);
623:         }

644:         if (data.tail == _startId && _NICR <= cdpManager.getCachedNominalICR(_startId)) {
645:             return (_startId, dummyId);
646:         }

L202, L259, L330, L621, L644

</details>

 

[G-31] Use unchecked for operations that cannot overflow/underflow

By bypassing Solidity's built in overflow/underflow checks using unchecked, we can save gas. This is especially beneficial for the index variable within for loops (saves 120 gas per iteration).

<details> <summary>Instances: 13</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

717:         bytes32 digest = keccak256(
718:             abi.encodePacked(
719:                 "\x19\x01",
720:                 domainSeparator(),
721:                 keccak256(
722:                     abi.encode(
723:                         _PERMIT_POSITION_MANAGER_TYPEHASH,
724:                         _borrower,
725:                         _positionManager,
726:                         _approval,
727:                         _nonces[_borrower]++,
728:                         _deadline
729:                     )
730:                 )
731:             )
732:         );

L717

File: packages/contracts/contracts/EBTCToken.sol

209:         bytes32 digest = keccak256(
210:             abi.encodePacked(
211:                 "\x19\x01",
212:                 domainSeparator(),
213:                 keccak256(
214:                     abi.encode(_PERMIT_TYPEHASH, owner, spender, amount, _nonces[owner]++, deadline)
215:                 )
216:             )
217:         );

L209

File: packages/contracts/contracts/Governor.sol

46:         for (uint256 i = 0; i < users.length(); i++) {

57:             for (uint256 i = 0; i < _usrs.length; i++) {

62:                     j++;

76:         for (uint8 i = 0; i < type(uint8).max; i++) {

84:             for (uint8 i = 0; i < type(uint8).max; i++) {

87:                     j++;

98:         for (uint8 i = 0; i < type(uint8).max; i++) {

107:             for (uint8 i = 0; i < type(uint8).max; i++) {

111:                     j++;

122:         for (uint8 i = 0; i < roleIds.length; i++) {

L46, L57, L62, L76, L84, L87, L98, L107, L111, L122

File: packages/contracts/contracts/HintHelpers.sol

195:             i++;

L195

</details>

 

[G-32] Function names can be optimized to save gas

public/external function names and public member variable names can be optimized to save gas. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted.

<details> <summary>Instances: 17</summary>
File: packages/contracts/contracts/ActivePool.sol

22: contract ActivePool is IActivePool, ERC3156FlashLender, ReentrancyGuard, BaseMath, AuthNoOwner {

74:     function getSystemCollShares() external view override returns (uint256) {

82:     function getSystemDebt() external view override returns (uint256) {

89:     function getFeeRecipientClaimableCollShares() external view override returns (uint256) {

100:     function transferSystemCollShares(address _account, uint256 _shares) public override {

129:     function transferSystemCollSharesAndLiquidatorReward(
130:         address _account,
131:         uint256 _shares,
132:         uint256 _liquidatorRewardShares
133:     ) external override {

157:     function allocateSystemCollSharesToFeeRecipient(uint256 _shares) external override {

194:     function increaseSystemDebt(uint256 _amount) external override {

207:     function decreaseSystemDebt(uint256 _amount) external override {

242:     function increaseSystemCollShares(uint256 _value) external override {

261:     function flashLoan(
262:         IERC3156FlashBorrower receiver,
263:         address token,
264:         uint256 amount,
265:         bytes calldata data
266:     ) external override returns (bool) {

317:     function flashFee(address token, uint256 amount) public view override returns (uint256) {

328:     function maxFlashLoan(address token) public view override returns (uint256) {

346:     function claimFeeRecipientCollShares(uint256 _shares) external override requiresAuth {

371:     function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {

390:     function setFeeRecipientAddress(address _feeRecipientAddress) external requiresAuth {

404:     function setFeeBps(uint256 _newFee) external requiresAuth {

417:     function setFlashLoansPaused(bool _paused) external requiresAuth {

L22, L74, L82, L89, L100, L129, L157, L194, L207, L242, L261, L317, L328, L346, L371, L390, L404, L417

File: packages/contracts/contracts/BorrowerOperations.sol

21: contract BorrowerOperations is
22:     EbtcBase,
23:     ReentrancyGuard,
24:     IBorrowerOperations,
25:     ERC3156FlashLender,
26:     AuthNoOwner,
27:     PermitNonce
28: {

168:     function openCdp(
169:         uint256 _debt,
170:         bytes32 _upperHint,
171:         bytes32 _lowerHint,
172:         uint256 _stEthBalance
173:     ) external override nonReentrantSelfAndCdpM returns (bytes32) {

187:     function openCdpFor(
188:         uint256 _debt,
189:         bytes32 _upperHint,
190:         bytes32 _lowerHint,
191:         uint256 _stEthBalance,
192:         address _borrower
193:     ) external override nonReentrantSelfAndCdpM returns (bytes32) {

203:     function addColl(
204:         bytes32 _cdpId,
205:         bytes32 _upperHint,
206:         bytes32 _lowerHint,
207:         uint256 _stEthBalanceIncrease
208:     ) external override nonReentrantSelfAndCdpM {

219:     function withdrawColl(
220:         bytes32 _cdpId,
221:         uint256 _stEthBalanceDecrease,
222:         bytes32 _upperHint,
223:         bytes32 _lowerHint
224:     ) external override nonReentrantSelfAndCdpM {

235:     function withdrawDebt(
236:         bytes32 _cdpId,
237:         uint256 _debt,
238:         bytes32 _upperHint,
239:         bytes32 _lowerHint
240:     ) external override nonReentrantSelfAndCdpM {

250:     function repayDebt(
251:         bytes32 _cdpId,
252:         uint256 _debt,
253:         bytes32 _upperHint,
254:         bytes32 _lowerHint
255:     ) external override nonReentrantSelfAndCdpM {

270:     function adjustCdp(
271:         bytes32 _cdpId,
272:         uint256 _stEthBalanceDecrease,
273:         uint256 _debtChange,
274:         bool _isDebtIncrease,
275:         bytes32 _upperHint,
276:         bytes32 _lowerHint
277:     ) external override nonReentrantSelfAndCdpM {

300:     function adjustCdpWithColl(
301:         bytes32 _cdpId,
302:         uint256 _stEthBalanceDecrease,
303:         uint256 _debtChange,
304:         bool _isDebtIncrease,
305:         bytes32 _upperHint,
306:         bytes32 _lowerHint,
307:         uint256 _stEthBalanceIncrease
308:     ) external override nonReentrantSelfAndCdpM {

553:     function closeCdp(bytes32 _cdpId) external override {

599:     function claimSurplusCollShares() external override {

608:     function getPositionManagerApproval(
609:         address _borrower,
610:         address _positionManager
611:     ) external view override returns (PositionManagerApproval) {

628:     function setPositionManagerApproval(
629:         address _positionManager,
630:         PositionManagerApproval _approval
631:     ) external override {

647:     function revokePositionManagerApproval(address _positionManager) external override {

653:     function renouncePositionManagerApproval(address _borrower) external override {

659:     function DOMAIN_SEPARATOR() external view returns (bytes32) {

665:     function domainSeparator() public view override returns (bytes32) {

687:     function version() external pure override returns (string memory) {

693:     function permitTypeHash() external pure override returns (bytes32) {

706:     function permitPositionManagerApproval(
707:         address _borrower,
708:         address _positionManager,
709:         PositionManagerApproval _approval,
710:         uint256 _deadline,
711:         uint8 v,
712:         bytes32 r,
713:         bytes32 s
714:     ) external override {

1077:     function flashLoan(
1078:         IERC3156FlashBorrower receiver,
1079:         address token,
1080:         uint256 amount,
1081:         bytes calldata data
1082:     ) external override returns (bool) {

1114:     function flashFee(address token, uint256 amount) public view override returns (uint256) {

1124:     function maxFlashLoan(address token) public view override returns (uint256) {

1140:     function setFeeRecipientAddress(address _feeRecipientAddress) external requiresAuth {

1154:     function setFeeBps(uint256 _newFee) external requiresAuth {

1167:     function setFlashLoansPaused(bool _paused) external requiresAuth {

L21, L168, L187, L203, L219, L235, L250, L270, L300, L553, L599, L608, L628, L647, L653, L659, L665, L687, L693, L706, L1077, L1114, L1124, L1140, L1154, L1167

File: packages/contracts/contracts/CdpManager.sol

16: contract CdpManager is CdpManagerStorage, ICdpManager, Proxy {

66:     function getActiveCdpsCount() external view override returns (uint256) {

73:     function getIdFromCdpIdsArray(uint256 _index) external view override returns (bytes32) {

99:     function liquidate(bytes32 _cdpId) external override {

109:     function partiallyLiquidate(
110:         bytes32 _cdpId,
111:         uint256 _partialAmount,
112:         bytes32 _upperPartialHint,
113:         bytes32 _lowerPartialHint
114:     ) external override {

126:     function batchLiquidateCdps(bytes32[] memory _cdpArray) external override {

320:     function redeemCollateral(
321:         uint256 _debt,
322:         bytes32 _firstRedemptionHint,
323:         bytes32 _upperPartialRedemptionHint,
324:         bytes32 _lowerPartialRedemptionHint,
325:         uint256 _partialRedemptionHintNICR,
326:         uint256 _maxIterations,
327:         uint256 _maxFeePercentage
328:     ) external override nonReentrantSelfAndBOps {

514:     function syncAccounting(bytes32 _cdpId) external virtual override {

523:     function updateStakeAndTotalStakes(bytes32 _cdpId) external override returns (uint256) {

538:     function closeCdp(
539:         bytes32 _cdpId,
540:         address _borrower,
541:         uint256 _debt,
542:         uint256 _coll
543:     ) external override {

570:     function getSystemDebt() public view returns (uint256 entireSystemDebt) {

580:     function getCachedTCR(uint256 _price) external view override returns (uint256) {

589:     function checkRecoveryMode(uint256 _price) external view override returns (bool) {

638:     function getRedemptionRate() public view override returns (uint256) {

643:     function getRedemptionRateWithDecay() public view override returns (uint256) {

661:     function getRedemptionFeeWithDecay(
662:         uint256 _stETHToRedeem
663:     ) external view override returns (uint256) {

717:     function getDeploymentStartTime() public view returns (uint256) {

727:     function checkPotentialRecoveryMode(
728:         uint256 _systemCollShares,
729:         uint256 _systemDebt,
730:         uint256 _price
731:     ) external view returns (bool) {

773:     function setStakingRewardSplit(uint256 _stakingRewardSplit) external requiresAuth {

788:     function setRedemptionFeeFloor(uint256 _redemptionFeeFloor) external requiresAuth {

807:     function setMinuteDecayFactor(uint256 _minuteDecayFactor) external requiresAuth {

830:     function setBeta(uint256 _beta) external requiresAuth {

843:     function setRedemptionsPaused(bool _paused) external requiresAuth {

856:     function getCdpStatus(bytes32 _cdpId) external view override returns (uint256) {

863:     function getCdpStake(bytes32 _cdpId) external view override returns (uint256) {

871:     function getCdpDebt(bytes32 _cdpId) external view override returns (uint256) {

879:     function getCdpCollShares(bytes32 _cdpId) external view override returns (uint256) {

891:     function getCdpLiquidatorRewardShares(bytes32 _cdpId) external view override returns (uint256) {

905:     function initializeCdp(
906:         bytes32 _cdpId,
907:         uint256 _debt,
908:         uint256 _coll,
909:         uint256 _liquidatorRewardShares,
910:         address _borrower
911:     ) external {

946:     function updateCdp(
947:         bytes32 _cdpId,
948:         address _borrower,
949:         uint256 _coll,
950:         uint256 _debt,
951:         uint256 _newColl,
952:         uint256 _newDebt
953:     ) external {

L16, L66, L73, L99, L109, L126, L320, L514, L523, L538, L570, L580, L589, L638, L643, L661, L717, L727, L773, L788, L807, L830, L843, L856, L863, L871, L879, L891, L905, L946

File: packages/contracts/contracts/LiquidationLibrary.sol

13: contract LiquidationLibrary is CdpManagerStorage {

40:     function liquidate(bytes32 _cdpId) external nonReentrantSelfAndBOps {

50:     function partiallyLiquidate(
51:         bytes32 _cdpId,
52:         uint256 _partialAmount,
53:         bytes32 _upperPartialHint,
54:         bytes32 _lowerPartialHint
55:     ) external nonReentrantSelfAndBOps {

679:     function batchLiquidateCdps(bytes32[] memory _cdpArray) external nonReentrantSelfAndBOps {

L13, L40, L50, L679

File: packages/contracts/contracts/CdpManagerStorage.sol

19: contract CdpManagerStorage is EbtcBase, ReentrancyGuard, ICdpManagerData, AuthNoOwner {

32:     function notifyStartGracePeriod(uint256 tcr) external {

42:     function notifyEndGracePeriod(uint256 tcr) external {

111:     function setGracePeriod(uint128 _gracePeriod) external requiresAuth {

504:     function syncGlobalAccounting() external {

527:     function syncGlobalAccountingAndGracePeriod() public {

552:     function calcFeeUponStakingReward(
553:         uint256 _newIndex,
554:         uint256 _prevIndex
555:     ) public view returns (uint256, uint256, uint256) {

616:     function getAccumulatedFeeSplitApplied(
617:         bytes32 _cdpId,
618:         uint256 _systemStEthFeePerUnitIndex
619:     ) public view returns (uint256, uint256) {

673:     function getCachedNominalICR(bytes32 _cdpId) external view returns (uint256) {

684:     function getSyncedNominalICR(bytes32 _cdpId) external view returns (uint256) {

701:     function getCachedICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {

719:     function getPendingRedistributedDebt(
720:         bytes32 _cdpId
721:     ) public view returns (uint256 pendingEBTCDebtReward) {

728:     function hasPendingRedistributedDebt(bytes32 _cdpId) public view returns (bool) {

745:     function getSyncedDebtAndCollShares(
746:         bytes32 _cdpId
747:     ) public view returns (uint256 debt, uint256 coll) {

839:     function getSyncedCdpDebt(bytes32 _cdpId) public view returns (uint256) {

848:     function getSyncedCdpCollShares(bytes32 _cdpId) public view returns (uint256) {

864:     function getSyncedICR(bytes32 _cdpId, uint256 _price) public view returns (uint256) {

874:     function getSyncedTCR(uint256 _price) public view returns (uint256) {

890:     function canLiquidateRecoveryMode(uint256 icr, uint256 tcr) public view returns (bool) {

L19, L32, L42, L111, L504, L527, L552, L616, L673, L684, L701, L719, L728, L745, L839, L848, L864, L874, L890

File: packages/contracts/contracts/CollSurplusPool.sol

16: contract CollSurplusPool is ICollSurplusPool, ReentrancyGuard, AuthNoOwner {

61:     function getTotalSurplusCollShares() external view override returns (uint256) {

67:     function getSurplusCollShares(address _account) external view override returns (uint256) {

77:     function increaseSurplusCollShares(address _account, uint256 _amount) external override {

89:     function claimSurplusCollShares(address _account) external override {

130:     function increaseTotalSurplusCollShares(uint256 _value) external override {

142:     function sweepToken(address token, uint256 amount) public nonReentrant requiresAuth {

L16, L61, L67, L77, L89, L130, L142

File: packages/contracts/contracts/EBTCToken.sol

26: contract EBTCToken is IEBTCToken, AuthNoOwner, PermitNonce {

85:     function mint(address _account, uint256 _amount) external override {

95:     function burn(address _account, uint256 _amount) external override {

104:     function burn(uint256 _amount) external {

111:     function totalSupply() external view override returns (uint256) {

115:     function balanceOf(address account) external view override returns (uint256) {

119:     function transfer(address recipient, uint256 amount) external override returns (bool) {

125:     function allowance(address owner, address spender) external view override returns (uint256) {

129:     function approve(address spender, uint256 amount) external override returns (bool) {

134:     function transferFrom(
135:         address sender,
136:         address recipient,
137:         uint256 amount
138:     ) external override returns (bool) {

152:     function increaseAllowance(
153:         address spender,
154:         uint256 addedValue
155:     ) external override returns (bool) {

160:     function decreaseAllowance(
161:         address spender,
162:         uint256 subtractedValue
163:     ) external override returns (bool) {

176:     function DOMAIN_SEPARATOR() external view returns (bytes32) {

182:     function domainSeparator() public view override returns (bytes32) {

199:     function permit(
200:         address owner,
201:         address spender,
202:         uint256 amount,
203:         uint256 deadline,
204:         uint8 v,
205:         bytes32 r,
206:         bytes32 s
207:     ) external override {

225:     function nonces(address owner) external view override(IERC2612, PermitNonce) returns (uint256) {

331:     function name() external pure override returns (string memory) {

337:     function symbol() external pure override returns (string memory) {

343:     function decimals() external pure override returns (uint8) {

349:     function version() external pure override returns (string memory) {

355:     function permitTypeHash() external pure override returns (bytes32) {

L26, L85, L95, L104, L111, L115, L119, L125, L129, L134, L152, L160, L176, L182, L199, L225, L331, L337, L343, L349, L355

File: packages/contracts/contracts/Governor.sol

13: contract Governor is RolesAuthority {

43:     function getUsersByRole(uint8 role) external view returns (address[] memory usersWithRole) {

73:     function getRolesForUser(address user) external view returns (uint8[] memory rolesForUser) {

96:     function getRolesFromByteMap(bytes32 byteMap) public pure returns (uint8[] memory roleIds) {

120:     function getByteMapFromRoles(uint8[] memory roleIds) public pure returns (bytes32) {

131:     function getEnabledFunctionsInTarget(
132:         address _target
133:     ) public view returns (bytes4[] memory _funcs) {

146:     function getRoleName(uint8 role) external view returns (string memory roleName) {

154:     function setRoleName(uint8 role, string memory roleName) external requiresAuth {

L13, L43, L73, L96, L120, L131, L146, L154

File: packages/contracts/contracts/PriceFeed.sol

21: contract PriceFeed is BaseMath, IPriceFeed, AuthNoOwner {

98:     function fetchPrice() external override returns (uint256) {

358:     function setFallbackCaller(address _fallbackCaller) external requiresAuth {

L21, L98, L358

File: packages/contracts/contracts/SortedCdps.sol

51: contract SortedCdps is ISortedCdps {

105:     function toCdpId(
106:         address owner,
107:         uint256 blockHeight,
108:         uint256 nonce
109:     ) public pure returns (bytes32) {

123:     function getOwnerAddress(bytes32 cdpId) public pure override returns (address) {

130:     function nonExistId() public pure override returns (bytes32) {

140:     function cdpOfOwnerByIndex(
141:         address owner,
142:         uint256 index
143:     ) external view override returns (bytes32) {

155:     function cdpOfOwnerByIdx(
156:         address owner,
157:         uint256 index,
158:         bytes32 startNodeId,
159:         uint maxNodes
160:     ) external view override returns (bytes32, bool) {

216:     function cdpCountOf(address owner) external view override returns (uint256) {

227:     function getCdpCountOf(
228:         address owner,
229:         bytes32 startNodeId,
230:         uint maxNodes
231:     ) external view override returns (uint256, bytes32) {

270:     function getCdpsOf(address owner) external view override returns (bytes32[] memory cdps) {

286:     function getAllCdpsOf(
287:         address owner,
288:         bytes32 startNodeId,
289:         uint maxNodes
290:     ) external view override returns (bytes32[] memory, uint256, bytes32) {

344:     function insert(
345:         address owner,
346:         uint256 _NICR,
347:         bytes32 _prevId,
348:         bytes32 _nextId
349:     ) external override returns (bytes32) {

410:     function remove(bytes32 _id) external override {

419:     function batchRemove(bytes32[] memory _ids) external override {

498:     function reInsert(
499:         bytes32 _id,
500:         uint256 _newNICR,
501:         bytes32 _prevId,
502:         bytes32 _nextId
503:     ) external override {

519:     function contains(bytes32 _id) public view override returns (bool) {

530:     function isFull() public view override returns (bool) {

536:     function isEmpty() public view override returns (bool) {

542:     function getSize() external view override returns (uint256) {

548:     function getMaxSize() external view override returns (uint256) {

554:     function getFirst() external view override returns (bytes32) {

560:     function getLast() external view override returns (bytes32) {

567:     function getNext(bytes32 _id) external view override returns (bytes32) {

574:     function getPrev(bytes32 _id) external view override returns (bytes32) {

583:     function validInsertPosition(
584:         uint256 _NICR,
585:         bytes32 _prevId,
586:         bytes32 _nextId
587:     ) external view override returns (bool) {

666:     function findInsertPosition(
667:         uint256 _NICR,
668:         bytes32 _prevId,
669:         bytes32 _nextId
670:     ) external view override returns (bytes32, bytes32) {

L51, L105, L123, L130, L140, L155, L216, L227, L270, L286, L344, L410, L419, L498, L519, L530, L536, L542, L548, L554, L560, L567, L574, L583, L666

File: packages/contracts/contracts/HintHelpers.sol

11: contract HintHelpers is EbtcBase {

48:     function getRedemptionHints(
49:         uint256 _EBTCamount,
50:         uint256 _price,
51:         uint256 _maxIterations
52:     )
53:         external
54:         view
55:         returns (
56:             bytes32 firstRedemptionHint,
57:             uint256 partialRedemptionHintNICR,
58:             uint256 truncatedEBTCamount,
59:             uint256 partialRedemptionNewColl
60:         )
61:     {

164:     function getApproxHint(
165:         uint256 _CR,
166:         uint256 _numTrials,
167:         uint256 _inputRandomSeed
168:     ) external view returns (bytes32 hint, uint256 diff, uint256 latestRandomSeed) {

203:     function computeNominalCR(uint256 _coll, uint256 _debt) external pure returns (uint256) {

212:     function computeCR(
213:         uint256 _coll,
214:         uint256 _debt,
215:         uint256 _price
216:     ) external pure returns (uint256) {

L11, L48, L164, L203, L212

File: packages/contracts/contracts/LeverageMacroBase.sol

26: contract LeverageMacroBase {

39:     function owner() public virtual returns (address) {

118:     function doOperation(
119:         FlashLoanType flType,
120:         uint256 borrowAmount,
121:         LeverageMacroOperation calldata operation,
122:         PostOperationCheck postCheckType,
123:         PostCheckParams calldata checkParams
124:     ) external {

214:     function sweepToCaller() public {

234:     function sweepToken(address token, uint256 amount) public {

338:     function decodeFLData(bytes calldata data) public view returns (LeverageMacroOperation memory) {

344:     function onFlashLoan(
345:         address initiator,
346:         address token,
347:         uint256 amount,
348:         uint256 fee,
349:         bytes calldata data
350:     ) external returns (bytes32) {

L26, L39, L118, L214, L234, L338, L344

File: packages/contracts/contracts/LeverageMacroFactory.sol

11: contract LeverageMacroFactory {

38:     function deployNewMacro() external returns (address) {

43:     function deployNewMacro(address _owner) public returns (address) {

L11, L38, L43

File: packages/contracts/contracts/LeverageMacroReference.sol

11: contract LeverageMacroReference is LeverageMacroBase {

44:     function owner() public override returns (address) {

50:     function resetApprovals() external {

L11, L44, L50

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

25: contract SimplifiedDiamondLike {

51:     function setFallbackHandler(bytes4 sig, address handler) external {

66:     function setAllowAnyCall(bool allowNonCallbacks) external {

76:     function enableCallbackForCall() external {

110:     function execute(Operation[] calldata ops) external payable {

L25, L51, L66, L76, L110

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

10: contract AuthNoOwner {

22:     function authority() public view returns (Authority) {

26:     function authorityInitialized() public view returns (bool) {

38:     function setAuthority(address newAuthority) public virtual {

L10, L22, L26, L38

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

12: contract RolesAuthority is IRolesAuthority, Auth, Authority {

38:     function doesUserHaveRole(address user, uint8 role) public view virtual returns (bool) {

42:     function doesRoleHaveCapability(
43:         uint8 role,
44:         address target,
45:         bytes4 functionSig
46:     ) public view virtual returns (bool) {

50:     function isPublicCapability(address target, bytes4 functionSig) public view returns (bool) {

63:     function canCall(
64:         address user,
65:         address target,
66:         bytes4 functionSig
67:     ) public view virtual override returns (bool) {

85:     function setPublicCapability(
86:         address target,
87:         bytes4 functionSig,
88:         bool enabled
89:     ) public virtual requiresAuth {

106:     function setRoleCapability(
107:         uint8 role,
108:         address target,
109:         bytes4 functionSig,
110:         bool enabled
111:     ) public virtual requiresAuth {

133:     function burnCapability(address target, bytes4 functionSig) public virtual requiresAuth {

147:     function setUserRole(address user, uint8 role, bool enabled) public virtual requiresAuth {

L12, L38, L42, L50, L63, L85, L106, L133, L147

</details>

 

[G-33] Pack state variables into fewer storage slots

If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables can also be cheaper.

For more information about variable packing, see here.

<details> <summary>Instances: 1</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

19: contract CdpManagerStorage is EbtcBase, ReentrancyGuard, ICdpManagerData, AuthNoOwner {

L19

</details>

 

[G-34] Use payable for constructor

Payable functions cost less gas to execute, since the compiler does not have to add extra checks to ensure that a payment wasn't provided. A constructor can safely be marked as payable, since only the deployer would be able to pass funds, and the project itself would not pass any funds.

<details> <summary>Instances: 19</summary>
File: packages/contracts/contracts/ActivePool.sol

46:     constructor(
47:         address _borrowerOperationsAddress,
48:         address _cdpManagerAddress,
49:         address _collTokenAddress,
50:         address _collSurplusAddress,
51:         address _feeRecipientAddress
52:     ) {

L46

File: packages/contracts/contracts/BorrowerOperations.sol

107:     constructor(
108:         address _cdpManagerAddress,
109:         address _activePoolAddress,
110:         address _collSurplusPoolAddress,
111:         address _priceFeedAddress,
112:         address _sortedCdpsAddress,
113:         address _ebtcTokenAddress,
114:         address _feeRecipientAddress,
115:         address _collTokenAddress
116:     ) EbtcBase(_activePoolAddress, _priceFeedAddress, _collTokenAddress) {

L107

File: packages/contracts/contracts/CdpManager.sol

30:     constructor(
31:         address _liquidationLibraryAddress,
32:         address _authorityAddress,
33:         address _borrowerOperationsAddress,
34:         address _collSurplusPoolAddress,
35:         address _ebtcTokenAddress,
36:         address _sortedCdpsAddress,
37:         address _activePoolAddress,
38:         address _priceFeedAddress,
39:         address _collTokenAddress
40:     )
41:         CdpManagerStorage(
42:             _liquidationLibraryAddress,
43:             _authorityAddress,
44:             _borrowerOperationsAddress,
45:             _collSurplusPoolAddress,
46:             _ebtcTokenAddress,
47:             _sortedCdpsAddress,
48:             _activePoolAddress,
49:             _priceFeedAddress,
50:             _collTokenAddress
51:         )
52:     {

L30

File: packages/contracts/contracts/LiquidationLibrary.sol

14:     constructor(
15:         address _borrowerOperationsAddress,
16:         address _collSurplusPool,
17:         address _ebtcToken,
18:         address _sortedCdps,
19:         address _activePool,
20:         address _priceFeed,
21:         address _collateral
22:     )
23:         CdpManagerStorage(
24:             address(0),
25:             address(0),
26:             _borrowerOperationsAddress,
27:             _collSurplusPool,
28:             _ebtcToken,
29:             _sortedCdps,
30:             _activePool,
31:             _priceFeed,
32:             _collateral
33:         )
34:     {}

L14

File: packages/contracts/contracts/CdpManagerStorage.sol

217:     constructor(
218:         address _liquidationLibraryAddress,
219:         address _authorityAddress,
220:         address _borrowerOperationsAddress,
221:         address _collSurplusPool,
222:         address _ebtcToken,
223:         address _sortedCdps,
224:         address _activePool,
225:         address _priceFeed,
226:         address _collateral
227:     ) EbtcBase(_activePool, _priceFeed, _collateral) {

L217

File: packages/contracts/contracts/CollSurplusPool.sol

42:     constructor(
43:         address _borrowerOperationsAddress,
44:         address _cdpManagerAddress,
45:         address _activePoolAddress,
46:         address _collTokenAddress
47:     ) {

L42

File: packages/contracts/contracts/EBTCToken.sol

61:     constructor(
62:         address _cdpManagerAddress,
63:         address _borrowerOperationsAddress,
64:         address _authorityAddress
65:     ) {

L61

File: packages/contracts/contracts/Governor.sol

36:     constructor(address _owner) RolesAuthority(_owner, Authority(address(this))) {}

L36

File: packages/contracts/contracts/PriceFeed.sol

57:     constructor(
58:         address _fallbackCallerAddress,
59:         address _authorityAddress,
60:         address _collEthCLFeed,
61:         address _ethBtcCLFeed
62:     ) {

L57

File: packages/contracts/contracts/SortedCdps.sol

88:     constructor(uint256 _size, address _cdpManagerAddress, address _borrowerOperationsAddress) {

L88

File: packages/contracts/contracts/HintHelpers.sol

27:     constructor(
28:         address _sortedCdpsAddress,
29:         address _cdpManagerAddress,
30:         address _collateralAddress,
31:         address _activePoolAddress,
32:         address _priceFeedAddress
33:     ) EbtcBase(_activePoolAddress, _priceFeedAddress, _collateralAddress) {

L27

File: packages/contracts/contracts/LeverageMacroBase.sol

51:     constructor(
52:         address _borrowerOperationsAddress,
53:         address _activePool,
54:         address _cdpManager,
55:         address _ebtc,
56:         address _coll,
57:         address _sortedCdps,
58:         bool _sweepToCaller
59:     ) {

L51

File: packages/contracts/contracts/LeverageMacroDelegateTarget.sol

41:     constructor(
42:         address _borrowerOperationsAddress,
43:         address _activePool,
44:         address _cdpManager,
45:         address _ebtc,
46:         address _coll,
47:         address _sortedCdps
48:     )
49:         LeverageMacroBase(
50:             _borrowerOperationsAddress,
51:             _activePool,
52:             _cdpManager,
53:             _ebtc,
54:             _coll,
55:             _sortedCdps,
56:             false // Do not sweep to caller
57:         )
58:     {

L41

File: packages/contracts/contracts/LeverageMacroFactory.sol

21:     constructor(
22:         address _borrowerOperationsAddress,
23:         address _activePool,
24:         address _cdpManager,
25:         address _ebtc,
26:         address _coll,
27:         address _sortedCdps
28:     ) {

L21

File: packages/contracts/contracts/LeverageMacroReference.sol

17:     constructor(
18:         address _borrowerOperationsAddress,
19:         address _activePool,
20:         address _cdpManager,
21:         address _ebtc,
22:         address _coll,
23:         address _sortedCdps,
24:         address _owner
25:     )
26:         LeverageMacroBase(
27:             _borrowerOperationsAddress,
28:             _activePool,
29:             _cdpManager,
30:             _ebtc,
31:             _coll,
32:             _sortedCdps,
33:             true // Sweep to caller since this is not supposed to hold funds
34:         )
35:     {

L17

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

44:     constructor(address _owner) {

L44

File: packages/contracts/contracts/Dependencies/Auth.sol

18:     constructor(address _owner, Authority _authority) {

L18

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

52:     constructor(address _activePoolAddress, address _priceFeedAddress, address _collateralAddress) {

L52

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

20:     constructor(address _owner, Authority _authority) Auth(_owner, _authority) {}

L20

</details>

 

[G-35] Pre-compute hashes

When calculating a deterministic keccak256 hash, compute the value beforehand and hard code it instead of computing at runtime/compile time to save gas.

Calculating a keccak256 hash costs 30 gas + 6 gas for each 256 bits of data being hashed.

<details> <summary>Instances: 3</summary>
File: packages/contracts/contracts/LeverageMacroBase.sol

37:     bytes32 constant FLASH_LOAN_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");

L37

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

39:     bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");

L39

File: packages/contracts/contracts/Dependencies/ERC3156FlashLender.sol

11:     bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

L11

</details>

 

[G-36] Use private rather than public for constants

Saves 3406-3606 gas in deployment gas due to the compiler not having to create non-payable getter functions for deployment calldata, not having to store the bytes of the value outside of where it's used, and not adding another entry to the method ID table. If needed to be viewed externally, the values can be read from the verified contract source code.

<details> <summary>Instances: 31</summary>
File: packages/contracts/contracts/ActivePool.sol

24:     string public constant NAME = "ActivePool";

L24

File: packages/contracts/contracts/BorrowerOperations.sol

29:     string public constant NAME = "BorrowerOperations";

L29

File: packages/contracts/contracts/CdpManagerStorage.sol

21:     uint128 public constant UNSET_TIMESTAMP = type(uint128).max;

22:     uint128 public constant MINIMUM_GRACE_PERIOD = 15 minutes;

122:     string public constant NAME = "CdpManager";

139:     uint256 public constant SECONDS_IN_ONE_MINUTE = 60;

141:     uint256 public constant MIN_REDEMPTION_FEE_FLOOR = (DECIMAL_PRECISION * 5) / 1000; // 0.5%

149:     uint256 public constant MIN_MINUTE_DECAY_FACTOR = 1; // Non-zero

150:     uint256 public constant MAX_MINUTE_DECAY_FACTOR = 999999999999999999; // Corresponds to a very fast decay rate, but not too extreme

L21, L22, L122, L139, L141, L149, L150

File: packages/contracts/contracts/CollSurplusPool.sol

19:     string public constant NAME = "CollSurplusPool";

L19

File: packages/contracts/contracts/PriceFeed.sol

22:     string public constant NAME = "PriceFeed";

32:     uint256 public constant TIMEOUT_ETH_BTC_FEED = 4800; // 1 hours & 20min: 60 * 80

33:     uint256 public constant TIMEOUT_STETH_ETH_FEED = 90000; // 25 hours: 60 * 60 * 25

36:     uint256 public constant MAX_PRICE_DEVIATION_FROM_PREVIOUS_ROUND = 5e17; // 50%

42:     uint256 public constant MAX_PRICE_DIFFERENCE_BETWEEN_ORACLES = 5e16; // 5%

L22, L32, L33, L36, L42

File: packages/contracts/contracts/SortedCdps.sol

52:     string public constant NAME = "SortedCdps";

80:     bytes32 public constant dummyId =

L52, L80

File: packages/contracts/contracts/HintHelpers.sol

12:     string public constant NAME = "HintHelpers";

L12

File: packages/contracts/contracts/Dependencies/ERC3156FlashLender.sol

9:     uint256 public constant MAX_BPS = 10_000;

10:     uint256 public constant MAX_FEE_BPS = 1_000; // 10%

11:     bytes32 public constant FLASH_SUCCESS_VALUE = keccak256("ERC3156FlashBorrower.onFlashLoan");

L9, L10, L11

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

19:     uint256 public constant LICR = 1030000000000000000; // 103%

22:     uint256 public constant MCR = 1100000000000000000; // 110%

25:     uint256 public constant CCR = 1250000000000000000; // 125%

28:     uint256 public constant LIQUIDATOR_REWARD = 2e17;

31:     uint256 public constant MIN_NET_STETH_BALANCE = 2e18;

33:     uint256 public constant PERCENT_DIVISOR = 200; // dividing by 200 yields 0.5%

35:     uint256 public constant BORROWING_FEE_FLOOR = 0; // 0.5%

37:     uint256 public constant STAKING_REWARD_SPLIT = 5_000; // taking 50% cut from staking reward

39:     uint256 public constant MAX_REWARD_SPLIT = 10_000;

L19, L22, L25, L28, L31, L33, L35, L37, L39

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

7:     uint256 public constant MAX_TCR = type(uint256).max;

L7

</details>

 

[G-37] require() or revert() statements that check input arguments should be at the top of the function

Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case.

<details> <summary>Instances: 5</summary>
File: packages/contracts/contracts/ActivePool.sol

269:         require(amount <= maxFlashLoan(token), "ActivePool: Too much");

407:         require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");

L269, L407

File: packages/contracts/contracts/BorrowerOperations.sol

1085:         require(amount <= maxFlashLoan(token), "BorrowerOperations: Too much");

L1085

File: packages/contracts/contracts/CdpManagerStorage.sol

453:             require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");

L453

File: packages/contracts/contracts/SortedCdps.sol

508:         require(_newNICR > 0, "SortedCdps: NICR must be positive");

L508

</details>

 

[G-38] Split require statements using &&

Replacing with two separate require statements saves 16 gas per instance.

<details> <summary>Instances: 33</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

734:         require(

735:             recoveredAddress != address(0) && recoveredAddress == _borrower,

736:             "BorrowerOperations: Invalid signature"

737:         );

L734, L735, L736, L737

File: packages/contracts/contracts/CdpManager.sol

762:         require(

763:             _maxFeePercentage >= redemptionFeeFloor && _maxFeePercentage <= DECIMAL_PRECISION,

764:             "Max fee percentage must be between redemption fee floor and 100%"

765:         );

L762, L763, L764, L765

File: packages/contracts/contracts/CdpManagerStorage.sol

261:         require(

262:             closedStatus != Status.nonExistent && closedStatus != Status.active,

263:             "CdpManagerStorage: close non-exist or non-active CDP!"

264:         );

466:         require(

467:             cdpStatus != Status.nonExistent && cdpStatus != Status.active,

468:             "CdpManagerStorage: remove non-exist or non-active CDP!"

469:         );

653:         require(

654:             CdpOwnersArrayLength > 1 && sortedCdps.getSize() > 1,

655:             "CdpManager: Only one cdp in the system"

656:         );

L261, L262, L263, L264, L466, L467, L468, L469, L653, L654, L655, L656

File: packages/contracts/contracts/EBTCToken.sol

296:         require(

297:             _recipient != address(0) && _recipient != address(this),

298:             "EBTC: Cannot transfer tokens directly to the EBTC token contract or the zero address"

299:         );

300:         require(

301:             _recipient != cdpManagerAddress && _recipient != borrowerOperationsAddress,

302:             "EBTC: Cannot transfer tokens directly to the CdpManager or BorrowerOps"

303:         );

L296, L297, L298, L299, L300, L301, L302, L303

File: packages/contracts/contracts/PriceFeed.sol

79:         require(

80:             !_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) &&

81:                 !_chainlinkIsFrozen(chainlinkResponse),

82:             "PriceFeed: Chainlink must be working and current"

83:         );

L79, L80, L81, L82, L83

</details>

 

[G-39] Not using the named return variable is confusing and can waste gas

Consider changing the variable to be an unnamed one, since the variable is never assigned, nor is it returned by name. If the optimizer is not turned on, leaving the code as it is will also waste gas for the stack variable.

<details> <summary>Instances: 21</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

744:     function _getCollSharesChangeFromStEthChange(
745:         uint256 _collReceived,
746:         uint256 _requestedCollWithdrawal
747:     ) internal view returns (uint256 collSharesChange, bool isCollIncrease) {

L744

File: packages/contracts/contracts/CdpManager.sol

570:     function getSystemDebt() public view returns (uint256 entireSystemDebt) {

L570

File: packages/contracts/contracts/LiquidationLibrary.sol

544:     function _calculatePartialLiquidationSurplusAndCap(
545:         uint256 _ICR,
546:         uint256 _price,
547:         uint256 _totalDebtToBurn,
548:         uint256 _totalColToSend
549:     ) private view returns (uint256 toLiquidator, uint256 collSurplus, uint256 debtToRedistribute) {

570:     function _calculateFullLiquidationSurplusAndCap(
571:         uint256 _ICR,
572:         uint256 _price,
573:         uint256 _totalDebtToBurn,
574:         uint256 _totalColToSend
575:     ) private view returns (uint256 toLiquidator, uint256 collSurplus, uint256 debtToRedistribute) {

L544, L570

File: packages/contracts/contracts/CdpManagerStorage.sol

304:     function _getPendingRedistributedDebt(
305:         bytes32 _cdpId
306:     ) internal view returns (uint256 pendingEBTCDebtReward, uint256 _debtIndexDiff) {

719:     function getPendingRedistributedDebt(
720:         bytes32 _cdpId
721:     ) public view returns (uint256 pendingEBTCDebtReward) {

745:     function getSyncedDebtAndCollShares(
746:         bytes32 _cdpId
747:     ) public view returns (uint256 debt, uint256 coll) {

L304, L719, L745

File: packages/contracts/contracts/Governor.sol

43:     function getUsersByRole(uint8 role) external view returns (address[] memory usersWithRole) {

73:     function getRolesForUser(address user) external view returns (uint8[] memory rolesForUser) {

96:     function getRolesFromByteMap(bytes32 byteMap) public pure returns (uint8[] memory roleIds) {

131:     function getEnabledFunctionsInTarget(
132:         address _target
133:     ) public view returns (bytes4[] memory _funcs) {

146:     function getRoleName(uint8 role) external view returns (string memory roleName) {

L43, L73, L96, L131, L146

File: packages/contracts/contracts/PriceFeed.sol

582:     function _getCurrentFallbackResponse()
583:         internal
584:         view
585:         returns (FallbackResponse memory fallbackResponse)
586:     {

L582

File: packages/contracts/contracts/SortedCdps.sol

270:     function getCdpsOf(address owner) external view override returns (bytes32[] memory cdps) {

L270

File: packages/contracts/contracts/HintHelpers.sol

48:     function getRedemptionHints(
49:         uint256 _EBTCamount,
50:         uint256 _price,
51:         uint256 _maxIterations
52:     )
53:         external
54:         view
55:         returns (
56:             bytes32 firstRedemptionHint,
57:             uint256 partialRedemptionHintNICR,
58:             uint256 truncatedEBTCamount,
59:             uint256 partialRedemptionNewColl
60:         )
61:     {

L48

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

83:     function _getStorage() internal pure returns (DiamondLikeStorage storage ds) {

L83

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

67:     function getSystemCollShares() public view returns (uint256 entireSystemColl) {

75:     function _getSystemDebt() internal view returns (uint256 entireSystemDebt) {

83:     function _getTCRWithSystemDebtAndCollShares(
84:         uint256 _price
85:     ) internal view returns (uint256 TCR, uint256 _coll, uint256 _debt) {

L67, L75, L83

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

35:     function decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {

L35

File: packages/contracts/contracts/Interfaces/ICdpManagerData.sol

259:     function getSyncedDebtAndCollShares(
260:         bytes32 _cdpId
261:     ) external view returns (uint256 debt, uint256 collShares);

L259

</details>

 

[G-40] Use Solidity version 0.8.19 for gas savings

Upgrade to the latest solidity version 0.8.19 to get additional gas savings. See here for reference.

<details> <summary>Instances: 19</summary>
File: packages/contracts/contracts/ActivePool.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/BorrowerOperations.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/CdpManager.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/LiquidationLibrary.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/CdpManagerStorage.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/CollSurplusPool.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/EBTCToken.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/Governor.sol

2: pragma solidity 0.8.17;

L2

File: packages/contracts/contracts/PriceFeed.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/SortedCdps.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/HintHelpers.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/LeverageMacroBase.sol

2: pragma solidity 0.8.17;

L2

File: packages/contracts/contracts/LeverageMacroDelegateTarget.sol

2: pragma solidity 0.8.17;

L2

File: packages/contracts/contracts/LeverageMacroFactory.sol

2: pragma solidity 0.8.17;

L2

File: packages/contracts/contracts/LeverageMacroReference.sol

2: pragma solidity 0.8.17;

L2

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

2: pragma solidity 0.8.17;

L2

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

2: pragma solidity 0.8.17;

L2

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

3: pragma solidity 0.8.17;

L3

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

2: pragma solidity 0.8.17;

L2

</details>

 

[G-41] State variable read in a loop

The state variable should be cached in and read from a local variable, or accumulated in a local variable then written to storage once outside of the loop, rather than reading/updating it on every iteration of the loop, which will replace each Gwarmaccess (100 gas) with a much cheaper stack read.

<details> <summary>Instances: 8</summary>
File: packages/contracts/contracts/SortedCdps.sol

199:             _currentCdpId = data.nodes[_currentCdpId].prevId;

256:             _currentCdpId = data.nodes[_currentCdpId].prevId;

327:             _currentCdpId = data.nodes[_currentCdpId].prevId;

450:             delete data.nodes[_ids[i]];

630:             prevId = data.nodes[prevId].nextId;
631:             nextId = data.nodes[prevId].nextId;

653:             nextId = data.nodes[nextId].prevId;
654:             prevId = data.nodes[nextId].prevId;

L199, L256, L327, L450, L630, L653

</details>

 

[G-42] Structs can be packed into fewer storage slots

Each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings.

<details> <summary>Instances: 3</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

97:     struct MoveTokensParams {
98:         address user;
99:         uint256 collSharesChange;
100:         uint256 collAddUnderlying; // ONLY for isCollIncrease=true
101:         bool isCollIncrease;
102:         uint256 netDebtChange;
103:         bool isDebtIncrease;
104:     }

L97

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

99:     struct Operation {
100:         address to; // Target to call
101:         bool checkSuccess; // If false we will ignore a revert
102:         uint128 value; // How much ETH to send
103:         uint128 gas; // How much gas to send
104:         bool capGas; // true to use above "gas" setting or we send gasleft()
105:         OperationType opType; // See `OperationType`
106:         bytes data; // Calldata to send (funsig + data)
107:     }

L99

File: packages/contracts/contracts/Interfaces/IPriceFeed.sol

17:     struct ChainlinkResponse {
18:         uint80 roundEthBtcId;
19:         uint80 roundStEthEthId;
20:         uint256 answer;
21:         uint256 timestampEthBtc;
22:         uint256 timestampStEthEth;
23:         bool success;
24:     }

L17

</details>

 

[G-43] Pack structs into fewer storage slots by truncating uint256 values referencing time

By using a uint32 rather than a larger type for variables that track timestamps, one can save gas by using fewer storage slots per struct, at the expense of the protocol breaking after the year 2106 (when uint32 wraps). If this is an acceptable tradeoff, each slot saved can avoid an extra Gsset (20000 gas) for the first setting of the struct. Subsequent reads as well as writes have smaller gas savings.

<details> <summary>Instances: 2</summary>
File: packages/contracts/contracts/Interfaces/IPriceFeed.sol

17:     struct ChainlinkResponse {
18:         uint80 roundEthBtcId;
19:         uint80 roundStEthEthId;
20:         uint256 answer;
21:         uint256 timestampEthBtc;
22:         uint256 timestampStEthEth;
23:         bool success;
24:     }

26:     struct FallbackResponse {
27:         uint256 answer;
28:         uint256 timestamp;
29:         bool success;
30:     }

L17, L26

</details>

 

[G-44] Remove superfluous event fields

block.number and block.timestamp are included in event emission automatically and so emitting them manually wastes gas.

<details> <summary>Instances: 3</summary>
File: packages/contracts/contracts/CdpManager.sol

698:             emit LastRedemptionTimestampUpdated(block.timestamp);

L698

File: packages/contracts/contracts/CdpManagerStorage.sol

542:             emit StEthIndexUpdated(_oldIndex, _newIndex, block.timestamp);

L542

File: packages/contracts/contracts/PriceFeed.sol

381:                 emit UnhealthyFallbackCaller(_fallbackCaller, block.timestamp);

L381

</details>

 

[G-45] Use != 0 instead of > 0 for uints

<details> <summary>Instances: 50</summary>
File: packages/contracts/contracts/ActivePool.sol

267:         require(amount > 0, "ActivePool: 0 Amount");

L267

File: packages/contracts/contracts/BorrowerOperations.sol

393:         if (!_isDebtIncrease && _debtChange > 0) {

463:         require(vars.netStEthBalance > 0, "BorrowerOperations: zero collateral for openCdp()!");

835:         require(_debtChange > 0, "BorrowerOperations: Debt increase requires non-zero debtChange");

936:         require(_debt > 0, "BorrowerOperations: Debt must be non-zero");

1083:         require(amount > 0, "BorrowerOperations: 0 Amount");

L393, L463, L835, L936, L1083

File: packages/contracts/contracts/CdpManager.sol

388:         while (
389:             currentBorrower != address(0) && totals.remainingDebtToRedeem > 0 && _maxIterations > 0
390:         ) {

431:         require(totals.collSharesDrawn > 0, "CdpManager: Unable to redeem any amount");

497:         while (_cnt > 0 && _id != bytes32(0)) {

626:         require(newBaseRate > 0, "CdpManager: new baseRate is zero!"); // Base rate is always non-zero after redemption

754:         require(_amount > 0, "CdpManager: Amount must be greater than zero");

L388, L431, L497, L626, L754

File: packages/contracts/contracts/LiquidationLibrary.sol

195:             if (_outputState.totalSurplusCollShares > 0) {

261:             if (_collSurplus > 0) {

266:             if (_debtToRedistribute > 0) {

336:             if (_collSurplus > 0) {

342:             if (_debtToRedistribute > 0) {

525:         if (totalDebtToRedistribute > 0) {

710:         require(totals.totalDebtInSequence > 0, "LiquidationLibrary: nothing to liquidate");

713:         if (totals.totalCollSurplus > 0) {

L195, L261, L266, L336, L342, L525, L710, L713

File: packages/contracts/contracts/CdpManagerStorage.sol

315:         if (_debtIndexDiff > 0) {

362:         if (_feeSplitDistributed > 0 || _debtIndexDelta > 0) {

369:             if (_feeSplitDistributed > 0) {

380:             if (_debtIndexDelta > 0) {

453:             require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");

512:         if (_newIndex > _oldIndex && totalStakes > 0) {

765:         if (_newIndex > _oldIndex && totalStakes > 0) {

796:         if (_cdpPerUnitIdx != _systemStEthFeePerUnitIndex && _cdpPerUnitIdx > 0) {

829:         if (pendingDebtRedistributed > 0) {

879:         if (_feeTaken > 0) {

L315, L362, L369, L380, L453, L512, L765, L796, L829, L879

File: packages/contracts/contracts/CollSurplusPool.sol

92:         require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");

L92

File: packages/contracts/contracts/Governor.sol

53:         if (count > 0) {

81:         if (count > 0) {

104:         if (count > 0) {

135:         if (_sigs.length > 0) {

L53, L81, L104, L135

File: packages/contracts/contracts/PriceFeed.sol

457:         uint256 percentDeviation = maxPrice > 0
458:             ? ((maxPrice - minPrice) * EbtcMath.DECIMAL_PRECISION) / maxPrice
459:             : 0;

488:         return
489:             _fallbackResponse.timestamp > 0 &&
490:             _responseTimeout(_fallbackResponse.timestamp, fallbackCaller.fallbackTimeout());

L457, L488

File: packages/contracts/contracts/SortedCdps.sol

202:             if (maxNodes > 0 && i >= maxNodes) {

259:             if (maxNodes > 0 && i >= maxNodes) {

274:         if (_ownedCount > 0) {

330:             if (maxNodes > 0 && i >= maxNodes) {

371:         require(_NICR > 0, "SortedCdps: NICR must be positive");

508:         require(_newNICR > 0, "SortedCdps: NICR must be positive");

L202, L259, L274, L330, L371, L508

File: packages/contracts/contracts/HintHelpers.sol

84:             while (
85:                 vars.currentCdpUser != address(0) &&
86:                 vars.remainingEbtcToRedeem > 0 &&
87:                 _maxIterations-- > 0
88:             ) {

L84

File: packages/contracts/contracts/LeverageMacroBase.sol

128:         if (operation.amountToTransferIn > 0) {

223:         if (ebtcBal > 0) {

227:         if (collateralBal > 0) {

293:         if (beforeSwapsLength > 0) {

307:         if (afterSwapsLength > 0) {

L128, L223, L227, L293, L307

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

93:         if (_debt > 0) {

109:         if (_debt > 0) {

L93, L109

</details>

 

[G-46] Usage of uint smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

Consider using a larger size then downcasting where needed.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html

<details> <summary>Instances: 6</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

21:     uint128 public constant UNSET_TIMESTAMP = type(uint128).max;

22:     uint128 public constant MINIMUM_GRACE_PERIOD = 15 minutes;

24:     uint128 public lastGracePeriodStartTimestamp = UNSET_TIMESTAMP; // use max to signify

25:     uint128 public recoveryModeGracePeriodDuration = MINIMUM_GRACE_PERIOD;

L21, L22, L24, L25

File: packages/contracts/contracts/Governor.sol

30:     mapping(uint8 => string) internal roleNames;

L30

File: packages/contracts/contracts/Dependencies/ERC3156FlashLender.sol

14:     uint16 public feeBps = 3; // may be subject to future adjustments through protocol governance

L14

</details>

 

[G-47] Remove unused state variables

Reduces bytecode size and reduces deployment cost.

<details> <summary>Instances: 9</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

142:     uint256 public redemptionFeeFloor = MIN_REDEMPTION_FEE_FLOOR;
143:     bool public redemptionsPaused;

148:     uint256 public minuteDecayFactor = 999037758833783000;

159:     uint256 public beta = 2;

161:     uint256 public baseRate;

166:     uint256 public lastRedemptionTimestamp;

193:     uint256 public lastEBTCDebtErrorRedistribution;

L142, L148, L159, L161, L166, L193

File: packages/contracts/contracts/Governor.sol

17:     bytes32 NO_ROLES = bytes32(0);

L17

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

30:     EnumerableSet.Bytes32Set internal enabledFunctionSigsPublic;

L30

</details>

 

[G-48] Avoid updating storage when the value hasn't changed

If the old value is equal to the new value, not re-storing the value will avoid a Gsreset (2900 gas), potentially at the expense of a Gcoldsload (2100 gas) or a Gwarmaccess (100 gas).

<details> <summary>Instances: 6</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

111:     function setGracePeriod(uint128 _gracePeriod) external requiresAuth {
112:         require(
113:             _gracePeriod >= MINIMUM_GRACE_PERIOD,
114:             "CdpManager: Grace period below minimum duration"
115:         );

117:         syncGlobalAccountingAndGracePeriod();
118:         recoveryModeGracePeriodDuration = _gracePeriod;
119:         emit GracePeriodDurationSet(_gracePeriod);
120:     }

574:     function _takeSplitAndUpdateFeePerUnit(
575:         uint256 _feeTaken,
576:         uint256 _newPerUnit,
577:         uint256 _newErrorPerUnit
578:     ) internal {
579:         uint256 _oldPerUnit = systemStEthFeePerUnitIndex;

581:         systemStEthFeePerUnitIndex = _newPerUnit;
582:         systemStEthFeePerUnitIndexError = _newErrorPerUnit;

584:         require(activePool.getSystemCollShares() > _feeTaken, "CDPManager: fee split is too big");
585:         activePool.allocateSystemCollSharesToFeeRecipient(_feeTaken);

587:         emit CollateralFeePerUnitUpdated(_oldPerUnit, _newPerUnit, _feeTaken);
588:     }

L111, L117, L574, L581, L584, L587

File: packages/contracts/contracts/PriceFeed.sol

546:     function _changeStatus(Status _status) internal {
547:         status = _status;
548:         emit PriceFeedStatusChanged(_status);
549:     }

553:     function _storePrice(uint256 _currentPrice) internal {
554:         lastGoodPrice = _currentPrice;
555:         emit LastGoodPriceUpdated(_currentPrice);
556:     }

L546, L553

File: packages/contracts/contracts/Dependencies/Auth.sol

42:     function setAuthority(Authority newAuthority) public virtual {
43:         // We check if the caller is the owner first because we want to ensure they can
44:         // always swap out the authority even if it's reverting or using up a lot of gas.
45:         require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));

47:         authority = newAuthority;

49:         emit AuthorityUpdated(msg.sender, newAuthority);
50:     }

52:     function transferOwnership(address newOwner) public virtual requiresAuth {
53:         owner = newOwner;

55:         emit OwnershipTransferred(msg.sender, newOwner);
56:     }

L42, L47, L49, L52, L55

</details>

 

[G-49] Use assembly for integer zero checks

Using assembly to check for zero can save gas by allowing more direct access to the evm and reducing some of the overhead associated with high-level operations in solidity.

<details> <summary>Instances: 26</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

807:         require(
808:             _stEthBalanceIncrease == 0 || _stEthBalanceDecrease == 0,
809:             "BorrowerOperations: Cannot add and withdraw collateral in same operation"
810:         );

831:         require(status == 0, "BorrowerOperations: Cdp is active or has been previously closed");

846:         require(
847:             _stEthBalanceDecrease == 0,
848:             "BorrowerOperations: Collateral withdrawal not permitted during Recovery Mode"
849:         );

L807, L831, L846

File: packages/contracts/contracts/CdpManager.sol

159:         if (newDebt == 0) {

377:         if (_maxIterations == 0) {

L159, L377

File: packages/contracts/contracts/LiquidationLibrary.sol

144:         if (_liqState.partialAmount == 0) {

157:             if (
158:                 liquidationValues.totalCollToSendToLiquidator == 0 &&
159:                 liquidationValues.debtToBurn == 0
160:             ) {

417:         if (newColl == 0) {

863:         if (_debt == 0) {

L144, L157, L417, L863

File: packages/contracts/contracts/CdpManagerStorage.sol

443:         if (totalCollateralSnapshot == 0) {

623:         if (
624:             _cdpStEthFeePerUnitIndex == 0 ||
625:             _cdpCol == 0 ||
626:             _cdpStEthFeePerUnitIndex == _systemStEthFeePerUnitIndex
627:         ) {

L443, L623

File: packages/contracts/contracts/PriceFeed.sol

419:         if (
420:             _response.timestampEthBtc == 0 ||
421:             _response.timestampEthBtc > block.timestamp ||
422:             _response.timestampStEthEth == 0 ||
423:             _response.timestampStEthEth > block.timestamp
424:         ) {

471:         if (_response.timestamp == 0 || _response.timestamp > block.timestamp) {

475:         if (_response.answer == 0) {

532:         if (minPrice == 0) return false;

695:         if (_currentRoundEthBtcId == 0 || _currentRoundStEthEthId == 0) {

777:         if (_roundId == 0) return false;

L419, L471, L475, L532, L695, L777

File: packages/contracts/contracts/SortedCdps.sol

89:         if (_size == 0) {

305:         if (maxArraySize == 0) {

352:         require(cdpManager.getCdpStatus(_id) == 0, "SortedCdps: new id is NOT nonExistent!");

537:         return data.size == 0;

L89, L305, L352, L537

File: packages/contracts/contracts/HintHelpers.sol

78:         if (_maxIterations == 0) {

171:         if (arrayLength == 0) {

L78, L171

File: packages/contracts/contracts/Dependencies/EbtcMath.sol

64:         if (_minutes == 0) {

74:             if (n % 2 == 0) {

L64, L74

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

124:             if (enabledFunctionSigsByTarget[target].length() == 0) {

L124

</details>

 

[G-50] Use custom errors

Use of custom errors reduces both deployment and runtime gas costs, and allows passing of dynamic information.

<details> <summary>Instances: 163</summary>
File: packages/contracts/contracts/ActivePool.sol

104:         require(cachedSystemCollShares >= _shares, "!ActivePoolBal");

137:         require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");

162:         require(cachedSystemCollShares >= _shares, "ActivePool: Insufficient collateral shares");

220:         require(
221:             msg.sender == borrowerOperationsAddress,
222:             "ActivePool: Caller is not BorrowerOperations"
223:         );

228:         require(
229:             msg.sender == borrowerOperationsAddress || msg.sender == cdpManagerAddress,
230:             "ActivePool: Caller is neither BorrowerOperations nor CdpManager"
231:         );

236:         require(msg.sender == cdpManagerAddress, "ActivePool: Caller is not CdpManager");

267:         require(amount > 0, "ActivePool: 0 Amount");

269:         require(amount <= maxFlashLoan(token), "ActivePool: Too much");

277:         require(
278:             receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
279:             "ActivePool: IERC3156: Callback failed"
280:         );

294:         require(
295:             collateral.balanceOf(address(this)) >= collateral.getPooledEthByShares(systemCollShares),
296:             "ActivePool: Must repay Balance"
297:         );
298:         require(
299:             collateral.sharesOf(address(this)) >= systemCollShares,
300:             "ActivePool: Must repay Share"
301:         );
302:         require(
303:             collateral.getPooledEthByShares(DECIMAL_PRECISION) == oldRate,
304:             "ActivePool: Should keep same collateral share rate"
305:         );

318:         require(token == address(collateral), "ActivePool: collateral Only");
319:         require(!flashLoansPaused, "ActivePool: Flash Loans Paused");

350:         require(
351:             cachedFeeRecipientCollShares >= _shares,
352:             "ActivePool: Insufficient fee recipient coll"
353:         );

374:         require(token != address(collateral), "ActivePool: Cannot Sweep Collateral");

377:         require(amount <= balance, "ActivePool: Attempt to sweep more than balance");

393:         require(
394:             _feeRecipientAddress != address(0),
395:             "ActivePool: Cannot set fee recipient to zero address"
396:         );

407:         require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");

L104, L137, L162, L220, L228, L236, L267, L269, L277, L294, L318, L350, L374, L377, L393, L407

File: packages/contracts/contracts/BorrowerOperations.sol

145:         require(locked == OPEN, "BorrowerOperations: Reentrancy in nonReentrant call");
146:         require(
147:             ReentrancyGuard(address(cdpManager)).locked() == OPEN,
148:             "CdpManager: Reentrancy in nonReentrant call"
149:         );

368:         require(
369:             _stEthBalanceDecrease <= _cdpStEthBalance,
370:             "BorrowerOperations: Cannot withdraw greater stEthBalance than the value in Cdp"
371:         );

463:         require(vars.netStEthBalance > 0, "BorrowerOperations: zero collateral for openCdp()!");

541:         require(
542:             vars.netStEthBalance + LIQUIDATOR_REWARD == _stEthBalance,
543:             "BorrowerOperations: deposited collateral mismatch!"
544:         );

715:         require(_deadline >= block.timestamp, "BorrowerOperations: Position manager permit expired");

734:         require(
735:             recoveredAddress != address(0) && recoveredAddress == _borrower,
736:             "BorrowerOperations: Invalid signature"
737:         );

807:         require(
808:             _stEthBalanceIncrease == 0 || _stEthBalanceDecrease == 0,
809:             "BorrowerOperations: Cannot add and withdraw collateral in same operation"
810:         );

818:         require(
819:             _stEthBalanceIncrease != 0 || _stEthBalanceDecrease != 0 || _debtChange != 0,
820:             "BorrowerOperations: There must be either a collateral change or a debt change"
821:         );

826:         require(status == 1, "BorrowerOperations: Cdp does not exist or is closed");

831:         require(status == 0, "BorrowerOperations: Cdp is active or has been previously closed");

835:         require(_debtChange > 0, "BorrowerOperations: Debt increase requires non-zero debtChange");

839:         require(
840:             !_checkRecoveryModeForTCR(_tcr),
841:             "BorrowerOperations: Operation not permitted during Recovery Mode"
842:         );

846:         require(
847:             _stEthBalanceDecrease == 0,
848:             "BorrowerOperations: Collateral withdrawal not permitted during Recovery Mode"
849:         );

911:         require(
912:             _newICR >= MCR,
913:             "BorrowerOperations: An operation that would result in ICR < MCR is not permitted"
914:         );

918:         require(_newICR >= CCR, "BorrowerOperations: Operation must leave cdp with ICR >= CCR");

922:         require(
923:             _newICR >= _oldICR,
924:             "BorrowerOperations: Cannot decrease your Cdp's ICR in Recovery Mode"
925:         );

929:         require(
930:             _newTCR >= CCR,
931:             "BorrowerOperations: An operation that would result in TCR < CCR is not permitted"
932:         );

936:         require(_debt > 0, "BorrowerOperations: Debt must be non-zero");

940:         require(
941:             _stEthBalance >= MIN_NET_STETH_BALANCE,
942:             "BorrowerOperations: Cdp's net stEth balance must not fall below minimum"
943:         );

947:         require(
948:             _debtRepayment <= _currentDebt,
949:             "BorrowerOperations: Amount repaid must not be larger than the Cdp's debt"
950:         );

957:         require(
958:             ebtcToken.balanceOf(_account) >= _debtRepayment,
959:             "BorrowerOperations: Caller doesnt have enough eBTC to make repayment"
960:         );

970:         require(
971:             _approval != PositionManagerApproval.None,
972:             "BorrowerOperations: Only borrower account or approved position manager can OpenCdp on borrower's behalf"
973:         );

1083:         require(amount > 0, "BorrowerOperations: 0 Amount");

1085:         require(amount <= maxFlashLoan(token), "BorrowerOperations: Too much");

1091:         require(
1092:             receiver.onFlashLoan(msg.sender, token, amount, fee, data) == FLASH_SUCCESS_VALUE,
1093:             "IERC3156: Callback failed"
1094:         );

1115:         require(token == address(ebtcToken), "BorrowerOperations: EBTC Only");
1116:         require(!flashLoansPaused, "BorrowerOperations: Flash Loans Paused");

1141:         require(
1142:             _feeRecipientAddress != address(0),
1143:             "BorrowerOperations: Cannot set feeRecipient to zero address"
1144:         );

1155:         require(_newFee <= MAX_FEE_BPS, "ERC3156FlashLender: _newFee should <= MAX_FEE_BPS");

L145, L368, L463, L541, L715, L734, L807, L818, L826, L831, L835, L839, L846, L911, L918, L922, L929, L936, L940, L947, L957, L970, L1083, L1085, L1091, L1115, L1141, L1155

File: packages/contracts/contracts/CdpManager.sol

332:         require(redemptionsPaused == false, "CdpManager: Redemptions Paused");

431:         require(totals.collSharesDrawn > 0, "CdpManager: Unable to redeem any amount");

502:         require(_toRemoveIds[0] == _start, "CdpManager: batchRemoveSortedCdpIds check start error");
503:         require(
504:             _toRemoveIds[_total - 1] == _end,
505:             "CdpManager: batchRemoveSortedCdpIds check end error"
506:         );

626:         require(newBaseRate > 0, "CdpManager: new baseRate is zero!"); // Base rate is always non-zero after redemption

672:         require(redemptionFee < _ETHDrawn, "CdpManager: Fee would eat up all returned collateral");

678:         require(decayedBaseRate <= DECIMAL_PRECISION, "CdpManager: baseRate too large!"); // The baseRate can decay to 0

743:         require(
744:             callerBalance >= _amount,
745:             "CdpManager: Requested redemption amount must be <= user's EBTC token balance"
746:         );
747:         require(
748:             callerBalance <= _totalSupply,
749:             "CdpManager: redeemer's EBTC balance exceeds total supply!"
750:         );

754:         require(_amount > 0, "CdpManager: Amount must be greater than zero");

758:         require(_TCR >= MCR, "CdpManager: Cannot redeem when TCR < MCR");

762:         require(
763:             _maxFeePercentage >= redemptionFeeFloor && _maxFeePercentage <= DECIMAL_PRECISION,
764:             "Max fee percentage must be between redemption fee floor and 100%"
765:         );

774:         require(
775:             _stakingRewardSplit <= MAX_REWARD_SPLIT,
776:             "CDPManager: new staking reward split exceeds max"
777:         );

789:         require(
790:             _redemptionFeeFloor >= MIN_REDEMPTION_FEE_FLOOR,
791:             "CDPManager: new redemption fee floor is lower than minimum"
792:         );
793:         require(
794:             _redemptionFeeFloor <= DECIMAL_PRECISION,
795:             "CDPManager: new redemption fee floor is higher than maximum"
796:         );

808:         require(
809:             _minuteDecayFactor >= MIN_MINUTE_DECAY_FACTOR,
810:             "CDPManager: new minute decay factor out of range"
811:         );
812:         require(
813:             _minuteDecayFactor <= MAX_MINUTE_DECAY_FACTOR,
814:             "CDPManager: new minute decay factor out of range"
815:         );

L332, L431, L502, L626, L672, L678, L743, L754, L758, L762, L774, L789, L808

File: packages/contracts/contracts/LiquidationLibrary.sol

56:         require(_partialAmount != 0, "LiquidationLibrary: use `liquidate` for 100%");

82:             require(
83:                 _checkICRAgainstLiqThreshold(_ICR, _TCR),
84:                 "LiquidationLibrary: ICR is not below liquidation threshold in current mode"
85:             );

89:             require(
90:                 cachedLastGracePeriodStartTimestamp != UNSET_TIMESTAMP,
91:                 "LiquidationLibrary: Recovery Mode grace period not started"
92:             );
93:             require(
94:                 block.timestamp >
95:                     cachedLastGracePeriodStartTimestamp + recoveryModeGracePeriodDuration,
96:                 "LiquidationLibrary: Recovery mode grace period still in effect"
97:             );

477:             require(
478:                 getCachedICR(_cdpId, _partialState.price) >= _partialState.ICR,
479:                 "LiquidationLibrary: !_newICR>=_ICR"
480:             );

680:         require(
681:             _cdpArray.length != 0,
682:             "LiquidationLibrary: Calldata address array must not be empty"
683:         );

710:         require(totals.totalDebtInSequence > 0, "LiquidationLibrary: nothing to liquidate");

901:         require(
902:             (_partialDebt + _convertDebtDenominationToBtc(MIN_NET_STETH_BALANCE, _price)) <=
903:                 _entireDebt,
904:             "LiquidationLibrary: Partial debt liquidated must be less than total debt"
905:         );

909:         require(
910:             _entireColl >= MIN_NET_STETH_BALANCE,
911:             "LiquidationLibrary: Coll remaining in partially liquidated CDP must be >= minimum"
912:         );

L56, L82, L89, L477, L680, L710, L901, L909

File: packages/contracts/contracts/CdpManagerStorage.sol

112:         require(
113:             _gracePeriod >= MINIMUM_GRACE_PERIOD,
114:             "CdpManager: Grace period below minimum duration"
115:         );

242:         require(locked == OPEN, "CdpManager: Reentrancy in nonReentrant call");
243:         require(
244:             ReentrancyGuard(borrowerOperationsAddress).locked() == OPEN,
245:             "BorrowerOperations: Reentrancy in nonReentrant call"
246:         );

261:         require(
262:             closedStatus != Status.nonExistent && closedStatus != Status.active,
263:             "CdpManagerStorage: close non-exist or non-active CDP!"
264:         );

453:             require(totalStakesSnapshot > 0, "CdpManagerStorage: zero totalStakesSnapshot!");

466:         require(
467:             cdpStatus != Status.nonExistent && cdpStatus != Status.active,
468:             "CdpManagerStorage: remove non-exist or non-active CDP!"
469:         );

475:         require(index <= idxLast, "CdpManagerStorage: CDP indexing overflow!");

556:         require(_newIndex > _prevIndex, "CDPManager: only take fee with bigger new index");

584:         require(activePool.getSystemCollShares() > _feeTaken, "CDPManager: fee split is too big");

649:         require(Cdps[_cdpId].status == Status.active, "CdpManager: Cdp does not exist or is closed");

653:         require(
654:             CdpOwnersArrayLength > 1 && sortedCdps.getSize() > 1,
655:             "CdpManager: Only one cdp in the system"
656:         );

660:         require(
661:             msg.sender == borrowerOperationsAddress,
662:             "CdpManager: Caller is not the BorrowerOperations contract"
663:         );

L112, L242, L261, L453, L466, L475, L556, L584, L649, L653, L660

File: packages/contracts/contracts/CollSurplusPool.sol

92:         require(claimableColl > 0, "CollSurplusPool: No collateral available to claim");

99:         require(cachedTotalSurplusCollShares >= claimableColl, "!CollSurplusPoolBal");

113:         require(
114:             msg.sender == borrowerOperationsAddress,
115:             "CollSurplusPool: Caller is not Borrower Operations"
116:         );

120:         require(msg.sender == cdpManagerAddress, "CollSurplusPool: Caller is not CdpManager");

124:         require(msg.sender == activePoolAddress, "CollSurplusPool: Caller is not Active Pool");

143:         require(token != address(collateral), "CollSurplusPool: Cannot Sweep Collateral");

146:         require(amount <= balance, "CollSurplusPool: Attempt to sweep more than balance");

L92, L99, L113, L120, L124, L143, L146

File: packages/contracts/contracts/EBTCToken.sol

144:             require(cachedAllowance >= amount, "ERC20: transfer amount exceeds allowance");

165:         require(cachedAllowances >= subtractedValue, "ERC20: decreased allowance below zero");

208:         require(deadline >= block.timestamp, "EBTC: expired deadline");

219:         require(recoveredAddress == owner, "EBTC: invalid signature");

247:         require(sender != address(0), "EBTCToken: zero sender!");
248:         require(recipient != address(0), "EBTCToken: zero recipient!");

251:         require(cachedSenderBalances >= amount, "ERC20: transfer amount exceeds balance");

263:         require(account != address(0), "EBTCToken: mint to zero recipient!");

271:         require(account != address(0), "EBTCToken: burn from zero account!");

274:         require(cachedBalance >= amount, "ERC20: burn amount exceeds balance");

286:         require(owner != address(0), "EBTCToken: zero approve owner!");
287:         require(spender != address(0), "EBTCToken: zero approve spender!");

296:         require(
297:             _recipient != address(0) && _recipient != address(this),
298:             "EBTC: Cannot transfer tokens directly to the EBTC token contract or the zero address"
299:         );
300:         require(
301:             _recipient != cdpManagerAddress && _recipient != borrowerOperationsAddress,
302:             "EBTC: Cannot transfer tokens directly to the CdpManager or BorrowerOps"
303:         );

307:         require(
308:             msg.sender == borrowerOperationsAddress,
309:             "EBTCToken: Caller is not BorrowerOperations"
310:         );

315:         require(
316:             msg.sender == borrowerOperationsAddress ||
317:                 msg.sender == cdpManagerAddress ||
318:                 isAuthorized(msg.sender, msg.sig),
319:             "EBTC: Caller is neither BorrowerOperations nor CdpManager nor authorized"
320:         );

324:         require(msg.sender == cdpManagerAddress, "EBTC: Caller is not CdpManager");

L144, L165, L208, L219, L247, L251, L263, L271, L274, L286, L296, L307, L315, L324

File: packages/contracts/contracts/PriceFeed.sol

79:         require(
80:             !_chainlinkIsBroken(chainlinkResponse, prevChainlinkResponse) &&
81:                 !_chainlinkIsFrozen(chainlinkResponse),
82:             "PriceFeed: Chainlink must be working and current"
83:         );

L79

File: packages/contracts/contracts/SortedCdps.sol

352:         require(cdpManager.getCdpStatus(_id) == 0, "SortedCdps: new id is NOT nonExistent!");

365:         require(!isFull(), "SortedCdps: List is full");

367:         require(!contains(_id), "SortedCdps: List already contains the node");

369:         require(_id != dummyId, "SortedCdps: Id cannot be zero");

371:         require(_NICR > 0, "SortedCdps: NICR must be positive");

422:         require(_len > 1, "SortedCdps: batchRemove() only apply to multiple cdpIds!");

427:         require(
428:             _firstPrev != dummyId || _lastNext != dummyId,
429:             "SortedCdps: batchRemove() leave ZERO node left!"
430:         );

433:             require(contains(_ids[i]), "SortedCdps: List does not contain the id");

458:         require(contains(_id), "SortedCdps: List does not contain the id");

506:         require(contains(_id), "SortedCdps: List does not contain the id");

508:         require(_newNICR > 0, "SortedCdps: NICR must be positive");

715:         require(msg.sender == address(cdpManager), "SortedCdps: Caller is not the CdpManager");

720:         require(
721:             msg.sender == borrowerOperationsAddress || msg.sender == address(cdpManager),
722:             "SortedCdps: Caller is neither BO nor CdpM"
723:         );

L352, L365, L367, L369, L371, L422, L427, L433, L458, L506, L508, L715, L720

File: packages/contracts/contracts/LeverageMacroBase.sol

40:         revert("Must be overridden");

45:         require(owner() == msg.sender, "Must be owner");

179:             require(
180:                 cdpInfo.status == checkParams.expectedStatus,
181:                 "!LeverageMacroReference: openCDP status check"
182:             );

191:             require(
192:                 cdpInfo.status == checkParams.expectedStatus,
193:                 "!LeverageMacroReference: adjustCDP status check"
194:             );

201:             require(
202:                 cdpInfo.status == checkParams.expectedStatus,
203:                 "!LeverageMacroReference: closeCDP status check"
204:             );

249:             require(check.value >= valueToCheck, "!LeverageMacroReference: gte post check");

251:             require(check.value <= valueToCheck, "!LeverageMacroReference: let post check");

253:             require(check.value == valueToCheck, "!LeverageMacroReference: equal post check");

255:             revert("Operator not found");

352:         require(initiator == address(this), "LeverageMacroReference: wrong initiator for flashloan");

356:             require(
357:                 msg.sender == address(borrowerOperations),
358:                 "LeverageMacroReference: wrong lender for eBTC flashloan"
359:             );

362:             require(
363:                 msg.sender == address(activePool),
364:                 "LeverageMacroReference: wrong lender for stETH flashloan"
365:             );

421:         require(success, "Call has failed");

440:                 require(
441:                     IERC20(swapChecks[i].tokenToCheck).balanceOf(address(this)) >
442:                         swapChecks[i].expectedMinOut,
443:                     "LeverageMacroReference: swap check failure!"
444:                 );

452:         require(addy != address(borrowerOperations));
453:         require(addy != address(sortedCdps));
454:         require(addy != address(activePool));
455:         require(addy != address(cdpManager));
456:         require(addy != address(this)); // If it could call this it could fake the forwarded caller

L40, L45, L179, L191, L201, L249, L251, L253, L255, L352, L356, L362, L421, L440, L452

File: packages/contracts/contracts/SimplifiedDiamondLike.sol

52:         require(msg.sender == owner);

56:         require(sig != 0x94b24d09);

67:         require(msg.sender == owner);

77:         require(msg.sender == address(this)); // Must call this via `execute` to explicitly set the flag

111:         require(msg.sender == owner, "Must be owner");

154:             require(success);

179:             require(ds.settings.callbackEnabledForCall, "Only Enabled Callbacks");

187:         require(facet != address(0), "Diamond: Function does not exist");

195:                 revert(0, returndatasize())

L52, L56, L67, L77, L111, L154, L179, L187, L195

File: packages/contracts/contracts/Dependencies/Auth.sol

27:         require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");

45:         require(msg.sender == owner || authority.canCall(msg.sender, address(this), msg.sig));

L27, L45

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

17:         require(isAuthorized(msg.sender, msg.sig), "Auth: UNAUTHORIZED");

41:         require(_authority.canCall(msg.sender, address(this), msg.sig));

56:         require(address(_authority) == address(0), "Auth: authority is non-zero");
57:         require(!_authorityInitialized, "Auth: authority already initialized");

L17, L41, L56

File: packages/contracts/contracts/Dependencies/EbtcBase.sol

109:         require(feePercentage <= _maxFeePercentage, "Fee exceeded provided maximum");

L109

File: packages/contracts/contracts/Dependencies/ReentrancyGuard.sol

14:         require(locked == OPEN, "ReentrancyGuard: Reentrancy in nonReentrant call");

L14

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

90:         require(
91:             capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
92:             "RolesAuthority: Capability Burned"
93:         );

134:         require(
135:             capabilityFlag[target][functionSig] != CapabilityFlag.Burned,
136:             "RolesAuthority: Capability Burned"
137:         );

L90, L134

</details>

 

[G-51] Using storage instead of memory for state variables saves gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declaring the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incurring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct.

<details> <summary>Instances: 7</summary>
File: packages/contracts/contracts/BorrowerOperations.sol

427:             MoveTokensParams memory _varMvTokens = MoveTokensParams(
428:                 msg.sender,
429:                 vars.collSharesChange,
430:                 (vars.isCollIncrease ? _stEthBalanceIncrease : 0),
431:                 vars.isCollIncrease,
432:                 _debtChange,
433:                 _isDebtIncrease
434:             );

L427

File: packages/contracts/contracts/SortedCdps.sol

522:             Node memory _node = data.nodes[_id];

L522

File: packages/contracts/contracts/LeverageMacroBase.sol

339:         LeverageMacroOperation memory leverageMacroData = abi.decode(data, (LeverageMacroOperation));

374:         LeverageMacroOperation memory operation = decodeFLData(data);

461:         OpenCdpOperation memory flData = abi.decode(data, (OpenCdpOperation));

475:         CloseCdpOperation memory flData = abi.decode(data, (CloseCdpOperation));

483:         AdjustCdpOperation memory flData = abi.decode(data, (AdjustCdpOperation));

L339, L374, L461, L475, L483

</details>

 

[G-52] Use uint(1)/uint(2) instead of true/false

If you don't use boolean for storage you will avoid Gwarmaccess 100 gas. In addition, state changes of boolean from true to false can cost up to ~20000 gas rather than uint(2) to uint(1) that would cost significantly less. This implementation can be found in OpenZeppelin's ReentrancyGuard implementation.

<details> <summary>Instances: 4</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

143:     bool public redemptionsPaused;

L143

File: packages/contracts/contracts/LeverageMacroBase.sol

35:     bool internal immutable willSweep;

L35

File: packages/contracts/contracts/Dependencies/AuthNoOwner.sol

14:     bool private _authorityInitialized;

L14

File: packages/contracts/contracts/Dependencies/ERC3156FlashLender.sol

15:     bool public flashLoansPaused;

L15

</details>

 

[G-53] ++i costs less gas than i++

True for --i/i-- as well, and is especially important in for loops. Saves 5 gas per iteration.

<details> <summary>Instances: 12</summary>
File: packages/contracts/contracts/CdpManager.sol

429:             _maxIterations--;

L429

File: packages/contracts/contracts/Governor.sol

46:         for (uint256 i = 0; i < users.length(); i++) {

57:             for (uint256 i = 0; i < _usrs.length; i++) {

62:                     j++;

76:         for (uint8 i = 0; i < type(uint8).max; i++) {

84:             for (uint8 i = 0; i < type(uint8).max; i++) {

87:                     j++;

98:         for (uint8 i = 0; i < type(uint8).max; i++) {

107:             for (uint8 i = 0; i < type(uint8).max; i++) {

111:                     j++;

122:         for (uint8 i = 0; i < roleIds.length; i++) {

L46, L57, L62, L76, L84, L87, L98, L107, L111, L122

File: packages/contracts/contracts/HintHelpers.sol

195:             i++;

L195

</details>

 

[G-54] State variables only assigned to in the constructor should be immutable

Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

While strings are not value types, and therefore cannot be immutable/constant if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract with virtual functions for the string accessors, and having a child contract override the functions with the hard-coded implementation-specific values.

<details> <summary>Instances: 2</summary>
File: packages/contracts/contracts/CdpManager.sol

59:         systemStEthFeePerUnitIndex = DECIMAL_PRECISION;

L59

File: packages/contracts/contracts/SortedCdps.sol

90:             _size = type(uint256).max;

L90

</details>

 

[G-55] State variables that do not change should be constant or immutable

Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values.

See Solidity docs for more info.

<details> <summary>Instances: 17</summary>
File: packages/contracts/contracts/CdpManagerStorage.sol

142:     uint256 public redemptionFeeFloor = MIN_REDEMPTION_FEE_FLOOR;
143:     bool public redemptionsPaused;

148:     uint256 public minuteDecayFactor = 999037758833783000;

159:     uint256 public beta = 2;

161:     uint256 public baseRate;

163:     uint256 public stakingRewardSplit;

166:     uint256 public lastRedemptionTimestamp;

193:     uint256 public lastEBTCDebtErrorRedistribution;

L142, L148, L159, L161, L163, L166, L193

File: packages/contracts/contracts/Governor.sol

17:     bytes32 NO_ROLES = bytes32(0);

L17

File: packages/contracts/contracts/SortedCdps.sol

77:     Data public data;

79:     uint256 public nextCdpNonce;

L77, L79

File: packages/contracts/contracts/Dependencies/ERC3156FlashLender.sol

14:     uint16 public feeBps = 3; // may be subject to future adjustments through protocol governance
15:     bool public flashLoansPaused;

L14

File: packages/contracts/contracts/Dependencies/ReentrancyGuard.sol

11:     uint256 public locked = OPEN;

L11

File: packages/contracts/contracts/Dependencies/RolesAuthority.sol

26:     EnumerableSet.AddressSet internal users;
27:     EnumerableSet.AddressSet internal targets;

30:     EnumerableSet.Bytes32Set internal enabledFunctionSigsPublic;

L26, L30

</details>

 

[G-56] Use via-ir for deployment

Enable on the command line using --via-ir or with the option {"viaIR": true} for more powerful optimization passes that span across functions. See here for more info.

 

#0 - c4-pre-sort

2023-11-17T14:41:59Z

bytes032 marked the issue as sufficient quality report

#1 - c4-judge

2023-11-28T02:48:18Z

jhsagd76 marked the issue as grade-c

#2 - jhsagd76

2023-12-06T20:16:36Z

1 * 11 + 2 * 34 - 3 * 8 + 2 * 0

55

#3 - c4-judge

2023-12-06T21:01:01Z

jhsagd76 marked the issue as grade-a

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter