@@ -54,7 +54,7 @@ contract CustomErrorPulseConsumer is IPulseConsumer {
5454 }
5555}
5656
57- contract PulseTest is Test , PulseEvents {
57+ contract PulseTest is Test , PulseEvents , IPulseConsumer {
5858 ERC1967Proxy public proxy;
5959 PulseUpgradeable public pulse;
6060 MockPulseConsumer public consumer;
@@ -876,4 +876,207 @@ contract PulseTest is Test, PulseEvents {
876876 vm.prank (secondProvider);
877877 pulse.executeCallback (sequenceNumber, updateData, priceIds);
878878 }
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+ }
8791082}
0 commit comments