Skip to content

Commit ec65255

Browse files
authored
fix(oracle-middleware): revert when the Pyth confidence is higher than the price (#918)
* fix(oracle-middleware): revert when the Pyth confidence is higher than the price * style: remove unused import in test * feat: revert on WusdnEth oracle * docs: ticker * perf: use solady abs() * docs: add comment on unchecked operations * docs: remove ticker name
1 parent 7c95b0b commit ec65255

File tree

5 files changed

+35
-23
lines changed

5 files changed

+35
-23
lines changed

src/OracleMiddleware/OracleMiddlewareWithPyth.sol

+10-10
Original file line numberDiff line numberDiff line change
@@ -147,19 +147,19 @@ contract OracleMiddlewareWithPyth is CommonOracleMiddleware, IOracleMiddlewareWi
147147
view
148148
returns (PriceInfo memory price_)
149149
{
150+
uint256 adjust = (pythPrice.conf * _confRatioBps) / BPS_DIVISOR;
151+
152+
if (adjust >= pythPrice.price) {
153+
revert OracleMiddlewareConfValueTooHigh();
154+
}
155+
150156
if (dir == PriceAdjustment.Down) {
151-
uint256 adjust = (pythPrice.conf * _confRatioBps) / BPS_DIVISOR;
152-
if (adjust >= pythPrice.price) {
153-
// avoid underflow or zero price due to confidence interval adjustment
154-
price_.price = 1;
155-
} else {
156-
// strictly positive
157-
unchecked {
158-
price_.price = pythPrice.price - adjust;
159-
}
157+
unchecked {
158+
// adjust is always less than pythPrice.price so we can safely subtract it
159+
price_.price = pythPrice.price - adjust;
160160
}
161161
} else if (dir == PriceAdjustment.Up) {
162-
price_.price = pythPrice.price + ((pythPrice.conf * _confRatioBps) / BPS_DIVISOR);
162+
price_.price = pythPrice.price + adjust;
163163
} else {
164164
price_.price = pythPrice.price;
165165
}

src/OracleMiddleware/WusdnToEthOracleMiddlewareWithDataStreams.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { CommonOracleMiddleware, OracleMiddlewareWithDataStreams } from "./Oracl
99

1010
/**
1111
* @title Middleware Implementation For Short ETH Protocol
12-
* @notice This contract is used to get the "inverse" price in ETH/WUSDN denomination, so that it can be used for a
12+
* @notice This contract is used to get the "inverse" price in number of ETH per WUSDN, so that it can be used for a
1313
* shorting version of the USDN protocol with WUSDN as the underlying asset.
1414
* @dev This version uses Pyth or Chainlink Data Streams for liquidations, and only Chainlink Data Streams for
1515
* validation actions.

src/OracleMiddleware/WusdnToEthOracleMiddlewareWithPyth.sol

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity 0.8.26;
33

4+
import { FixedPointMathLib } from "solady/src/utils/FixedPointMathLib.sol";
5+
46
import { IBaseOracleMiddleware } from "../interfaces/OracleMiddleware/IBaseOracleMiddleware.sol";
57
import { PriceInfo } from "../interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol";
68
import { IUsdn } from "../interfaces/Usdn/IUsdn.sol";
@@ -9,7 +11,7 @@ import { CommonOracleMiddleware, OracleMiddlewareWithPyth } from "./OracleMiddle
911

1012
/**
1113
* @title Middleware Implementation For Short ETH Protocol
12-
* @notice This contract is used to get the "inverse" price in ETH/WUSDN denomination, so that it can be used for a
14+
* @notice This contract is used to get the "inverse" price in number of ETH per WUSDN, so that it can be used for a
1315
* shorting version of the USDN protocol with WUSDN as the underlying asset.
1416
* @dev This version uses Pyth for low-latency prices used during validation actions and liquidations.
1517
*/
@@ -93,12 +95,19 @@ contract WusdnToEthOracleMiddlewareWithPyth is OracleMiddlewareWithPyth {
9395
// invert the sign of the confidence interval if necessary
9496
if (adjustmentDelta != 0) {
9597
uint256 adjustedPrice;
96-
if (adjustmentDelta >= int256(ethPrice.neutralPrice)) {
97-
// avoid underflow or zero price due to confidence interval adjustment
98-
adjustedPrice = 1;
98+
if (FixedPointMathLib.abs(adjustmentDelta) >= ethPrice.neutralPrice) {
99+
revert OracleMiddlewareConfValueTooHigh();
100+
}
101+
102+
if (adjustmentDelta > 0) {
103+
// adjustmentDelta is strictly smaller than neutralPrice so we can safely subtract it
104+
unchecked {
105+
adjustedPrice = ethPrice.neutralPrice - uint256(adjustmentDelta);
106+
}
99107
} else {
100-
adjustedPrice = uint256(int256(ethPrice.neutralPrice) - adjustmentDelta);
108+
adjustedPrice = ethPrice.neutralPrice + uint256(-adjustmentDelta);
101109
}
110+
102111
return PriceInfo({
103112
price: PRICE_NUMERATOR / (adjustedPrice * divisor),
104113
neutralPrice: PRICE_NUMERATOR / (ethPrice.neutralPrice * divisor),

src/interfaces/OracleMiddleware/IOracleMiddlewareErrors.sol

+3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ interface IOracleMiddlewareErrors {
4242
*/
4343
error OracleMiddlewarePythPositiveExponent(int32 expo);
4444

45+
/// @notice Indicates that the confidence value is higher than the price.
46+
error OracleMiddlewareConfValueTooHigh();
47+
4548
/// @notice Indicates that the confidence ratio is too high.
4649
error OracleMiddlewareConfRatioTooHigh();
4750

test/unit/Middlewares/Oracle/PythOracle.t.sol

+7-7
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ pragma solidity 0.8.26;
44
import { MOCK_PYTH_DATA } from "../utils/Constants.sol";
55
import { OracleMiddlewareBaseFixture } from "../utils/Fixtures.sol";
66

7-
import { PriceInfo } from "../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol";
87
import { IUsdnProtocolTypes as Types } from "../../../../src/interfaces/UsdnProtocol/IUsdnProtocolTypes.sol";
98

109
/// @custom:feature The `PythOracle` specific functions
@@ -18,17 +17,18 @@ contract TestOracleMiddlewarePythOracle is OracleMiddlewareBaseFixture {
1817
* @custom:given The price of the asset is $10 and the confidence interval is $30.
1918
* @custom:when The `parseAndValidatePrice` function is called with an action that uses the lower bound of the conf
2019
* interval.
21-
* @custom:then The price is adjusted to 1 wei to avoid being negative or zero.
20+
* @custom:then The function reverts with the error {OracleMiddlewareConfValueTooHigh}.
2221
*/
2322
function test_pythConfGreaterThanPrice() public {
23+
uint256 validationCost = oracleMiddleware.validationCost(MOCK_PYTH_DATA, Types.ProtocolAction.ValidateDeposit);
24+
2425
mockPyth.setPrice(10e8);
2526
mockPyth.setConf(30e8);
2627

27-
// ValidateDeposit adjusts down with conf
28-
PriceInfo memory price = oracleMiddleware.parseAndValidatePrice{
29-
value: oracleMiddleware.validationCost(MOCK_PYTH_DATA, Types.ProtocolAction.ValidateDeposit)
30-
}("", uint128(block.timestamp), Types.ProtocolAction.ValidateDeposit, MOCK_PYTH_DATA);
31-
assertEq(price.price, 1, "price should be 1");
28+
vm.expectRevert(OracleMiddlewareConfValueTooHigh.selector);
29+
oracleMiddleware.parseAndValidatePrice{ value: validationCost }(
30+
"", uint128(block.timestamp), Types.ProtocolAction.ValidateDeposit, MOCK_PYTH_DATA
31+
);
3232
}
3333

3434
/**

0 commit comments

Comments
 (0)