Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,14 @@ abstract contract Scheduler is IScheduler, SchedulerState {
(
PythStructs.PriceFeed[] memory priceFeeds,
uint64[] memory slots
) = pyth.parsePriceFeedUpdatesWithSlotsStrict{value: pythFee}(
) = pyth.parsePriceFeedUpdatesWithConfig{value: pythFee}(
updateData,
params.priceIds,
0, // We enforce the past max validity ourselves in _validateShouldUpdatePrices
curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD
curTime + FUTURE_TIMESTAMP_MAX_VALIDITY_PERIOD,
false,
true,
false
);

// Verify all price feeds have the same Pythnet slot.
Expand Down
73 changes: 28 additions & 45 deletions target_chains/ethereum/contracts/contracts/pyth/Pyth.sol
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's bump the version() to 1.4.5-alpha.1

Original file line number Diff line number Diff line change
Expand Up @@ -333,35 +333,43 @@ abstract contract Pyth is
uint64[] memory slots
)
{
{
uint requiredFee = getUpdateFee(updateData);
if (msg.value < requiredFee) revert PythErrors.InsufficientFee();
}
if (msg.value < getUpdateFee(updateData)) revert PythErrors.InsufficientFee();

// Create the context struct that holds all shared parameters
PythInternalStructs.UpdateParseContext memory context;
context.priceIds = priceIds;
context.minAllowedPublishTime = minAllowedPublishTime;
context.maxAllowedPublishTime = maxAllowedPublishTime;
context.checkUniqueness = checkUniqueness;
context.checkUpdateDataIsMinimal = checkUpdateDataIsMinimal;
context.priceFeeds = new PythStructs.PriceFeed[](priceIds.length);
context.slots = new uint64[](priceIds.length);
PythInternalStructs.UpdateParseContext memory context = PythInternalStructs.UpdateParseContext({
priceIds: priceIds,
minAllowedPublishTime: minAllowedPublishTime,
maxAllowedPublishTime: maxAllowedPublishTime,
checkUniqueness: checkUniqueness,
checkUpdateDataIsMinimal: checkUpdateDataIsMinimal,
priceFeeds: new PythStructs.PriceFeed[](priceIds.length),
slots: new uint64[](priceIds.length)
});

// Track total updates for minimal update data check
uint64 totalUpdatesAcrossBlobs = 0;

unchecked {
// Process each update, passing the context struct
// Parsed results will be filled in context.priceFeeds and context.slots
for (uint i = 0; i < updateData.length; i++) {
for (uint i = 0; i < updateData.length; ++i) {
totalUpdatesAcrossBlobs += _processSingleUpdateDataBlob(
updateData[i],
context
);
if (storeUpdatesIfFresh) {
bytes32 curPriceId = context.priceIds[i];
updateLatestPriceIfNecessary(curPriceId, _state.latestPriceInfo[curPriceId]);
}

for (uint j = 0; j < priceIds.length; ++j) {
PythStructs.PriceFeed memory pf = context.priceFeeds[j];
if (storeUpdatesIfFresh && pf.id != 0) {
updateLatestPriceIfNecessary(priceIds[j], PythInternalStructs.PriceInfo({
publishTime: uint64(pf.price.publishTime),
expo: pf.price.expo,
price: pf.price.price,
conf: pf.price.conf,
emaPrice: pf.emaPrice.price,
emaConf: pf.emaPrice.conf
}));
}
}
}
Expand All @@ -375,7 +383,7 @@ abstract contract Pyth is
}

// Check all price feeds were found
for (uint k = 0; k < priceIds.length; k++) {
for (uint k = 0; k < priceIds.length; ++k) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how is preincrement different here?

if (context.priceFeeds[k].id == 0) {
revert PythErrors.PriceFeedNotFoundWithinRange();
}
Expand All @@ -384,6 +392,7 @@ abstract contract Pyth is
// Return results
return (context.priceFeeds, context.slots);
}

function parsePriceFeedUpdates(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
Expand All @@ -402,36 +411,10 @@ abstract contract Pyth is
maxPublishTime,
false,
false,
true
false
);
}

function parsePriceFeedUpdatesWithSlotsStrict(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64 minPublishTime,
uint64 maxPublishTime
)
external
payable
override
returns (
PythStructs.PriceFeed[] memory priceFeeds,
uint64[] memory slots
)
{
return
parsePriceFeedUpdatesWithConfig(
updateData,
priceIds,
minPublishTime,
maxPublishTime,
false,
true,
true
);
}

function extractTwapPriceInfos(
bytes calldata updateData
)
Expand Down Expand Up @@ -639,7 +622,7 @@ abstract contract Pyth is
maxPublishTime,
true,
false,
true
false
);
}

