Platform: Code4rena
Start Date: 01/07/2022
Pot Size: $75,000 USDC
Total HM: 17
Participants: 105
Period: 7 days
Judge: Jack the Pug
Total Solo HM: 5
Id: 143
League: ETH
Rank: 21/105
Findings: 5
Award: $662.30
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: 0xNineDec
Also found by: 0x1f8b, 0x29A, 0x52, 0xDjango, 0xdanial, 0xf15ers, Cheeezzyyyy, Chom, Franfran, GalloDaSballo, Green, IllIllI, Meera, Ruhum, bardamu, cccz, codexploder, defsec, hake, hansfriese, horsefacts, hubble, hyh, jonatascm, kebabsec, oyc_109, pashov, rbserver, simon135, tabish, tintin, zzzitron
14.8726 USDC - $14.87
latestRoundData()
is used to fetch the asset price from a Chainlink aggregator, but it's missing additional validations to ensure that the round is complete. If there is a problem with Chainlink starting a new round and finding consensus on the new value for the oracle (e.g. Chainlink nodes abandon the oracle, chain congestion, vulnerability/attacks on the Chainlink system) consumers of this contract may continue using outdated stale data / stale prices.
JBChainlinkV3PriceFeed.sol:44: (, int256 _price, , , ) = feed.latestRoundData();
Consider adding missing checks for stale data.
As an example:
function currentPrice(uint256 _decimals) external view override returns (uint256) { // Get the latest round information. Only need the price is needed. - (, int256 _price, , , ) = feed.latestRoundData(); + (uint80 roundID, int256 _price, , uint256 updatedAt, uint80 answeredInRound) = feed.latestRoundData(); //@audit The updatedAt data feed property is the timestamp of an answered round and answeredInRound is the round it was updated in + require(updatedAt > 0, "Price data is stale"); //@audit A timestamp with zero value means the round is not complete and should not be used. + require(_price > 0, "Price data not valid"); + require(answeredInRound >= roundID, "Price data is stale"); //@audit If answeredInRound is less than roundID, the _price is being carried over. If answeredInRound is equal to roundID, then the _price is fresh. // Get a reference to the number of decimals the feed uses. uint256 _feedDecimals = feed.decimals(); // Return the price, adjusted to the target decimals. return uint256(_price).adjustDecimals(_feedDecimals, _decimals); }
#0 - mejango
2022-07-12T18:48:09Z
dup #138
๐ Selected for report: horsefacts
Also found by: 0x1f8b, 0x29A, 0x52, 0xf15ers, AlleyCat, Ch_301, Chom, Franfran, IllIllI, Kaiziron, Limbooo, Meera, Ruhum, Sm4rty, apostle0x01, berndartmueller, cccz, cloudjunky, codexploder, cryptphi, delfin454000, durianSausage, fatherOfBlocks, hake, hansfriese, hyh, jonatascm, m_Rassska, oyc_109, peritoflores, rajatbeladiya, rbserver, svskaushik, zzzitron
3.4075 USDC - $3.41
https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBERC20PaymentTerminal.sol#L98-L100 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L518 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L1042 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L1107 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L1226
DOS
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 approve()
function will revert if the current approval is not zero, to protect against front-running changes of approvals.
As USDT is an extremely popular ERC20 token, it's quite probable that it'll be used in the JBERC20PaymentTerminal
, which implements the JBPayoutRedemptionPaymentTerminal
abstract contract and overrides the _beforeTransferTo()
function as such:
File: JBERC20PaymentTerminal.sol 19: contract JBERC20PaymentTerminal is JBPayoutRedemptionPaymentTerminal { ... 098: function _beforeTransferTo(address _to, uint256 _amount) internal override { 099: IERC20(token).approve(_to, _amount); 100: }
In the abstract contract JBPayoutRedemptionPaymentTerminal
, calls to _beforeTransferTo()
are always protected against the 0 value, meaning it won't ever be possible to approve another amount after the initial one. As _beforeTransferTo()
is a hook in several functions (migrate()
, _distributeToPayoutSplitsOf()
, _processFee()
), these functions would just revert with USDT or similar tokens.
Consider approving 0 first to avoid this type of DOS:
File: JBERC20PaymentTerminal.sol 098: function _beforeTransferTo(address _to, uint256 _amount) internal override { + 099: IERC20(token).approve(_to, 0); 099: IERC20(token).approve(_to, _amount); 100: }
#0 - mejango
2022-07-12T18:54:11Z
dup #101
https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L332-L349 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBERC20PaymentTerminal.sol#L81-L89 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L540-L555
As arbitrary ERC20 tokens can be used, the amount here should be calculated every time to take into consideration a possible fee-on-transfer or deflation. Also, it's a good practice for the future of the solution.
Affected code:
File: JBERC20PaymentTerminal.sol 81: function _transferFrom( 82: address _from, 83: address payable _to, 84: uint256 _amount 85: ) internal override { 86: _from == address(this) 87: ? IERC20(token).transfer(_to, _amount) //@audit low: result not checked 88: : IERC20(token).transferFrom(_from, _to, _amount); 89: }
File: JBPayoutRedemptionPaymentTerminal.sol 332: function pay( ... 348: // Transfer tokens to this terminal from the msg sender. 349: _transferFrom(msg.sender, payable(address(this)), _amount);//@audit FoT incompatible ... 355: _pay( 356: _amount, //@audit wrong amount 357: msg.sender, 358: _projectId, 359: _beneficiary, 360: _minReturnedTokens, 361: _preferClaimedTokens, 362: _memo, 363: _metadata 364: );
File: JBPayoutRedemptionPaymentTerminal.sol 540: function addToBalanceOf( ... 554: // Transfer tokens to this terminal from the msg sender. 555: _transferFrom(msg.sender, payable(address(this)), _amount);//@audit FoT incompatible ... 561: _addToBalanceOf(_projectId, _amount, !isFeelessAddress[msg.sender], _memo, _metadata); //@audit wrong amount
Use the balance before and after the transfer to calculate the received amount instead of assuming that it would be equal to the amount passed as a parameter. Or explicitly document that such tokens shouldn't be used and won't be supported
#0 - drgorillamd
2022-07-12T19:13:16Z
Duplicate of #304
๐ Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xdanial, 0xf15ers, Bnke0x0, Ch_301, Chandr, Chom, Funen, GimelSec, Hawkeye, JC, Kaiziron, Lambda, Meera, MiloTruck, Noah3o6, Picodes, ReyAdmirado, Rohan16, Sm4rty, TerrierLover, TomJ, Waze, _Adam, __141345__, asutorufos, aysha, berndartmueller, brgltd, cccz, codexploder, defsec, delfin454000, djxploit, durianSausage, fatherOfBlocks, hake, horsefacts, hubble, jayfromthe13th, joestakey, jonatascm, m_Rassska, oyc_109, pashov, rajatbeladiya, rbserver, robee, sach1r0, sahar, samruna, simon135, svskaushik, zzzitron
176.2104 USDC - $176.21
Overview
Risk Rating | Number of issues |
---|---|
Low Risk | 5 |
Non-Critical Risk | 3 |
Table of Contents
To avoid unexpected behavior in the future (be it for the solution or for a fork), it's recommended to always follow the CEI pattern.
Consider always moving the state-changes before the external calls.
While here, there's no real impact, it's still a bad practice (_safeMint
has a callback, and a state change is occurring just after the call, albeit under certain conditions and without any thinkable impact here):
File: JBProjects.sol 131: function createFor(address _owner, JBProjectMetadata calldata _metadata) 132: external 133: override 134: returns (uint256 projectId) 135: { 136: // Increment the count, which will be used as the ID. 137: projectId = ++count; 138: + 139: // Set the metadata if one was provided. + 140: if (bytes(_metadata.content).length > 0) + 141: metadataContentOf[projectId][_metadata.domain] = _metadata.content; 139: // Mint the project. 140: _safeMint(_owner, projectId); //@audit low: beware of _safeMint's callback 141: - 142: // Set the metadata if one was provided. - 143: if (bytes(_metadata.content).length > 0) - 144: metadataContentOf[projectId][_metadata.domain] = _metadata.content; //@audit low: CEI pattern not respected, but no real impact here, just bad practice. 145: 146: emit Create(projectId, _owner, _metadata, msg.sender); 147: }
Consider adding an address(0)
check for immutable variables:
028: address public immutable override token; ... 124: constructor( 125: address _token, 126: uint256 _decimals, 127: uint256 _currency 128: ) { + 129: require(_token != address(0)); 129: token = _token; 130: decimals = _decimals; 131: currency = _currency; 132: }
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 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. Use OpenZeppelinโs SafeERC20
's safeTransfer()
/safeTransferFrom()
instead or check the return value.
Bad:
IERC20(token).transferFrom(msg.sender, address(this), amount);
Good (using OpenZeppelin's SafeERC20
):
import {SafeERC20} from "openzeppelin/token/utils/SafeERC20.sol"; // ... IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
Good (using require
):
bool success = IERC20(token).transferFrom(msg.sender, address(this), amount); require(success, "ERC20 transfer failed");
Consider using safeTransfer
/safeTransferFrom
or wrap in a require
statement here:
File: JBERC20PaymentTerminal.sol 81: function _transferFrom( 82: address _from, 83: address payable _to, 84: uint256 _amount 85: ) internal override { 86: _from == address(this) 87: ? IERC20(token).transfer(_to, _amount) 88: : IERC20(token).transferFrom(_from, _to, _amount); 89: }
Transferring tokens to the zero address is usually prohibited to accidentally avoid "burning" tokens by sending them to an unrecoverable zero address.
Consider adding a check to prevent accidentally burning tokens here:
function _transferFrom( address _from, address payable _to, uint256 _amount ) internal override { _from == address(this) ? IERC20(token).transfer(_to, _amount) : IERC20(token).transferFrom(_from, _to, _amount); }
Contracts inheriting from OpenZeppelin's libraries have the default transferOwnership()
function (a one-step process). It's possible that the onlyOwner
role mistakenly transfers ownership to a wrong address, resulting in a loss of the onlyOwner
role.
Consider overriding the default transferOwnership()
function to first nominate an address as the pendingOwner
and implementing an acceptOwnership()
function which is called by the pendingOwner
to confirm the transfer.
File: JBToken.sol 207: function transferOwnership(uint256 _projectId, address _newOwner) 208: public 209: virtual 210: override 211: onlyOwner 212: { 213: _projectId; // Prevents unused var compiler and natspec complaints. 214: 215: return super.transferOwnership(_newOwner); 216: }
abstract/JBPayoutRedemptionPaymentTerminal.sol:837: // If the fee is zero or if the fee is being used by an address that doesn't incur fees, set the discount to 100% for convinience. abstract/JBPayoutRedemptionPaymentTerminal.sol:948: // If the fee is zero or if the fee is being used by an address that doesn't incur fees, set the discount to 100% for convinience.
abstract/JBPayoutRedemptionPaymentTerminal.sol:1077: // Add to balance if prefered. abstract/JBPayoutRedemptionPaymentTerminal.sol:1116: // Add to balance if prefered.
abstract/JBPayoutRedemptionPaymentTerminal.sol:1470: // If the guage reverts, set the discount to 0.
libraries/JBFundingCycleMetadataResolver.sol:120: // global metadta in bits 8-23 (16 bits).
JBSingleTokenPaymentTerminalStore.sol:384: // The weight is always a fixed point mumber with 18 decimals. To ensure this, the ratio should use the same number of decimals as the `_amount`.
JBSingleTokenPaymentTerminalStore.sol:452: // Get areference to the terminal's currency.
JBSplitsStore.sol:218: // Allow lock extention.
1000000000
should be changed to 1e9
for readability reasonslibraries/JBConstants.sol:9: uint256 public constant MAX_RESERVED_RATE = 10000; libraries/JBConstants.sol:10: uint256 public constant MAX_REDEMPTION_RATE = 10000; libraries/JBConstants.sol:11: uint256 public constant MAX_DISCOUNT_RATE = 1000000000; libraries/JBConstants.sol:12: uint256 public constant SPLITS_TOTAL_PERCENT = 1000000000; libraries/JBConstants.sol:13: uint256 public constant MAX_FEE = 1000000000; libraries/JBConstants.sol:14: uint256 public constant MAX_FEE_DISCOUNT = 1000000000;
bytes.concat()
Solidity version 0.8.4 introduces bytes.concat()
(vs abi.encodePacked(<bytes>,<bytes>)
)
abstract/JBPayoutRedemptionPaymentTerminal.sol:2:pragma solidity 0.8.6; abstract/JBPayoutRedemptionPaymentTerminal.sol:1075: _projectMetadata = bytes(abi.encodePacked(_projectId)); abstract/JBPayoutRedemptionPaymentTerminal.sol:1114: _projectMetadata = bytes(abi.encodePacked(_projectId));
๐ Selected for report: 0xA5DF
Also found by: 0v3rf10w, 0x09GTO, 0x1f8b, 0x29A, 0xDjango, 0xKitsune, 0xNazgul, 0xdanial, 0xf15ers, Aymen0909, Bnke0x0, Ch_301, Cheeezzyyyy, Chom, ElKu, Funen, Hawkeye, IllIllI, JC, JohnSmith, Kaiziron, Lambda, Limbooo, Meera, Metatron, MiloTruck, Noah3o6, Picodes, Randyyy, RedOneN, ReyAdmirado, Rohan16, Saintcode_, Sm4rty, TomJ, Tomio, Tutturu, UnusualTurtle, Waze, _Adam, __141345__, ajtra, apostle0x01, asutorufos, brgltd, c3phas, cRat1st0s, codexploder, defsec, delfin454000, djxploit, durianSausage, exd0tpy, fatherOfBlocks, hake, horsefacts, ignacio, jayfromthe13th, joestakey, jonatascm, kaden, kebabsec, m_Rassska, mektigboy, mrpathfindr, oyc_109, rajatbeladiya, rbserver, rfa, robee, sach1r0, sashik_eth, simon135
45.8024 USDC - $45.80
Overview
Risk Rating | Number of issues |
---|---|
Gas Issues | 9 |
Table of Contents:
<array>.length
should not be looked up in every loop of a for-loop
++i
costs less gas compared to i++
or i += 1
(same for --i
vs i--
or i -= 1
)payable
Caching a mapping's value in a local storage
or calldata
variable when the value is accessed multiple times saves ~42 gas per access due to not having to perform the same offset calculation every time.
Affected code:
File: JBOperatorStore.sol 134: function setOperators(JBOperatorData[] calldata _operatorData) external override { 135: for (uint256 _i = 0; _i < _operatorData.length; _i++) { 136: // Pack the indexes into a uint256. + 136: JBOperatorData calldata _operatorDataI = _operatorData[_i]; - 137: uint256 _packed = _packedPermissions(_operatorData[_i].permissionIndexes); + 137: uint256 _packed = _packedPermissions(_operatorDataI.permissionIndexes); 138: 139: // Store the new value. - 140: permissionsOf[_operatorData[_i].operator][msg.sender][_operatorData[_i].domain] = _packed; + 140: permissionsOf[_operatorDataI.operator][msg.sender][_operatorDataI.domain] = _packed; 141: 142: emit SetOperator( - 143: _operatorData[_i].operator, + 143: _operatorDataI.operator, 144: msg.sender, - 145: _operatorData[_i].domain, + 145: _operatorDataI.domain, - 146: _operatorData[_i].permissionIndexes, + 146: _operatorDataI.permissionIndexes, 147: _packed 148: ); 149: } 150: }
JBTokenStore.sol:203: token = new JBToken(_name, _symbol);
There's a way to save a significant amount of gas on deployment using Clones: https://www.youtube.com/watch?v=3Mw-pMmJ7TA .
This is a solution that was adopted, as an example, by Porter Finance. They realized that deploying using clones was 10x cheaper:
Keep in mind however, that the gas saving only concerns the deployment cost. Indeed, by using this pattern, the cost to use the deployed contract increases a bit.
Depending on the sponsor's choice (saving a bit of gas for the end-users or a lot of gas for the deployer), this pattern might be considered.
Checking non-zero transfer values can avoid an expensive external call and save gas.
Consider adding a non-zero-value check here:
File: JBERC20PaymentTerminal.sol 81: function _transferFrom( 82: address _from, 83: address payable _to, 84: uint256 _amount 85: ) internal override { 86: _from == address(this) 87: ? IERC20(token).transfer(_to, _amount) 88: : IERC20(token).transferFrom(_from, _to, _amount); 89: }
<array>.length
should not be looked up in every loop of a for-loop
Reading array length at each iteration of the loop consumes more gas than necessary.
In the best case scenario (length read on a memory variable), caching the array length in the stack saves around 3 gas per iteration. In the worst case scenario (external calls at each iteration), the amount of gas wasted can be massive.
Here, consider storing the array's length in a variable before the for-loop, and use this new variable instead:
abstract/JBPayoutRedemptionPaymentTerminal.sol:1008: for (uint256 _i = 0; _i < _splits.length; ) { JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.length; _i++) { JBDirectory.sol:139: for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) { JBDirectory.sol:167: for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) JBDirectory.sol:275: for (uint256 _i; _i < _terminals.length; _i++) JBDirectory.sol:276: for (uint256 _j = _i + 1; _j < _terminals.length; _j++) JBOperatorStore.sol:85: for (uint256 _i = 0; _i < _permissionIndexes.length; _i++) { JBOperatorStore.sol:135: for (uint256 _i = 0; _i < _operatorData.length; _i++) { JBOperatorStore.sol:165: for (uint256 _i = 0; _i < _indexes.length; _i++) { JBSingleTokenPaymentTerminalStore.sol:862: for (uint256 _i = 0; _i < _terminals.length; _i++) JBSplitsStore.sol:204: for (uint256 _i = 0; _i < _currentSplits.length; _i++) { JBSplitsStore.sol:211: for (uint256 _j = 0; _j < _splits.length; _j++) { JBSplitsStore.sol:229: for (uint256 _i = 0; _i < _splits.length; _i++) {
++i
costs less gas compared to i++
or i += 1
(same for --i
vs i--
or i -= 1
)Pre-increments and pre-decrements are cheaper.
For a uint256 i
variable, the following is true with the Optimizer enabled at 10k:
Increment:
i += 1
is the most expensive formi++
costs 6 gas less than i += 1
++i
costs 5 gas less than i++
(11 gas less than i += 1
)Decrement:
i -= 1
is the most expensive formi--
costs 11 gas less than i -= 1
--i
costs 5 gas less than i--
(16 gas less than i -= 1
)Note that post-increments (or post-decrements) return the old value before incrementing or decrementing, hence the name post-increment:
uint i = 1; uint j = 2; require(j == i++, "This will be false as i is incremented after the comparison");
However, pre-increments (or pre-decrements) return the new value:
uint i = 1; uint j = 2; require(j == ++i, "This will be true as i is incremented before the comparison");
In the pre-increment case, the compiler has to create a temporary variable (when used) for returning 1
instead of 2
.
Affected code:
JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.length; _i++) { JBDirectory.sol:139: for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) { JBDirectory.sol:167: for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) JBDirectory.sol:275: for (uint256 _i; _i < _terminals.length; _i++) JBDirectory.sol:276: for (uint256 _j = _i + 1; _j < _terminals.length; _j++) JBFundingCycleStore.sol:724: for (uint256 i = 0; i < _discountMultiple; i++) { JBOperatorStore.sol:85: for (uint256 _i = 0; _i < _permissionIndexes.length; _i++) { JBOperatorStore.sol:135: for (uint256 _i = 0; _i < _operatorData.length; _i++) { JBOperatorStore.sol:165: for (uint256 _i = 0; _i < _indexes.length; _i++) { JBSingleTokenPaymentTerminalStore.sol:862: for (uint256 _i = 0; _i < _terminals.length; _i++) JBSplitsStore.sol:204: for (uint256 _i = 0; _i < _currentSplits.length; _i++) { JBSplitsStore.sol:211: for (uint256 _j = 0; _j < _splits.length; _j++) { JBSplitsStore.sol:229: for (uint256 _i = 0; _i < _splits.length; _i++) { JBSplitsStore.sol:304: for (uint256 _i = 0; _i < _splitCount; _i++) {
Consider using pre-increments and pre-decrements where they are relevant (meaning: not where post-increments/decrements logic are relevant).
In Solidity 0.8+, there's a default overflow check on unsigned integers. It's possible to uncheck this in for-loops and save some gas at each iteration, but at the cost of some code readability, as this uncheck cannot be made inline.
Consider wrapping with an unchecked
block here (around 25 gas saved per instance):
JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.length; _i++) { JBDirectory.sol:139: for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) { JBDirectory.sol:167: for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) JBDirectory.sol:275: for (uint256 _i; _i < _terminals.length; _i++) JBDirectory.sol:276: for (uint256 _j = _i + 1; _j < _terminals.length; _j++) JBFundingCycleStore.sol:724: for (uint256 i = 0; i < _discountMultiple; i++) { JBOperatorStore.sol:85: for (uint256 _i = 0; _i < _permissionIndexes.length; _i++) { JBOperatorStore.sol:135: for (uint256 _i = 0; _i < _operatorData.length; _i++) { JBOperatorStore.sol:165: for (uint256 _i = 0; _i < _indexes.length; _i++) { JBSingleTokenPaymentTerminalStore.sol:862: for (uint256 _i = 0; _i < _terminals.length; _i++) JBSplitsStore.sol:204: for (uint256 _i = 0; _i < _currentSplits.length; _i++) { JBSplitsStore.sol:211: for (uint256 _j = 0; _j < _splits.length; _j++) { JBSplitsStore.sol:229: for (uint256 _i = 0; _i < _splits.length; _i++) { JBSplitsStore.sol:304: for (uint256 _i = 0; _i < _splitCount; _i++) {
The change would be:
- for (uint256 i; i < numIterations; i++) { + for (uint256 i; i < numIterations;) { // ... + unchecked { ++i; } }
The risk of overflow is non-existant for uint256
here.
If the optimizer is enabled, this finding isn't true anymore
If a variable is not set/initialized, it is assumed to have the default value (0
for uint
, false
for bool
, address(0)
for address...). Explicitly initializing it with its default value is an anti-pattern and wastes gas (around 3 gas per instance).
As an example: for (uint256 i = 0; i < numIterations; ++i) {
should be replaced with for (uint256 i; i < numIterations; ++i) {
Affected code:
abstract/JBPayoutRedemptionPaymentTerminal.sol:594: for (uint256 _i = 0; _i < _heldFeeLength; ) { abstract/JBPayoutRedemptionPaymentTerminal.sol:1008: for (uint256 _i = 0; _i < _splits.length; ) { abstract/JBPayoutRedemptionPaymentTerminal.sol:1396: for (uint256 _i = 0; _i < _heldFeesLength; ) { JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { JBFundingCycleStore.sol:724: for (uint256 i = 0; i < _discountMultiple; i++) { JBOperatorStore.sol:85: for (uint256 _i = 0; _i < _permissionIndexes.length; _i++) { JBOperatorStore.sol:135: for (uint256 _i = 0; _i < _operatorData.length; _i++) { JBOperatorStore.sol:165: for (uint256 _i = 0; _i < _indexes.length; _i++) { JBProjects.sol:40: uint256 public override count = 0; JBSingleTokenPaymentTerminalStore.sol:862: for (uint256 _i = 0; _i < _terminals.length; _i++) JBSplitsStore.sol:165: for (uint256 _i = 0; _i < _groupedSplitsLength; ) { JBSplitsStore.sol:204: for (uint256 _i = 0; _i < _currentSplits.length; _i++) { JBSplitsStore.sol:209: bool _includesLocked = false; JBSplitsStore.sol:211: for (uint256 _j = 0; _j < _splits.length; _j++) { JBSplitsStore.sol:227: uint256 _percentTotal = 0; JBSplitsStore.sol:229: for (uint256 _i = 0; _i < _splits.length; _i++) { JBSplitsStore.sol:304: for (uint256 _i = 0; _i < _splitCount; _i++) {
Consider removing explicit initializations for default values.
Using newer compiler versions and the optimizer give gas optimizations. Also, additional safety checks are available for free.
The advantages here are:
Consider upgrading here :
abstract/JBControllerUtility.sol:2:pragma solidity 0.8.6; abstract/JBOperatable.sol:2:pragma solidity 0.8.6; abstract/JBPayoutRedemptionPaymentTerminal.sol:2:pragma solidity 0.8.6; abstract/JBSingleTokenPaymentTerminal.sol:2:pragma solidity 0.8.6; JBChainlinkV3PriceFeed.sol:2:pragma solidity 0.8.6; JBController.sol:2:pragma solidity 0.8.6; JBDirectory.sol:2:pragma solidity 0.8.6; JBERC20PaymentTerminal.sol:2:pragma solidity 0.8.6; JBETHPaymentTerminal.sol:2:pragma solidity 0.8.6; JBFundingCycleStore.sol:2:pragma solidity 0.8.6; JBOperatorStore.sol:2:pragma solidity 0.8.6; JBPrices.sol:2:pragma solidity 0.8.6; JBProjects.sol:2:pragma solidity 0.8.6; JBReconfigurationBufferBallot.sol:2:pragma solidity 0.8.6; JBSingleTokenPaymentTerminalStore.sol:2:pragma solidity 0.8.6; JBSplitsStore.sol:2:pragma solidity 0.8.6; JBToken.sol:2:pragma solidity 0.8.6; JBTokenStore.sol:2:pragma solidity 0.8.6;
payable
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
abstract/JBControllerUtility.sol:8: Provides tools for contracts with functionality that can only be accessed by a project's controller. abstract/JBPayoutRedemptionPaymentTerminal.sol:622: function setFee(uint256 _fee) external virtual override onlyOwner { abstract/JBPayoutRedemptionPaymentTerminal.sol:644: function setFeeGauge(IJBFeeGauge _feeGauge) external virtual override onlyOwner { abstract/JBPayoutRedemptionPaymentTerminal.sol:661: function setFeelessAddress(address _address, bool _flag) external virtual override onlyOwner { JBController.sol:28: JBOperatable: Several functions in this contract can only be accessed by a project owner, or an address that has been preconfifigured to be an operator of the project. JBProjects.sol:179: function setTokenUriResolver(IJBTokenUriResolver _newResolver) external override onlyOwner {