Skip to content

Instantly share code, notes, and snippets.

@misirov
Created February 13, 2024 00:34
Show Gist options
  • Save misirov/e8926047908369732297d2ee137f2b39 to your computer and use it in GitHub Desktop.
Save misirov/e8926047908369732297d2ee137f2b39 to your computer and use it in GitHub Desktop.
4nalyzer opal-contracts.md

Report

Gas Optimizations

Issue Instances
GAS-1 Using bools for storage incurs overhead 4
GAS-2 Cache array length outside of loop 6
GAS-3 Use calldata instead of memory for function arguments that do not get mutated 1
GAS-4 For Operations that will not overflow, you could use unchecked 287
GAS-5 Use Custom Errors 6
GAS-6 Don't initialize variables with default value 13
GAS-7 Functions guaranteed to revert when called by normal users can be marked payable 19
GAS-8 ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too) 15
GAS-9 Using private rather than public for constants, saves gas 1
GAS-10 Use shift Right/Left instead of division/multiplication if possible 1
GAS-11 Use != 0 instead of > 0 for unsigned integer comparison 8

[GAS-1] Using bools for storage incurs overhead

Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See source.

Instances (4):

File: pools/Omnipool.sol

48:     bool public isShutdown;

49:     bool public desactivated;

50:     bool public rebalancingRewardActive;
File: tokenomics/GaugeFactory.sol

14:     mapping(address => bool) public isFactoryGauge;

[GAS-2] Cache array length outside of loop

If not cached, 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).

Instances (6):

File: pools/Omnipool.sol

