Juicebox V2 contest - rbserver'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: 24/105

Findings: 5

Award: $568.39

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

14.8726 USDC - $14.87

Labels

bug
duplicate
3 (High Risk)
valid

External Links

Lines of code

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

Vulnerability details

Impact

The Chainlink oracle data feed is not further validated after calling the latestRoundData method. As a result, the returned price can be stale.

Proof of Concept

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.

Tools Used

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

Lines of code

https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L1042-L1058

Vulnerability details

Impact

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.

Proof of Concept

Please see the following steps.

  1. A project is set up to accept USDT and configures a split allocator.
  2. When calling distributePayoutsOf that eventually calls _distributeToPayoutSplitsOf for the first time, _beforeTransferTo is called to approve the split allocator to spend _netPayoutAmount.
  3. If the split allocator's allocate function does not spend all of _netPayoutAmount, calling _split.allocator.allocate{value: _payableValue}(_data) will not reduce the approved allowance to 0.
  4. For the same project, when calling distributePayoutsOf again, which also calls _distributeToPayoutSplitsOf, calling _beforeTransferTo will now revert because the approved allowance for the split allocator is currently not zero.

Tools Used

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

Findings Information

🌟 Selected for report: IllIllI

Also found by: Meera, cccz, hake, rbserver, robee

Labels

bug
duplicate
2 (Med Risk)
valid

Awards

422.0095 USDC - $422.01

External Links

Lines of code

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

Vulnerability details

Impact

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.

Proof of Concept

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.

Tools Used

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

[L-01] MISSING ZERO-ADDRESS CHECK

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,

[L-02] UNBOUNDED ARRAY LOOP CAN CAUSE DOS

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; )

[L-03] CONSTANTS CAN BE USED INSTEAD OF MAGIC NUMBERS

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)

[N-01] TYPO IN NATSPEC

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.

[N-02] MISSING NATSPEC

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

[N-03] MISSING INDEXED EVENT FIELDS

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

[N-04] UNDERSCORES IN NUMBER LITERALS OR SCIENTIFIC NOTATION WITH EXPONENT CAN BE USED

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; }

[G-01] VARIABLE DOES NOT NEED TO BE INITIALIZED TO ITS DEFAULT VALUE

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; )

[G-02] ARRAY LENGTH CAN BE CACHED OUTSIDE OF LOOP

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; )

[G-03] ++VARIABLE CAN BE USED INSTEAD OF VARIABLE++

++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++)

[G-04] ARITHMETIC OPERATIONS THAT DO NOT OVERFLOW OR UNDERFLOW CAN BE UNCHECKED

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,

  • because of the following comparison:
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;
  • because of _unclaimedBalance < _amount, _amount - _unclaimedBalance can be unchecked for the following:
contracts\JBTokenStore.sol:350 -> else _claimedTokensToBurn = _unclaimedBalance < _amount ? _amount - _unclaimedBalance : 0;
  • similarly, the following substractions can be unchecked:
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);

[G-05] X = X + Y CAN BE USED INSTEAD OF X += Y

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);

[G-06] RETURN VALUES FROM STATIC CALLS TO STORAGE CAN BE CACHED IN MEMORY

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;
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