Skip to content

Commit 74ca371

Browse files
authored
feat(pulse): Pulse Gas Benchmark (#2467)
* gas benchmark * add test utils for pulse and gas benchmark * cleanup * forgot this file * pr comments * fix ci
1 parent 45df937 commit 74ca371

File tree

3 files changed

+270
-124
lines changed

3 files changed

+270
-124
lines changed

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

+34-124
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pragma solidity ^0.8.0;
55
import "forge-std/Test.sol";
66
import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
77
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
8+
import "./utils/PulseTestUtils.t.sol";
89
import "../contracts/pulse/PulseUpgradeable.sol";
910
import "../contracts/pulse/IPulse.sol";
1011
import "../contracts/pulse/PulseState.sol";
@@ -84,7 +85,7 @@ contract CustomErrorPulseConsumer is IPulseConsumer {
8485
}
8586

8687
// FIXME: this shouldn't be IPulseConsumer.
87-
contract PulseTest is Test, PulseEvents, IPulseConsumer {
88+
contract PulseTest is Test, PulseEvents, IPulseConsumer, PulseTestUtils {
8889
ERC1967Proxy public proxy;
8990
PulseUpgradeable public pulse;
9091
MockPulseConsumer public consumer;
@@ -97,20 +98,6 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
9798
uint128 constant DEFAULT_PROVIDER_FEE_PER_GAS = 1 wei;
9899
uint128 constant DEFAULT_PROVIDER_BASE_FEE = 1 wei;
99100
uint128 constant DEFAULT_PROVIDER_FEE_PER_FEED = 10 wei;
100-
uint constant MOCK_PYTH_FEE_PER_FEED = 10 wei;
101-
102-
uint128 constant CALLBACK_GAS_LIMIT = 1_000_000;
103-
bytes32 constant BTC_PRICE_FEED_ID =
104-
0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43;
105-
bytes32 constant ETH_PRICE_FEED_ID =
106-
0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace;
107-
108-
// Price feed constants
109-
int8 constant MOCK_PRICE_FEED_EXPO = -8;
110-
int64 constant MOCK_BTC_PRICE = 5_000_000_000_000; // $50,000
111-
int64 constant MOCK_ETH_PRICE = 300_000_000_000; // $3,000
112-
uint64 constant MOCK_BTC_CONF = 10_000_000_000; // $100
113-
uint64 constant MOCK_ETH_CONF = 5_000_000_000; // $50
114101

115102
function setUp() public {
116103
owner = address(1);
@@ -139,102 +126,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
139126
consumer = new MockPulseConsumer(address(proxy));
140127
}
141128

142-
// Helper function to create price IDs array
143-
function createPriceIds() internal pure returns (bytes32[] memory) {
144-
bytes32[] memory priceIds = new bytes32[](2);
145-
priceIds[0] = BTC_PRICE_FEED_ID;
146-
priceIds[1] = ETH_PRICE_FEED_ID;
147-
return priceIds;
148-
}
149-
150-
// Helper function to create mock price feeds
151-
function createMockPriceFeeds(
152-
uint256 publishTime
153-
) internal pure returns (PythStructs.PriceFeed[] memory) {
154-
PythStructs.PriceFeed[] memory priceFeeds = new PythStructs.PriceFeed[](
155-
2
156-
);
157-
158-
priceFeeds[0].id = BTC_PRICE_FEED_ID;
159-
priceFeeds[0].price.price = MOCK_BTC_PRICE;
160-
priceFeeds[0].price.conf = MOCK_BTC_CONF;
161-
priceFeeds[0].price.expo = MOCK_PRICE_FEED_EXPO;
162-
priceFeeds[0].price.publishTime = publishTime;
163-
164-
priceFeeds[1].id = ETH_PRICE_FEED_ID;
165-
priceFeeds[1].price.price = MOCK_ETH_PRICE;
166-
priceFeeds[1].price.conf = MOCK_ETH_CONF;
167-
priceFeeds[1].price.expo = MOCK_PRICE_FEED_EXPO;
168-
priceFeeds[1].price.publishTime = publishTime;
169-
170-
return priceFeeds;
171-
}
172-
173-
// Helper function to mock Pyth response
174-
function mockParsePriceFeedUpdates(
175-
PythStructs.PriceFeed[] memory priceFeeds
176-
) internal {
177-
uint expectedFee = MOCK_PYTH_FEE_PER_FEED * priceFeeds.length;
178-
179-
vm.mockCall(
180-
address(pyth),
181-
abi.encodeWithSelector(IPyth.getUpdateFee.selector),
182-
abi.encode(expectedFee)
183-
);
184-
185-
vm.mockCall(
186-
address(pyth),
187-
expectedFee,
188-
abi.encodeWithSelector(IPyth.parsePriceFeedUpdates.selector),
189-
abi.encode(priceFeeds)
190-
);
191-
}
192-
193-
// Helper function to create mock update data
194-
function createMockUpdateData(
195-
PythStructs.PriceFeed[] memory priceFeeds
196-
) internal pure returns (bytes[] memory) {
197-
bytes[] memory updateData = new bytes[](2);
198-
updateData[0] = abi.encode(priceFeeds[0]);
199-
updateData[1] = abi.encode(priceFeeds[1]);
200-
return updateData;
201-
}
202-
203129
// Helper function to calculate total fee
204130
// FIXME: I think this helper probably needs to take some arguments.
205131
function calculateTotalFee() internal view returns (uint128) {
206132
return
207133
pulse.getFee(defaultProvider, CALLBACK_GAS_LIMIT, createPriceIds());
208134
}
209135

210-
// Helper function to setup consumer request
211-
function setupConsumerRequest(
212-
address consumerAddress
213-
)
214-
internal
215-
returns (
216-
uint64 sequenceNumber,
217-
bytes32[] memory priceIds,
218-
uint64 publishTime
219-
)
220-
{
221-
priceIds = createPriceIds();
222-
publishTime = SafeCast.toUint64(block.timestamp);
223-
vm.deal(consumerAddress, 1 gwei);
224-
225-
uint128 totalFee = calculateTotalFee();
226-
227-
vm.prank(consumerAddress);
228-
sequenceNumber = pulse.requestPriceUpdatesWithCallback{value: totalFee}(
229-
defaultProvider,
230-
publishTime,
231-
priceIds,
232-
CALLBACK_GAS_LIMIT
233-
);
234-
235-
return (sequenceNumber, priceIds, publishTime);
236-
}
237-
238136
function testRequestPriceUpdate() public {
239137
// Set a realistic gas price
240138
vm.txGasPrice(30 gwei);
@@ -334,7 +232,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
334232
publishTime
335233
);
336234
// FIXME: this test doesn't ensure the Pyth fee is paid.
337-
mockParsePriceFeedUpdates(priceFeeds);
235+
mockParsePriceFeedUpdates(pyth, priceFeeds);
338236

339237
// Create arrays for expected event data
340238
int64[] memory expectedPrices = new int64[](2);
@@ -405,12 +303,16 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
405303
uint64 sequenceNumber,
406304
bytes32[] memory priceIds,
407305
uint256 publishTime
408-
) = setupConsumerRequest(address(failingConsumer));
306+
) = setupConsumerRequest(
307+
pulse,
308+
defaultProvider,
309+
address(failingConsumer)
310+
);
409311

410312
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
411313
publishTime
412314
);
413-
mockParsePriceFeedUpdates(priceFeeds);
315+
mockParsePriceFeedUpdates(pyth, priceFeeds);
414316
bytes[] memory updateData = createMockUpdateData(priceFeeds);
415317

