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: 38/105
Findings: 4
Award: $145.78
π 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/blob/248fa2470ebd3f52c87c16a0cf2946e46f997397/contracts/JBChainlinkV3PriceFeed.sol#L42-L51 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBPrices.sol#L57-L80
This could lead to stale prices according to the Chainlink documentation:
https://docs.chain.link/docs/historical-price-data/#historical-rounds https://docs.chain.link/docs/faq/#how-can-i-check-if-the-answer-to-a-round-is-being-carried-over-from-a-previous-round
On JBChainlinkV3PriceFeed.sol, we are using latestRoundData, but there is no check if the return value indicates stale data.
function currentPrice(uint256 _decimals) external view override returns (uint256) { // Get the latest round information. Only need the price is needed. (, int256 _price, , , ) = feed.latestRoundData(); // 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); }
This may be used in JBPrices.sol in priceFor function
function priceFor( uint256 _currency, uint256 _base, uint256 _decimals ) external view override returns (uint256) { // If the currency is the base, return 1 since they are priced the same. Include the desired number of decimals. if (_currency == _base) return 10**_decimals; // Get a reference to the feed. IJBPriceFeed _feed = feedFor[_currency][_base]; // If it exists, return the price. if (_feed != IJBPriceFeed(address(0))) return _feed.currentPrice(_decimals); // Get the inverse feed. _feed = feedFor[_base][_currency]; // If it exists, return the inverse price. if (_feed != IJBPriceFeed(address(0))) return PRBMath.mulDiv(10**_decimals, 10**_decimals, _feed.currentPrice(_decimals)); // No price feed available, revert. revert PRICE_FEED_NOT_FOUND(); }
Compare with previous audit: https://code4rena.com/reports/2022-04-backd/#m-05-chainlinks-latestrounddata-might-return-stale-or-incorrect-results
function currentPrice(uint256 _decimals) external view override returns (uint256) { // Get the latest round information. (uint80 roundID, int256 _price, , uint256 updatedAt, uint80 answeredInRound) = feed.latestRoundData(); require(answeredInRound >= roundID, "Stale price"); require(answer > 0," Error.NEGATIVE_PRICE"); require(block.timestamp <= updatedAt + stalePriceDelay, Error.STALE_PRICE); // 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:48:52Z
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
https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBERC20PaymentTerminal.sol#L81-L89 https://github.com/jbx-protocol/juice-contracts-v2-code4rena/blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBERC20PaymentTerminal.sol#L98-L100
JBERC20PaymentTerminal may not working with USDT due to following reasons
function _transferFrom( address _from, address payable _to, uint256 _amount ) internal override { _from == address(this) ? IERC20(token).transfer(_to, _amount) : IERC20(token).transferFrom(_from, _to, _amount); }
function _beforeTransferTo(address _to, uint256 _amount) internal override { IERC20(token).approve(_to, _amount); }
If some line of code leave some dust USDT fund (Allowance not become zero after transfer). Consequence transfers will be reverted as it cannot be approved due to not having zero allowance.
Manual
Use SafeERC20 library
using SafeERC20 for IERC20; ... function _transferFrom( address _from, address payable _to, uint256 _amount ) internal override { _from == address(this) ? IERC20(token).safeTransfer(_to, _amount) : IERC20(token).safeTransferFrom(_from, _to, _amount); } function _beforeTransferTo(address _to, uint256 _amount) internal override { IERC20(token).safeApprove(0, _amount); IERC20(token).safeApprove(_to, _amount); }
#0 - mejango
2022-07-12T18:55:39Z
dup #101
π 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
// Mint the project into the wallet of the message sender. projectId = projects.createFor(_owner, _projectMetadata);
Should be
// Mint the project into the wallet of the owner. projectId = projects.createFor(_owner, _projectMetadata);
π 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.2306 USDC - $38.23
This reduce gas cost as show here https://forum.openzeppelin.com/t/a-collection-of-gas-optimisation-tricks/19966/5
Caching the length in for loops:
for loop postcondition can be made unchecked Gas savings: roughly speaking this can save 30-40 gas per loop iteration. For lengthy loops, this can be significant!
for (uint256 _i; _i < _fundAccessConstraints.length; _i++) {
Can be optimized to
uint256 fundAccessConstraintsLength = _fundAccessConstraints.length; for (uint256 _i; _i < _fundAccessConstraintsLength; ) { ... unchecked { _i++; } }
for (uint256 _i = 0; _i < _splits.length; _i++) {
Can be optimized to
uint256 splitsLength = _splits.length; for (uint256 _i = 0; _i < _splitsLength; ) { ... unchecked { _i++; } }