Platform: Code4rena
Start Date: 19/04/2022
Pot Size: $30,000 USDC
Total HM: 10
Participants: 43
Period: 3 days
Judges: moose-code, JasoonS
Total Solo HM: 7
Id: 90
League: ETH
Rank: 14/43
Findings: 3
Award: $130.12
🌟 Selected for report: 0
🚀 Solo Findings: 0
22.0499 USDC - $22.05
https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ChainlinkPriceOracle.sol#L83-L84
The data returned by the Chainlink latestRoundData()
function may be stale. There should be checks applied on the data received from Chainlink to validate that it is not stale.
https://docs.chain.link/docs/faq/#how-can-i-check-if-the-answer-to-a-round-is-being-carried-over-from-a-previous-round
The ChainlinkPriceOracle contract has these two lines https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ChainlinkPriceOracle.sol#L83-L84
(, int basePrice, , , ) = baseAggregator.latestRoundData(); (, int quotePrice, , , ) = assetInfo.aggregator.latestRoundData();
The Chainlink oracle data is not validated properly. There is no check for stale price and round completeness. The price returned can be stale and can lead to inaccurate return values.
Manual analysis
Validate the round and timestamp returned by the oracle data
(uint80 roundID, int basePrice, , uint256 timestamp, uint80 answeredInRound) = baseAggregator.latestRoundData(); require(basePrice > 0, "price is 0"); require(answeredInRound >= roundID, "stale price"); require(timestamp > 0, "incomplete round"); (uint80 roundID, int quotePrice, , uint256 timestamp, uint80 answeredInRound) = assetInfo.aggregator.latestRoundData(); require(quotePrice > 0, "price is 0"); require(answeredInRound >= roundID, "stale price"); require(timestamp > 0, "incomplete round");
#0 - olivermehr
2022-05-02T20:04:54Z
Duplicate of #1
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0xDjango, 0xkatana, Dravee, Kenshin, Tadashi, TerrierLover, abhinavmir, defsec, ellahi, fatima_naz, foobar, gzeon, hyh, joestakey, kebabsec, kenta, minhquanym, oyc_109, rayn, robee, sseefried, xpriment626, z3s
69.53 USDC - $69.53
The factory state variable is never set in ManagedIndex and TopNMarketCapIndex. This will cause the initialize function to be uncalled unless the zero address calls the function, which is not possible for the Phuture Finance team to do.
These line checks if the caller of the initialize function is the factory address https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ManagedIndex.sol#L28 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/TopNMarketCapIndex.sol#L45 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/TrackedIndex.sol#L30
But the factory address is never set before it is referenced, so it will have a value of address(0). The factory address state variable is imported from the BaseIndex contract, but the value of this state variable is not borrowed from importing the contract.
Manual analysis
Set the value of the factory state variable in the constructor of ManagedIndex
The ManagedIndex initializer function does not have an initializer modifier. This could allow the function to be called more than once y the factory contract, unexpectedly changing important state variable values.
These initialize functions should have the initializer modifier, like the other initialize functions in the project have https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ManagedIndex.sol#L27 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/TopNMarketCapIndex.sol#L37 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/TrackedIndex.sol#L25
Manual analysis
Add the initializer modifier to all initialize functions
#0 - moose-code
2022-05-23T11:41:38Z
#1 - moose-code
2022-05-23T11:45:37Z
2- Hard to tell because I can't see factory source code, only IIndexFactory.sol - I imagine its possibly intended behavior that the factory can reinitialize things if needed? If the factory is upgradeable it can be an issue. Worth flagging.
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0xDjango, 0xNazgul, 0xkatana, Dravee, Kenshin, MaratCerby, Tadashi, TerrierLover, Tomio, TrungOre, defsec, ellahi, fatherOfBlocks, fatima_naz, gzeon, joestakey, kenta, minhquanym, oyc_109, rayn, rfa, robee, simon135, slywaters, windhustler, z3s
38.5445 USDC - $38.54
Combining require statement conditions with && logic uses unnecessary gas. It is better to split up each part of the logical statement into separate require statements
Several instances of this issue was found https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ChainlinkPriceOracle.sol#L51 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ChainlinkPriceOracle.sol#L86 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ManagedIndexReweightingLogic.sol#L30-L31 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/UniswapV2PriceOracle.sol#L46
Manual analysis
Use separate require statements instead of concatenating with &&
Using a prefix increment (++i) instead of a postfix increment (i++) saves gas for each loop cycle and so can have a big gas impact when the loop executes on a large number of elements.
There are three examples of this https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/UniswapV2PathPriceOracle.sol#L34 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/UniswapV2PathPriceOracle.sol#L49
Manual analysis
Use prefix not postfix to increment in a loop
Strings in solidity are handled in 32 byte chunks. A require string longer than 32 bytes uses more gas. Shortening these strings will save gas.
Several cases of this gas optimization were found. These are a few examples, but more may exist
Manual analysis
Shorten all require strings to less than 32 characters
Using > 0
uses slightly more gas than using != 0
. Use != 0
when comparing uint variables to zero, which cannot hold values below zero
Locations where this was found include https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ManagedIndexReweightingLogic.sol#L56 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ManagedIndexReweightingLogic.sol#L61 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ManagedIndexReweightingLogic.sol#L98 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L76 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L86 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L98 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L114 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L141 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/PhutureIndex.sol#L56 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/PhutureIndex.sol#L64
grep
Replace > 0
with != 0
to save gas
Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop. This saves gas.
This optimization is already used in some places, but is not used in these places https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L39 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L60 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/IndexLogic.sol#L125 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/ManagedIndexReweightingLogic.sol#L38 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/TopNMarketCapReweightingLogic.sol#L37 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/TrackedIndexReweightingLogic.sol#L37 https://github.com/code-423n4/2022-04-phuture/blob/main/contracts/TrackedIndexReweightingLogic.sol#L66
Manual analysis
Cache the array length before the for loop
#0 - jn-lp
2022-05-03T15:43:19Z
Tips 2-5 were useful, thanks