From 6ee30d09c0cd288a1cd4c2180dda184970b80373 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Sun, 9 Mar 2025 06:37:55 -0700 Subject: [PATCH 1/6] gas benchmark --- .../forge-test/PulseGasBenchmark.t.sol | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol diff --git a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol new file mode 100644 index 0000000000..d97773b196 --- /dev/null +++ b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "../contracts/pulse/PulseUpgradeable.sol"; +import "../contracts/pulse/IPulse.sol"; +import "../contracts/pulse/PulseState.sol"; +import "../contracts/pulse/PulseEvents.sol"; +import "../contracts/pulse/PulseErrors.sol"; + +// TODO +// - what's the impact of # of in-flight requests on gas usage? More requests => more hashes to +// verify the provider's value. +contract PulseGasBenchmark is Test { + ERC1967Proxy public proxy; + PulseUpgradeable public pulse; + IPulseConsumer public consumer; + + address public owner; + address public admin; + address public pyth; + address public defaultProvider; + + uint128 constant PYTH_FEE = 1 wei; + uint128 constant DEFAULT_PROVIDER_FEE_PER_GAS = 1 wei; + uint128 constant DEFAULT_PROVIDER_BASE_FEE = 1 wei; + uint128 constant DEFAULT_PROVIDER_FEE_PER_FEED = 10 wei; + + function setUp() public { + owner = address(1); + admin = address(2); + pyth = address(3); + defaultProvider = address(4); + PulseUpgradeable _pulse = new PulseUpgradeable(); + proxy = new ERC1967Proxy(address(_pulse), ""); + pulse = PulseUpgradeable(address(proxy)); + + pulse.initialize( + owner, + admin, + PYTH_FEE, + pyth, + defaultProvider, + false, + 15 + ); + vm.prank(defaultProvider); + pulse.registerProvider( + DEFAULT_PROVIDER_BASE_FEE, + DEFAULT_PROVIDER_FEE_PER_FEED, + DEFAULT_PROVIDER_FEE_PER_GAS + ); + consumer = new VoidPulseConsumer(address(proxy)); + } + + function testBasicFlow() public { + bytes32[] memory priceIds = createPriceIds(); + uint64 publishTime = SafeCast.toUint64(block.timestamp); + + uint128 totalFee = calculateTotalFee(); + vm.deal(address(consumer), 1 ether); + vm.prank(address(consumer)); + uint64 sequenceNumber = pulse.requestPriceUpdatesWithCallback{ + value: totalFee + }(defaultProvider, publishTime, priceIds, 100000); + + pulse.executeCallback( + defaultProvider, + sequenceNumber, + updateData, + priceIds + ); + } +} + +contract VoidPulseConsumer is IPulseConsumer { + address private _pulse; + + constructor(address pulse) { + _pulse = pulse; + } + + function getPulse() internal view override returns (address) { + return _pulse; + } + + function pulseCallback( + uint64 sequenceNumber, + PythStructs.PriceFeed[] memory priceFeeds + ) internal override {} +} From 49f81799297411446bee0d5eb90d1e87e98f8084 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Sun, 9 Mar 2025 07:03:10 -0700 Subject: [PATCH 2/6] add test utils for pulse and gas benchmark --- .../ethereum/contracts/forge-test/Pulse.t.sol | 158 ++++-------------- .../forge-test/PulseGasBenchmark.t.sol | 33 +++- 2 files changed, 63 insertions(+), 128 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/Pulse.t.sol b/target_chains/ethereum/contracts/forge-test/Pulse.t.sol index 9187987cb9..4041076bfa 100644 --- a/target_chains/ethereum/contracts/forge-test/Pulse.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Pulse.t.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import "./utils/PulseTestUtils.t.sol"; import "../contracts/pulse/PulseUpgradeable.sol"; import "../contracts/pulse/IPulse.sol"; import "../contracts/pulse/PulseState.sol"; @@ -84,7 +85,7 @@ contract CustomErrorPulseConsumer is IPulseConsumer { } // FIXME: this shouldn't be IPulseConsumer. -contract PulseTest is Test, PulseEvents, IPulseConsumer { +contract PulseTest is Test, PulseEvents, IPulseConsumer, PulseTestUtils { ERC1967Proxy public proxy; PulseUpgradeable public pulse; MockPulseConsumer public consumer; @@ -97,20 +98,6 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint128 constant DEFAULT_PROVIDER_FEE_PER_GAS = 1 wei; uint128 constant DEFAULT_PROVIDER_BASE_FEE = 1 wei; uint128 constant DEFAULT_PROVIDER_FEE_PER_FEED = 10 wei; - uint constant MOCK_PYTH_FEE_PER_FEED = 10 wei; - - uint128 constant CALLBACK_GAS_LIMIT = 1_000_000; - bytes32 constant BTC_PRICE_FEED_ID = - 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43; - bytes32 constant ETH_PRICE_FEED_ID = - 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; - - // Price feed constants - int8 constant MOCK_PRICE_FEED_EXPO = -8; - int64 constant MOCK_BTC_PRICE = 5_000_000_000_000; // $50,000 - int64 constant MOCK_ETH_PRICE = 300_000_000_000; // $3,000 - uint64 constant MOCK_BTC_CONF = 10_000_000_000; // $100 - uint64 constant MOCK_ETH_CONF = 5_000_000_000; // $50 function setUp() public { owner = address(1); @@ -139,67 +126,6 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { consumer = new MockPulseConsumer(address(proxy)); } - // Helper function to create price IDs array - function createPriceIds() internal pure returns (bytes32[] memory) { - bytes32[] memory priceIds = new bytes32[](2); - priceIds[0] = BTC_PRICE_FEED_ID; - priceIds[1] = ETH_PRICE_FEED_ID; - return priceIds; - } - - // Helper function to create mock price feeds - function createMockPriceFeeds( - uint256 publishTime - ) internal pure returns (PythStructs.PriceFeed[] memory) { - PythStructs.PriceFeed[] memory priceFeeds = new PythStructs.PriceFeed[]( - 2 - ); - - priceFeeds[0].id = BTC_PRICE_FEED_ID; - priceFeeds[0].price.price = MOCK_BTC_PRICE; - priceFeeds[0].price.conf = MOCK_BTC_CONF; - priceFeeds[0].price.expo = MOCK_PRICE_FEED_EXPO; - priceFeeds[0].price.publishTime = publishTime; - - priceFeeds[1].id = ETH_PRICE_FEED_ID; - priceFeeds[1].price.price = MOCK_ETH_PRICE; - priceFeeds[1].price.conf = MOCK_ETH_CONF; - priceFeeds[1].price.expo = MOCK_PRICE_FEED_EXPO; - priceFeeds[1].price.publishTime = publishTime; - - return priceFeeds; - } - - // Helper function to mock Pyth response - function mockParsePriceFeedUpdates( - PythStructs.PriceFeed[] memory priceFeeds - ) internal { - uint expectedFee = MOCK_PYTH_FEE_PER_FEED * priceFeeds.length; - - vm.mockCall( - address(pyth), - abi.encodeWithSelector(IPyth.getUpdateFee.selector), - abi.encode(expectedFee) - ); - - vm.mockCall( - address(pyth), - expectedFee, - abi.encodeWithSelector(IPyth.parsePriceFeedUpdates.selector), - abi.encode(priceFeeds) - ); - } - - // Helper function to create mock update data - function createMockUpdateData( - PythStructs.PriceFeed[] memory priceFeeds - ) internal pure returns (bytes[] memory) { - bytes[] memory updateData = new bytes[](2); - updateData[0] = abi.encode(priceFeeds[0]); - updateData[1] = abi.encode(priceFeeds[1]); - return updateData; - } - // Helper function to calculate total fee // FIXME: I think this helper probably needs to take some arguments. function calculateTotalFee() internal view returns (uint128) { @@ -207,34 +133,6 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { pulse.getFee(defaultProvider, CALLBACK_GAS_LIMIT, createPriceIds()); } - // Helper function to setup consumer request - function setupConsumerRequest( - address consumerAddress - ) - internal - returns ( - uint64 sequenceNumber, - bytes32[] memory priceIds, - uint64 publishTime - ) - { - priceIds = createPriceIds(); - publishTime = SafeCast.toUint64(block.timestamp); - vm.deal(consumerAddress, 1 gwei); - - uint128 totalFee = calculateTotalFee(); - - vm.prank(consumerAddress); - sequenceNumber = pulse.requestPriceUpdatesWithCallback{value: totalFee}( - defaultProvider, - publishTime, - priceIds, - CALLBACK_GAS_LIMIT - ); - - return (sequenceNumber, priceIds, publishTime); - } - function testRequestPriceUpdate() public { // Set a realistic gas price vm.txGasPrice(30 gwei); @@ -334,7 +232,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { publishTime ); // FIXME: this test doesn't ensure the Pyth fee is paid. - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); // Create arrays for expected event data int64[] memory expectedPrices = new int64[](2); @@ -405,12 +303,16 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint64 sequenceNumber, bytes32[] memory priceIds, uint256 publishTime - ) = setupConsumerRequest(address(failingConsumer)); + ) = setupConsumerRequest( + pulse, + defaultProvider, + address(failingConsumer) + ); PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); vm.expectEmit(); @@ -440,12 +342,16 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint64 sequenceNumber, bytes32[] memory priceIds, uint256 publishTime - ) = setupConsumerRequest(address(failingConsumer)); + ) = setupConsumerRequest( + pulse, + defaultProvider, + address(failingConsumer) + ); PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); vm.expectEmit(); @@ -472,13 +378,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint64 sequenceNumber, bytes32[] memory priceIds, uint256 publishTime - ) = setupConsumerRequest(address(consumer)); + ) = setupConsumerRequest(pulse, defaultProvider, address(consumer)); // Setup mock data PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); // Try executing with only 100K gas when 1M is required @@ -508,7 +414,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( futureTime // Mock price feeds with future timestamp ); - mockParsePriceFeedUpdates(priceFeeds); // This will make parsePriceFeedUpdates return future-dated prices + mockParsePriceFeedUpdates(pyth, priceFeeds); // This will make parsePriceFeedUpdates return future-dated prices bytes[] memory updateData = createMockUpdateData(priceFeeds); vm.prank(defaultProvider); @@ -555,12 +461,12 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint64 sequenceNumber, bytes32[] memory priceIds, uint256 publishTime - ) = setupConsumerRequest(address(consumer)); + ) = setupConsumerRequest(pulse, defaultProvider, address(consumer)); PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); // First execution @@ -747,7 +653,11 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint256 publishTime = block.timestamp; // Setup request - (uint64 sequenceNumber, , ) = setupConsumerRequest(address(consumer)); + (uint64 sequenceNumber, , ) = setupConsumerRequest( + pulse, + defaultProvider, + address(consumer) + ); // Create different priceIds bytes32[] memory wrongPriceIds = new bytes32[](2); @@ -757,7 +667,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); // Should revert when trying to execute with wrong priceIds @@ -923,13 +833,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint64 sequenceNumber, bytes32[] memory priceIds, uint256 publishTime - ) = setupConsumerRequest(address(consumer)); + ) = setupConsumerRequest(pulse, defaultProvider, address(consumer)); // Setup mock data PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); // Try to execute with second provider during exclusivity period @@ -965,13 +875,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint64 sequenceNumber, bytes32[] memory priceIds, uint256 publishTime - ) = setupConsumerRequest(address(consumer)); + ) = setupConsumerRequest(pulse, defaultProvider, address(consumer)); // Setup mock data PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); // Wait for exclusivity period to end @@ -1006,13 +916,13 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { uint64 sequenceNumber, bytes32[] memory priceIds, uint256 publishTime - ) = setupConsumerRequest(address(consumer)); + ) = setupConsumerRequest(pulse, defaultProvider, address(consumer)); // Setup mock data PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); // Try at 29 seconds (should fail for second provider) @@ -1080,7 +990,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( SafeCast.toUint64(block.timestamp) ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); updateData = createMockUpdateData(priceFeeds); vm.deal(defaultProvider, 2 ether); // Increase ETH allocation to prevent OutOfFunds @@ -1208,7 +1118,7 @@ contract PulseTest is Test, PulseEvents, IPulseConsumer { PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( publishTime ); - mockParsePriceFeedUpdates(priceFeeds); + mockParsePriceFeedUpdates(pyth, priceFeeds); bytes[] memory updateData = createMockUpdateData(priceFeeds); // Create 20 requests with some gaps diff --git a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol index d97773b196..313748a5f5 100644 --- a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol @@ -9,11 +9,12 @@ import "../contracts/pulse/IPulse.sol"; import "../contracts/pulse/PulseState.sol"; import "../contracts/pulse/PulseEvents.sol"; import "../contracts/pulse/PulseErrors.sol"; +import "./utils/PulseTestUtils.t.sol"; // TODO // - what's the impact of # of in-flight requests on gas usage? More requests => more hashes to // verify the provider's value. -contract PulseGasBenchmark is Test { +contract PulseGasBenchmark is Test, PulseTestUtils { ERC1967Proxy public proxy; PulseUpgradeable public pulse; IPulseConsumer public consumer; @@ -55,16 +56,40 @@ contract PulseGasBenchmark is Test { consumer = new VoidPulseConsumer(address(proxy)); } + // Estimate how much gas is used by all of the data mocking functionality in the other gas benchmarks. + // Subtract this amount from the gas benchmarks to estimate the true usafe of the pulse flow. + function testDataMocking() public { + uint64 timestamp = SafeCast.toUint64(block.timestamp); + createPriceIds(); + + PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( + timestamp + ); + mockParsePriceFeedUpdates(pyth, priceFeeds); + createMockUpdateData(priceFeeds); + } + function testBasicFlow() public { + uint64 timestamp = SafeCast.toUint64(block.timestamp); bytes32[] memory priceIds = createPriceIds(); - uint64 publishTime = SafeCast.toUint64(block.timestamp); - uint128 totalFee = calculateTotalFee(); + uint128 callbackGasLimit = 100000; + uint128 totalFee = pulse.getFee( + defaultProvider, + callbackGasLimit, + priceIds + ); vm.deal(address(consumer), 1 ether); vm.prank(address(consumer)); uint64 sequenceNumber = pulse.requestPriceUpdatesWithCallback{ value: totalFee - }(defaultProvider, publishTime, priceIds, 100000); + }(defaultProvider, timestamp, priceIds, callbackGasLimit); + + PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( + timestamp + ); + mockParsePriceFeedUpdates(pyth, priceFeeds); + bytes[] memory updateData = createMockUpdateData(priceFeeds); pulse.executeCallback( defaultProvider, From 9991fe7fae34350070414686b71eb8752a3b34b9 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Sun, 9 Mar 2025 07:03:48 -0700 Subject: [PATCH 3/6] cleanup --- .../ethereum/contracts/forge-test/PulseGasBenchmark.t.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol index 313748a5f5..b3d66f69f0 100644 --- a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol @@ -11,9 +11,6 @@ import "../contracts/pulse/PulseEvents.sol"; import "../contracts/pulse/PulseErrors.sol"; import "./utils/PulseTestUtils.t.sol"; -// TODO -// - what's the impact of # of in-flight requests on gas usage? More requests => more hashes to -// verify the provider's value. contract PulseGasBenchmark is Test, PulseTestUtils { ERC1967Proxy public proxy; PulseUpgradeable public pulse; From 347c17fd38e931d77fa2bf0449e44b1301dff837 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Tue, 11 Mar 2025 12:38:18 -0700 Subject: [PATCH 4/6] forgot this file --- .../forge-test/utils/PulseTestUtils.t.sol | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 target_chains/ethereum/contracts/forge-test/utils/PulseTestUtils.t.sol diff --git a/target_chains/ethereum/contracts/forge-test/utils/PulseTestUtils.t.sol b/target_chains/ethereum/contracts/forge-test/utils/PulseTestUtils.t.sol new file mode 100644 index 0000000000..9341cac989 --- /dev/null +++ b/target_chains/ethereum/contracts/forge-test/utils/PulseTestUtils.t.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: Apache 2 + +pragma solidity ^0.8.0; + +import "forge-std/Test.sol"; +import "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import "../../contracts/pulse/IPulse.sol"; + +abstract contract PulseTestUtils is Test { + bytes32 constant BTC_PRICE_FEED_ID = + 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43; + bytes32 constant ETH_PRICE_FEED_ID = + 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; + + // Price feed constants + int8 constant MOCK_PRICE_FEED_EXPO = -8; + int64 constant MOCK_BTC_PRICE = 5_000_000_000_000; // $50,000 + int64 constant MOCK_ETH_PRICE = 300_000_000_000; // $3,000 + uint64 constant MOCK_BTC_CONF = 10_000_000_000; // $100 + uint64 constant MOCK_ETH_CONF = 5_000_000_000; // $50 + + // Fee charged by the Pyth oracle per price feed + uint constant MOCK_PYTH_FEE_PER_FEED = 10 wei; + + uint128 constant CALLBACK_GAS_LIMIT = 1_000_000; + + // Helper function to create price IDs array + function createPriceIds() internal pure returns (bytes32[] memory) { + bytes32[] memory priceIds = new bytes32[](2); + priceIds[0] = BTC_PRICE_FEED_ID; + priceIds[1] = ETH_PRICE_FEED_ID; + return priceIds; + } + + // Helper function to create mock price feeds + function createMockPriceFeeds( + uint256 publishTime + ) internal pure returns (PythStructs.PriceFeed[] memory) { + PythStructs.PriceFeed[] memory priceFeeds = new PythStructs.PriceFeed[]( + 2 + ); + + priceFeeds[0].id = BTC_PRICE_FEED_ID; + priceFeeds[0].price.price = MOCK_BTC_PRICE; + priceFeeds[0].price.conf = MOCK_BTC_CONF; + priceFeeds[0].price.expo = MOCK_PRICE_FEED_EXPO; + priceFeeds[0].price.publishTime = publishTime; + + priceFeeds[1].id = ETH_PRICE_FEED_ID; + priceFeeds[1].price.price = MOCK_ETH_PRICE; + priceFeeds[1].price.conf = MOCK_ETH_CONF; + priceFeeds[1].price.expo = MOCK_PRICE_FEED_EXPO; + priceFeeds[1].price.publishTime = publishTime; + + return priceFeeds; + } + + // Helper function to mock Pyth response + function mockParsePriceFeedUpdates( + address pyth, + PythStructs.PriceFeed[] memory priceFeeds + ) internal { + uint expectedFee = MOCK_PYTH_FEE_PER_FEED * priceFeeds.length; + + vm.mockCall( + pyth, + abi.encodeWithSelector(IPyth.getUpdateFee.selector), + abi.encode(expectedFee) + ); + + vm.mockCall( + pyth, + expectedFee, + abi.encodeWithSelector(IPyth.parsePriceFeedUpdates.selector), + abi.encode(priceFeeds) + ); + } + + // Helper function to create mock update data + function createMockUpdateData( + PythStructs.PriceFeed[] memory priceFeeds + ) internal pure returns (bytes[] memory) { + bytes[] memory updateData = new bytes[](2); + updateData[0] = abi.encode(priceFeeds[0]); + updateData[1] = abi.encode(priceFeeds[1]); + return updateData; + } + + // Helper function to setup consumer request + function setupConsumerRequest( + IPulse pulse, + address provider, + address consumerAddress + ) + internal + returns ( + uint64 sequenceNumber, + bytes32[] memory priceIds, + uint64 publishTime + ) + { + priceIds = createPriceIds(); + publishTime = SafeCast.toUint64(block.timestamp); + vm.deal(consumerAddress, 1 gwei); + + uint128 totalFee = pulse.getFee(provider, CALLBACK_GAS_LIMIT, priceIds); + + vm.prank(consumerAddress); + sequenceNumber = pulse.requestPriceUpdatesWithCallback{value: totalFee}( + provider, + publishTime, + priceIds, + CALLBACK_GAS_LIMIT + ); + + return (sequenceNumber, priceIds, publishTime); + } +} From c4a9cd811dee6926e4019e26fa4dd0c444eca908 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Wed, 12 Mar 2025 09:13:11 -0700 Subject: [PATCH 5/6] pr comments --- .../ethereum/contracts/forge-test/PulseGasBenchmark.t.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol index b3d66f69f0..b078235371 100644 --- a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol @@ -54,10 +54,10 @@ contract PulseGasBenchmark is Test, PulseTestUtils { } // Estimate how much gas is used by all of the data mocking functionality in the other gas benchmarks. - // Subtract this amount from the gas benchmarks to estimate the true usafe of the pulse flow. + // Subtract this amount from the gas benchmarks to estimate the true usage of the pulse flow. function testDataMocking() public { uint64 timestamp = SafeCast.toUint64(block.timestamp); - createPriceIds(); + bytes32[] memory priceIds = createPriceIds(); PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( timestamp @@ -97,6 +97,8 @@ contract PulseGasBenchmark is Test, PulseTestUtils { } } +// A simple consumer that does nothing with the price updates. +// Used to estimate the gas usage of the pulse flow. contract VoidPulseConsumer is IPulseConsumer { address private _pulse; From e9993e49b9763bb2cc1ac50732941207bb467a57 Mon Sep 17 00:00:00 2001 From: Jayant Krishnamurthy Date: Fri, 14 Mar 2025 04:15:35 -0700 Subject: [PATCH 6/6] fix ci --- apps/argus/Cargo.lock | 2 +- .../ethereum/contracts/forge-test/PulseGasBenchmark.t.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/argus/Cargo.lock b/apps/argus/Cargo.lock index 1d30bf76ce..7569c01028 100644 --- a/apps/argus/Cargo.lock +++ b/apps/argus/Cargo.lock @@ -1594,7 +1594,7 @@ dependencies = [ [[package]] name = "fortuna" -version = "7.4.7" +version = "7.4.8" dependencies = [ "anyhow", "axum", diff --git a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol index b078235371..fcba9908cb 100644 --- a/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol +++ b/target_chains/ethereum/contracts/forge-test/PulseGasBenchmark.t.sol @@ -57,7 +57,7 @@ contract PulseGasBenchmark is Test, PulseTestUtils { // Subtract this amount from the gas benchmarks to estimate the true usage of the pulse flow. function testDataMocking() public { uint64 timestamp = SafeCast.toUint64(block.timestamp); - bytes32[] memory priceIds = createPriceIds(); + createPriceIds(); PythStructs.PriceFeed[] memory priceFeeds = createMockPriceFeeds( timestamp