Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Gas Optimizations #210

Open
code423n4 opened this issue May 30, 2022 · 2 comments
Open

Gas Optimizations #210

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

Comments

@code423n4
Copy link
Contributor

Table of Contents:

Cheap Contract Deployment Through Clones

See @audit tag:

contracts/contracts/Pair.sol:
   93:         fees = address(new PairFees(_token0, _token1));

contracts/contracts/factories/BribeFactory.sol:
  10:         last_gauge = address(new Bribe());

contracts/contracts/factories/GaugeFactory.sol:
  24:         last_gauge = address(new Gauge(_pool, _bribe, _ve, msg.sender, isPair));
  30:         last_gauge = address(new Gauge(_pool, _bribe, _ve, _voter, isPair));

contracts/contracts/factories/PairFactory.sol:
  95:         pair = address(new Pair{salt:salt}());

There's a way to save a significant amount of gas on deployment using Clones: https://www.youtube.com/watch?v=3Mw-pMmJ7TA .

This is a solution that was adopted, as an example, by Porter Finance. They realized that deploying using clones was 10x cheaper:

I suggest applying a similar pattern (use the cloneDeterministic method to mimic the current create2 in PairFactory.sol)

Bytes constants are more efficient than string constants

From the Solidity doc:

If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper.

Why do Solidity examples use bytes32 type instead of string?