416318
vm.expectEmit();
@@ -440,12 +342,16 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
440342
uint64 sequenceNumber,
441343
bytes32[] memory priceIds,
442344
uint256 publishTime
443-
) = setupConsumerRequest(address(failingConsumer));
345+
) = setupConsumerRequest(
346+
pulse,
347+
defaultProvider,
348+
address(failingConsumer)
349+
);
444350

445351
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
446352
publishTime
447353
);
448-
mockParsePriceFeedUpdates(priceFeeds);
354+
mockParsePriceFeedUpdates(pyth, priceFeeds);
449355
bytes[] memory updateData = createMockUpdateData(priceFeeds);
450356

451357
vm.expectEmit();
@@ -472,13 +378,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
472378
uint64 sequenceNumber,
473379
bytes32[] memory priceIds,
474380
uint256 publishTime
475-
) = setupConsumerRequest(address(consumer));
381+
) = setupConsumerRequest(pulse, defaultProvider, address(consumer));
476382

477383
// Setup mock data
478384
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
479385
publishTime
480386
);
481-
mockParsePriceFeedUpdates(priceFeeds);
387+
mockParsePriceFeedUpdates(pyth, priceFeeds);
482388
bytes[] memory updateData = createMockUpdateData(priceFeeds);
483389

484390
// Try executing with only 100K gas when 1M is required
@@ -508,7 +414,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
508414
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
509415
futureTime // Mock price feeds with future timestamp
510416
);
511-
mockParsePriceFeedUpdates(priceFeeds); // This will make parsePriceFeedUpdates return future-dated prices
417+
mockParsePriceFeedUpdates(pyth, priceFeeds); // This will make parsePriceFeedUpdates return future-dated prices
512418
bytes[] memory updateData = createMockUpdateData(priceFeeds);
513419

