- 
                Notifications
    
You must be signed in to change notification settings  - Fork 306
 
refactor: Optional storage flag when parsing price feed updates #2727
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
4fe1222
              552a34f
              b3cf5d3
              2db25fc
              a1822f8
              2a5b2ec
              6b4b0c8
              2e79358
              f1183f1
              53690d2
              0636cb1
              f604a13
              660717e
              27bf014
              02fe840
              File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -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 | ||
| 
        
          
        
         | 
    @@ -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) { | ||
                
       | 
||
| if (context.priceFeeds[k].id == 0) { | ||
| revert PythErrors.PriceFeedNotFoundWithinRange(); | ||
| } | ||
| 
        
          
        
         | 
    @@ -378,6 +392,7 @@ abstract contract Pyth is | |
| // Return results | ||
| return (context.priceFeeds, context.slots); | ||
| } | ||
| 
     | 
||
| function parsePriceFeedUpdates( | ||
| bytes[] calldata updateData, | ||
| bytes32[] calldata priceIds, | ||
| 
        
          
        
         | 
    @@ -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 | ||
| ) | ||
| 
          
            
          
           | 
    @@ -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 | ||
| ); | ||
| } | ||
| 
          
            
          
           | 
    ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -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); | ||
| 
        
          
        
         | 
    @@ -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); | ||
| 
          
            
          
           | 
    @@ -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}( | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| ); | ||
| } | ||
| 
     | 
||
| 
          
            
          
           | 
    @@ -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 | ||
| ); | ||
| } | ||
| 
     | 
||
| 
          
            
          
           | 
    ||
| Original file line number | Diff line number | Diff line change | 
|---|---|---|
| 
          
            
          
           | 
    @@ -136,21 +136,6 @@ abstract contract AbstractPyth is IPyth { | |
| override | ||
| returns (PythStructs.PriceFeed[] memory priceFeeds); | ||
| 
     | 
||
| function parsePriceFeedUpdatesWithSlotsStrict( | ||
| 
         There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| 
          
            
          
           | 
    ||
There was a problem hiding this comment.
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