diff --git a/contract_manager/scripts/check_proposal.ts b/contract_manager/scripts/check_proposal.ts index 529af177ee..686b790548 100644 --- a/contract_manager/scripts/check_proposal.ts +++ b/contract_manager/scripts/check_proposal.ts @@ -7,7 +7,7 @@ import { createHash } from "node:crypto"; -import NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet"; +import { Wallet } from "@coral-xyz/anchor"; import type { PythCluster } from "@pythnetwork/client/lib/cluster"; import { getPythClusterApiUrl } from "@pythnetwork/client/lib/cluster"; import { @@ -21,7 +21,7 @@ import { } from "@pythnetwork/xc-admin-common"; import type { AccountMeta } from "@solana/web3.js"; import { Keypair, PublicKey } from "@solana/web3.js"; -import SquadsMesh from "@sqds/mesh"; +import SquadsMeshClass from "@sqds/mesh"; import Web3 from "web3"; import yargs from "yargs"; import { hideBin } from "yargs/helpers"; @@ -32,9 +32,18 @@ import { EvmPriceFeedContract, getCodeDigestWithoutAddress, EvmWormholeContract, + EvmLazerContract, } from "../src/core/contracts/evm"; import { DefaultStore } from "../src/node/utils/store"; +function getSquadsMesh() { + // Handle nested default export from @sqds/mesh + return ( + (SquadsMeshClass as { default?: typeof SquadsMeshClass }).default ?? + SquadsMeshClass + ); +} + const parser = yargs(hideBin(process.argv)) .usage("Usage: $0 --cluster --proposal ") .options({ @@ -48,14 +57,21 @@ const parser = yargs(hideBin(process.argv)) demandOption: true, desc: "The proposal address to check", }, + "contract-type": { + type: "string", + demandOption: false, + desc: "Type of EVM contract to verify (entropy or lazer). Required when checking EvmExecute instructions.", + choices: ["entropy", "lazer"], + }, }); async function main() { const argv = await parser.argv; const cluster = argv.cluster as PythCluster; - const squad = SquadsMesh.endpoint( + const mesh = getSquadsMesh(); + const squad = mesh.endpoint( getPythClusterApiUrl(cluster), - new NodeWallet(Keypair.generate()), // dummy wallet + new Wallet(Keypair.generate()), // dummy wallet ); const transaction = await squad.getTransaction(new PublicKey(argv.proposal)); const instructions = await getProposalInstructions(squad, transaction); @@ -148,7 +164,7 @@ async function main() { if (instruction.governanceAction instanceof EvmExecute) { // Note: it only checks for upgrade entropy contracts right now console.log( - `Verifying EVMExecute on ${instruction.governanceAction.targetChainId}`, + `\nVerifying EVMExecute on ${instruction.governanceAction.targetChainId}`, ); for (const chain of Object.values(DefaultStore.chains)) { if ( @@ -161,9 +177,13 @@ async function main() { const callAddress = instruction.governanceAction.callAddress; const calldata = instruction.governanceAction.calldata; - // TODO: If we add additional EVM contracts using the executor, we need to - // add some logic here to identify what kind of contract is at the call address. - const contract = new EvmEntropyContract(chain, callAddress); + // Get contract type from flag, default to "entropy" for backward compatibility + const contractType = argv["contract-type"] ?? "entropy"; + + const contract: EvmEntropyContract | EvmLazerContract = + contractType === "lazer" + ? new EvmLazerContract(chain, callAddress) + : new EvmEntropyContract(chain, callAddress); const owner = await contract.getOwner(); if ( diff --git a/contract_manager/src/node/utils/governance.ts b/contract_manager/src/node/utils/governance.ts index c2793374ba..bd641de72a 100644 --- a/contract_manager/src/node/utils/governance.ts +++ b/contract_manager/src/node/utils/governance.ts @@ -282,7 +282,11 @@ export class WormholeMultisigProposal { type SquadsMeshInstance = InstanceType; function getSquadsMesh() { - return SquadsMeshClass; + // Handle nested default export from @sqds/mesh + return ( + (SquadsMeshClass as { default?: typeof SquadsMeshClass }).default ?? + SquadsMeshClass + ); } export class Vault extends Storable { diff --git a/contract_manager/src/store/chains/EvmChains.json b/contract_manager/src/store/chains/EvmChains.json index ff5042f397..39c6d14306 100644 --- a/contract_manager/src/store/chains/EvmChains.json +++ b/contract_manager/src/store/chains/EvmChains.json @@ -1323,9 +1323,9 @@ "type": "EvmChain" }, { - "id": "itsnotreal2", + "id": "ethereal", "mainnet": true, - "rpcUrl": "https://rpc.itsnotreal2.lol", + "rpcUrl": "https://rpc.ethereal.trade", "networkId": 5064014, "type": "EvmChain" }, diff --git a/contract_manager/src/store/contracts/EvmExecutorContracts.json b/contract_manager/src/store/contracts/EvmExecutorContracts.json index 99426ab7a5..4091558638 100644 --- a/contract_manager/src/store/contracts/EvmExecutorContracts.json +++ b/contract_manager/src/store/contracts/EvmExecutorContracts.json @@ -230,7 +230,7 @@ "type": "EvmExecutorContract" }, { - "chain": "itsnotreal2", + "chain": "ethereal", "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", "type": "EvmExecutorContract" }, diff --git a/contract_manager/src/store/contracts/EvmLazerContracts.json b/contract_manager/src/store/contracts/EvmLazerContracts.json index 40840d0615..d4427d6e1c 100644 --- a/contract_manager/src/store/contracts/EvmLazerContracts.json +++ b/contract_manager/src/store/contracts/EvmLazerContracts.json @@ -4,11 +4,6 @@ "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481", "type": "EvmLazerContract" }, - { - "chain": "ethereal_testnet", - "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481", - "type": "EvmLazerContract" - }, { "chain": "ethereal_testnet_v2", "address": "0x4D4772F06c595F69FB57039599a180536FDE8245", @@ -60,7 +55,7 @@ "type": "EvmLazerContract" }, { - "chain": "itsnotreal2", + "chain": "ethereal", "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481", "type": "EvmLazerContract" }, diff --git a/contract_manager/src/store/contracts/EvmWormholeContracts.json b/contract_manager/src/store/contracts/EvmWormholeContracts.json index c9627994f2..8db036885c 100644 --- a/contract_manager/src/store/contracts/EvmWormholeContracts.json +++ b/contract_manager/src/store/contracts/EvmWormholeContracts.json @@ -880,7 +880,7 @@ "type": "EvmWormholeContract" }, { - "chain": "itsnotreal2", + "chain": "ethereal", "address": "0x35a58BeeE77a2Ad547FcDed7e8CB1c6e19746b13", "type": "EvmWormholeContract" }, diff --git a/governance/xc_admin/packages/xc_admin_common/src/chains.ts b/governance/xc_admin/packages/xc_admin_common/src/chains.ts index a80da3f88d..77a5cb3236 100644 --- a/governance/xc_admin/packages/xc_admin_common/src/chains.ts +++ b/governance/xc_admin/packages/xc_admin_common/src/chains.ts @@ -120,7 +120,7 @@ export const RECEIVER_CHAINS = { zero_gravity: 60088, itsnotreal: 60089, // Deprecated plasma: 60090, - itsnotreal2: 60091, + ethereal: 60091, injective_evm: 60092, orange: 60093, diff --git a/lazer/contracts/evm/foundry.toml b/lazer/contracts/evm/foundry.toml index b41cddc9fb..bbaf1ca904 100644 --- a/lazer/contracts/evm/foundry.toml +++ b/lazer/contracts/evm/foundry.toml @@ -7,3 +7,4 @@ fs_permissions = [{ access = "read-write", path = "."}] optimizer = true optimizer_runs = 100000 solc_version = "0.8.23" +evm_version = "paris" \ No newline at end of file diff --git a/lazer/contracts/evm/src/PythLazerLib.sol b/lazer/contracts/evm/src/PythLazerLib.sol index 2ac31032b0..418ac0b259 100644 --- a/lazer/contracts/evm/src/PythLazerLib.sol +++ b/lazer/contracts/evm/src/PythLazerLib.sol @@ -118,7 +118,7 @@ library PythLazerLib { function parseFeedValueUint64( bytes calldata update, uint16 pos - ) public pure returns (uint64 value, uint16 new_pos) { + ) internal pure returns (uint64 value, uint16 new_pos) { value = uint64(bytes8(update[pos:pos + 8])); pos += 8; new_pos = pos; @@ -127,7 +127,7 @@ library PythLazerLib { function parseFeedValueInt64( bytes calldata update, uint16 pos - ) public pure returns (int64 value, uint16 new_pos) { + ) internal pure returns (int64 value, uint16 new_pos) { value = int64(uint64(bytes8(update[pos:pos + 8]))); pos += 8; new_pos = pos; @@ -136,7 +136,7 @@ library PythLazerLib { function parseFeedValueUint16( bytes calldata update, uint16 pos - ) public pure returns (uint16 value, uint16 new_pos) { + ) internal pure returns (uint16 value, uint16 new_pos) { value = uint16(bytes2(update[pos:pos + 2])); pos += 2; new_pos = pos; @@ -145,7 +145,7 @@ library PythLazerLib { function parseFeedValueInt16( bytes calldata update, uint16 pos - ) public pure returns (int16 value, uint16 new_pos) { + ) internal pure returns (int16 value, uint16 new_pos) { value = int16(uint16(bytes2(update[pos:pos + 2]))); pos += 2; new_pos = pos; @@ -154,7 +154,7 @@ library PythLazerLib { function parseFeedValueUint8( bytes calldata update, uint16 pos - ) public pure returns (uint8 value, uint16 new_pos) { + ) internal pure returns (uint8 value, uint16 new_pos) { value = uint8(update[pos]); pos += 1; new_pos = pos; @@ -166,7 +166,7 @@ library PythLazerLib { /// @return update The parsed Update struct containing all feeds and their properties function parseUpdateFromPayload( bytes calldata payload - ) public pure returns (PythLazerStructs.Update memory update) { + ) internal pure returns (PythLazerStructs.Update memory update) { // Parse payload header uint16 pos; uint8 feedsLen; @@ -447,14 +447,14 @@ library PythLazerLib { /// @notice Check if price exists function hasPrice( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue(feed, uint8(PythLazerStructs.PriceFeedProperty.Price)); } /// @notice Check if best bid price exists function hasBestBidPrice( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -465,7 +465,7 @@ library PythLazerLib { /// @notice Check if best ask price exists function hasBestAskPrice( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -476,7 +476,7 @@ library PythLazerLib { /// @notice Check if publisher count exists function hasPublisherCount( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -487,7 +487,7 @@ library PythLazerLib { /// @notice Check if exponent exists function hasExponent( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue(feed, uint8(PythLazerStructs.PriceFeedProperty.Exponent)); } @@ -495,7 +495,7 @@ library PythLazerLib { /// @notice Check if confidence exists function hasConfidence( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -506,7 +506,7 @@ library PythLazerLib { /// @notice Check if funding rate exists function hasFundingRate( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -517,7 +517,7 @@ library PythLazerLib { /// @notice Check if funding timestamp exists function hasFundingTimestamp( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -528,7 +528,7 @@ library PythLazerLib { /// @notice Check if funding rate interval exists function hasFundingRateInterval( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -539,7 +539,7 @@ library PythLazerLib { /// @notice Check if market session exists function hasMarketSession( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _hasValue( feed, @@ -550,14 +550,14 @@ library PythLazerLib { // Requested helpers — property included in this update function isPriceRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested(feed, uint8(PythLazerStructs.PriceFeedProperty.Price)); } function isBestBidPriceRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -567,7 +567,7 @@ library PythLazerLib { function isBestAskPriceRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -577,7 +577,7 @@ library PythLazerLib { function isPublisherCountRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -587,7 +587,7 @@ library PythLazerLib { function isExponentRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -597,7 +597,7 @@ library PythLazerLib { function isConfidenceRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -607,7 +607,7 @@ library PythLazerLib { function isFundingRateRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -617,7 +617,7 @@ library PythLazerLib { function isFundingTimestampRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -627,7 +627,7 @@ library PythLazerLib { function isFundingRateIntervalRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -637,7 +637,7 @@ library PythLazerLib { function isMarketSessionRequested( PythLazerStructs.Feed memory feed - ) public pure returns (bool) { + ) internal pure returns (bool) { return _isRequested( feed, @@ -650,7 +650,7 @@ library PythLazerLib { /// @notice Get price (reverts if not exists) function getPrice( PythLazerStructs.Feed memory feed - ) public pure returns (int64) { + ) internal pure returns (int64) { require( isPriceRequested(feed), "Price is not requested for the timestamp" @@ -662,7 +662,7 @@ library PythLazerLib { /// @notice Get best bid price (reverts if not exists) function getBestBidPrice( PythLazerStructs.Feed memory feed - ) public pure returns (int64) { + ) internal pure returns (int64) { require( isBestBidPriceRequested(feed), "Best bid price is not requested for the timestamp" @@ -677,7 +677,7 @@ library PythLazerLib { /// @notice Get best ask price (reverts if not exists) function getBestAskPrice( PythLazerStructs.Feed memory feed - ) public pure returns (int64) { + ) internal pure returns (int64) { require( isBestAskPriceRequested(feed), "Best ask price is not requested for the timestamp" @@ -692,7 +692,7 @@ library PythLazerLib { /// @notice Get publisher count (reverts if not exists) function getPublisherCount( PythLazerStructs.Feed memory feed - ) public pure returns (uint16) { + ) internal pure returns (uint16) { require( isPublisherCountRequested(feed), "Publisher count is not requested for the timestamp" @@ -707,7 +707,7 @@ library PythLazerLib { /// @notice Get exponent (reverts if not exists) function getExponent( PythLazerStructs.Feed memory feed - ) public pure returns (int16) { + ) internal pure returns (int16) { require( isExponentRequested(feed), "Exponent is not requested for the timestamp" @@ -719,7 +719,7 @@ library PythLazerLib { /// @notice Get confidence (reverts if not exists) function getConfidence( PythLazerStructs.Feed memory feed - ) public pure returns (uint64) { + ) internal pure returns (uint64) { require( isConfidenceRequested(feed), "Confidence is not requested for the timestamp" @@ -734,7 +734,7 @@ library PythLazerLib { /// @notice Get funding rate (reverts if not exists) function getFundingRate( PythLazerStructs.Feed memory feed - ) public pure returns (int64) { + ) internal pure returns (int64) { require( isFundingRateRequested(feed), "Funding rate is not requested for the timestamp" @@ -749,7 +749,7 @@ library PythLazerLib { /// @notice Get funding timestamp (reverts if not exists) function getFundingTimestamp( PythLazerStructs.Feed memory feed - ) public pure returns (uint64) { + ) internal pure returns (uint64) { require( isFundingTimestampRequested(feed), "Funding timestamp is not requested for the timestamp" @@ -764,7 +764,7 @@ library PythLazerLib { /// @notice Get funding rate interval (reverts if not exists) function getFundingRateInterval( PythLazerStructs.Feed memory feed - ) public pure returns (uint64) { + ) internal pure returns (uint64) { require( isFundingRateIntervalRequested(feed), "Funding rate interval is not requested for the timestamp" diff --git a/lazer/contracts/evm/test/PythLazer.t.sol b/lazer/contracts/evm/test/PythLazer.t.sol index 170cfde078..87412b8108 100644 --- a/lazer/contracts/evm/test/PythLazer.t.sol +++ b/lazer/contracts/evm/test/PythLazer.t.sol @@ -5,10 +5,12 @@ import {Test, console} from "forge-std/Test.sol"; import {PythLazer} from "../src/PythLazer.sol"; import {PythLazerLib} from "../src/PythLazerLib.sol"; import {PythLazerStructs} from "../src/PythLazerStructs.sol"; +import {PythLazerLibTestHelper} from "./PythLazerLibTestHelper.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; contract PythLazerTest is Test { PythLazer public pythLazer; + PythLazerLibTestHelper public libHelper; address owner; function setUp() public { @@ -20,6 +22,7 @@ contract PythLazerTest is Test { abi.encodeWithSelector(PythLazer.initialize.selector, owner) ); pythLazer = PythLazer(address(proxy)); + libHelper = new PythLazerLibTestHelper(); } function test_update_add_signer() public { @@ -173,7 +176,7 @@ contract PythLazerTest is Test { } /// @notice Test parsing single feed with all 10 properties - function test_parseUpdate_singleFeed_allProperties() public pure { + function test_parseUpdate_singleFeed_allProperties() public view { bytes[] memory properties = new bytes[](10); properties[0] = buildProperty(0, encodeInt64(100000000)); // price properties[1] = buildProperty(1, encodeInt64(99000000)); // bestBid @@ -195,7 +198,7 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); // Verify update header @@ -225,20 +228,20 @@ contract PythLazerTest is Test { ); // Verify exists flags (all should be set) - assertTrue(PythLazerLib.hasPrice(feed)); - assertTrue(PythLazerLib.hasBestBidPrice(feed)); - assertTrue(PythLazerLib.hasBestAskPrice(feed)); - assertTrue(PythLazerLib.hasPublisherCount(feed)); - assertTrue(PythLazerLib.hasExponent(feed)); - assertTrue(PythLazerLib.hasConfidence(feed)); - assertTrue(PythLazerLib.hasFundingRate(feed)); - assertTrue(PythLazerLib.hasFundingTimestamp(feed)); - assertTrue(PythLazerLib.hasFundingRateInterval(feed)); - assertTrue(PythLazerLib.hasMarketSession(feed)); + assertTrue(libHelper.hasPrice(feed)); + assertTrue(libHelper.hasBestBidPrice(feed)); + assertTrue(libHelper.hasBestAskPrice(feed)); + assertTrue(libHelper.hasPublisherCount(feed)); + assertTrue(libHelper.hasExponent(feed)); + assertTrue(libHelper.hasConfidence(feed)); + assertTrue(libHelper.hasFundingRate(feed)); + assertTrue(libHelper.hasFundingTimestamp(feed)); + assertTrue(libHelper.hasFundingRateInterval(feed)); + assertTrue(libHelper.hasMarketSession(feed)); } /// @notice Test parsing single feed with minimal properties - function test_parseUpdate_singleFeed_minimalProperties() public pure { + function test_parseUpdate_singleFeed_minimalProperties() public { bytes[] memory properties = new bytes[](2); properties[0] = buildProperty(0, encodeInt64(50000000)); properties[1] = buildProperty(4, encodeInt16(-6)); @@ -252,7 +255,7 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); assertEq(update.feeds.length, 1); @@ -264,26 +267,26 @@ contract PythLazerTest is Test { assertEq(feed._exponent, -6); // Only price and exponent should exist - assertTrue(PythLazerLib.hasPrice(feed)); - assertTrue(PythLazerLib.hasExponent(feed)); - assertFalse(PythLazerLib.hasBestBidPrice(feed)); - assertFalse(PythLazerLib.hasConfidence(feed)); + assertTrue(libHelper.hasPrice(feed)); + assertTrue(libHelper.hasExponent(feed)); + assertFalse(libHelper.hasBestBidPrice(feed)); + assertFalse(libHelper.hasConfidence(feed)); // Requested checks (tri-state applicability) - assertTrue(PythLazerLib.isPriceRequested(feed)); - assertTrue(PythLazerLib.isExponentRequested(feed)); - assertFalse(PythLazerLib.isBestBidPriceRequested(feed)); - assertFalse(PythLazerLib.isBestAskPriceRequested(feed)); - assertFalse(PythLazerLib.isPublisherCountRequested(feed)); - assertFalse(PythLazerLib.isConfidenceRequested(feed)); - assertFalse(PythLazerLib.isFundingRateRequested(feed)); - assertFalse(PythLazerLib.isFundingTimestampRequested(feed)); - assertFalse(PythLazerLib.isFundingRateIntervalRequested(feed)); - assertFalse(PythLazerLib.isMarketSessionRequested(feed)); + assertTrue(libHelper.isPriceRequested(feed)); + assertTrue(libHelper.isExponentRequested(feed)); + assertFalse(libHelper.isBestBidPriceRequested(feed)); + assertFalse(libHelper.isBestAskPriceRequested(feed)); + assertFalse(libHelper.isPublisherCountRequested(feed)); + assertFalse(libHelper.isConfidenceRequested(feed)); + assertFalse(libHelper.isFundingRateRequested(feed)); + assertFalse(libHelper.isFundingTimestampRequested(feed)); + assertFalse(libHelper.isFundingRateIntervalRequested(feed)); + assertFalse(libHelper.isMarketSessionRequested(feed)); } /// @notice Test parsing multiple feeds - function test_parseUpdate_multipleFeeds() public pure { + function test_parseUpdate_multipleFeeds() public { // Feed 1 bytes[] memory props1 = new bytes[](5); props1[0] = buildProperty(0, encodeInt64(50000000000)); @@ -314,7 +317,7 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); assertEq(update.feeds.length, 3); @@ -323,39 +326,35 @@ contract PythLazerTest is Test { assertEq(update.feeds[0].feedId, 1); assertEq(PythLazerLib.getFeedId(update.feeds[0]), 1); assertEq(update.feeds[0]._price, 50000000000); - assertTrue(PythLazerLib.hasConfidence(update.feeds[0])); + assertTrue(libHelper.hasConfidence(update.feeds[0])); // Requested checks for Feed 1 (props: price, publisherCount, exponent, confidence, bestBid) - assertTrue(PythLazerLib.isPriceRequested(update.feeds[0])); - assertTrue(PythLazerLib.isPublisherCountRequested(update.feeds[0])); - assertTrue(PythLazerLib.isExponentRequested(update.feeds[0])); - assertTrue(PythLazerLib.isConfidenceRequested(update.feeds[0])); - assertTrue(PythLazerLib.isBestBidPriceRequested(update.feeds[0])); - assertFalse(PythLazerLib.isBestAskPriceRequested(update.feeds[0])); - assertFalse(PythLazerLib.isFundingRateRequested(update.feeds[0])); - assertFalse(PythLazerLib.isFundingTimestampRequested(update.feeds[0])); - assertFalse( - PythLazerLib.isFundingRateIntervalRequested(update.feeds[0]) - ); - assertFalse(PythLazerLib.isMarketSessionRequested(update.feeds[0])); + assertTrue(libHelper.isPriceRequested(update.feeds[0])); + assertTrue(libHelper.isPublisherCountRequested(update.feeds[0])); + assertTrue(libHelper.isExponentRequested(update.feeds[0])); + assertTrue(libHelper.isConfidenceRequested(update.feeds[0])); + assertTrue(libHelper.isBestBidPriceRequested(update.feeds[0])); + assertFalse(libHelper.isBestAskPriceRequested(update.feeds[0])); + assertFalse(libHelper.isFundingRateRequested(update.feeds[0])); + assertFalse(libHelper.isFundingTimestampRequested(update.feeds[0])); + assertFalse(libHelper.isFundingRateIntervalRequested(update.feeds[0])); + assertFalse(libHelper.isMarketSessionRequested(update.feeds[0])); // Verify Feed 2 assertEq(update.feeds[1].feedId, 2); assertEq(PythLazerLib.getFeedId(update.feeds[1]), 2); assertEq(update.feeds[1]._price, 3000000000); - assertFalse(PythLazerLib.hasConfidence(update.feeds[1])); + assertFalse(libHelper.hasConfidence(update.feeds[1])); // Requested checks for Feed 2 (props: price, exponent) - assertTrue(PythLazerLib.isPriceRequested(update.feeds[1])); - assertTrue(PythLazerLib.isExponentRequested(update.feeds[1])); - assertFalse(PythLazerLib.isBestBidPriceRequested(update.feeds[1])); - assertFalse(PythLazerLib.isBestAskPriceRequested(update.feeds[1])); - assertFalse(PythLazerLib.isPublisherCountRequested(update.feeds[1])); - assertFalse(PythLazerLib.isConfidenceRequested(update.feeds[1])); - assertFalse(PythLazerLib.isFundingRateRequested(update.feeds[1])); - assertFalse(PythLazerLib.isFundingTimestampRequested(update.feeds[1])); - assertFalse( - PythLazerLib.isFundingRateIntervalRequested(update.feeds[1]) - ); - assertFalse(PythLazerLib.isMarketSessionRequested(update.feeds[1])); + assertTrue(libHelper.isPriceRequested(update.feeds[1])); + assertTrue(libHelper.isExponentRequested(update.feeds[1])); + assertFalse(libHelper.isBestBidPriceRequested(update.feeds[1])); + assertFalse(libHelper.isBestAskPriceRequested(update.feeds[1])); + assertFalse(libHelper.isPublisherCountRequested(update.feeds[1])); + assertFalse(libHelper.isConfidenceRequested(update.feeds[1])); + assertFalse(libHelper.isFundingRateRequested(update.feeds[1])); + assertFalse(libHelper.isFundingTimestampRequested(update.feeds[1])); + assertFalse(libHelper.isFundingRateIntervalRequested(update.feeds[1])); + assertFalse(libHelper.isMarketSessionRequested(update.feeds[1])); // Verify Feed 3 assertEq(update.feeds[2].feedId, 3); @@ -363,22 +362,20 @@ contract PythLazerTest is Test { assertEq(update.feeds[2]._price, 100000000); assertEq(update.feeds[2]._publisherCount, 7); // Requested checks for Feed 3 (props: price, exponent, publisherCount) - assertTrue(PythLazerLib.isPriceRequested(update.feeds[2])); - assertTrue(PythLazerLib.isExponentRequested(update.feeds[2])); - assertTrue(PythLazerLib.isPublisherCountRequested(update.feeds[2])); - assertFalse(PythLazerLib.isBestBidPriceRequested(update.feeds[2])); - assertFalse(PythLazerLib.isBestAskPriceRequested(update.feeds[2])); - assertFalse(PythLazerLib.isConfidenceRequested(update.feeds[2])); - assertFalse(PythLazerLib.isFundingRateRequested(update.feeds[2])); - assertFalse(PythLazerLib.isFundingTimestampRequested(update.feeds[2])); - assertFalse( - PythLazerLib.isFundingRateIntervalRequested(update.feeds[2]) - ); - assertFalse(PythLazerLib.isMarketSessionRequested(update.feeds[2])); + assertTrue(libHelper.isPriceRequested(update.feeds[2])); + assertTrue(libHelper.isExponentRequested(update.feeds[2])); + assertTrue(libHelper.isPublisherCountRequested(update.feeds[2])); + assertFalse(libHelper.isBestBidPriceRequested(update.feeds[2])); + assertFalse(libHelper.isBestAskPriceRequested(update.feeds[2])); + assertFalse(libHelper.isConfidenceRequested(update.feeds[2])); + assertFalse(libHelper.isFundingRateRequested(update.feeds[2])); + assertFalse(libHelper.isFundingTimestampRequested(update.feeds[2])); + assertFalse(libHelper.isFundingRateIntervalRequested(update.feeds[2])); + assertFalse(libHelper.isMarketSessionRequested(update.feeds[2])); } /// @notice Test parsing MarketSession property - function test_parseUpdate_marketSession() public pure { + function test_parseUpdate_marketSession() public { bytes[] memory properties = new bytes[](3); properties[0] = buildProperty(0, encodeInt64(100000000)); // price properties[1] = buildProperty(4, encodeInt16(-8)); // exponent @@ -393,7 +390,7 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); PythLazerStructs.Feed memory feed = update.feeds[0]; @@ -424,7 +421,7 @@ contract PythLazerTest is Test { feeds2 ); - PythLazerStructs.Update memory update2 = PythLazerLib + PythLazerStructs.Update memory update2 = libHelper .parseUpdateFromPayload(payload2); PythLazerStructs.Feed memory feed2 = update2.feeds[0]; @@ -439,7 +436,7 @@ contract PythLazerTest is Test { } /// @notice Test MarketSession getter when not requested - function test_parseUpdate_marketSessionNotRequested() public pure { + function test_parseUpdate_marketSessionNotRequested() public { bytes[] memory properties = new bytes[](2); properties[0] = buildProperty(0, encodeInt64(100000000)); properties[1] = buildProperty(4, encodeInt16(-8)); @@ -453,17 +450,17 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); PythLazerStructs.Feed memory feed = update.feeds[0]; - assertFalse(PythLazerLib.isMarketSessionRequested(feed)); - assertFalse(PythLazerLib.hasMarketSession(feed)); + assertFalse(libHelper.isMarketSessionRequested(feed)); + assertFalse(libHelper.hasMarketSession(feed)); } /// @notice Test when optional properties are zero - function test_parseUpdate_optionalMissing_priceZero() public pure { + function test_parseUpdate_optionalMissing_priceZero() public { bytes[] memory properties = new bytes[](3); properties[0] = buildProperty(0, encodeInt64(0)); properties[1] = buildProperty(4, encodeInt16(-8)); @@ -478,20 +475,20 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); PythLazerStructs.Feed memory feed = update.feeds[0]; assertEq(feed._price, 0); - assertTrue(PythLazerLib.isPriceRequested(feed)); - assertFalse(PythLazerLib.hasPrice(feed)); - assertTrue(PythLazerLib.hasExponent(feed)); - assertTrue(PythLazerLib.hasPublisherCount(feed)); + assertTrue(libHelper.isPriceRequested(feed)); + assertFalse(libHelper.hasPrice(feed)); + assertTrue(libHelper.hasExponent(feed)); + assertTrue(libHelper.hasPublisherCount(feed)); } /// @notice Test confidence = 0 - function test_parseUpdate_optionalMissing_confidenceZero() public pure { + function test_parseUpdate_optionalMissing_confidenceZero() public { bytes[] memory properties = new bytes[](3); properties[0] = buildProperty(0, encodeInt64(100000)); properties[1] = buildProperty(4, encodeInt16(-6)); @@ -506,19 +503,19 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); PythLazerStructs.Feed memory feed = update.feeds[0]; - assertTrue(PythLazerLib.hasPrice(feed)); - assertTrue(PythLazerLib.isConfidenceRequested(feed)); - assertFalse(PythLazerLib.hasConfidence(feed)); + assertTrue(libHelper.hasPrice(feed)); + assertTrue(libHelper.isConfidenceRequested(feed)); + assertFalse(libHelper.hasConfidence(feed)); assertEq(feed._confidence, 0); } /// @notice Test negative values for signed fields - function test_parseUpdate_negativeValues() public pure { + function test_parseUpdate_negativeValues() public { bytes[] memory properties = new bytes[](3); properties[0] = buildProperty(0, encodeInt64(-50000000)); // negative price properties[1] = buildProperty(4, encodeInt16(-12)); // negative exponent @@ -533,7 +530,7 @@ contract PythLazerTest is Test { feeds ); - PythLazerStructs.Update memory update = PythLazerLib + PythLazerStructs.Update memory update = libHelper .parseUpdateFromPayload(payload); PythLazerStructs.Feed memory feed = update.feeds[0]; @@ -543,8 +540,8 @@ contract PythLazerTest is Test { assertEq(feed._fundingRate, -999); // Negative values should still count as "exists" - assertTrue(PythLazerLib.hasPrice(feed)); - assertTrue(PythLazerLib.hasFundingRate(feed)); + assertTrue(libHelper.hasPrice(feed)); + assertTrue(libHelper.hasFundingRate(feed)); } function test_parseUpdate_extraBytes() public { @@ -567,7 +564,7 @@ contract PythLazerTest is Test { ); vm.expectRevert("Payload has extra unknown bytes"); - PythLazerLib.parseUpdateFromPayload(payloadWithExtra); + libHelper.parseUpdateFromPayload(payloadWithExtra); } /// @notice Test unknown property ID @@ -588,6 +585,6 @@ contract PythLazerTest is Test { ); vm.expectRevert("Unknown property"); - PythLazerLib.parseUpdateFromPayload(payload); + libHelper.parseUpdateFromPayload(payload); } } diff --git a/lazer/contracts/evm/test/PythLazerApi.t.sol b/lazer/contracts/evm/test/PythLazerApi.t.sol index edaeb47692..5decaa1321 100644 --- a/lazer/contracts/evm/test/PythLazerApi.t.sol +++ b/lazer/contracts/evm/test/PythLazerApi.t.sol @@ -5,6 +5,7 @@ import {Test, console2} from "forge-std/Test.sol"; import {PythLazer} from "../src/PythLazer.sol"; import {PythLazerLib} from "../src/PythLazerLib.sol"; import {PythLazerStructs} from "../src/PythLazerStructs.sol"; +import {PythLazerLibTestHelper} from "./PythLazerLibTestHelper.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; /** @@ -14,6 +15,7 @@ import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.so */ contract PythLazerApiTest is Test { PythLazer public pythLazer; + PythLazerLibTestHelper public libHelper; address owner; address trustedSigner = 0x26FB61A864c758AE9fBA027a96010480658385B9; uint256 trustedSignerExpiration = 3000000000000000; @@ -27,6 +29,7 @@ contract PythLazerApiTest is Test { abi.encodeWithSelector(PythLazer.initialize.selector, owner) ); pythLazer = PythLazer(address(proxy)); + libHelper = new PythLazerLibTestHelper(); vm.prank(owner); pythLazer.updateTrustedSigner(trustedSigner, trustedSignerExpiration); assert(pythLazer.isValidSigner(trustedSigner)); @@ -95,7 +98,7 @@ contract PythLazerApiTest is Test { ); assertEq(signerFeed3, trustedSigner, "Feed 3: Signer mismatch"); - PythLazerStructs.Update memory updateFeed3 = PythLazerLib + PythLazerStructs.Update memory updateFeed3 = libHelper .parseUpdateFromPayload(payloadFeed3); assertEq( updateFeed3.feeds.length, @@ -114,46 +117,46 @@ contract PythLazerApiTest is Test { // Requested checks for Feed 3 (should be requested for price/exponent/confidence/publisherCount/bid/ask; not requested for funding*) assertTrue( - PythLazerLib.isPriceRequested(feed3), + libHelper.isPriceRequested(feed3), "Feed 3: price should be requested" ); assertTrue( - PythLazerLib.isExponentRequested(feed3), + libHelper.isExponentRequested(feed3), "Feed 3: exponent should be requested" ); assertTrue( - PythLazerLib.isConfidenceRequested(feed3), + libHelper.isConfidenceRequested(feed3), "Feed 3: confidence should be requested" ); assertTrue( - PythLazerLib.isPublisherCountRequested(feed3), + libHelper.isPublisherCountRequested(feed3), "Feed 3: publisher count should be requested" ); assertTrue( - PythLazerLib.isBestBidPriceRequested(feed3), + libHelper.isBestBidPriceRequested(feed3), "Feed 3: best bid price should be requested" ); assertTrue( - PythLazerLib.isBestAskPriceRequested(feed3), + libHelper.isBestAskPriceRequested(feed3), "Feed 3: best ask price should be requested" ); assertFalse( - PythLazerLib.isFundingRateRequested(feed3), + libHelper.isFundingRateRequested(feed3), "Feed 3: funding rate should NOT be requested" ); assertFalse( - PythLazerLib.isFundingTimestampRequested(feed3), + libHelper.isFundingTimestampRequested(feed3), "Feed 3: funding timestamp should NOT be requested" ); assertFalse( - PythLazerLib.isFundingRateIntervalRequested(feed3), + libHelper.isFundingRateIntervalRequested(feed3), "Feed 3: funding rate interval should NOT be requested" ); // MarketSession may or may not be requested depending on API response // If present, verify it can be accessed correctly - if (PythLazerLib.isMarketSessionRequested(feed3)) { + if (libHelper.isMarketSessionRequested(feed3)) { assertTrue( - PythLazerLib.hasMarketSession(feed3), + libHelper.hasMarketSession(feed3), "Feed 3: if market session is requested, it should be present" ); PythLazerStructs.MarketSession marketSession = PythLazerLib @@ -173,32 +176,32 @@ contract PythLazerApiTest is Test { // Verify parsed values match API reference values exactly assertEq( - PythLazerLib.getPrice(feed3), + libHelper.getPrice(feed3), apiRefFeed3Price, "Feed 3: price mismatch" ); assertEq( - PythLazerLib.getExponent(feed3), + libHelper.getExponent(feed3), apiRefFeed3Exponent, "Feed 3: exponent mismatch" ); assertEq( - PythLazerLib.getConfidence(feed3), + libHelper.getConfidence(feed3), apiRefFeed3Confidence, "Feed 3: confidence mismatch" ); assertEq( - PythLazerLib.getPublisherCount(feed3), + libHelper.getPublisherCount(feed3), apiRefFeed3PublisherCount, "Feed 3: publisher count mismatch" ); assertEq( - PythLazerLib.getBestBidPrice(feed3), + libHelper.getBestBidPrice(feed3), apiRefFeed3BestBid, "Feed 3: best bid price mismatch" ); assertEq( - PythLazerLib.getBestAskPrice(feed3), + libHelper.getBestAskPrice(feed3), apiRefFeed3BestAsk, "Feed 3: best ask price mismatch" ); @@ -266,7 +269,7 @@ contract PythLazerApiTest is Test { ); assertEq(signerFeed112, trustedSigner, "Feed 112: Signer mismatch"); - PythLazerStructs.Update memory updateFeed112 = PythLazerLib + PythLazerStructs.Update memory updateFeed112 = libHelper .parseUpdateFromPayload(payloadFeed112); assertEq( updateFeed112.feeds.length, @@ -285,46 +288,46 @@ contract PythLazerApiTest is Test { // Requested checks for Feed 112 (should be requested for price/exponent/publisherCount/funding*; not requested for bid/ask/confidence) assertTrue( - PythLazerLib.isPriceRequested(feed112), + libHelper.isPriceRequested(feed112), "Feed 112: price should be requested" ); assertTrue( - PythLazerLib.isExponentRequested(feed112), + libHelper.isExponentRequested(feed112), "Feed 112: exponent should be requested" ); assertTrue( - PythLazerLib.isPublisherCountRequested(feed112), + libHelper.isPublisherCountRequested(feed112), "Feed 112: publisher count should be requested" ); assertTrue( - PythLazerLib.isFundingRateRequested(feed112), + libHelper.isFundingRateRequested(feed112), "Feed 112: funding rate should be requested" ); assertTrue( - PythLazerLib.isFundingTimestampRequested(feed112), + libHelper.isFundingTimestampRequested(feed112), "Feed 112: funding timestamp should be requested" ); assertTrue( - PythLazerLib.isFundingRateIntervalRequested(feed112), + libHelper.isFundingRateIntervalRequested(feed112), "Feed 112: funding rate interval should be requested" ); assertFalse( - PythLazerLib.isBestBidPriceRequested(feed112), + libHelper.isBestBidPriceRequested(feed112), "Feed 112: best bid price should NOT be requested" ); assertFalse( - PythLazerLib.isBestAskPriceRequested(feed112), + libHelper.isBestAskPriceRequested(feed112), "Feed 112: best ask price should NOT be requested" ); assertFalse( - PythLazerLib.isConfidenceRequested(feed112), + libHelper.isConfidenceRequested(feed112), "Feed 112: confidence should NOT be requested" ); // MarketSession may or may not be requested depending on API response // If present, verify it can be accessed correctly - if (PythLazerLib.isMarketSessionRequested(feed112)) { + if (libHelper.isMarketSessionRequested(feed112)) { assertTrue( - PythLazerLib.hasMarketSession(feed112), + libHelper.hasMarketSession(feed112), "Feed 112: if market session is requested, it should be present" ); PythLazerStructs.MarketSession marketSession = PythLazerLib @@ -344,37 +347,37 @@ contract PythLazerApiTest is Test { // Verify parsed values match API reference values exactly assertEq( - PythLazerLib.getPrice(feed112), + libHelper.getPrice(feed112), apiRefFeed112Price, "Feed 112: price mismatch" ); assertEq( - PythLazerLib.getExponent(feed112), + libHelper.getExponent(feed112), apiRefFeed112Exponent, "Feed 112: exponent mismatch" ); assertEq( - PythLazerLib.getPublisherCount(feed112), + libHelper.getPublisherCount(feed112), apiRefFeed112PublisherCount, "Feed 112: publisher count mismatch" ); assertEq( - PythLazerLib.getFundingRate(feed112), + libHelper.getFundingRate(feed112), apiRefFeed112FundingRate, "Feed 112: funding rate mismatch" ); assertEq( - PythLazerLib.getFundingTimestamp(feed112), + libHelper.getFundingTimestamp(feed112), apiRefFeed112FundingTimestamp, "Feed 112: funding timestamp mismatch" ); assertEq( - PythLazerLib.getFundingRateInterval(feed112), + libHelper.getFundingRateInterval(feed112), apiRefFeed112FundingRateInterval, "Feed 112: funding rate interval mismatch" ); diff --git a/lazer/contracts/evm/test/PythLazerLibTestHelper.sol b/lazer/contracts/evm/test/PythLazerLibTestHelper.sol new file mode 100644 index 0000000000..7490df03c7 --- /dev/null +++ b/lazer/contracts/evm/test/PythLazerLibTestHelper.sol @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {PythLazerLib} from "../src/PythLazerLib.sol"; +import {PythLazerStructs} from "../src/PythLazerStructs.sol"; + +/// @notice Test helper contract that exposes internal library functions for testing +/// @dev This contract wraps internal library functions with external functions +/// so tests can call them with memory bytes (converted to calldata) +contract PythLazerLibTestHelper { + function parseUpdateFromPayload( + bytes calldata payload + ) external pure returns (PythLazerStructs.Update memory) { + return PythLazerLib.parseUpdateFromPayload(payload); + } + + function parsePayloadHeader( + bytes calldata update + ) + external + pure + returns ( + uint64 timestamp, + PythLazerStructs.Channel channel, + uint8 feedsLen, + uint16 pos + ) + { + return PythLazerLib.parsePayloadHeader(update); + } + + function parseFeedHeader( + bytes calldata update, + uint16 pos + ) + external + pure + returns (uint32 feed_id, uint8 num_properties, uint16 new_pos) + { + return PythLazerLib.parseFeedHeader(update, pos); + } + + function parseFeedProperty( + bytes calldata update, + uint16 pos + ) + external + pure + returns (PythLazerStructs.PriceFeedProperty property, uint16 new_pos) + { + return PythLazerLib.parseFeedProperty(update, pos); + } + + // Existence check helpers + function hasPrice( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasPrice(feed); + } + + function hasBestBidPrice( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasBestBidPrice(feed); + } + + function hasBestAskPrice( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasBestAskPrice(feed); + } + + function hasPublisherCount( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasPublisherCount(feed); + } + + function hasExponent( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasExponent(feed); + } + + function hasConfidence( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasConfidence(feed); + } + + function hasFundingRate( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasFundingRate(feed); + } + + function hasFundingTimestamp( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasFundingTimestamp(feed); + } + + function hasFundingRateInterval( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasFundingRateInterval(feed); + } + + function hasMarketSession( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.hasMarketSession(feed); + } + + // Requested check helpers + function isPriceRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isPriceRequested(feed); + } + + function isBestBidPriceRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isBestBidPriceRequested(feed); + } + + function isBestAskPriceRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isBestAskPriceRequested(feed); + } + + function isPublisherCountRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isPublisherCountRequested(feed); + } + + function isExponentRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isExponentRequested(feed); + } + + function isConfidenceRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isConfidenceRequested(feed); + } + + function isFundingRateRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isFundingRateRequested(feed); + } + + function isFundingTimestampRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isFundingTimestampRequested(feed); + } + + function isFundingRateIntervalRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isFundingRateIntervalRequested(feed); + } + + function isMarketSessionRequested( + PythLazerStructs.Feed memory feed + ) external pure returns (bool) { + return PythLazerLib.isMarketSessionRequested(feed); + } + + // Safe getter helpers + function getPrice( + PythLazerStructs.Feed memory feed + ) external pure returns (int64) { + return PythLazerLib.getPrice(feed); + } + + function getBestBidPrice( + PythLazerStructs.Feed memory feed + ) external pure returns (int64) { + return PythLazerLib.getBestBidPrice(feed); + } + + function getBestAskPrice( + PythLazerStructs.Feed memory feed + ) external pure returns (int64) { + return PythLazerLib.getBestAskPrice(feed); + } + + function getPublisherCount( + PythLazerStructs.Feed memory feed + ) external pure returns (uint16) { + return PythLazerLib.getPublisherCount(feed); + } + + function getExponent( + PythLazerStructs.Feed memory feed + ) external pure returns (int16) { + return PythLazerLib.getExponent(feed); + } + + function getConfidence( + PythLazerStructs.Feed memory feed + ) external pure returns (uint64) { + return PythLazerLib.getConfidence(feed); + } + + function getFundingRate( + PythLazerStructs.Feed memory feed + ) external pure returns (int64) { + return PythLazerLib.getFundingRate(feed); + } + + function getFundingTimestamp( + PythLazerStructs.Feed memory feed + ) external pure returns (uint64) { + return PythLazerLib.getFundingTimestamp(feed); + } + + function getFundingRateInterval( + PythLazerStructs.Feed memory feed + ) external pure returns (uint64) { + return PythLazerLib.getFundingRateInterval(feed); + } + + function getMarketSession( + PythLazerStructs.Feed memory feed + ) external pure returns (PythLazerStructs.MarketSession) { + return PythLazerLib.getMarketSession(feed); + } +}