514420
vm.prank(defaultProvider);
@@ -555,12 +461,12 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
555461
uint64 sequenceNumber,
556462
bytes32[] memory priceIds,
557463
uint256 publishTime
558-
) = setupConsumerRequest(address(consumer));
464+
) = setupConsumerRequest(pulse, defaultProvider, address(consumer));
559465

560466
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
561467
publishTime
562468
);
563-
mockParsePriceFeedUpdates(priceFeeds);
469+
mockParsePriceFeedUpdates(pyth, priceFeeds);
564470
bytes[] memory updateData = createMockUpdateData(priceFeeds);
565471

566472
// First execution
@@ -747,7 +653,11 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
747653
uint256 publishTime = block.timestamp;
748654

749655
// Setup request
750-
(uint64 sequenceNumber, , ) = setupConsumerRequest(address(consumer));
656+
(uint64 sequenceNumber, , ) = setupConsumerRequest(
657+
pulse,
658+
defaultProvider,
659+
address(consumer)
660+
);
751661

752662
// Create different priceIds
753663
bytes32[] memory wrongPriceIds = new bytes32[](2);
@@ -757,7 +667,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
757667
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
758668
publishTime
759669
);
760-
mockParsePriceFeedUpdates(priceFeeds);
670+
mockParsePriceFeedUpdates(pyth, priceFeeds);
761671
bytes[] memory updateData = createMockUpdateData(priceFeeds);
762672

763673
// Should revert when trying to execute with wrong priceIds
@@ -923,13 +833,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
923833
uint64 sequenceNumber,
924834
bytes32[] memory priceIds,
925835
uint256 publishTime
926-
) = setupConsumerRequest(address(consumer));
836+
) = setupConsumerRequest(pulse, defaultProvider, address(consumer));
927837

928838
// Setup mock data
929839
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
930840
publishTime
931841
);
932-
mockParsePriceFeedUpdates(priceFeeds);
842+
mockParsePriceFeedUpdates(pyth, priceFeeds);
933843
bytes[] memory updateData = createMockUpdateData(priceFeeds);
934844

935845
// Try to execute with second provider during exclusivity period
@@ -965,13 +875,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
965875
uint64 sequenceNumber,
966876
bytes32[] memory priceIds,
967877
uint256 publishTime
968-
) = setupConsumerRequest(address(consumer));
878+
) = setupConsumerRequest(pulse, defaultProvider, address(consumer));
969879

970880
// Setup mock data
971881
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
972882
publishTime
973883
);
974-
mockParsePriceFeedUpdates(priceFeeds);
884+
mockParsePriceFeedUpdates(pyth, priceFeeds);
975885
bytes[] memory updateData = createMockUpdateData(priceFeeds);
976886

977887
// Wait for exclusivity period to end
@@ -1006,13 +916,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
1006916
uint64 sequenceNumber,
1007917
bytes32[] memory priceIds,
1008918
uint256 publishTime
1009-
) = setupConsumerRequest(address(consumer));
919+
) = setupConsumerRequest(pulse, defaultProvider, address(consumer));
1010920

1011921
// Setup mock data
1012922
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
1013923
publishTime
1014924
);
1015-
mockParsePriceFeedUpdates(priceFeeds);
925+
mockParsePriceFeedUpdates(pyth, priceFeeds);
1016926
bytes[] memory updateData = createMockUpdateData(priceFeeds);
1017927

1018928
// Try at 29 seconds (should fail for second provider)
@@ -1080,7 +990,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
1080990
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
1081991
SafeCast.toUint64(block.timestamp)
1082992
);
1083-
mockParsePriceFeedUpdates(priceFeeds);
993+
mockParsePriceFeedUpdates(pyth, priceFeeds);
1084994
updateData = createMockUpdateData(priceFeeds);
1085995

1086996
vm.deal(defaultProvider, 2 ether); // Increase ETH allocation to prevent OutOfFunds
@@ -1208,7 +1118,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer {
12081118
PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds(
12091119
publishTime
12101120
);
1211-
mockParsePriceFeedUpdates(priceFeeds);
1121+
mockParsePriceFeedUpdates(pyth, priceFeeds);
12121122
bytes[] memory updateData = createMockUpdateData(priceFeeds);
12131123

12141124
// Create 20 requests with some gaps

0 commit comments

Comments
 (0)