Expand Down
30 changes: 30 additions & 0 deletions target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,36 @@ contract GasBenchmark is Test, WormholeTestUtils, PythTestUtils {
);
}

function testBenchmarkParsePriceFeedUpdatesWithConfigTrue() public {
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];

pyth.parsePriceFeedUpdatesWithConfig{value: freshPricesUpdateFee[0]}(
freshPricesUpdateData[0],
ids,
0,
50,
false,
true, // check minimal
false
);
}

function testBenchmarkParsePriceFeedUpdatesWithConfigFalse() public {
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];

pyth.parsePriceFeedUpdatesWithConfig{value: freshPricesUpdateFee[0]}(
freshPricesUpdateData[0], // contains only priceIds[0]
ids,
0,
50,
false,
true, // check minimal
false
);
}

function testBenchmarkParsePriceFeedUpdatesUniqueFor() public {
bytes32[] memory ids = new bytes32[](1);
ids[0] = priceIds[0];
Expand Down
89 changes: 83 additions & 6 deletions target_chains/ethereum/contracts/forge-test/Pyth.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,74 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
}
}

function testParsePriceFeedUpdatesWithConfigIfStorageTrue(uint seed) public {
setRandSeed(seed);
uint numMessages = 1 + (getRandUint() % 10);
(
bytes32[] memory priceIds,
PriceFeedMessage[] memory messages
) = generateRandomPriceMessages(numMessages);

(
bytes[] memory updateData,
uint updateFee
) = createBatchedUpdateDataFromMessages(messages);

(PythStructs.PriceFeed[] memory priceFeeds,) = pyth.parsePriceFeedUpdatesWithConfig{
value: updateFee
}(updateData, priceIds, 0, MAX_UINT64, false, true, true);

for (uint i = 0; i < numMessages; i++) {
// Validating that returned priceIds are equal
assertEq(priceFeeds[i].id, priceIds[i]);
assertEq(priceFeeds[i].price.price, messages[i].price);
assertEq(priceFeeds[i].price.conf, messages[i].conf);
assertEq(priceFeeds[i].price.expo, messages[i].expo);
assertEq(priceFeeds[i].price.publishTime, messages[i].publishTime);
assertEq(priceFeeds[i].emaPrice.price, messages[i].emaPrice);
assertEq(priceFeeds[i].emaPrice.conf, messages[i].emaConf);
assertEq(priceFeeds[i].emaPrice.expo, messages[i].expo);
assertEq(
priceFeeds[i].emaPrice.publishTime,
messages[i].publishTime
);

// Validating that prices are stored on chain
PythStructs.Price memory curPrice = pyth.getPriceUnsafe(
messages[i].priceId
);

assertEq(priceFeeds[i].price.price, curPrice.price);
assertEq(priceFeeds[i].price.conf, curPrice.conf);
assertEq(priceFeeds[i].price.expo, curPrice.expo);
assertEq(priceFeeds[i].price.publishTime, curPrice.publishTime);
}
}

function testParsePriceFeedUpdatesWithConfigIfStorageFalse(uint seed) public {
setRandSeed(seed);
uint numMessages = 1 + (getRandUint() % 10);
(
bytes32[] memory priceIds,
PriceFeedMessage[] memory messages
) = generateRandomPriceMessages(numMessages);

(
bytes[] memory updateData,
uint updateFee
) = createBatchedUpdateDataFromMessages(messages);

pyth.parsePriceFeedUpdatesWithConfig{
value: updateFee
}(updateData, priceIds, 0, MAX_UINT64, false, true, false);

// validate that stored prices of each priceId are still unpopulated
for (uint i = 0; i < numMessages; i++) {
vm.expectRevert(PythErrors.PriceFeedNotFound.selector);
pyth.getPriceUnsafe(priceIds[i]);
}
}

