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: 24/105
Findings: 5
Award: $568.39
🌟 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
https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBChainlinkV3PriceFeed.sol#L44 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBPrices.sol#L69 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBPrices.sol#L76
The Chainlink oracle data feed is not further validated after calling the latestRoundData method. As a result, the returned price can be stale.
In JBChainlinkV3PriceFeed.sol#L44 of the currentPrice
function, the latestRoundData
method is called to get _price
. However, the returned price data is not further validated and can be stale.
Because the currentPrice
function is called in JBPrices.sol#L69 and JBPrices.sol#L76 of the priceFor
function, the priceFor
function can also return stale price.
Such stale prices can be used in the JBSingleTokenPaymentTerminalStore
and JBPayoutRedemptionPaymentTerminal
contracts since the priceFor
function is called in multiple places of these contracts.
VSCode
In the currentPrice
function, the data returned by the latestRoundData
method can be further validated as follows.
function currentPrice(uint256 _decimals) external view override returns (uint256) { // Get the latest round information. (uint80 _roundId, int256 _price, , uint256 _timestamp, uint80 _answeredInRound) = feed.latestRoundData(); require(_answeredInRound >= _roundId, “price is stale”); require(_timestamp > 0, “round is incomplete”); require(_price > 0, “price is zero”); ...
#0 - mejango
2022-07-12T18:50:05Z
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
For tokens like USDT, the approve
function with some value will revert if the current approved allowance is not zero. When a project accepts USDT and configures a split allocator that does not fully spend the approved allowance after calling _distributeToPayoutSplitsOf
for the first time, calling _distributeToPayoutSplitsOf
again will have the call of _beforeTransferTo
to revert.
Please see the following steps.
distributePayoutsOf
that eventually calls _distributeToPayoutSplitsOf
for the first time, _beforeTransferTo
is called to approve the split allocator to spend _netPayoutAmount
.allocate
function does not spend all of _netPayoutAmount
, calling _split.allocator.allocate{value: _payableValue}(_data)
will not reduce the approved allowance to 0.distributePayoutsOf
again, which also calls _distributeToPayoutSplitsOf
, calling _beforeTransferTo
will now revert because the approved allowance for the split allocator is currently not zero.VSCode
Before calling _beforeTransferTo
in the _distributeToPayoutSplitsOf
function, _beforeTransferTo(address(_split.allocator), 0)
can be added.
#0 - mejango
2022-07-12T18:55:14Z
dup #101
https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L332-L365 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L540-L562 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L712-L786 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L809-L900 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L921-L979 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L994-L1174
Some ERC20 tokens charge a transfer fee. For these tokens, after an incoming transfer, the amount received by the protocol is less than specified. However, the specified amount is used for accounting updates and state changing operations, such as minting project tokens after paying. As a result, the actual received amount and recorded amounts are not in sync. Similarly, after an outgoing transfer, the amount received by the receiving party will also be less than specified.
In the pay
, and addToBalanceOf
functions, tokens are transferred to the project's payment terminal. For tokens with transfer fees, the actual received amounts are less than specified. However, the specified amounts are used for accounting updates and state changing operations.
In the _redeemTokensOf
, _distributePayoutsOf
, _useAllowanceOf
, and _distributeToPayoutSplitsOf
functions, tokens are transferred from the project's payment terminal. For tokens with transfer fees, the receiving parties would receive amounts less than specified.
VSCode
For incoming transfers, the actual received token amounts can be used instead of specified amounts for accounting updates and state changing operations. For outgoing transfers, the protocol's documentation can be updated to inform users that the received amounts would be less than specified.
#0 - mejango
2022-07-12T20:04:13Z
dup #13
#1 - jack-the-pug
2022-07-24T02:38:29Z
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
89.271 USDC - $89.27
Addresses should be checked against address(0).
contracts\JBDirectory.sol:186 -> address _owner contracts\JBETHERC20ProjectPayer.sol:124 -> address payable _defaultBeneficiary, contracts\JBETHERC20ProjectPayer.sol:130 -> address _owner contracts\abstract\JBSingleTokenPaymentTerminal.sol:125 -> address _token,
To prevent DOS due to gas limit, number of items in an array that needs to be iterated through can be capped at a reasonable limit. Function for removing items from the array can also be added.
contracts\JBController.sol:913 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\JBController.sol:1014 -> for (uint256 _i; _i < _fundAccessConstraints.length; _i++) contracts\JBDirectory.sol:139 -> for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) contracts\JBDirectory.sol:167 -> for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) contracts\JBDirectory.sol:275 -> for (uint256 _i; _i < _terminals.length; _i++) contracts\JBDirectory.sol:276 -> for (uint256 _j = _i + 1; _j < _terminals.length; _j++) contracts\JBETHERC20SplitsPayer.sol:466 -> for (uint256 i = 0; i < _splits.length; i++) { contracts\JBFundingCycleStore.sol:724 -> for (uint256 i = 0; i < _discountMultiple; i++) contracts\JBOperatorStore.sol:85 -> or (uint256 _i = 0; _i < _permissionIndexes.length; _i++) contracts\JBOperatorStore.sol:135 -> for (uint256 _i = 0; _i < _operatorData.length; _i++) contracts\JBOperatorStore.sol:165 -> for (uint256 _i = 0; _i < _indexes.length; _i++) contracts\JBSingleTokenPaymentTerminalStore.sol:862 -> for (uint256 _i = 0; _i < _terminals.length; _i++) contracts\JBSplitsStore.sol:165 -> for (uint256 _i = 0; _i < _groupedSplitsLength; ) contracts\JBSplitsStore.sol:204 -> for (uint256 _i = 0; _i < _currentSplits.length; _i++) contracts\JBSplitsStore.sol:211 -> for (uint256 _j = 0; _j < _splits.length; _j++) contracts\JBSplitsStore.sol:229 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\JBSplitsStore.sol:304 -> for (uint256 _i = 0; _i < _splitCount; _i++) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:594 -> for (uint256 _i = 0; _i < _heldFeeLength; ) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1008 -> for (uint256 _i = 0; _i < _splits.length; ) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1396 -> for (uint256 _i = 0; _i < _heldFeesLength; )
To improve readability and maintainability, constants can be used instead of magic numbers.
contracts\JBSingleTokenPaymentTerminalStore.sol:868 -> PRBMath.mulDiv(_ethOverflow, 10**18, prices.priceFor(JBCurrencies.ETH, _currency, 18)); contracts\JBSingleTokenPaymentTerminalStore.sol:872 -> (_decimals == 18) contracts\JBSingleTokenPaymentTerminalStore.sol:874 -> JBFixedPointNumber.adjustDecimals(_totalOverflow18Decimal, 18, _decimals); contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:209 -> uint256 _adjustedOverflow = (decimals == 18) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:211 -> JBFixedPointNumber.adjustDecimals(_overflow, decimals, 18) contracts\JBController.sol:948,15 -> 18, contracts\JBETHPaymentTerminal.sol:42 -> 18, // 18 decimals. contracts\JBTokenStore.sol:249 -> if (_token != IJBToken(address(0)) && _token.decimals() != 18)
It should be @param _permissionIndex
instead of @param _domain
.
contracts\abstract\JBOperatable.sol:111 -> @param _domain The permission index that an operator must have within the specified domain to be allowed.
NatSpec comments are missing for the following:
contracts\libraries\JBCurrencies.sol:4 -> library JBCurrencies contracts\libraries\JBFixedPointNumber.sol:4 -> library JBFixedPointNumber contracts\libraries\JBFixedPointNumber.sol:5 -> function adjustDecimals contracts\libraries\JBFundingCycleMetadataResolver.sol:10 -> library JBFundingCycleMetadataResolver contracts\libraries\JBFundingCycleMetadataResolver.sol:11 -> function global(JBFundingCycle memory _fundingCycle) contracts\libraries\JBFundingCycleMetadataResolver.sol:19 -> function reservedRate(JBFundingCycle memory _fundingCycle) internal pure returns (uint256) contracts\libraries\JBFundingCycleMetadataResolver.sol:23 -> function redemptionRate(JBFundingCycle memory _fundingCycle) internal pure returns (uint256) contracts\libraries\JBFundingCycleMetadataResolver.sol:28 -> function ballotRedemptionRate(JBFundingCycle memory _fundingCycle) contracts\libraries\JBFundingCycleMetadataResolver.sol:37 -> function payPaused(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:41 -> function distributionsPaused(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:45 -> function redeemPaused(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:49 -> function burnPaused(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:53 -> function mintingAllowed(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:57 -> function changeTokenAllowed(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:61 -> function terminalMigrationAllowed(JBFundingCycle memory _fundingCycle) contracts\libraries\JBFundingCycleMetadataResolver.sol:69 -> function controllerMigrationAllowed(JBFundingCycle memory _fundingCycle) contracts\libraries\JBFundingCycleMetadataResolver.sol:77 -> function shouldHoldFees(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:81 -> function useTotalOverflowForRedemptions(JBFundingCycle memory _fundingCycle) contracts\libraries\JBFundingCycleMetadataResolver.sol:89 -> function useDataSourceForPay(JBFundingCycle memory _fundingCycle) internal pure returns (bool) contracts\libraries\JBFundingCycleMetadataResolver.sol:93 -> function useDataSourceForRedeem(JBFundingCycle memory _fundingCycle) contracts\libraries\JBFundingCycleMetadataResolver.sol:101 -> function dataSource(JBFundingCycle memory _fundingCycle) internal pure returns (address) contracts\libraries\JBGlobalFundingCycleMetadataResolver.sol:8 -> library JBGlobalFundingCycleMetadataResolver contracts\libraries\JBGlobalFundingCycleMetadataResolver.sol:9 -> function setTerminalsAllowed(uint8 _data) internal pure returns (bool) contracts\libraries\JBGlobalFundingCycleMetadataResolver.sol:13 -> function setControllerAllowed(uint8 _data) internal pure returns (bool) contracts\libraries\JBOperations.sol:4 -> library JBOperations contracts\libraries\JBSplitsGroups.sol:4 -> library JBSplitsGroups contracts\libraries\JBTokens.sol:4 -> library JBTokens
The following events are missing indexed fields:
contracts\interfaces\IJBController.sol:19 -> event LaunchProject(uint256 configuration, uint256 projectId, string memo, address caller); contracts\interfaces\IJBController.sol:21 -> event LaunchFundingCycles(uint256 configuration, uint256 projectId, string memo, address caller); contracts\interfaces\IJBController.sol:23 -> event ReconfigureFundingCycles( contracts\interfaces\IJBPayoutRedemptionPaymentTerminal.sol:135 -> event SetFee(uint256 fee, address caller); contracts\interfaces\IJBSplitsPayer.sol:48 -> event DistributeToSplit
Underscores in number literals or scientific notation with exponent can be used for improving readability and maintainability.
contracts\libraries\JBConstants.sol:8-15 -> library JBConstants { uint256 public constant MAX_RESERVED_RATE = 10000; uint256 public constant MAX_REDEMPTION_RATE = 10000; uint256 public constant MAX_DISCOUNT_RATE = 1000000000; uint256 public constant SPLITS_TOTAL_PERCENT = 1000000000; uint256 public constant MAX_FEE = 1000000000; uint256 public constant MAX_FEE_DISCOUNT = 1000000000; }
🌟 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.8282 USDC - $38.83
Explicitly initializing a variable with its default value costs more gas than uninitializing it.
contracts\JBController.sol:913 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\JBETHERC20SplitsPayer.sol:466 -> for (uint256 i = 0; i < _splits.length; i++) contracts\JBOperatorStore.sol:85 -> for (uint256 _i = 0; _i < _permissionIndexes.length; _i++) contracts\JBOperatorStore.sol:135 -> for (uint256 _i = 0; _i < _operatorData.length; _i++) contracts\JBOperatorStore.sol:165 -> for (uint256 _i = 0; _i < _indexes.length; _i++) contracts\JBSingleTokenPaymentTerminalStore.sol:862 -> for (uint256 _i = 0; _i < _terminals.length; _i++) contracts\JBSplitsStore.sol:165 -> for (uint256 _i = 0; _i < _groupedSplitsLength; ) contracts\JBSplitsStore.sol:204 -> for (uint256 _i = 0; _i < _currentSplits.length; _i++) contracts\JBSplitsStore.sol:211 -> for (uint256 _j = 0; _j < _splits.length; _j++) contracts\JBSplitsStore.sol:229 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\JBSplitsStore.sol:304 -> for (uint256 _i = 0; _i < _splitCount; _i++) contracts\JBFundingCycleStore.sol:724 -> for (uint256 i = 0; i < _discountMultiple; i++) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:594 -> for (uint256 _i = 0; _i < _heldFeeLength; ) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1008 -> for (uint256 _i = 0; _i < _splits.length; ) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1396 -> for (uint256 _i = 0; _i < _heldFeesLength; )
Caching the array length outside of the loop and using the cached length in the loop costs less gas than reading the array length for each iteration.
contracts\JBController.sol:913 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\JBController.sol:1014 -> for (uint256 _i; _i < _fundAccessConstraints.length; _i++) contracts\JBDirectory.sol:139 -> for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) contracts\JBDirectory.sol:167 -> for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) contracts\JBDirectory.sol:275 -> for (uint256 _i; _i < _terminals.length; _i++) contracts\JBDirectory.sol:276 -> for (uint256 _j = _i + 1; _j < _terminals.length; _j++) contracts\JBETHERC20SplitsPayer.sol:466 -> for (uint256 i = 0; i < _splits.length; i++) contracts\JBOperatorStore.sol:85 -> for (uint256 _i = 0; _i < _permissionIndexes.length; _i++) contracts\JBOperatorStore.sol:135 -> for (uint256 _i = 0; _i < _operatorData.length; _i++) contracts\JBOperatorStore.sol:165 -> for (uint256 _i = 0; _i < _indexes.length; _i++) contracts\JBSingleTokenPaymentTerminalStore.sol:862 -> for (uint256 _i = 0; _i < _terminals.length; _i++) contracts\JBSplitsStore.sol:204 -> for (uint256 _i = 0; _i < _currentSplits.length; _i++) contracts\JBSplitsStore.sol:211 -> for (uint256 _j = 0; _j < _splits.length; _j++) contracts\JBSplitsStore.sol:229 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1008 -> for (uint256 _i = 0; _i < _splits.length; )
++variable costs less gas than variable++.
contracts\JBController.sol:913 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\JBController.sol:1014 -> for (uint256 _i; _i < _fundAccessConstraints.length; _i++) contracts\JBDirectory.sol:139 -> for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) contracts\JBDirectory.sol:167 -> for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) contracts\JBDirectory.sol:275 -> for (uint256 _i; _i < _terminals.length; _i++) contracts\JBDirectory.sol:276 -> for (uint256 _j = _i + 1; _j < _terminals.length; _j++) contracts\JBOperatorStore.sol:85 -> for (uint256 _i = 0; _i < _permissionIndexes.length; _i++) contracts\JBOperatorStore.sol:135 -> for (uint256 _i = 0; _i < _operatorData.length; _i++) contracts\JBOperatorStore.sol:165 -> for (uint256 _i = 0; _i < _indexes.length; _i++) contracts\JBSingleTokenPaymentTerminalStore.sol:862 -> for (uint256 _i = 0; _i < _terminals.length; _i++) contracts\JBSplitsStore.sol:204 -> for (uint256 _i = 0; _i < _currentSplits.length; _i++) contracts\JBSplitsStore.sol:211 -> for (uint256 _j = 0; _j < _splits.length; _j++) contracts\JBSplitsStore.sol:229 -> for (uint256 _i = 0; _i < _splits.length; _i++) contracts\JBSplitsStore.sol:304 -> for (uint256 _i = 0; _i < _splitCount; _i++) contracts\JBETHERC20SplitsPayer.sol:466 -> for (uint256 i = 0; i < _splits.length; i++) contracts\JBFundingCycleStore.sol:724 -> for (uint256 i = 0; i < _discountMultiple; i++)
Explicitly unchecking arithmetic operations that do not overflow or underflow by wrapping these in unchecked {}
costs less gas than implicitly checking these.
For loops, if increasing or decreasing the counter variable is very unlikely to overflow or underflow, then unchecked {++i}
or unchecked {--i}
at the end of the loop block can be used, where i
is the counter variable.
For situations where the related comparisons are already performed to guarantee that the arithmetic operations do not overflow or underflow, the operations can be wrapped in unchecked {}
to save gas.
For example,
contracts\JBSingleTokenPaymentTerminalStore.sol:514-515 -> if (reclaimAmount > balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId]) revert INADEQUATE_PAYMENT_TERMINAL_STORE_BALANCE();
the following substraction can be unchecked:
contracts\JBSingleTokenPaymentTerminalStore.sol:519-521 -> balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] = balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] - reclaimAmount;
_unclaimedBalance < _amount
, _amount - _unclaimedBalance
can be unchecked for the following:contracts\JBTokenStore.sol:350 -> else _claimedTokensToBurn = _unclaimedBalance < _amount ? _amount - _unclaimedBalance : 0;
contracts\JBTokenStore.sol:353 -> uint256 _unclaimedTokensToBurn = _amount - _claimedTokensToBurn; contracts\libraries\JBFixedPointNumber.sol:12 -> else if (_targetDecimals > _decimals) return _value * 10**(_targetDecimals - _decimals); contracts\libraries\JBFixedPointNumber.sol:13 -> else return _value / 10**(_decimals - _targetDecimals);
x = x + y costs less gas than x += y.
contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:860 -> _feeEligibleDistributionAmount += _leftoverDistributionAmount; contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1038 -> feeEligibleDistributionAmount += _payoutAmount; contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1103 -> feeEligibleDistributionAmount += _payoutAmount; contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1145 -> feeEligibleDistributionAmount += _payoutAmount; contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1401 -> refundedFees += _feeAmount( contracts\abstract\JBPayoutRedemptionPaymentTerminal.sol:1417 -> refundedFees += _feeAmount(leftoverAmount, _heldFees[_i].fee, _heldFees[_i].feeDiscount);
When the storage needs to be accessed for multiple times, caching the return values from these static calls in memory can save gas.
balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId]
can be cached for the following:
contracts\JBSingleTokenPaymentTerminalStore.sol:513-521 -> // The amount being reclaimed must be within the project's balance. if (reclaimAmount > balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId]) revert INADEQUATE_PAYMENT_TERMINAL_STORE_BALANCE(); // Remove the reclaimed funds from the project's balance. if (reclaimAmount > 0) balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] = balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] - reclaimAmount; contracts\JBSingleTokenPaymentTerminalStore.sol:588-600 -> // The amount being distributed must be available. if (distributedAmount > balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId]) revert INADEQUATE_PAYMENT_TERMINAL_STORE_BALANCE(); ... // Removed the distributed funds from the project's token balance. balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] = balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] - distributedAmount;