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: 44/105
Findings: 3
Award: $131.86
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
JBERC20PaymentTerminal.sol: L73-89
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.
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); }
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
🌟 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
90.2101 USDC - $90.21
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
_projectId The ID of the project to get instrinsic properties of.
Change instrinsic
to intrinsic
// If there is not funding cycle to base the current one on, there can't be a current one.
Change not
to no
// Efficiently stores a funding cycles provided user defined properties.
Change a funding cycles provided
to funding cycles based on
or otherwise clarify comment
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:
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
// Allow lock extention.
Change extention
to extension
The same typo (percents
) occurs in all three lines referenced below:
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:
JBPayoutRedemptionPaymentTerminal.sol: L162
Example (JBSplitsStore.sol: L230):
// The percent should be greater than 0.
Change percent
to percentage
in each case
// Add up all the percents to make sure they cumulative are under 100%.
Change cumulative are
to they total to
// Get a reference to the fist packed data.
Change fist
to first
- 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
🌟 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
38.2406 USDC - $38.24
For example, initializing uint
variables to their default value of 0
is unnecessary and costs gas
uint256 public override count = 0;
Change to uint256 public override count;
bool _includesLocked = false;
Change to bool _includesLocked;
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
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;
for
loopSince 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:
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; }
++i
instead of i++
to increase count in a for
loopSince 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
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; }
for
loopUnderflow/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
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 exampleWhile, 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.
++i
rather than i++
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; } }