bytes32 uses less gas because it fits in a single word of the EVM, and string is a dynamically sized-type which has current limitations in Solidity (such as can't be returned from a function to a contract).

If data can fit into 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is cheaper in solidity. Basically, any fixed size variable in solidity is cheaper than variable size. That will save gas on the contract.

Instances of string constant that can be replaced by bytes(1..32) constant :

Velo.sol:6:    string public constant name = "Velodrome";
Velo.sol:7:    string public constant symbol = "VELO";
VotingEscrow.sol:122:    string constant public name = "veNFT";
VotingEscrow.sol:123:    string constant public symbol = "veNFT";
VotingEscrow.sol:124:    string constant public version = "1.0.0";

> 0 is less efficient than != 0 for unsigned integers (with proof)

!= 0 costs less gas compared to > 0 for unsigned integers in require statements with the optimizer enabled (6 gas)

Proof: While it may seem that > 0 is cheaper than !=, this is only true without the optimizer enabled and outside a require statement. If you enable the optimizer at 10k AND you're in a require statement, this will save gas. You can see this tweet for more proofs: https://twitter.com/gzeon/status/1485428085885640706

I suggest changing > 0 with != 0 here:

Bribe.sol:42:      require(amount > 0);
Bribe.sol:93:      require(token.code.length > 0);
Bribe.sol:100:      require(token.code.length > 0);
Gauge.sol:512:        require(amount > 0);
Gauge.sol:592:        require(amount > 0);
Gauge.sol:613:        require(rewardRate[token] > 0);
Gauge.sol:665:        require(token.code.length > 0);
Gauge.sol:672:        require(token.code.length > 0);
Gauge.sol:679:        require(token.code.length > 0);
Pair.sol:303:        require(liquidity > 0, 'ILM'); // Pair: INSUFFICIENT_LIQUIDITY_MINTED
Pair.sol:322:        require(amount0 > 0 && amount1 > 0, 'ILB'); // Pair: INSUFFICIENT_LIQUIDITY_BURNED
Pair.sol:336:        require(amount0Out > 0 || amount1Out > 0, 'IOA'); // Pair: INSUFFICIENT_OUTPUT_AMOUNT
Pair.sol:353:        require(amount0In > 0 || amount1In > 0, 'IIA'); // Pair: INSUFFICIENT_INPUT_AMOUNT
Pair.sol:361:        require(_k(_balance0, _balance1) >= _k(_reserve0, _reserve1), 'K'); // Pair: K
Pair.sol:522:        require(token.code.length > 0);
PairFees.sol:20:        require(token.code.length > 0);
Router.sol:58:        require(amountA > 0, 'Router: INSUFFICIENT_AMOUNT');
Router.sol:59:        require(reserveA > 0 && reserveB > 0, 'Router: INSUFFICIENT_LIQUIDITY');
Router.sol:410:        require(token.code.length > 0);
Router.sol:417:        require(token.code.length > 0);
Voter.sol:352:        require(token.code.length > 0);
VotingEscrow.sol:772:        require(_value > 0); // dev: need non-zero value
VotingEscrow.sol:773:        require(_locked.amount > 0, 'No existing lock found');
VotingEscrow.sol:785:        require(_value > 0); // dev: need non-zero value
VotingEscrow.sol:819:        assert(_value > 0); // dev: need non-zero value
VotingEscrow.sol:820:        require(_locked.amount > 0, 'No existing lock found');
VotingEscrow.sol:835:        require(_locked.amount > 0, 'Nothing is locked');

Also, please enable the Optimizer.

>= is cheaper than > (and <= cheaper than <)

Strict inequalities (>) are more expensive than non-strict ones (>=). This is due to some supplementary checks (ISZERO, 3 gas). This also holds true between <= and <.

Consider replacing strict inequalities with non-strict ones to save some gas here:

factories/PairFactory.sol:90:        (address token0, address token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
Pair.sol:351:        uint amount0In = _balance0 > _reserve0 - amount0Out ? _balance0 - (_reserve0 - amount0Out) : 0;
Pair.sol:352:        uint amount1In = _balance1 > _reserve1 - amount1Out ? _balance1 - (_reserve1 - amount1Out) : 0;
Router.sol:41:        (token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);

Use shift right/left instead of division/multiplication if possible

While the DIV / MUL opcode uses 5 gas, the SHR / SHL opcode only uses 3 gas. Furthermore, beware that Solidity's division operation also includes a division-by-0 prevention which is bypassed using shifting. Eventually, overflow checks are never performed for shift operations as they are done for arithmetic operations. Instead, the result is always truncated.

  • Use >> 1 instead of / 2
  • Use >> 2 instead of / 4
  • Use << 3 instead of * 8

Affected code:

Gauge.sol:225:            uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Gauge.sol:257:            uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
Gauge.sol:289:            uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
RewardsDistributor.sol:107:            uint _mid = (_min + _max + 2) / 2;
RewardsDistributor.sol:123:            uint _mid = (_min + _max + 2) / 2;
VotingEscrow.sol:891:            uint _mid = (_min + _max + 1) / 2;
VotingEscrow.sol:947:            uint _mid = (_min + _max + 1) / 2;
VotingEscrow.sol:1171:            uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow

An array's length should be cached to save gas in for-loops

Reading array length at each iteration of the loop consumes more gas than necessary.

In the best case scenario (length read on a memory variable), caching the array length in the stack saves around 3 gas per iteration.
In the worst case scenario (external calls at each iteration), the amount of gas wasted can be massive.

Here, I suggest storing the array's length in a variable before the for-loop, and use this new variable instead:

Gauge.sol:353:        for (uint i = 0; i < tokens.length; i++) {
Minter.sol:57:        for (uint i = 0; i < claimants.length; i++) {
Pair.sol:257:        for (uint i = 0; i < _prices.length; i++) {
RewardsDistributor.sol:301:        for (uint i = 0; i < _tokenIds.length; i++) {
Router.sol:90:        for (uint i = 0; i < routes.length; i++) {
Router.sol:316:        for (uint i = 0; i < routes.length; i++) {
Voter.sol:76:        for (uint i = 0; i < _tokens.length; i++) {
Voter.sol:266:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:304:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:310:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:346:        for (uint x = 0; x < _gauges.length; x++) {
VotingEscrow.sol:1146:        for (uint i = 0; i < _tokenIds.length; i++) {
VotingEscrow.sol:1193:        for (uint i = 0; i < _tokenIds.length; i++) {
VotingEscrow.sol:1225:                for (uint i = 0; i < srcRepOld.length; i++) {
VotingEscrow.sol:1249:                for (uint i = 0; i < dstRepOld.length; i++) {
VotingEscrow.sol:1295:                for (uint i = 0; i < srcRepOld.length; i++) {
VotingEscrow.sol:1320:                for (uint i = 0; i < dstRepOld.length; i++) {

++i costs less gas compared to i++ or i += 1 (same for --i vs i-- or i -= 1)

Pre-increments and pre-decrements are cheaper.

For a uint256 i variable, the following is true with the Optimizer enabled at 10k:

Increment:

  • i += 1 is the most expensive form
  • i++ costs 6 gas less than i += 1
  • ++i costs 5 gas less than i++ (11 gas less than i += 1)

Decrement:

  • i -= 1 is the most expensive form
  • i-- costs 11 gas less than i -= 1
  • --i costs 5 gas less than i-- (16 gas less than i -= 1)

Note that post-increments (or post-decrements) return the old value before incrementing or decrementing, hence the name post-increment:

uint i = 1;  
uint j = 2;
require(j == i++, "This will be false as i is incremented after the comparison");

However, pre-increments (or pre-decrements) return the new value:

uint i = 1;  
uint j = 2;
require(j == ++i, "This will be true as i is incremented before the comparison");

In the pre-increment case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2.

Affected code:

Gauge.sol:179:        for (uint i = 0; i < numRewards; i++) {
Gauge.sol:353:        for (uint i = 0; i < tokens.length; i++) {
Gauge.sol:405:        for (uint i = _startIndex; i < _endIndex; i++) {
Gauge.sol:426:        for (uint i; i < length; i++) {
Gauge.sol:448:            for (uint i = _startIndex; i < _endIndex; i++) {
Gauge.sol:484:            for (uint i = _startIndex; i < _endIndex; i++) {
Minter.sol:57:        for (uint i = 0; i < claimants.length; i++) {
Pair.sol:257:        for (uint i = 0; i < _prices.length; i++) {
Pair.sol:389:        for (uint i = 0; i < 255; i++) {
RewardsDistributor.sol:75:        for (uint i = 0; i < 20; i++) {
RewardsDistributor.sol:105:        for (uint i = 0; i < 128; i++) {
RewardsDistributor.sol:121:        for (uint i = 0; i < 128; i++) {
RewardsDistributor.sol:148:        for (uint i = 0; i < 20; i++) {
RewardsDistributor.sol:195:        for (uint i = 0; i < 50; i++) {
RewardsDistributor.sol:199:                user_epoch += 1;
RewardsDistributor.sol:252:        for (uint i = 0; i < 50; i++) {
RewardsDistributor.sol:256:                user_epoch += 1;
RewardsDistributor.sol:301:        for (uint i = 0; i < _tokenIds.length; i++) {
Router.sol:90:        for (uint i = 0; i < routes.length; i++) {
Router.sol:316:        for (uint i = 0; i < routes.length; i++) {
VelodromeLibrary.sol:24:        for (uint i = 0; i < 255; i++) {
Voter.sol:76:        for (uint i = 0; i < _tokens.length; i++) {
Voter.sol:103:        for (uint i = 0; i < _poolVoteCnt; i ++) {
Voter.sol:128:        for (uint i = 0; i < _poolCnt; i ++) {
Voter.sol:143:        for (uint i = 0; i < _poolCnt; i++) {
Voter.sol:147:        for (uint i = 0; i < _poolCnt; i++) {
Voter.sol:266:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:272:        for (uint i = start; i < end; i++) {
Voter.sol:304:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:310:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:340:        for (uint x = start; x < finish; x++) {
Voter.sol:346:        for (uint x = 0; x < _gauges.length; x++) {
VotingEscrow.sol:137:            digits++;
VotingEscrow.sol:142:            digits -= 1;
VotingEscrow.sol:453:        ownerToNFTokenCount[_to] += 1;
VotingEscrow.sol:514:        ownerToNFTokenCount[_from] -= 1;
VotingEscrow.sol:655:                _epoch += 1;
VotingEscrow.sol:1146:        for (uint i = 0; i < _tokenIds.length; i++) {
VotingEscrow.sol:1193:        for (uint i = 0; i < _tokenIds.length; i++) {
VotingEscrow.sol:1225:                for (uint i = 0; i < srcRepOld.length; i++) {
VotingEscrow.sol:1249:                for (uint i = 0; i < dstRepOld.length; i++) {
VotingEscrow.sol:1295:                for (uint i = 0; i < srcRepOld.length; i++) {
VotingEscrow.sol:1320:                for (uint i = 0; i < dstRepOld.length; i++) {
VotingEscrow.sol:1325:                for (uint i = 0; i < ownerTokenCount; i++) {

Consider using pre-increments and pre-decrements where they are relevant (meaning: not where post-increments/decrements logic are relevant).

Increments can be unchecked

In Solidity 0.8+, there's a default overflow check on unsigned integers. It's possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.

ethereum/solidity#10695

Affected code:

contracts/contracts/Gauge.sol:
  179:         for (uint i = 0; i < numRewards; i++) {
  353:         for (uint i = 0; i < tokens.length; i++) {
  405:         for (uint i = _startIndex; i < _endIndex; i++) {
  426:         for (uint i; i < length; i++) {
  448:             for (uint i = _startIndex; i < _endIndex; i++) {
  484:             for (uint i = _startIndex; i < _endIndex; i++) {

contracts/contracts/Minter.sol:
  57:         for (uint i = 0; i < claimants.length; i++) {

contracts/contracts/Pair.sol:
  257:         for (uint i = 0; i < _prices.length; i++) {
  389:         for (uint i = 0; i < 255; i++) {

contracts/contracts/RewardsDistributor.sol:
   75:         for (uint i = 0; i < 20; i++) {
  105:         for (uint i = 0; i < 128; i++) {
  121:         for (uint i = 0; i < 128; i++) {
  148:         for (uint i = 0; i < 20; i++) {
  195:         for (uint i = 0; i < 50; i++) {
  252:         for (uint i = 0; i < 50; i++) {
  301:         for (uint i = 0; i < _tokenIds.length; i++) {

contracts/contracts/Router.sol:
   90:         for (uint i = 0; i < routes.length; i++) {
  316:         for (uint i = 0; i < routes.length; i++) {

contracts/contracts/VelodromeLibrary.sol:
  24:         for (uint i = 0; i < 255; i++) {

contracts/contracts/Voter.sol:
   76:         for (uint i = 0; i < _tokens.length; i++) {
  103:         for (uint i = 0; i < _poolVoteCnt; i ++) {
  128:         for (uint i = 0; i < _poolCnt; i ++) {
  143:         for (uint i = 0; i < _poolCnt; i++) {
  147:         for (uint i = 0; i < _poolCnt; i++) {
  266:         for (uint i = 0; i < _gauges.length; i++) {
  272:         for (uint i = start; i < end; i++) {
  304:         for (uint i = 0; i < _gauges.length; i++) {
  310:         for (uint i = 0; i < _gauges.length; i++) {
  340:         for (uint x = start; x < finish; x++) {
  346:         for (uint x = 0; x < _gauges.length; x++) {

contracts/contracts/VotingEscrow.sol:
   632:             for (uint i = 0; i < 255; ++i) {
   886:         for (uint i = 0; i < 128; ++i) {
   942:         for (uint i = 0; i < 128; ++i) {
  1017:         for (uint i = 0; i < 255; ++i) {
  1146:         for (uint i = 0; i < _tokenIds.length; i++) {
  1193:         for (uint i = 0; i < _tokenIds.length; i++) {
  1225:                 for (uint i = 0; i < srcRepOld.length; i++) {
  1249:                 for (uint i = 0; i < dstRepOld.length; i++) {
  1295:                 for (uint i = 0; i < srcRepOld.length; i++) {
  1320:                 for (uint i = 0; i < dstRepOld.length; i++) {
  1325:                 for (uint i = 0; i < ownerTokenCount; i++) {

The code would go from:

for (uint256 i; i < numIterations; i++) {  //or i--
 // ...  
}  

to:

for (uint256 i; i < numIterations;) {  
 // ...  
 unchecked { ++i; }  //or unchecked { --i; }
}  

The risk of overflow is inexistant for uint256 here.

No need to explicitly initialize variables with default values

If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address...). Explicitly initializing it with its default value is an anti-pattern and wastes gas.

As an example: for (uint256 i = 0; i < numIterations; ++i) { should be replaced with for (uint256 i; i < numIterations; ++i) {

Affected code:

Gauge.sol:179:        for (uint i = 0; i < numRewards; i++) {
Gauge.sol:222:        uint lower = 0;
Gauge.sol:254:        uint lower = 0;
Gauge.sol:286:        uint lower = 0;
Gauge.sol:353:        for (uint i = 0; i < tokens.length; i++) {
Gauge.sol:481:        uint reward = 0;
Gauge.sol:551:        uint tokenId = 0;
Minter.sol:57:        for (uint i = 0; i < claimants.length; i++) {
Pair.sol:20:    uint public totalSupply = 0;
Pair.sol:61:    uint public index0 = 0;
Pair.sol:62:    uint public index1 = 0;
Pair.sol:257:        for (uint i = 0; i < _prices.length; i++) {
Pair.sol:273:        uint nextIndex = 0;
Pair.sol:274:        uint index = 0;
Pair.sol:389:        for (uint i = 0; i < 255; i++) {
RewardsDistributor.sol:73:        uint next_week = 0;
RewardsDistributor.sol:75:        for (uint i = 0; i < 20; i++) {
RewardsDistributor.sol:103:        uint _min = 0;
RewardsDistributor.sol:105:        for (uint i = 0; i < 128; i++) {
RewardsDistributor.sol:119:        uint _min = 0;
RewardsDistributor.sol:121:        for (uint i = 0; i < 128; i++) {
RewardsDistributor.sol:148:        for (uint i = 0; i < 20; i++) {
RewardsDistributor.sol:154:                int128 dt = 0;
RewardsDistributor.sol:170:        uint user_epoch = 0;
RewardsDistributor.sol:171:        uint to_distribute = 0;
RewardsDistributor.sol:195:        for (uint i = 0; i < 50; i++) {
RewardsDistributor.sol:227:        uint user_epoch = 0;
RewardsDistributor.sol:228:        uint to_distribute = 0;
RewardsDistributor.sol:252:        for (uint i = 0; i < 50; i++) {
RewardsDistributor.sol:299:        uint total = 0;
RewardsDistributor.sol:301:        for (uint i = 0; i < _tokenIds.length; i++) {
Router.sol:90:        for (uint i = 0; i < routes.length; i++) {
Router.sol:112:        uint _totalSupply = 0;
Router.sol:316:        for (uint i = 0; i < routes.length; i++) {
Velo.sol:9:    uint public totalSupply = 0;
VelodromeLibrary.sol:24:        for (uint i = 0; i < 255; i++) {
Voter.sol:76:        for (uint i = 0; i < _tokens.length; i++) {
Voter.sol:101:        uint256 _totalWeight = 0;
Voter.sol:103:        for (uint i = 0; i < _poolVoteCnt; i ++) {
Voter.sol:128:        for (uint i = 0; i < _poolCnt; i ++) {
Voter.sol:139:        uint256 _totalVoteWeight = 0;
Voter.sol:140:        uint256 _totalWeight = 0;
Voter.sol:141:        uint256 _usedWeight = 0;
Voter.sol:143:        for (uint i = 0; i < _poolCnt; i++) {
Voter.sol:147:        for (uint i = 0; i < _poolCnt; i++) {
Voter.sol:266:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:304:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:310:        for (uint i = 0; i < _gauges.length; i++) {
Voter.sol:346:        for (uint x = 0; x < _gauges.length; x++) {
VotingEscrow.sol:584:        int128 old_dslope = 0;
VotingEscrow.sol:585:        int128 new_dslope = 0;
VotingEscrow.sol:622:        uint block_slope = 0; // dblock/dt
VotingEscrow.sol:632:            for (uint i = 0; i < 255; ++i) {
VotingEscrow.sol:636:                int128 d_slope = 0;
VotingEscrow.sol:884:        uint _min = 0;
VotingEscrow.sol:886:        for (uint i = 0; i < 128; ++i) {
VotingEscrow.sol:940:        uint _min = 0;
VotingEscrow.sol:942:        for (uint i = 0; i < 128; ++i) {
VotingEscrow.sol:960:        uint d_block = 0;
VotingEscrow.sol:961:        uint d_t = 0;
VotingEscrow.sol:996:        uint dt = 0;
VotingEscrow.sol:1017:        for (uint i = 0; i < 255; ++i) {
VotingEscrow.sol:1019:            int128 d_slope = 0;
VotingEscrow.sol:1145:        uint votes = 0;
VotingEscrow.sol:1146:        for (uint i = 0; i < _tokenIds.length; i++) {
VotingEscrow.sol:1168:        uint32 lower = 0;
VotingEscrow.sol:1192:        uint votes = 0;
VotingEscrow.sol:1193:        for (uint i = 0; i < _tokenIds.length; i++) {
VotingEscrow.sol:1225:                for (uint i = 0; i < srcRepOld.length; i++) {
VotingEscrow.sol:1249:                for (uint i = 0; i < dstRepOld.length; i++) {
VotingEscrow.sol:1295:                for (uint i = 0; i < srcRepOld.length; i++) {
VotingEscrow.sol:1320:                for (uint i = 0; i < dstRepOld.length; i++) {
VotingEscrow.sol:1325:                for (uint i = 0; i < ownerTokenCount; i++) {

I suggest removing explicit initializations for default values.

Reduce the size of error messages (Long revert Strings)

Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.

Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.

Revert strings > 32 bytes:

VotingEscrow.sol:1247:                    "dstRep would have too many tokenIds"
VotingEscrow.sol:1317:                    "dstRep would have too many tokenIds"
VotingEscrow.sol:1380:            "VotingEscrow::delegateBySig: invalid signature"
VotingEscrow.sol:1384:            "VotingEscrow::delegateBySig: invalid nonce"
VotingEscrow.sol:1388:            "VotingEscrow::delegateBySig: signature expired" 

I suggest shortening the revert strings to fit in 32 bytes.

Use Custom Errors instead of Revert Strings to save Gas

Solidity 0.8.4 introduced custom errors. They are more gas efficient than revert strings, when it comes to deploy cost as well as runtime cost when the revert condition is met. Use custom errors instead of revert strings for gas savings.

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)

Source: https://blog.soliditylang.org/2021/04/21/custom-errors/:

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.

Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).

I suggest replacing all revert strings with custom errors in the solution. Impacted files:

  • factories/GaugeFactory.sol
  • factories/PairFactory.sol
  • redeem/RedemptionReceiver.sol
  • redeem/RedemptionSender.sol
  • Bribe.sol
  • Gauge.sol
  • Minter.sol
  • Pair.sol
  • PairFees.sol
  • RewardsDistributor.sol
  • Router.sol
  • Velo.sol
  • VeloGovernor.sol
  • Voter.sol
  • VotingEscrow.sol
@code423n4 code423n4 added bug Something isn't working G (Gas Optimization) labels May 30, 2022
code423n4 added a commit that referenced this issue May 30, 2022
@GalloDaSballo
Copy link
Collaborator

Great report, unfortunately missing any potential immutable / storage saving operation that would give it a lot of points

@GalloDaSballo
Copy link
Collaborator

Cheap Contract Deployment Through Clones

Finding is valid although hard to quantify

Bytes constants are more efficient than string constants

Would have liked to see an estimate

> 0 is less efficient than != 0 for unsigned integers (with proof)

Doesn't work after 0.8.12

>= is cheaper than > (and <= cheaper than <)

Intuitively disagreed, went and tested
Screenshot 2022-07-04 at 00 17 05

is 3 gas cheaper as expected

Use shift right/left instead of division/multiplication if possible

Saves 2 gas per instance + 20 for unchecked
8 * 22 = 176

An array's length should be cached to save gas in for-loops

3 gas per instance
17 * 3 = 51

++i and unchecked

25 per instance
44 * 25 = 1100

No need to explicitly initialize variables with default values

3 per instance
73 * 3 = 219

Reduce the size of error messages (Long revert Strings)

6 per instance
5 * 6 = 30

Use Custom Errors instead of Revert Strings to save Gas

Valid but hard to quantify without POC

Very well written report, with a couple of invalid findings, unfortunately missed the immutable for big points

Total Gas Saved
1576

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

No branches or pull requests

2 participants