Velodrome Finance contest - MadWookie's results

A base layer AMM on Optimism, inspired by Solidly.

General Information

Platform: Code4rena

Start Date: 23/05/2022

Pot Size: $75,000 USDC

Total HM: 23

Participants: 75

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 13

Id: 130

League: ETH

Velodrome Finance

Findings Distribution

Researcher Performance

Rank: 63/75

Findings: 1

Award: $52.50

🌟 Selected for report: 0

🚀 Solo Findings: 0

Gas Report

All calculations for gas saved came from

forge test --gas-report

Gauge.sol

constructor

File: Gauge.sol#110-119

isForPair = _isForPair;
        if (isForPair) {
            (address _token0, address _token1) = IPair(stake).tokens();
            IBribe(bribe).addRewardToken(_token0);
            isReward[_token0] = true;
            rewards.push(_token0);
            IBribe(bribe).addRewardToken(_token1);
            isReward[_token1] = true;
            rewards.push(_token1);
        }

Only set isForpair in the if statement. The default value for bools are false so if _isForPair is false gas is being wasted.

I suggest changing it to the code below, which saves 2,245 gas on deployment.

 if (_isForPair) {
            (address _token0, address _token1) = IPair(stake).tokens();
            IBribe(bribe).addRewardToken(_token0);
            isReward[_token0] = true;
            rewards.push(_token0);
            IBribe(bribe).addRewardToken(_token1);
            isReward[_token1] = true;
            rewards.push(_token1);
            isForPair = _isForPair;
        }

Bribe.sol

function notifyRewardAmount()

File: Bribe.sol#41-60

function notifyRewardAmount(address token, uint amount) external lock {
      require(amount > 0);
      if (!isReward[token]) {
        require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
      }
      // bribes kick in at the start of next bribe period
      uint adjustedTstamp = getEpochStart(block.timestamp);
      uint epochRewards = tokenRewardsPerEpoch[token][adjustedTstamp];

      _safeTransferFrom(token, msg.sender, address(this), amount);
      tokenRewardsPerEpoch[token][adjustedTstamp] = epochRewards + amount;

      if (!isReward[token]) {
          isReward[token] = true;
          rewards.push(token);
          IGauge(gauge).addBribeRewardToken(token);
      }

      emit NotifyReward(msg.sender, token, adjustedTstamp, amount);
  }

isReward[token] is used twice and can be cached in memory to save gas. The below implementation saves 149 gas per function call and 2,607 on deployment.

function notifyRewardAmount(address token, uint amount) external lock {
      require(amount > 0);
      bool _isReward = isReward[token];

      if (!_isReward) {
        require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
      }
      // bribes kick in at the start of next bribe period
      uint adjustedTstamp = getEpochStart(block.timestamp);
      uint epochRewards = tokenRewardsPerEpoch[token][adjustedTstamp];

      _safeTransferFrom(token, msg.sender, address(this), amount);
      tokenRewardsPerEpoch[token][adjustedTstamp] = epochRewards + amount;

      if (!_isReward) {
          isReward[token] = true;
          rewards.push(token);
          IGauge(gauge).addBribeRewardToken(token);
      }

      emit NotifyReward(msg.sender, token, adjustedTstamp, amount);
  }

Voter.sol

function _reset()

File: Voter.sol#118

totalWeight -= uint256(_totalWeight);

totalWeight should never underflow here. Changing to the code below saves 74 gas.

unchecked {
        totalWeight -= uint256(_totalWeight);
        }

For-loops

For loops can be made cheaper by using unchecked arithmetic when increasing the incrementor. In my opinion since this code base has a great deal of for-loops it would be a large benefit even if readability is reduced a little. At the very minimum i++ should be changed to ++i.

An example from Gauge.sol File: Gauge.sol#179-185

for (uint i = 0; i < numRewards; i++) {
            address token = sb.rewards(i);
            uint epochRewards = sb.deliverReward(token, bribeStart);
            if (epochRewards > 0) {
                _notifyBribeAmount(token, epochRewards, bribeStart);
            }
        }

Can be changed to

for (uint i = 0; i < numRewards;) { 
            address token = sb.rewards(i);
            uint epochRewards = sb.deliverReward(token, bribeStart);
            if (epochRewards > 0) {
                _notifyBribeAmount(token, epochRewards, bribeStart);
            }
            unchecked{
                ++i; //++i is cheaper than i++
            }
        }

Instances where this can be implented

#0 - GalloDaSballo

2022-06-30T00:51:20Z

constructor

Will give 100 gas for not using a SLOAD

noitifyReward

149, amazing report and work!

function _reset()

79 per the math shown (80 is avg for unchecked, seems about right)

Unchecked ++i

25 per instance * 22 = 550

Great little report with actual gas estimates, good job!

Would highly recommend hunting for more immutable and SLOAD related savings which will help you score tons of savings with minimal report length

Total Gas Saved 878

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