Omniscia Bonq Audit
price-feed Manual Review Findings
price-feed Manual Review Findings
PRI-01M: Inexplicable Price Measurement Methodology
Type | Severity | Location |
---|---|---|
Mathematical Operations | price-feed.sol:L144-L150 |
Description:
The price measurement utilized here appears incorrect as it will attempt to calculate the delta between the current period and the period located 14
slots before.
Example:
144/// @notice this will always return 0 before update has been called successfully for the first time.145function price() external view override returns (uint256) {146 uint256 startSnapshotIndex = shift + 1 == PRICE_AVERAGE_PERIOD ? 0 : shift + 1;147 return148 (price0CumulativeLast - snapshots[startSnapshotIndex].price0Cumulative) /149 (blockTimestampLast - snapshots[startSnapshotIndex].blockTimestamp);150}
Recommendation:
We advise this calculation method to be revised or explained via in-line documentation as it does not appear rational in its current state.
Alleviation:
The Bonq Protocol team has stated that they will not proceed with the price feed implementations that they currently have in place after the findings we have identified within the report. The price feeds they will instead utilize will be Chainlink and as such, we consider these exhibits nullified.
PRI-02M: Incorrect Price Measurements
Type | Severity | Location |
---|---|---|
Logical Fault | price-feed.sol:L127, L128 |
Description:
The referenced price measurements factor into account the fees imposed by the Uniswap system whilst the price feed should not do so.
Impact:
Prices reported by the price-feed will be artificially lower than the actual price of the assets thus opening up a constant arbitrage opportunity for passive holders of both assets.
Example:
117function _getUpdatePrices(uint256 timeElapsed)118 private119 view120 returns (121 uint256 price0,122 uint256 price1,123 uint256 price0Cumulative,124 uint256 price1Cumulative125 )126{127 price0 = router.getAmountOut(DECIMAL_PRECISION, collateralToken, stableCoin);128 price1 = router.getAmountOut(DECIMAL_PRECISION, stableCoin, collateralToken);129
130 // ensure that at least one full period has passed since the last update131 // require(timeElapsed >= UPDATE_PERIOD, "PriceFeed: update: update period not elapsed");132
133 price0Cumulative = price0CumulativeLast + (price0 * timeElapsed);134 price1Cumulative = price1CumulativeLast + (price1 * timeElapsed);135}
Recommendation:
We advise the calculations to be performed directly, assuming a Uniswap-V2 like Router
system the reserve values can be utilized directly in fractional format.
Alleviation:
The Bonq Protocol team has stated that they will not proceed with the price feed implementations that they currently have in place after the findings we have identified within the report. The price feeds they will instead utilize will be Chainlink and as such, we consider these exhibits nullified.
PRI-03M: Selective Storage of Price Feed Measurements
Type | Severity | Location |
---|---|---|
Centralization Concern | price-feed.sol:L67 |
Description:
The update
function allows the owner to selectively store price measurements from the Time-Weighted Price Average (TWAP) calculator, a trait that is highly undesirable for a decentralized system.
Impact:
As the snapshots
are not always stored, it is possible for the owner of the contract to skew the system towards a price point they desire to favour any loans they may have procured themselves.
Example:
58function update(bool savePrevious) external override onlyOwner {59 uint256 blockTimestamp = block.timestamp;60 uint256 timeElapsed = blockTimestamp - blockTimestampLast;61 bool isFirstUpdate = shift == 0 && snapshots[shift + 1].blockTimestamp == 0;62
63 (uint256 price0, uint256 price1, uint256 price0Cumulative, uint256 price1Cumulative) = _getUpdatePrices(64 timeElapsed65 );66
67 if (savePrevious && !isFirstUpdate) {68 shift = shift + 1 == PRICE_AVERAGE_PERIOD ? 0 : shift + 1;69
70 price0Average = price0AverageTemp;71 price1Average = price1AverageTemp;72
73 blockTimestampLast = blockTimestampLastTemp;74 price0CumulativeLast = price0CumulativeLastTemp;75 price1CumulativeLast = price1CumulativeLastTemp;76
77 snapshots[shift].blockTimestamp = blockTimestampLastTemp;78 snapshots[shift].price0Cumulative = price0CumulativeLastTemp;79 snapshots[shift].price1Cumulative = price1CumulativeLastTemp;80 }81
82 price0AverageTemp = (price0Cumulative - price0CumulativeLast) / timeElapsed;83 price1AverageTemp = (price1Cumulative - price1CumulativeLast) / timeElapsed;84
85 blockTimestampLastTemp = blockTimestamp;86 price0CumulativeLastTemp = price0Cumulative;87 price1CumulativeLastTemp = price1Cumulative;88
89 // filling price points with hipotetical values90 if (isFirstUpdate) {91 uint256 timeStep = 100;92 uint256 emittedBlockTimestamp = blockTimestamp - (timeStep * (PRICE_AVERAGE_PERIOD));93 for (uint256 i = 1; i < snapshots.length; i++) {94 snapshots[i].blockTimestamp = emittedBlockTimestamp;95 snapshots[i].price0Cumulative = i == 196 ? price0Cumulative97 : snapshots[i - 1].price0Cumulative + (price0 * timeStep);98 snapshots[i].price1Cumulative = i == 199 ? price1Cumulative100 : snapshots[i - 1].price1Cumulative + (price1 * timeStep);101 emittedBlockTimestamp += timeStep;102 }103 price0Cumulative = snapshots[PRICE_AVERAGE_PERIOD - 1].price0Cumulative + (price0 * timeStep);104 price1Cumulative = snapshots[PRICE_AVERAGE_PERIOD - 1].price1Cumulative + (price1 * timeStep);105
106 snapshots[shift].price0Cumulative = price0Cumulative;107 snapshots[shift].price1Cumulative = price1Cumulative;108 snapshots[shift].blockTimestamp = blockTimestamp - timeStep;109 price0CumulativeLast = price0Cumulative;110 price1CumulativeLast = price1Cumulative;111 price0CumulativeLastTemp = price0Cumulative + (price0 * timeStep);112 price1CumulativeLastTemp = price1Cumulative + (price1 * timeStep);113 blockTimestampLast = blockTimestamp - timeStep;114 }115}
Recommendation:
We advise all periods to be properly stored as otherwise the system is completely susceptible to artificial price fluctuations.
Alleviation:
The Bonq Protocol team has stated that they will not proceed with the price feed implementations that they currently have in place after the findings we have identified within the report. The price feeds they will instead utilize will be Chainlink and as such, we consider these exhibits nullified.
PRI-04M: Incorrect Order of Snapshot Update
Type | Severity | Location |
---|---|---|
Logical Fault | price-feed.sol:L67-L80 |
Description:
The snapshot update system is in incorrect order as the timeElapsed
calculated at the beginning of the function is incorrect in case the snapshot is stored as the blockTimestamp - blockTimestampLast
delta would be different after the snapshot storage logic block.
Impact:
Incorrect price measurements would yield a price that is weighted across a significantly bigger time period than expected.
Example:
58function update(bool savePrevious) external override onlyOwner {59 uint256 blockTimestamp = block.timestamp;60 uint256 timeElapsed = blockTimestamp - blockTimestampLast;61 bool isFirstUpdate = shift == 0 && snapshots[shift + 1].blockTimestamp == 0;62
63 (uint256 price0, uint256 price1, uint256 price0Cumulative, uint256 price1Cumulative) = _getUpdatePrices(64 timeElapsed65 );66
67 if (savePrevious && !isFirstUpdate) {68 shift = shift + 1 == PRICE_AVERAGE_PERIOD ? 0 : shift + 1;69
70 price0Average = price0AverageTemp;71 price1Average = price1AverageTemp;72
73 blockTimestampLast = blockTimestampLastTemp;74 price0CumulativeLast = price0CumulativeLastTemp;75 price1CumulativeLast = price1CumulativeLastTemp;76
77 snapshots[shift].blockTimestamp = blockTimestampLastTemp;78 snapshots[shift].price0Cumulative = price0CumulativeLastTemp;79 snapshots[shift].price1Cumulative = price1CumulativeLastTemp;80 }
Recommendation:
We advise the snapshot logic to be relocated at the very top of the function to ensure the calculations within price the correct time period instead of using the old time measurement.
Alleviation:
The Bonq Protocol team has stated that they will not proceed with the price feed implementations that they currently have in place after the findings we have identified within the report. The price feeds they will instead utilize will be Chainlink and as such, we consider these exhibits nullified.
PRI-05M: Unsafe Initialization Methodology
Type | Severity | Location |
---|---|---|
Logical Fault | price-feed.sol:L90-L114 |
Description:
The initialization methodology of the update
function will fill in all 15
values of the snapshots
array with hypothetical values which is incorrect, especially for a sensitive component such as a price feed.
Impact:
The initial price points reported by the price feed cannot be deemed as safe to use for any purpose.
Example:
89// filling price points with hipotetical values90if (isFirstUpdate) {91 uint256 timeStep = 100;92 uint256 emittedBlockTimestamp = blockTimestamp - (timeStep * (PRICE_AVERAGE_PERIOD));93 for (uint256 i = 1; i < snapshots.length; i++) {94 snapshots[i].blockTimestamp = emittedBlockTimestamp;95 snapshots[i].price0Cumulative = i == 196 ? price0Cumulative97 : snapshots[i - 1].price0Cumulative + (price0 * timeStep);98 snapshots[i].price1Cumulative = i == 199 ? price1Cumulative100 : snapshots[i - 1].price1Cumulative + (price1 * timeStep);101 emittedBlockTimestamp += timeStep;102 }103 price0Cumulative = snapshots[PRICE_AVERAGE_PERIOD - 1].price0Cumulative + (price0 * timeStep);104 price1Cumulative = snapshots[PRICE_AVERAGE_PERIOD - 1].price1Cumulative + (price1 * timeStep);105
106 snapshots[shift].price0Cumulative = price0Cumulative;107 snapshots[shift].price1Cumulative = price1Cumulative;108 snapshots[shift].blockTimestamp = blockTimestamp - timeStep;109 price0CumulativeLast = price0Cumulative;110 price1CumulativeLast = price1Cumulative;111 price0CumulativeLastTemp = price0Cumulative + (price0 * timeStep);112 price1CumulativeLastTemp = price1Cumulative + (price1 * timeStep);113 blockTimestampLast = blockTimestamp - timeStep;114}
Recommendation:
We advise all 15
measurements to be carried out properly before the price feed is enabled for use as otherwise it can be susceptible to incorrect price measurements.
Alleviation:
The Bonq Protocol team has stated that they will not proceed with the price feed implementations that they currently have in place after the findings we have identified within the report. The price feeds they will instead utilize will be Chainlink and as such, we consider these exhibits nullified.
PRI-06M: Unsafe Price Feed Implementation
Type | Severity | Location |
---|---|---|
Language Specific | price-feed.sol:L21-L25, L31-L35, L37-L41 |
Description:
The price feed implementation that the Bonq system utilizes is unsafe as TWAP mechanisms will be insecure following the Ethereum staking hard-fork as it will be trivial to affect the price of a pair multiple blocks in advance.
Impact:
Any TWAP implementation in post-staking-merge Ethereum will be ill-advised and susceptible to multi-block price manipulation attacks unless a significantly large TWAP period is selected in which case the TWAP would become unusable.
Example:
21struct CumulativePriceSnapshot {22 uint256 blockTimestamp;23 uint256 price0Cumulative;24 uint256 price1Cumulative;25}26
27CumulativePriceSnapshot[PRICE_AVERAGE_PERIOD] public snapshots;28// array index that always points to latest added snapshot29uint256 public shift;30
31uint256 public price0CumulativeLast;32uint256 public price1CumulativeLast;33uint256 public blockTimestampLast;34uint256 public price0Average;35uint256 public price1Average;36
37uint256 public price0CumulativeLastTemp;38uint256 public price1CumulativeLastTemp;39uint256 public blockTimestampLastTemp;40uint256 public price0AverageTemp;41uint256 public price1AverageTemp;
Recommendation:
We advise the TWAP system to be re-evaluated in light of this and external price feeds to be used by the borrowing system instead such as Chainlink oracles.
Alleviation:
The Bonq Protocol team has stated that they will not proceed with the price feed implementations that they currently have in place after the findings we have identified within the report. The price feeds they will instead utilize will be Chainlink and as such, we consider these exhibits nullified.