@@ -54,7 +54,7 @@ contract CustomErrorPulseConsumer is IPulseConsumer {
54
54
}
55
55
}
56
56
57
- contract PulseTest is Test , PulseEvents {
57
+ contract PulseTest is Test , PulseEvents , IPulseConsumer {
58
58
ERC1967Proxy public proxy;
59
59
PulseUpgradeable public pulse;
60
60
MockPulseConsumer public consumer;
@@ -876,4 +876,207 @@ contract PulseTest is Test, PulseEvents {
876
876
vm.prank (secondProvider);
877
877
pulse.executeCallback (sequenceNumber, updateData, priceIds);
878
878
}
879
+
880
+ function testGetFirstActiveRequests () public {
881
+ // Setup test data
882
+ (
883
+ bytes32 [] memory priceIds ,
884
+ bytes [] memory updateData
885
+ ) = setupTestData ();
886
+ createTestRequests (priceIds);
887
+ completeRequests (updateData, priceIds);
888
+
889
+ testRequestScenarios (priceIds, updateData);
890
+ }
891
+
892
+ function setupTestData ()
893
+ private
894
+ pure
895
+ returns (bytes32 [] memory , bytes [] memory )
896
+ {
897
+ bytes32 [] memory priceIds = new bytes32 [](1 );
898
+ priceIds[0 ] = bytes32 (uint256 (1 ));
899
+
900
+ bytes [] memory updateData = new bytes [](1 );
901
+ return (priceIds, updateData);
902
+ }
903
+
904
+ function createTestRequests (bytes32 [] memory priceIds ) private {
905
+ uint256 publishTime = block .timestamp ;
906
+ for (uint i = 0 ; i < 5 ; i++ ) {
907
+ vm.deal (address (this ), 1 ether);
908
+ pulse.requestPriceUpdatesWithCallback {value: 1 ether }(
909
+ publishTime,
910
+ priceIds,
911
+ 1000000
912
+ );
913
+ }
914
+ }
915
+
916
+ function completeRequests (
917
+ bytes [] memory updateData ,
918
+ bytes32 [] memory priceIds
919
+ ) private {
920
+ // Create mock price feeds and setup Pyth response
921
+ PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds (
922
+ block .timestamp
923
+ );
924
+ mockParsePriceFeedUpdates (priceFeeds);
925
+ updateData = createMockUpdateData (priceFeeds);
926
+
927
+ vm.deal (defaultProvider, 2 ether); // Increase ETH allocation to prevent OutOfFunds
928
+ vm.startPrank (defaultProvider);
929
+ pulse.executeCallback {value: 1 ether }(2 , updateData, priceIds);
930
+ pulse.executeCallback {value: 1 ether }(4 , updateData, priceIds);
931
+ vm.stopPrank ();
932
+ }
933
+
934
+ function testRequestScenarios (
935
+ bytes32 [] memory priceIds ,
936
+ bytes [] memory updateData
937
+ ) private {
938
+ // Test 1: Request more than available
939
+ checkMoreThanAvailable ();
940
+
941
+ // Test 2: Request exact number
942
+ checkExactNumber ();
943
+
944
+ // Test 3: Request fewer than available
945
+ checkFewerThanAvailable ();
946
+
947
+ // Test 4: Request zero
948
+ checkZeroRequest ();
949
+
950
+ // Test 5: Clear all and check empty
951
+ clearAllRequests (updateData, priceIds);
952
+ checkEmptyState ();
953
+ }
954
+
955
+ // Split test scenarios into separate functions
956
+ function checkMoreThanAvailable () private {
957
+ (PulseState.Request[] memory requests , uint256 count ) = pulse
958
+ .getFirstActiveRequests (10 );
959
+ assertEq (count, 3 , "Should find 3 active requests " );
960
+ assertEq (requests.length , 3 , "Array should be resized to 3 " );
961
+ assertEq (
962
+ requests[0 ].sequenceNumber,
963
+ 1 ,
964
+ "First request should be oldest "
965
+ );
966
+ assertEq (requests[1 ].sequenceNumber, 3 , "Second request should be #3 " );
967
+ assertEq (requests[2 ].sequenceNumber, 5 , "Third request should be #5 " );
968
+ }
969
+
970
+ function checkExactNumber () private {
971
+ (PulseState.Request[] memory requests , uint256 count ) = pulse
972
+ .getFirstActiveRequests (3 );
973
+ assertEq (count, 3 , "Should find 3 active requests " );
974
+ assertEq (requests.length , 3 , "Array should match requested size " );
975
+ }
976
+
977
+ function checkFewerThanAvailable () private {
978
+ (PulseState.Request[] memory requests , uint256 count ) = pulse
979
+ .getFirstActiveRequests (2 );
980
+ assertEq (count, 2 , "Should find 2 active requests " );
981
+ assertEq (requests.length , 2 , "Array should match requested size " );
982
+ assertEq (
983
+ requests[0 ].sequenceNumber,
984
+ 1 ,
985
+ "First request should be oldest "
986
+ );
987
+ assertEq (requests[1 ].sequenceNumber, 3 , "Second request should be #3 " );
988
+ }
989
+
990
+ function checkZeroRequest () private {
991
+ (PulseState.Request[] memory requests , uint256 count ) = pulse
992
+ .getFirstActiveRequests (0 );
993
+ assertEq (count, 0 , "Should find 0 active requests " );
994
+ assertEq (requests.length , 0 , "Array should be empty " );
995
+ }
996
+
997
+ function clearAllRequests (
998
+ bytes [] memory updateData ,
999
+ bytes32 [] memory priceIds
1000
+ ) private {
1001
+ vm.deal (defaultProvider, 3 ether); // Increase ETH allocation
1002
+ vm.startPrank (defaultProvider);
1003
+ pulse.executeCallback {value: 1 ether }(1 , updateData, priceIds);
1004
+ pulse.executeCallback {value: 1 ether }(3 , updateData, priceIds);
1005
+ pulse.executeCallback {value: 1 ether }(5 , updateData, priceIds);
1006
+ vm.stopPrank ();
1007
+ }
1008
+
1009
+ function checkEmptyState () private {
1010
+ (PulseState.Request[] memory requests , uint256 count ) = pulse
1011
+ .getFirstActiveRequests (10 );
1012
+ assertEq (count, 0 , "Should find 0 active requests " );
1013
+ assertEq (requests.length , 0 , "Array should be empty " );
1014
+ }
1015
+
1016
+ function testGetFirstActiveRequestsGasUsage () public {
1017
+ // Setup test data
1018
+ bytes32 [] memory priceIds = new bytes32 [](1 );
1019
+ priceIds[0 ] = bytes32 (uint256 (1 ));
1020
+ uint256 publishTime = block .timestamp ;
1021
+ uint256 callbackGasLimit = 1000000 ;
1022
+
1023
+ // Create mock price feeds and setup Pyth response
1024
+ PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds (
1025
+ publishTime
1026
+ );
1027
+ mockParsePriceFeedUpdates (priceFeeds);
1028
+ bytes [] memory updateData = createMockUpdateData (priceFeeds);
1029
+
1030
+ // Create 20 requests with some gaps
1031
+ for (uint i = 0 ; i < 20 ; i++ ) {
1032
+ vm.deal (address (this ), 1 ether);
1033
+ pulse.requestPriceUpdatesWithCallback {value: 1 ether }(
1034
+ publishTime,
1035
+ priceIds,
1036
+ callbackGasLimit
1037
+ );
1038
+
1039
+ // Complete every third request to create gaps
1040
+ if (i % 3 == 0 ) {
1041
+ vm.deal (defaultProvider, 1 ether);
1042
+ vm.prank (defaultProvider);
1043
+ pulse.executeCallback {value: 1 ether }(
1044
+ uint64 (i + 1 ),
1045
+ updateData,
1046
+ priceIds
1047
+ );
1048
+ }
1049
+ }
1050
+
1051
+ // Measure gas for different request counts
1052
+ uint256 gas1 = gasleft ();
1053
+ pulse.getFirstActiveRequests (5 );
1054
+ uint256 gas1Used = gas1 - gasleft ();
1055
+
1056
+ uint256 gas2 = gasleft ();
1057
+ pulse.getFirstActiveRequests (10 );
1058
+ uint256 gas2Used = gas2 - gasleft ();
1059
+
1060
+ // Log gas usage for analysis
1061
+ emit log_named_uint ("Gas used for 5 requests " , gas1Used);
1062
+ emit log_named_uint ("Gas used for 10 requests " , gas2Used);
1063
+
1064
+ // Verify gas usage scales roughly linearly
1065
+ // Allow 10% margin for other factors
1066
+ assertApproxEqRel (
1067
+ gas2Used,
1068
+ gas1Used * 2 ,
1069
+ 0.1e18 , // 10% tolerance
1070
+ "Gas usage should scale roughly linearly "
1071
+ );
1072
+ }
1073
+
1074
+ // Mock implementation of pulseCallback
1075
+ function pulseCallback (
1076
+ uint64 sequenceNumber ,
1077
+ PythStructs.PriceFeed[] memory priceFeeds
1078
+ ) external override {
1079
+ // Just accept the callback, no need to do anything with the data
1080
+ // This prevents the revert we're seeing
1081
+ }
879
1082
}
0 commit comments