@@ -27,16 +27,18 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
27
27
uint8 constant MERKLE_TREE_DEPTH = 9 ;
28
28
29
29
// Base TWAP messages that will be used as templates for tests
30
- TwapPriceFeedMessage[1 ] baseTwapStartMessages;
31
- TwapPriceFeedMessage[1 ] baseTwapEndMessages;
32
- bytes32 [1 ] basePriceIds;
30
+ TwapPriceFeedMessage[2 ] baseTwapStartMessages;
31
+ TwapPriceFeedMessage[2 ] baseTwapEndMessages;
32
+ bytes32 [2 ] basePriceIds;
33
33
34
34
function setUp () public {
35
35
pyth = IPyth (setUpPyth (setUpWormholeReceiver (NUM_GUARDIAN_SIGNERS)));
36
36
37
- // Initialize base TWAP messages
37
+ // Initialize base TWAP messages for two price feeds
38
38
basePriceIds[0 ] = bytes32 (uint256 (1 ));
39
+ basePriceIds[1 ] = bytes32 (uint256 (2 ));
39
40
41
+ // First price feed TWAP messages
40
42
baseTwapStartMessages[0 ] = TwapPriceFeedMessage ({
41
43
priceId: basePriceIds[0 ],
42
44
cumulativePrice: 100_000 , // Base cumulative value
@@ -58,6 +60,29 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
58
60
prevPublishTime: 1000 ,
59
61
expo: - 8
60
62
});
63
+
64
+ // Second price feed TWAP messages
65
+ baseTwapStartMessages[1 ] = TwapPriceFeedMessage ({
66
+ priceId: basePriceIds[1 ],
67
+ cumulativePrice: 500_000 , // Different base cumulative value
68
+ cumulativeConf: 20_000 , // Different base cumulative conf
69
+ numDownSlots: 0 ,
70
+ publishSlot: 1000 ,
71
+ publishTime: 1000 ,
72
+ prevPublishTime: 900 ,
73
+ expo: - 8
74
+ });
75
+
76
+ baseTwapEndMessages[1 ] = TwapPriceFeedMessage ({
77
+ priceId: basePriceIds[1 ],
78
+ cumulativePrice: 800_000 , // Increased by 300_000
79
+ cumulativeConf: 40_000 , // Increased by 20_000
80
+ numDownSlots: 0 ,
81
+ publishSlot: 1100 ,
82
+ publishTime: 1100 ,
83
+ prevPublishTime: 1000 ,
84
+ expo: - 8
85
+ });
61
86
}
62
87
63
88
function generateRandomPriceMessages (
@@ -766,4 +791,162 @@ contract PythTest is Test, WormholeTestUtils, PythTestUtils {
766
791
priceIds
767
792
);
768
793
}
794
+
795
+ function testParseTwapPriceFeedUpdatesMultipleFeeds () public {
796
+ bytes32 [] memory priceIds = new bytes32 [](2 );
797
+ priceIds[0 ] = basePriceIds[0 ];
798
+ priceIds[1 ] = basePriceIds[1 ];
799
+
800
+ // Create update data for both price feeds
801
+ bytes [] memory updateData = new bytes [](4 ); // 2 updates (start/end) for each price feed
802
+
803
+ // First price feed updates
804
+ TwapPriceFeedMessage[]
805
+ memory startMessages1 = new TwapPriceFeedMessage [](1 );
806
+ TwapPriceFeedMessage[] memory endMessages1 = new TwapPriceFeedMessage [](
807
+ 1
808
+ );
809
+ startMessages1[0 ] = baseTwapStartMessages[0 ];
810
+ endMessages1[0 ] = baseTwapEndMessages[0 ];
811
+
812
+ // Second price feed updates
813
+ TwapPriceFeedMessage[]
814
+ memory startMessages2 = new TwapPriceFeedMessage [](1 );
815
+ TwapPriceFeedMessage[] memory endMessages2 = new TwapPriceFeedMessage [](
816
+ 1
817
+ );
818
+ startMessages2[0 ] = baseTwapStartMessages[1 ];
819
+ endMessages2[0 ] = baseTwapEndMessages[1 ];
820
+
821
+ // Generate Merkle updates for both price feeds
822
+ MerkleUpdateConfig memory config = MerkleUpdateConfig (
823
+ MERKLE_TREE_DEPTH,
824
+ NUM_GUARDIAN_SIGNERS,
825
+ SOURCE_EMITTER_CHAIN_ID,
826
+ SOURCE_EMITTER_ADDRESS,
827
+ false
828
+ );
829
+
830
+ updateData[0 ] = generateWhMerkleTwapUpdateWithSource (
831
+ startMessages1,
832
+ config
833
+ );
834
+ updateData[1 ] = generateWhMerkleTwapUpdateWithSource (
835
+ endMessages1,
836
+ config
837
+ );
838
+ updateData[2 ] = generateWhMerkleTwapUpdateWithSource (
839
+ startMessages2,
840
+ config
841
+ );
842
+ updateData[3 ] = generateWhMerkleTwapUpdateWithSource (
843
+ endMessages2,
844
+ config
845
+ );
846
+
847
+ uint updateFee = pyth.getUpdateFee (updateData);
848
+
849
+ // Parse the TWAP updates
850
+ PythStructs.TwapPriceFeed[] memory twapPriceFeeds = pyth
851
+ .parseTwapPriceFeedUpdates {value: updateFee}(updateData, priceIds);
852
+
853
+ // Validate results for first price feed
854
+ assertEq (twapPriceFeeds[0 ].id, basePriceIds[0 ]);
855
+ assertEq (
856
+ twapPriceFeeds[0 ].startTime,
857
+ baseTwapStartMessages[0 ].publishTime
858
+ );
859
+ assertEq (twapPriceFeeds[0 ].endTime, baseTwapEndMessages[0 ].publishTime);
860
+ assertEq (twapPriceFeeds[0 ].twap.expo, baseTwapStartMessages[0 ].expo);
861
+ // Expected TWAP price: (210_000 - 100_000) / (1100 - 1000) = 1100
862
+ assertEq (twapPriceFeeds[0 ].twap.price, int64 (1100 ));
863
+ // Expected TWAP conf: (18_000 - 10_000) / (1100 - 1000) = 80
864
+ assertEq (twapPriceFeeds[0 ].twap.conf, uint64 (80 ));
865
+ assertEq (twapPriceFeeds[0 ].downSlotsRatio, uint32 (0 ));
866
+
867
+ // Validate results for second price feed
868
+ assertEq (twapPriceFeeds[1 ].id, basePriceIds[1 ]);
869
+ assertEq (
870
+ twapPriceFeeds[1 ].startTime,
871
+ baseTwapStartMessages[1 ].publishTime
872
+ );
873
+ assertEq (twapPriceFeeds[1 ].endTime, baseTwapEndMessages[1 ].publishTime);
874
+ assertEq (twapPriceFeeds[1 ].twap.expo, baseTwapStartMessages[1 ].expo);
875
+ // Expected TWAP price: (800_000 - 500_000) / (1100 - 1000) = 3000
876
+ assertEq (twapPriceFeeds[1 ].twap.price, int64 (3000 ));
877
+ // Expected TWAP conf: (40_000 - 20_000) / (1100 - 1000) = 200
878
+ assertEq (twapPriceFeeds[1 ].twap.conf, uint64 (200 ));
879
+ assertEq (twapPriceFeeds[1 ].downSlotsRatio, uint32 (0 ));
880
+ }
881
+
882
+ function testParseTwapPriceFeedUpdatesRevertsWithMismatchedArrayLengths ()
883
+ public
884
+ {
885
+ // Case 1: More updates than needed for price feeds
886
+ bytes32 [] memory priceIds = new bytes32 [](1 ); // One price feed
887
+ priceIds[0 ] = basePriceIds[0 ];
888
+
889
+ // Create 4 updates (should only be 2 for one price feed)
890
+ bytes [] memory updateData = new bytes [](4 );
891
+
892
+ TwapPriceFeedMessage[]
893
+ memory startMessages = new TwapPriceFeedMessage [](1 );
894
+ TwapPriceFeedMessage[] memory endMessages = new TwapPriceFeedMessage [](
895
+ 1
896
+ );
897
+ startMessages[0 ] = baseTwapStartMessages[0 ];
898
+ endMessages[0 ] = baseTwapEndMessages[0 ];
899
+
900
+ MerkleUpdateConfig memory config = MerkleUpdateConfig (
901
+ MERKLE_TREE_DEPTH,
902
+ NUM_GUARDIAN_SIGNERS,
903
+ SOURCE_EMITTER_CHAIN_ID,
904
+ SOURCE_EMITTER_ADDRESS,
905
+ false
906
+ );
907
+
908
+ // Fill with valid updates, but too many of them
909
+ updateData[0 ] = generateWhMerkleTwapUpdateWithSource (
910
+ startMessages,
911
+ config
912
+ );
913
+ updateData[1 ] = generateWhMerkleTwapUpdateWithSource (
914
+ endMessages,
915
+ config
916
+ );
917
+ updateData[2 ] = generateWhMerkleTwapUpdateWithSource (
918
+ startMessages,
919
+ config
920
+ );
921
+ updateData[3 ] = generateWhMerkleTwapUpdateWithSource (
922
+ endMessages,
923
+ config
924
+ );
925
+
926
+ uint updateFee = pyth.getUpdateFee (updateData);
927
+
928
+ vm.expectRevert (PythErrors.InvalidUpdateData.selector );
929
+ pyth.parseTwapPriceFeedUpdates {value: updateFee}(updateData, priceIds);
930
+
931
+ // Case 2: Fewer updates than needed for price feeds
932
+ priceIds = new bytes32 [](2 ); // Two price feeds
933
+ priceIds[0 ] = basePriceIds[0 ];
934
+ priceIds[1 ] = basePriceIds[1 ];
935
+
936
+ // Create only 2 updates (should be 4 for two price feeds)
937
+ updateData = new bytes [](2 );
938
+ updateData[0 ] = generateWhMerkleTwapUpdateWithSource (
939
+ startMessages,
940
+ config
941
+ );
942
+ updateData[1 ] = generateWhMerkleTwapUpdateWithSource (
943
+ endMessages,
944
+ config
945
+ );
946
+
947
+ updateFee = pyth.getUpdateFee (updateData);
948
+
949
+ vm.expectRevert (PythErrors.InvalidUpdateData.selector );
950
+ pyth.parseTwapPriceFeedUpdates {value: updateFee}(updateData, priceIds);
951
+ }
769
952
}
0 commit comments