function testParsePriceFeedUpdatesWithSlotsStrictWorks(uint seed) public {
setRandSeed(seed);
uint numMessages = 1 + (getRandUint() % 10);
Expand All @@ -293,11 +361,14 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
(
PythStructs.PriceFeed[] memory priceFeeds,
uint64[] memory slots
) = pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}(
) = pyth.parsePriceFeedUpdatesWithConfig{value: updateFee}(
updateData,
priceIds,
0,
MAX_UINT64
MAX_UINT64,
false,
true,
false
);

assertEq(priceFeeds.length, numMessages);
Expand Down Expand Up @@ -459,11 +530,14 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {

// Should revert in strict mode
vm.expectRevert(PythErrors.InvalidArgument.selector);
pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}(
pyth.parsePriceFeedUpdatesWithConfig{value: updateFee}(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we update the names of these tests please

updateData,
requestedPriceIds,
0,
MAX_UINT64
MAX_UINT64,
false,
true,
false
);
}

Expand Down Expand Up @@ -496,11 +570,14 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {

// Should revert in strict mode because we have fewer updates than price IDs
vm.expectRevert(PythErrors.InvalidArgument.selector);
pyth.parsePriceFeedUpdatesWithSlotsStrict{value: updateFee}(
pyth.parsePriceFeedUpdatesWithConfig{value: updateFee}(
updateData,
requestedPriceIds,
0,
MAX_UINT64
MAX_UINT64,
false,
true,
false
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ abstract contract MockPriceFeedTestUtils is Test {
pyth,
expectedFee,
abi.encodeWithSelector(
IPyth.parsePriceFeedUpdatesWithSlotsStrict.selector
IPyth.parsePriceFeedUpdatesWithConfig.selector
),
abi.encode(priceFeeds, slots)
);
Expand Down
15 changes: 0 additions & 15 deletions target_chains/ethereum/sdk/solidity/AbstractPyth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,6 @@ abstract contract AbstractPyth is IPyth {
override
returns (PythStructs.PriceFeed[] memory priceFeeds);

function parsePriceFeedUpdatesWithSlotsStrict(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we squeeze within the size limit with this? :)

bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64 minPublishTime,
uint64 maxPublishTime
)
external
payable
virtual
override
returns (
PythStructs.PriceFeed[] memory priceFeeds,
uint64[] memory slots
);

function parseTwapPriceFeedUpdates(
bytes[] calldata updateData,
bytes32[] calldata priceIds
Expand Down
20 changes: 0 additions & 20 deletions target_chains/ethereum/sdk/solidity/IPyth.sol
Original file line number Diff line number Diff line change
Expand Up @@ -202,24 +202,4 @@ interface IPyth is IPythEvents {
uint64 minPublishTime,
uint64 maxPublishTime
) external payable returns (PythStructs.PriceFeed[] memory priceFeeds);

/// @dev Same as `parsePriceFeedUpdates`, but checks that the updateData is minimal and also returns the Pythnet slots.
/// @param updateData Array of price update data.
/// @param priceIds Array of price ids.
/// @param minPublishTime minimum acceptable publishTime for the given `priceIds`.
/// @param maxPublishTime maximum acceptable publishTime for the given `priceIds`.
/// @return priceFeeds Array of the price feeds corresponding to the given `priceIds` (with the same order).
/// @return slots Array of the Pythnet slot corresponding to the given `priceIds` (with the same order).
function parsePriceFeedUpdatesWithSlotsStrict(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64 minPublishTime,
uint64 maxPublishTime
)
external
payable
returns (
PythStructs.PriceFeed[] memory priceFeeds,
uint64[] memory slots
);
}
Loading
Loading