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
Rank: 5/75
Findings: 5
Award: $5,064.40
đ Selected for report: 4
đ Solo Findings: 0
2912.922 USDC - $2,912.92
Users can get unlimited votes which leads to them:
_mint()
calls _moveTokenDelegates()
to set up delegation...
File: contracts/contracts/VotingEscrow.sol #1 462 function _mint(address _to, uint _tokenId) internal returns (bool) { 463 // Throws if `_to` is zero address 464 assert(_to != address(0)); 465 // TODO add delegates 466 // checkpoint for gov 467 _moveTokenDelegates(address(0), delegates(_to), _tokenId);
and _transferFrom()
calls _moveTokenDelegates()
to transfer delegates...
File: contracts/contracts/VotingEscrow.sol #2 301 function _transferFrom( 302 address _from, 303 address _to, 304 uint _tokenId, 305 address _sender 306 ) internal { 307 require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached"); 308 // Check requirements 309 require(_isApprovedOrOwner(_sender, _tokenId)); 310 // Clear approval. Throws if `_from` is not the current owner 311 _clearApproval(_from, _tokenId); 312 // Remove NFT. Throws if `_tokenId` is not a valid NFT 313 _removeTokenFrom(_from, _tokenId); 314 // TODO delegates 315 // auto re-delegate 316 _moveTokenDelegates(delegates(_from), delegates(_to), _tokenId);
but _burn()
does not transfer them back to address(0)
File: contracts/contracts/VotingEscrow.sol #3 517 function _burn(uint _tokenId) internal { 518 require(_isApprovedOrOwner(msg.sender, _tokenId), "caller is not owner nor approved"); 519 520 address owner = ownerOf(_tokenId); 521 522 // Clear approval 523 approve(address(0), _tokenId); 524 // TODO add delegates 525 // Remove token 526 _removeTokenFrom(msg.sender, _tokenId); 527 emit Transfer(owner, address(0), _tokenId); 528 }
A user can deposit a token, lock it, wait for the lock to expire, transfer the token to another address, and repeat. During each iteration, a new NFT is minted and checkpointed. Calls to getPastVotes()
will show the wrong values, since it will think the account still holds the delegation of the burnt NFT. Bribes and gauges also look at the checkpoints and will also have the wrong information
Code inspection
Call _moveTokenDelegates(owner,address(0))
in _burn()
#0 - pooltypes
2022-06-10T02:44:12Z
Nice catch! We intended to fix this issue (see TODO), included in our mainnet deploy. Thanks for surfacing
#1 - GalloDaSballo
2022-06-26T23:37:25Z
The warden has shown an exploit that, leveraging the _moveTokenDelegates
function, which is not present in burn
can allow any attacker to inflate their votes.
The sponsor has confirmed and they indeed have mitigated the issue in their deployed code: https://optimistic.etherscan.io/address/0x9c7305eb78a432ced5C4D14Cac27E8Ed569A2e26#code#L873
đ Selected for report: MiloTruck
Also found by: 0x52, Dravee, IllIllI, MaratCerby, WatchPug, unforgiven
Some ERC20 tokens, such as USDT, allow for charging a fee any time transfer()
or transferFrom()
is called. If a contract does not allow for amounts to change after transfers, subsequent transfer operations based on the original amount will revert()
due to the contract having an insufficient balance.
Note that even though a token may not currently charge a fee, a future upgrade to the token may institute one.
If there is only one user that has deposited a fee-on-transfer token, that user will be unable to withdraw their funds, because the amount available will be less than the amount stated during deposit, and therefore the token's transfer()
call will revert during withdrawal. For more users, consider what happens if the token has a 10% fee-on-transfer fee - deposits will be underfunded by 10%, and the users trying to withdraw the last 10% of deposits/rewards will have their calls revert due to the contract not holding enough tokens. If a whale does a large withdrawal, the extra 10% that that whale gets will mean that many users will not be able to withdraw anything at all.
The _value
input parameter is what's stored in _locked.amount
, and this is stored before the actual transfer is done on line 748:
File: contracts/contracts/VotingEscrow.sol #1 720 function _deposit_for( 721 uint _tokenId, 722 uint _value, 723 uint unlock_time, 724 LockedBalance memory locked_balance, 725 DepositType deposit_type 726 ) internal { 727 LockedBalance memory _locked = locked_balance; 728 uint supply_before = supply; 729 730 supply = supply_before + _value; 731 LockedBalance memory old_locked; 732 (old_locked.amount, old_locked.end) = (_locked.amount, _locked.end); 733 // Adding to existing lock, or if a lock is expired - creating a new one 734 _locked.amount += int128(int256(_value)); 735 if (unlock_time != 0) { 736 _locked.end = unlock_time; 737 } 738 locked[_tokenId] = _locked; 739 740 // Possibilities: 741 // Both old_locked.end could be current or expired (>/< block.timestamp) 742 // value == 0 (extend lock) or value > 0 (add to lock or extend lock) 743 // _locked.end > block.timestamp (always) 744 _checkpoint(_tokenId, old_locked, _locked); 745 746 address from = msg.sender; 747 if (_value != 0 && deposit_type != DepositType.MERGE_TYPE) { 748 assert(IERC20(token).transferFrom(from, address(this), _value)); 749 } 750 751 emit Deposit(from, _tokenId, _value, _locked.end, deposit_type, block.timestamp); 752 emit Supply(supply_before, supply_before + _value); 753 }
During withdrawal, the amount previously stored (line 850) is transferred (line 861), which will be wrong for fee-on-transfer tokens:
File: contracts/contracts/VotingEscrow.sol #2 850 uint value = uint(int256(_locked.amount)); 851 852 locked[_tokenId] = LockedBalance(0,0); 853 uint supply_before = supply; 854 supply = supply_before - value; 855 856 // old_locked can have either expired <= timestamp or zero end 857 // _locked has only 0 end 858 // Both can have >= 0 amount 859 _checkpoint(_tokenId, _locked, LockedBalance(0,0)); 860 861 assert(IERC20(token).transfer(msg.sender, value));
Code inspection
Measure the contract balance before and after the call to transferFrom()
, and use the difference between the two as the amount, rather than the amount stated
#0 - pooltypes
2022-06-13T18:38:56Z
Duplicate of #222
#1 - GalloDaSballo
2022-06-28T22:40:13Z
Dup of #222
đ Selected for report: IllIllI
Also found by: Ruhum, rotcivegaf
524.326 USDC - $524.33
https://github.com/code-423n4/2022-05-velodrome/blob/7fda97c570b758bbfa7dd6724a336c43d4041740/contracts/contracts/VotingEscrow.sol#L462-L472 https://github.com/code-423n4/2022-05-velodrome/blob/7fda97c570b758bbfa7dd6724a336c43d4041740/contracts/contracts/VotingEscrow.sol#L378-L406
veNFTs may be sent to contracts that cannot handle them, and therefore all rewards and voting power, as well as the underlying are locked forever
The original code had the following warning:
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients * are aware of the ERC721 protocol to prevent tokens from being forever locked.
The minting code, which creates the locks, does not do this check:
File: contracts/contracts/VotingEscrow.sol #1 462 function _mint(address _to, uint _tokenId) internal returns (bool) { 463 // Throws if `_to` is zero address 464 assert(_to != address(0)); 465 // TODO add delegates 466 // checkpoint for gov 467 _moveTokenDelegates(address(0), delegates(_to), _tokenId); 468 // Add NFT. Throws if `_tokenId` is owned by someone 469 _addTokenTo(_to, _tokenId); 470 emit Transfer(address(0), _to, _tokenId); 471 return true; 472 }
Once a lock is already minted, if it's transfered to a contract, the code does attempt to check for the issue:
File: contracts/contracts/VotingEscrow.sol #2 378 /// If `_to` is a smart contract, it calls `onERC721Received` on `_to` and throws if 379 /// the return value is not `bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))`. 380 /// @param _from The current owner of the NFT. 381 /// @param _to The new owner. 382 /// @param _tokenId The NFT to transfer. 383 /// @param _data Additional data with no specified format, sent in call to `_to`. 384 function safeTransferFrom( 385 address _from, 386 address _to, 387 uint _tokenId, 388 bytes memory _data 389 ) public { 390 _transferFrom(_from, _to, _tokenId, msg.sender); 391 392 if (_isContract(_to)) { 393 // Throws if transfer destination is a contract which does not implement 'onERC721Received' 394 try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4) {} catch ( 395 bytes memory reason 396 ) { 397 if (reason.length == 0) { 398 revert('ERC721: transfer to non ERC721Receiver implementer'); 399 } else { 400 assembly { 401 revert(add(32, reason), mload(reason)) 402 } 403 } 404 } 405 } 406 }
While the transfer function does in fact execute onERC721Received
, it doesn't actually do a check of the bytes4
variable - it only checks for a non-zero length. The ERC721 standard says that for the function call to be valid, it must return the bytes4
function selector, otherwise it's invalid. If a user of the escrow system uses a contract that is attempting to explicitly reject NFTs by returning zero in its onERC721Received()
function, the VotingEscrow
will interpret that response as success and will transfer the NFT, potentially locking it forever. If the lock is minted to a contract, no checks are done, and the NFT can be locked forever.
Code inspection
Call onERC721Received()
in _mint()
and ensure that the return value equals IERC721Receiver.onERC721Received.selector
in both _mint()
and safeTransferFrom()
#0 - GalloDaSballo
2022-06-28T22:47:07Z
While I would be surprised by any realistic scenario of a smart contract that would implement onERC721Received
and not return the specified selector, I have to acknowledge that technically we can get multiple false positives, especially when dealing with contracts that have a fallback
function .
That said, to me that was not sufficient to constitute a valid finding.
However, I went and checked the EIP-721 and the standard definition of safeTransferFrom
, which can be verified here:
https://eips.ethereum.org/EIPS/eip-721
According to the standard the function must check for the return value, and for that reason I believe the finding to be valid.
While I would be surprised if this causes any issue in the real world, because the function is non-conformant to the standard and technically a loss can happen because of it, I believe Medium Severity to be appropriate
#1 - GalloDaSballo
2022-06-28T22:48:09Z
To confirm, I doubt any meaningful loss will ever happen.
However this finding is basically: "Incorrect implementation of onERC721Received
" and it's a valid finding at that
đ Selected for report: IllIllI
Also found by: 0x1f8b, 0x52, 0xNazgul, 0xNineDec, AlleyCat, BouSalman, CertoraInc, Chom, Dravee, Funen, GimelSec, Hawkeye, MaratCerby, Nethermind, Picodes, RoiEvenHaim, SooYa, TerrierLover, WatchPug, _Adam, asutorufos, berndartmueller, c3phas, catchup, cccz, cryptphi, csanuragjain, delfin454000, djxploit, fatherOfBlocks, gzeon, hake, hansfriese, horsefacts, hyh, jayjonah8, minhquanym, oyc_109, p_crypt0, pauliax, robee, rotcivegaf, sach1r0, sashik_eth, simon135, sorrynotsorry, teddav, unforgiven, xiaoming90
990.875 USDC - $990.88
Issue | Instances | |
---|---|---|
1 | Math.max(<x>,0) used with int cast to uint | 4 |
2 | approveMax variable causes problems | 2 |
3 | Front-runable initializer | 1 |
4 | Incorrect comments | 1 |
5 | Only a billion checkpoints available | 1 |
6 | Unsafe use of transfer() /transferFrom() with IERC20 | 2 |
7 | require() should be used instead of assert() | 18 |
8 | _safeMint() should be used rather than _mint() wherever possible | 1 |
9 | Missing checks for address(0x0) when assigning values to address state variables | 33 |
10 | Open TODOs | 4 |
Total: 67 instances over 10 issues
Issue | Instances | |
---|---|---|
1 | Use two-phase ownership transfers | 4 |
2 | Avoid the use of sensitive terms | 3 |
3 | Consider addings checks for signature malleability | 2 |
4 | Return values of approve() not checked | 2 |
5 | require() /revert() statements should have descriptive reason strings | 93 |
6 | public functions not called by the contract should be declared external instead | 9 |
7 | Non-assembly method available | 1 |
8 | constant s should be defined rather than using magic numbers | 95 |
9 | Redundant cast | 4 |
10 | Numeric values having to do with time should use time units for readability | 7 |
11 | Large multiples of ten should use scientific notation (e.g. 1e6 ) rather than decimal literals (e.g. 1000000 ), for readability | 3 |
12 | Missing event for critical parameter change | 16 |
13 | Expressions for constant values such as a call to keccak256() , should use immutable rather than constant | 2 |
14 | Use scientific notation (e.g. 1e18 ) rather than exponentiation (e.g. 10**18 ) | 3 |
15 | Constant redefined elsewhere | 6 |
16 | Variable names that consist of all capital letters should be reserved for constant /immutable variables | 1 |
17 | Typos | 7 |
18 | File is missing NatSpec | 13 |
19 | NatSpec is incomplete | 13 |
20 | Event is missing indexed fields | 31 |
21 | Not using the named return variables anywhere in the function is confusing | 12 |
Total: 327 instances over 21 issues
Math.max(<x>,0)
used with int
cast to uint
The code casts an int
to a uint
before passing it to Math.max()
. It seems as though the Math.max()
call is attempting to prevent values from being negative, but since the int
is being cast to uint
, the value will never be negative, and instead will overflow if either the multiplication involving the slope and timestamp is positive. I wasn't able to find a scenario where this is the case, but this seems very dangerous, and the Math.max()
call is sending misleading signals, so I suggest moving it to inside the cast to uint
There are 4 instances of this issue:
File: contracts/contracts/RewardsDistributor.sol #1 139: return Math.max(uint(int256(pt.bias - pt.slope * (int128(int256(_timestamp - pt.ts))))), 0);
File: contracts/contracts/RewardsDistributor.sol #2 158: ve_supply[t] = Math.max(uint(int256(pt.bias - pt.slope * dt)), 0);
File: contracts/contracts/RewardsDistributor.sol #3 208: uint balance_of = Math.max(uint(int256(old_user_point.bias - dt * old_user_point.slope)), 0);
File: contracts/contracts/RewardsDistributor.sol #4 265: uint balance_of = Math.max(uint(int256(old_user_point.bias - dt * old_user_point.slope)), 0);
approveMax
variable causes problemsThe approveMax
variable controls whether the liquidity value is used, or the maximum is used. If the result of that check doesn't match what was provided during signature generation, the permit()
call will fail, so in the best case the variable has no effect. In the worst case it leads to incorrect state handling
There are 2 instances of this issue:
File: contracts/contracts/Router.sol #1 290 uint value = approveMax ? type(uint).max : liquidity; 291: IPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
File: contracts/contracts/Router.sol #2 308 uint value = approveMax ? type(uint).max : liquidity; 309: IPair(pair).permit(msg.sender, address(this), value, deadline, v, r, s);
There is nothing preventing another account from calling the initializer before the contract owner. In the best case, the owner is forced to waste gas and re-deploy. In the worst case, the owner does not notice that his/her call reverts, and everyone starts using a contract under the control of an attacker
There is 1 instance of this issue:
File: contracts/contracts/Bribe.sol #1 30 function setGauge(address _gauge) external { 31 require(gauge == address(0), "gauge already set"); 32 gauge = _gauge; 33: }
The WEEK
variable is used to calculate a specific timestamp. However, if there's a leap year, then the calculation will shift, and the timestamp will no longer be 00:00 UTC
There is 1 instance of this issue:
File: contracts/contracts/Minter.sol #1 14: uint internal constant WEEK = 86400 * 7; // allows minting once per week (reset every Thursday 00:00 UTC)
A user can only have a billion checkpoints which, if the user is a DAO, may cause issues down the line, especially if the last checkpoint involved delegating and can thereafter not be undone
There is 1 instance of this issue:
File: contracts/contracts/VotingEscrow.sol #1 535: mapping(uint => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
transfer()
/transferFrom()
with IERC20
Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s transfer()
and transferFrom()
functions on L1 do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast to IERC20
, their function signatures do not match and therefore the calls made, revert (see this link for a test case). Use OpenZeppelinâs SafeERC20
's safeTransfer()
/safeTransferFrom()
instead
There are 2 instances of this issue:
File: contracts/contracts/VotingEscrow.sol #1 748: assert(IERC20(token).transferFrom(from, address(this), _value));
File: contracts/contracts/VotingEscrow.sol #2 861: assert(IERC20(token).transfer(msg.sender, value));
require()
should be used instead of assert()
Prior to solidity version 0.8.0, hitting an assert consumes the remainder of the transaction's available gas rather than returning it, as require()
/revert()
do. assert()
should be avoided even past solidity version 0.8.0 as its documentation states that "The assert function creates an error of type Panic(uint256). ... Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix".
There are 18 instances of this issue:
File: contracts/contracts/Router.sol 36: assert(msg.sender == address(weth)); // only accept ETH via fallback from the WETH contract 181: assert(amountAOptimal <= amountADesired); 227: assert(weth.transfer(pair, amountETH)); 373: assert(weth.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]));
File: contracts/contracts/VotingEscrow.sol 262: assert(_operator != msg.sender); 272: assert(idToOwner[_tokenId] == _owner); 447: assert(idToOwner[_tokenId] == address(0)); 464: assert(_to != address(0)); 508: assert(idToOwner[_tokenId] == _from); 748: assert(IERC20(token).transferFrom(from, address(this), _value)); 815: assert(_isApprovedOrOwner(msg.sender, _tokenId)); 819: assert(_value > 0); // dev: need non-zero value 829: assert(_isApprovedOrOwner(msg.sender, _tokenId)); 845: assert(_isApprovedOrOwner(msg.sender, _tokenId)); 861: assert(IERC20(token).transfer(msg.sender, value)); 937: assert(_block <= block.number); 991: assert(_block <= block.number);
File: contracts/contracts/RewardsDistributor.sol 98: assert(msg.sender == depositor);
_safeMint()
should be used rather than _mint()
wherever possible_mint()
is discouraged in favor of _safeMint()
which ensures that the recipient is either an EOA or implements IERC721Receiver
. Both OpenZeppelin and solmate have versions of this function
There is 1 instance of this issue:
File: contracts/contracts/VotingEscrow.sol #1 791: _mint(_to, _tokenId);
address(0x0)
when assigning values to address
state variablesThere are 33 instances of this issue:
File: contracts/contracts/Minter.sol 66: pendingTeam = _team;
File: contracts/contracts/redeem/RedemptionSender.sol 22: weve = _weve; 24: endpoint = _endpoint; 25: optimismReceiver = _optimismReceiver;
File: contracts/contracts/redeem/RedemptionReceiver.sol 28: endpoint = _endpoint; 53: fantomSender = _fantomSender;
File: contracts/contracts/Router.sol 30: factory = _factory;
File: contracts/contracts/Gauge.sol 97: stake = _stake; 98: bribe = _bribe; 99: _ve = __ve; 100: voter = _voter;
File: contracts/contracts/VotingEscrow.sol 88: token = token_addr; 1061: voter = _voter;
File: contracts/contracts/VeloGovernor.sol 41: team = newTeam;
File: contracts/contracts/RewardsDistributor.sol 54: token = _token; 55: voting_escrow = _voting_escrow; 320: depositor = _depositor;
File: contracts/contracts/PairFees.sol 15: token0 = _token0; 16: token1 = _token1;
File: contracts/contracts/Velo.sol 28: minter = _minter; 33: redemptionReceiver = _receiver;
File: contracts/contracts/Bribe.sol 32: gauge = _gauge;
File: contracts/contracts/factories/GaugeFactory.sol 14: pairFactory = _pairFactory; 19: team = _team;
File: contracts/contracts/factories/PairFactory.sol 42: pendingPauser = _pauser; 57: pendingFeeManager = _feeManager;
File: contracts/contracts/Voter.sol 55: _ve = __ve; 56: factory = _factory; 58: gaugefactory = _gauges; 59: bribefactory = _bribes; 79: minter = _minter; 84: governor = _governor; 89: emergencyCouncil = _council;
Code architecture, incentives, and error handling/reporting questions/issues should be resolved before deployment
There are 4 instances of this issue:
File: contracts/contracts/VotingEscrow.sol #1 314: // TODO delegates
File: contracts/contracts/VotingEscrow.sol #2 465: // TODO add delegates
File: contracts/contracts/VotingEscrow.sol #3 524: // TODO add delegates
File: contracts/contracts/VelodromeLibrary.sol #4 9: IRouter internal immutable router; // TODO make modifiable?
Consider adding a two-phase transfer, where the current owner nominates the next owner, and the next owner has to call accept*()
to become the new owner. This prevents passing the ownership to an account that is unable to use it.
There are 4 instances of this issue:
File: contracts/contracts/factories/GaugeFactory.sol #1 17 function setTeam(address _team) external { 18 require(msg.sender == team); 19 team = _team; 20: }
File: contracts/contracts/VeloGovernor.sol #2 39 function setTeam(address newTeam) external { 40 require(msg.sender == team, "not team"); 41 team = newTeam; 42: }
File: contracts/contracts/Voter.sol #3 82 function setGovernor(address _governor) public { 83 require(msg.sender == governor); 84 governor = _governor; 85: }
File: contracts/contracts/Voter.sol #4 87 function setEmergencyCouncil(address _council) public { 88 require(msg.sender == emergencyCouncil); 89 emergencyCouncil = _council; 90: }
Use alternative variants, e.g. allowlist/denylist instead of whitelist/blacklist
There are 3 instances of this issue:
File: contracts/contracts/Voter.sol #1 38: mapping(address => bool) public isWhitelisted;
File: contracts/contracts/Voter.sol #2 52: event Whitelisted(address indexed whitelister, address indexed token);
File: contracts/contracts/Voter.sol #3 178: function whitelist(address _token) public {
Use OpenZeppelin's ECDSA
contract rather than calling ecrecover()
directly
There are 2 instances of this issue:
File: contracts/contracts/VotingEscrow.sol #1 1377: address signatory = ecrecover(digest, v, r, s);
File: contracts/contracts/Pair.sol #2 484: address recoveredAddress = ecrecover(digest, v, r, s);
approve()
not checkedNot 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
There are 2 instances of this issue:
File: contracts/contracts/RewardsDistributor.sol #1 57: IERC20(_token).approve(_voting_escrow, type(uint).max);
File: contracts/contracts/Voter.sol #2 198: IERC20(base).approve(_gauge, type(uint).max);
require()
/revert()
statements should have descriptive reason stringsThere are 93 instances of this issue:
File: contracts/contracts/Minter.sol 54: require(initializer == msg.sender); 128: require(_velo.transfer(team, _teamEmissions)); 129: require(_velo.transfer(address(_rewards_distributor), _growth));
File: contracts/contracts/Router.sol 164: require(amountADesired >= amountAMin); 165: require(amountBDesired >= amountBMin); 245: require(IPair(pair).transferFrom(msg.sender, pair, liquidity)); // send liquidity to pair 410: require(token.code.length > 0); 413: require(success && (data.length == 0 || abi.decode(data, (bool)))); 417: require(token.code.length > 0); 420: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Gauge.sol 125: require(_unlocked == 1); 174: require(msg.sender == voter); 189: require(msg.sender == voter); 348: require(msg.sender == account || msg.sender == voter); 512: require(amount > 0); 521: require(IVotingEscrow(_ve).ownerOf(tokenId) == msg.sender); 526: require(tokenIds[msg.sender] == tokenId); 564: require(tokenId == tokenIds[msg.sender]); 591: require(token != stake); 592: require(amount > 0); 609: require(amount > _left); 613: require(rewardRate[token] > 0); 628: require(rewards[i] == oldToken); 640: require(msg.sender == bribe); 654: require(amount > _left); 665: require(token.code.length > 0); 668: require(success && (data.length == 0 || abi.decode(data, (bool)))); 672: require(token.code.length > 0); 675: require(success && (data.length == 0 || abi.decode(data, (bool)))); 679: require(token.code.length > 0); 682: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/VotingEscrow.sol 112: require(_entered_state == _not_entered); 242: require(owner != address(0)); 244: require(_approved != owner); 248: require(senderIsOwner || senderIsApprovedForAll); 309: require(_isApprovedOrOwner(_sender, _tokenId)); 772: require(_value > 0); // dev: need non-zero value 785: require(_value > 0); // dev: need non-zero value 1060: require(msg.sender == voter); 1065: require(msg.sender == voter); 1070: require(msg.sender == voter); 1075: require(msg.sender == voter); 1080: require(msg.sender == voter); 1086: require(_from != _to); 1087: require(_isApprovedOrOwner(msg.sender, _from)); 1088: require(_isApprovedOrOwner(msg.sender, _to));
File: contracts/contracts/RewardsDistributor.sol 319: require(msg.sender == depositor);
File: contracts/contracts/PairFees.sol 20: require(token.code.length > 0); 23: require(success && (data.length == 0 || abi.decode(data, (bool)))); 28: require(msg.sender == pair);
File: contracts/contracts/Velo.sol 27: require(msg.sender == minter); 32: require(msg.sender == minter); 69: require(msg.sender == minter); 75: require(msg.sender == redemptionReceiver);
File: contracts/contracts/Bribe.sol 24: require(_unlocked == 1); 42: require(amount > 0); 67: require(msg.sender == gauge); 76: require(msg.sender == gauge); 77: require(rewards[i] == oldToken); 84: require(msg.sender == gauge); 93: require(token.code.length > 0); 96: require(success && (data.length == 0 || abi.decode(data, (bool)))); 100: require(token.code.length > 0); 103: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/factories/GaugeFactory.sol 18: require(msg.sender == team);
File: contracts/contracts/factories/PairFactory.sol 41: require(msg.sender == pauser); 46: require(msg.sender == pendingPauser); 51: require(msg.sender == pauser);
File: contracts/contracts/Pair.sol 111: require(_unlocked == 1); 335: require(!PairFactory(factory).isPaused()); 522: require(token.code.length > 0); 525: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Voter.sol 68: require(_unlocked == 1); 75: require(msg.sender == minter); 83: require(msg.sender == governor); 88: require(msg.sender == emergencyCouncil); 93: require(IVotingEscrow(_ve).isApprovedOrOwner(msg.sender, _tokenId)); 153: require(votes[_tokenId][_pool] == 0); 154: require(_poolWeight != 0); 173: require(IVotingEscrow(_ve).isApprovedOrOwner(msg.sender, tokenId)); 174: require(_poolVote.length == _weights.length); 179: require(msg.sender == governor); 184: require(!isWhitelisted[_token]); 225: require(isGauge[msg.sender]); 226: require(isAlive[msg.sender]); // killed gauges cannot attach tokens to themselves 232: require(isGauge[msg.sender]); 233: require(isAlive[msg.sender]); 238: require(isGauge[msg.sender]); 244: require(isGauge[msg.sender]); 286: require(isAlive[_gauge]); // killed gauges cannot be updated 316: require(isAlive[_gauge]); // killed gauges cannot distribute 352: require(token.code.length > 0); 355: require(success && (data.length == 0 || abi.decode(data, (bool))));
public
functions not called by the contract should be declared external
insteadContracts are allowed to override their parents' functions and change the visibility from external
to public
.
There are 9 instances of this issue:
File: contracts/contracts/redeem/RedemptionSender.sol 28 function redeemWEVE( 29 uint256 amount, 30 address zroPaymentAddress, 31: bytes memory zroTransactionParams
File: contracts/contracts/Gauge.sol 163: function getVotingStage(uint timestamp) public pure returns (VotingStage) {
File: contracts/contracts/VotingEscrow.sol 1184 function getPastVotes(address account, uint timestamp) 1185 public 1186 view 1187: returns (uint) 1349: function delegate(address delegatee) public { 1354 function delegateBySig( 1355 address delegatee, 1356 uint nonce, 1357 uint expiry, 1358 uint8 v, 1359 bytes32 r, 1360: bytes32 s
File: contracts/contracts/factories/PairFactory.sol 76: function getFee(bool _stable) public view returns(uint256) {
File: contracts/contracts/Voter.sol 82: function setGovernor(address _governor) public { 87: function setEmergencyCouncil(address _council) public { 178: function whitelist(address _token) public {
assembly{ id := chainid() }
=> uint256 id = block.chainid;
, assembly { size := extcodesize() }
=> uint256 size = address().code.length
There is 1 instance of this issue:
File: contracts/contracts/VotingEscrow.sol #1 367: size := extcodesize(account)
constant
s should be defined rather than using magic numbersThere are 95 instances of this issue:
File: contracts/contracts/Minter.sol /// @audit 15000000e18 22: uint public weekly = 15000000e18; /// @audit 30 41: teamRate = 30; // 30 bps = 0.03%
File: contracts/contracts/redeem/RedemptionSender.sol /// @audit 11 21: require(_optimismChainId == 11 || _optimismChainId == 10011, "CHAIN_ID_NOT_OP"); /// @audit 10011 21: require(_optimismChainId == 11 || _optimismChainId == 10011, "CHAIN_ID_NOT_OP"); /// @audit 0x000000000000000000000000000000000000dEaD 37: 0x000000000000000000000000000000000000dEaD,
File: contracts/contracts/redeem/RedemptionReceiver.sol /// @audit 12 24: require(_fantomChainId == 12 || _fantomChainId == 10012, "CHAIN_ID_NOT_FTM"); /// @audit 10012 24: require(_fantomChainId == 12 || _fantomChainId == 10012, "CHAIN_ID_NOT_FTM");
File: contracts/contracts/Gauge.sol /// @audit 7 164: uint modTime = timestamp % (7 days); /// @audit 7 176: uint bribeStart = block.timestamp - (block.timestamp % (7 days)) + BRIBE_LAG; /// @audit 7 496: uint lastCpWeeksVoteEnd = cp.timestamp - (cp.timestamp % (7 days)) + BRIBE_LAG + DURATION; /// @audit 7 597: uint bribeStart = block.timestamp - (block.timestamp % (7 days)) + BRIBE_LAG; /// @audit 7 598: uint adjustedTstamp = block.timestamp < bribeStart ? bribeStart : bribeStart + 7 days;
File: contracts/contracts/VotingEscrow.sol /// @audit 48 143: buffer[digits] = bytes1(uint8(48 + uint(value % 10))); /// @audit 255 632: for (uint i = 0; i < 255; ++i) { /// @audit 128 886: for (uint i = 0; i < 128; ++i) { /// @audit 128 942: for (uint i = 0; i < 128; ++i) { /// @audit 255 1017: for (uint i = 0; i < 255; ++i) {
File: contracts/contracts/VeloGovernor.sol /// @audit 4 26: L2GovernorVotesQuorumFraction(4) // 4% /// @audit 15 32: return 15 minutes; // 1 block
File: contracts/contracts/VelodromeLibrary.sol /// @audit 1e18 16: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 16: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 16: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 16: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 16: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 16: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 3 20: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 20: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 20: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 20: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 20: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 255 24: for (uint i = 0; i < 255; i++) { /// @audit 1e18 28: uint dy = (xy - k)*1e18/_d(x0, y); /// @audit 1e18 31: uint dy = (k - xy)*1e18/_d(x0, y); /// @audit 1e18 50: a = _getAmountOut(sample, tokenIn, r0, r1, t0, dec0, dec1, st) * 1e18 / sample; /// @audit 1e18 51: b = _getAmountOut(amountIn, tokenIn, r0, r1, t0, dec0, dec1, st) * 1e18 / amountIn; /// @audit 1e18 57: a = _getAmountOut(sample, tokenIn, r0, r1, t0, dec0, dec1, st) * 1e18 / sample; /// @audit 1e18 58: b = _getAmountOut(amountIn, tokenIn, r0, r1, t0, dec0, dec1, st) * 1e18 / amountIn; /// @audit 1e18 64: return _getAmountOut(sample, tokenIn, r0, r1, t0, dec0, dec1, st) * 1e18 / sample; /// @audit 1e18 75: return _getAmountOut(amountIn, tokenIn, r0, r1, t0, dec0, dec1, st) * 1e18 / amountIn; /// @audit 1e18 81: _reserve0 = _reserve0 * 1e18 / decimals0; /// @audit 1e18 82: _reserve1 = _reserve1 * 1e18 / decimals1; /// @audit 1e18 84: amountIn = tokenIn == token0 ? amountIn * 1e18 / decimals0 : amountIn * 1e18 / decimals1; /// @audit 1e18 84: amountIn = tokenIn == token0 ? amountIn * 1e18 / decimals0 : amountIn * 1e18 / decimals1; /// @audit 1e18 86: return y * (tokenIn == token0 ? decimals1 : decimals0) / 1e18; /// @audit 1e18 95: uint _x = x * 1e18 / decimals0; /// @audit 1e18 96: uint _y = y * 1e18 / decimals1; /// @audit 1e18 97: uint _a = (_x * _y) / 1e18; /// @audit 1e18 98: uint _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18); /// @audit 1e18 98: uint _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18); /// @audit 1e18 99: return _a * _b / 1e18; // x3y+y3x >= k
File: contracts/contracts/RewardsDistributor.sol /// @audit 20 75: for (uint i = 0; i < 20; i++) { /// @audit 128 105: for (uint i = 0; i < 128; i++) { /// @audit 128 121: for (uint i = 0; i < 128; i++) { /// @audit 20 148: for (uint i = 0; i < 20; i++) { /// @audit 50 195: for (uint i = 0; i < 50; i++) { /// @audit 50 252: for (uint i = 0; i < 50; i++) {
File: contracts/contracts/Velo.sol /// @audit 0x0 45: emit Transfer(address(0x0), _to, _amount);
File: contracts/contracts/Bribe.sol /// @audit 7 36: uint bribeStart = timestamp - (timestamp % (7 days)) + BRIBE_LAG; /// @audit 7 38: return timestamp < bribeEnd ? bribeStart : bribeStart + 7 days;
File: contracts/contracts/Pair.sol /// @audit 1e18 153: uint256 _ratio = amount * 1e18 / totalSupply; // 1e18 adjustment is removed during claim /// @audit 1e18 163: uint256 _ratio = amount * 1e18 / totalSupply; /// @audit 1e18 184: uint _share = _supplied * _delta0 / 1e18; // add accrued difference for each supplied token /// @audit 1e18 188: uint _share = _supplied * _delta1 / 1e18; /// @audit 10000 356: if (amount0In > 0) _update0(amount0In * PairFactory(factory).getFee(stable) / 10000); // accrue fees for token0 and move them out of pool /// @audit 10000 357: if (amount1In > 0) _update1(amount1In * PairFactory(factory).getFee(stable) / 10000); // accrue fees for token1 and move them out of pool /// @audit 1e18 381: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 381: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 381: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 381: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 381: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 1e18 381: return x0*(y*y/1e18*y/1e18)/1e18+(x0*x0/1e18*x0/1e18)*y/1e18; /// @audit 3 385: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 385: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 385: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 385: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 1e18 385: return 3*x0*(y*y/1e18)/1e18+(x0*x0/1e18*x0/1e18); /// @audit 255 389: for (uint i = 0; i < 255; i++) { /// @audit 1e18 393: uint dy = (xy - k)*1e18/_d(x0, y); /// @audit 1e18 396: uint dy = (k - xy)*1e18/_d(x0, y); /// @audit 10000 414: amountIn -= amountIn * PairFactory(factory).getFee(stable) / 10000; // remove fee from amount received /// @audit 1e18 421: _reserve0 = _reserve0 * 1e18 / decimals0; /// @audit 1e18 422: _reserve1 = _reserve1 * 1e18 / decimals1; /// @audit 1e18 424: amountIn = tokenIn == token0 ? amountIn * 1e18 / decimals0 : amountIn * 1e18 / decimals1; /// @audit 1e18 424: amountIn = tokenIn == token0 ? amountIn * 1e18 / decimals0 : amountIn * 1e18 / decimals1; /// @audit 1e18 426: return y * (tokenIn == token0 ? decimals1 : decimals0) / 1e18; /// @audit 1e18 435: uint _x = x * 1e18 / decimals0; /// @audit 1e18 436: uint _y = y * 1e18 / decimals1; /// @audit 1e18 437: uint _a = (_x * _y) / 1e18; /// @audit 1e18 438: uint _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18); /// @audit 1e18 438: uint _b = ((_x * _x) / 1e18 + (_y * _y) / 1e18); /// @audit 1e18 439: return _a * _b / 1e18; // x3y+y3x >= k
File: contracts/contracts/Voter.sol /// @audit 0x0 190: require(gauges[_pool] == address(0x0), "exists"); /// @audit 1e18 258: uint256 _ratio = amount * 1e18 / totalWeight; // 1e18 adjustment is removed during claim /// @audit 1e18 295: uint _share = uint(_supplied) * _delta / 1e18; // add accrued difference for each supplied token /// @audit 7 317: uint dayCalc = block.timestamp % (7 days);
The type of the variable is the same as the type to which the variable is being cast
There are 4 instances of this issue:
File: contracts/contracts/Bribe.sol #1 /// @audit address(gauge) 87: _safeTransfer(token, address(gauge), rewardPerEpoch);
File: contracts/contracts/Voter.sol #2 /// @audit uint256(_totalWeight) 118: totalWeight -= uint256(_totalWeight);
File: contracts/contracts/Voter.sol #3 /// @audit uint256(_totalWeight) 168: totalWeight += uint256(_totalWeight);
File: contracts/contracts/Voter.sol #4 /// @audit uint256(_usedWeight) 169: usedWeights[_tokenId] = uint256(_usedWeight);
There are units for seconds, minutes, hours, days, and weeks
There are 7 instances of this issue:
File: contracts/contracts/Minter.sol /// @audit 86400 14: uint internal constant WEEK = 86400 * 7; // allows minting once per week (reset every Thursday 00:00 UTC) /// @audit 15000000e18 22: uint public weekly = 15000000e18; /// @audit 86400 24: uint internal constant LOCK = 86400 * 7 * 52 * 4;
File: contracts/contracts/VotingEscrow.sol /// @audit 86400 542: uint internal constant MAXTIME = 4 * 365 * 86400; /// @audit 86400 543: int128 internal constant iMAXTIME = 4 * 365 * 86400;
File: contracts/contracts/RewardsDistributor.sol /// @audit 86400 30: uint constant WEEK = 7 * 86400;
File: contracts/contracts/Pair.sol /// @audit 1800 45: uint constant periodSize = 1800;
1e6
) rather than decimal literals (e.g. 1000000
), for readabilityThere are 3 instances of this issue:
File: contracts/contracts/VotingEscrow.sol #1 535: mapping(uint => Point[1000000000]) public user_point_history; // user -> Point[user_epoch]
File: contracts/contracts/RewardsDistributor.sol #2 38: uint[1000000000000000] public tokens_per_week;
File: contracts/contracts/RewardsDistributor.sol #3 44: uint[1000000000000000] public ve_supply;
There are 16 instances of this issue:
File: contracts/contracts/Minter.sol 64 function setTeam(address _team) external { 65 require(msg.sender == team, "not team"); 66 pendingTeam = _team; 67: } 74 function setTeamRate(uint _teamRate) external { 75 require(msg.sender == team, "not team"); 76 require(_teamRate <= MAX_TEAM_RATE, "rate too high"); 77 teamRate = _teamRate; 78: }
File: contracts/contracts/VotingEscrow.sol 1059 function setVoter(address _voter) external { 1060 require(msg.sender == voter); 1061 voter = _voter; 1062: }
File: contracts/contracts/VeloGovernor.sol 39 function setTeam(address newTeam) external { 40 require(msg.sender == team, "not team"); 41 team = newTeam; 42: } 44 function setProposalNumerator(uint256 numerator) external { 45 require(msg.sender == team, "not team"); 46 require(numerator <= MAX_PROPOSAL_NUMERATOR, "numerator too high"); 47 proposalNumerator = numerator; 48: }
File: contracts/contracts/RewardsDistributor.sol 318 function setDepositor(address _depositor) external { 319 require(msg.sender == depositor); 320 depositor = _depositor; 321: }
File: contracts/contracts/Velo.sol 26 function setMinter(address _minter) external { 27 require(msg.sender == minter); 28 minter = _minter; 29: } 31 function setRedemptionReceiver(address _receiver) external { 32 require(msg.sender == minter); 33 redemptionReceiver = _receiver; 34: }
File: contracts/contracts/Bribe.sol 30 function setGauge(address _gauge) external { 31 require(gauge == address(0), "gauge already set"); 32 gauge = _gauge; 33: }
File: contracts/contracts/factories/GaugeFactory.sol 17 function setTeam(address _team) external { 18 require(msg.sender == team); 19 team = _team; 20: }
File: contracts/contracts/factories/PairFactory.sol 40 function setPauser(address _pauser) external { 41 require(msg.sender == pauser); 42 pendingPauser = _pauser; 43: } 50 function setPause(bool _state) external { 51 require(msg.sender == pauser); 52 isPaused = _state; 53: } 55 function setFeeManager(address _feeManager) external { 56 require(msg.sender == feeManager, 'not fee manager'); 57 pendingFeeManager = _feeManager; 58: } 65 function setFee(bool _stable, uint256 _fee) external { 66 require(msg.sender == feeManager, 'not fee manager'); 67 require(_fee <= MAX_FEE, 'fee too high'); 68 require(_fee != 0, 'fee must be nonzero'); 69 if (_stable) { 70 stableFee = _fee; 71 } else { 72 volatileFee = _fee; 73 } 74: }
File: contracts/contracts/Voter.sol 82 function setGovernor(address _governor) public { 83 require(msg.sender == governor); 84 governor = _governor; 85: } 87 function setEmergencyCouncil(address _council) public { 88 require(msg.sender == emergencyCouncil); 89 emergencyCouncil = _council; 90: }
keccak256()
, should use immutable
rather than constant
There are 2 instances of this issue:
File: contracts/contracts/VotingEscrow.sol #1 1106: bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
File: contracts/contracts/VotingEscrow.sol #2 1109: bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
1e18
) rather than exponentiation (e.g. 10**18
)There are 3 instances of this issue:
File: contracts/contracts/Router.sol #1 21: uint internal constant MINIMUM_LIQUIDITY = 10**3;
File: contracts/contracts/Gauge.sol #2 36: uint internal constant PRECISION = 10 ** 18;
File: contracts/contracts/Pair.sol #3 30: uint internal constant MINIMUM_LIQUIDITY = 10**3;
Consider defining in only one contract so that values cannot become out of sync when only one location is updated. A cheap way to store constants in a single location is to create an internal constant
in a library
. If the variable is a local cache of another contract's value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don't get out of sync.
There are 6 instances of this issue:
File: contracts/contracts/Gauge.sol /// @audit seen in /var/tmp/hh/contracts/contracts/Minter.sol 16: address public immutable _ve; // the ve token used for gauges
File: contracts/contracts/Velo.sol /// @audit seen in /var/tmp/hh/contracts/contracts/VotingEscrow.sol 6: string public constant name = "Velodrome"; /// @audit seen in /var/tmp/hh/contracts/contracts/VotingEscrow.sol 7: string public constant symbol = "VELO"; /// @audit seen in /var/tmp/hh/contracts/contracts/VotingEscrow.sol 8: uint8 public constant decimals = 18;
File: contracts/contracts/Pair.sol /// @audit seen in /var/tmp/hh/contracts/contracts/Velo.sol 15: uint8 public constant decimals = 18;
File: contracts/contracts/Voter.sol /// @audit seen in /var/tmp/hh/contracts/contracts/Gauge.sol 16: address public immutable _ve; // the ve token that governs these contracts
constant
/immutable
variablesIf the variable needs to be different based on which class it comes from, a view
/pure
function should be used instead (e.g. like this).
There is 1 instance of this issue:
File: contracts/contracts/Pair.sol #1 25: bytes32 internal DOMAIN_SEPARATOR;
There are 7 instances of this issue:
File: contracts/contracts/redeem/RedemptionReceiver.sol /// @audit FTM 14: uint16 public immutable fantomChainId; // 12 for FTM, 10012 for FTM testnet
File: contracts/contracts/VotingEscrow.sol /// @audit blocktimes 38: * and per block could be fairly bad b/c Ethereum changes blocktimes. /// @audit Exeute 295: /// @dev Exeute transfer of a NFT. /// @audit Pevious 575: /// @param old_locked Pevious locked amount / end lock time for the user
File: contracts/contracts/PairFees.sol /// @audit localy 10: address internal immutable token0; // token0 of pair, saved localy and statically for gas optimization /// @audit localy 11: address internal immutable token1; // Token1 of pair, saved localy and statically for gas optimization
File: contracts/contracts/Pair.sol /// @audit obervations 37: // Structure to capture time period obervations every 30 minutes, used for local oracles
There are 13 instances of this issue:
File: contracts/contracts/Minter.sol
File: contracts/contracts/Router.sol
File: contracts/contracts/VeloGovernor.sol
File: contracts/contracts/VelodromeLibrary.sol
File: contracts/contracts/RewardsDistributor.sol
File: contracts/contracts/PairFees.sol
File: contracts/contracts/Velo.sol
File: contracts/contracts/Bribe.sol
File: contracts/contracts/factories/GaugeFactory.sol
File: contracts/contracts/factories/BribeFactory.sol
File: contracts/contracts/factories/PairFactory.sol
File: contracts/contracts/Pair.sol
File: contracts/contracts/Voter.sol
There are 13 instances of this issue:
File: contracts/contracts/VotingEscrow.sol /// @audit Missing: '@return' 160 /// @dev Returns current token URI metadata 161 /// @param _tokenId Token ID to fetch URI for. 162: function tokenURI(uint _tokenId) external view returns (string memory) { /// @audit Missing: '@return' 184 /// @dev Returns the address of the owner of the NFT. 185 /// @param _tokenId The identifier for an NFT. 186: function ownerOf(uint _tokenId) public view returns (address) { /// @audit Missing: '@return' 191 /// Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid. 192 /// @param _owner Address for whom to query the balance. 193: function _balance(address _owner) internal view returns (uint) { /// @audit Missing: '@return' 198 /// Throws if `_owner` is the zero address. NFTs assigned to the zero address are considered invalid. 199 /// @param _owner Address for whom to query the balance. 200: function balanceOf(address _owner) external view returns (uint) { /// @audit Missing: '@return' 216 /// @dev Get the approved address for a single NFT. 217 /// @param _tokenId ID of the NFT to query the approval of. 218: function getApproved(uint _tokenId) external view returns (address) { /// @audit Missing: '@return' 223 /// @param _owner The address that owns the NFTs. 224 /// @param _operator The address that acts on behalf of the owner. 225: function isApprovedForAll(address _owner, address _operator) external view returns (bool) { /// @audit Missing: '@return' 412 /// @dev Interface identification is specified in ERC-165. 413 /// @param _interfaceID Id of the interface 414: function supportsInterface(bytes4 _interfaceID) external view returns (bool) { /// @audit Missing: '@return' 780 /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week) 781 /// @param _to Address to deposit 782: function _create_lock(uint _value, uint _lock_duration, address _to) internal returns (uint) { /// @audit Missing: '@return' 798 /// @param _value Amount to deposit 799 /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week) 800: function create_lock(uint _value, uint _lock_duration) external nonreentrant returns (uint) { /// @audit Missing: '@return' 806 /// @param _lock_duration Number of seconds to lock tokens for (rounded down to nearest week) 807 /// @param _to Address to deposit 808: function create_lock_for(uint _value, uint _lock_duration, address _to) external nonreentrant returns (uint) { /// @audit Missing: '@param _tokenId' 812 /// @notice Deposit `_value` additional tokens for `_tokenId` without modifying the unlock time 813 /// @param _value Amount of tokens to deposit and add to the lock 814: function increase_amount(uint _tokenId, uint _value) external nonreentrant { /// @audit Missing: '@param _tokenId' 826 /// @notice Extend the unlock time for `_tokenId` 827 /// @param _lock_duration New number of seconds until tokens unlock 828: function increase_unlock_time(uint _tokenId, uint _lock_duration) external nonreentrant { /// @audit Missing: '@param t' 1043 /// @notice Calculate total voting power 1044 /// @dev Adheres to the ERC20 `totalSupply` interface for Aragon compatibility 1045 /// @return Total voting power 1046: function totalSupplyAtT(uint t) public view returns (uint) {
indexed
fieldsEach event
should use three indexed
fields if there are three or more fields
There are 31 instances of this issue:
File: contracts/contracts/Minter.sol 32: event Mint(address indexed sender, uint weekly, uint circulating_supply, uint circulating_emission);
File: contracts/contracts/Gauge.sol 90: event Deposit(address indexed from, uint tokenId, uint amount); 91: event Withdraw(address indexed from, uint tokenId, uint amount); 92: event NotifyReward(address indexed from, address indexed reward, uint amount); 93: event ClaimFees(address indexed from, uint claimed0, uint claimed1); 94: event ClaimRewards(address indexed from, address indexed reward, uint amount);
File: contracts/contracts/VotingEscrow.sol 51 event Deposit( 52 address indexed provider, 53 uint tokenId, 54 uint value, 55 uint indexed locktime, 56 DepositType deposit_type, 57 uint ts 58: ); 59: event Withdraw(address indexed provider, uint tokenId, uint value, uint ts); 60: event Supply(uint prevSupply, uint supply);
File: contracts/contracts/RewardsDistributor.sol 18 event CheckpointToken( 19 uint time, 20 uint tokens 21: ); 23 event Claimed( 24 uint tokenId, 25 uint amount, 26 uint claim_epoch, 27 uint max_epoch 28: );
File: contracts/contracts/Velo.sol 17: event Transfer(address indexed from, address indexed to, uint value); 18: event Approval(address indexed owner, address indexed spender, uint value);
File: contracts/contracts/Bribe.sol 19: event NotifyReward(address indexed from, address indexed reward, uint epoch, uint amount);
File: contracts/contracts/factories/PairFactory.sol 26: event PairCreated(address indexed token0, address indexed token1, bool stable, address pair, uint);
File: contracts/contracts/Pair.sol 72: event Fees(address indexed sender, uint amount0, uint amount1); 73: event Mint(address indexed sender, uint amount0, uint amount1); 74: event Burn(address indexed sender, uint amount0, uint amount1, address indexed to); 75 event Swap( 76 address indexed sender, 77 uint amount0In, 78 uint amount1In, 79 uint amount0Out, 80 uint amount1Out, 81 address indexed to 82: ); 83: event Sync(uint reserve0, uint reserve1); 84: event Claim(address indexed sender, address indexed recipient, uint amount0, uint amount1); 86: event Transfer(address indexed from, address indexed to, uint amount); 87: event Approval(address indexed owner, address indexed spender, uint amount);
File: contracts/contracts/Voter.sol 44: event Voted(address indexed voter, uint tokenId, uint256 weight); 45: event Abstained(uint tokenId, uint256 weight); 46: event Deposit(address indexed lp, address indexed gauge, uint tokenId, uint amount); 47: event Withdraw(address indexed lp, address indexed gauge, uint tokenId, uint amount); 48: event NotifyReward(address indexed sender, address indexed reward, uint amount); 49: event DistributeReward(address indexed sender, address indexed gauge, uint amount); 50: event Attach(address indexed owner, address indexed gauge, uint tokenId); 51: event Detach(address indexed owner, address indexed gauge, uint tokenId);
Consider changing the variable to be an unnamed one
There are 12 instances of this issue:
File: contracts/contracts/Router.sol /// @audit amount 71: function getAmountOut(uint amountIn, address tokenIn, address tokenOut) external view returns (uint amount, bool stable) { /// @audit stable 71: function getAmountOut(uint amountIn, address tokenIn, address tokenOut) external view returns (uint amount, bool stable) {
File: contracts/contracts/Gauge.sol /// @audit claimed0 131: function claimFees() external lock returns (uint claimed0, uint claimed1) { /// @audit claimed1 131: function claimFees() external lock returns (uint claimed0, uint claimed1) {
File: contracts/contracts/Pair.sol /// @audit dec0 125: function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) { /// @audit dec1 125: function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) { /// @audit r0 125: function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) { /// @audit r1 125: function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) { /// @audit st 125: function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) { /// @audit t0 125: function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) { /// @audit t1 125: function metadata() external view returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1) { /// @audit amountOut 254: function quote(address tokenIn, uint amountIn, uint granularity) external view returns (uint amountOut) {
#0 - liveactionllama
2022-05-31T17:15:53Z
Warden created this issue as a placeholder, because their submission was too large for the contest form. They then emailed their md file to our team on 05/30/2022 at 11:12 UTC. I've updated this issue with their md file content.
#1 - GalloDaSballo
2022-07-04T00:43:23Z
Great find, Valid Low
I believe NC is more appropriate, caller can call the wrong one
Because of the architecture of the system this is a Valid and Low finding
Because token imp is known, this finding is not valid
Valid NC
You cannot go from public
to external
(just tested), only from external
 to public
, that said all of these are not override
so while the comment is wrong, the advice of using external
is valid
Valid NC
Valid NC
#2 - GalloDaSballo
2022-07-04T21:26:52Z
Valid Low
Valid Non-Critical
Valid Low
Valid NC
Wouldn't the timestamp shift by 24 hours every 4 years? It will always end at 00 just on a different day
Valid NC
Valid NC
Valid NC
In lack of a POC I cannot give it points, with a POC this finding is potentially Med / High Severity
Because the implementation is known the finding is not valid
##Â 5. require()/revert() statements should have descriptive reason strings Valid NC
Valid NC
Because the code compiles to the same result, there is no advantage, hence will not give it points
Valid NC
Valid NC
I would save since the constant is declared it would be more appropriate to re-use it. Valid NC
Valid NC
Valid NC
Not anymore, debunked here
Because it's for constants I don't think it makes a difference
I don't think this suggestion is actionable
Valid NC
Disagree with the finding, not all fields need natspec as it's optional
Valid NC, although most of these wouldn't be useful
Valid NC
#3 - GalloDaSballo
2022-07-04T21:28:02Z
All in all a really thorough report, which would benefit by mentioning a finding once, and then listing all other occurrences, especially when the code is pretty much the same
That said, one of the best reports
#4 - GalloDaSballo
2022-07-04T21:30:33Z
5 L, 21 NC
#5 - GalloDaSballo
2022-07-11T21:53:46Z
L-1. Math.max(,0) used with int cast to uint
L-2. Front-runable initializer
L-3. require() should be used instead of assert()
L-4. _safeMint() should be used rather than _mint() wherever possible
L-5. Missing checks for address(0x0) when assigning values to address state variables
NC-1. approveMax variable causes problems
NC-2. require()/revert() statements should have descriptive reason strings
NC-3. public functions not called by the contract should be declared external instead
NC-4. Variable names that consist of all capital letters should be reserved for constant/immutable variables
NC-5. Typos
NC-6. constants should be defined rather than using magic numbers
NC-7. Open TODOs
NC-8. Only a billion checkpoints available
NC-9. Use two-phase ownership transfers
NC-10. Avoid the use of sensitive terms
NC-11. require()/revert() statements should have descriptive reason strings
NC-12. public functions not called by the contract should be declared external instead
NC-13. constants should be defined rather than using magic number
NC-14. Redundant cast
NC-15. Numeric values having to do with time should use time units for readability
NC-16. Large multiples of ten should use scientific notation (e.g. 1e6) rather than decimal literals (e.g. 1000000), for readability
NC-17. Missing event for critical parameter change
NC-18. File is missing NatSpec
NC-20. Event is missing indexed fields
NC-21. Not using the named return variables anywhere in the function is confusing
đ Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0xNazgul, 0xf15ers, 0xkatana, Chom, ControlCplusControlV, DavidGialdi, Deivitto, Dravee, ElKu, Fitraldys, Funen, GimelSec, MadWookie, MaratCerby, MiloTruck, Picodes, Randyyy, TerrierLover, TomJ, Tomio, UnusualTurtle, WatchPug, Waze, _Adam, asutorufos, c3phas, catchup, csanuragjain, delfin454000, djxploit, fatherOfBlocks, gzeon, hake, hansfriese, horsefacts, orion, oyc_109, pauliax, reassor, rfa, rotcivegaf, sach1r0, saian, sashik_eth, simon135, supernova, teddav, z3s
488.8393 USDC - $488.84
Issue | Instances | |
---|---|---|
1 | Multiple address mappings can be combined into a single mapping of an address to a struct , where appropriate | 8 |
2 | State variables only set in the constructor should be declared immutable | 5 |
3 | State variables can be packed into fewer storage slots | 1 |
4 | Using calldata instead of memory for read-only arguments in external functions saves gas | 13 |
5 | State variables should be cached in stack variables rather than re-reading them from storage | 39 |
6 | Multiple accesses of a mapping should use a local variable cache | 34 |
7 | <x> += <y> costs more gas than <x> = <x> + <y> for state variables | 21 |
8 | internal functions only called once can be inlined to save gas | 25 |
9 | <array>.length should not be looked up in every loop of a for -loop | 17 |
10 | ++i /i++ should be unchecked{++i} /unchecked{i++} when it is not possible for them to overflow, as is the case when used in for - and while -loops | 41 |
11 | require() /revert() strings longer than 32 bytes cost extra gas | 14 |
12 | keccak256() should only need to be called on a specific string literal once | 1 |
13 | Using bool s for storage incurs overhead | 14 |
14 | Using > 0 costs more gas than != 0 when used on a uint in a require() statement | 6 |
15 | It costs more gas to initialize variables to zero than to let the default of zero be applied | 68 |
16 | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) | 37 |
17 | Splitting require() statements that use && saves gas | 17 |
18 | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead | 45 |
19 | abi.encode() is less efficient than abi.encodePacked() | 1 |
20 | Using private rather than public for constants, saves gas | 18 |
21 | Duplicated require() /revert() checks should be refactored to a modifier or function | 30 |
22 | Division by two should use bit shifting | 9 |
23 | Stack variable used as a cheaper cache for a state variable is only used once | 1 |
24 | require() or revert() statements that check input arguments should be at the top of the function | 13 |
25 | Use custom errors rather than revert() /require() strings to save deployment gas | 86 |
Total: 564 instances over 25 issues
address
mappings can be combined into a single mapping
of an address
to a struct
, where appropriateSaves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There are 8 instances of this issue:
File: contracts/contracts/Gauge.sol #1 39 mapping(address => uint) public rewardRate; 40 mapping(address => uint) public periodFinish; 41 mapping(address => uint) public lastUpdateTime; 42 mapping(address => uint) public rewardPerTokenStored; 43 44 mapping(address => mapping(address => uint)) public lastEarn; 45 mapping(address => mapping(address => uint)) public userRewardPerTokenStored; 46 47: mapping(address => uint) public tokenIds;
File: contracts/contracts/Gauge.sol #2 75 mapping (address => mapping (uint => Checkpoint)) public checkpoints; 76 /// @notice The number of checkpoints for each account 77: mapping (address => uint) public numCheckpoints;
File: contracts/contracts/Gauge.sol #3 83 mapping (address => mapping (uint => RewardPerTokenCheckpoint)) public rewardPerTokenCheckpoints; 84 /// @notice The number of checkpoints for each token 85: mapping (address => uint) public rewardPerTokenNumCheckpoints;
File: contracts/contracts/VotingEscrow.sol #4 1116 mapping(address => mapping(uint32 => Checkpoint)) public checkpoints; 1117 1118 /// @notice The number of checkpoints for each account 1119 mapping(address => uint32) public numCheckpoints; 1120 1121 /// @notice A record of states for signing / validating signatures 1122: mapping(address => uint) public nonces;
File: contracts/contracts/Pair.sol #5 65 mapping(address => uint) public supplyIndex0; 66 mapping(address => uint) public supplyIndex1; 67 68 // tracks the amount of unclaimed, but claimable tokens off of fees for token0 and token1 69 mapping(address => uint) public claimable0; 70: mapping(address => uint) public claimable1;
File: contracts/contracts/Voter.sol #6 30 mapping(address => address) public gauges; // pool => gauge 31 mapping(address => address) public poolForGauge; // gauge => pool 32 mapping(address => address) public bribes; // gauge => bribe 33: mapping(address => uint256) public weights; // pool => weight
File: contracts/contracts/Voter.sol #7 37 mapping(address => bool) public isGauge; 38 mapping(address => bool) public isWhitelisted; 39: mapping(address => bool) public isAlive;
File: contracts/contracts/Voter.sol #8 253 mapping(address => uint) internal supplyIndex; 254: mapping(address => uint) public claimable;
immutable
Avoids a Gsset (20000 gas) in the constructor, and replaces each Gwarmacces (100 gas) with a PUSH32
(3 gas).
There are 5 instances of this issue:
File: contracts/contracts/Gauge.sol #1 20: bool public isForPair;
File: contracts/contracts/RewardsDistributor.sol #2 32: uint public start_time;
File: contracts/contracts/RewardsDistributor.sol #3 40: address public voting_escrow;
File: contracts/contracts/Pair.sol #4 13: string public name;
File: contracts/contracts/Pair.sol #5 14: string public symbol;
If variables occupying the same slot are both written the same function or by the constructor, avoids a separate Gsset (20000 gas). Reads of the variables can also be cheaper
There is 1 instance of this issue:
File: contracts/contracts/VotingEscrow.sol #1 /// @audit Variable ordering with 23 slots instead of the current 24: /// @audit mapping(32):point_history, mapping(32):supportedInterfaces, uint256(32):tokenId, mapping(32):idToOwner, mapping(32):ownerToNFTokenCount, mapping(32):idToApprovals, mapping(32):ownerToOperators, mapping(32):ownership_change, mapping(32):ownerToNFTokenIdList, mapping(32):tokenToOwnerIndex, mapping(32):user_point_epoch, mapping(32):user_point_history, mapping(32):locked, uint256(32):epoch, mapping(32):slope_changes, uint256(32):supply, mapping(32):attachments, mapping(32):voted, mapping(32):_delegates, mapping(32):checkpoints, mapping(32):numCheckpoints, mapping(32):nonces, address(20):voter, uint8(1):_entered_state 67: address public voter;
calldata
instead of memory
for read-only arguments in external
functions saves gasWhen a function with a memory
array is called externally, the abi.decode()
step has to use a for-loop to copy each index of the calldata
to the memory
index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length
). Using calldata
directly, obliviates the need for such a loop in the contract code and runtime execution.
If the array is passed to an internal
function which passes the array to another internal function where the array is modified and therefore memory
is used in the external
call, it's still more gass-efficient to use calldata
when the external
function uses modifiers, since the modifiers may prevent the internal functions from being called. Structs have the same overhead as an array of length one
There are 13 instances of this issue:
File: contracts/contracts/Minter.sol #1 50: address[] memory claimants,
File: contracts/contracts/Minter.sol #2 51: uint[] memory amounts,
File: contracts/contracts/redeem/RedemptionReceiver.sol #3 74: bytes memory srcAddress,
File: contracts/contracts/redeem/RedemptionReceiver.sol #4 76: bytes memory payload
File: contracts/contracts/Router.sol #5 394: uint[] memory amounts,
File: contracts/contracts/Gauge.sol #6 347: function getReward(address account, address[] memory tokens) external lock {
File: contracts/contracts/RewardsDistributor.sol #7 294: function claim_many(uint[] memory _tokenIds) external returns (bool) {
File: contracts/contracts/Voter.sol #8 74: function initialize(address[] memory _tokens, address _minter) external {
File: contracts/contracts/Voter.sol #9 265: function updateFor(address[] memory _gauges) external {
File: contracts/contracts/Voter.sol #10 303: function claimRewards(address[] memory _gauges, address[][] memory _tokens) external {
File: contracts/contracts/Voter.sol #11 303: function claimRewards(address[] memory _gauges, address[][] memory _tokens) external {
File: contracts/contracts/Voter.sol #12 309: function distributeFees(address[] memory _gauges) external {
File: contracts/contracts/Voter.sol #13 345: function distribute(address[] memory _gauges) external {
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replace each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
There are 39 instances of this issue:
File: contracts/contracts/Minter.sol #1 /// @audit weekly 119: uint _growth = calculate_growth(weekly);
File: contracts/contracts/Minter.sol #2 /// @audit weekly 120: uint _teamEmissions = (teamRate * (_growth + weekly)) /
File: contracts/contracts/Minter.sol #3 /// @audit weekly 122: uint _required = _growth + weekly + _teamEmissions;
File: contracts/contracts/Minter.sol #4 /// @audit weekly 133: _velo.approve(address(_voter), weekly);
File: contracts/contracts/Minter.sol #5 /// @audit weekly 134: _voter.notifyRewardAmount(weekly);
File: contracts/contracts/Minter.sol #6 /// @audit weekly 136: emit Mint(msg.sender, weekly, circulating_supply(), circulating_emission());
File: contracts/contracts/Minter.sol #7 /// @audit pendingTeam 71: team = pendingTeam;
File: contracts/contracts/Minter.sol #8 /// @audit teamRate 121: (PRECISION - teamRate);
File: contracts/contracts/redeem/RedemptionReceiver.sol #9 /// @audit fantomSender 82: addressFromPackedBytes(srcAddress) == fantomSender,
File: contracts/contracts/redeem/RedemptionReceiver.sol #10 /// @audit eligibleWEVE 69: shareOfVELO = (amountWEVE * redeemableVELO) / eligibleWEVE;
File: contracts/contracts/Router.sol #11 /// @audit routes[i].from 93: amounts[i+1] = IPair(pair).getAmountOut(amounts[i], routes[i].from);
File: contracts/contracts/Router.sol #12 /// @audit routes[i].from 321: IPair(pairFor(routes[i].from, routes[i].to, routes[i].stable)).swap(
File: contracts/contracts/Router.sol #13 /// @audit routes[i].to 321: IPair(pairFor(routes[i].from, routes[i].to, routes[i].stable)).swap(
File: contracts/contracts/Router.sol #14 /// @audit routes[0].from 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #15 /// @audit routes[0].from 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #16 /// @audit routes[0].to 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #17 /// @audit routes[0].stable 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #18 /// @audit routes[0].from 358: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #19 /// @audit routes[0].from 373: assert(weth.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]));
File: contracts/contracts/Router.sol #20 /// @audit routes[0].from 386: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #21 /// @audit routes[0].from 399: _safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
File: contracts/contracts/Gauge.sol #22 /// @audit isForPair 111: if (isForPair) {
File: contracts/contracts/Gauge.sol #23 /// @audit derivedSupply 379: return rewardPerTokenStored[token] + ((lastTimeRewardApplicable(token) - Math.min(lastUpdateTime[token], periodFinish[token])) * rewardRate[token] * PRECISION / derivedSupply);
File: contracts/contracts/Gauge.sol #24 /// @audit tokenIds 526: require(tokenIds[msg.sender] == tokenId);
File: contracts/contracts/Gauge.sol #25 /// @audit supplyNumCheckpoints 403: uint _endIndex = Math.min(supplyNumCheckpoints-1, maxRuns);
File: contracts/contracts/Gauge.sol #26 /// @audit supplyNumCheckpoints 445: uint _endIndex = supplyNumCheckpoints-1;
File: contracts/contracts/VotingEscrow.sol #27 /// @audit tokenId 100: emit Transfer(address(this), address(0), tokenId);
File: contracts/contracts/VotingEscrow.sol #28 /// @audit tokenId 790: uint _tokenId = tokenId;
File: contracts/contracts/RewardsDistributor.sol #29 /// @audit voting_escrow 288: IVotingEscrow(voting_escrow).deposit_for(_tokenId, amount);
File: contracts/contracts/Velo.sol #30 /// @audit redemptionReceiver 76: _mint(redemptionReceiver, amount);
File: contracts/contracts/Bribe.sol #31 /// @audit gauge 87: _safeTransfer(token, address(gauge), rewardPerEpoch);
File: contracts/contracts/factories/GaugeFactory.sol #32 /// @audit last_gauge 25: return last_gauge;
File: contracts/contracts/factories/GaugeFactory.sol #33 /// @audit last_gauge 31: return last_gauge;
File: contracts/contracts/factories/BribeFactory.sol #34 /// @audit last_gauge 11: return last_gauge;
File: contracts/contracts/factories/PairFactory.sol #35 /// @audit pendingPauser 47: pauser = pendingPauser;
File: contracts/contracts/factories/PairFactory.sol #36 /// @audit pendingFeeManager 62: feeManager = pendingFeeManager;
File: contracts/contracts/Pair.sol #37 /// @audit DOMAIN_SEPARATOR 480: DOMAIN_SEPARATOR,
File: contracts/contracts/Pair.sol #38 /// @audit reserve0 220: emit Sync(reserve0, reserve1);
File: contracts/contracts/Pair.sol #39 /// @audit reserve1 220: emit Sync(reserve0, reserve1);
The instances below point to the second+ access of a value inside a mapping, within a function. Caching a mapping's value in a local storage
variable when the value is accessed multiple times, saves ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There are 34 instances of this issue:
File: contracts/contracts/Router.sol #1 /// @audit routes[i] 91: address pair = pairFor(routes[i].from, routes[i].to, routes[i].stable);
File: contracts/contracts/Router.sol #2 /// @audit routes[i] 91: address pair = pairFor(routes[i].from, routes[i].to, routes[i].stable);
File: contracts/contracts/Router.sol #3 /// @audit routes[i] 93: amounts[i+1] = IPair(pair).getAmountOut(amounts[i], routes[i].from);
File: contracts/contracts/Router.sol #4 /// @audit routes[i] 317: (address token0,) = sortTokens(routes[i].from, routes[i].to);
File: contracts/contracts/Router.sol #5 /// @audit routes[i] 319: (uint amount0Out, uint amount1Out) = routes[i].from == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
File: contracts/contracts/Router.sol #6 /// @audit routes[<etc>] 320: address to = i < routes.length - 1 ? pairFor(routes[i+1].from, routes[i+1].to, routes[i+1].stable) : _to;
File: contracts/contracts/Router.sol #7 /// @audit routes[<etc>] 320: address to = i < routes.length - 1 ? pairFor(routes[i+1].from, routes[i+1].to, routes[i+1].stable) : _to;
File: contracts/contracts/Router.sol #8 /// @audit routes[i] 321: IPair(pairFor(routes[i].from, routes[i].to, routes[i].stable)).swap(
File: contracts/contracts/Router.sol #9 /// @audit routes[i] 321: IPair(pairFor(routes[i].from, routes[i].to, routes[i].stable)).swap(
File: contracts/contracts/Router.sol #10 /// @audit routes[i] 321: IPair(pairFor(routes[i].from, routes[i].to, routes[i].stable)).swap(
File: contracts/contracts/Router.sol #11 /// @audit routes[0] 338: routes[0].to = tokenTo;
File: contracts/contracts/Router.sol #12 /// @audit routes[0] 339: routes[0].stable = stable;
File: contracts/contracts/Router.sol #13 /// @audit routes[0] 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #14 /// @audit routes[0] 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #15 /// @audit routes[0] 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #16 /// @audit routes[0] 343: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #17 /// @audit routes[0] 358: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #18 /// @audit routes[0] 358: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #19 /// @audit routes[0] 358: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #20 /// @audit routes[0] 373: assert(weth.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]));
File: contracts/contracts/Router.sol #21 /// @audit routes[0] 373: assert(weth.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]));
File: contracts/contracts/Router.sol #22 /// @audit routes[0] 373: assert(weth.transfer(pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]));
File: contracts/contracts/Router.sol #23 /// @audit routes[0] 386: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #24 /// @audit routes[0] 386: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #25 /// @audit routes[0] 386: routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]
File: contracts/contracts/Router.sol #26 /// @audit routes[0] 399: _safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
File: contracts/contracts/Router.sol #27 /// @audit routes[0] 399: _safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
File: contracts/contracts/Router.sol #28 /// @audit routes[0] 399: _safeTransferFrom(routes[0].from, msg.sender, pairFor(routes[0].from, routes[0].to, routes[0].stable), amounts[0]);
File: contracts/contracts/Gauge.sol #29 /// @audit supplyCheckpoints[<etc>] 331: supplyCheckpoints[_nCheckPoints - 1].supply = derivedSupply;
File: contracts/contracts/VotingEscrow.sol #30 /// @audit point_history[0] 91: point_history[0].ts = block.timestamp;
File: contracts/contracts/Pair.sol #31 /// @audit observations[nextIndex] 279: uint _reserve0 = (observations[nextIndex].reserve0Cumulative - observations[i].reserve0Cumulative) / timeElapsed;
File: contracts/contracts/Pair.sol #32 /// @audit observations[i] 279: uint _reserve0 = (observations[nextIndex].reserve0Cumulative - observations[i].reserve0Cumulative) / timeElapsed;
File: contracts/contracts/Pair.sol #33 /// @audit observations[nextIndex] 280: uint _reserve1 = (observations[nextIndex].reserve1Cumulative - observations[i].reserve1Cumulative) / timeElapsed;
File: contracts/contracts/Pair.sol #34 /// @audit observations[i] 280: uint _reserve1 = (observations[nextIndex].reserve1Cumulative - observations[i].reserve1Cumulative) / timeElapsed;
<x> += <y>
costs more gas than <x> = <x> + <y>
for state variablesThere are 21 instances of this issue:
File: contracts/contracts/redeem/RedemptionReceiver.sol #1 92: (redeemedWEVE += amountWEVE) <= eligibleWEVE,
File: contracts/contracts/Gauge.sol #2 365: derivedSupply -= _derivedBalance;
File: contracts/contracts/Gauge.sol #3 368: derivedSupply += _derivedBalance;
File: contracts/contracts/Gauge.sol #4 517: totalSupply += amount;
File: contracts/contracts/Gauge.sol #5 532: derivedSupply -= _derivedBalance;
File: contracts/contracts/Gauge.sol #6 535: derivedSupply += _derivedBalance;
File: contracts/contracts/Gauge.sol #7 559: totalSupply -= amount;
File: contracts/contracts/Gauge.sol #8 572: derivedSupply -= _derivedBalance;
File: contracts/contracts/Gauge.sol #9 575: derivedSupply += _derivedBalance;
File: contracts/contracts/RewardsDistributor.sol #10 289: token_last_balance -= amount;
File: contracts/contracts/RewardsDistributor.sol #11 311: token_last_balance -= total;
File: contracts/contracts/Velo.sol #12 44: totalSupply += _amount;
File: contracts/contracts/Pair.sol #13 155: index0 += _ratio;
File: contracts/contracts/Pair.sol #14 165: index1 += _ratio;
File: contracts/contracts/Pair.sol #15 208: reserve0CumulativeLast += _reserve0 * timeElapsed;
File: contracts/contracts/Pair.sol #16 209: reserve1CumulativeLast += _reserve1 * timeElapsed;
File: contracts/contracts/Pair.sol #17 447: totalSupply += amount;
File: contracts/contracts/Pair.sol #18 454: totalSupply -= amount;
File: contracts/contracts/Voter.sol #19 118: totalWeight -= uint256(_totalWeight);
File: contracts/contracts/Voter.sol #20 168: totalWeight += uint256(_totalWeight);
File: contracts/contracts/Voter.sol #21 260: index += _ratio;
internal
functions only called once can be inlined to save gasNot inlining costs 20 to 40 gas because of two extra JUMP
instructions and additional stack operations needed for function calls.
There are 25 instances of this issue:
File: contracts/contracts/Router.sol #1 409: function _safeTransfer(address token, address to, uint256 value) internal {
File: contracts/contracts/Gauge.sol #2 390: function _batchRewardPerToken(address token, uint maxRuns) internal returns (uint, uint) {
File: contracts/contracts/Gauge.sol #3 648: function _notifyBribeAmount(address token, uint amount, uint epochStart) internal {
File: contracts/contracts/VotingEscrow.sol #4 149: function _tokenURI(uint _tokenId, uint _balanceOf, uint _locked_end, uint _value) internal pure returns (string memory output) {
File: contracts/contracts/VotingEscrow.sol #5 270: function _clearApproval(address _owner, uint _tokenId) internal {
File: contracts/contracts/VotingEscrow.sol #6 361: function _isContract(address account) internal view returns (bool) {
File: contracts/contracts/VotingEscrow.sol #7 436: function _addTokenToOwnerList(address _to, uint _tokenId) internal {
File: contracts/contracts/VotingEscrow.sol #8 462: function _mint(address _to, uint _tokenId) internal returns (bool) {
File: contracts/contracts/VotingEscrow.sol #9 477: function _removeTokenFromOwnerList(address _from, uint _tokenId) internal {
File: contracts/contracts/VotingEscrow.sol #10 934: function _balanceOfAtNFT(uint _tokenId, uint _block) internal view returns (uint) {
File: contracts/contracts/VotingEscrow.sol #11 1278 function _moveAllDelegates( 1279 address owner, 1280 address srcRep, 1281: address dstRep
File: contracts/contracts/VelodromeLibrary.sol #12 15: function _f(uint x0, uint y) internal pure returns (uint) {
File: contracts/contracts/VelodromeLibrary.sol #13 23: function _get_y(uint x0, uint xy, uint y) internal pure returns (uint) {
File: contracts/contracts/VelodromeLibrary.sol #14 93: function _k(uint x, uint y, bool stable, uint decimals0, uint decimals1) internal pure returns (uint) {
File: contracts/contracts/RewardsDistributor.sol #15 64 function _checkpoint_token() internal { 65: uint token_balance = IERC20(token).balanceOf(address(this));
File: contracts/contracts/RewardsDistributor.sol #16 102: function _find_timestamp_epoch(address ve, uint _timestamp) internal view returns (uint) {
File: contracts/contracts/RewardsDistributor.sol #17 226: function _claimable(uint _tokenId, address ve, uint _last_token_time) internal view returns (uint) {
File: contracts/contracts/Bribe.sol #18 92: function _safeTransfer(address token, address to, uint256 value) internal {
File: contracts/contracts/Bribe.sol #19 99: function _safeTransferFrom(address token, address from, address to, uint256 value) internal {
File: contracts/contracts/Pair.sol #20 151: function _update0(uint amount) internal {
File: contracts/contracts/Pair.sol #21 161: function _update1(uint amount) internal {
File: contracts/contracts/Pair.sol #22 380: function _f(uint x0, uint y) internal pure returns (uint) {
File: contracts/contracts/Pair.sol #23 388: function _get_y(uint x0, uint xy, uint y) internal pure returns (uint) {
File: contracts/contracts/Pair.sol #24 452: function _burn(address dst, uint amount) internal {
File: contracts/contracts/Voter.sol #25 351: function _safeTransferFrom(address token, address from, address to, uint256 value) internal {
<array>.length
should not be looked up in every loop of a for
-loopThe overheads outlined below are PER LOOP, excluding the first loop
MLOAD
(3 gas)CALLDATALOAD
(3 gas)Caching the length changes each of these to a DUP<N>
(3 gas), and gets rid of the extra DUP<N>
needed to store the stack offset
There are 17 instances of this issue:
File: contracts/contracts/Minter.sol #1 57: for (uint i = 0; i < claimants.length; i++) {
File: contracts/contracts/Router.sol #2 90: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Router.sol #3 316: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Gauge.sol #4 353: for (uint i = 0; i < tokens.length; i++) {
File: contracts/contracts/VotingEscrow.sol #5 1146: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #6 1193: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #7 1225: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #8 1249: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #9 1295: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #10 1320: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/RewardsDistributor.sol #11 301: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/Pair.sol #12 257: for (uint i = 0; i < _prices.length; i++) {
File: contracts/contracts/Voter.sol #13 76: for (uint i = 0; i < _tokens.length; i++) {
File: contracts/contracts/Voter.sol #14 266: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #15 304: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #16 310: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #17 346: for (uint x = 0; x < _gauges.length; x++) {
++i
/i++
should be unchecked{++i}
/unchecked{i++}
when it is not possible for them to overflow, as is the case when used in for
- and while
-loopsThe unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas per loop
There are 41 instances of this issue:
File: contracts/contracts/Minter.sol #1 57: for (uint i = 0; i < claimants.length; i++) {
File: contracts/contracts/Router.sol #2 90: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Router.sol #3 316: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Gauge.sol #4 179: for (uint i = 0; i < numRewards; i++) {
File: contracts/contracts/Gauge.sol #5 353: for (uint i = 0; i < tokens.length; i++) {
File: contracts/contracts/Gauge.sol #6 405: for (uint i = _startIndex; i < _endIndex; i++) {
File: contracts/contracts/Gauge.sol #7 426: for (uint i; i < length; i++) {
File: contracts/contracts/Gauge.sol #8 448: for (uint i = _startIndex; i < _endIndex; i++) {
File: contracts/contracts/Gauge.sol #9 484: for (uint i = _startIndex; i < _endIndex; i++) {
File: contracts/contracts/VotingEscrow.sol #10 632: for (uint i = 0; i < 255; ++i) {
File: contracts/contracts/VotingEscrow.sol #11 886: for (uint i = 0; i < 128; ++i) {
File: contracts/contracts/VotingEscrow.sol #12 942: for (uint i = 0; i < 128; ++i) {
File: contracts/contracts/VotingEscrow.sol #13 1017: for (uint i = 0; i < 255; ++i) {
File: contracts/contracts/VotingEscrow.sol #14 1146: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #15 1193: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #16 1225: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #17 1249: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #18 1295: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #19 1320: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #20 1325: for (uint i = 0; i < ownerTokenCount; i++) {
File: contracts/contracts/VelodromeLibrary.sol #21 24: for (uint i = 0; i < 255; i++) {
File: contracts/contracts/RewardsDistributor.sol #22 75: for (uint i = 0; i < 20; i++) {
File: contracts/contracts/RewardsDistributor.sol #23 105: for (uint i = 0; i < 128; i++) {
File: contracts/contracts/RewardsDistributor.sol #24 121: for (uint i = 0; i < 128; i++) {
File: contracts/contracts/RewardsDistributor.sol #25 148: for (uint i = 0; i < 20; i++) {
File: contracts/contracts/RewardsDistributor.sol #26 195: for (uint i = 0; i < 50; i++) {
File: contracts/contracts/RewardsDistributor.sol #27 252: for (uint i = 0; i < 50; i++) {
File: contracts/contracts/RewardsDistributor.sol #28 301: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/Pair.sol #29 257: for (uint i = 0; i < _prices.length; i++) {
File: contracts/contracts/Pair.sol #30 389: for (uint i = 0; i < 255; i++) {
File: contracts/contracts/Voter.sol #31 76: for (uint i = 0; i < _tokens.length; i++) {
File: contracts/contracts/Voter.sol #32 103: for (uint i = 0; i < _poolVoteCnt; i ++) {
File: contracts/contracts/Voter.sol #33 128: for (uint i = 0; i < _poolCnt; i ++) {
File: contracts/contracts/Voter.sol #34 143: for (uint i = 0; i < _poolCnt; i++) {
File: contracts/contracts/Voter.sol #35 147: for (uint i = 0; i < _poolCnt; i++) {
File: contracts/contracts/Voter.sol #36 266: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #37 272: for (uint i = start; i < end; i++) {
File: contracts/contracts/Voter.sol #38 304: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #39 310: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #40 340: for (uint x = start; x < finish; x++) {
File: contracts/contracts/Voter.sol #41 346: for (uint x = 0; x < _gauges.length; x++) {
require()
/revert()
strings longer than 32 bytes cost extra gasThere are 14 instances of this issue:
File: contracts/contracts/Router.sol #1 341: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #2 356: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #3 371: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #4 384: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #5 406: require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
File: contracts/contracts/VotingEscrow.sol #6 398: revert('ERC721: transfer to non ERC721Receiver implementer');
File: contracts/contracts/VotingEscrow.sol #7 774: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');
File: contracts/contracts/VotingEscrow.sol #8 786: require(unlock_time > block.timestamp, 'Can only lock until time in the future');
File: contracts/contracts/VotingEscrow.sol #9 821: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');
File: contracts/contracts/VotingEscrow.sol #10 1245 require( 1246 dstRepOld.length + 1 <= MAX_DELEGATES, 1247 "dstRep would have too many tokenIds" 1248: );
File: contracts/contracts/VotingEscrow.sol #11 1315 require( 1316 dstRepOld.length + ownerTokenCount <= MAX_DELEGATES, 1317 "dstRep would have too many tokenIds" 1318: );
File: contracts/contracts/VotingEscrow.sol #12 1378 require( 1379 signatory != address(0), 1380 "VotingEscrow::delegateBySig: invalid signature" 1381: );
File: contracts/contracts/VotingEscrow.sol #13 1382 require( 1383 nonce == nonces[signatory]++, 1384 "VotingEscrow::delegateBySig: invalid nonce" 1385: );
File: contracts/contracts/VotingEscrow.sol #14 1386 require( 1387 block.timestamp <= expiry, 1388 "VotingEscrow::delegateBySig: signature expired" 1389: );
keccak256()
should only need to be called on a specific string literal onceIt should be saved to an immutable variable, and the variable used instead. If the hash is being used as a part of a function selector, the cast to bytes4
should also only be done once
There is 1 instance of this issue:
File: contracts/contracts/Pair.sol #1 470: keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
bool
s for storage incurs overhead// Booleans are more expensive than uint256 or any type that takes up a full // word because each write operation emits an extra SLOAD to first read the // slot's contents, replace the bits taken up by the boolean, and then write // back. This is the compiler's defense against contract upgrades and // pointer aliasing, and it cannot be disabled.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/58f635312aa21f947cae5f8578638a85aa2519f5/contracts/security/ReentrancyGuard.sol#L23-L27
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
There are 14 instances of this issue:
File: contracts/contracts/Gauge.sol #1 20: bool public isForPair;
File: contracts/contracts/Gauge.sol #2 53: mapping(address => bool) public isReward;
File: contracts/contracts/VotingEscrow.sol #3 71: mapping(bytes4 => bool) internal supportedInterfaces;
File: contracts/contracts/VotingEscrow.sol #4 212: mapping(address => mapping(address => bool)) internal ownerToOperators;
File: contracts/contracts/VotingEscrow.sol #5 1057: mapping(uint => bool) public voted;
File: contracts/contracts/Bribe.sol #6 17: mapping(address => bool) public isReward;
File: contracts/contracts/factories/PairFactory.sol #7 8: bool public isPaused;
File: contracts/contracts/factories/PairFactory.sol #8 18: mapping(address => mapping(address => mapping(bool => address))) public getPair;
File: contracts/contracts/factories/PairFactory.sol #9 20: mapping(address => bool) public isPair; // simplified check if its a pair, given that `stable` flag might not be available in peripherals
File: contracts/contracts/factories/PairFactory.sol #10 24: bool internal _temp;
File: contracts/contracts/Pair.sol #11 18: bool public immutable stable;
File: contracts/contracts/Voter.sol #12 37: mapping(address => bool) public isGauge;
File: contracts/contracts/Voter.sol #13 38: mapping(address => bool) public isWhitelisted;
File: contracts/contracts/Voter.sol #14 39: mapping(address => bool) public isAlive;
> 0
costs more gas than != 0
when used on a uint
in a require()
statementThis change saves 6 gas per instance
There are 6 instances of this issue:
File: contracts/contracts/Router.sol #1 58: require(amountA > 0, 'Router: INSUFFICIENT_AMOUNT');
File: contracts/contracts/Gauge.sol #2 512: require(amount > 0);
File: contracts/contracts/Gauge.sol #3 592: require(amount > 0);
File: contracts/contracts/VotingEscrow.sol #4 772: require(_value > 0); // dev: need non-zero value
File: contracts/contracts/VotingEscrow.sol #5 785: require(_value > 0); // dev: need non-zero value
File: contracts/contracts/Bribe.sol #6 42: require(amount > 0);
There are 68 instances of this issue:
File: contracts/contracts/Minter.sol #1 57: for (uint i = 0; i < claimants.length; i++) {
File: contracts/contracts/Router.sol #2 90: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Router.sol #3 112: uint _totalSupply = 0;
File: contracts/contracts/Router.sol #4 316: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Gauge.sol #5 179: for (uint i = 0; i < numRewards; i++) {
File: contracts/contracts/Gauge.sol #6 222: uint lower = 0;
File: contracts/contracts/Gauge.sol #7 254: uint lower = 0;
File: contracts/contracts/Gauge.sol #8 286: uint lower = 0;
File: contracts/contracts/Gauge.sol #9 353: for (uint i = 0; i < tokens.length; i++) {
File: contracts/contracts/Gauge.sol #10 481: uint reward = 0;
File: contracts/contracts/Gauge.sol #11 551: uint tokenId = 0;
File: contracts/contracts/VotingEscrow.sol #12 584: int128 old_dslope = 0;
File: contracts/contracts/VotingEscrow.sol #13 585: int128 new_dslope = 0;
File: contracts/contracts/VotingEscrow.sol #14 632: for (uint i = 0; i < 255; ++i) {
File: contracts/contracts/VotingEscrow.sol #15 636: int128 d_slope = 0;
File: contracts/contracts/VotingEscrow.sol #16 884: uint _min = 0;
File: contracts/contracts/VotingEscrow.sol #17 886: for (uint i = 0; i < 128; ++i) {
File: contracts/contracts/VotingEscrow.sol #18 940: uint _min = 0;
File: contracts/contracts/VotingEscrow.sol #19 942: for (uint i = 0; i < 128; ++i) {
File: contracts/contracts/VotingEscrow.sol #20 960: uint d_block = 0;
File: contracts/contracts/VotingEscrow.sol #21 961: uint d_t = 0;
File: contracts/contracts/VotingEscrow.sol #22 996: uint dt = 0;
File: contracts/contracts/VotingEscrow.sol #23 1017: for (uint i = 0; i < 255; ++i) {
File: contracts/contracts/VotingEscrow.sol #24 1019: int128 d_slope = 0;
File: contracts/contracts/VotingEscrow.sol #25 1145: uint votes = 0;
File: contracts/contracts/VotingEscrow.sol #26 1146: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #27 1168: uint32 lower = 0;
File: contracts/contracts/VotingEscrow.sol #28 1192: uint votes = 0;
File: contracts/contracts/VotingEscrow.sol #29 1193: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #30 1225: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #31 1249: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #32 1295: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #33 1320: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #34 1325: for (uint i = 0; i < ownerTokenCount; i++) {
File: contracts/contracts/VelodromeLibrary.sol #35 24: for (uint i = 0; i < 255; i++) {
File: contracts/contracts/RewardsDistributor.sol #36 73: uint next_week = 0;
File: contracts/contracts/RewardsDistributor.sol #37 75: for (uint i = 0; i < 20; i++) {
File: contracts/contracts/RewardsDistributor.sol #38 103: uint _min = 0;
File: contracts/contracts/RewardsDistributor.sol #39 105: for (uint i = 0; i < 128; i++) {
File: contracts/contracts/RewardsDistributor.sol #40 119: uint _min = 0;
File: contracts/contracts/RewardsDistributor.sol #41 121: for (uint i = 0; i < 128; i++) {
File: contracts/contracts/RewardsDistributor.sol #42 148: for (uint i = 0; i < 20; i++) {
File: contracts/contracts/RewardsDistributor.sol #43 154: int128 dt = 0;
File: contracts/contracts/RewardsDistributor.sol #44 170: uint user_epoch = 0;
File: contracts/contracts/RewardsDistributor.sol #45 171: uint to_distribute = 0;
File: contracts/contracts/RewardsDistributor.sol #46 195: for (uint i = 0; i < 50; i++) {
File: contracts/contracts/RewardsDistributor.sol #47 227: uint user_epoch = 0;
File: contracts/contracts/RewardsDistributor.sol #48 228: uint to_distribute = 0;
File: contracts/contracts/RewardsDistributor.sol #49 252: for (uint i = 0; i < 50; i++) {
File: contracts/contracts/RewardsDistributor.sol #50 299: uint total = 0;
File: contracts/contracts/RewardsDistributor.sol #51 301: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/Pair.sol #52 257: for (uint i = 0; i < _prices.length; i++) {
File: contracts/contracts/Pair.sol #53 273: uint nextIndex = 0;
File: contracts/contracts/Pair.sol #54 274: uint index = 0;
File: contracts/contracts/Pair.sol #55 389: for (uint i = 0; i < 255; i++) {
File: contracts/contracts/Voter.sol #56 76: for (uint i = 0; i < _tokens.length; i++) {
File: contracts/contracts/Voter.sol #57 101: uint256 _totalWeight = 0;
File: contracts/contracts/Voter.sol #58 103: for (uint i = 0; i < _poolVoteCnt; i ++) {
File: contracts/contracts/Voter.sol #59 128: for (uint i = 0; i < _poolCnt; i ++) {
File: contracts/contracts/Voter.sol #60 139: uint256 _totalVoteWeight = 0;
File: contracts/contracts/Voter.sol #61 140: uint256 _totalWeight = 0;
File: contracts/contracts/Voter.sol #62 141: uint256 _usedWeight = 0;
File: contracts/contracts/Voter.sol #63 143: for (uint i = 0; i < _poolCnt; i++) {
File: contracts/contracts/Voter.sol #64 147: for (uint i = 0; i < _poolCnt; i++) {
File: contracts/contracts/Voter.sol #65 266: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #66 304: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #67 310: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #68 346: for (uint x = 0; x < _gauges.length; x++) {
++i
costs less gas than i++
, especially when it's used in for
-loops (--i
/i--
too)Saves 6 gas per loop
There are 37 instances of this issue:
File: contracts/contracts/Minter.sol #1 57: for (uint i = 0; i < claimants.length; i++) {
File: contracts/contracts/Router.sol #2 90: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Router.sol #3 316: for (uint i = 0; i < routes.length; i++) {
File: contracts/contracts/Gauge.sol #4 179: for (uint i = 0; i < numRewards; i++) {
File: contracts/contracts/Gauge.sol #5 353: for (uint i = 0; i < tokens.length; i++) {
File: contracts/contracts/Gauge.sol #6 405: for (uint i = _startIndex; i < _endIndex; i++) {
File: contracts/contracts/Gauge.sol #7 426: for (uint i; i < length; i++) {
File: contracts/contracts/Gauge.sol #8 448: for (uint i = _startIndex; i < _endIndex; i++) {
File: contracts/contracts/Gauge.sol #9 484: for (uint i = _startIndex; i < _endIndex; i++) {
File: contracts/contracts/VotingEscrow.sol #10 1146: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #11 1193: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/VotingEscrow.sol #12 1225: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #13 1249: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #14 1295: for (uint i = 0; i < srcRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #15 1320: for (uint i = 0; i < dstRepOld.length; i++) {
File: contracts/contracts/VotingEscrow.sol #16 1325: for (uint i = 0; i < ownerTokenCount; i++) {
File: contracts/contracts/VelodromeLibrary.sol #17 24: for (uint i = 0; i < 255; i++) {
File: contracts/contracts/RewardsDistributor.sol #18 75: for (uint i = 0; i < 20; i++) {
File: contracts/contracts/RewardsDistributor.sol #19 105: for (uint i = 0; i < 128; i++) {
File: contracts/contracts/RewardsDistributor.sol #20 121: for (uint i = 0; i < 128; i++) {
File: contracts/contracts/RewardsDistributor.sol #21 148: for (uint i = 0; i < 20; i++) {
File: contracts/contracts/RewardsDistributor.sol #22 195: for (uint i = 0; i < 50; i++) {
File: contracts/contracts/RewardsDistributor.sol #23 252: for (uint i = 0; i < 50; i++) {
File: contracts/contracts/RewardsDistributor.sol #24 301: for (uint i = 0; i < _tokenIds.length; i++) {
File: contracts/contracts/Pair.sol #25 257: for (uint i = 0; i < _prices.length; i++) {
File: contracts/contracts/Pair.sol #26 389: for (uint i = 0; i < 255; i++) {
File: contracts/contracts/Voter.sol #27 76: for (uint i = 0; i < _tokens.length; i++) {
File: contracts/contracts/Voter.sol #28 103: for (uint i = 0; i < _poolVoteCnt; i ++) {
File: contracts/contracts/Voter.sol #29 128: for (uint i = 0; i < _poolCnt; i ++) {
File: contracts/contracts/Voter.sol #30 143: for (uint i = 0; i < _poolCnt; i++) {
File: contracts/contracts/Voter.sol #31 147: for (uint i = 0; i < _poolCnt; i++) {
File: contracts/contracts/Voter.sol #32 266: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #33 272: for (uint i = start; i < end; i++) {
File: contracts/contracts/Voter.sol #34 304: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #35 310: for (uint i = 0; i < _gauges.length; i++) {
File: contracts/contracts/Voter.sol #36 340: for (uint x = start; x < finish; x++) {
File: contracts/contracts/Voter.sol #37 346: for (uint x = 0; x < _gauges.length; x++) {
require()
statements that use &&
saves gasSee this issue which describes the fact that there is a larger deployment gas cost, but with enough runtime calls, the change ends up being cheaper
There are 17 instances of this issue:
File: contracts/contracts/redeem/RedemptionReceiver.sol #1 79 require( 80 msg.sender == endpoint && 81 srcChainId == fantomChainId && 82 addressFromPackedBytes(srcAddress) == fantomSender, 83 "UNAUTHORIZED_CALLER" 84: );
File: contracts/contracts/Router.sol #2 59: require(reserveA > 0 && reserveB > 0, 'Router: INSUFFICIENT_LIQUIDITY');
File: contracts/contracts/Router.sol #3 413: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Router.sol #4 420: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Gauge.sol #5 668: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Gauge.sol #6 675: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Gauge.sol #7 682: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/VotingEscrow.sol #8 307: require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
File: contracts/contracts/VotingEscrow.sol #9 846: require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
File: contracts/contracts/VotingEscrow.sol #10 1085: require(attachments[_from] == 0 && !voted[_from], "attached");
File: contracts/contracts/PairFees.sol #11 23: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Bribe.sol #12 96: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Bribe.sol #13 103: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Pair.sol #14 485: require(recoveredAddress != address(0) && recoveredAddress == owner, 'Pair: INVALID_SIGNATURE');
File: contracts/contracts/Pair.sol #15 525: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Voter.sol #16 194: require(isWhitelisted[tokenA] && isWhitelisted[tokenB], "!whitelisted");
File: contracts/contracts/Voter.sol #17 355: require(success && (data.length == 0 || abi.decode(data, (bool))));
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contractâs gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed
There are 45 instances of this issue:
File: contracts/contracts/redeem/RedemptionSender.sol #1 11: uint16 public immutable optimismChainId; // 11 for OP, 10011 for OP Kovan
File: contracts/contracts/redeem/RedemptionSender.sol #2 17: uint16 _optimismChainId,
File: contracts/contracts/redeem/RedemptionReceiver.sol #3 14: uint16 public immutable fantomChainId; // 12 for FTM, 10012 for FTM testnet
File: contracts/contracts/redeem/RedemptionReceiver.sol #4 21: uint16 _fantomChainId,
File: contracts/contracts/redeem/RedemptionReceiver.sol #5 73: uint16 srcChainId,
File: contracts/contracts/redeem/RedemptionReceiver.sol #6 75: uint64,
File: contracts/contracts/Router.sol #7 286: bool approveMax, uint8 v, bytes32 r, bytes32 s
File: contracts/contracts/Router.sol #8 305: bool approveMax, uint8 v, bytes32 r, bytes32 s
File: contracts/contracts/VotingEscrow.sol #9 27: int128 amount;
File: contracts/contracts/VotingEscrow.sol #10 32: int128 bias;
File: contracts/contracts/VotingEscrow.sol #11 33: int128 slope; // # -dweight / dt
File: contracts/contracts/VotingEscrow.sol #12 108: uint8 internal constant _not_entered = 1;
File: contracts/contracts/VotingEscrow.sol #13 109: uint8 internal constant _entered = 2;
File: contracts/contracts/VotingEscrow.sol #14 110: uint8 internal _entered_state = 1;
File: contracts/contracts/VotingEscrow.sol #15 125: uint8 constant public decimals = 18;
File: contracts/contracts/VotingEscrow.sol #16 543: int128 internal constant iMAXTIME = 4 * 365 * 86400;
File: contracts/contracts/VotingEscrow.sol #17 553: function get_last_user_slope(uint _tokenId) external view returns (int128) {
File: contracts/contracts/VotingEscrow.sol #18 584: int128 old_dslope = 0;
File: contracts/contracts/VotingEscrow.sol #19 585: int128 new_dslope = 0;
File: contracts/contracts/VotingEscrow.sol #20 636: int128 d_slope = 0;
File: contracts/contracts/VotingEscrow.sol #21 1019: int128 d_slope = 0;
File: contracts/contracts/VotingEscrow.sol #22 1140: uint32 nCheckpoints = numCheckpoints[account];
File: contracts/contracts/VotingEscrow.sol #23 1153: function getPastVotesIndex(address account, uint timestamp) public view returns (uint32) {
File: contracts/contracts/VotingEscrow.sol #24 1154: uint32 nCheckpoints = numCheckpoints[account];
File: contracts/contracts/VotingEscrow.sol #25 1168: uint32 lower = 0;
File: contracts/contracts/VotingEscrow.sol #26 1169: uint32 upper = nCheckpoints - 1;
File: contracts/contracts/VotingEscrow.sol #27 1171: uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
File: contracts/contracts/VotingEscrow.sol #28 1189: uint32 _checkIndex = getPastVotesIndex(account, timestamp);
File: contracts/contracts/VotingEscrow.sol #29 1216: uint32 srcRepNum = numCheckpoints[srcRep];
File: contracts/contracts/VotingEscrow.sol #30 1220: uint32 nextSrcRepNum = _findWhatCheckpointToWrite(srcRep);
File: contracts/contracts/VotingEscrow.sol #31 1236: uint32 dstRepNum = numCheckpoints[dstRep];
File: contracts/contracts/VotingEscrow.sol #32 1240: uint32 nextDstRepNum = _findWhatCheckpointToWrite(dstRep);
File: contracts/contracts/VotingEscrow.sol #33 1263: returns (uint32)
File: contracts/contracts/VotingEscrow.sol #34 1266: uint32 _nCheckPoints = numCheckpoints[account];
File: contracts/contracts/VotingEscrow.sol #35 1286: uint32 srcRepNum = numCheckpoints[srcRep];
File: contracts/contracts/VotingEscrow.sol #36 1290: uint32 nextSrcRepNum = _findWhatCheckpointToWrite(srcRep);
File: contracts/contracts/VotingEscrow.sol #37 1306: uint32 dstRepNum = numCheckpoints[dstRep];
File: contracts/contracts/VotingEscrow.sol #38 1310: uint32 nextDstRepNum = _findWhatCheckpointToWrite(dstRep);
File: contracts/contracts/VotingEscrow.sol #39 1358: uint8 v,
File: contracts/contracts/RewardsDistributor.sol #40 154: int128 dt = 0;
File: contracts/contracts/RewardsDistributor.sol #41 207: int128 dt = int128(int256(week_cursor - old_user_point.ts));
File: contracts/contracts/RewardsDistributor.sol #42 264: int128 dt = int128(int256(week_cursor - old_user_point.ts));
File: contracts/contracts/Velo.sol #43 8: uint8 public constant decimals = 18;
File: contracts/contracts/Pair.sol #44 15: uint8 public constant decimals = 18;
File: contracts/contracts/Pair.sol #45 466: function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
abi.encode()
is less efficient than abi.encodePacked()
There is 1 instance of this issue:
File: contracts/contracts/redeem/RedemptionSender.sol #1 46: abi.encode(msg.sender, amount),
private
rather than public
for constants, saves gasIf needed, the value can be read from the verified contract source code. Savings are due to the compiler not having to create non-payable getter functions for deployment calldata, and not adding another entry to the method ID table
There are 18 instances of this issue:
File: contracts/contracts/Minter.sol #1 30: uint public constant MAX_TEAM_RATE = 50; // 50 bps = 0.05%
File: contracts/contracts/redeem/RedemptionSender.sol #2 11: uint16 public immutable optimismChainId; // 11 for OP, 10011 for OP Kovan
File: contracts/contracts/redeem/RedemptionReceiver.sol #3 14: uint16 public immutable fantomChainId; // 12 for FTM, 10012 for FTM testnet
File: contracts/contracts/VotingEscrow.sol #4 122: string constant public name = "veNFT";
File: contracts/contracts/VotingEscrow.sol #5 123: string constant public symbol = "veNFT";
File: contracts/contracts/VotingEscrow.sol #6 124: string constant public version = "1.0.0";
File: contracts/contracts/VotingEscrow.sol #7 125: uint8 constant public decimals = 18;
File: contracts/contracts/VotingEscrow.sol #8 1106: bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");
File: contracts/contracts/VotingEscrow.sol #9 1109: bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegatee,uint256 nonce,uint256 expiry)");
File: contracts/contracts/VotingEscrow.sol #10 1113: uint public constant MAX_DELEGATES = 1024; // avoid too much gas
File: contracts/contracts/VeloGovernor.sol #11 19: uint256 public constant MAX_PROPOSAL_NUMERATOR = 50; // max 5%
File: contracts/contracts/VeloGovernor.sol #12 20: uint256 public constant PROPOSAL_DENOMINATOR = 1000;
File: contracts/contracts/Velo.sol #13 6: string public constant name = "Velodrome";
File: contracts/contracts/Velo.sol #14 7: string public constant symbol = "VELO";
File: contracts/contracts/Velo.sol #15 8: uint8 public constant decimals = 18;
File: contracts/contracts/factories/PairFactory.sol #16 14: uint256 public constant MAX_FEE = 5; // 0.05%
File: contracts/contracts/Pair.sol #17 15: uint8 public constant decimals = 18;
File: contracts/contracts/Pair.sol #18 18: bool public immutable stable;
require()
/revert()
checks should be refactored to a modifier or functionSaves deployment costs
There are 30 instances of this issue:
File: contracts/contracts/Minter.sol #1 75: require(msg.sender == team, "not team");
File: contracts/contracts/Router.sol #2 356: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #3 417: require(token.code.length > 0);
File: contracts/contracts/Router.sol #4 420: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/Gauge.sol #5 189: require(msg.sender == voter);
File: contracts/contracts/Gauge.sol #6 592: require(amount > 0);
File: contracts/contracts/Gauge.sol #7 642: require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
File: contracts/contracts/Gauge.sol #8 654: require(amount > _left);
File: contracts/contracts/Gauge.sol #9 635: require(msg.sender == IGaugeFactory(factory).team(), 'only team');
File: contracts/contracts/Gauge.sol #10 672: require(token.code.length > 0);
File: contracts/contracts/Gauge.sol #11 675: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/VotingEscrow.sol #12 846: require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
File: contracts/contracts/VotingEscrow.sol #13 785: require(_value > 0); // dev: need non-zero value
File: contracts/contracts/VotingEscrow.sol #14 820: require(_locked.amount > 0, 'No existing lock found');
File: contracts/contracts/VotingEscrow.sol #15 821: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');
File: contracts/contracts/VotingEscrow.sol #16 837: require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');
File: contracts/contracts/VotingEscrow.sol #17 1065: require(msg.sender == voter);
File: contracts/contracts/VeloGovernor.sol #18 45: require(msg.sender == team, "not team");
File: contracts/contracts/Velo.sol #19 32: require(msg.sender == minter);
File: contracts/contracts/Bribe.sol #20 69: require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
File: contracts/contracts/Bribe.sol #21 76: require(msg.sender == gauge);
File: contracts/contracts/Bribe.sol #22 100: require(token.code.length > 0);
File: contracts/contracts/Bribe.sol #23 103: require(success && (data.length == 0 || abi.decode(data, (bool))));
File: contracts/contracts/factories/PairFactory.sol #24 51: require(msg.sender == pauser);
File: contracts/contracts/factories/PairFactory.sol #25 66: require(msg.sender == feeManager, 'not fee manager');
File: contracts/contracts/Voter.sol #26 179: require(msg.sender == governor);
File: contracts/contracts/Voter.sol #27 218: require(msg.sender == emergencyCouncil, "not emergency council");
File: contracts/contracts/Voter.sol #28 232: require(isGauge[msg.sender]);
File: contracts/contracts/Voter.sol #29 233: require(isAlive[msg.sender]);
File: contracts/contracts/Voter.sol #30 316: require(isAlive[_gauge]); // killed gauges cannot distribute
<x> / 2
is the same as <x> >> 1
. The DIV
opcode costs 5 gas, whereas SHR
only costs 3 gas
There are 9 instances of this issue:
File: contracts/contracts/Minter.sol #1 105 (((((_minted * _veTotal) / _veloTotal) * _veTotal) / _veloTotal) * 106 _veTotal) / 107 _veloTotal / 108: 2;
File: contracts/contracts/Gauge.sol #2 225: uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
File: contracts/contracts/Gauge.sol #3 257: uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
File: contracts/contracts/Gauge.sol #4 289: uint center = upper - (upper - lower) / 2; // ceil, avoiding overflow
File: contracts/contracts/VotingEscrow.sol #5 891: uint _mid = (_min + _max + 1) / 2;
File: contracts/contracts/VotingEscrow.sol #6 947: uint _mid = (_min + _max + 1) / 2;
File: contracts/contracts/VotingEscrow.sol #7 1171: uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow
File: contracts/contracts/RewardsDistributor.sol #8 107: uint _mid = (_min + _max + 2) / 2;
File: contracts/contracts/RewardsDistributor.sol #9 123: uint _mid = (_min + _max + 2) / 2;
If the variable is only accessed once, it's cheaper to use the state variable directly that one time
There is 1 instance of this issue:
File: contracts/contracts/VotingEscrow.sol #1 1047: uint _epoch = epoch;
require()
or revert()
statements that check input arguments should be at the top of the functionChecks that involve constants should come before checks that involve state variables
There are 13 instances of this issue:
File: contracts/contracts/Minter.sol #1 76: require(_teamRate <= MAX_TEAM_RATE, "rate too high");
File: contracts/contracts/Router.sol #2 341: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #3 356: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #4 371: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #5 384: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Gauge.sol #6 628: require(rewards[i] == oldToken);
File: contracts/contracts/VotingEscrow.sol #7 772: require(_value > 0); // dev: need non-zero value
File: contracts/contracts/VotingEscrow.sol #8 785: require(_value > 0); // dev: need non-zero value
File: contracts/contracts/VotingEscrow.sol #9 1086: require(_from != _to);
File: contracts/contracts/VotingEscrow.sol #10 1382 require( 1383 nonce == nonces[signatory]++, 1384 "VotingEscrow::delegateBySig: invalid nonce" 1385: );
File: contracts/contracts/VeloGovernor.sol #11 46: require(numerator <= MAX_PROPOSAL_NUMERATOR, "numerator too high");
File: contracts/contracts/factories/PairFactory.sol #12 67: require(_fee <= MAX_FEE, 'fee too high');
File: contracts/contracts/factories/PairFactory.sol #13 68: require(_fee != 0, 'fee must be nonzero');
revert()
/require()
strings to save deployment gasCustom errors are available from solidity version 0.8.4. The instances below match or exceed that version
There are 86 instances of this issue:
File: contracts/contracts/Minter.sol #1 65: require(msg.sender == team, "not team");
File: contracts/contracts/Minter.sol #2 70: require(msg.sender == pendingTeam, "not pending team");
File: contracts/contracts/Minter.sol #3 75: require(msg.sender == team, "not team");
File: contracts/contracts/Minter.sol #4 76: require(_teamRate <= MAX_TEAM_RATE, "rate too high");
File: contracts/contracts/redeem/RedemptionSender.sol #5 21: require(_optimismChainId == 11 || _optimismChainId == 10011, "CHAIN_ID_NOT_OP");
File: contracts/contracts/redeem/RedemptionSender.sol #6 33: require(amount != 0, "AMOUNT_ZERO");
File: contracts/contracts/redeem/RedemptionSender.sol #7 34 require( 35 IERC20(weve).transferFrom( 36 msg.sender, 37 0x000000000000000000000000000000000000dEaD, 38 amount 39 ), 40 "WEVE: TRANSFER_FAILED" 41: );
File: contracts/contracts/redeem/RedemptionReceiver.sol #8 24: require(_fantomChainId == 12 || _fantomChainId == 10012, "CHAIN_ID_NOT_FTM");
File: contracts/contracts/redeem/RedemptionReceiver.sol #9 43: require(msg.sender == deployer, "ONLY_DEPLOYER");
File: contracts/contracts/redeem/RedemptionReceiver.sol #10 44: require(fantomSender == address(0), "ALREADY_INITIALIZED");
File: contracts/contracts/redeem/RedemptionReceiver.sol #11 45 require( 46 USDC.transferFrom(msg.sender, address(this), _redeemableUSDC), 47 "USDC_TRANSFER_FAILED" 48: );
File: contracts/contracts/redeem/RedemptionReceiver.sol #12 49 require( 50 VELO.mintToRedemptionReceiver(_redeemableVELO), 51 "VELO_MINT_FAILED" 52: );
File: contracts/contracts/redeem/RedemptionReceiver.sol #13 78: require(fantomSender != address(0), "NOT_INITIALIZED");
File: contracts/contracts/redeem/RedemptionReceiver.sol #14 79 require( 80 msg.sender == endpoint && 81 srcChainId == fantomChainId && 82 addressFromPackedBytes(srcAddress) == fantomSender, 83 "UNAUTHORIZED_CALLER" 84: );
File: contracts/contracts/redeem/RedemptionReceiver.sol #15 91 require( 92 (redeemedWEVE += amountWEVE) <= eligibleWEVE, 93 "cannot redeem more than eligible" 94: );
File: contracts/contracts/redeem/RedemptionReceiver.sol #16 97 require( 98 USDC.transfer(redemptionAddress, shareOfUSDC), 99 "USDC_TRANSFER_FAILED" 100: );
File: contracts/contracts/redeem/RedemptionReceiver.sol #17 101 require( 102 VELO.transfer(redemptionAddress, shareOfVELO), 103 "VELO_TRANSFER_FAILED" 104: );
File: contracts/contracts/Router.sol #18 25: require(deadline >= block.timestamp, 'Router: EXPIRED');
File: contracts/contracts/Router.sol #19 40: require(tokenA != tokenB, 'Router: IDENTICAL_ADDRESSES');
File: contracts/contracts/Router.sol #20 42: require(token0 != address(0), 'Router: ZERO_ADDRESS');
File: contracts/contracts/Router.sol #21 58: require(amountA > 0, 'Router: INSUFFICIENT_AMOUNT');
File: contracts/contracts/Router.sol #22 59: require(reserveA > 0 && reserveB > 0, 'Router: INSUFFICIENT_LIQUIDITY');
File: contracts/contracts/Router.sol #23 87: require(routes.length >= 1, 'Router: INVALID_PATH');
File: contracts/contracts/Router.sol #24 177: require(amountBOptimal >= amountBMin, 'Router: INSUFFICIENT_B_AMOUNT');
File: contracts/contracts/Router.sol #25 182: require(amountAOptimal >= amountAMin, 'Router: INSUFFICIENT_A_AMOUNT');
File: contracts/contracts/Router.sol #26 249: require(amountA >= amountAMin, 'Router: INSUFFICIENT_A_AMOUNT');
File: contracts/contracts/Router.sol #27 250: require(amountB >= amountBMin, 'Router: INSUFFICIENT_B_AMOUNT');
File: contracts/contracts/Router.sol #28 341: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #29 356: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #30 369: require(routes[0].from == address(weth), 'Router: INVALID_PATH');
File: contracts/contracts/Router.sol #31 371: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #32 382: require(routes[routes.length - 1].to == address(weth), 'Router: INVALID_PATH');
File: contracts/contracts/Router.sol #33 384: require(amounts[amounts.length - 1] >= amountOutMin, 'Router: INSUFFICIENT_OUTPUT_AMOUNT');
File: contracts/contracts/Router.sol #34 406: require(success, 'TransferHelper: ETH_TRANSFER_FAILED');
File: contracts/contracts/Gauge.sol #35 594: require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
File: contracts/contracts/Gauge.sol #36 615: require(rewardRate[token] <= balance / DURATION, "Provided reward too high");
File: contracts/contracts/Gauge.sol #37 627: require(msg.sender == IGaugeFactory(factory).team(), 'only team');
File: contracts/contracts/Gauge.sol #38 635: require(msg.sender == IGaugeFactory(factory).team(), 'only team');
File: contracts/contracts/Gauge.sol #39 642: require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
File: contracts/contracts/VotingEscrow.sol #40 163: require(idToOwner[_tokenId] != address(0), "Query for nonexistent token");
File: contracts/contracts/VotingEscrow.sol #41 307: require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
File: contracts/contracts/VotingEscrow.sol #42 518: require(_isApprovedOrOwner(msg.sender, _tokenId), "caller is not owner nor approved");
File: contracts/contracts/VotingEscrow.sol #43 773: require(_locked.amount > 0, 'No existing lock found');
File: contracts/contracts/VotingEscrow.sol #44 774: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');
File: contracts/contracts/VotingEscrow.sol #45 786: require(unlock_time > block.timestamp, 'Can only lock until time in the future');
File: contracts/contracts/VotingEscrow.sol #46 787: require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');
File: contracts/contracts/VotingEscrow.sol #47 820: require(_locked.amount > 0, 'No existing lock found');
File: contracts/contracts/VotingEscrow.sol #48 821: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');
File: contracts/contracts/VotingEscrow.sol #49 834: require(_locked.end > block.timestamp, 'Lock expired');
File: contracts/contracts/VotingEscrow.sol #50 835: require(_locked.amount > 0, 'Nothing is locked');
File: contracts/contracts/VotingEscrow.sol #51 836: require(unlock_time > _locked.end, 'Can only increase lock duration');
File: contracts/contracts/VotingEscrow.sol #52 837: require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 4 years max');
File: contracts/contracts/VotingEscrow.sol #53 846: require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
File: contracts/contracts/VotingEscrow.sol #54 849: require(block.timestamp >= _locked.end, "The lock didn't expire");
File: contracts/contracts/VotingEscrow.sol #55 1085: require(attachments[_from] == 0 && !voted[_from], "attached");
File: contracts/contracts/VotingEscrow.sol #56 1245 require( 1246 dstRepOld.length + 1 <= MAX_DELEGATES, 1247 "dstRep would have too many tokenIds" 1248: );
File: contracts/contracts/VotingEscrow.sol #57 1315 require( 1316 dstRepOld.length + ownerTokenCount <= MAX_DELEGATES, 1317 "dstRep would have too many tokenIds" 1318: );
File: contracts/contracts/VotingEscrow.sol #58 1378 require( 1379 signatory != address(0), 1380 "VotingEscrow::delegateBySig: invalid signature" 1381: );
File: contracts/contracts/VotingEscrow.sol #59 1382 require( 1383 nonce == nonces[signatory]++, 1384 "VotingEscrow::delegateBySig: invalid nonce" 1385: );
File: contracts/contracts/VotingEscrow.sol #60 1386 require( 1387 block.timestamp <= expiry, 1388 "VotingEscrow::delegateBySig: signature expired" 1389: );
File: contracts/contracts/VeloGovernor.sol #61 40: require(msg.sender == team, "not team");
File: contracts/contracts/VeloGovernor.sol #62 45: require(msg.sender == team, "not team");
File: contracts/contracts/VeloGovernor.sol #63 46: require(numerator <= MAX_PROPOSAL_NUMERATOR, "numerator too high");
File: contracts/contracts/Bribe.sol #64 31: require(gauge == address(0), "gauge already set");
File: contracts/contracts/Bribe.sol #65 44: require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
File: contracts/contracts/Bribe.sol #66 69: require(rewards.length < MAX_REWARD_TOKENS, "too many rewards tokens");
File: contracts/contracts/factories/PairFactory.sol #67 56: require(msg.sender == feeManager, 'not fee manager');
File: contracts/contracts/factories/PairFactory.sol #68 61: require(msg.sender == pendingFeeManager, 'not pending fee manager');
File: contracts/contracts/factories/PairFactory.sol #69 66: require(msg.sender == feeManager, 'not fee manager');
File: contracts/contracts/factories/PairFactory.sol #70 67: require(_fee <= MAX_FEE, 'fee too high');
File: contracts/contracts/factories/PairFactory.sol #71 68: require(_fee != 0, 'fee must be nonzero');
File: contracts/contracts/Pair.sol #72 303: require(liquidity > 0, 'ILM'); // Pair: INSUFFICIENT_LIQUIDITY_MINTED
File: contracts/contracts/Pair.sol #73 322: require(amount0 > 0 && amount1 > 0, 'ILB'); // Pair: INSUFFICIENT_LIQUIDITY_BURNED
File: contracts/contracts/Pair.sol #74 338: require(amount0Out < _reserve0 && amount1Out < _reserve1, 'IL'); // Pair: INSUFFICIENT_LIQUIDITY
File: contracts/contracts/Pair.sol #75 344: require(to != _token0 && to != _token1, 'IT'); // Pair: INVALID_TO
File: contracts/contracts/Pair.sol #76 361: require(_k(_balance0, _balance1) >= _k(_reserve0, _reserve1), 'K'); // Pair: K
File: contracts/contracts/Pair.sol #77 467: require(deadline >= block.timestamp, 'Pair: EXPIRED');
File: contracts/contracts/Pair.sol #78 485: require(recoveredAddress != address(0) && recoveredAddress == owner, 'Pair: INVALID_SIGNATURE');
File: contracts/contracts/Voter.sol #79 190: require(gauges[_pool] == address(0x0), "exists");
File: contracts/contracts/Voter.sol #80 192: require(IPairFactory(factory).isPair(_pool), "!_pool");
File: contracts/contracts/Voter.sol #81 194: require(isWhitelisted[tokenA] && isWhitelisted[tokenB], "!whitelisted");
File: contracts/contracts/Voter.sol #82 211: require(msg.sender == emergencyCouncil, "not emergency council");
File: contracts/contracts/Voter.sol #83 212: require(isAlive[_gauge], "gauge already dead");
File: contracts/contracts/Voter.sol #84 218: require(msg.sender == emergencyCouncil, "not emergency council");
File: contracts/contracts/Voter.sol #85 219: require(!isAlive[_gauge], "gauge already alive");
File: contracts/contracts/Voter.sol #86 318: require((dayCalc < BRIBE_LAG) || (dayCalc > (DURATION + BRIBE_LAG)), "cannot claim during votes period");
#0 - liveactionllama
2022-05-31T17:18:50Z
Warden created this issue as a placeholder, because their submission was too large for the contest form. They then emailed their md file to our team on 05/29/2022 at 11:37 UTC. I've updated this issue with their md file content.
#1 - GalloDaSballo
2022-06-30T00:17:29Z
The warden could have sent the 5 immutable variables findings and could have probably won.
Just the 5 immutable variables would save 2100 * 5 = 10500 gas for end users by saving at least on SLOAD
Will rate the rest of the savings after reviewing other reports
#2 - GalloDaSballo
2022-07-04T00:18:11Z
Interesting find that would benefit from further details, especially if some of the Structs could be packed for massive gas savings.
I believe some of these are valuable, especially the more obvious address => uint mappings for which we can save 42 gas from computing storage
17 entries - 4 (that would still be there) 13 * 42 = 546
Massive savings, in lack of further details, will give 1 SLOAD per variable 2100 * 5 = 10500
Finding is valid and may save more gas for multi-sig operations, however the lock
is not used in conjunction with that slot so it won't save that much gas beside the first write
Finding is valid and estimate looks right 15 * 60 = 900
First read will save 94 gas, second onward will save 97 Giving 94 each for simplicity 39 * 94 = 3666
These seem to be memory variables for which the finding will not apply
Does work when not using unchecked, saves 13 gas per instance 21 * 13 = 273
Valid, I'd value at 16 gas (2 JUMP) in lack of POC 25 * 16 = 400
Saves 3 gas per instance 17 * 3 = 51
Valuing at 25 per instance 41 * 25 = 1025
6 gas per instance 14 * 6 = 84
Saves 30 gas
Pretty good idea for some of the lines, although some are meant to be set only once, hard to quantify in lack of POC as these would mostly be savings for one call
Not anymore (after 0.8.12)
Saves 3 gas per instance 68 * 3 = 204
Already counted above
Valid, saves 3 gas 17 * 3 = 51
Cannot quantity in lack of a Coded Alternative
Disagree per the documentation of LZ
Saves 2 gas + 20 for unchecked 9 * 22 = 198
3 gas
Have ommited 20, 21 because they save gas at deploy time and 24 because I can't quantify in lack of a coded poc
Very thorough report, that I think missed 1 immutable variable, overall great, however some findings would benefit by not pasting the same "mistake" 30+ times
Total Gas Saved 17931
#3 - GalloDaSballo
2022-07-05T00:22:59Z
Winning Report, well played!