Skip to content

Instantly share code, notes, and snippets.

@misirov
Created January 4, 2024 13:51
Show Gist options
  • Save misirov/4dbae4e3810f0a9a75e6b3558d39ebd3 to your computer and use it in GitHub Desktop.
Save misirov/4dbae4e3810f0a9a75e6b3558d39ebd3 to your computer and use it in GitHub Desktop.
4nalyz3r-incentive-contracts

Report

Gas Optimizations

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

[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 (12):

File: VestedZeroLend.sol

18:     mapping(address => bool) public whitelist;

19:     mapping(address => bool) public blacklist;

20:     bool public enableWhitelist;

21:     bool public enableBlacklist;
File: ZLRewardsController.sol

29:     bool public persistRewardsPerSecond;

48:     mapping(address => bool) private validRTokens;

60:     mapping(address => bool) public eligibilityExempt;

87:     mapping(address => bool) public authorizedContracts;

90:     mapping(address => bool) public whitelist;

92:     bool public whitelistActive;
File: ZeroLend.sol

18:     mapping(address => bool) public blacklisted;
File: ZeroLocker.sol

64:     mapping(address => mapping(address => bool)) internal ownerToOperators;

[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 (5):

File: FeeDistributor.sol

291:         for (uint256 index = 0; index < nftIds.length; index++) {
File: ZeroLockerTimelock.sol

118:         for (uint256 i = 0; i < proposers.length; ++i) {

124:         for (uint256 i = 0; i < executors.length; ++i) {

302:         for (uint256 i = 0; i < targets.length; ++i) {

414:         for (uint256 i = 0; i < targets.length; ++i) {

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

Instances (368):

File: BonusPool.sol

15: import {IBonusPool} from "./interfaces/IBonusPool.sol";

15: import {IBonusPool} from "./interfaces/IBonusPool.sol";

19:     uint256 public immutable PCT_100 = 100000; // 100%

19:     uint256 public immutable PCT_100 = 100000; // 100%

23:         _setBonusBps(20000); // 20%

23:         _setBonusBps(20000); // 20%

27:         return (amount * bonusBps) / PCT_100;

27:         return (amount * bonusBps) / PCT_100;
File: Epoch.sol

44:         return (lastExecutedAt - startTime) / (period);

44:         return (lastExecutedAt - startTime) / (period);

48:         return (Math.max(startTime, block.timestamp) - (startTime)) / (period);

48:         return (Math.max(startTime, block.timestamp) - (startTime)) / (period);

63:         return _getLastEpoch() + (1);

72:         return (Math.max(startTime, block.timestamp) - (startTime)) / (period);

72:         return (Math.max(startTime, block.timestamp) - (startTime)) / (period);

80:         return startTime + (_getNextEpoch() * (period));

80:         return startTime + (_getNextEpoch() * (period));
File: FeeDistributor.sol

16: import {IFeeDistributor} from "./interfaces/IFeeDistributor.sol";

16: import {IFeeDistributor} from "./interfaces/IFeeDistributor.sol";

17: import {IZeroLocker} from "./interfaces/IZeroLocker.sol";

17: import {IZeroLocker} from "./interfaces/IZeroLocker.sol";

35:     uint256[1000000000000000] public veSupply; // VE total supply at week bounds

35:     uint256[1000000000000000] public veSupply; // VE total supply at week bounds

45:         WEEK = 7 * 86400;

49:         uint256 t = (block.timestamp / WEEK) * WEEK;

49:         uint256 t = (block.timestamp / WEEK) * WEEK;

60:         uint256 toDistribute = tokenBalance - tokenLastBalance;

64:         uint256 sinceLast = timestamp - t;

67:         uint256 thisWeek = (t / WEEK) * WEEK;

67:         uint256 thisWeek = (t / WEEK) * WEEK;

70:         for (uint256 index = 0; index < 20; index++) {

70:         for (uint256 index = 0; index < 20; index++) {

71:             nextWeek = thisWeek + WEEK;

74:                     tokensPerWeek[thisWeek] += toDistribute;

76:                     tokensPerWeek[thisWeek] +=

77:                         (toDistribute * (timestamp - t)) /

77:                         (toDistribute * (timestamp - t)) /

77:                         (toDistribute * (timestamp - t)) /

82:                     tokensPerWeek[thisWeek] += toDistribute;

84:                     tokensPerWeek[thisWeek] +=

85:                         (toDistribute * (nextWeek - t)) /

85:                         (toDistribute * (nextWeek - t)) /

85:                         (toDistribute * (nextWeek - t)) /

97:             ((block.timestamp > lastTokenTime + TOKEN_CHECKPOINT_DEADLINE)),

109:         for (uint256 index = 0; index < 128; index++) {

109:         for (uint256 index = 0; index < 128; index++) {

113:             uint256 mid = (min + max + 2) / 2;

113:             uint256 mid = (min + max + 2) / 2;

113:             uint256 mid = (min + max + 2) / 2;

117:             else max = mid - 1;

131:         for (uint256 index = 0; index < 128; index++) {

131:         for (uint256 index = 0; index < 128; index++) {

135:             uint256 mid = (min + max + 2) / 2;

135:             uint256 mid = (min + max + 2) / 2;

135:             uint256 mid = (min + max + 2) / 2;

139:             else max = mid - 1;

147:         uint256 roundedTimestamp = (timestamp / WEEK) * WEEK;

147:         uint256 roundedTimestamp = (timestamp / WEEK) * WEEK;

151:         for (uint256 index = 0; index < 20; index++) {

151:         for (uint256 index = 0; index < 20; index++) {

159:                 if (t > pt.ts) dt = int128(uint128(t - pt.ts));

160:                 veSupply[t] = Math.max(uint128(pt.bias - pt.slope * dt), 0);

160:                 veSupply[t] = Math.max(uint128(pt.bias - pt.slope * dt), 0);

163:             t += WEEK;

203:             weekCursor = ((userPoint.ts + WEEK - 1) / WEEK) * WEEK;

203:             weekCursor = ((userPoint.ts + WEEK - 1) / WEEK) * WEEK;

203:             weekCursor = ((userPoint.ts + WEEK - 1) / WEEK) * WEEK;

203:             weekCursor = ((userPoint.ts + WEEK - 1) / WEEK) * WEEK;

211:         for (uint256 index = 0; index < 50; index++) {

211:         for (uint256 index = 0; index < 50; index++) {

215:                 userEpoch += 1;

222:                 int128 dt = int128(uint128(weekCursor - oldUserPoint.ts));

224:                     uint128(oldUserPoint.bias - dt * oldUserPoint.slope),

224:                     uint128(oldUserPoint.bias - dt * oldUserPoint.slope),

230:                     toDistribute +=

231:                         (balanceOf * tokensPerWeek[weekCursor]) /

231:                         (balanceOf * tokensPerWeek[weekCursor]) /

234:                 weekCursor += WEEK;

238:         userEpoch = Math.min(maxUserEpoch, userEpoch - 1);

252:         if ((block.timestamp > lastTokenTime + TOKEN_CHECKPOINT_DEADLINE)) {

257:         _lastTokenTime = (_lastTokenTime / WEEK) * WEEK;

257:         _lastTokenTime = (_lastTokenTime / WEEK) * WEEK;

263:             tokenLastBalance -= amount;

284:         if ((block.timestamp > lastTokenTime + TOKEN_CHECKPOINT_DEADLINE)) {

289:         _lastTokenTime = (_lastTokenTime / WEEK) * WEEK;

289:         _lastTokenTime = (_lastTokenTime / WEEK) * WEEK;

291:         for (uint256 index = 0; index < nftIds.length; index++) {

291:         for (uint256 index = 0; index < nftIds.length; index++) {

296:                 tokenLastBalance -= amount;
File: StakingEmissions.sol

17: import {IFeeDistributor} from "./interfaces/IFeeDistributor.sol";

17: import {IFeeDistributor} from "./interfaces/IFeeDistributor.sol";

18: import {IZeroLocker} from "./interfaces/IZeroLocker.sol";

18: import {IZeroLocker} from "./interfaces/IZeroLocker.sol";

19: import {Epoch} from "./Epoch.sol";

45:         initEpoch(86400 * 7, block.timestamp);
File: StreamedVesting.sol

28:     uint256 public duration = 3 * 30 days; // 3 months vesting

28:     uint256 public duration = 3 * 30 days; // 3 months vesting

28:     uint256 public duration = 3 * 30 days; // 3 months vesting

75:         lastId++;

75:         lastId++;

87:         userVestCounts[to] = userVestCount + 1;

96:         uint256 lockAmount = vest.amount - vest.claimed;

110:             lockAmount += bonusAmount;

114:         locker.createLockFor(lockAmount, 86400 * 365 * 4, msg.sender);

114:         locker.createLockFor(lockAmount, 86400 * 365 * 4, msg.sender);

125:         vest.claimed += val;

137:         uint256 pendingAmt = vest.amount - vest.claimed;

141:         vest.claimed += pendingAmt;

146:         uint256 penaltyAmt = ((pendingAmt * penaltyPct) / 1e18);

146:         uint256 penaltyAmt = ((pendingAmt * penaltyPct) / 1e18);

147:         underlying.transfer(msg.sender, pendingAmt - penaltyAmt);

178:         uint256 pendingAmt = vest.amount - vest.claimed;

180:             pendingAmt -

181:             ((pendingAmt * _penaltyAmt) / 1e18);

181:             ((pendingAmt * _penaltyAmt) / 1e18);

192:         uint256 pendingAmt = vest.amount - vest.claimed;

195:         return pendingAmt - ((pendingAmt * _penalty) / 1e18);

195:         return pendingAmt - ((pendingAmt * _penalty) / 1e18);

195:         return pendingAmt - ((pendingAmt * _penalty) / 1e18);

207:         if (nowTime > startTime + duration) return 0;

210:         if (nowTime < startTime) return 95e18 / 100;

212:         uint256 percentage = ((nowTime - startTime) * 1e18) / duration;

212:         uint256 percentage = ((nowTime - startTime) * 1e18) / duration;

212:         uint256 percentage = ((nowTime - startTime) * 1e18) / duration;

214:         uint256 penaltyE20 = 95e18 - (75e18 * percentage) / 1e18;

214:         uint256 penaltyE20 = 95e18 - (75e18 * percentage) / 1e18;

214:         uint256 penaltyE20 = 95e18 - (75e18 * percentage) / 1e18;

215:         return penaltyE20 / 100;

221:             _claimable(vest.amount, vest.startAt, block.timestamp) -

231:         if (nowTime > startTime + duration) return amount;

237:         return (amount * (nowTime - startTime)) / duration;

237:         return (amount * (nowTime - startTime)) / duration;

237:         return (amount * (nowTime - startTime)) / duration;

243:         for (uint i = 0; i < userVestCounts[who]; i++) {

243:         for (uint i = 0; i < userVestCounts[who]; i++) {
File: ZLRewardsController.sol

181:         totalAllocPoint = totalAllocPoint + _allocPoint;

207:                 _totalAllocPoint -

208:                 pool.allocPoint +

212:                 i++;

212:                 i++;

242:             uint128 offset = uint128(block.timestamp - startTime);

249:                     i++;

249:                     i++;

256:                     emissionSchedule[i - 1].rewardsPerSecond

276:                 i++;

276:                 i++;

298:                 if (_startTimeOffsets[i - 1] > _startTimeOffsets[i])

309:                 if (_startTimeOffsets[i] < block.timestamp - startTime)

319:                 i++;

319:                 i++;

361:                 i++;

361:                 i++;

389:         accountedRewards = accountedRewards + reward;

390:         pool.accRewardPerShare = pool.accRewardPerShare + newAccRewardPerShare;

418:                 accRewardPerShare = accRewardPerShare + newAccRewardPerShare;

421:                 (user.amount * accRewardPerShare) /

421:                 (user.amount * accRewardPerShare) /

422:                 ACC_REWARD_PRECISION -

425:                 i++;

425:                 i++;

454:             uint256 rewardDebt = (user.amount * pool.accRewardPerShare) /

454:             uint256 rewardDebt = (user.amount * pool.accRewardPerShare) /

456:             pending = pending + rewardDebt - user.rewardDebt;

456:             pending = pending + rewardDebt - user.rewardDebt;

460:                 i++;

460:                 i++;

537:             uint256 pending = (amount * accRewardPerShare) /

537:             uint256 pending = (amount * accRewardPerShare) /

538:                 ACC_REWARD_PRECISION -

541:                 userBaseClaimable[_user] = userBaseClaimable[_user] + pending;

544:         pool.totalSupply = pool.totalSupply - user.amount;

546:         user.rewardDebt = (_balance * accRewardPerShare) / ACC_REWARD_PRECISION;

546:         user.rewardDebt = (_balance * accRewardPerShare) / ACC_REWARD_PRECISION;

547:         pool.totalSupply = pool.totalSupply + _balance;

596:                     poolInfo[registeredTokens[i]].totalSupply +

597:                         newBal -

602:                 i++;

602:                 i++;

623:                 i++;

623:                 i++;

654:             endingTime.lastUpdatedTime + endingTime.updateCadence >

667:                 extra +=

668:                     ((pool.lastRewardTime - lastAllPoolUpdate) *

668:                     ((pool.lastRewardTime - lastAllPoolUpdate) *

669:                         pool.allocPoint *

670:                         rewardsPerSecond) /

674:                 i++;

674:                 i++;

683:             uint256 newEndTime = (unclaimedRewards + extra) /

683:             uint256 newEndTime = (unclaimedRewards + extra) /

684:                 rewardsPerSecond +

708:         depositedRewards = depositedRewards + _amount;

722:         return depositedRewards - accountedRewards;

745:             pending += claimable[i];

747:                 i++;

747:                 i++;

777:             uint256 duration = block.timestamp - pool.lastRewardTime;

778:             uint256 rawReward = duration * rewardsPerSecond;

784:             newReward = (rawReward * pool.allocPoint) / _totalAllocPoint;

784:             newReward = (rawReward * pool.allocPoint) / _totalAllocPoint;

786:                 (newReward * ACC_REWARD_PRECISION) /

786:                 (newReward * ACC_REWARD_PRECISION) /
File: ZeroLend.sol

23:         _mint(msg.sender, 100_000_000_000 * 10 ** decimals());

23:         _mint(msg.sender, 100_000_000_000 * 10 ** decimals());

23:         _mint(msg.sender, 100_000_000_000 * 10 ** decimals());
File: ZeroLocker.sol

33:     mapping(uint256 => Point) internal _pointHistory; // epoch -> unsigned point

33:     mapping(uint256 => Point) internal _pointHistory; // epoch -> unsigned point

33:     mapping(uint256 => Point) internal _pointHistory; // epoch -> unsigned point

34:     mapping(uint256 => Point[1000000000]) internal _userPointHistory; // user -> Point[userEpoch]

34:     mapping(uint256 => Point[1000000000]) internal _userPointHistory; // user -> Point[userEpoch]

34:     mapping(uint256 => Point[1000000000]) internal _userPointHistory; // user -> Point[userEpoch]

37:     mapping(uint256 => int128) public slopeChanges; // time -> signed slope change

37:     mapping(uint256 => int128) public slopeChanges; // time -> signed slope change

37:     mapping(uint256 => int128) public slopeChanges; // time -> signed slope change

73:         MAXTIME = 4 * 365 * 86400;

73:         MAXTIME = 4 * 365 * 86400;

74:         iMAXTIME = 4 * 365 * 86400;

74:         iMAXTIME = 4 * 365 * 86400;

93:             bytes4(0x01ffc9a7) == _interfaceID || // ERC165

93:             bytes4(0x01ffc9a7) == _interfaceID || // ERC165

94:             bytes4(0x80ac58cd) == _interfaceID || // ERC721

94:             bytes4(0x80ac58cd) == _interfaceID || // ERC721

95:             bytes4(0x5b5e139f) == _interfaceID || // ERC721Metadata

95:             bytes4(0x5b5e139f) == _interfaceID || // ERC721Metadata

183:         for (uint256 index = 0; index < ownerToNFTokenCount[_owner]; index++) {

183:         for (uint256 index = 0; index < ownerToNFTokenCount[_owner]; index++) {

185:             _power += _balanceOfNFT(_tokenId, block.timestamp);

254:         uint256 currentCount = _balance(_from) - 1;

289:         ownerToNFTokenCount[_to] += 1;

302:         ownerToNFTokenCount[_from] -= 1;

514:                 uOld.slope = oldLocked.amount / iMAXTIME;

516:                     uOld.slope *

517:                     int128(int256(oldLocked.end - block.timestamp));

520:                 uNew.slope = newLocked.amount / iMAXTIME;

522:                     uNew.slope *

523:                     int128(int256(newLocked.end - block.timestamp));

553:         uint256 blockSlope = 0; // dblock/dt

553:         uint256 blockSlope = 0; // dblock/dt

553:         uint256 blockSlope = 0; // dblock/dt

556:                 (MULTIPLIER * (block.number - lastPoint.blk)) /

556:                 (MULTIPLIER * (block.number - lastPoint.blk)) /

556:                 (MULTIPLIER * (block.number - lastPoint.blk)) /

557:                 (block.timestamp - lastPoint.ts);

564:             uint256 tI = (lastCheckpoint / WEEK) * WEEK;

564:             uint256 tI = (lastCheckpoint / WEEK) * WEEK;

565:             for (uint256 i = 0; i < 255; ++i) {

565:             for (uint256 i = 0; i < 255; ++i) {

568:                 tI += WEEK;

575:                 lastPoint.bias -=

576:                     lastPoint.slope *

577:                     int128(int256(tI - lastCheckpoint));

578:                 lastPoint.slope += dSlope;

590:                     initialLastPoint.blk +

591:                     (blockSlope * (tI - initialLastPoint.ts)) /

591:                     (blockSlope * (tI - initialLastPoint.ts)) /

591:                     (blockSlope * (tI - initialLastPoint.ts)) /

593:                 _epoch += 1;

609:             lastPoint.slope += (uNew.slope - uOld.slope);

609:             lastPoint.slope += (uNew.slope - uOld.slope);

610:             lastPoint.bias += (uNew.bias - uOld.bias);

610:             lastPoint.bias += (uNew.bias - uOld.bias);

628:                 oldDslope += uOld.slope;

630:                     oldDslope -= uNew.slope; // It was a new deposit, not extension

630:                     oldDslope -= uNew.slope; // It was a new deposit, not extension

630:                     oldDslope -= uNew.slope; // It was a new deposit, not extension

637:                     newDslope -= uNew.slope; // old slope disappeared at this point

637:                     newDslope -= uNew.slope; // old slope disappeared at this point

637:                     newDslope -= uNew.slope; // old slope disappeared at this point

643:             uint256 userEpoch = userPointEpoch[_tokenId] + 1;

668:         supply = supplyBefore + _value;

673:         _locked.amount += int128(int256(_value));

702:         emit Supply(supplyBefore, supplyBefore + _value);

743:         require(_value > 0, "value = 0"); // dev: need non-zero value

743:         require(_value > 0, "value = 0"); // dev: need non-zero value

743:         require(_value > 0, "value = 0"); // dev: need non-zero value

758:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

758:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

758:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

758:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

758:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

760:         require(_value > 0, "value = 0"); // dev: need non-zero value

760:         require(_value > 0, "value = 0"); // dev: need non-zero value

760:         require(_value > 0, "value = 0"); // dev: need non-zero value

763:             unlockTime <= block.timestamp + MAXTIME,

767:         ++tokenId;

767:         ++tokenId;

816:         assert(_value > 0); // dev: need non-zero value

816:         assert(_value > 0); // dev: need non-zero value

816:         assert(_value > 0); // dev: need non-zero value

841:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

841:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

841:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

841:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

841:         uint256 unlockTime = ((block.timestamp + _lockDuration) / WEEK) * WEEK; // Locktime is rounded down to weeks

847:             unlockTime <= block.timestamp + MAXTIME,

851:             unlockTime <= _locked.start + MAXTIME,

878:         supply = supplyBefore - value;

891:         emit Supply(supplyBefore, supplyBefore - value);

909:         for (uint256 i = 0; i < 128; ++i) {

909:         for (uint256 i = 0; i < 128; ++i) {

914:             uint256 _mid = (_min + _max + 1) / 2;

914:             uint256 _mid = (_min + _max + 1) / 2;

914:             uint256 _mid = (_min + _max + 1) / 2;

918:                 _max = _mid - 1;

938:             lastPoint.bias -=

939:                 lastPoint.slope *

940:                 int128(int256(_t) - int256(lastPoint.ts));

978:         for (uint256 i = 0; i < 128; ++i) {

978:         for (uint256 i = 0; i < 128; ++i) {

983:             uint256 _mid = (_min + _max + 1) / 2;

983:             uint256 _mid = (_min + _max + 1) / 2;

983:             uint256 _mid = (_min + _max + 1) / 2;

987:                 _max = _mid - 1;

999:             Point memory point1 = _pointHistory[_epoch + 1];

1000:             dBlock = point1.blk - point0.blk;

1001:             dT = point1.ts - point0.ts;

1003:             dBlock = block.number - point0.blk;

1004:             dT = block.timestamp - point0.ts;

1008:             blockTime += (dT * (_block - point0.blk)) / dBlock;

1008:             blockTime += (dT * (_block - point0.blk)) / dBlock;

1008:             blockTime += (dT * (_block - point0.blk)) / dBlock;

1008:             blockTime += (dT * (_block - point0.blk)) / dBlock;

1011:         upoint.bias -= upoint.slope * int128(int256(blockTime - upoint.ts));

1011:         upoint.bias -= upoint.slope * int128(int256(blockTime - upoint.ts));

1011:         upoint.bias -= upoint.slope * int128(int256(blockTime - upoint.ts));

1035:         uint256 tI = (lastPoint.ts / WEEK) * WEEK;

1035:         uint256 tI = (lastPoint.ts / WEEK) * WEEK;

1036:         for (uint256 i = 0; i < 255; ++i) {

1036:         for (uint256 i = 0; i < 255; ++i) {

1037:             tI += WEEK;

1044:             lastPoint.bias -=

1045:                 lastPoint.slope *

1046:                 int128(int256(tI - lastPoint.ts));

1050:             lastPoint.slope += dSlope;

1086:             Point memory pointNext = _pointHistory[targetEpoch + 1];

1089:                     ((_block - point.blk) * (pointNext.ts - point.ts)) /

1089:                     ((_block - point.blk) * (pointNext.ts - point.ts)) /

1089:                     ((_block - point.blk) * (pointNext.ts - point.ts)) /

1089:                     ((_block - point.blk) * (pointNext.ts - point.ts)) /

1090:                     (pointNext.blk - point.blk);

1095:                     ((_block - point.blk) * (block.timestamp - point.ts)) /

1095:                     ((_block - point.blk) * (block.timestamp - point.ts)) /

1095:                     ((_block - point.blk) * (block.timestamp - point.ts)) /

1095:                     ((_block - point.blk) * (block.timestamp - point.ts)) /

1096:                     (block.number - point.blk);

1100:         return _supplyAt(point, point.ts + dt);
File: ZeroLockerTimelock.sol

118:         for (uint256 i = 0; i < proposers.length; ++i) {

118:         for (uint256 i = 0; i < proposers.length; ++i) {

124:         for (uint256 i = 0; i < executors.length; ++i) {

124:         for (uint256 i = 0; i < executors.length; ++i) {

302:         for (uint256 i = 0; i < targets.length; ++i) {

302:         for (uint256 i = 0; i < targets.length; ++i) {

330:         _timestamps[id] = block.timestamp + delay;

414:         for (uint256 i = 0; i < targets.length; ++i) {

414:         for (uint256 i = 0; i < targets.length; ++i) {
File: interfaces/IZLRewardsController.sol

4: import "./IIncentivesController.sol";

18:         uint256 allocPoint; // How many allocation points assigned to this pool.

18:         uint256 allocPoint; // How many allocation points assigned to this pool.

19:         uint256 lastRewardTime; // Last second that reward distribution occurs.

19:         uint256 lastRewardTime; // Last second that reward distribution occurs.

20:         uint256 accRewardPerShare; // Accumulated rewards per share, times ACC_REWARD_PRECISION. See below.

20:         uint256 accRewardPerShare; // Accumulated rewards per share, times ACC_REWARD_PRECISION. See below.
File: interfaces/IZeroLocker.sol

57:         int128 slope; // # -dweight / dt

57:         int128 slope; // # -dweight / dt

57:         int128 slope; // # -dweight / dt

57:         int128 slope; // # -dweight / dt

59:         uint256 blk; // block

59:         uint256 blk; // block

[GAS-4] 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 (32):

File: BonusPool.sol

41:             require(IERC20(tkn).balanceOf(address(this)) > 0, "No tokens");

48:         require(bonusBps <= PCT_100, "bonus too high");
File: Epoch.sol

32:         require(block.timestamp >= startTime, "Epoch: not started yet");

37:         require(block.timestamp > startTime, "Epoch: not started yet");

38:         require(_callable(), "Epoch: not allowed");
File: StreamedVesting.sol

94:         require(msg.sender == vest.who, "not owner");

119:         require(msg.sender == vest.who, "not owner");

122:         require(val > 0, "no claimable amount");

135:         require(msg.sender == vest.who, "not owner");

138:         require(pendingAmt > 0, "no pending amount");
File: VestedZeroLend.sol

53:             require(whitelist[from] || whitelist[to], "!whitelist");

57:             require(!blacklist[to] && !blacklist[from], "blacklist");
File: ZeroLend.sol

39:         require(!blacklisted[from] && !blacklisted[to], "blacklisted");
File: ZeroLocker.sol

329:         require(_isApprovedOrOwner(_sender, _tokenId), "not approved sender");

442:         require(owner != address(0), "owner is 0x0");

444:         require(_approved != owner, "not owner");

448:         require(senderIsOwner || senderIsApprovedForAll, "invalid sender");

706:         require(_from != _to, "same nft");

707:         require(_isApprovedOrOwner(msg.sender, _from), "from not approved");

708:         require(_isApprovedOrOwner(msg.sender, _to), "to not approved");

743:         require(_value > 0, "value = 0"); // dev: need non-zero value

744:         require(_locked.amount > 0, "No existing lock found");

745:         require(_locked.end > block.timestamp, "Cannot add to expired lock.");

760:         require(_value > 0, "value = 0"); // dev: need non-zero value

761:         require(unlockTime > block.timestamp, "Can only lock in the future");

817:         require(_locked.amount > 0, "No existing lock found");

818:         require(_locked.end > block.timestamp, "Cannot add to expired lock.");

843:         require(_locked.end > block.timestamp, "Lock expired");

844:         require(_locked.amount > 0, "Nothing is locked");

845:         require(unlockTime > _locked.end, "Can only increase lock duration");

873:         require(block.timestamp >= _locked.end, "The lock didn't expire");
File: ZeroLockerTimelock.sol

433:         require(success, "TimelockController: underlying transaction reverted");

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

Instances (41):

File: FeeDistributor.sol

68:         uint256 nextWeek = 0;

70:         for (uint256 index = 0; index < 20; index++) {

106:         uint256 min = 0;

109:         for (uint256 index = 0; index < 128; index++) {

128:         uint256 min = 0;

131:         for (uint256 index = 0; index < 128; index++) {

151:         for (uint256 index = 0; index < 20; index++) {

157:                 int128 dt = 0;

177:         uint256 userEpoch = 0;

178:         uint256 toDistribute = 0;

211:         for (uint256 index = 0; index < 50; index++) {

291:         for (uint256 index = 0; index < nftIds.length; index++) {
File: StreamedVesting.sol

243:         for (uint i = 0; i < userVestCounts[who]; i++) {
File: ZLRewardsController.sol

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

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

661:         uint256 extra = 0;
File: ZeroLocker.sol

183:         for (uint256 index = 0; index < ownerToNFTokenCount[_owner]; index++) {

506:         int128 oldDslope = 0;

507:         int128 newDslope = 0;

553:         uint256 blockSlope = 0; // dblock/dt

565:             for (uint256 i = 0; i < 255; ++i) {

569:                 int128 dSlope = 0;

581:                     lastPoint.bias = 0;

585:                     lastPoint.slope = 0;

612:                 lastPoint.slope = 0;

615:                 lastPoint.bias = 0;

907:         uint256 _min = 0;

909:         for (uint256 i = 0; i < 128; ++i) {

942:                 lastPoint.bias = 0;

976:         uint256 _min = 0;

978:         for (uint256 i = 0; i < 128; ++i) {

996:         uint256 dBlock = 0;

997:         uint256 dT = 0;

1036:         for (uint256 i = 0; i < 255; ++i) {

1038:             int128 dSlope = 0;

1055:             lastPoint.bias = 0;

1084:         uint256 dt = 0;
File: ZeroLockerTimelock.sol

118:         for (uint256 i = 0; i < proposers.length; ++i) {

124:         for (uint256 i = 0; i < executors.length; ++i) {

302:         for (uint256 i = 0; i < targets.length; ++i) {

414:         for (uint256 i = 0; i < targets.length; ++i) {

[GAS-6] Long revert strings

Instances (1):

File: ZeroLockerTimelock.sol

433:         require(success, "TimelockController: underlying transaction reverted");

[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 (17):

File: BonusPool.sol

30:     function setBonusBps(uint256 amount) external onlyOwner {

34:     function withdrawStuckTokens(address tkn) public onlyOwner {
File: StakingEmissions.sol

44:     function start() external onlyOwner {
File: StreamedVesting.sol

56:     function start() external onlyOwner {
File: VestedZeroLend.sol

34:     function addblacklist(address who, bool what) external onlyOwner {

38:     function addwhitelist(address who, bool what) external onlyOwner {

42:     function toggleWhitelist(bool from, bool to) external onlyOwner {
File: ZLRewardsController.sol

167:     function start() public onlyOwner {

696:     function setEndingTimeUpdateCadence(uint256 _lapse) external onlyOwner {

707:     function registerRewardDeposit(uint256 _amount) external onlyOwner {

755:     function pause() external onlyOwner {

762:     function unpause() external onlyOwner {

796:     function setAddressWLstatus(address user, bool status) external onlyOwner {

803:     function toggleWhitelist() external onlyOwner {
File: ZeroLend.sol

26:     function mint(uint256 amt) public onlyOwner {

30:     function toggleBlacklist(address who, bool what) public onlyOwner {
File: ZeroLockerTimelock.sol

340:     function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {

[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 (20):

File: FeeDistributor.sol

70:         for (uint256 index = 0; index < 20; index++) {

109:         for (uint256 index = 0; index < 128; index++) {

131:         for (uint256 index = 0; index < 128; index++) {

151:         for (uint256 index = 0; index < 20; index++) {

211:         for (uint256 index = 0; index < 50; index++) {

291:         for (uint256 index = 0; index < nftIds.length; index++) {
File: StreamedVesting.sol

75:         lastId++;

243:         for (uint i = 0; i < userVestCounts[who]; i++) {
File: ZLRewardsController.sol

212:                 i++;

249:                     i++;

276:                 i++;

319:                 i++;

361:                 i++;

425:                 i++;

460:                 i++;

602:                 i++;

623:                 i++;

674:                 i++;

747:                 i++;
File: ZeroLocker.sol

183:         for (uint256 index = 0; index < ownerToNFTokenCount[_owner]; index++) {

[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 (4):

File: ZeroLockerTimelock.sol

36:     bytes32 public constant TIMELOCK_ADMIN_ROLE =

38:     bytes32 public constant PROPOSER_ROLE = keccak256("PROPOSER_ROLE");

39:     bytes32 public constant EXECUTOR_ROLE = keccak256("EXECUTOR_ROLE");

40:     bytes32 public constant CANCELLER_ROLE = keccak256("CANCELLER_ROLE");

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

Instances (4):

File: FeeDistributor.sol

113:             uint256 mid = (min + max + 2) / 2;

135:             uint256 mid = (min + max + 2) / 2;
File: ZeroLocker.sol

914:             uint256 _mid = (_min + _max + 1) / 2;

983:             uint256 _mid = (_min + _max + 1) / 2;

[GAS-11] Splitting require() statements that use && saves gas

Instances (2):

File: VestedZeroLend.sol

57:             require(!blacklist[to] && !blacklist[from], "blacklist");
File: ZeroLend.sol

39:         require(!blacklisted[from] && !blacklisted[to], "blacklisted");

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

Instances (19):

File: BonusPool.sol

41:             require(IERC20(tkn).balanceOf(address(this)) > 0, "No tokens");
File: FeeDistributor.sol

229:                 if (balanceOf > 0)
File: StreamedVesting.sol

122:         require(val > 0, "no claimable amount");

138:         require(pendingAmt > 0, "no pending amount");
File: ZLRewardsController.sol

297:             if (i > 0) {

308:             if (startTime > 0) {

710:         if (rewardsPerSecond == 0 && lastRPS > 0) {

776:         if (lpSupply > 0) {
File: ZeroLocker.sol

368:         return size > 0;

513:             if (oldLocked.end > block.timestamp && oldLocked.amount > 0) {

519:             if (newLocked.end > block.timestamp && newLocked.amount > 0) {

545:         if (_epoch > 0) {

743:         require(_value > 0, "value = 0"); // dev: need non-zero value

744:         require(_locked.amount > 0, "No existing lock found");

760:         require(_value > 0, "value = 0"); // dev: need non-zero value

816:         assert(_value > 0); // dev: need non-zero value

817:         require(_locked.amount > 0, "No existing lock found");

844:         require(_locked.amount > 0, "Nothing is locked");
File: ZeroLockerTimelock.sol

172:         return getTimestamp(id) > 0;

Non Critical Issues

Issue Instances
NC-1 Return values of approve() not checked 3
NC-2 Event is missing indexed fields 4
NC-3 Constants should be defined rather than using magic numbers 3

[NC-1] Return values of approve() not checked

Not all IERC20 implementations revert() when there's a failure in approve(). The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything

Instances (3):

File: BonusPool.sol

22:         _underlying.approve(_vesting, type(uint256).max);
File: StreamedVesting.sol

48:         underlying.approve(address(_locker), type(uint256).max);
File: ZeroLocker.sol

461:         _approve(_approved, _tokenId);

[NC-2] 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 (4):

File: interfaces/IBonusPool.sol

12:     event SetBonusBPS(uint256 oldValue, uint256 newValue);
File: interfaces/IZeroLocker.sol

72:     event Deposit(

81:     event Withdraw(

88:     event Supply(uint256 prevSupply, uint256 supply);

[NC-3] Constants should be defined rather than using magic numbers

Instances (3):

File: BonusPool.sol

23:         _setBonusBps(20000); // 20%
File: StakingEmissions.sol

45:         initEpoch(86400 * 7, block.timestamp);
File: StreamedVesting.sol

214:         uint256 penaltyE20 = 95e18 - (75e18 * percentage) / 1e18;

Low Issues

Issue Instances
L-1 Do not use deprecated library functions 5
L-2 Empty Function Body - Consider commenting why 4
L-3 Initializers could be front-run 16
L-4 Unsafe ERC20 operation(s) 12

[L-1] Do not use deprecated library functions

Instances (5):

File: ZeroLockerTimelock.sol

110:         _setupRole(TIMELOCK_ADMIN_ROLE, address(this));

114:             _setupRole(TIMELOCK_ADMIN_ROLE, admin);

119:             _setupRole(PROPOSER_ROLE, proposers[i]);

120:             _setupRole(CANCELLER_ROLE, proposers[i]);

125:             _setupRole(EXECUTOR_ROLE, executors[i]);

[L-2] Empty Function Body - Consider commenting why

Instances (4):

File: ZLRewardsController.sol

564:     function handleActionBefore(address _user) external {}

571:     function beforeLockUpdate(address _user) external {}
File: ZeroLocker.sol

400:             returns (bytes4) {} catch (bytes memory reason) {
File: ZeroLockerTimelock.sol

148:     receive() external payable {}

[L-3] 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 (16):

File: FeeDistributor.sol

41:     function initialize(

44:     ) external initializer {
File: StakingEmissions.sol

30:     function initialize(

34:     ) external initializer {

39:         __Ownable_init();

40:         __Pausable_init();
File: StreamedVesting.sol

38:     function initialize(

43:     ) external initializer {

50:         __Ownable_init();

51:         __Pausable_init();
File: ZLRewardsController.sol

116:     function initialize(

123:     ) public initializer {

128:         __Ownable_init();

129:         __Pausable_init();
File: ZeroLocker.sol

71:     function initialize(address _underlying) public initializer {

71:     function initialize(address _underlying) public initializer {

[L-4] Unsafe ERC20 operation(s)

Instances (12):

File: BonusPool.sol

22:         _underlying.approve(_vesting, type(uint256).max);

43:             IERC20(tkn).transfer(msg.sender, amount);
File: FeeDistributor.sol

264:             token.transfer(who, amount);

297:                 token.transfer(who, amount);
File: StakingEmissions.sol

57:         token.transfer(address(feeDistributor), amtPerEpoch);
File: StreamedVesting.sol

48:         underlying.approve(address(_locker), type(uint256).max);

105:             underlying.transferFrom(

129:         underlying.transfer(msg.sender, val);

147:         underlying.transfer(msg.sender, pendingAmt - penaltyAmt);

148:         underlying.transfer(dead, penaltyAmt);
File: ZeroLocker.sol

691:             assert(underlying.transferFrom(from, address(this), _value));

885:         assert(underlying.transfer(msg.sender, value));

Medium Issues

Issue Instances
M-1 Centralization Risk for trusted owners 28

[M-1] Centralization Risk for trusted owners

Impact:

Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.

Instances (28):

File: BonusPool.sol

17: contract BonusPool is Ownable, IBonusPool {

30:     function setBonusBps(uint256 amount) external onlyOwner {

34:     function withdrawStuckTokens(address tkn) public onlyOwner {
File: StakingEmissions.sol

44:     function start() external onlyOwner {
File: StreamedVesting.sol

56:     function start() external onlyOwner {
File: VestedZeroLend.sol

17: contract VestedZeroLend is ERC20, ERC20Burnable, Ownable {

34:     function addblacklist(address who, bool what) external onlyOwner {

38:     function addwhitelist(address who, bool what) external onlyOwner {

42:     function toggleWhitelist(bool from, bool to) external onlyOwner {
File: ZLRewardsController.sol

155:     ) external onlyOwner {

167:     function start() public onlyOwner {

198:     ) external onlyOwner {

228:     ) external onlyOwner {

291:     ) external onlyOwner {

333:     ) external onlyOwner {

484:     ) external onlyOwner {

696:     function setEndingTimeUpdateCadence(uint256 _lapse) external onlyOwner {

707:     function registerRewardDeposit(uint256 _amount) external onlyOwner {

755:     function pause() external onlyOwner {

762:     function unpause() external onlyOwner {

796:     function setAddressWLstatus(address user, bool status) external onlyOwner {

803:     function toggleWhitelist() external onlyOwner {
File: ZeroLend.sol

26:     function mint(uint256 amt) public onlyOwner {

30:     function toggleBlacklist(address who, bool what) public onlyOwner {
File: ZeroLockerTimelock.sol

32:     AccessControlEnumerable,

259:     ) public virtual onlyRole(PROPOSER_ROLE) {

284:     ) public virtual onlyRole(PROPOSER_ROLE) {

340:     function cancel(bytes32 id) public virtual onlyRole(CANCELLER_ROLE) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment