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: 2/105
Findings: 7
Award: $11,055.17
π Selected for report: 2
π Solo Findings: 2
π 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
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 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
https://github.com/jbx-protocol/juice-contracts-v2-code4rena//blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBChainlinkV3PriceFeed.sol#L42-L51 https://github.com/jbx-protocol/juice-contracts-v2-code4rena//blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBPrices.sol#L69-L70 https://github.com/jbx-protocol/juice-contracts-v2-code4rena//blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBPrices.sol#L76-L77
None
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(answeredInRound >= roundID, "Stale price"); + require(timestamp != 0,"Round not complete"); + require(_price > 0,"Chainlink answer reporting 0");
#0 - mejango
2022-07-12T18:27:53Z
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
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.
None
Consider using safeTransfer/safeTransferFrom or require() consistently.
#0 - mejango
2022-07-12T18:28:46Z
dup #281
422.0095 USDC - $422.01
In the pay and addToBalanceOf functions of the JBPayoutRedemptionPaymentTerminal contract, if the token is a fee-on-transfer token, the actual number of tokens received by the contract will be less than _amount. This will make the amount recorded in the JBSingleTokenPaymentTerminalStore contract incorrect.
https://github.com/jbx-protocol/juice-contracts-v2-code4rena//blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L349-L350 https://github.com/jbx-protocol/juice-contracts-v2-code4rena//blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/abstract/JBPayoutRedemptionPaymentTerminal.sol#L555-L556
None
Consider getting the received amount by calculating the difference of token balance (using balanceOf) before and after the _transferFrom.
#0 - drgorillamd
2022-07-12T20:18:48Z
Duplicate of #304
1929.6275 USDC - $1,929.63
When the JBPayoutRedemptionPaymentTerminal contract distributes assets to _split, _transferFrom may be called to send tokens to _split.beneficiary. If the token is an ERC777 token, a malicious _split can revert the transaction in the tokensReceived function of _split.beneficiary, which prevents the contract from distributing assets.
None
Consider letting _split withdraw the distributed assets by itself
#0 - mejango
2022-07-12T19:25:27Z
project owners can bring their own risks and opportunity with split allocators. this is by design and noted here: https://info.juicebox.money/dev/learn/risks#setting-a-distribution-limit-and-payout-splits
#1 - jack-the-pug
2022-08-07T04:28:41Z
Duplicate of #229
π Selected for report: cccz
4288.0611 USDC - $4,288.06
When the owner calls the changeTokenOf function of the JBController contract, the token corresponding to the current project will be changed, which will make the oldToken holder unable to redeem the overflowing assets.
https://github.com/jbx-protocol/juice-contracts-v2-code4rena//blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBController.sol#L588-L606 https://github.com/jbx-protocol/juice-contracts-v2-code4rena//blob/828bf2f3e719873daa08081cfa0d0a6deaa5ace5/contracts/JBTokenStore.sol#L236-L269
None
Consider adding a delay to changeTokenOf, or adding a function to convert oldToken to newToken
π Selected for report: cccz
4288.0611 USDC - $4,288.06
The owner of the JBToken contract can mint arbitrary amount of tokens. In general, the owner of the JBToken contract is the JBTokenStore contract, and the minting of the tokens is controlled by the JBController contract, but when the changeTokenOf function of the JBController contract is called, the owner will be transferred to any address, which can mint arbitrary amount of tokens.
function mint( uint256 _projectId, address _account, uint256 _amount ) external override onlyOwner { _projectId; // Prevents unused var compiler and natspec complaints. return _mint(_account, _amount); }
None
Consider setting minter as the JBTokenStore contract and adding the onlyminter modifier to the mint function
π 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
109.1272 USDC - $109.13
The owner of the JBToken contract can burn anyone's tokens. In general, the owner of the JBToken contract is the JBTokenStore contract, and the burning of the tokens is controlled by the JBController contract, but when the changeTokenOf function of the JBController contract is called, the owner will be transferred to any address, which can burn anyone's tokens.
function burn( uint256 _projectId, address _account, uint256 _amount ) external override onlyOwner { _projectId; // Prevents unused var compiler and natspec complaints. return _burn(_account, _amount); }
None
Consider asking the user to approve the JBTokenStore contract to use his tokens before calling the JBController contract's burnTokensOf function, and only the token's owner and an approved address can burn his tokens
#0 - mejango
2022-07-12T17:19:58Z
acknowledged. will look to mitigate in documentation and future controllers.