Skip to content

Commit 5c01b1f

Browse files
committed
feat: check price conf width against price in PythPriceFeed
1 parent f8cf181 commit 5c01b1f

File tree

3 files changed

+37
-12
lines changed

3 files changed

+37
-12
lines changed

contracts/oracles/updatable/PythPriceFeed.sol

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {IncorrectPriceException} from "@gearbox-protocol/core-v3/contracts/inter
1212

1313
import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
1414
import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
15+
import {PERCENTAGE_FACTOR} from "@gearbox-protocol/core-v3/contracts/libraries/Constants.sol";
1516

1617
/// @dev Max period that the payload can be backward in time relative to the block
1718
uint256 constant MAX_DATA_TIMESTAMP_DELAY_SECONDS = 10 minutes;
@@ -28,13 +29,16 @@ interface IPythExtended {
2829
interface IPythPriceFeedExceptions {
2930
/// @notice Thrown when the timestamp sent with the payload for early stop does not match
3031
/// the payload's internal timestamp
31-
error IncorrectExpectedPublishTimestamp();
32+
error IncorrectExpectedPublishTimestampException();
3233

3334
/// @notice Thrown when a retrieved price's publish time is too far ahead in the future
34-
error PriceTimestampTooFarAhead();
35+
error PriceTimestampTooFarAheadException();
3536

3637
/// @notice Thrown when a retrieved price's publish time is too far behind the curent block timestamp
37-
error PriceTimestampTooFarBehind();
38+
error PriceTimestampTooFarBehindException();
39+
40+
/// @notice Thrown when the the ratio between the confidence interval and price is higher than max allowed
41+
error ConfToPriceRatioTooHighException();
3842
}
3943

4044
/// @title Pyth price feed
@@ -59,17 +63,31 @@ contract PythPriceFeed is IUpdatablePriceFeed, IPythPriceFeedExceptions {
5963
/// @dev Price feed description
6064
string public description;
6165

62-
constructor(address _token, bytes32 _priceFeedId, address _pyth, string memory _descriptionTicker) {
66+
/// @notice The max ratio of p.conf to p.price that would trigger the price feed to revert
67+
uint256 public immutable maxConfToPriceRatio;
68+
69+
constructor(
70+
address _token,
71+
bytes32 _priceFeedId,
72+
address _pyth,
73+
string memory _descriptionTicker,
74+
uint256 _maxConfToPriceRatio
75+
) {
6376
token = _token;
6477
priceFeedId = _priceFeedId;
6578
pyth = _pyth;
6679
description = string(abi.encodePacked(_descriptionTicker, " Pyth price feed"));
80+
maxConfToPriceRatio = _maxConfToPriceRatio;
6781
}
6882

6983
/// @notice Returns the USD price of the token with 8 decimals and the last update timestamp
7084
function latestRoundData() external view override returns (uint80, int256, uint256, uint256, uint80) {
7185
PythStructs.Price memory priceData = IPyth(pyth).getPriceUnsafe(priceFeedId);
7286

87+
if (uint256(priceData.conf) * PERCENTAGE_FACTOR > uint256(int256(priceData.price)) * maxConfToPriceRatio) {
88+
revert ConfToPriceRatioTooHighException();
89+
}
90+
7391
_validatePublishTimestamp(priceData.publishTime);
7492

7593
int256 price = _getDecimalAdjustedPrice(priceData);
@@ -98,7 +116,7 @@ contract PythPriceFeed is IUpdatablePriceFeed, IPythPriceFeedExceptions {
98116

99117
PythStructs.Price memory priceData = IPyth(pyth).getPriceUnsafe(priceFeedId);
100118

101-
if (priceData.publishTime != expectedPublishTimestamp) revert IncorrectExpectedPublishTimestamp();
119+
if (priceData.publishTime != expectedPublishTimestamp) revert IncorrectExpectedPublishTimestampException();
102120
if (priceData.price == 0) revert IncorrectPriceException();
103121
}
104122

@@ -121,10 +139,10 @@ contract PythPriceFeed is IUpdatablePriceFeed, IPythPriceFeedExceptions {
121139
function _validatePublishTimestamp(uint256 publishTimestamp) internal view {
122140
if ((block.timestamp < publishTimestamp)) {
123141
if ((publishTimestamp - block.timestamp) > MAX_DATA_TIMESTAMP_AHEAD_SECONDS) {
124-
revert PriceTimestampTooFarAhead();
142+
revert PriceTimestampTooFarAheadException();
125143
}
126144
} else if ((block.timestamp - publishTimestamp) > MAX_DATA_TIMESTAMP_DELAY_SECONDS) {
127-
revert PriceTimestampTooFarBehind();
145+
revert PriceTimestampTooFarBehindException();
128146
}
129147
}
130148

contracts/test/suites/PriceFeedDeployer.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ contract PriceFeedDeployer is Test, PriceFeedDataLive {
145145

146146
address pf = address(
147147
new PythPriceFeed(
148-
token, pythPriceFeedData.priceFeedId, pythPriceFeedData.pyth, pythPriceFeedData.ticker
148+
token, pythPriceFeedData.priceFeedId, pythPriceFeedData.pyth, pythPriceFeedData.ticker, 10000000
149149
)
150150
);
151151

contracts/test/unit/updatable/PythPriceFeed.unit.t.sol

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ contract PythPriceFeedUnitTest is TestHelper, IPythPriceFeedExceptions {
2929
pyth = new PythMock();
3030
token = address(new ERC20Mock("USD Coin", "USDC", 6));
3131

32-
pf = new PythPriceFeed(token, bytes32(uint256(1)), address(pyth), "USDC/USD");
32+
pf = new PythPriceFeed(token, bytes32(uint256(1)), address(pyth), "USDC/USD", 5000);
3333
vm.deal(address(pf), 100000);
3434
}
3535

@@ -69,14 +69,14 @@ contract PythPriceFeedUnitTest is TestHelper, IPythPriceFeedExceptions {
6969

7070
bytes memory updateData = abi.encode(block.timestamp + 64000, payloads);
7171

72-
vm.expectRevert(PriceTimestampTooFarAhead.selector);
72+
vm.expectRevert(PriceTimestampTooFarAheadException.selector);
7373
pf.updatePrice(updateData);
7474

7575
pyth.setPriceData(bytes32(uint256(1)), 10 ** 8, 0, -8, block.timestamp - 64001);
7676

7777
updateData = abi.encode(block.timestamp - 64000, payloads);
7878

79-
vm.expectRevert(PriceTimestampTooFarBehind.selector);
79+
vm.expectRevert(PriceTimestampTooFarBehindException.selector);
8080
pf.updatePrice(updateData);
8181
}
8282

@@ -98,7 +98,7 @@ contract PythPriceFeedUnitTest is TestHelper, IPythPriceFeedExceptions {
9898

9999
bytes memory updateData = abi.encode(block.timestamp, payloads);
100100

101-
vm.expectRevert(IncorrectExpectedPublishTimestamp.selector);
101+
vm.expectRevert(IncorrectExpectedPublishTimestampException.selector);
102102
pf.updatePrice(updateData);
103103
}
104104

@@ -132,4 +132,11 @@ contract PythPriceFeedUnitTest is TestHelper, IPythPriceFeedExceptions {
132132

133133
assertEq(price, 100 * 10 ** 8, "Incorrect price when pyth decimals are 0");
134134
}
135+
136+
function test_U_PYPF_08_latestRoundData_reverts_on_too_high_conf_to_price_ratio() public {
137+
pyth.setPriceData(bytes32(uint256(1)), 10 ** 8, 10000000000000000000, -8, block.timestamp - 64001);
138+
139+
vm.expectRevert(ConfToPriceRatioTooHighException.selector);
140+
pf.latestRoundData();
141+
}
135142
}

0 commit comments

Comments
 (0)