Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 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, SchedulerConstants {
(
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
80 changes: 36 additions & 44 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 @@ -317,47 +317,61 @@ abstract contract Pyth is
return merkleData.numUpdates;
}

function parsePriceFeedUpdatesInternal(
function parsePriceFeedUpdatesWithConfig(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
uint64 minAllowedPublishTime,
uint64 maxAllowedPublishTime,
bool checkUniqueness,
bool checkUpdateDataIsMinimal
bool checkUpdateDataIsMinimal,
bool storeUpdatesIfFresh
)
internal
public
payable
returns (
PythStructs.PriceFeed[] memory priceFeeds,
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
);
}

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
}));
}
}
}

// In minimal update data mode, revert if we have more or less updates than price IDs
Expand All @@ -369,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 @@ -378,6 +392,7 @@ abstract contract Pyth is
// Return results
return (context.priceFeeds, context.slots);
}

function parsePriceFeedUpdates(
bytes[] calldata updateData,
bytes32[] calldata priceIds,
Expand All @@ -389,41 +404,17 @@ abstract contract Pyth is
override
returns (PythStructs.PriceFeed[] memory priceFeeds)
{
(priceFeeds, ) = parsePriceFeedUpdatesInternal(
(priceFeeds, ) = parsePriceFeedUpdatesWithConfig(
updateData,
priceIds,
minPublishTime,
maxPublishTime,
false,
false,
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
parsePriceFeedUpdatesInternal(
updateData,
priceIds,
minPublishTime,
maxPublishTime,
false,
true
);
}

function extractTwapPriceInfos(
bytes calldata updateData
)
Expand Down Expand Up @@ -624,12 +615,13 @@ abstract contract Pyth is
override
returns (PythStructs.PriceFeed[] memory priceFeeds)
{
(priceFeeds, ) = parsePriceFeedUpdatesInternal(
(priceFeeds, ) = parsePriceFeedUpdatesWithConfig(
updateData,
priceIds,
minPublishTime,
maxPublishTime,
true,
false,
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
Loading
Loading