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: 41/105
Findings: 2
Award: $135.07
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
I recommend adding check of 0-address for immutable addresses. Not doing so might lead to non-functional contract when it is updated to 0-address accidentally.
2 immutable addresses of "projects" and "directory" of JBSplitsStore.sol https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBSplitsStore.sol#L103-L109
2 immutable addresses of "projects" and "fundingCycleStore" of JBDirectory.sol https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBDirectory.sol#L182-L192
4 immutable addresses of "projects", "fundingCycleStore", "tokenStore" "splitsStore" and "directory" of JBController.sol https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBController.sol#L369-L382
4 immutable addresses of "projects", "directory" "splitsStore", "prices" and "store" of JBPayoutRedemptionPaymentTerminal.sol https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L283-L297
3 immutable addresses of "directory", "fundingCycleStore" and "prices" of JBSingleTokenPaymentTerminalStore.sol https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBSingleTokenPaymentTerminalStore.sol#L276-L284
Add 0-address check for immutable addresses shown in above PoC.
🌟 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
 
Since Solidity 0.8.0, all arithmetic operations revert on overflow and underflow by default. However in places where overflow and underflow is not possible, it is better to use unchecked block to reduce the gas usage. Reference: https://docs.soliditylang.org/en/v0.8.15/control-structures.html#checked-or-unchecked-arithmetic
Issue found at
Wrap line 237 with unchecked since underflow is not possible due to line 234 check 234: if (_splits[_i].projectId > type(uint56).max) revert INVALID_PROJECT_ID(); 237: _percentTotal = _percentTotal + _splits[_i].percent;
Wrap the code with uncheck like described in above PoC.
 
SLOADs cost 100 gas where MLOADs/MSTOREs cost only 3 gas. Whenever function reads storage value more than once, it should be cached to save gas.
Cache _processedTokenTrackerOf of function mintTokensOf 4 SLOADs to 1 SLOAD, 1 MSTORE and 3 MLOAD https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBController.sol#L666-L680
Cache directory of function launchFundingCyclesFor 2 SLOADs to 1 SLOAD, 1 MSTORE and 1 MLOAD https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBController.sol#L485-L498
When certain state variable is read more than once, cache it to local variable to save gas.
 
Certain variables is defined even though they are used only once. Remove these unnecessary variables to save gas. For cases where it will reduce the readability, one can use comments to help describe what the code is doing.
Issue found at
fundingCycle = _getStructFor(_projectId, latestConfigurationOf[_projectId]);
JBFundingCycle memory _fundingCycle = _getStructFor(_projectId, latestConfigurationOf[_projectId]);
_size := extcodesize(address(_data.ballot)) // No contract at the address ?
uint256 _discountMultiple = (_start - _baseFundingCycle.start) / _baseFundingCycle.duration;
return _baseFundingCycle.number + ((_start - _baseFundingCycle.start) / _baseFundingCycle.duration);
Don't define variable that is used only once. Details are listed on above PoC.
 
When variable is not initialized, it will have its default values. For example, 0 for uint, false for bool and address(0) for address. Reference: https://docs.soliditylang.org/en/v0.8.15/control-structures.html#scoping-and-declarations
./JBSplitsStore.sol:165: for (uint256 _i = 0; _i < _groupedSplitsLength; ) { ./JBSplitsStore.sol:204: for (uint256 _i = 0; _i < _currentSplits.length; _i++) { ./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++) { ./JBTokenStore.sol:262: if (oldToken != IJBToken(address(0))) projectOf[oldToken] = 0; ./JBTokenStore.sol:345: _claimedTokensToBurn = 0; ./JBFundingCycleStore.sol:724: for (uint256 i = 0; i < _discountMultiple; i++) { ./JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _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++) { ./JBPayoutRedemptionPaymentTerminal.sol:594: for (uint256 _i = 0; _i < _heldFeeLength; ) { ./JBPayoutRedemptionPaymentTerminal.sol:1008: for (uint256 _i = 0; _i < _splits.length; ) { ./JBPayoutRedemptionPaymentTerminal.sol:1396: for (uint256 _i = 0; _i < _heldFeesLength; ) { ./JBPayoutRedemptionPaymentTerminal.sol:1419: leftoverAmount = 0; ./JBPayoutRedemptionPaymentTerminal.sol:1469: feeDiscount = 0; ./JBPayoutRedemptionPaymentTerminal.sol:1475: feeDiscount = 0; ./JBPayoutRedemptionPaymentTerminal.sol:1479: if (feeDiscount > JBConstants.MAX_FEE_DISCOUNT) feeDiscount = 0; ./JBSingleTokenPaymentTerminalStore.sol:730: balanceOf[IJBSingleTokenPaymentTerminal(msg.sender)][_projectId] = 0; ./JBSingleTokenPaymentTerminalStore.sol:862: for (uint256 _i = 0; _i < _terminals.length; _i++) ./JBProjects.sol:40: uint256 public override count = 0; ./JBSplitsStore.sol:209: bool _includesLocked = false;
I suggest removing default value initialization. For example,
 
By storing an array's length as a variable before the for-loop, can save 3 gas per iteration.
./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++) { ./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++) ./JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { ./JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.length; _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++) { ./JBPayoutRedemptionPaymentTerminal.sol:1008: for (uint256 _i = 0; _i < _splits.length; ) { ./JBSingleTokenPaymentTerminalStore.sol:862: for (uint256 _i = 0; _i < _terminals.length; _i++)
Store array's length as a variable before looping it. For example, I suggest changing it to
length = _currentSplits.length for (uint256 _i = 0; _i < length; _i++) {
 
Prefix increments/decrements (++i or --i) costs cheaper gas than postfix increment/decrements (i++ or i--).
./JBSplitsStore.sol:173: ++_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++) { ./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++) { ./JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { ./JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.length; _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++)
Change it to postfix increments/decrements. For example,
for (uint256 _i = 0; _i < _currentSplits.length; ++_i) {
 
It is cheaper gas to use calldata than memory if the function parameter is read only. Calldata is a non-modifiable, non-persistent area where function arguments are stored, and behaves mostly like memory. More details on following link. link: https://docs.soliditylang.org/en/v0.8.15/types.html#data-location
./JBFundingCycleStore.sol:624: function _mockFundingCycleBasedOn(JBFundingCycle memory _baseFundingCycle, bool _allowMidCycle) ./JBFundingCycleStore.sol:664: function _deriveStartFrom(JBFundingCycle memory _baseFundingCycle, uint256 _mustStartAtOrAfter) ./JBFundingCycleStore.sol:698: function _deriveWeightFrom(JBFundingCycle memory _baseFundingCycle, uint256 _start) ./JBFundingCycleStore.sol:746: function _deriveNumberFrom(JBFundingCycle memory _baseFundingCycle, uint256 _start) ./JBFundingCycleStore.sol:770: function _isApproved(uint256 _projectId, JBFundingCycle memory _fundingCycle) ./JBController.sol:418: IJBPaymentTerminal[] memory _terminals, ./JBController.sol:419: string memory _memo ./JBController.sol:470: JBFundAccessConstraints[] memory _fundAccessConstraints, ./JBController.sol:471: IJBPaymentTerminal[] memory _terminals, ./JBController.sol:472: string memory _memo ./JBController.sol:841: function _distributeReservedTokensOf(uint256 _projectId, string memory _memo) ./JBController.sol:988: JBGroupedSplits[] memory _groupedSplits, ./JBController.sol:989: JBFundAccessConstraints[] memory _fundAccessConstraints ./JBPayoutRedemptionPaymentTerminal.sol:392: string memory _memo, ./JBPayoutRedemptionPaymentTerminal.sol:393: bytes memory _metadata ./JBPayoutRedemptionPaymentTerminal.sol:477: string memory _memo ./JBPayoutRedemptionPaymentTerminal.sol:718: string memory _memo, ./JBPayoutRedemptionPaymentTerminal.sol:719: bytes memory _metadata ./JBPayoutRedemptionPaymentTerminal.sol:927: string memory _memo ./JBPayoutRedemptionPaymentTerminal.sol:1190: JBFundingCycle memory _fundingCycle, ./JBPayoutRedemptionPaymentTerminal.sol:1267: string memory _memo, ./JBPayoutRedemptionPaymentTerminal.sol:1268: bytes memory _metadata ./JBPayoutRedemptionPaymentTerminal.sol:1358: string memory _memo, ./JBPayoutRedemptionPaymentTerminal.sol:1359: bytes memory _metadata ./JBSingleTokenPaymentTerminalStore.sol:320: bytes memory _metadata ./JBSingleTokenPaymentTerminalStore.sol:326: JBFundingCycle memory fundingCycle, ./JBSingleTokenPaymentTerminalStore.sol:329: string memory memo ./JBSingleTokenPaymentTerminalStore.sol:418: string memory _memo, ./JBSingleTokenPaymentTerminalStore.sol:419: bytes memory _metadata ./JBSingleTokenPaymentTerminalStore.sol:425: JBFundingCycle memory fundingCycle, ./JBSingleTokenPaymentTerminalStore.sol:428: string memory memo ./JBSingleTokenPaymentTerminalStore.sol:754: JBFundingCycle memory _fundingCycle, ./JBSingleTokenPaymentTerminalStore.sol:807: JBFundingCycle memory _fundingCycle,
Change memory to calldata