@@ -41,7 +41,7 @@ contract PulseGasBenchmark is Test, PulseTestUtils {
4141 PYTH_FEE,
4242 pyth,
4343 defaultProvider,
44- false ,
44+ true ,
4545 15
4646 );
4747 vm.prank (defaultProvider);
@@ -66,9 +66,10 @@ contract PulseGasBenchmark is Test, PulseTestUtils {
6666 createMockUpdateData (priceFeeds);
6767 }
6868
69- function testBasicFlow () public {
69+ // Helper function to run the basic request + fulfill flow with a specified number of feeds
70+ function _runBenchmarkWithFeeds (uint256 numFeeds ) internal {
7071 uint64 timestamp = SafeCast.toUint64 (block .timestamp );
71- bytes32 [] memory priceIds = createPriceIds ();
72+ bytes32 [] memory priceIds = createPriceIds (numFeeds );
7273
7374 uint32 callbackGasLimit = 100000 ;
7475 uint96 totalFee = pulse.getFee (
@@ -83,7 +84,8 @@ contract PulseGasBenchmark is Test, PulseTestUtils {
8384 }(defaultProvider, timestamp, priceIds, callbackGasLimit);
8485
8586 PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds (
86- timestamp
87+ timestamp,
88+ numFeeds
8789 );
8890 mockParsePriceFeedUpdates (pyth, priceFeeds);
8991 bytes [] memory updateData = createMockUpdateData (priceFeeds);
@@ -96,77 +98,92 @@ contract PulseGasBenchmark is Test, PulseTestUtils {
9698 );
9799 }
98100
99- // Runs benchmark with feeds and returns the gas usage breakdown
100- function _runBenchmarkWithFeedsAndGasTracking (
101- uint256 numFeeds
102- ) internal returns (uint256 requestGas , uint256 executeGas ) {
103- uint64 timestamp = SafeCast.toUint64 (block .timestamp );
104- bytes32 [] memory priceIds = createPriceIds (numFeeds);
101+ function testFlow_01_Feed () public {
102+ _runBenchmarkWithFeeds (1 );
103+ }
104+
105+ function testFlow_02_Feeds () public {
106+ _runBenchmarkWithFeeds (2 );
107+ }
105108
109+ function testFlow_04_Feeds () public {
110+ _runBenchmarkWithFeeds (4 );
111+ }
112+
113+ function testFlow_08_Feeds () public {
114+ _runBenchmarkWithFeeds (8 );
115+ }
116+
117+ function testFlow_10_Feeds () public {
118+ _runBenchmarkWithFeeds (10 );
119+ }
120+
121+ // This test checks the gas usage for worst-case out-of-order fulfillment.
122+ // It creates 10 requests, and then fulfills them in reverse order.
123+ //
124+ // The last fulfillment will be the most expensive since it needs
125+ // to linearly scan through all the fulfilled requests in storage
126+ // in order to update _state.lastUnfulfilledReq
127+ // NOTE: Run test with -vv to see extra gas logs.
128+ function testMultipleRequestsOutOfOrderFulfillment () public {
129+ uint64 timestamp = SafeCast.toUint64 (block .timestamp );
130+ bytes32 [] memory priceIds = createPriceIds (2 );
106131 uint32 callbackGasLimit = 100000 ;
107- uint96 totalFee = pulse.getFee (
132+ uint128 totalFee = pulse.getFee (
108133 defaultProvider,
109134 callbackGasLimit,
110135 priceIds
111136 );
112- vm.deal (address (consumer), 1 ether);
113137
114- // Measure gas for request
115- uint256 gasBefore = gasleft ();
116- vm.prank (address (consumer));
117- uint64 sequenceNumber = pulse.requestPriceUpdatesWithCallback {
118- value: totalFee
119- }(defaultProvider, timestamp, priceIds, callbackGasLimit);
120- requestGas = gasBefore - gasleft ();
138+ // Create 10 requests
139+ uint64 [] memory sequenceNumbers = new uint64 [](10 );
140+ vm.deal (address (consumer), 10 ether);
141+
142+ for (uint i = 0 ; i < 10 ; i++ ) {
143+ vm.prank (address (consumer));
144+ sequenceNumbers[i] = pulse.requestPriceUpdatesWithCallback {
145+ value: totalFee
146+ }(
147+ defaultProvider,
148+ timestamp + uint64 (i),
149+ priceIds,
150+ callbackGasLimit
151+ );
152+ }
121153
122154 PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds (
123- timestamp,
124- numFeeds
155+ timestamp
125156 );
126157 mockParsePriceFeedUpdates (pyth, priceFeeds);
127158 bytes [] memory updateData = createMockUpdateData (priceFeeds);
128159
129- // Measure gas for execute
130- gasBefore = gasleft ();
160+ // Execute callbacks in reverse
161+ uint startGas = gasleft ();
162+ for (uint i = 9 ; i > 0 ; i-- ) {
163+ pulse.executeCallback (
164+ defaultProvider,
165+ sequenceNumbers[i],
166+ updateData,
167+ priceIds
168+ );
169+ }
170+ uint midGas = gasleft ();
171+
172+ // Execute the first request last - this would be the most expensive
173+ // in the original implementation as it would need to loop through
174+ // all sequence numbers
131175 pulse.executeCallback (
132176 defaultProvider,
133- sequenceNumber ,
177+ sequenceNumbers[ 0 ] ,
134178 updateData,
135179 priceIds
136180 );
137- executeGas = gasBefore - gasleft ();
138- }
181+ uint endGas = gasleft ();
139182
140- function testGasBreakdownByFeeds () public {
141- uint256 [] memory feedCounts = new uint256 [](5 );
142- feedCounts[0 ] = 1 ;
143- feedCounts[1 ] = 2 ;
144- feedCounts[2 ] = 4 ;
145- feedCounts[3 ] = 8 ;
146- feedCounts[4 ] = 10 ;
147-
148- console.log ("=== Gas Usage Breakdown === " );
149- for (uint256 i = 0 ; i < feedCounts.length ; i++ ) {
150- console.log ("--> Feeds: %s " , vm.toString (feedCounts[i]));
151- (
152- uint256 requestGas ,
153- uint256 executeCallbackGas
154- ) = _runBenchmarkWithFeedsAndGasTracking (feedCounts[i]);
155-
156- string memory requestGasStr = vm.toString (requestGas);
157- string memory executeCallbackGasStr = vm.toString (
158- executeCallbackGas
159- );
160- string memory totalGasStr = vm.toString (
161- requestGas + executeCallbackGas
162- );
163- console.log (
164- "Request gas: %s | Callback gas: %s | Total gas: %s " ,
165- requestGasStr,
166- executeCallbackGasStr,
167- totalGasStr
168- );
169- }
183+ // Log gas usage for the last callback which would be the most expensive
184+ // in the original implementation
185+ console.log ("Gas used for last callback (seq 1): " , midGas - endGas);
186+ console.log ("Gas used for all other callbacks: " , startGas - midGas);
170187 }
171188}
172189
0 commit comments