471:         for (uint256 i; i < allocatedPerPool.length; i++) {

559:         for (uint256 i; i < allocatedPerPool.length; i++) {
File: pools/OmnipoolController.sol

253:         for (uint256 i; i < pools.length; i++) {

268:             for (uint256 i; i < pools.length; i++) {

272:             for (uint256 i; i < pools.length; i++) {

290:         for (uint256 i; i < pools.length; i++) {

[GAS-3] Use calldata instead of memory for function arguments that do not get mutated

Mark data types as calldata instead of memory where possible. This makes it so that the data is not automatically loaded into memory. If the data passed into the function does not need to be changed (like updating values in an array), it can be passed in as calldata. The one exception to this is if the argument must later be passed into another function that takes an argument that specifies memory storage.

Instances (1):

File: tokenomics/GaugeController.sol

611:     function addType(string memory name, uint256 weight) external nonReentrant onlyOpalTeam {

[GAS-4] For Operations that will not overflow, you could use unchecked

Instances (287):

File: pools/BPTOracle.sol

77:                     ++i;

77:                     ++i;

88:                 ++i;

88:                 ++i;

91:         return (min * IRateProvider(poolAddress).getRate()) / 1e18;

91:         return (min * IRateProvider(poolAddress).getRate()) / 1e18;

127:                 ++i;

127:                 ++i;

160:                     ++i;

160:                     ++i;

171:             uint256 actualPrice = assetPrice * poolRate / poolRate;

171:             uint256 actualPrice = assetPrice * poolRate / poolRate;

176:                 ++i;

176:                 ++i;

180:         uint256 priceResult = minPrice * poolRate;

182:         return priceResult / 1e18;

215:         if (updatedAt + tokenHeartbeat[token] < block.timestamp) revert StalePrice();

229:             price = _price / (10 ** (_answerDigits - targetDigits));

229:             price = _price / (10 ** (_answerDigits - targetDigits));

229:             price = _price / (10 ** (_answerDigits - targetDigits));

229:             price = _price / (10 ** (_answerDigits - targetDigits));

232:             price = _price * (10 ** (targetDigits - _answerDigits));

232:             price = _price * (10 ** (targetDigits - _answerDigits));

232:             price = _price * (10 ** (targetDigits - _answerDigits));

232:             price = _price * (10 ** (targetDigits - _answerDigits));

244:         return amount * getUSDPrice(token) / 1e18;

244:         return amount * getUSDPrice(token) / 1e18;
File: pools/Omnipool.sol

38:     UnderlyingPool[] public underlyingPools; // slice of all balancer underlying pools

38:     UnderlyingPool[] public underlyingPools; // slice of all balancer underlying pools

42:     uint256 public totalDeposited; // total amount of deposit tokens deposited

42:     uint256 public totalDeposited; // total amount of deposit tokens deposited

43:     uint256 public totalUnderlyingPools; // total number of underlying balancer pools

43:     uint256 public totalUnderlyingPools; // total number of underlying balancer pools

143:         uint256 bptValue = bptPrice * bptBalance / 1e18;

143:         uint256 bptValue = bptPrice * bptBalance / 1e18;

155:             totalTvl += getPoolTvl(i);

157:                 ++i;

157:                 ++i;

222:         uint256 underlyingBalanceIncrease = afterTotalUnderlying - beforeTotalUnderlying;

229:         totalDeposited += _amountIn;

269:                 ++i;

269:                 ++i;

285:             ++totalUnderlyingPools;

285:             ++totalUnderlyingPools;

331:             uint256 underlyingToWithdraw_ = underlyingToReceive_ - underlyingBalanceBefore_;

337:         uint256 underlyingFees = underlyingWithdrawn_ * WITHDRAW_FEES / SCALED_ONE;

337:         uint256 underlyingFees = underlyingWithdrawn_ * WITHDRAW_FEES / SCALED_ONE;

338:         underlyingWithdrawn_ -= underlyingFees;

345:         totalDeposited -= underlyingWithdrawn_;

380:         return valuation * bptBalance;

401:             userDataAmountsIn = new uint256[](_pool.assets.length - 1);

402:             userDataAmountsIn[_pool.assetIndex - 1] = _underlyingAmountIn;

439:         uint256 totalAfterDeposit = totalUnderlying_ + _underlyingAmountIn;

446:             if (depositsRemaining < maxDeposit + 1e2) {

455:             depositsRemaining -= toDeposit;

456:             allocatedPerPoolCopy[poolIndex] += toDeposit;

470:         int256 depositPoolIndex = -1;

471:         for (uint256 i; i < allocatedPerPool.length; i++) {

471:         for (uint256 i; i < allocatedPerPool.length; i++) {

478:             uint256 maxBalance_ = targetAllocation_ + targetAllocation_.mulDown(_getMaxDeviation());

479:             uint256 maxDepositAmount_ = maxBalance_ - currentAlloc;

484:         require(depositPoolIndex > -1, "error retrieving deposit pool");

513:             assetIndex = assetIndex - 1;

533:         uint256 totalAfterWithdrawal = totalUnderlying_ - underlyingToWithdraw_;

540:             if (withdrawalsRemaining < maxWithdrawal + 1e2) {

548:             withdrawalsRemaining -= underlyingToWithdraw;

549:             allocatedPerPoolCopy[poolIndex] -= underlyingToWithdraw;

558:         int256 withdrawPoolIndex = -1;

559:         for (uint256 i; i < allocatedPerPool.length; i++) {

559:         for (uint256 i; i < allocatedPerPool.length; i++) {

568:                 uint256 allocatedUsd = (price_ * currentAlloc) / 10 ** underlyingToken.decimals();

568:                 uint256 allocatedUsd = (price_ * currentAlloc) / 10 ** underlyingToken.decimals();

568:                 uint256 allocatedUsd = (price_ * currentAlloc) / 10 ** underlyingToken.decimals();

568:                 uint256 allocatedUsd = (price_ * currentAlloc) / 10 ** underlyingToken.decimals();

569:                 if (allocatedUsd >= _MAX_USD_LP_VALUE_FOR_REMOVING_CURVE_POOL / 2) {

576:             uint256 minBalance_ = targetAllocation_ - targetAllocation_.mulDown(_getMaxDeviation());

577:             uint256 maxWithdrawAmount_ = currentAlloc - minBalance_;

582:         require(withdrawPoolIndex > -1, "error retrieving withdraw pool");

617:             total += getUserDeposit(user, i);

619:                 ++i;

619:                 ++i;

640:         if (lpSupply == 0 || totalUnderlying_ == 0) return 10 ** 18;

640:         if (lpSupply == 0 || totalUnderlying_ == 0) return 10 ** 18;

653:         return poolTvl == 0 || totalTvl == 0 ? 0 : (poolTvl * 100 / totalTvl);

653:         return poolTvl == 0 || totalTvl == 0 ? 0 : (poolTvl * 100 / totalTvl);

674:             totalUnderlying_ += poolUnderlying;

676:                 ++i;

676:                 ++i;

679:         totalAllocated = totalUnderlying_ + underlyingToken.balanceOf(address(this));

693:             int256 diff = int256(underlyingPools[i].targetWeight) - int256(currentPoolWeight);

699:                 ++i;

699:                 ++i;

791:         uint256 deadline = block.timestamp + 60_000;

854:                 ++i;

854:                 ++i;

868:         int256 scaleUp_ = int256(ScaledMath.ONE.divDown(ScaledMath.ONE - pool.targetWeight));

878:                 ++i;

878:                 ++i;

911:             totalDeviation += targetAmount.absSub(perPoolAllocations_[i]);

913:                 ++i;

913:                 ++i;

952:             total += newWeight;

954:                 ++i;

954:                 ++i;

1044:                 ++i;

1044:                 ++i;

1063:                 ++i;

1063:                 ++i;

1097:                 ++i;

1097:                 ++i;

1110:                 ++i;

1110:                 ++i;

1122:                 ++i;

1122:                 ++i;
File: pools/OmnipoolController.sol

185:                 ++i;

185:                 ++i;

202:                 ++i;

202:                 ++i;

239:                 ++i;

239:                 ++i;

253:         for (uint256 i; i < pools.length; i++) {

253:         for (uint256 i; i < pools.length; i++) {

261:                 totalUSDValue += poolUSDValue;

268:             for (uint256 i; i < pools.length; i++) {

268:             for (uint256 i; i < pools.length; i++) {

269:                 poolWeights[i] = ScaledMath.ONE / pools.length;

272:             for (uint256 i; i < pools.length; i++) {

272:             for (uint256 i; i < pools.length; i++) {

290:         for (uint256 i; i < pools.length; i++) {

290:         for (uint256 i; i < pools.length; i++) {

298:                 totalUSDValue += usdValue;

307:             totalUSDValue == 0 ? ScaledMath.ONE / pools.length : poolUSDValue.divDown(totalUSDValue);
File: tokenomics/EscrowedToken.sol

36:     uint256 public vestingDuration = WEEK * 8;

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

87:     function transfer(address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

102:     function transferFrom(address, /*from*/ address, /*to*/ uint256 /*amount*/ )

132:                 activeCount++;

132:                 activeCount++;

135:                 ++i;

135:                 ++i;

145:                     ++index;

145:                     ++index;

149:                 ++i;

149:                 ++i;

174:             remainingTime = userVesting.end - block.timestamp;

178:             userVesting.amount * (SCALED_ONE + (ratePerToken - userVesting.ratePerToken))

178:             userVesting.amount * (SCALED_ONE + (ratePerToken - userVesting.ratePerToken))

178:             userVesting.amount * (SCALED_ONE + (ratePerToken - userVesting.ratePerToken))

179:         ) / SCALED_ONE;

182:         uint256 removedAmount = (claimAmount * remainingTime) / vestingDuration;

182:         uint256 removedAmount = (claimAmount * remainingTime) / vestingDuration;

183:         claimAmount -= removedAmount;

206:         uint256 vestingEnd = startTimestamp + vestingDuration;

217:         userVestingCount[receiver]++;

217:         userVestingCount[receiver]++;

219:         totalVesting += amount;

244:                 i++;

244:                 i++;

254:         for (uint256 i; i < userNextIndex; i++) {

254:         for (uint256 i; i < userNextIndex; i++) {

274:             remainingTime = userVesting.end - block.timestamp;

277:             userVesting.amount * (SCALED_ONE + (ratePerToken - userVesting.ratePerToken))

277:             userVesting.amount * (SCALED_ONE + (ratePerToken - userVesting.ratePerToken))

277:             userVesting.amount * (SCALED_ONE + (ratePerToken - userVesting.ratePerToken))

278:         ) / SCALED_ONE;

279:         uint256 removedAmount = (claimAmount * remainingTime) / vestingDuration;

279:         uint256 removedAmount = (claimAmount * remainingTime) / vestingDuration;

280:         claimAmount -= removedAmount;

283:         totalVesting -= userVesting.amount;

288:             ratePerToken += (SCALED_ONE * removedAmount) / totalVesting;

288:             ratePerToken += (SCALED_ONE * removedAmount) / totalVesting;

288:             ratePerToken += (SCALED_ONE * removedAmount) / totalVesting;
File: tokenomics/GaugeController.sol

9:     uint256 public constant WEIGHT_VOTE_DELAY = 10 * 86_400; // 10 days

9:     uint256 public constant WEIGHT_VOTE_DELAY = 10 * 86_400; // 10 days

9:     uint256 public constant WEIGHT_VOTE_DELAY = 10 * 86_400; // 10 days

52:     mapping(address => uint256) public lastGaugeUpdate; // last scheduled time (next week)

52:     mapping(address => uint256) public lastGaugeUpdate; // last scheduled time (next week)

58:     mapping(int128 => uint256) public lastTypeUpdate; // last scheduled time (next week)

58:     mapping(int128 => uint256) public lastTypeUpdate; // last scheduled time (next week)

61:     uint256 public lastUpdate; // last scheduled time (next week)

61:     uint256 public lastUpdate; // last scheduled time (next week)

65:     mapping(int128 => uint256) public lastTypeWeightUpdate; // last scheduled time (next week)

65:     mapping(int128 => uint256) public lastTypeWeightUpdate; // last scheduled time (next week)

102:         lastUpdate = (block.timestamp / WEEK) * WEEK;

102:         lastUpdate = (block.timestamp / WEEK) * WEEK;

114:         return gaugeTypes[_gauge] - 1;

224:                 ++i; 

224:                 ++i; 

243:             timestamp += WEEK;

252:                 ++i;

252:                 ++i;

271:             timestamp += WEEK;

278:                     votes -= voteChanges;

288:                 ++i;

288:                 ++i;

309:                 ++i;

309:                 ++i;

317:             timestamp += WEEK;

324:                 votesTotal += typeVoteChange * typeWeight;

324:                 votesTotal += typeVoteChange * typeWeight;

327:                     ++k;

327:                     ++k;

338:                 ++j;

338:                 ++j;

357:             timestamp += WEEK;

364:                     votes -= voteChanges;

374:                 ++i;

374:                 ++i;

392:         timestamp = (timestamp / WEEK) * WEEK;

392:         timestamp = (timestamp / WEEK) * WEEK;

397:         int128 gaugeType = gaugeTypes[gauge] - 1;

400:         return SCALED_ONE * typeWeight * gaugeWeight / totalWeight;

400:         return SCALED_ONE * typeWeight * gaugeWeight / totalWeight;

400:         return SCALED_ONE * typeWeight * gaugeWeight / totalWeight;

412:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

412:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

412:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

414:         totalWeight += (oldSum * weight) - (oldSum * oldWeight);

414:         totalWeight += (oldSum * weight) - (oldSum * oldWeight);

414:         totalWeight += (oldSum * weight) - (oldSum * oldWeight);

414:         totalWeight += (oldSum * weight) - (oldSum * oldWeight);

431:         int128 gaugeType = gaugeTypes[gauge] - 1;

436:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

436:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

436:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

441:         uint256 newSum = oldSum + (weight - oldGaugeWeight);

441:         uint256 newSum = oldSum + (weight - oldGaugeWeight);

442:         totalWeight += (oldSum * weight) - (oldSum * typeWeight);

442:         totalWeight += (oldSum * weight) - (oldSum * typeWeight);

442:         totalWeight += (oldSum * weight) - (oldSum * typeWeight);

442:         totalWeight += (oldSum * weight) - (oldSum * typeWeight);

475:         vars.nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

475:         vars.nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

475:         vars.nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

476:         if (locks[vars.len - 1].unlockTime < vars.nextTimestamp) revert NoActiveLocks();

478:         if (block.timestamp < lastUserVote[user][gauge] + WEIGHT_VOTE_DELAY) revert VoteCooldown();

485:         uint256 i = vars.len - 1;

488:             uint256 weightedAmount = currentLock.amount * voteWeight / 10_000;

488:             uint256 weightedAmount = currentLock.amount * voteWeight / 10_000;

489:             newUserVote.amount += weightedAmount;

497:                 i--;

497:                 i--;

504:         vars.gaugeType = gaugeTypes[gauge] - 1;

510:         vars.powerUsed = vars.powerUsed + voteWeight - lastVote.weight;

510:         vars.powerUsed = vars.powerUsed + voteWeight - lastVote.weight;

522:         for (uint256 j; j < vars.oldUnlocksLen; j++) {

522:         for (uint256 j; j < vars.oldUnlocksLen; j++) {

526:             gaugeVoteChanges[gauge][oldUnlocks[j].unlockTime] -= oldUnlocks[j].amount;

527:             typeVoteChanges[vars.gaugeType][oldUnlocks[j].unlockTime] -= oldUnlocks[j].amount;

531:             vars.oldGaugeWeight -= oldUnlocks[j].amount;

532:             vars.oldSum -= oldUnlocks[j].amount;

535:         gaugeVotes[gauge][vars.nextTimestamp] = vars.oldGaugeWeight + newUserVote.amount;

536:         typeVotes[vars.gaugeType][vars.nextTimestamp] = vars.oldSum + newUserVote.amount;

538:         for (uint256 k; k < vars.len; k++) {

538:         for (uint256 k; k < vars.len; k++) {

542:             gaugeVoteChanges[gauge][unlocks[k].unlockTime] += unlocks[k].amount;

543:             typeVoteChanges[vars.gaugeType][unlocks[k].unlockTime] += unlocks[k].amount;

552:         for (uint256 l; l < vars.len; l++) {

552:         for (uint256 l; l < vars.len; l++) {

579:         numberGauges++;

579:         numberGauges++;

582:         gaugeTypes[gauge] = gaugeType + 1;

583:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

583:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

583:         uint256 nextTimestamp = ((block.timestamp + WEEK) / WEEK) * WEEK;

590:             typeVotes[gaugeType][nextTimestamp] = oldSum + weight;

592:             totalVotes[nextTimestamp] = olTotal + (weight * typeWeight);

592:             totalVotes[nextTimestamp] = olTotal + (weight * typeWeight);

613:         numberGaugeTypes++;

613:         numberGaugeTypes++;

[GAS-5] Use Custom Errors

Source Instead of using error strings, to reduce deployment and runtime cost, you should use Custom Errors. This would save both deployment and runtime cost.

Instances (6):

File: pools/Omnipool.sol

225:         require(lpReceived >= _minLpReceived, "too much slippage");

484:         require(depositPoolIndex > -1, "error retrieving deposit pool");

506:         require(balance >= _bptAmountOut, "not enough balance");

582:         require(withdrawPoolIndex > -1, "error retrieving withdraw pool");

863:         if (weight_ == 0) revert("pool already set to 0 weight");

865:             revert("can't remove last pool");

[GAS-6] Don't initialize variables with default value

Instances (13):

File: pools/BPTOracle.sol

114:         for (uint256 i = 0; i < length;) {
File: pools/Omnipool.sol

154:         for (uint8 i = 0; i < len;) {

266:         for (uint256 i = 0; i < length;) {

616:         for (uint8 i = 0; i < length;) {

670:         for (uint8 i = 0; i < length;) {

688:         uint8 min = 0;

689:         int256 largestDiff = 0;

691:         for (uint8 i = 0; i < length;) {

849:         for (uint8 i = 0; i < length;) {

1058:         for (uint8 i = 0; i < len;) {

1092:         for (uint8 i = 0; i < length;) {

1105:         for (uint8 i = 0; i < length;) {

1119:         for (uint8 i = 0; i < length;) {

[GAS-7] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.

Instances (19):

File: pools/BPTOracle.sol

55:     function setHeartbeat(address token, uint256 heartbeat) external onlyOpalTeam {
File: pools/Omnipool.sol

167:     function setRewardManager(address _rewardManager) external onlyOpalTeam {

590:     function setMaxDeviation(uint256 _maxDeviation) external onlyOpalTeam {

710:     function approveForRewardManager(address token, uint256 amount) external onlyRewardManager {

719:     function setExtraRewardPool(address _token, bytes32 _poolId) external onlyOpalTeam {

731:     function setGemPoolId(bytes32 _poolId) external onlyOpalTeam {

834:     function desactivate() external onlyController {
File: pools/OmnipoolController.sol

85:     function addOmnipool(address poolAddress) external onlyOpalTeam {

100:     function removePool(address poolAddress) external onlyOpalTeam {

112:     function addRebalancingRewardHandler(address pool, address handler) external onlyOpalTeam {

120:     function removeBalanceHandler(address pool, address handler) external onlyOpalTeam {

133:     function addPoolToOmnipool(address balancerPool, address omnipool_) external onlyOpalTeam {

146:     function desactivatePool(address poolAddress) external onlyOpalTeam {

212:     function setWeightUpdateMinDelay(uint256 delay) external onlyOpalTeam {
File: pools/OpalLpToken.sol

46:     function mint(address to, uint256 amount) public override onlyMinter returns (uint256) {

58:     function burn(address _owner, uint256 _amount) external override onlyBurner returns (uint256) {
File: tokenomics/GaugeController.sol

611:     function addType(string memory name, uint256 weight) external nonReentrant onlyOpalTeam {

636:     function changeGaugeWeight(address gauge, uint256 weight) external nonReentrant onlyOpalTeam {
File: tokenomics/GaugeFactory.sol

74:     function setImplementation(address _implementation) external onlyOpalTeam {

[GAS-8] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

Saves 5 gas per loop

Instances (15):

File: pools/Omnipool.sol

471:         for (uint256 i; i < allocatedPerPool.length; i++) {

559:         for (uint256 i; i < allocatedPerPool.length; i++) {
File: pools/OmnipoolController.sol

253:         for (uint256 i; i < pools.length; i++) {

268:             for (uint256 i; i < pools.length; i++) {

272:             for (uint256 i; i < pools.length; i++) {

290:         for (uint256 i; i < pools.length; i++) {
File: tokenomics/EscrowedToken.sol

132:                 activeCount++;

217:         userVestingCount[receiver]++;

244:                 i++;

254:         for (uint256 i; i < userNextIndex; i++) {
File: tokenomics/GaugeController.sol

522:         for (uint256 j; j < vars.oldUnlocksLen; j++) {

538:         for (uint256 k; k < vars.len; k++) {

552:         for (uint256 l; l < vars.len; l++) {

579:         numberGauges++;

613:         numberGaugeTypes++;

[GAS-9] Using private rather than public for constants, saves gas

If needed, the values can be read from the verified contract source code, or if there are multiple values there can be a single getter function that returns a tuple of the values of all currently-public 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

Instances (1):

File: tokenomics/GaugeController.sol

9:     uint256 public constant WEIGHT_VOTE_DELAY = 10 * 86_400; // 10 days

[GAS-10] Use shift Right/Left instead of division/multiplication if possible

Instances (1):

File: pools/Omnipool.sol

569:                 if (allocatedUsd >= _MAX_USD_LP_VALUE_FOR_REMOVING_CURVE_POOL / 2) {

[GAS-11] Use != 0 instead of > 0 for unsigned integer comparison

Instances (8):

File: pools/Omnipool.sol

400:         if (_pool.bptIndex > 0) {

442:         while (depositsRemaining > 0) {

512:         if (_pool.bptIndex > 0 && _pool.bptIndex < assetIndex) {

536:         while (withdrawalsRemaining > 0) {
File: tokenomics/EscrowedToken.sol

287:         if (totalVesting > 0) {
File: tokenomics/GaugeController.sol

2: pragma solidity >=0.8.16;

496:             if (i > 0) {

585:         if (weight > 0) {

Non Critical Issues

Issue Instances
NC-1 Event is missing indexed fields 5

[NC-1] Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed per event (three fields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

Instances (5):

File: tokenomics/GaugeController.sol

69:     event AddType(string name, int128 typeId);

70:     event NewGauge(address gauge, int128 gaugeType, uint256 weight);

71:     event NewTypeWeight(int128 typeId, uint256 time, uint256 weight, uint256 totalWeight);

72:     event NewGaugeWeight(address gauge, uint256 time, uint256 weight, uint256 totalWeight);

73:     event VoteForGauge(uint256 time, address user, address gauge, uint256 weight);

Low Issues

Issue Instances
L-1 Initializers could be front-run 1
L-2 Unspecific compiler version pragma 1

[L-1] Initializers could be front-run

Initializers could be front-run, allowing an attacker to either set their own values, take ownership of the contract, and in the best case forcing a re-deployment

Instances (1):

File: tokenomics/GaugeFactory.sol

56:         ILiquidityGauge(gauge).initialize(lpToken);

[L-2] Unspecific compiler version pragma

Instances (1):

File: tokenomics/GaugeController.sol

2: pragma solidity >=0.8.16;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment