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: 34/105
Findings: 4
Award: $158.58
🌟 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
ChainlinkAggregator fetches the price in JBChainlinkV3PriceFeed
using the latestRoundData function. However, there are no checks on roundID nor timeStamp, resulting in stale prices.
The contract JBPrices
use currentPrice
function to return the current price of a token and might return an incorrect value.
function currentPrice(uint256 _decimals) external view override returns (uint256) { // Get the latest round information. Only need the price is needed. - (, int256 _price, , , ) = feed.latestRoundData(); + (uint80 roundID, int256 price, , uint256 timeStamp, uint80 answeredInRound) = feed.latestRoundData(); + require(price > 0, "invalid price"); + require(answeredInRound >= roundID, "invalid answeredInRound"); + require(timeStamp != 0, "invalid timestamp"); // Get a reference to the number of decimals the feed uses. uint256 _feedDecimals = feed.decimals(); // Return the price, adjusted to the target decimals. return uint256(_price).adjustDecimals(_feedDecimals, _decimals); }
#0 - mejango
2022-07-12T18:50:25Z
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
Some tokens fail silently, not throwing an error. The functions safeTransfer
and safeTransferFrom
has checks for that returns and throw an error if not possible to transfer the tokens
+ using SafeERC20 for IERC20; ... _from == address(this) - ? IERC20(token).transfer(_to, _amount) - : IERC20(token).transferFrom(_from, _to, _amount); + ? IERC20(token).safeTransfer(_to, _amount) + : IERC20(token).safeTransferFrom(_from, _to, _amount);
#0 - mejango
2022-07-12T18:30:49Z
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
94.5013 USDC - $94.50
launchProjectFor
in JBController
Is able to create a null owner to the project, should check if the _owner is set before create a new project.
) external virtual override returns (uint256 projectId) { + if (_owner == address(0)) revert CUSTOM_ERROR(); // Mint the project into the wallet of the message sender. projectId = projects.createFor(_owner, _projectMetadata); ... }
All contracts
Is possible to create the contracts without setting the corrects immutable variables.
Should check for variables in constructor for all contracts, example:
In JBTokenStore.sol
constructor( IJBOperatorStore _operatorStore, IJBProjects _projects, IJBDirectory _directory ) JBOperatable(_operatorStore) JBControllerUtility(_directory) { + if(project == IJBProjects(address(0))) revert NO_PROJECT_SET; projects = _projects; }
JBPayoutRedemptionPaymentTerminal.sol#L444
Return named variables should be used or removed
Remove the named return
- ) external virtual override returns (uint256 netLeftoverDistributionAmount) { + ) external virtual override returns (uint256) {
or add the named return instead of return, like this code:
- return _distributePayoutsOf(_projectId, _amount, _currency, _minReturnedTokens, _memo); + netLeftoverDistributionAmount = _distributePayoutsOf(_projectId, _amount, _currency, _minReturnedTokens, _memo);
🌟 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
JBSingleTokenPaymentTerminalStore.sol#L862
JBPayoutRedemptionPaymentTerminal.sol#L594
JBPayoutRedemptionPaymentTerminal.sol#L1008
Most of all loops in project aren't optimized
Fix all loops following the steps:
- for (uint256 _i = 0; _i < array.length(); _i++) {...} + uint256 length = array.length(); + for (uint256 _i; _i < length;){ + ... + unchecked { ++_i; } + }
JBPayoutRedemptionPaymentTerminal.sol#L594-L611
JBPayoutRedemptionPaymentTerminal.sol#L1396-L1428
Caching storage variables into memory variable can save gas when accessed more than 1 time in loop
//JBSplitsStore.sol#L204-L223 for (uint256 _i = 0; _i < _currentSplits.length; _i++) { + JBSplit memory currentSplit = _currentSplits[i]; ... for (uint256 _j = 0; _j < _splits.length; _j++) { // Check for sameness. + JBSplit memory split = _splits[j] ... } }
//JBSplitsStore.sol#L227-274 for (uint256 _i = 0; _i < _splits.length; _i++) { + JBSplit memory split = _splits[i]; ... }
//JBPayoutRedemptionPaymentTerminal.sol#L596-L612 for (uint256 _i = 0; _i < _heldFeeLength; ) { + JBFee memory heldFee = _heldFees[_i]; ... }
JBPayoutRedemptionPaymentTerminal.sol#L1398-L1427 for (uint256 _i = 0; _i < _heldFeesLength; ) { + JBFee memory heldFee = _heldFees[_i]; ... }
JBSingleTokenPaymentTerminalStore.sol#L730
Variables declared with default value aren't gas optimized, they shouldn't be declared with default value
Follow the changes to optimize the code:
- uint256 x = 0; + uint256 x; - bool flag = false; + bool flag; mapping(uint256 => uint256) map; - map[x] = 0; + delete map[x];
feed
to immutable in JBChainlinkV3PriceFeed
JBChainlinkV3PriceFeed.sol#L28
Variables declared as immutable saves gas
- AggregatorV3Interface public feed; + AggregatorV3Interface public immutable feed;
JBTokenStore
Simplifying the if/else conditions can save gas
// The amount of tokens to burn. uint256 _claimedTokensToBurn; - if (_claimedBalance == 0) - _claimedTokensToBurn = 0; - else if (_preferClaimedTokens) - _claimedTokensToBurn = _claimedBalance < _amount ? _claimedBalance : _amount; - else _claimedTokensToBurn = _unclaimedBalance < _amount ? _amount - _unclaimedBalance : 0; + if (_claimedBalance != 0) { + if (_preferClaimedTokens) + _claimedTokensToBurn = _claimedBalance < _amount ? _claimedBalance : _amount; + else _claimedTokensToBurn = _unclaimedBalance < _amount ? _amount - _unclaimedBalance : 0; + }