Skip to content

Commit 31e8d02

Browse files
authored
[eth] - optimize parse/updatePriceFeeds for gas & bytesize (#868)
* perf: optimize parse/updatePriceFeeds for gas & bytesize * chore: cleanup * refactor: renaming functions * test: add benchmark tests for parse with wh merkle, clean up duplicate code
1 parent 624222e commit 31e8d02

File tree

3 files changed

+177
-146
lines changed

3 files changed

+177
-146
lines changed

target_chains/ethereum/contracts/contracts/pyth/Pyth.sol

Lines changed: 67 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -438,53 +438,79 @@ abstract contract Pyth is
438438
UnsafeBytesLib.toUint32(updateData[i], 0) ==
439439
ACCUMULATOR_MAGIC
440440
) {
441-
(
442-
PythInternalStructs.PriceInfo[]
443-
memory accumulatorPriceInfos,
444-
bytes32[] memory accumulatorPriceIds
445-
) = extractPriceInfosFromAccumulatorUpdate(updateData[i]);
446-
447-
for (
448-
uint accDataIdx = 0;
449-
accDataIdx < accumulatorPriceIds.length;
450-
accDataIdx++
451-
) {
452-
bytes32 accumulatorPriceId = accumulatorPriceIds[
453-
accDataIdx
454-
];
455-
// check whether caller requested for this data
456-
uint k = findIndexOfPriceId(
457-
priceIds,
458-
accumulatorPriceId
441+
bytes memory accumulatorUpdate = updateData[i];
442+
uint offset;
443+
{
444+
UpdateType updateType;
445+
(
446+
offset,
447+
updateType
448+
) = extractUpdateTypeFromAccumulatorHeader(
449+
accumulatorUpdate
459450
);
460451

461-
// If priceFeed[k].id != 0 then it means that there was a valid
462-
// update for priceIds[k] and we don't need to process this one.
463-
if (k == priceIds.length || priceFeeds[k].id != 0) {
464-
continue;
452+
if (updateType != UpdateType.WormholeMerkle) {
453+
revert PythErrors.InvalidUpdateData();
465454
}
455+
}
456+
bytes20 digest;
457+
uint8 numUpdates;
458+
bytes memory encoded = UnsafeBytesLib.slice(
459+
accumulatorUpdate,
460+
offset,
461+
accumulatorUpdate.length - offset
462+
);
466463

467-
PythInternalStructs.PriceInfo
468-
memory info = accumulatorPriceInfos[accDataIdx];
464+
(
465+
offset,
466+
digest,
467+
numUpdates
468+
) = extractWormholeMerkleHeaderDigestAndNumUpdates(encoded);
469469

470-
uint publishTime = uint(info.publishTime);
471-
// Check the publish time of the price is within the given range
472-
// and only fill the priceFeedsInfo if it is.
473-
// If is not, default id value of 0 will still be set and
474-
// this will allow other updates for this price id to be processed.
475-
if (
476-
publishTime >= minPublishTime &&
477-
publishTime <= maxPublishTime
478-
) {
479-
fillPriceFeedFromPriceInfo(
480-
priceFeeds,
481-
k,
482-
accumulatorPriceId,
483-
info,
484-
publishTime
485-
);
470+
for (uint j = 0; j < numUpdates; j++) {
471+
PythInternalStructs.PriceInfo memory info;
472+
bytes32 priceId;
473+
474+
(
475+
offset,
476+
info,
477+
priceId
478+
) = extractPriceInfoFromMerkleProof(
479+
digest,
480+
encoded,
481+
offset
482+
);
483+
{
484+
// check whether caller requested for this data
485+
uint k = findIndexOfPriceId(priceIds, priceId);
486+
487+
// If priceFeed[k].id != 0 then it means that there was a valid
488+
// update for priceIds[k] and we don't need to process this one.
489+
if (k == priceIds.length || priceFeeds[k].id != 0) {
490+
continue;
491+
}
492+
493+
uint publishTime = uint(info.publishTime);
494+
// Check the publish time of the price is within the given range
495+
// and only fill the priceFeedsInfo if it is.
496+
// If is not, default id value of 0 will still be set and
497+
// this will allow other updates for this price id to be processed.
498+
if (
499+
publishTime >= minPublishTime &&
500+
publishTime <= maxPublishTime
501+
) {
502+
fillPriceFeedFromPriceInfo(
503+
priceFeeds,
504+
k,
505+
priceId,
506+
info,
507+
publishTime
508+
);
509+
}
486510
}
487511
}
512+
if (offset != encoded.length)
513+
revert PythErrors.InvalidUpdateData();
488514
} else {
489515
bytes memory encoded;
490516
{
@@ -573,8 +599,7 @@ abstract contract Pyth is
573599
bytes32 targetPriceId
574600
) private pure returns (uint index) {
575601
uint k = 0;
576-
uint len = priceIds.length;
577-
for (; k < len; k++) {
602+
for (; k < priceIds.length; k++) {
578603
if (priceIds[k] == targetPriceId) {
579604
break;
580605
}

target_chains/ethereum/contracts/contracts/pyth/PythAccumulator.sol

Lines changed: 22 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -42,33 +42,6 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
4242
revert PythErrors.InvalidUpdateDataSource();
4343
}
4444

45-
function extractPriceInfosFromAccumulatorUpdate(
46-
bytes memory accumulatorUpdate
47-
)
48-
internal
49-
view
50-
returns (
51-
PythInternalStructs.PriceInfo[] memory priceInfos,
52-
bytes32[] memory priceIds
53-
)
54-
{
55-
(
56-
uint offset,
57-
UpdateType updateType
58-
) = extractUpdateTypeFromAccumulatorHeader(accumulatorUpdate);
59-
60-
if (updateType != UpdateType.WormholeMerkle) {
61-
revert PythErrors.InvalidUpdateData();
62-
}
63-
(priceInfos, priceIds) = extractPriceInfosFromWormholeMerkle(
64-
UnsafeBytesLib.slice(
65-
accumulatorUpdate,
66-
offset,
67-
accumulatorUpdate.length - offset
68-
)
69-
);
70-
}
71-
7245
function extractUpdateTypeFromAccumulatorHeader(
7346
bytes memory accumulatorUpdate
7447
) internal pure returns (uint offset, UpdateType updateType) {
@@ -134,37 +107,6 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
134107
}
135108
}
136109

137-
function extractPriceInfosFromWormholeMerkle(
138-
bytes memory encoded
139-
)
140-
internal
141-
view
142-
returns (
143-
PythInternalStructs.PriceInfo[] memory priceInfos,
144-
bytes32[] memory priceIds
145-
)
146-
{
147-
unchecked {
148-
(
149-
uint offset,
150-
bytes20 digest,
151-
uint8 numUpdates
152-
) = extractWormholeMerkleHeaderDigestAndNumUpdates(encoded);
153-
154-
priceInfos = new PythInternalStructs.PriceInfo[](numUpdates);
155-
priceIds = new bytes32[](numUpdates);
156-
for (uint i = 0; i < numUpdates; i++) {
157-
(
158-
offset,
159-
priceInfos[i],
160-
priceIds[i]
161-
) = extractPriceFeedFromMerkleProof(digest, encoded, offset);
162-
}
163-
164-
if (offset != encoded.length) revert PythErrors.InvalidUpdateData();
165-
}
166-
}
167-
168110
function extractWormholeMerkleHeaderDigestAndNumUpdates(
169111
bytes memory encoded
170112
) internal view returns (uint offset, bytes20 digest, uint8 numUpdates) {
@@ -228,12 +170,12 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
228170
}
229171
}
230172

231-
function extractPriceFeedFromMerkleProof(
173+
function extractPriceInfoFromMerkleProof(
232174
bytes20 digest,
233175
bytes memory encoded,
234176
uint offset
235177
)
236-
private
178+
internal
237179
pure
238180
returns (
239181
uint endOffset,
@@ -249,7 +191,9 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
249191
digest
250192
);
251193

252-
(priceInfo, priceId) = extractPriceFeedMessage(encodedMessage);
194+
(priceInfo, priceId) = extractPriceInfoAndIdFromPriceFeedMessage(
195+
encodedMessage
196+
);
253197

254198
return (endOffset, priceInfo, priceId);
255199
}
@@ -284,15 +228,17 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
284228
}
285229
}
286230

287-
function extractPriceFeedMessage(
231+
function extractPriceInfoAndIdFromPriceFeedMessage(
288232
bytes memory encodedMessage
289233
)
290234
private
291235
pure
292236
returns (PythInternalStructs.PriceInfo memory info, bytes32 priceId)
293237
{
294238
unchecked {
295-
MessageType messageType = getMessageType(encodedMessage);
239+
MessageType messageType = MessageType(
240+
UnsafeBytesLib.toUint8(encodedMessage, 0)
241+
);
296242
if (messageType == MessageType.PriceFeed) {
297243
(info, priceId) = parsePriceFeedMessage(
298244
UnsafeBytesLib.slice(
@@ -307,12 +253,6 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
307253
}
308254
}
309255

310-
function getMessageType(
311-
bytes memory encodedMessage
312-
) private pure returns (MessageType messageType) {
313-
return MessageType(UnsafeBytesLib.toUint8(encodedMessage, 0));
314-
}
315-
316256
function parsePriceFeedMessage(
317257
bytes memory encodedPriceFeed
318258
)
@@ -402,47 +342,25 @@ abstract contract PythAccumulator is PythGetters, PythSetters, AbstractPyth {
402342
) = extractWormholeMerkleHeaderDigestAndNumUpdates(encoded);
403343

404344
for (uint i = 0; i < numUpdates; i++) {
405-
offset = verifyAndUpdatePriceFeedFromMerkleProof(
345+
PythInternalStructs.PriceInfo memory priceInfo;
346+
bytes32 priceId;
347+
(offset, priceInfo, priceId) = extractPriceInfoFromMerkleProof(
406348
digest,
407349
encoded,
408350
offset
409351
);
352+
uint64 latestPublishTime = latestPriceInfoPublishTime(priceId);
353+
if (priceInfo.publishTime > latestPublishTime) {
354+
setLatestPriceInfo(priceId, priceInfo);
355+
emit PriceFeedUpdate(
356+
priceId,
357+
priceInfo.publishTime,
358+
priceInfo.price,
359+
priceInfo.conf
360+
);
361+
}
410362
}
411-
412363
if (offset != encoded.length) revert PythErrors.InvalidUpdateData();
413364
}
414365
}
415-
416-
function verifyAndUpdatePriceFeedFromMerkleProof(
417-
bytes20 digest,
418-
bytes memory encoded,
419-
uint offset
420-
) private returns (uint endOffset) {
421-
PythInternalStructs.PriceInfo memory priceInfo;
422-
bytes32 priceId;
423-
(offset, priceInfo, priceId) = extractPriceFeedFromMerkleProof(
424-
digest,
425-
encoded,
426-
offset
427-
);
428-
processMessage(priceInfo, priceId);
429-
430-
return offset;
431-
}
432-
433-
function processMessage(
434-
PythInternalStructs.PriceInfo memory info,
435-
bytes32 priceId
436-
) private {
437-
uint64 latestPublishTime = latestPriceInfoPublishTime(priceId);
438-
if (info.publishTime > latestPublishTime) {
439-
setLatestPriceInfo(priceId, info);
440-
emit PriceFeedUpdate(
441-
priceId,
442-
info.publishTime,
443-
info.price,
444-
info.conf
445-
);
446-
}
447-
}
448366
}

0 commit comments

Comments
 (0)