Juicebox V2 contest - delfin454000's results

The decentralized fundraising and treasury protocol.

General Information

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

Juicebox

Findings Distribution

Researcher Performance

Rank: 44/105

Findings: 3

Award: $131.86

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

3.4075 USDC - $3.41

Labels

bug
duplicate
2 (Med Risk)
old-submission-method
valid

External Links

Lines of code

JBERC20PaymentTerminal.sol: L73-89

Vulnerability details

Impact

Transfers that do not check the return value of the transfer and do not throw an exception when the sender has insufficient funds are potentially unsafe. Silent failures of such transfers are possible, which could affect contract token accounting. There is also the possibility that an attacker could make a transfer without having adequate token funds and the resulting transaction could pass the existing verification checks.

Proof of Concept

There are two instances of this potential problem in the Juicebox V2 in scope contracts, as shown below. transfer function is used in L87 and transferFrom is used in L88:

JBERC20PaymentTerminal.sol: L73-89

  /** 
    @notice
    Transfers tokens.

    @param _from The address from which the transfer should originate.
    @param _to The address to which the transfer should go.
    @param _amount The amount of the transfer, as a fixed point number with the same number of decimals as this terminal.
  */
  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);
  }

Tools Used

Manual analysis

Use safeTransfer/safeTransferFrom instead of transfer/transferFrom or otherwise check the return value of token transfers

#0 - mejango

2022-07-12T18:38:43Z

dup of #281

Duplicate comment

Identical comments occur in lines 154 and 157, as shown below:

JBFundingCycleStore.sol: L152-158

      if (_isApproved(_projectId, fundingCycle)) return fundingCycle;

      // Resolve the funding cycle for the latest configured funding cycle.
      fundingCycle = _getStructFor(_projectId, fundingCycle.basedOn);
    } else {
      // Resolve the funding cycle for the latest configured funding cycle.
      fundingCycle = _getStructFor(_projectId, latestConfigurationOf[_projectId]);

Recommendation: Correct or remove the first comment


Typos

JBFundingCycleStore.sol: L47

    _projectId The ID of the project to get instrinsic properties of.

Change instrinsic to intrinsic


JBFundingCycleStore.sol: L234

    // If there is not funding cycle to base the current one on, there can't be a current one.

Change not to no


JBFundingCycleStore.sol: L343

    // Efficiently stores a funding cycles provided user defined properties.

Change a funding cycles provided to funding cycles based on or otherwise clarify comment


JBFundingCycleStore.sol: L495

    Efficiently stores a funding cycle's provided intrinsic properties.

Change a funding cycle's provided to funding cycles based on or otherwise clarify comment


The same typo (adherance) occurs in all four lines referenced below:

JBProjects.sol: L84

JBController.sol: L29

JBController.sol: L341

JBPayoutRedemptionPaymentTerminal.sol: L247

Example (JBProjects.sol: L84):

    @param _interfaceId The ID of the interface to check for adherance to.

Change adherance to adherence in each case


JBSplitsStore.sol: L218

          // Allow lock extention.

Change extention to extension


The same typo (percents) occurs in all three lines referenced below:

JBSplitsStore.sol: L226

JBSplitsStore.sol: L236

JBController.sol: L898

Example (JBSplitsStore.sol: L226):

    // Add up all the percents to make sure they cumulative are under 100%.

Change percents to percentages in each case


The same typo (percent) occurs in all four lines referenced below:

JBSplitsStore.sol: L230

JBSplitsStore.sol: L248

JBSplitsStore.sol: L315

JBPayoutRedemptionPaymentTerminal.sol: L162

Example (JBSplitsStore.sol: L230):

      // The percent should be greater than 0.

Change percent to percentage in each case


JBSplitsStore.sol: L226

    // Add up all the percents to make sure they cumulative are under 100%.

Change cumulative are to they total to


JBSplitsStore.sol: L305

      // Get a reference to the fist packed data.

Change fist to first


JBDirectory.sol: L206

    - or, an allowedlisted address is setting a controller for a project that doesn't already have a controller.

Change allowedlisted to allowlisted


The same typo (repeated word the) occurs in both lines referenced below:

JBPayoutRedemptionPaymentTerminal.sol: L327

JBPayoutRedemptionPaymentTerminal.sol: L1255

    @param _memo A memo to pass along to the emitted event, and passed along the the funding cycle's data source and delegate.  A data source can alter the memo before emitting in the event and forwarding to the delegate.

Remove repeated word the in both cases


The same typo ('incure`) occurs in both lines referenced below:

JBPayoutRedemptionPaymentTerminal.sol: L426

JBPayoutRedemptionPaymentTerminal.sol: L799

    All funds distributed outside of this contract or any feeless terminals incure the protocol fee.

Change incure to incur in each case


JBPayoutRedemptionPaymentTerminal.sol: L571

    @param _projectId The ID of the project whos held fees should be processed.

Change whos held fees to with held fees that


The same typo (convinience) occurs in both lines referenced below:

JBPayoutRedemptionPaymentTerminal.sol: L837

JBPayoutRedemptionPaymentTerminal.sol: L948

      // 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.

Change convinience to convenience in each case


JBPayoutRedemptionPaymentTerminal.sol: L1019

      // The payout amount substracting any applicable incurred fees.

Change substracting to subtracting


The same typo (prefered) occurs in both lines referenced below:

JBPayoutRedemptionPaymentTerminal.sol: L1077

JBPayoutRedemptionPaymentTerminal.sol: L1116

            // Add to balance if prefered.

Change prefered to preferred in each case


JBPayoutRedemptionPaymentTerminal.sol: L1470

      // If the guage reverts, set the discount to 0.

Change guage to gauge


JBSingleTokenPaymentTerminalStore.sol: L207

    // Use the project's total overflow across all of its terminals if the flag species specifies so. Otherwise, use the overflow local to the specified terminal.

Remove the word species


The same typo (that) occurs in all three lines referenced below:

JBSingleTokenPaymentTerminalStore.sol: L219

JBSingleTokenPaymentTerminalStore.sol: L256

JBSingleTokenPaymentTerminalStore.sol: L472

    // Can't redeem more tokens that is in the supply.

Change that to than in each case


JBSingleTokenPaymentTerminalStore.sol: L384

    // 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`.

Change mumber to number


JBSingleTokenPaymentTerminalStore.sol: L452

        // Get areference to the terminal's currency.

Change areference to a reference


Jinterfaces/IJBSplitAllocator.sol: L27

    Critical business logic should be protected by an appropriate access control. The token and/or eth are optimistically transfered

Change transfered to transferred


Variables should not be initialized to their default values

For example, initializing uint variables to their default value of 0 is unnecessary and costs gas


JBProjects.sol: L40

  uint256 public override count = 0;

Change to uint256 public override count;


JBSplitsStore.sol: L209

      bool _includesLocked = false;

Change to bool _includesLocked;


JBSplitsStore.sol: L227

    uint256 _percentTotal = 0;

Change to uint256 _percentTotal;


The for loop counter (i, _i or _j) is initialized unnecessarily to zero in the 14 loops referenced below:

JBFundingCycleStore.sol: L724-734

JBSplitsStore.sol: L165-175

JBSplitsStore.sol: L204-224

JBSplitsStore.sol: L211-221

JBSplitsStore.sol: L229-280

JBSplitsStore.sol: L304-331

JBOperatorStore.sol: L85-92

JBOperatorStore.sol: L135-149

JBOperatorStore.sol: L165-172

JBController.sol: L913-967

JBPayoutRedemptionPaymentTerminal.sol: L594-610

JBPayoutRedemptionPaymentTerminal.sol: L1008-1173

JBPayoutRedemptionPaymentTerminal.sol: L1396-1424

JBSingleTokenPaymentTerminalStore.sol: L862-875

Example (JBSplitsStore.sol: L211-221):

      for (uint256 _j = 0; _j < _splits.length; _j++) {
        // Check for sameness.
        if (
          _splits[_j].percent == _currentSplits[_i].percent &&
          _splits[_j].beneficiary == _currentSplits[_i].beneficiary &&
          _splits[_j].allocator == _currentSplits[_i].allocator &&
          _splits[_j].projectId == _currentSplits[_i].projectId &&
          // Allow lock extention.
          _splits[_j].lockedUntil >= _currentSplits[_i].lockedUntil
        ) _includesLocked = true;
      }

Change uint256 _j = 0; to uint256 _j;


Array length should not be looked up in every iteration of a for loop

Since calculating the array length costs gas, it's best to read the length of the array from memory before executing the loop


The array length is looked up in every iteration of the nine for loops referenced below:

JBSplitsStore.sol: L204-224

JBSplitsStore.sol: L211-221

JBSplitsStore.sol: L229-280

JBOperatorStore.sol: L85-92

JBOperatorStore.sol: L135-149

JBOperatorStore.sol: L165-172

JBController.sol: L913-967

JBPayoutRedemptionPaymentTerminal.sol: L1008-1173

JBSingleTokenPaymentTerminalStore.sol: L862-875

Example (JBSplitsStore.sol: L211-221):

      for (uint256 _j = 0; _j < _splits.length; _j++) {
        // Check for sameness.
        if (
          _splits[_j].percent == _currentSplits[_i].percent &&
          _splits[_j].beneficiary == _currentSplits[_i].beneficiary &&
          _splits[_j].allocator == _currentSplits[_i].allocator &&
          _splits[_j].projectId == _currentSplits[_i].projectId &&
          // Allow lock extention.
          _splits[_j].lockedUntil >= _currentSplits[_i].lockedUntil
        ) _includesLocked = true;
      }

Suggestion:

      uint256 totalSplitsLength = _splits.length; 
      for (uint256 _j = 0; _j < totalSplitsLength; _j++) {
        // Check for sameness.
        if (
          _splits[_j].percent == _currentSplits[_i].percent &&
          _splits[_j].beneficiary == _currentSplits[_i].beneficiary &&
          _splits[_j].allocator == _currentSplits[_i].allocator &&
          _splits[_j].projectId == _currentSplits[_i].projectId &&
          // Allow lock extention.
          _splits[_j].lockedUntil >= _currentSplits[_i].lockedUntil
        ) _includesLocked = true;
      }

Use ++i instead of i++ to increase count in a for loop

Since use of i++ (or j++ or equivalent counter) cost more gas, it is better to use ++i/++j


i++, _i++ or _j++ is used in the 10 for loops referenced below:

JBFundingCycleStore.sol: L724-734

JBSplitsStore.sol: L204-224

JBSplitsStore.sol: L211-221

JBSplitsStore.sol: L229-280

JBSplitsStore.sol: L304-331

JBOperatorStore.sol: L85-92

JBOperatorStore.sol: L135-149

JBOperatorStore.sol: L165-172

JBController.sol: L913-967

JBSingleTokenPaymentTerminalStore.sol: L862-875

Example (JBSplitsStore.sol: L211-221):

      for (uint256 _j = 0; _j < _splits.length; _j++) {
        // Check for sameness.
        if (
          _splits[_j].percent == _currentSplits[_i].percent &&
          _splits[_j].beneficiary == _currentSplits[_i].beneficiary &&
          _splits[_j].allocator == _currentSplits[_i].allocator &&
          _splits[_j].projectId == _currentSplits[_i].projectId &&
          // Allow lock extention.
          _splits[_j].lockedUntil >= _currentSplits[_i].lockedUntil
        ) _includesLocked = true;
      }

Suggestion:

      for (uint256 _j = 0; _j < _splits.length; ++_j) {
        // Check for sameness.
        if (
          _splits[_j].percent == _currentSplits[_i].percent &&
          _splits[_j].beneficiary == _currentSplits[_i].beneficiary &&
          _splits[_j].allocator == _currentSplits[_i].allocator &&
          _splits[_j].projectId == _currentSplits[_i].projectId &&
          // Allow lock extention.
          _splits[_j].lockedUntil >= _currentSplits[_i].lockedUntil
        ) _includesLocked = true;
      }

Avoid use of default "checked" behavior in a for loop

Underflow/overflow checks are made every time i++ (or ++i or equivalent counter) is called. Such checks are unnecessary since i is already limited. Therefore, use unchecked{++i}/unchecked{i++} instead


Default checked behavior is used in the six for loops referenced below:

JBFundingCycleStore.sol: L724-734

JBOperatorStore.sol: L85-92

JBOperatorStore.sol: L135-149

JBOperatorStore.sol: L165-172

JBController.sol: L913-967

JBSingleTokenPaymentTerminalStore.sol: L862-875

Example (JBOperatorStore.sol: L165-172):

    for (uint256 _i = 0; _i < _indexes.length; _i++) {
      uint256 _index = _indexes[_i];

      if (_index > 255) revert PERMISSION_INDEX_OUT_OF_BOUNDS();

      // Turn the bit at the index on.
      packed |= 1 << _index;
    }

Suggestion:

    for (uint256 _i = 0; _i < _indexes.length;) {
      uint256 _index = _indexes[_i];

      if (_index > 255) revert PERMISSION_INDEX_OUT_OF_BOUNDS();

      // Turn the bit at the index on.
      packed |= 1 << _index;

      unchecked {
        _i++;
      }
    }

For loop gas optimization example

While, for presentation purposes, I have separated the for loop-related gas savings opportunities above, they should be combined. Below I show how all four of the gas saving methods outlined in this submission can be implemented together.

  1. Don't initialize the loop counter to zero
  2. Read the array length before executing the loop
  3. Use ++i rather than i++
  4. Use unchecked ++i

JBSingleTokenPaymentTerminalStore.sol: L862-875

    for (uint256 _i = 0; _i < _terminals.length; _i++)
      _ethOverflow = _ethOverflow + _terminals[_i].currentEthOverflowOf(_projectId);


    // Convert the ETH overflow to the specified currency if needed, maintaining a fixed point number with 18 decimals.
    uint256 _totalOverflow18Decimal = _currency == JBCurrencies.ETH
      ? _ethOverflow
      : PRBMath.mulDiv(_ethOverflow, 10**18, prices.priceFor(JBCurrencies.ETH, _currency, 18));


    // Adjust the decimals of the fixed point number if needed to match the target decimals.
    return
      (_decimals == 18)
        ? _totalOverflow18Decimal
        : JBFixedPointNumber.adjustDecimals(_totalOverflow18Decimal, 18, _decimals);
  }

Recommendation:

    uint256 totalTerminalsLength = _terminals.length; 
    for (uint256 _i; _i < totalTerminalsLength; ){
      _ethOverflow = _ethOverflow + _terminals[_i].currentEthOverflowOf(_projectId);

    // Convert the ETH overflow to the specified currency if needed, maintaining a fixed point number with 18 decimals.
    uint256 _totalOverflow18Decimal = _currency == JBCurrencies.ETH
      ? _ethOverflow
      : PRBMath.mulDiv(_ethOverflow, 10**18, prices.priceFor(JBCurrencies.ETH, _currency, 18));

    // Adjust the decimals of the fixed point number if needed to match the target decimals.
    return
      (_decimals == 18)
        ? _totalOverflow18Decimal
        : JBFixedPointNumber.adjustDecimals(_totalOverflow18Decimal, 18, _decimals);

        unchecked {
         ++_i;
       }
    }

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter