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: 43/105
Findings: 3
Award: $132.45
🌟 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
https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBERC20PaymentTerminal.sol#L87 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBERC20PaymentTerminal.sol#L88
It is good to add a require() statement that checks the return value of token transfers or to use something like OpenZeppelin’s safeTransfer/safeTransferFrom unless one is sure the given token reverts in case of a failure. Failure to do so will cause silent failures of transfers and affect token accounting in contract.
JBERC20PaymentTerminal.sol:87 JBERC20PaymentTerminal.sol:88
contracts/JBERC20PaymentTerminal.sol:87: ? IERC20(token).transfer(_to, _amount) contracts/JBERC20PaymentTerminal.sol:88: : IERC20(token).transferFrom(_from, _to, _amount);
VsCode
This similar medium-severity finding from Consensys Diligence Audit of Fei Protocol.
Consider using safeTransfer/safeTransferFrom or require() consistently.
#0 - mejango
2022-07-12T18:38:06Z
dup #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
Contract should make use of _sendValueWithFallbackWithdraw() instead of sendValue(). This will ensure invalid transfers are correctly handled.Generally the use of sendValueWithFallbackWithdraw is when we are sending ETH to an address other than the msg.sender
contracts/JBETHPaymentTerminal.sol:78: Address.sendValue(_to, _amount);
Contract should make use of _sendValueWithFallbackWithdraw() instead of sendValue(). This will ensure invalid transfers are correctly handled.
This is probably an oversight since SafeERC20 was imported and safeTransfer() was used for ERC20 token transfers. Nevertheless, note that approve() will fail for certain token implementations that do not return a boolean value (). Hence it is recommend to use safeApprove().
contracts/JBERC20PaymentTerminal.sol:99
contracts/JBERC20PaymentTerminal.sol:99: IERC20(token).approve(_to, _amount);
Update to IERC20(token).safeapprove(_to, _amount); in the function.
As this array can grow quite large, the transaction’s gas cost could exceed the block gas limit and make it impossible to call this function at all (see @audit):
JBDirectory.sol:355-372 JBDirectory.sol:126-141
File: JBDirectory.sol 355: function _addTerminalIfNeeded(uint256 _projectId, IJBPaymentTerminal _terminal) private { ... ... 369: _terminalsOf[_projectId].push(_terminal); //@audit low: a push exist but there's no pop in the solution. ... ... 372: } ... 126: function primaryTerminalOf(uint256 _projectId, address _token) ... ... 139: for (uint256 _i; _i < _terminalsOf[_projectId].length; _i++) {//@audit low: poolInfo is unbounded 140: IJBPaymentTerminal _terminal = _terminalsOf[_projectId][_i];
Consider introducing a reasonable upper limit based on block gas limits and/or adding a remove method to remove elements in the array.
🌟 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
If a variable is not set/initialized, it is assumed to have the default value (0 for uint, false for bool, address(0) for address…). Explicitly initializing it with its default value is an anti-pattern and wastes gas.
We can use uint number;
instead of uint number = 0;
contracts/JBProjects.sol:40 contracts/JBSplitsStore.sol:227
contracts/JBSplitsStore.sol:209
contracts/JBProjects.sol:40: uint256 public override count = 0; contracts/JBSplitsStore.sol:227: uint256 _percentTotal = 0; contracts/JBSplitsStore.sol:209: bool _includesLocked = false;
I suggest removing explicit initializations for default values.
Saves 6 gas PER LOOP
contracts/JBDirectory.sol:139: contracts/JBDirectory.sol:167: contracts/JBDirectory.sol:275: contracts/JBDirectory.sol:276: contracts/JBOperatorStore.sol:85: contracts/JBOperatorStore.sol:135: contracts/JBOperatorStore.sol:165: contracts/JBController.sol:913: contracts/JBController.sol:1014: contracts/JBSplitsStore.sol:204: contracts/JBSplitsStore.sol:211: contracts/JBSplitsStore.sol:229: contracts/JBSplitsStore.sol:304:
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/JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { contracts/JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.length; _i++) { contracts/JBFundingCycleStore.sol:724: for (uint256 i = 0; i < _discountMultiple; 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++) {
The unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
contracts/JBDirectory.sol:139: contracts/JBDirectory.sol:167: contracts/JBDirectory.sol:275: contracts/JBDirectory.sol:276: contracts/JBOperatorStore.sol:85: contracts/JBOperatorStore.sol:135: contracts/JBOperatorStore.sol:165: contracts/JBController.sol:913: contracts/JBController.sol:1014: contracts/JBSplitsStore.sol:204: contracts/JBSplitsStore.sol:211: contracts/JBSplitsStore.sol:229: contracts/JBSplitsStore.sol:304:
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/JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { contracts/JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.length; _i++) { contracts/JBFundingCycleStore.sol:724: for (uint256 i = 0; i < _discountMultiple; 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++) {
Reading array length at each iteration of the loop takes 6 gas (3 for mload and 3 to place memory_offset) in the stack. Caching the array length in the stack saves around 3 gas per iteration.
contracts/JBDirectory.sol:139: contracts/JBDirectory.sol:167: contracts/JBDirectory.sol:275: contracts/JBDirectory.sol:276: contracts/JBOperatorStore.sol:85: contracts/JBOperatorStore.sol:135: contracts/JBOperatorStore.sol:165: contracts/JBController.sol:913: contracts/JBController.sol:1014: contracts/JBSplitsStore.sol:204: contracts/JBSplitsStore.sol:211: contracts/JBSplitsStore.sol:229:
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/JBController.sol:913: for (uint256 _i = 0; _i < _splits.length; _i++) { contracts/JBController.sol:1014: for (uint256 _i; _i < _fundAccessConstraints.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++) {
Here, I suggest storing the array’s length in a variable before the for-loop, and use it instead.