Skip to content

Commit 945e081

Browse files
committed
test: add out of order fulfillment benchmark
1 parent d5c61c2 commit 945e081

File tree

2 files changed

+73
-58
lines changed

2 files changed

+73
-58
lines changed

target_chains/ethereum/contracts/contracts/pulse/Pulse.sol

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ abstract contract Pulse is IPulse, PulseState {
4444
req.publishTime = 1;
4545
req.callbackGasLimit = 1;
4646
req.requester = address(1);
47-
// Initialize with empty array, no need to pre-warm with a fixed size anymore
48-
delete req.priceIdPrefixes;
4947
}
5048
}
5149
}

target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol

Lines changed: 73 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)