From 4bdfb72748042f9657beb12ac3644a79c110d2fb Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Wed, 9 Jul 2025 18:22:24 +0200 Subject: [PATCH 01/37] Chore(contract-manager) Executor Contract Type (#2838) * added EvmExecutorContract.json * temp_list * deploy_evm_executor script * deployed Executor on Ethereum Sepolia * updat * update * update * update * chore(executor)-Deployed Executor on Ethereal Testnet * update --- contract_manager/scripts/common.ts | 27 ++- .../scripts/deploy_evm_entropy_contracts.ts | 44 +--- .../scripts/deploy_evm_executor.ts | 147 +++++++++++++ contract_manager/src/core/contracts/evm.ts | 31 ++- contract_manager/src/node/utils/store.ts | 9 +- contract_manager/store/chains/EvmChains.json | 9 +- .../store/contracts/EvmEntropyContracts.json | 2 +- .../store/contracts/EvmExecutorContracts.json | 202 ++++++++++++++++++ .../store/contracts/EvmWormholeContracts.json | 5 + .../packages/xc_admin_common/src/chains.ts | 1 + .../evm/script/PythLazerChangeOwnership.s.sol | 36 ++++ 11 files changed, 466 insertions(+), 47 deletions(-) create mode 100644 contract_manager/scripts/deploy_evm_executor.ts create mode 100644 contract_manager/store/contracts/EvmExecutorContracts.json create mode 100644 lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol diff --git a/contract_manager/scripts/common.ts b/contract_manager/scripts/common.ts index 274361a295..fce41ea6ce 100644 --- a/contract_manager/scripts/common.ts +++ b/contract_manager/scripts/common.ts @@ -6,7 +6,11 @@ import { Contract } from "web3-eth-contract"; import { InferredOptionType } from "yargs"; import { PrivateKey, getDefaultDeploymentConfig } from "../src/core/base"; import { EvmChain } from "../src/core/chains"; -import { EvmEntropyContract, EvmWormholeContract } from "../src/core/contracts"; +import { + EvmEntropyContract, + EvmExecutorContract, + EvmWormholeContract, +} from "../src/core/contracts"; export interface BaseDeployConfig { gasMultiplier: number; @@ -237,6 +241,27 @@ export function findWormholeContract( } } +/** + * Finds the executor contract for a given EVM chain. + * @param {EvmChain} chain The EVM chain to find the executor contract for. + * @returns If found, the executor contract for the given EVM chain. Else, undefined + */ +export function findExecutorContract( + chain: EvmChain, +): EvmExecutorContract | undefined { + for (const contract of Object.values(DefaultStore.executor_contracts)) { + if ( + contract instanceof EvmExecutorContract && + contract.chain.getId() === chain.getId() + ) { + console.log( + `Found executor contract for ${chain.getId()} at ${contract.address}`, + ); + return contract; + } + } +} + export interface DeployWormholeReceiverContractsConfig extends BaseDeployConfig { saveContract: boolean; diff --git a/contract_manager/scripts/deploy_evm_entropy_contracts.ts b/contract_manager/scripts/deploy_evm_entropy_contracts.ts index 547c87427f..fdcea61b9e 100644 --- a/contract_manager/scripts/deploy_evm_entropy_contracts.ts +++ b/contract_manager/scripts/deploy_evm_entropy_contracts.ts @@ -9,7 +9,6 @@ import { } from "../src/core/contracts/evm"; import { DeploymentType, - getDefaultDeploymentConfig, toDeploymentType, toPrivateKey, } from "../src/core/base"; @@ -22,6 +21,7 @@ import { topupAccountsIfNecessary, DefaultAddresses, } from "./common"; +import { getOrDeployExecutorContract } from "./deploy_evm_executor"; interface DeploymentConfig extends BaseDeployConfig { type: DeploymentType; @@ -44,44 +44,6 @@ const parser = yargs(hideBin(process.argv)) }, }); -async function deployExecutorContracts( - chain: EvmChain, - config: DeploymentConfig, - wormholeAddr: string, -): Promise { - const executorImplAddr = await deployIfNotCached( - CACHE_FILE, - chain, - config, - "ExecutorUpgradable", - [], - ); - - // Craft the init data for the proxy contract - const { governanceDataSource } = getDefaultDeploymentConfig(config.type); - - const executorImplContract = getWeb3Contract( - config.jsonOutputDir, - "ExecutorUpgradable", - executorImplAddr, - ); - - const executorInitData = executorImplContract.methods - .initialize( - wormholeAddr, - 0, // lastExecutedSequence, - chain.getWormholeChainId(), - governanceDataSource.emitterChain, - `0x${governanceDataSource.emitterAddress}`, - ) - .encodeABI(); - - return await deployIfNotCached(CACHE_FILE, chain, config, "ERC1967Proxy", [ - executorImplAddr, - executorInitData, - ]); -} - async function deployEntropyContracts( chain: EvmChain, config: DeploymentConfig, @@ -166,7 +128,7 @@ async function main() { console.log(`Deploying entropy contracts on ${chain.getId()}...`); - const executorAddr = await deployExecutorContracts( + const executorContract = await getOrDeployExecutorContract( chain, deploymentConfig, wormholeContract.address, @@ -174,7 +136,7 @@ async function main() { const entropyAddr = await deployEntropyContracts( chain, deploymentConfig, - executorAddr, + executorContract.address, ); if (deploymentConfig.saveContract) { diff --git a/contract_manager/scripts/deploy_evm_executor.ts b/contract_manager/scripts/deploy_evm_executor.ts new file mode 100644 index 0000000000..703b06003f --- /dev/null +++ b/contract_manager/scripts/deploy_evm_executor.ts @@ -0,0 +1,147 @@ +import yargs from "yargs"; +import { hideBin } from "yargs/helpers"; +import { EvmChain } from "../src/core/chains"; +import { + BaseDeployConfig, + COMMON_DEPLOY_OPTIONS, + deployIfNotCached, + findExecutorContract, + getOrDeployWormholeContract, + getWeb3Contract, +} from "./common"; +import { + DeploymentType, + getDefaultDeploymentConfig, + toDeploymentType, + toPrivateKey, +} from "../src/core/base"; +import { DefaultStore } from "../src/node/utils/store"; +import { EvmExecutorContract } from "../src/core/contracts/evm"; + +const CACHE_FILE = ".cache-deploy-evm-executor"; + +const parser = yargs(hideBin(process.argv)) + .scriptName("deploy_evm_executor.ts") + .usage( + "Usage: $0 --std-output-dir --private-key --chain ", + ) + .options({ + ...COMMON_DEPLOY_OPTIONS, + chain: { + type: "string", + demandOption: true, + desc: "Chain to upload the contract on. Can be one of the evm chains available in the store", + }, + }); + +interface DeploymentConfig extends BaseDeployConfig { + type: DeploymentType; + saveContract: boolean; +} + +export async function getOrDeployExecutorContract( + chain: EvmChain, + config: DeploymentConfig, + wormholeAddr: string, +): Promise { + return ( + findExecutorContract(chain) ?? + (await deployExecutorContracts(chain, config, wormholeAddr)) + ); +} + +/** + * Deploys the executor contracts for a given EVM chain. + * @param {EvmChain} chain The EVM chain to deploy the executor contracts for. + * @param {DeploymentConfig} config The deployment configuration. + * @param {string} wormholeAddr The address of the wormhole contract. + * @returns {Promise} The address of the deployed executor contract. + */ +export async function deployExecutorContracts( + chain: EvmChain, + config: DeploymentConfig, + wormholeAddr: string, +): Promise { + const executorImplAddr = await deployIfNotCached( + CACHE_FILE, + chain, + config, + "ExecutorUpgradable", + [], + ); + + // Craft the init data for the proxy contract + const { governanceDataSource } = getDefaultDeploymentConfig(config.type); + + const executorImplContract = getWeb3Contract( + config.jsonOutputDir, + "ExecutorUpgradable", + executorImplAddr, + ); + + const executorInitData = executorImplContract.methods + .initialize( + wormholeAddr, + 0, // lastExecutedSequence, + chain.getWormholeChainId(), + governanceDataSource.emitterChain, + `0x${governanceDataSource.emitterAddress}`, + ) + .encodeABI(); + + const executorAddr = await deployIfNotCached( + CACHE_FILE, + chain, + config, + "ERC1967Proxy", + [executorImplAddr, executorInitData], + ); + + return new EvmExecutorContract(chain, executorAddr); +} + +export async function main() { + const argv = await parser.argv; + + const chain = DefaultStore.getChainOrThrow(argv.chain, EvmChain); + + const deploymentConfig: DeploymentConfig = { + type: toDeploymentType(argv.deploymentType), + gasMultiplier: argv.gasMultiplier, + gasPriceMultiplier: argv.gasPriceMultiplier, + privateKey: toPrivateKey(argv.privateKey), + jsonOutputDir: argv.stdOutputDir, + saveContract: argv.saveContract, + }; + + const wormholeContract = await getOrDeployWormholeContract( + chain, + deploymentConfig, + CACHE_FILE, + ); + + console.log( + `Deployment config: ${JSON.stringify(deploymentConfig, null, 2)}\n`, + ); + + console.log(`Deploying executor contracts on ${chain.getId()}...`); + + const executorContract = await getOrDeployExecutorContract( + chain, + deploymentConfig, + wormholeContract.address, + ); + + if (deploymentConfig.saveContract) { + console.log("Saving the contract in the store..."); + DefaultStore.executor_contracts[executorContract.getId()] = + executorContract; + DefaultStore.saveAllContracts(); + } + + console.log( + `✅ Executor contract on ${chain.getId()} at ${executorContract.address}\n\n`, + ); +} + +main(); diff --git a/contract_manager/src/core/contracts/evm.ts b/contract_manager/src/core/contracts/evm.ts index e59ab931f1..599934299b 100644 --- a/contract_manager/src/core/contracts/evm.ts +++ b/contract_manager/src/core/contracts/evm.ts @@ -412,16 +412,32 @@ export class EvmEntropyContract extends Storable { } } -export class EvmExecutorContract { +export class EvmExecutorContract extends Storable { + static type = "EvmExecutorContract"; + constructor( public chain: EvmChain, public address: string, - ) {} + ) { + super(); + } getId(): string { return `${this.chain.getId()}_${this.address}`; } + getType(): string { + return EvmExecutorContract.type; + } + + toJson() { + return { + chain: this.chain.getId(), + address: this.address, + type: EvmExecutorContract.type, + }; + } + async getWormholeContract(): Promise { const web3 = this.chain.getWeb3(); //Unfortunately, there is no public method to get the wormhole address @@ -431,6 +447,17 @@ export class EvmExecutorContract { return new EvmWormholeContract(this.chain, address); } + static fromJson( + chain: Chain, + parsed: { type: string; address: string }, + ): EvmExecutorContract { + if (parsed.type !== EvmExecutorContract.type) + throw new Error("Invalid type"); + if (!(chain instanceof EvmChain)) + throw new Error(`Wrong chain type ${chain}`); + return new EvmExecutorContract(chain, parsed.address); + } + getContract() { const web3 = this.chain.getWeb3(); return new web3.eth.Contract(EXECUTOR_ABI, this.address); diff --git a/contract_manager/src/node/utils/store.ts b/contract_manager/src/node/utils/store.ts index 008d697e88..3529791e03 100644 --- a/contract_manager/src/node/utils/store.ts +++ b/contract_manager/src/node/utils/store.ts @@ -29,6 +29,7 @@ import { IotaWormholeContract, IotaPriceFeedContract, EvmPulseContract, + EvmExecutorContract, } from "../../core/contracts"; import { Token } from "../../core/token"; import { PriceFeedContract, Storable } from "../../core/base"; @@ -46,6 +47,7 @@ import { export class Store { public chains: Record = { global: new GlobalChain() }; public contracts: Record = {}; + public executor_contracts: Record = {}; public entropy_contracts: Record = {}; public pulse_contracts: Record = {}; public wormhole_contracts: Record = {}; @@ -118,6 +120,7 @@ export class Store { const contracts: Storable[] = Object.values(this.contracts); contracts.push(...Object.values(this.entropy_contracts)); contracts.push(...Object.values(this.wormhole_contracts)); + contracts.push(...Object.values(this.executor_contracts)); for (const contract of contracts) { if (!contractsByType[contract.getType()]) { contractsByType[contract.getType()] = []; @@ -167,6 +170,7 @@ export class Store { [AptosWormholeContract.type]: AptosWormholeContract, [EvmEntropyContract.type]: EvmEntropyContract, [EvmWormholeContract.type]: EvmWormholeContract, + [EvmExecutorContract.type]: EvmExecutorContract, [FuelPriceFeedContract.type]: FuelPriceFeedContract, [FuelWormholeContract.type]: FuelWormholeContract, [StarknetPriceFeedContract.type]: StarknetPriceFeedContract, @@ -192,7 +196,8 @@ export class Store { if ( this.contracts[chainContract.getId()] || this.entropy_contracts[chainContract.getId()] || - this.wormhole_contracts[chainContract.getId()] + this.wormhole_contracts[chainContract.getId()] || + this.executor_contracts[chainContract.getId()] ) throw new Error( `Multiple contracts with id ${chainContract.getId()} found`, @@ -201,6 +206,8 @@ export class Store { this.entropy_contracts[chainContract.getId()] = chainContract; } else if (chainContract instanceof WormholeContract) { this.wormhole_contracts[chainContract.getId()] = chainContract; + } else if (chainContract instanceof EvmExecutorContract) { + this.executor_contracts[chainContract.getId()] = chainContract; } else { this.contracts[chainContract.getId()] = chainContract; } diff --git a/contract_manager/store/chains/EvmChains.json b/contract_manager/store/chains/EvmChains.json index 9f7ce46b3f..6fca380397 100644 --- a/contract_manager/store/chains/EvmChains.json +++ b/contract_manager/store/chains/EvmChains.json @@ -401,7 +401,7 @@ { "id": "sepolia", "mainnet": false, - "rpcUrl": "https://eth-sepolia.blastapi.io/$ENV_BLAST_API_KEY", + "rpcUrl": "https://eth-sepolia.public.blastapi.io", "networkId": 11155111, "type": "EvmChain" }, @@ -1265,5 +1265,12 @@ "rpcUrl": "https://k8s.testnet.json-rpc.injective.network/", "networkId": 1439, "type": "EvmChain" + }, + { + "id": "ethereal_testnet", + "mainnet": false, + "rpcUrl": "https://rpc-ethereal-testnet.t.conduit.xyz", + "networkId": 657468, + "type": "EvmChain" } ] diff --git a/contract_manager/store/contracts/EvmEntropyContracts.json b/contract_manager/store/contracts/EvmEntropyContracts.json index 7cd26b45b4..84e47a6fe8 100644 --- a/contract_manager/store/contracts/EvmEntropyContracts.json +++ b/contract_manager/store/contracts/EvmEntropyContracts.json @@ -189,4 +189,4 @@ "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", "type": "EvmEntropyContract" } -] +] \ No newline at end of file diff --git a/contract_manager/store/contracts/EvmExecutorContracts.json b/contract_manager/store/contracts/EvmExecutorContracts.json new file mode 100644 index 0000000000..57023ef3d8 --- /dev/null +++ b/contract_manager/store/contracts/EvmExecutorContracts.json @@ -0,0 +1,202 @@ +[ + { + "chain": "arbitrum_sepolia", + "address": "0x9DF02366A266D79DA5E978aDBEe8B8502117ee1a", + "type": "EvmExecutorContract" + }, + { + "chain": "blast_s2_testnet", + "address": "0x5f3c61944CEb01B3eAef861251Fb1E0f14b848fb", + "type": "EvmExecutorContract" + }, + { + "chain": "base_sepolia", + "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", + "type": "EvmExecutorContract" + }, + { + "chain": "optimism_sepolia", + "address": "0x549Ebba8036Ab746611B4fFA1423eb0A4Df61440", + "type": "EvmExecutorContract" + }, + { + "chain": "zetachain_testnet", + "address": "0xfA25E653b44586dBbe27eE9d252192F0e4956683", + "type": "EvmExecutorContract" + }, + { + "chain": "etherlink_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "sei_evm_testnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "kaia_testnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "b3_testnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "apechain_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "soneium_minato_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "sanko_testnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "abstract_testnet", + "address": "0x0d8B7FE8598e2BcEcAf1E60f51B4b8B8E4453BA5", + "type": "EvmExecutorContract" + }, + { + "chain": "sonic_blaze_testnet", + "address": "0x8D254a21b3C86D32F7179855531CE99164721933", + "type": "EvmExecutorContract" + }, + { + "chain": "unichain_sepolia", + "address": "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", + "type": "EvmExecutorContract" + }, + { + "chain": "tabi_testnet", + "address": "0x8D254a21b3C86D32F7179855531CE99164721933", + "type": "EvmExecutorContract" + }, + { + "chain": "monad_testnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "story_testnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "berachain_bepolia", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "hyperevm_testnet", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "arbitrum", + "address": "0x24654078A8E043e8985D962a5100CDfA2026f92C", + "type": "EvmExecutorContract" + }, + { + "chain": "optimism", + "address": "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb", + "type": "EvmExecutorContract" + }, + { + "chain": "blast", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "zetachain", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "base", + "address": "0xf0a1b566B55e0A0CB5BeF52Eb2a57142617Bee67", + "type": "EvmExecutorContract" + }, + { + "chain": "sei_evm_mainnet", + "address": "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E", + "type": "EvmExecutorContract" + }, + { + "chain": "etherlink", + "address": "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", + "type": "EvmExecutorContract" + }, + { + "chain": "kaia", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "b3_mainnet", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "apechain_mainnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "sanko", + "address": "0x87047526937246727E4869C5f76A347160e08672", + "type": "EvmExecutorContract" + }, + { + "chain": "unichain", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "abstract", + "address": "0x6650bBd680A4cAdcB30AFfa0Ec78ca0811Db0B85", + "type": "EvmExecutorContract" + }, + { + "chain": "fantom_sonic_mainnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "berachain_mainnet", + "address": "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", + "type": "EvmExecutorContract" + }, + { + "chain": "hyperevm", + "address": "0x5D289Ad1CE59fCC25b6892e7A303dfFf3a9f7167", + "type": "EvmExecutorContract" + }, + { + "chain": "story", + "address": "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb", + "type": "EvmExecutorContract" + }, + { + "chain": "soneium", + "address": "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c", + "type": "EvmExecutorContract" + }, + { + "chain": "sepolia", + "address": "0xB0D4a9640aDdd415551B6A4fe75403c9f73A7C49", + "type": "EvmExecutorContract" + }, + { + "chain": "ethereal_testnet", + "address": "0xD458261E832415CFd3BAE5E416FdF3230ce6F134", + "type": "EvmExecutorContract" + } +] \ No newline at end of file diff --git a/contract_manager/store/contracts/EvmWormholeContracts.json b/contract_manager/store/contracts/EvmWormholeContracts.json index 41384b2247..ce1ced7a16 100644 --- a/contract_manager/store/contracts/EvmWormholeContracts.json +++ b/contract_manager/store/contracts/EvmWormholeContracts.json @@ -838,5 +838,10 @@ "chain": "injective_evm_testnet", "address": "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", "type": "EvmWormholeContract" + }, + { + "chain": "ethereal_testnet", + "address": "0x74f09cb3c7e2A01865f424FD14F6dc9A14E3e94E", + "type": "EvmWormholeContract" } ] \ No newline at end of file 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 388cd1879b..d2be72f363 100644 --- a/governance/xc_admin/packages/xc_admin_common/src/chains.ts +++ b/governance/xc_admin/packages/xc_admin_common/src/chains.ts @@ -246,6 +246,7 @@ export const RECEIVER_CHAINS = { mezo_testnet: 50124, hemi_testnet: 50125, injective_evm_testnet: 50126, + ethereal_testnet: 50127, }; // If there is any overlapping value the receiver chain will replace the wormhole diff --git a/lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol b/lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol new file mode 100644 index 0000000000..62bb1b7410 --- /dev/null +++ b/lazer/contracts/evm/script/PythLazerChangeOwnership.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {PythLazer} from "../src/PythLazer.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; + +contract PythLazerChangeOwnership is Script { + address public constant LAZER_PROXY_ADDRESS = + address(0xACeA761c27A909d4D3895128EBe6370FDE2dF481); + + uint256 public OLD_OWNER_PRIVATE_KEY = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address public OLD_OWNER = vm.addr(OLD_OWNER_PRIVATE_KEY); + // EVM Executor Contract + address public NEW_OWNER = vm.envAddress("NEW_OWNER"); + + function run() public { + console.log("Old owner: %s", OLD_OWNER); + console.log("New owner: %s", NEW_OWNER); + console.log("Lazer proxy address: %s", LAZER_PROXY_ADDRESS); + console.log("Lazer owner: %s", PythLazer(LAZER_PROXY_ADDRESS).owner()); + console.log("Moving ownership from %s to %s", OLD_OWNER, NEW_OWNER); + + PythLazer lazer = PythLazer(LAZER_PROXY_ADDRESS); + vm.startBroadcast(OLD_OWNER_PRIVATE_KEY); + require(lazer.owner() == OLD_OWNER, "Old owner mismatch"); + lazer.transferOwnership(NEW_OWNER); + console.log("Ownership transferred"); + console.log( + "New Lazer owner: %s", + PythLazer(LAZER_PROXY_ADDRESS).owner() + ); + vm.stopBroadcast(); + } +} From ac3d5055bee1e50c37a3fc998d2551e097dc70a7 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 11:58:57 -0500 Subject: [PATCH 02/37] removing extraneous comments --- .../contracts/pyth-receiver/src/structs.rs | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index 722eb7312a..9e95dc012a 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -46,18 +46,6 @@ pub struct DataSource { pub emitter_address: FixedBytes<32>, } -// impl StorageKey for DataSourceStorage { -// fn to_slot(&self, root: B256) -> U256 { -// let chain_id: u16 = self.chain_id.get().to::(); -// let emitter_address = self.emitter_address.get(); - -// let bytes = serialize_data_source_to_bytes(chain_id, emitter_address.as_slice().try_into().unwrap()) -// .expect("Failed to serialize DataSource"); - -// keccak256(bytes).to_slot(root) -// } -// } - impl StorageKey for DataSource { fn to_slot(&self, root: B256) -> U256 { let chain_id: u16 = self.chain_id.to::(); @@ -69,24 +57,6 @@ impl StorageKey for DataSource { keccak256(bytes).to_slot(root) } } - -// pub trait GetDataSource { -// fn data_source(&self) -> DataSourceStorage; -// } - -// impl GetDataSource for VerifiedVM { -// fn data_source(&self) -> DataSourceStorage { -// let mut ds = DataSourceStorage { -// chain_id: StorageU16::new(storage_key!("chain_id")), -// emitter_address: StorageFixedBytes::<32>::new(storage_key!("emitter_address")), -// }; -// ds.chain_id.set(self.emitter_chain_id.into()); -// ds.emitter_address.set(self.emitter_address); -// ds -// } -// } - -// PriceInfo struct storing price information #[storage] pub struct PriceInfoStorage { pub publish_time: StorageU64, @@ -109,20 +79,6 @@ pub struct PriceInfoStorage { // pub ema_conf: U64, // } -// impl From<&PriceFeedMessage> for PriceInfo { -// fn from(price_feed_message: &PriceFeedMessage) -> Self { -// Self { -// publish_time: U64::from(price_feed_message.publish_time), -// expo: I32::from_be_bytes(price_feed_message.exponent.to_be_bytes()), -// price: I64::from_be_bytes(price_feed_message.price.to_be_bytes()), -// conf: U64::from(price_feed_message.conf), -// ema_price: I64::from_be_bytes(price_feed_message.ema_price.to_be_bytes()), -// ema_conf: U64::from(price_feed_message.ema_conf), -// } -// } -// } - -// PriceInfo struct storing price information pub type PriceInfoReturn = (U64, I32, I64, U64, I64, U64); #[cfg(test)] From 0f9de33304beb71bc9bc2f5578cc774b78a9945a Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 12:04:34 -0500 Subject: [PATCH 03/37] cleaned serialization --- .../contracts/pyth-receiver/src/structs.rs | 34 +++++-------------- 1 file changed, 8 insertions(+), 26 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index 9e95dc012a..abfcfd7d77 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -1,5 +1,4 @@ -use alloc::{boxed::Box, format, vec::Vec}; -use pythnet_sdk::wire::to_vec; +use alloc::{vec::Vec}; use serde::Serialize; use stylus_sdk::alloy_primitives::{keccak256, FixedBytes, B256, I32, I64, U16, U256, U64}; use stylus_sdk::{ @@ -7,32 +6,17 @@ use stylus_sdk::{ storage::{StorageFixedBytes, StorageI32, StorageI64, StorageKey, StorageU16, StorageU64}, }; -#[derive(Serialize)] -struct SerializableDataSource { - chain_id: u16, - #[serde(with = "pythnet_sdk::wire::array")] - emitter_address: [u8; 32], -} - fn serialize_data_source_to_bytes( chain_id: u16, emitter_address: &[u8; 32], -) -> Result<[u8; 34], Box> { - let data_source = SerializableDataSource { - chain_id, - emitter_address: *emitter_address, - }; - - let bytes = to_vec::<_, byteorder::BE>(&data_source)?; - if bytes.len() != 34 { - return Err(format!("Expected 34 bytes, got {}", bytes.len()).into()); - } - +) -> [u8; 34] { let mut result = [0u8; 34]; - result.copy_from_slice(&bytes); - Ok(result) + result[0..2].copy_from_slice(&chain_id.to_be_bytes()); + result[2..].copy_from_slice(emitter_address); + result } + #[derive(Debug)] #[storage] pub struct DataSourceStorage { @@ -51,8 +35,7 @@ impl StorageKey for DataSource { let chain_id: u16 = self.chain_id.to::(); let emitter_address: [u8; 32] = self.emitter_address.as_slice().try_into().unwrap(); - let bytes = serialize_data_source_to_bytes(chain_id, &emitter_address) - .expect("Failed to serialize DataSource"); + let bytes = serialize_data_source_to_bytes(chain_id, &emitter_address); keccak256(bytes).to_slot(root) } @@ -100,8 +83,7 @@ mod tests { expected_bytes[0..2].copy_from_slice(&chain_id.to_be_bytes()); expected_bytes[2..].copy_from_slice(&emitter_address); - let actual_bytes = serialize_data_source_to_bytes(chain_id, &emitter_address) - .expect("Serialization should succeed"); + let actual_bytes = serialize_data_source_to_bytes(chain_id, &emitter_address); assert_eq!( actual_bytes, expected_bytes, From 01880f798f288f14d2da41347a291571c4a4c2a2 Mon Sep 17 00:00:00 2001 From: Bart Platak Date: Wed, 9 Jul 2025 18:35:54 +0100 Subject: [PATCH 04/37] feat(pyth-lazer) Create schema for lazer agent JRPC endpoint (#2836) --- Cargo.lock | 22 +- lazer/contracts/solana/Cargo.lock | 36 +- .../pyth-lazer-solana-contract/Cargo.toml | 2 +- lazer/publisher_sdk/rust/Cargo.toml | 2 +- lazer/publisher_sdk/rust/src/lib.rs | 38 ++ lazer/sdk/rust/client/Cargo.toml | 2 +- lazer/sdk/rust/protocol/Cargo.toml | 4 +- lazer/sdk/rust/protocol/src/jrpc.rs | 398 ++++++++++++++++++ lazer/sdk/rust/protocol/src/lib.rs | 5 +- lazer/sdk/rust/protocol/src/router.rs | 37 +- 10 files changed, 498 insertions(+), 48 deletions(-) create mode 100644 lazer/sdk/rust/protocol/src/jrpc.rs diff --git a/Cargo.lock b/Cargo.lock index 6ce85720ee..474331d5e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5540,8 +5540,8 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "protobuf", - "pyth-lazer-protocol 0.7.2", - "pyth-lazer-publisher-sdk 0.1.5", + "pyth-lazer-protocol 0.7.3", + "pyth-lazer-publisher-sdk 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", "soketto", @@ -5569,7 +5569,7 @@ dependencies = [ "futures-util", "hex", "libsecp256k1 0.7.2", - "pyth-lazer-protocol 0.7.3", + "pyth-lazer-protocol 0.8.0", "serde", "serde_json", "tokio", @@ -5580,9 +5580,9 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9bdf4e2ba853a8b437309487542e742c7d094d8db189db194cb538f2be02ecd" +checksum = "6445dc5d2f7fff7c677fb8edc5a080a82ef7583c1bdb39daa95421788c23f695" dependencies = [ "anyhow", "base64 0.22.1", @@ -5597,17 +5597,17 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.7.3" +version = "0.8.0" dependencies = [ "alloy-primitives 0.8.25", "anyhow", - "base64 0.22.1", "bincode 1.3.3", "bs58", "byteorder", "derive_more 1.0.0", "ed25519-dalek 2.1.1", "hex", + "humantime-serde", "itertools 0.13.0", "libsecp256k1 0.7.2", "protobuf", @@ -5618,16 +5618,14 @@ dependencies = [ [[package]] name = "pyth-lazer-publisher-sdk" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e633db28ca38210de8ab3e99d5bd85ad8cae08a08bb0292506340ee9d1c718" +version = "0.1.6" dependencies = [ "anyhow", "fs-err", "humantime", "protobuf", "protobuf-codegen", - "pyth-lazer-protocol 0.7.2", + "pyth-lazer-protocol 0.8.0", "serde-value", "tracing", ] @@ -5635,6 +5633,8 @@ dependencies = [ [[package]] name = "pyth-lazer-publisher-sdk" version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6ef4052ebf2a7943259b3d52a10b2231ffc346717735c50e44d73fe92019d5" dependencies = [ "anyhow", "fs-err", diff --git a/lazer/contracts/solana/Cargo.lock b/lazer/contracts/solana/Cargo.lock index d689daad3a..a4b07b8930 100644 --- a/lazer/contracts/solana/Cargo.lock +++ b/lazer/contracts/solana/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "Inflector" @@ -616,12 +616,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "base64ct" version = "1.6.0" @@ -2006,6 +2000,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" version = "0.14.31" @@ -3200,12 +3204,12 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.7.3" +version = "0.8.0" dependencies = [ "anyhow", - "base64 0.22.1", "byteorder", "derive_more", + "humantime-serde", "itertools 0.13.0", "protobuf", "rust_decimal", @@ -3213,20 +3217,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "pyth-lazer-publisher-sdk" -version = "0.1.6" -dependencies = [ - "anyhow", - "fs-err", - "humantime", - "protobuf", - "protobuf-codegen", - "pyth-lazer-protocol", - "serde-value", - "tracing", -] - [[package]] name = "pyth-lazer-solana-contract" version = "0.4.2" diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml index aa3e2a9f85..4888f6669e 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml @@ -19,7 +19,7 @@ no-log-ix-name = [] idl-build = ["anchor-lang/idl-build"] [dependencies] -pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.7.2" } +pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.8.0" } anchor-lang = "0.30.1" bytemuck = "1.20.0" diff --git a/lazer/publisher_sdk/rust/Cargo.toml b/lazer/publisher_sdk/rust/Cargo.toml index 13f8bb6c61..25c00f42c3 100644 --- a/lazer/publisher_sdk/rust/Cargo.toml +++ b/lazer/publisher_sdk/rust/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" repository = "https://github.com/pyth-network/pyth-crosschain" [dependencies] -pyth-lazer-protocol = { version = "0.7.2", path = "../../sdk/rust/protocol" } +pyth-lazer-protocol = { version = "0.8.0", path = "../../sdk/rust/protocol" } anyhow = "1.0.98" protobuf = "3.7.2" serde-value = "0.7.0" diff --git a/lazer/publisher_sdk/rust/src/lib.rs b/lazer/publisher_sdk/rust/src/lib.rs index bc341e2c5d..d027371f97 100644 --- a/lazer/publisher_sdk/rust/src/lib.rs +++ b/lazer/publisher_sdk/rust/src/lib.rs @@ -1,9 +1,12 @@ use std::{collections::BTreeMap, time::Duration}; +use crate::publisher_update::feed_update::Update; +use crate::publisher_update::{FeedUpdate, FundingRateUpdate, PriceUpdate}; use ::protobuf::MessageField; use anyhow::{bail, ensure, Context}; use humantime::format_duration; use protobuf::dynamic_value::{dynamic_value, DynamicValue}; +use pyth_lazer_protocol::jrpc::{FeedUpdateParams, UpdateParams}; use pyth_lazer_protocol::router::TimestampUs; pub mod transaction_envelope { @@ -164,3 +167,38 @@ impl TryFrom for serde_value::Value { } } } + +impl From for FeedUpdate { + fn from(value: FeedUpdateParams) -> Self { + FeedUpdate { + feed_id: Some(value.feed_id.0), + source_timestamp: value.source_timestamp.into(), + update: Some(value.update.into()), + special_fields: Default::default(), + } + } +} + +impl From for Update { + fn from(value: UpdateParams) -> Self { + match value { + UpdateParams::PriceUpdate { + price, + best_bid_price, + best_ask_price, + } => Update::PriceUpdate(PriceUpdate { + price: Some(price.0.into()), + best_bid_price: best_bid_price.map(|p| p.0.into()), + best_ask_price: best_ask_price.map(|p| p.0.into()), + special_fields: Default::default(), + }), + UpdateParams::FundingRateUpdate { price, rate } => { + Update::FundingRateUpdate(FundingRateUpdate { + price: price.map(|p| p.0.into()), + rate: Some(rate.0), + special_fields: Default::default(), + }) + } + } + } +} diff --git a/lazer/sdk/rust/client/Cargo.toml b/lazer/sdk/rust/client/Cargo.toml index 4670ea301b..405556457f 100644 --- a/lazer/sdk/rust/client/Cargo.toml +++ b/lazer/sdk/rust/client/Cargo.toml @@ -6,7 +6,7 @@ description = "A Rust client for Pyth Lazer" license = "Apache-2.0" [dependencies] -pyth-lazer-protocol = { path = "../protocol", version = "0.7.2" } +pyth-lazer-protocol = { path = "../protocol", version = "0.8.0" } tokio = { version = "1", features = ["full"] } tokio-tungstenite = { version = "0.20", features = ["native-tls"] } futures-util = "0.3" diff --git a/lazer/sdk/rust/protocol/Cargo.toml b/lazer/sdk/rust/protocol/Cargo.toml index 75ba2f0d08..d0ef7aa904 100644 --- a/lazer/sdk/rust/protocol/Cargo.toml +++ b/lazer/sdk/rust/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-lazer-protocol" -version = "0.7.3" +version = "0.8.0" edition = "2021" description = "Pyth Lazer SDK - protocol types." license = "Apache-2.0" @@ -14,8 +14,8 @@ serde_json = "1.0" derive_more = { version = "1.0.0", features = ["from"] } itertools = "0.13.0" rust_decimal = "1.36.0" -base64 = "0.22.1" protobuf = "3.7.2" +humantime-serde = "1.1.1" [dev-dependencies] bincode = "1.3.3" diff --git a/lazer/sdk/rust/protocol/src/jrpc.rs b/lazer/sdk/rust/protocol/src/jrpc.rs new file mode 100644 index 0000000000..fa3fdce72c --- /dev/null +++ b/lazer/sdk/rust/protocol/src/jrpc.rs @@ -0,0 +1,398 @@ +use crate::router::{Channel, Price, PriceFeedId, Rate, TimestampUs}; +use crate::symbol_state::SymbolState; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct PythLazerAgentJrpcV1 { + pub jsonrpc: JsonRpcVersion, + #[serde(flatten)] + pub params: JrpcCall, + pub id: i64, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[serde(tag = "method", content = "params")] +#[serde(rename_all = "snake_case")] +pub enum JrpcCall { + PushUpdate(FeedUpdateParams), + GetMetadata(GetMetadataParams), +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct FeedUpdateParams { + pub feed_id: PriceFeedId, + pub source_timestamp: TimestampUs, + pub update: UpdateParams, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +#[serde(tag = "type")] +pub enum UpdateParams { + #[serde(rename = "price")] + PriceUpdate { + price: Price, + best_bid_price: Option, + best_ask_price: Option, + }, + #[serde(rename = "funding_rate")] + FundingRateUpdate { price: Option, rate: Rate }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct Filter { + pub name: Option, + pub asset_type: Option, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct GetMetadataParams { + pub names: Option>, + pub asset_types: Option>, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub enum JsonRpcVersion { + #[serde(rename = "2.0")] + V2, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub enum JrpcResponse { + Success(JrpcSuccessResponse), + Error(JrpcErrorResponse), +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct JrpcSuccessResponse { + pub jsonrpc: JsonRpcVersion, + pub result: T, + pub id: i64, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct JrpcErrorResponse { + pub jsonrpc: JsonRpcVersion, + pub error: JrpcErrorObject, + pub id: Option, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] +pub struct JrpcErrorObject { + pub code: i64, + pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum JrpcError { + ParseError, + InternalError, +} + +// note: error codes can be found in the rfc https://www.jsonrpc.org/specification#error_object +impl From for JrpcErrorObject { + fn from(error: JrpcError) -> Self { + match error { + JrpcError::ParseError => JrpcErrorObject { + code: -32700, + message: "Parse error".to_string(), + data: None, + }, + JrpcError::InternalError => JrpcErrorObject { + code: -32603, + message: "Internal error".to_string(), + data: None, + }, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)] +pub struct SymbolMetadata { + pub pyth_lazer_id: PriceFeedId, + pub name: String, + pub symbol: String, + pub description: String, + pub asset_type: String, + pub exponent: i16, + pub cmc_id: Option, + #[serde(default, with = "humantime_serde", alias = "interval")] + pub funding_rate_interval: Option, + pub min_publishers: u16, + pub min_channel: Channel, + pub state: SymbolState, + pub hermes_id: Option, + pub quote_currency: Option, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::jrpc::JrpcCall::{GetMetadata, PushUpdate}; + + #[test] + fn test_push_update_price() { + let json = r#" + { + "jsonrpc": "2.0", + "method": "push_update", + "params": { + "feed_id": 1, + "source_timestamp": 124214124124, + + "update": { + "type": "price", + "price": 1234567890, + "best_bid_price": 1234567891, + "best_ask_price": 1234567892 + } + }, + "id": 1 + } + "#; + + let expected = PythLazerAgentJrpcV1 { + jsonrpc: JsonRpcVersion::V2, + params: PushUpdate(FeedUpdateParams { + feed_id: PriceFeedId(1), + source_timestamp: TimestampUs(124214124124), + update: UpdateParams::PriceUpdate { + price: Price::from_integer(1234567890, 0).unwrap(), + best_bid_price: Some(Price::from_integer(1234567891, 0).unwrap()), + best_ask_price: Some(Price::from_integer(1234567892, 0).unwrap()), + }, + }), + id: 1, + }; + + assert_eq!( + serde_json::from_str::(json).unwrap(), + expected + ); + } + + #[test] + fn test_push_update_price_without_bid_ask() { + let json = r#" + { + "jsonrpc": "2.0", + "method": "push_update", + "params": { + "feed_id": 1, + "source_timestamp": 124214124124, + + "update": { + "type": "price", + "price": 1234567890 + } + }, + "id": 1 + } + "#; + + let expected = PythLazerAgentJrpcV1 { + jsonrpc: JsonRpcVersion::V2, + params: PushUpdate(FeedUpdateParams { + feed_id: PriceFeedId(1), + source_timestamp: TimestampUs(124214124124), + update: UpdateParams::PriceUpdate { + price: Price::from_integer(1234567890, 0).unwrap(), + best_bid_price: None, + best_ask_price: None, + }, + }), + id: 1, + }; + + assert_eq!( + serde_json::from_str::(json).unwrap(), + expected + ); + } + + #[test] + fn test_push_update_funding_rate() { + let json = r#" + { + "jsonrpc": "2.0", + "method": "push_update", + "params": { + "feed_id": 1, + "source_timestamp": 124214124124, + + "update": { + "type": "funding_rate", + "price": 1234567890, + "rate": 1234567891 + } + }, + "id": 1 + } + "#; + + let expected = PythLazerAgentJrpcV1 { + jsonrpc: JsonRpcVersion::V2, + params: PushUpdate(FeedUpdateParams { + feed_id: PriceFeedId(1), + source_timestamp: TimestampUs(124214124124), + update: UpdateParams::FundingRateUpdate { + price: Some(Price::from_integer(1234567890, 0).unwrap()), + rate: Rate::from_integer(1234567891, 0).unwrap(), + }, + }), + id: 1, + }; + + assert_eq!( + serde_json::from_str::(json).unwrap(), + expected + ); + } + #[test] + fn test_push_update_funding_rate_without_price() { + let json = r#" + { + "jsonrpc": "2.0", + "method": "push_update", + "params": { + "feed_id": 1, + "source_timestamp": 124214124124, + + "update": { + "type": "funding_rate", + "rate": 1234567891 + } + }, + "id": 1 + } + "#; + + let expected = PythLazerAgentJrpcV1 { + jsonrpc: JsonRpcVersion::V2, + params: PushUpdate(FeedUpdateParams { + feed_id: PriceFeedId(1), + source_timestamp: TimestampUs(124214124124), + update: UpdateParams::FundingRateUpdate { + price: None, + rate: Rate::from_integer(1234567891, 0).unwrap(), + }, + }), + id: 1, + }; + + assert_eq!( + serde_json::from_str::(json).unwrap(), + expected + ); + } + + #[test] + fn test_send_get_metadata() { + let json = r#" + { + "jsonrpc": "2.0", + "method": "get_metadata", + "params": { + "names": ["BTC/USD"], + "asset_types": ["crypto"] + }, + "id": 1 + } + "#; + + let expected = PythLazerAgentJrpcV1 { + jsonrpc: JsonRpcVersion::V2, + params: GetMetadata(GetMetadataParams { + names: Some(vec!["BTC/USD".to_string()]), + asset_types: Some(vec!["crypto".to_string()]), + }), + id: 1, + }; + + assert_eq!( + serde_json::from_str::(json).unwrap(), + expected + ); + } + + #[test] + fn test_get_metadata_without_filters() { + let json = r#" + { + "jsonrpc": "2.0", + "method": "get_metadata", + "params": {}, + "id": 1 + } + "#; + + let expected = PythLazerAgentJrpcV1 { + jsonrpc: JsonRpcVersion::V2, + params: GetMetadata(GetMetadataParams { + names: None, + asset_types: None, + }), + id: 1, + }; + + assert_eq!( + serde_json::from_str::(json).unwrap(), + expected + ); + } + + #[test] + fn test_response_format_error() { + let response = serde_json::from_str::( + r#" + { + "jsonrpc": "2.0", + "id": 2, + "error": { + "message": "Internal error", + "code": -32603 + } + } + "#, + ) + .unwrap(); + + assert_eq!( + response, + JrpcErrorResponse { + jsonrpc: JsonRpcVersion::V2, + error: JrpcErrorObject { + code: -32603, + message: "Internal error".to_string(), + data: None, + }, + id: Some(2), + } + ); + } + + #[test] + pub fn test_response_format_success() { + let response = serde_json::from_str::>( + r#" + { + "jsonrpc": "2.0", + "id": 2, + "result": "success" + } + "#, + ) + .unwrap(); + + assert_eq!( + response, + JrpcSuccessResponse:: { + jsonrpc: JsonRpcVersion::V2, + result: "success".to_string(), + id: 2, + } + ); + } +} diff --git a/lazer/sdk/rust/protocol/src/lib.rs b/lazer/sdk/rust/protocol/src/lib.rs index d10bedeebc..ded13bec8c 100644 --- a/lazer/sdk/rust/protocol/src/lib.rs +++ b/lazer/sdk/rust/protocol/src/lib.rs @@ -2,6 +2,7 @@ pub mod api; pub mod binary_update; +pub mod jrpc; pub mod message; pub mod payload; pub mod publisher; @@ -23,7 +24,7 @@ fn magics_in_big_endian() { }; // The values listed in this test can be used when reading the magic headers in BE format - // (e.g. on EVM). + // (e.g., on EVM). assert_eq!(u32::swap_bytes(BINARY_UPDATE_FORMAT_MAGIC), 1937213467); assert_eq!(u32::swap_bytes(PAYLOAD_FORMAT_MAGIC), 1976813459); @@ -44,6 +45,6 @@ fn magics_in_big_endian() { LE_UNSIGNED_FORMAT_MAGIC, ] { // Required to distinguish between byte orders. - assert!(u32::swap_bytes(magic) != magic); + assert_ne!(u32::swap_bytes(magic), magic); } } diff --git a/lazer/sdk/rust/protocol/src/router.rs b/lazer/sdk/rust/protocol/src/router.rs index 356b7dab88..29dda4f298 100644 --- a/lazer/sdk/rust/protocol/src/router.rs +++ b/lazer/sdk/rust/protocol/src/router.rs @@ -1,5 +1,6 @@ -//! WebSocket JSON protocol types for API the router provides to consumers and publishers. +//! WebSocket JSON protocol types for the API the router provides to consumers and publishers. +use protobuf::MessageField; use { crate::payload::AggregatedPriceFeedData, anyhow::{bail, Context}, @@ -37,6 +38,26 @@ impl TryFrom<&Timestamp> for TimestampUs { } } +impl From for Timestamp { + fn from(value: TimestampUs) -> Self { + Timestamp { + #[allow( + clippy::cast_possible_wrap, + reason = "u64 to i64 after this division can never overflow because the value cannot be too big" + )] + seconds: (value.0 / 1_000_000) as i64, + nanos: (value.0 % 1_000_000) as i32 * 1000, + special_fields: Default::default(), + } + } +} + +impl From for MessageField { + fn from(value: TimestampUs) -> Self { + MessageField::some(value.into()) + } +} + impl TimestampUs { pub fn now() -> Self { let value = SystemTime::now() @@ -304,7 +325,7 @@ impl<'de> Deserialize<'de> for Channel { D: serde::Deserializer<'de>, { let value = ::deserialize(deserializer)?; - parse_channel(&value).ok_or_else(|| D::Error::custom("unknown channel")) + parse_channel(&value).ok_or_else(|| Error::custom("unknown channel")) } } @@ -341,12 +362,14 @@ fn fixed_rate_values() { "values must be unique and sorted" ); for value in FixedRate::ALL { - assert!( - 1000 % value.ms == 0, + assert_eq!( + 1000 % value.ms, + 0, "1 s must contain whole number of intervals" ); - assert!( - value.value_us() % FixedRate::MIN.value_us() == 0, + assert_eq!( + value.value_us() % FixedRate::MIN.value_us(), + 0, "the interval's borders must be a subset of the minimal interval's borders" ); } @@ -383,7 +406,7 @@ impl<'de> Deserialize<'de> for SubscriptionParams { D: serde::Deserializer<'de>, { let value = SubscriptionParamsRepr::deserialize(deserializer)?; - Self::new(value).map_err(D::Error::custom) + Self::new(value).map_err(Error::custom) } } From 7bb548835a982cfc4374afd3401458a658bd59d0 Mon Sep 17 00:00:00 2001 From: Bart Platak Date: Wed, 9 Jul 2025 18:48:49 +0100 Subject: [PATCH 05/37] feat(pyth-lazer) Add more context to ParseError in JRPC protocol (#2843) --- Cargo.lock | 6 +++--- lazer/contracts/solana/Cargo.lock | 2 +- .../solana/programs/pyth-lazer-solana-contract/Cargo.toml | 2 +- lazer/publisher_sdk/rust/Cargo.toml | 2 +- lazer/sdk/rust/client/Cargo.toml | 2 +- lazer/sdk/rust/protocol/Cargo.toml | 2 +- lazer/sdk/rust/protocol/src/jrpc.rs | 6 +++--- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 474331d5e9..cc6a2f6bc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5569,7 +5569,7 @@ dependencies = [ "futures-util", "hex", "libsecp256k1 0.7.2", - "pyth-lazer-protocol 0.8.0", + "pyth-lazer-protocol 0.8.1", "serde", "serde_json", "tokio", @@ -5597,7 +5597,7 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.8.0" +version = "0.8.1" dependencies = [ "alloy-primitives 0.8.25", "anyhow", @@ -5625,7 +5625,7 @@ dependencies = [ "humantime", "protobuf", "protobuf-codegen", - "pyth-lazer-protocol 0.8.0", + "pyth-lazer-protocol 0.8.1", "serde-value", "tracing", ] diff --git a/lazer/contracts/solana/Cargo.lock b/lazer/contracts/solana/Cargo.lock index a4b07b8930..70c8d18134 100644 --- a/lazer/contracts/solana/Cargo.lock +++ b/lazer/contracts/solana/Cargo.lock @@ -3204,7 +3204,7 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.8.0" +version = "0.8.1" dependencies = [ "anyhow", "byteorder", diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml index 4888f6669e..62b020b6ac 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml @@ -19,7 +19,7 @@ no-log-ix-name = [] idl-build = ["anchor-lang/idl-build"] [dependencies] -pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.8.0" } +pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.8.1" } anchor-lang = "0.30.1" bytemuck = "1.20.0" diff --git a/lazer/publisher_sdk/rust/Cargo.toml b/lazer/publisher_sdk/rust/Cargo.toml index 25c00f42c3..c733917d83 100644 --- a/lazer/publisher_sdk/rust/Cargo.toml +++ b/lazer/publisher_sdk/rust/Cargo.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" repository = "https://github.com/pyth-network/pyth-crosschain" [dependencies] -pyth-lazer-protocol = { version = "0.8.0", path = "../../sdk/rust/protocol" } +pyth-lazer-protocol = { version = "0.8.1", path = "../../sdk/rust/protocol" } anyhow = "1.0.98" protobuf = "3.7.2" serde-value = "0.7.0" diff --git a/lazer/sdk/rust/client/Cargo.toml b/lazer/sdk/rust/client/Cargo.toml index 405556457f..2851f82cc9 100644 --- a/lazer/sdk/rust/client/Cargo.toml +++ b/lazer/sdk/rust/client/Cargo.toml @@ -6,7 +6,7 @@ description = "A Rust client for Pyth Lazer" license = "Apache-2.0" [dependencies] -pyth-lazer-protocol = { path = "../protocol", version = "0.8.0" } +pyth-lazer-protocol = { path = "../protocol", version = "0.8.1" } tokio = { version = "1", features = ["full"] } tokio-tungstenite = { version = "0.20", features = ["native-tls"] } futures-util = "0.3" diff --git a/lazer/sdk/rust/protocol/Cargo.toml b/lazer/sdk/rust/protocol/Cargo.toml index d0ef7aa904..0e4cf3b290 100644 --- a/lazer/sdk/rust/protocol/Cargo.toml +++ b/lazer/sdk/rust/protocol/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-lazer-protocol" -version = "0.8.0" +version = "0.8.1" edition = "2021" description = "Pyth Lazer SDK - protocol types." license = "Apache-2.0" diff --git a/lazer/sdk/rust/protocol/src/jrpc.rs b/lazer/sdk/rust/protocol/src/jrpc.rs index fa3fdce72c..2653beb27b 100644 --- a/lazer/sdk/rust/protocol/src/jrpc.rs +++ b/lazer/sdk/rust/protocol/src/jrpc.rs @@ -87,7 +87,7 @@ pub struct JrpcErrorObject { #[derive(Debug, Eq, PartialEq)] pub enum JrpcError { - ParseError, + ParseError(String), InternalError, } @@ -95,10 +95,10 @@ pub enum JrpcError { impl From for JrpcErrorObject { fn from(error: JrpcError) -> Self { match error { - JrpcError::ParseError => JrpcErrorObject { + JrpcError::ParseError(error_message) => JrpcErrorObject { code: -32700, message: "Parse error".to_string(), - data: None, + data: Some(error_message.into()), }, JrpcError::InternalError => JrpcErrorObject { code: -32603, From ab0b2a386052070470283eb902b2294108b225c4 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 13:09:35 -0500 Subject: [PATCH 06/37] cleaned up test_data file usage --- .../pyth-receiver/src/integration_tests.rs | 87 +++---------------- .../contracts/pyth-receiver/src/structs.rs | 1 - .../contracts/pyth-receiver/src/test_data.rs | 68 +++++++++++++++ 3 files changed, 79 insertions(+), 77 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index 9e6131146b..9cffae0bb7 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -1,9 +1,10 @@ #[cfg(test)] mod test { - use crate::error::PythReceiverError; - use crate::test_data; + use crate::{error::PythReceiverError}; + use crate::test_data::{self, good_update2_results, multiple_updates_results}; + use crate::test_data::good_update1_results; use crate::PythReceiver; - use alloy_primitives::{address, Address, I32, I64, U256, U64}; + use alloy_primitives::{Address, U256}; use motsu::prelude::*; use pythnet_sdk::wire::v1::{AccumulatorUpdateData, Proof}; use wormhole_contract::WormholeContract; @@ -12,13 +13,7 @@ mod test { 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, 0x5b, 0x43, ]; - const TEST_PUBLISH_TIME: u64 = 1751563000; - const TEST_PRICE: i64 = 10967241867779; - const TEST_CONF: u64 = 4971244966; - const TEST_EXPO: i32 = -8; - const TEST_EMA_PRICE: i64 = 10942391100000; - const TEST_EMA_CONF: u64 = 4398561400; - + const PYTHNET_CHAIN_ID: u16 = 26; const PYTHNET_EMITTER_ADDRESS: [u8; 32] = [ 0xe1, 0x01, 0xfa, 0xed, 0xac, 0x58, 0x51, 0xe3, 0x2b, 0x9b, 0x23, 0xb5, 0xf9, 0x41, 0x1a, @@ -32,31 +27,6 @@ mod test { const SINGLE_UPDATE_FEE_IN_WEI: U256 = U256::from_limbs([100, 0, 0, 0]); - #[cfg(test)] - fn current_guardians() -> Vec
{ - vec![ - address!("0x5893B5A76c3f739645648885bDCcC06cd70a3Cd3"), // Rockaway - address!("0xfF6CB952589BDE862c25Ef4392132fb9D4A42157"), // Staked - address!("0x114De8460193bdf3A2fCf81f86a09765F4762fD1"), // Figment - address!("0x107A0086b32d7A0977926A205131d8731D39cbEB"), // ChainodeTech - address!("0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2"), // Inotel - address!("0x11b39756C042441BE6D8650b69b54EbE715E2343"), // HashKey Cloud - address!("0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd"), // ChainLayer - address!("0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20"), // xLabs - address!("0x74a3bf913953D695260D88BC1aA25A4eeE363ef0"), // Forbole - address!("0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e"), // Staking Fund - address!("0xAF45Ced136b9D9e24903464AE889F5C8a723FC14"), // Moonlet Wallet - address!("0xf93124b7c738843CBB89E864c862c38cddCccF95"), // P2P Validator - address!("0xD2CC37A4dc036a8D232b48f62cDD4731412f4890"), // 01node - address!("0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811"), // MCF - address!("0x71AA1BE1D36CaFE3867910F99C09e347899C19C3"), // Everstake - address!("0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf"), // Chorus One - address!("0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8"), // Syncnode - address!("0x5E1487F35515d02A92753504a8D75471b9f49EdB"), // Triton - address!("0x6FbEBc898F403E4773E95feB15E80C9A99c8348d"), // Staking Facilities - ] - } - #[cfg(test)] fn mock_get_update_fee(update_data: Vec) -> Result { let update_data_array: &[u8] = &update_data; @@ -77,7 +47,7 @@ mod test { wormhole_contract: &Contract, alice: &Address, ) { - let guardians = current_guardians(); + let guardians = test_data::current_guardians(); let governance_contract = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); wormhole_contract @@ -137,14 +107,7 @@ mod test { assert!(price_result.is_ok()); assert_eq!( price_result.unwrap(), - ( - U64::from(TEST_PUBLISH_TIME), - I32::from_le_bytes(TEST_EXPO.to_le_bytes()), - I64::from_le_bytes(TEST_PRICE.to_le_bytes()), - U64::from(TEST_CONF), - I64::from_le_bytes(TEST_EMA_PRICE.to_le_bytes()), - U64::from(TEST_EMA_CONF) - ) + good_update1_results() ); } @@ -198,14 +161,7 @@ mod test { assert!(price_result.is_ok()); assert_eq!( price_result.unwrap(), - ( - U64::from(1751573860u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(10985663592646i64.to_le_bytes()), - U64::from(4569386330u64), - I64::from_le_bytes(10977795800000i64.to_le_bytes()), - U64::from(3919318300u64) - ) + good_update2_results() ); } @@ -273,14 +229,7 @@ mod test { assert!(price_result.is_ok()); assert_eq!( price_result.unwrap(), - ( - U64::from(1751573860u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(10985663592646i64.to_le_bytes()), - U64::from(4569386330u64), - I64::from_le_bytes(10977795800000i64.to_le_bytes()), - U64::from(3919318300u64) - ) + good_update2_results() ); } @@ -345,28 +294,14 @@ mod test { assert!(first_price_result.is_ok()); assert_eq!( first_price_result.unwrap(), - ( - U64::from(1751573123u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(10990356724259i64.to_le_bytes()), - U64::from(3891724259u64), - I64::from_le_bytes(10974970400000i64.to_le_bytes()), - U64::from(3918344000u64) - ) + multiple_updates_results()[0] ); let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); assert!(second_price_result.is_ok()); assert_eq!( second_price_result.unwrap(), - ( - U64::from(1751573123u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(258906787480i64.to_le_bytes()), - U64::from(158498649u64), - I64::from_le_bytes(258597182000i64.to_le_bytes()), - U64::from(131285914u64) - ) + multiple_updates_results()[1] ); } } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index abfcfd7d77..504619ae4c 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -1,5 +1,4 @@ use alloc::{vec::Vec}; -use serde::Serialize; use stylus_sdk::alloy_primitives::{keccak256, FixedBytes, B256, I32, I64, U16, U256, U64}; use stylus_sdk::{ prelude::*, diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index 07aac6deb1..c9c7708ee6 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -1,4 +1,5 @@ use hex::FromHex; +use alloy_primitives::{I32, I64, U64, address, Address}; pub fn good_update1() -> Vec { let hex_str = "504e41550100000003b801000000040d0216f3809b6396fdb0708bc94515ddb96a3bc8fb1993916e74f522ab4c34a268836a0bb38786303b55cc40ecb50d18c92bf9fd61688a143f3d24a73a3f468e4ab0000365be4f3a330fe96bab246922d9fa4816a865dec40d7c522e37f7e3605673b8f52eb30a4dad7aee3e1ac4b21337b8831c6147ef6e359e843b307872e83b5c7fe80004b6f12766eb395d04bdb146b1b69a4b9a8831a9c9ea20ee841d55efe72c629aef17038b925584493af7882981260b09587d73b6463415f4e1f8fb0a2a2c959c8301065c18a776cf558e43494bf31dd26ac0e3aed07a01ca0063abcfca3736b892368e4bf8b957f2d2e83ff9eb3fa8f667ef97a5f48c286103f70fda4a72e077719a0200085896c930febfb864a8c292bde679092e386e53389cb2d26bd12ceed64d22c0cd27676dc5c46d3e3c5949abbce89d4cc5ec378cfbf9ab1b0c24cfeb3371df4864000a1c67e67adea9f8a71eee76765f1fe4c7d9539d21b62d0336513692897cc6acbe1c827599a1f8c73b7478e8d788443b4cfd4e373344ab1ac9f771c9c494124863000b7e08e1cc1291340e6ec7d1b04c6a9c63f74077a074ee68edf4f95cf24c9743620f8abc8213884c8e4848fb76ec319d365e0cdc746a6e534a4cb828e51719be79010c61a2fedeedf34e4c80624d80cb93e24bafe9f6d23339173004d84e61c2395bfb5f552e87b901beb192ba6f4fc905eae30317c52b8614071e08a265592f24e8de000de38d08612edc5ebc863ea5a96325cb991750a94ff0e50f86bc322cce81db7cd17cb4ed3703f7477eb111a9d881e1345f5b79d618814f31b46034196563ee6a18000e5b11510d585a2decaba0be9e71386865b6069061478f7254d7852d55116569eb426c385267e40264d16db08e0f1a9e2c44b7d1c926d3c3ba662b212572386483010fa1ad0278402104b146b6f4d1cf85ac4df961d24eea0c7948b700f9973596cad130abfff25feef37125ba38507f34308d967455651e7014e40264a0c6510d3af001101f2e4e70868d6c327c92229537f1ffa33e488da3140ccc086f8210b437b162190742bd01ad1cb495a93f20045b4bb47e1562fdd82a8a1548d87d788a492b17190111847f5095df3edefc58be0956aee19876e850516e132506fd67504afee3c8cf240f07fb607b3c8282a2e56dcad23959e6759bdf8ad345ba8150448da56ff34fcc016866baf800000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000880cea9014155575600000000000d8e5c8d0000271085e6ab1bb044f57c4cd6c1d32aa0a82a5032198301005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009f9828e420300000001284f2da6fffffff8000000006866baf8000000006866baf8000009f3b955a26000000001062cb8780c1283a49180b4986f0dec3c746d3daeb597887747b8f66a09876e1253a1ebb8a6bc4a5793daaa343db6057b82ec29715d7e1db0ffd6db523f7b80b03e3866ef5f5c80728801b92f5acecc28d8517e5615335d89c553f94b4370f3a20be0bc23e0fd401c4e5bd8bd32948a26233fc48f116428a490f087030ccfc442753e3074e2b9bbc1c61a009d86aaa200645c627a6b7f2f6597e34c60b14a58ef2583bcbb1d0e21b71a264fad2648ecc545031c7ed598772ffe875bf94a488389a49e6025e1b2a1f07ec598d0d9aa8ef7dd2733c8502c49d1d1323f1ae664e82e8a5e14978d52ab448ba9b1afc78f06c8cd17415a17"; @@ -6,6 +7,49 @@ pub fn good_update1() -> Vec { bytes } +pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { + ( + U64::from(1751563000u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10967241867779i64.to_le_bytes()), + U64::from(4971244966u64), + I64::from_le_bytes(10942391100000i64.to_le_bytes()), + U64::from(4398561400u64) + ) +} + +pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { + [ + ( + U64::from(1751573123u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10990356724259i64.to_le_bytes()), + U64::from(3891724259u64), + I64::from_le_bytes(10974970400000i64.to_le_bytes()), + U64::from(3918344000u64) + ), + ( + U64::from(1751573123u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(258906787480i64.to_le_bytes()), + U64::from(158498649u64), + I64::from_le_bytes(258597182000i64.to_le_bytes()), + U64::from(131285914u64) + ) + ] +} + +pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { + ( + U64::from(1751573860u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10985663592646i64.to_le_bytes()), + U64::from(4569386330u64), + I64::from_le_bytes(10977795800000i64.to_le_bytes()), + U64::from(3919318300u64) + ) +} + pub fn good_update2() -> Vec { let hex_str = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); @@ -17,3 +61,27 @@ pub fn multiple_updates() -> Vec { let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); bytes } + +pub fn current_guardians() -> Vec
{ + vec![ + address!("0x5893B5A76c3f739645648885bDCcC06cd70a3Cd3"), // Rockaway + address!("0xfF6CB952589BDE862c25Ef4392132fb9D4A42157"), // Staked + address!("0x114De8460193bdf3A2fCf81f86a09765F4762fD1"), // Figment + address!("0x107A0086b32d7A0977926A205131d8731D39cbEB"), // ChainodeTech + address!("0x8C82B2fd82FaeD2711d59AF0F2499D16e726f6b2"), // Inotel + address!("0x11b39756C042441BE6D8650b69b54EbE715E2343"), // HashKey Cloud + address!("0x54Ce5B4D348fb74B958e8966e2ec3dBd4958a7cd"), // ChainLayer + address!("0x15e7cAF07C4e3DC8e7C469f92C8Cd88FB8005a20"), // xLabs + address!("0x74a3bf913953D695260D88BC1aA25A4eeE363ef0"), // Forbole + address!("0x000aC0076727b35FBea2dAc28fEE5cCB0fEA768e"), // Staking Fund + address!("0xAF45Ced136b9D9e24903464AE889F5C8a723FC14"), // Moonlet Wallet + address!("0xf93124b7c738843CBB89E864c862c38cddCccF95"), // P2P Validator + address!("0xD2CC37A4dc036a8D232b48f62cDD4731412f4890"), // 01node + address!("0xDA798F6896A3331F64b48c12D1D57Fd9cbe70811"), // MCF + address!("0x71AA1BE1D36CaFE3867910F99C09e347899C19C3"), // Everstake + address!("0x8192b6E7387CCd768277c17DAb1b7a5027c0b3Cf"), // Chorus One + address!("0x178e21ad2E77AE06711549CFBB1f9c7a9d8096e8"), // Syncnode + address!("0x5E1487F35515d02A92753504a8D75471b9f49EdB"), // Triton + address!("0x6FbEBc898F403E4773E95feB15E80C9A99c8348d"), // Staking Facilities + ] +} From 619c87afd9997c9afe3397357fa4392731761a43 Mon Sep 17 00:00:00 2001 From: Bart Platak Date: Wed, 9 Jul 2025 19:13:10 +0100 Subject: [PATCH 07/37] chore(pyth-lazer) Bump the version of publisher sdk (#2844) --- Cargo.lock | 12 ++++++------ lazer/publisher_sdk/rust/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc6a2f6bc6..aab5e1e099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5541,7 +5541,7 @@ dependencies = [ "hyper-util", "protobuf", "pyth-lazer-protocol 0.7.3", - "pyth-lazer-publisher-sdk 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pyth-lazer-publisher-sdk 0.1.6", "serde", "serde_json", "soketto", @@ -5619,29 +5619,29 @@ dependencies = [ [[package]] name = "pyth-lazer-publisher-sdk" version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6ef4052ebf2a7943259b3d52a10b2231ffc346717735c50e44d73fe92019d5" dependencies = [ "anyhow", "fs-err", "humantime", "protobuf", "protobuf-codegen", - "pyth-lazer-protocol 0.8.1", + "pyth-lazer-protocol 0.7.3", "serde-value", "tracing", ] [[package]] name = "pyth-lazer-publisher-sdk" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6ef4052ebf2a7943259b3d52a10b2231ffc346717735c50e44d73fe92019d5" +version = "0.1.7" dependencies = [ "anyhow", "fs-err", "humantime", "protobuf", "protobuf-codegen", - "pyth-lazer-protocol 0.7.3", + "pyth-lazer-protocol 0.8.1", "serde-value", "tracing", ] diff --git a/lazer/publisher_sdk/rust/Cargo.toml b/lazer/publisher_sdk/rust/Cargo.toml index c733917d83..4660a9c6a2 100644 --- a/lazer/publisher_sdk/rust/Cargo.toml +++ b/lazer/publisher_sdk/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-lazer-publisher-sdk" -version = "0.1.6" +version = "0.1.7" edition = "2021" description = "Pyth Lazer Publisher SDK types." license = "Apache-2.0" From 685d2236460e37d5efaacf64dd2326a64bb39f99 Mon Sep 17 00:00:00 2001 From: Bart Platak Date: Wed, 9 Jul 2025 19:33:16 +0100 Subject: [PATCH 08/37] feat(pyth-lazer) Implement JRPC endpoint for the lazer agent (#2837) --- Cargo.lock | 127 +++++-- apps/pyth-lazer-agent/Cargo.toml | 7 +- apps/pyth-lazer-agent/config/config.toml | 1 + apps/pyth-lazer-agent/src/config.rs | 1 + apps/pyth-lazer-agent/src/http_server.rs | 63 +++- apps/pyth-lazer-agent/src/jrpc_handle.rs | 328 ++++++++++++++++++ apps/pyth-lazer-agent/src/lazer_publisher.rs | 3 +- apps/pyth-lazer-agent/src/main.rs | 1 + apps/pyth-lazer-agent/src/publisher_handle.rs | 6 +- apps/pyth-lazer-agent/src/relayer_session.rs | 6 +- 10 files changed, 495 insertions(+), 48 deletions(-) create mode 100644 apps/pyth-lazer-agent/src/jrpc_handle.rs diff --git a/Cargo.lock b/Cargo.lock index aab5e1e099..d4e79a1803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -768,6 +768,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -3383,6 +3389,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", + "indexmap 2.10.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.11.2" @@ -3659,7 +3684,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -3682,6 +3707,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2 0.4.11", "http 1.3.1", "http-body 1.0.1", "httparse", @@ -3737,6 +3763,22 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.14" @@ -3756,9 +3798,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -5540,8 +5584,9 @@ dependencies = [ "hyper 1.6.0", "hyper-util", "protobuf", - "pyth-lazer-protocol 0.7.3", - "pyth-lazer-publisher-sdk 0.1.6", + "pyth-lazer-protocol 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "pyth-lazer-publisher-sdk 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.12.22", "serde", "serde_json", "soketto", @@ -5580,15 +5625,19 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6445dc5d2f7fff7c677fb8edc5a080a82ef7583c1bdb39daa95421788c23f695" +version = "0.8.1" dependencies = [ + "alloy-primitives 0.8.25", "anyhow", - "base64 0.22.1", + "bincode 1.3.3", + "bs58", "byteorder", "derive_more 1.0.0", + "ed25519-dalek 2.1.1", + "hex", + "humantime-serde", "itertools 0.13.0", + "libsecp256k1 0.7.2", "protobuf", "rust_decimal", "serde", @@ -5598,18 +5647,14 @@ dependencies = [ [[package]] name = "pyth-lazer-protocol" version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1258b8770756a82a39b7b02a296c10a91b93aa58c0cded47950defe4d9377644" dependencies = [ - "alloy-primitives 0.8.25", "anyhow", - "bincode 1.3.3", - "bs58", "byteorder", "derive_more 1.0.0", - "ed25519-dalek 2.1.1", - "hex", "humantime-serde", "itertools 0.13.0", - "libsecp256k1 0.7.2", "protobuf", "rust_decimal", "serde", @@ -5618,16 +5663,14 @@ dependencies = [ [[package]] name = "pyth-lazer-publisher-sdk" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6ef4052ebf2a7943259b3d52a10b2231ffc346717735c50e44d73fe92019d5" +version = "0.1.7" dependencies = [ "anyhow", "fs-err", "humantime", "protobuf", "protobuf-codegen", - "pyth-lazer-protocol 0.7.3", + "pyth-lazer-protocol 0.8.1", "serde-value", "tracing", ] @@ -5635,13 +5678,15 @@ dependencies = [ [[package]] name = "pyth-lazer-publisher-sdk" version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d52a515b21b77a89266d584da4363fcd1e121213ac3065ab7ff0dab1172006" dependencies = [ "anyhow", "fs-err", "humantime", "protobuf", "protobuf-codegen", - "pyth-lazer-protocol 0.8.1", + "pyth-lazer-protocol 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde-value", "tracing", ] @@ -6102,12 +6147,12 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "hyper 0.14.32", "hyper-rustls 0.24.2", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -6122,7 +6167,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "sync_wrapper 0.1.2", - "system-configuration", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", @@ -6144,17 +6189,22 @@ dependencies = [ "async-compression", "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", + "h2 0.4.11", "http 1.3.1", "http-body 1.0.1", "http-body-util", "hyper 1.6.0", "hyper-rustls 0.27.7", + "hyper-tls 0.6.0", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -6165,6 +6215,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", + "tokio-native-tls", "tokio-rustls 0.26.2", "tokio-util", "tower 0.5.2", @@ -9644,7 +9695,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -9657,6 +9719,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -10743,6 +10815,17 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-registry" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.3.4" diff --git a/apps/pyth-lazer-agent/Cargo.toml b/apps/pyth-lazer-agent/Cargo.toml index b457a2cdb8..bf73676e22 100644 --- a/apps/pyth-lazer-agent/Cargo.toml +++ b/apps/pyth-lazer-agent/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.3" edition = "2024" [dependencies] -pyth-lazer-publisher-sdk = "0.1.5" -pyth-lazer-protocol = "0.7.2" +pyth-lazer-publisher-sdk = "0.1.7" +pyth-lazer-protocol = "0.8.1" anyhow = "1.0.98" backoff = "0.4.0" @@ -20,7 +20,7 @@ futures-util = "0.3.31" http = "1.3.1" http-body-util = "0.1.3" humantime-serde = "1.1.1" -hyper = { version = "1.6.0", features = ["http1", "server"] } +hyper = { version = "1.6.0", features = ["http1", "server", "client"] } hyper-util = { version = "0.1.10", features = ["tokio"] } protobuf = "3.7.2" serde = { version = "1.0.219", features = ["derive"] } @@ -33,6 +33,7 @@ tokio-util = { version = "0.7.14", features = ["compat"] } tracing = "0.1.41" tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] } url = { version = "2.5.4", features = ["serde"] } +reqwest = "0.12.22" [dev-dependencies] tempfile = "3.20.0" diff --git a/apps/pyth-lazer-agent/config/config.toml b/apps/pyth-lazer-agent/config/config.toml index d75a708880..7459a196ab 100644 --- a/apps/pyth-lazer-agent/config/config.toml +++ b/apps/pyth-lazer-agent/config/config.toml @@ -2,3 +2,4 @@ relayer_urls = ["ws://relayer-0.pyth-lazer.dourolabs.app/v1/transaction", "ws:// publish_keypair_path = "/path/to/solana/id.json" listen_address = "0.0.0.0:8910" publish_interval_duration = "25ms" +authorization_token="token1" diff --git a/apps/pyth-lazer-agent/src/config.rs b/apps/pyth-lazer-agent/src/config.rs index 57b0652be7..d0721939e9 100644 --- a/apps/pyth-lazer-agent/src/config.rs +++ b/apps/pyth-lazer-agent/src/config.rs @@ -17,6 +17,7 @@ pub struct Config { pub publish_keypair_path: PathBuf, #[serde(with = "humantime_serde", default = "default_publish_interval")] pub publish_interval_duration: Duration, + pub history_service_url: Option, } fn default_publish_interval() -> Duration { diff --git a/apps/pyth-lazer-agent/src/http_server.rs b/apps/pyth-lazer-agent/src/http_server.rs index 235fdb2af7..8e5b5a2bec 100644 --- a/apps/pyth-lazer-agent/src/http_server.rs +++ b/apps/pyth-lazer-agent/src/http_server.rs @@ -1,32 +1,39 @@ +use crate::jrpc_handle::{JrpcConnectionContext, handle_jrpc}; +use crate::publisher_handle::handle_publisher; +use crate::{ + config::Config, lazer_publisher::LazerPublisher, publisher_handle::PublisherConnectionContext, +}; use anyhow::{Context, Result}; +use hyper::body::Incoming; use hyper::{Response, StatusCode, body::Bytes, server::conn::http1, service::service_fn}; use hyper_util::rt::TokioIo; use soketto::{ BoxedError, handshake::http::{Server, is_upgrade_request}, }; +use std::fmt::Debug; use std::{io, net::SocketAddr}; use tokio::net::{TcpListener, TcpStream}; use tracing::{debug, info, instrument, warn}; -use crate::{ - config::Config, - lazer_publisher::LazerPublisher, - publisher_handle::{PublisherConnectionContext, handle_publisher}, -}; - type FullBody = http_body_util::Full; -#[derive(Debug)] -pub enum Request { +#[derive(Debug, Copy, Clone)] +pub enum PublisherRequest { PublisherV1, PublisherV2, } -pub struct RelayerRequest(pub http::Request); +pub enum Request { + PublisherRequest(PublisherRequest), + JrpcV1, +} + +pub struct RelayerRequest(pub http::Request); -const PUBLISHER_WS_URI: &str = "/v1/publisher"; +const PUBLISHER_WS_URI_V1: &str = "/v1/publisher"; const PUBLISHER_WS_URI_V2: &str = "/v2/publisher"; +const JRPC_WS_URI_V1: &str = "/v1/jprc"; const READINESS_PROBE_PATH: &str = "/ready"; const LIVENESS_PROBE_PATH: &str = "/live"; @@ -38,8 +45,11 @@ pub async fn run(config: Config, lazer_publisher: LazerPublisher) -> Result<()> loop { let stream_addr = listener.accept().await; let lazer_publisher_clone = lazer_publisher.clone(); - tokio::spawn(async { - if let Err(err) = try_handle_connection(stream_addr, lazer_publisher_clone).await { + let config = config.clone(); + tokio::spawn(async move { + if let Err(err) = + try_handle_connection(config, stream_addr, lazer_publisher_clone).await + { warn!("error while handling connection: {err:?}"); } }); @@ -47,6 +57,7 @@ pub async fn run(config: Config, lazer_publisher: LazerPublisher) -> Result<()> } async fn try_handle_connection( + config: Config, stream_addr: io::Result<(TcpStream, SocketAddr)>, lazer_publisher: LazerPublisher, ) -> Result<()> { @@ -58,7 +69,12 @@ async fn try_handle_connection( TokioIo::new(stream), service_fn(move |r| { let request = RelayerRequest(r); - request_handler(request, remote_addr, lazer_publisher.clone()) + request_handler( + config.clone(), + request, + remote_addr, + lazer_publisher.clone(), + ) }), ) .with_upgrades() @@ -68,6 +84,7 @@ async fn try_handle_connection( #[instrument(skip_all, fields(component = "http_server", remote_addr = remote_addr.to_string()))] async fn request_handler( + config: Config, request: RelayerRequest, remote_addr: SocketAddr, lazer_publisher: LazerPublisher, @@ -75,8 +92,9 @@ async fn request_handler( let path = request.0.uri().path(); let request_type = match path { - PUBLISHER_WS_URI => Request::PublisherV1, - PUBLISHER_WS_URI_V2 => Request::PublisherV2, + PUBLISHER_WS_URI_V1 => Request::PublisherRequest(PublisherRequest::PublisherV1), + PUBLISHER_WS_URI_V2 => Request::PublisherRequest(PublisherRequest::PublisherV2), + JRPC_WS_URI_V1 => Request::JrpcV1, LIVENESS_PROBE_PATH => { let response = Response::builder().status(StatusCode::OK); return Ok(response.body(FullBody::default())?); @@ -113,9 +131,9 @@ async fn request_handler( Ok(response) => { info!("accepted connection from publisher"); match request_type { - Request::PublisherV1 | Request::PublisherV2 => { + Request::PublisherRequest(publisher_request_type) => { let publisher_connection_context = PublisherConnectionContext { - request_type, + request_type: publisher_request_type, _remote_addr: remote_addr, }; tokio::spawn(handle_publisher( @@ -126,6 +144,17 @@ async fn request_handler( )); Ok(response.map(|()| FullBody::default())) } + Request::JrpcV1 => { + let publisher_connection_context = JrpcConnectionContext {}; + tokio::spawn(handle_jrpc( + config.clone(), + server, + request.0, + publisher_connection_context, + lazer_publisher, + )); + Ok(response.map(|()| FullBody::default())) + } } } Err(e) => { diff --git a/apps/pyth-lazer-agent/src/jrpc_handle.rs b/apps/pyth-lazer-agent/src/jrpc_handle.rs new file mode 100644 index 0000000000..b0c752910c --- /dev/null +++ b/apps/pyth-lazer-agent/src/jrpc_handle.rs @@ -0,0 +1,328 @@ +use crate::config::Config; +use crate::lazer_publisher::LazerPublisher; +use crate::websocket_utils::{handle_websocket_error, send_text}; +use anyhow::Error; +use futures::{AsyncRead, AsyncWrite}; +use futures_util::io::{BufReader, BufWriter}; +use hyper_util::rt::TokioIo; +use pyth_lazer_protocol::jrpc::{ + GetMetadataParams, JrpcCall, JrpcError, JrpcErrorResponse, JrpcResponse, JrpcSuccessResponse, + JsonRpcVersion, PythLazerAgentJrpcV1, SymbolMetadata, +}; +use soketto::Sender; +use soketto::handshake::http::Server; +use std::str::FromStr; +use tokio::{pin, select}; +use tokio_util::compat::TokioAsyncReadCompatExt; +use tracing::{debug, error, instrument}; +use url::Url; + +const DEFAULT_HISTORY_SERVICE_URL: &str = + "https://history.pyth-lazer.dourolabs.app/history/v1/symbols"; + +pub struct JrpcConnectionContext {} + +#[instrument( + skip(server, request, lazer_publisher, context), + fields(component = "jrpc_ws") +)] +pub async fn handle_jrpc( + config: Config, + server: Server, + request: hyper::Request, + context: JrpcConnectionContext, + lazer_publisher: LazerPublisher, +) { + if let Err(err) = try_handle_jrpc(config, server, request, context, lazer_publisher).await { + handle_websocket_error(err); + } +} + +#[instrument( + skip(server, request, lazer_publisher, _context), + fields(component = "jrpc_ws") +)] +async fn try_handle_jrpc( + config: Config, + server: Server, + request: hyper::Request, + _context: JrpcConnectionContext, + lazer_publisher: LazerPublisher, +) -> anyhow::Result<()> { + let stream = hyper::upgrade::on(request).await?; + let io = TokioIo::new(stream); + let stream = BufReader::new(BufWriter::new(io.compat())); + let (mut ws_sender, mut ws_receiver) = server.into_builder(stream).finish(); + + let mut receive_buf = Vec::new(); + + loop { + receive_buf.clear(); + { + // soketto is not cancel-safe, so we need to store the future and poll it + // in the inner loop. + let receive = async { ws_receiver.receive(&mut receive_buf).await }; + pin!(receive); + #[allow(clippy::never_loop, reason = "false positive")] // false positive + loop { + select! { + _result = &mut receive => { + break + } + } + } + } + + match handle_jrpc_inner(&config, &mut ws_sender, &mut receive_buf, &lazer_publisher).await { + Ok(_) => {} + Err(err) => { + debug!("Error handling JRPC request: {}", err); + send_text( + &mut ws_sender, + serde_json::to_string::>(&JrpcResponse::Error( + JrpcErrorResponse { + jsonrpc: JsonRpcVersion::V2, + error: JrpcError::InternalError.into(), + id: None, + }, + ))? + .as_str(), + ) + .await?; + } + } + } +} + +async fn handle_jrpc_inner( + config: &Config, + sender: &mut Sender, + receive_buf: &mut Vec, + lazer_publisher: &LazerPublisher, +) -> anyhow::Result<()> { + match serde_json::from_slice::(receive_buf.as_slice()) { + Ok(jrpc_request) => match jrpc_request.params { + JrpcCall::PushUpdate(request_params) => { + match lazer_publisher + .push_feed_update(request_params.into()) + .await + { + Ok(_) => { + send_text( + sender, + serde_json::to_string::>(&JrpcResponse::Success( + JrpcSuccessResponse:: { + jsonrpc: JsonRpcVersion::V2, + result: "success".to_string(), + id: jrpc_request.id, + }, + ))? + .as_str(), + ) + .await?; + } + Err(err) => { + debug!("error while sending updates: {:?}", err); + send_text( + sender, + serde_json::to_string::>(&JrpcResponse::Error( + JrpcErrorResponse { + jsonrpc: JsonRpcVersion::V2, + error: JrpcError::InternalError.into(), + id: Some(jrpc_request.id), + }, + ))? + .as_str(), + ) + .await?; + } + } + } + JrpcCall::GetMetadata(request_params) => match get_metadata(config.clone()).await { + Ok(symbols) => { + let symbols = filter_symbols(symbols.clone(), request_params); + + send_text( + sender, + serde_json::to_string::>>( + &JrpcResponse::Success(JrpcSuccessResponse::> { + jsonrpc: JsonRpcVersion::V2, + result: symbols, + id: jrpc_request.id, + }), + )? + .as_str(), + ) + .await?; + } + Err(err) => { + error!("error while retrieving metadata: {:?}", err); + send_text( + sender, + serde_json::to_string::>(&JrpcResponse::Error( + JrpcErrorResponse { + jsonrpc: JsonRpcVersion::V2, + // note: right now specifying an invalid method results in a parse error + error: JrpcError::InternalError.into(), + id: None, + }, + ))? + .as_str(), + ) + .await?; + } + }, + }, + Err(err) => { + debug!("Error parsing JRPC request: {}", err); + send_text( + sender, + serde_json::to_string::>(&JrpcResponse::Error( + JrpcErrorResponse { + jsonrpc: JsonRpcVersion::V2, + error: JrpcError::ParseError(err.to_string()).into(), + id: None, + }, + ))? + .as_str(), + ) + .await?; + } + } + Ok(()) +} + +async fn get_metadata(config: Config) -> Result, Error> { + let result = reqwest::get( + config + .history_service_url + .unwrap_or(Url::from_str(DEFAULT_HISTORY_SERVICE_URL)?), + ) + .await?; + + if result.status().is_success() { + Ok(serde_json::from_str::>( + &result.text().await?, + )?) + } else { + Err(anyhow::anyhow!( + "Error getting metadata (status_code={}, body={})", + result.status(), + result.text().await.unwrap_or("none".to_string()) + )) + } +} + +fn filter_symbols( + symbols: Vec, + get_metadata_params: GetMetadataParams, +) -> Vec { + let names = &get_metadata_params.names.clone(); + let asset_types = &get_metadata_params.asset_types.clone(); + + let res: Vec = symbols + .into_iter() + .filter(|symbol| { + if let Some(names) = names { + if !names.contains(&symbol.name) { + return false; + } + } + + if let Some(asset_types) = asset_types { + if !asset_types.contains(&symbol.asset_type) { + return false; + } + } + + true + }) + .collect(); + + res +} + +#[cfg(test)] +pub mod tests { + use super::*; + use pyth_lazer_protocol::router::{Channel, FixedRate, PriceFeedId}; + use pyth_lazer_protocol::symbol_state::SymbolState; + use std::net::SocketAddr; + + fn gen_test_symbol(name: String, asset_type: String) -> SymbolMetadata { + SymbolMetadata { + pyth_lazer_id: PriceFeedId(1), + name, + symbol: "".to_string(), + description: "".to_string(), + asset_type, + exponent: 0, + cmc_id: None, + funding_rate_interval: None, + min_publishers: 0, + min_channel: Channel::FixedRate(FixedRate::MIN), + state: SymbolState::Stable, + hermes_id: None, + quote_currency: None, + } + } + + #[tokio::test] + #[ignore] + async fn test_try_get_metadata() { + let config = Config { + listen_address: SocketAddr::from(([127, 0, 0, 1], 0)), + relayer_urls: vec![], + authorization_token: None, + publish_keypair_path: Default::default(), + publish_interval_duration: Default::default(), + history_service_url: None, + }; + + println!("{:?}", get_metadata(config).await.unwrap()); + } + + #[test] + fn test_filter_symbols() { + let symbol1 = gen_test_symbol("BTC".to_string(), "crypto".to_string()); + let symbol2 = gen_test_symbol("XMR".to_string(), "crypto".to_string()); + let symbol3 = gen_test_symbol("BTCUSDT".to_string(), "funding-rate".to_string()); + let symbols = vec![symbol1.clone(), symbol2.clone(), symbol3.clone()]; + + // just a name filter + assert_eq!( + filter_symbols( + symbols.clone(), + GetMetadataParams { + names: Some(vec!["XMR".to_string()]), + asset_types: None, + }, + ), + vec![symbol2.clone()] + ); + + // just an asset type filter + assert_eq!( + filter_symbols( + symbols.clone(), + GetMetadataParams { + names: None, + asset_types: Some(vec!["crypto".to_string()]), + }, + ), + vec![symbol1.clone(), symbol2.clone()] + ); + + // name and asset type + assert_eq!( + filter_symbols( + symbols.clone(), + GetMetadataParams { + names: Some(vec!["BTC".to_string()]), + asset_types: Some(vec!["crypto".to_string()]), + }, + ), + vec![symbol1.clone()] + ); + } +} diff --git a/apps/pyth-lazer-agent/src/lazer_publisher.rs b/apps/pyth-lazer-agent/src/lazer_publisher.rs index b5c2d304cd..b453362caf 100644 --- a/apps/pyth-lazer-agent/src/lazer_publisher.rs +++ b/apps/pyth-lazer-agent/src/lazer_publisher.rs @@ -24,7 +24,7 @@ use tokio::{ }; use tracing::error; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct LazerPublisher { sender: Sender, pub(crate) is_ready: Arc, @@ -223,6 +223,7 @@ mod tests { authorization_token: None, publish_keypair_path: PathBuf::from(signing_key_file.path()), publish_interval_duration: Duration::from_millis(25), + history_service_url: None, }; let (relayer_sender, mut relayer_receiver) = broadcast::channel(CHANNEL_CAPACITY); diff --git a/apps/pyth-lazer-agent/src/main.rs b/apps/pyth-lazer-agent/src/main.rs index 8cbe81c927..7d319a7c4c 100644 --- a/apps/pyth-lazer-agent/src/main.rs +++ b/apps/pyth-lazer-agent/src/main.rs @@ -8,6 +8,7 @@ use { mod config; mod http_server; +mod jrpc_handle; mod lazer_publisher; mod publisher_handle; mod relayer_session; diff --git a/apps/pyth-lazer-agent/src/publisher_handle.rs b/apps/pyth-lazer-agent/src/publisher_handle.rs index 45c2b65b4e..a75ddc6d87 100644 --- a/apps/pyth-lazer-agent/src/publisher_handle.rs +++ b/apps/pyth-lazer-agent/src/publisher_handle.rs @@ -22,7 +22,7 @@ use crate::{ }; pub struct PublisherConnectionContext { - pub request_type: http_server::Request, + pub request_type: http_server::PublisherRequest, pub _remote_addr: SocketAddr, } @@ -81,7 +81,7 @@ async fn try_handle_publisher( // reply with an error if we can't parse the binary update let feed_update: FeedUpdate = match context.request_type { - http_server::Request::PublisherV1 => { + http_server::PublisherRequest::PublisherV1 => { match bincode::serde::decode_from_slice::( &receive_buf, bincode::config::legacy(), @@ -132,7 +132,7 @@ async fn try_handle_publisher( } } } - http_server::Request::PublisherV2 => { + http_server::PublisherRequest::PublisherV2 => { match bincode::serde::decode_from_slice::( &receive_buf, bincode::config::legacy(), diff --git a/apps/pyth-lazer-agent/src/relayer_session.rs b/apps/pyth-lazer-agent/src/relayer_session.rs index d89d8b140f..33423da33b 100644 --- a/apps/pyth-lazer-agent/src/relayer_session.rs +++ b/apps/pyth-lazer-agent/src/relayer_session.rs @@ -35,6 +35,7 @@ async fn connect_to_relayer( HeaderValue::from_str(&format!("Bearer {token}"))?, ); let (ws_stream, _) = connect_async_with_config(req, None, true).await?; + tracing::info!("connected to the relayer at {}", url); Ok(ws_stream.split()) } @@ -149,7 +150,7 @@ impl RelayerSessionTask { msg = relayer_ws_receiver.next() => { match msg { Some(Ok(msg)) => { - tracing::debug!("Received message from relayer: {msg:?}"); + tracing::debug!("Received a message from relayer: {msg:?}"); } Some(Err(e)) => { tracing::error!("Error receiving message from at relayer: {e:?}"); @@ -165,6 +166,7 @@ impl RelayerSessionTask { } } +//noinspection DuplicatedCode #[cfg(test)] mod tests { use crate::relayer_session::RelayerSessionTask; @@ -215,7 +217,7 @@ mod tests { while let Some(msg) = read.next().await { if let Ok(msg) = msg { if msg.is_binary() { - tracing::info!("Received binary message: {msg:?}"); + tracing::info!("Received a binary message: {msg:?}"); let transaction = SignedLazerTransaction::parse_from_bytes(msg.into_data().as_ref()) .unwrap(); From 8b2c59e24cc6af8270c655a21bfca1921b1565b1 Mon Sep 17 00:00:00 2001 From: Bart Platak Date: Wed, 9 Jul 2025 19:37:15 +0100 Subject: [PATCH 09/37] chore(pyth-lazer) Bump pyth-lazer-agent to 0.1.4 (#2846) --- Cargo.lock | 2 +- apps/pyth-lazer-agent/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4e79a1803..29e38ea49b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5566,7 +5566,7 @@ dependencies = [ [[package]] name = "pyth-lazer-agent" -version = "0.1.3" +version = "0.1.4" dependencies = [ "anyhow", "backoff", diff --git a/apps/pyth-lazer-agent/Cargo.toml b/apps/pyth-lazer-agent/Cargo.toml index bf73676e22..6283cbe0ad 100644 --- a/apps/pyth-lazer-agent/Cargo.toml +++ b/apps/pyth-lazer-agent/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pyth-lazer-agent" -version = "0.1.3" +version = "0.1.4" edition = "2024" [dependencies] From 87bc98a8bff524362d206d11485a9cf504464f86 Mon Sep 17 00:00:00 2001 From: Bart Platak Date: Wed, 9 Jul 2025 19:45:13 +0100 Subject: [PATCH 10/37] fix(argus) Fix compilation issues related to EscalationPolicy (#2842) --- apps/argus/src/config.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/argus/src/config.rs b/apps/argus/src/config.rs index 8f79402c2f..82520636d0 100644 --- a/apps/argus/src/config.rs +++ b/apps/argus/src/config.rs @@ -159,10 +159,6 @@ impl Default for EscalationPolicyConfig { impl EscalationPolicyConfig { pub fn to_policy(&self) -> EscalationPolicy { EscalationPolicy { - gas_limit_tolerance_pct: self.gas_limit_tolerance_pct, - initial_gas_multiplier_pct: self.initial_gas_multiplier_pct, - gas_multiplier_pct: self.gas_multiplier_pct, - gas_multiplier_cap_pct: self.gas_multiplier_cap_pct, fee_multiplier_pct: self.fee_multiplier_pct, fee_multiplier_cap_pct: self.fee_multiplier_cap_pct, } From 3a559c05a4753b87b6f72abe80a29ae2014f3319 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 14:15:00 -0500 Subject: [PATCH 11/37] fixed update and parse functions to take in a vector of vector of u8s to support multiple updates --- .../pyth-receiver/src/integration_tests.rs | 86 +++++++++---------- .../stylus/contracts/pyth-receiver/src/lib.rs | 52 +++++++---- .../contracts/pyth-receiver/src/structs.rs | 8 +- .../contracts/pyth-receiver/src/test_data.rs | 40 ++++----- 4 files changed, 96 insertions(+), 90 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index 9cffae0bb7..19e959facb 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -1,8 +1,8 @@ #[cfg(test)] mod test { - use crate::{error::PythReceiverError}; - use crate::test_data::{self, good_update2_results, multiple_updates_results}; + use crate::error::PythReceiverError; use crate::test_data::good_update1_results; + use crate::test_data::{self, good_update2_results, multiple_updates_results}; use crate::PythReceiver; use alloy_primitives::{Address, U256}; use motsu::prelude::*; @@ -13,7 +13,7 @@ mod test { 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, 0x5b, 0x43, ]; - + const PYTHNET_CHAIN_ID: u16 = 26; const PYTHNET_EMITTER_ADDRESS: [u8; 32] = [ 0xe1, 0x01, 0xfa, 0xed, 0xac, 0x58, 0x51, 0xe3, 0x2b, 0x9b, 0x23, 0xb5, 0xf9, 0x41, 0x1a, @@ -26,19 +26,29 @@ mod test { const GOVERNANCE_CONTRACT: U256 = U256::from_limbs([4, 0, 0, 0]); const SINGLE_UPDATE_FEE_IN_WEI: U256 = U256::from_limbs([100, 0, 0, 0]); + const TRANSACTION_FEE_IN_WEI: U256 = U256::from_limbs([32, 0, 0, 0]); #[cfg(test)] - fn mock_get_update_fee(update_data: Vec) -> Result { - let update_data_array: &[u8] = &update_data; - let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) - .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; - match accumulator_update.proof { - Proof::WormholeMerkle { vaa: _, updates } => { - let num_updates = - u8::try_from(updates.len()).map_err(|_| PythReceiverError::TooManyUpdates)?; - Ok(U256::from(num_updates).saturating_mul(SINGLE_UPDATE_FEE_IN_WEI)) + fn mock_get_update_fee(update_data: Vec>) -> Result { + let mut total_num_updates: u64 = 0; + for data in &update_data { + let update_data_array: &[u8] = &data; + let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) + .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; + match accumulator_update.proof { + Proof::WormholeMerkle { vaa: _, updates } => { + let num_updates = u64::try_from(updates.len()) + .map_err(|_| PythReceiverError::TooManyUpdates)?; + total_num_updates += num_updates; + } } } + Ok(get_total_fee(total_num_updates)) + } + + fn get_total_fee(total_num_updates: u64) -> U256 { + U256::from(total_num_updates).saturating_mul(SINGLE_UPDATE_FEE_IN_WEI) + + TRANSACTION_FEE_IN_WEI } #[cfg(test)] @@ -93,11 +103,11 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - alice.fund(U256::from(200)); - let update_data = test_data::good_update1(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + alice.fund(update_fee); + let result = pyth_contract .sender_and_value(alice, update_fee) .update_price_feeds(update_data); @@ -105,10 +115,7 @@ mod test { let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); assert!(price_result.is_ok()); - assert_eq!( - price_result.unwrap(), - good_update1_results() - ); + assert_eq!(price_result.unwrap(), good_update1_results()); } #[motsu::test] @@ -140,18 +147,19 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - alice.fund(U256::from(200)); - let update_data1 = test_data::good_update1(); let update_fee1 = mock_get_update_fee(update_data1.clone()).unwrap(); + + let update_data2 = test_data::good_update2(); + let update_fee2 = mock_get_update_fee(update_data2.clone()).unwrap(); + + alice.fund(update_fee1 + update_fee2); + let result1 = pyth_contract .sender_and_value(alice, update_fee1) .update_price_feeds(update_data1); assert!(result1.is_ok()); - let update_data2 = test_data::good_update2(); - let update_fee2 = mock_get_update_fee(update_data2.clone()).unwrap(); - let result2 = pyth_contract .sender_and_value(alice, update_fee2) .update_price_feeds(update_data2); @@ -159,10 +167,7 @@ mod test { let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); assert!(price_result.is_ok()); - assert_eq!( - price_result.unwrap(), - good_update2_results() - ); + assert_eq!(price_result.unwrap(), good_update2_results()); } #[motsu::test] @@ -213,11 +218,11 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - alice.fund(U256::from(200)); - let update_data = test_data::good_update2(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + alice.fund(update_fee); + let result = pyth_contract .sender_and_value(alice, update_fee) .update_price_feeds(update_data); @@ -227,10 +232,7 @@ mod test { .sender(alice) .get_price_no_older_than(TEST_PRICE_ID, u64::MAX); assert!(price_result.is_ok()); - assert_eq!( - price_result.unwrap(), - good_update2_results() - ); + assert_eq!(price_result.unwrap(), good_update2_results()); } #[motsu::test] @@ -241,11 +243,11 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - alice.fund(U256::from(200)); - let update_data = test_data::good_update2(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + alice.fund(update_fee); + let result = pyth_contract .sender_and_value(alice, update_fee) .update_price_feeds(update_data); @@ -269,11 +271,11 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - alice.fund(U256::from(200)); - let update_data = test_data::multiple_updates(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + alice.fund(update_fee); + let result = pyth_contract .sender_and_value(alice, update_fee) .update_price_feeds(update_data); @@ -292,16 +294,10 @@ mod test { let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); assert!(first_price_result.is_ok()); - assert_eq!( - first_price_result.unwrap(), - multiple_updates_results()[0] - ); + assert_eq!(first_price_result.unwrap(), multiple_updates_results()[0]); let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); assert!(second_price_result.is_ok()); - assert_eq!( - second_price_result.unwrap(), - multiple_updates_results()[1] - ); + assert_eq!(second_price_result.unwrap(), multiple_updates_results()[1]); } } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 3626e0aaaa..3ec35c650d 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -171,8 +171,21 @@ impl PythReceiver { } #[payable] - pub fn update_price_feeds(&mut self, update_data: Vec) -> Result<(), PythReceiverError> { - self.update_price_feeds_internal(update_data)?; + pub fn update_price_feeds( + &mut self, + update_data: Vec>, + ) -> Result<(), PythReceiverError> { + for data in &update_data { + self.update_price_feeds_internal(data.clone())?; + } + + let total_fee = self.get_update_fee(update_data)?; + + let value = self.vm().msg_value(); + + if value < total_fee { + return Err(PythReceiverError::InsufficientFee); + } Ok(()) } @@ -234,14 +247,6 @@ impl PythReceiver { let root_digest: MerkleRoot = parse_wormhole_proof(vaa)?; - let total_fee = self.get_update_fee(update_data)?; - - let value = self.vm().msg_value(); - - if value < total_fee { - return Err(PythReceiverError::InsufficientFee); - } - for update in updates { let message_vec = Vec::from(update.message); let proof: MerklePath = update.proof; @@ -294,17 +299,26 @@ impl PythReceiver { Ok(()) } - fn get_update_fee(&self, update_data: Vec) -> Result { - let update_data_array: &[u8] = &update_data; - let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) - .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; - match accumulator_update.proof { - Proof::WormholeMerkle { vaa: _, updates } => { - let num_updates = - u8::try_from(updates.len()).map_err(|_| PythReceiverError::TooManyUpdates)?; - Ok(U256::from(num_updates).saturating_mul(self.single_update_fee_in_wei.get())) + fn get_update_fee(&self, update_data: Vec>) -> Result { + let mut total_num_updates: u64 = 0; + for data in &update_data { + let update_data_array: &[u8] = &data; + let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) + .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; + match accumulator_update.proof { + Proof::WormholeMerkle { vaa: _, updates } => { + let num_updates = u64::try_from(updates.len()) + .map_err(|_| PythReceiverError::TooManyUpdates)?; + total_num_updates += num_updates; + } } } + Ok(self.get_total_fee(total_num_updates)) + } + + fn get_total_fee(&self, total_num_updates: u64) -> U256 { + U256::from(total_num_updates).saturating_mul(self.single_update_fee_in_wei.get()) + + self.transaction_fee_in_wei.get() } pub fn get_twap_update_fee(&self, _update_data: Vec>) -> U256 { diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index 504619ae4c..13e419ca15 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -1,21 +1,17 @@ -use alloc::{vec::Vec}; +use alloc::vec::Vec; use stylus_sdk::alloy_primitives::{keccak256, FixedBytes, B256, I32, I64, U16, U256, U64}; use stylus_sdk::{ prelude::*, storage::{StorageFixedBytes, StorageI32, StorageI64, StorageKey, StorageU16, StorageU64}, }; -fn serialize_data_source_to_bytes( - chain_id: u16, - emitter_address: &[u8; 32], -) -> [u8; 34] { +fn serialize_data_source_to_bytes(chain_id: u16, emitter_address: &[u8; 32]) -> [u8; 34] { let mut result = [0u8; 34]; result[0..2].copy_from_slice(&chain_id.to_be_bytes()); result[2..].copy_from_slice(emitter_address); result } - #[derive(Debug)] #[storage] pub struct DataSourceStorage { diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index c9c7708ee6..aa8cecc655 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -1,10 +1,22 @@ +use alloy_primitives::{address, Address, I32, I64, U64}; use hex::FromHex; -use alloy_primitives::{I32, I64, U64, address, Address}; -pub fn good_update1() -> Vec { +pub fn good_update1() -> Vec> { let hex_str = "504e41550100000003b801000000040d0216f3809b6396fdb0708bc94515ddb96a3bc8fb1993916e74f522ab4c34a268836a0bb38786303b55cc40ecb50d18c92bf9fd61688a143f3d24a73a3f468e4ab0000365be4f3a330fe96bab246922d9fa4816a865dec40d7c522e37f7e3605673b8f52eb30a4dad7aee3e1ac4b21337b8831c6147ef6e359e843b307872e83b5c7fe80004b6f12766eb395d04bdb146b1b69a4b9a8831a9c9ea20ee841d55efe72c629aef17038b925584493af7882981260b09587d73b6463415f4e1f8fb0a2a2c959c8301065c18a776cf558e43494bf31dd26ac0e3aed07a01ca0063abcfca3736b892368e4bf8b957f2d2e83ff9eb3fa8f667ef97a5f48c286103f70fda4a72e077719a0200085896c930febfb864a8c292bde679092e386e53389cb2d26bd12ceed64d22c0cd27676dc5c46d3e3c5949abbce89d4cc5ec378cfbf9ab1b0c24cfeb3371df4864000a1c67e67adea9f8a71eee76765f1fe4c7d9539d21b62d0336513692897cc6acbe1c827599a1f8c73b7478e8d788443b4cfd4e373344ab1ac9f771c9c494124863000b7e08e1cc1291340e6ec7d1b04c6a9c63f74077a074ee68edf4f95cf24c9743620f8abc8213884c8e4848fb76ec319d365e0cdc746a6e534a4cb828e51719be79010c61a2fedeedf34e4c80624d80cb93e24bafe9f6d23339173004d84e61c2395bfb5f552e87b901beb192ba6f4fc905eae30317c52b8614071e08a265592f24e8de000de38d08612edc5ebc863ea5a96325cb991750a94ff0e50f86bc322cce81db7cd17cb4ed3703f7477eb111a9d881e1345f5b79d618814f31b46034196563ee6a18000e5b11510d585a2decaba0be9e71386865b6069061478f7254d7852d55116569eb426c385267e40264d16db08e0f1a9e2c44b7d1c926d3c3ba662b212572386483010fa1ad0278402104b146b6f4d1cf85ac4df961d24eea0c7948b700f9973596cad130abfff25feef37125ba38507f34308d967455651e7014e40264a0c6510d3af001101f2e4e70868d6c327c92229537f1ffa33e488da3140ccc086f8210b437b162190742bd01ad1cb495a93f20045b4bb47e1562fdd82a8a1548d87d788a492b17190111847f5095df3edefc58be0956aee19876e850516e132506fd67504afee3c8cf240f07fb607b3c8282a2e56dcad23959e6759bdf8ad345ba8150448da56ff34fcc016866baf800000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000880cea9014155575600000000000d8e5c8d0000271085e6ab1bb044f57c4cd6c1d32aa0a82a5032198301005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009f9828e420300000001284f2da6fffffff8000000006866baf8000000006866baf8000009f3b955a26000000001062cb8780c1283a49180b4986f0dec3c746d3daeb597887747b8f66a09876e1253a1ebb8a6bc4a5793daaa343db6057b82ec29715d7e1db0ffd6db523f7b80b03e3866ef5f5c80728801b92f5acecc28d8517e5615335d89c553f94b4370f3a20be0bc23e0fd401c4e5bd8bd32948a26233fc48f116428a490f087030ccfc442753e3074e2b9bbc1c61a009d86aaa200645c627a6b7f2f6597e34c60b14a58ef2583bcbb1d0e21b71a264fad2648ecc545031c7ed598772ffe875bf94a488389a49e6025e1b2a1f07ec598d0d9aa8ef7dd2733c8502c49d1d1323f1ae664e82e8a5e14978d52ab448ba9b1afc78f06c8cd17415a17"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); - bytes + vec![bytes] +} + +pub fn good_update2() -> Vec> { + let hex_str = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + vec![bytes] +} + +pub fn multiple_updates() -> Vec> { + let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + vec![bytes] } pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { @@ -14,7 +26,7 @@ pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { I64::from_le_bytes(10967241867779i64.to_le_bytes()), U64::from(4971244966u64), I64::from_le_bytes(10942391100000i64.to_le_bytes()), - U64::from(4398561400u64) + U64::from(4398561400u64), ) } @@ -26,7 +38,7 @@ pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { I64::from_le_bytes(10990356724259i64.to_le_bytes()), U64::from(3891724259u64), I64::from_le_bytes(10974970400000i64.to_le_bytes()), - U64::from(3918344000u64) + U64::from(3918344000u64), ), ( U64::from(1751573123u64), @@ -34,8 +46,8 @@ pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { I64::from_le_bytes(258906787480i64.to_le_bytes()), U64::from(158498649u64), I64::from_le_bytes(258597182000i64.to_le_bytes()), - U64::from(131285914u64) - ) + U64::from(131285914u64), + ), ] } @@ -46,22 +58,10 @@ pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { I64::from_le_bytes(10985663592646i64.to_le_bytes()), U64::from(4569386330u64), I64::from_le_bytes(10977795800000i64.to_le_bytes()), - U64::from(3919318300u64) + U64::from(3919318300u64), ) } -pub fn good_update2() -> Vec { - let hex_str = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; - let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); - bytes -} - -pub fn multiple_updates() -> Vec { - let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; - let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); - bytes -} - pub fn current_guardians() -> Vec
{ vec![ address!("0x5893B5A76c3f739645648885bDCcC06cd70a3Cd3"), // Rockaway From 18c4a44a016603a81b10574da44a4575a1fd8459 Mon Sep 17 00:00:00 2001 From: Tejas Badadare <17058023+tejasbadadare@users.noreply.github.com> Date: Wed, 9 Jul 2025 13:56:50 -0700 Subject: [PATCH 12/37] feat(fortuna): Support Postgres DB backend (#2841) * use AnyPool for generic SQL cxn, add postgres migrations * migrations * fix sqlite issues * fix sqlite issues * remove old migrations, update migrations!() * bump ver --- Cargo.lock | 20 +- apps/fortuna/.gitignore | 1 + ...0b88336dd2aab632411114f02ce8dd8fe07e8.json | 12 - ...9fa50da70066f30b74f354e5d3a843ba6a2c0.json | 12 - ...b5d72360326593407518770fe537ac3da1e10.json | 12 - apps/fortuna/Cargo.toml | 12 +- apps/fortuna/README.md | 35 +-- .../migrations/20250502164500_init.down.sql | 1 - .../migrations/20250502164500_init.up.sql | 26 -- .../migrations/20250521203448_gas.down.sql | 4 - .../migrations/20250521203448_gas.up.sql | 5 - ...7_add_indices_for_advanced_search.down.sql | 22 -- ...0605165549_re-add_tx_hash_indices.down.sql | 4 - ...250605165549_re-add_tx_hash_indices.up.sql | 4 - .../migrations/20250707000000_init.down.sql | 1 + ...arch.up.sql => 20250707000000_init.up.sql} | 32 ++- apps/fortuna/src/api.rs | 10 + apps/fortuna/src/api/explorer.rs | 2 +- apps/fortuna/src/command/run.rs | 2 + apps/fortuna/src/history.rs | 230 +++++++++++++----- 20 files changed, 256 insertions(+), 191 deletions(-) delete mode 100644 apps/fortuna/.sqlx/query-03901bcfb28b127d99fe8a53e480b88336dd2aab632411114f02ce8dd8fe07e8.json delete mode 100644 apps/fortuna/.sqlx/query-4c8c05ec08e128d847faafdd3d79fa50da70066f30b74f354e5d3a843ba6a2c0.json delete mode 100644 apps/fortuna/.sqlx/query-b0d9afebb3825c3509ad80e5ebab5d72360326593407518770fe537ac3da1e10.json delete mode 100644 apps/fortuna/migrations/20250502164500_init.down.sql delete mode 100644 apps/fortuna/migrations/20250502164500_init.up.sql delete mode 100644 apps/fortuna/migrations/20250521203448_gas.down.sql delete mode 100644 apps/fortuna/migrations/20250521203448_gas.up.sql delete mode 100644 apps/fortuna/migrations/20250605004757_add_indices_for_advanced_search.down.sql delete mode 100644 apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.down.sql delete mode 100644 apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.up.sql create mode 100644 apps/fortuna/migrations/20250707000000_init.down.sql rename apps/fortuna/migrations/{20250605004757_add_indices_for_advanced_search.up.sql => 20250707000000_init.up.sql} (54%) diff --git a/Cargo.lock b/Cargo.lock index 29e38ea49b..e5d3dc7ee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2300,6 +2300,12 @@ dependencies = [ "const-random", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dotenvy" version = "0.15.7" @@ -3052,7 +3058,7 @@ dependencies = [ [[package]] name = "fortuna" -version = "8.1.0" +version = "8.2.0" dependencies = [ "anyhow", "axum 0.6.20", @@ -3064,6 +3070,7 @@ dependencies = [ "byteorder", "chrono", "clap", + "dotenv", "ethabi", "ethers", "futures", @@ -9361,6 +9368,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", + "rustls 0.23.28", "serde", "serde_json", "sha2 0.10.9", @@ -9370,6 +9378,7 @@ dependencies = [ "tokio-stream", "tracing", "url", + "webpki-roots 0.26.11", ] [[package]] @@ -10702,6 +10711,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.1", +] + [[package]] name = "webpki-roots" version = "1.0.1" diff --git a/apps/fortuna/.gitignore b/apps/fortuna/.gitignore index b978440e2e..707493b53c 100644 --- a/apps/fortuna/.gitignore +++ b/apps/fortuna/.gitignore @@ -4,3 +4,4 @@ *private-key* .envrc fortuna.db* +.env* diff --git a/apps/fortuna/.sqlx/query-03901bcfb28b127d99fe8a53e480b88336dd2aab632411114f02ce8dd8fe07e8.json b/apps/fortuna/.sqlx/query-03901bcfb28b127d99fe8a53e480b88336dd2aab632411114f02ce8dd8fe07e8.json deleted file mode 100644 index 28642818e3..0000000000 --- a/apps/fortuna/.sqlx/query-03901bcfb28b127d99fe8a53e480b88336dd2aab632411114f02ce8dd8fe07e8.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE request SET state = ?, last_updated_at = ?, info = ?, provider_random_number = ? WHERE network_id = ? AND sequence = ? AND provider = ? AND request_tx_hash = ? AND state = 'Pending'", - "describe": { - "columns": [], - "parameters": { - "Right": 8 - }, - "nullable": [] - }, - "hash": "03901bcfb28b127d99fe8a53e480b88336dd2aab632411114f02ce8dd8fe07e8" -} diff --git a/apps/fortuna/.sqlx/query-4c8c05ec08e128d847faafdd3d79fa50da70066f30b74f354e5d3a843ba6a2c0.json b/apps/fortuna/.sqlx/query-4c8c05ec08e128d847faafdd3d79fa50da70066f30b74f354e5d3a843ba6a2c0.json deleted file mode 100644 index 2fc58d302c..0000000000 --- a/apps/fortuna/.sqlx/query-4c8c05ec08e128d847faafdd3d79fa50da70066f30b74f354e5d3a843ba6a2c0.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "UPDATE request SET state = ?, last_updated_at = ?, reveal_block_number = ?, reveal_tx_hash = ?, provider_random_number =?, gas_used = ? WHERE network_id = ? AND sequence = ? AND provider = ? AND request_tx_hash = ?", - "describe": { - "columns": [], - "parameters": { - "Right": 10 - }, - "nullable": [] - }, - "hash": "4c8c05ec08e128d847faafdd3d79fa50da70066f30b74f354e5d3a843ba6a2c0" -} diff --git a/apps/fortuna/.sqlx/query-b0d9afebb3825c3509ad80e5ebab5d72360326593407518770fe537ac3da1e10.json b/apps/fortuna/.sqlx/query-b0d9afebb3825c3509ad80e5ebab5d72360326593407518770fe537ac3da1e10.json deleted file mode 100644 index 2888b76e36..0000000000 --- a/apps/fortuna/.sqlx/query-b0d9afebb3825c3509ad80e5ebab5d72360326593407518770fe537ac3da1e10.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT INTO request(chain_id, network_id, provider, sequence, created_at, last_updated_at, state, request_block_number, request_tx_hash, user_random_number, sender, gas_limit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 12 - }, - "nullable": [] - }, - "hash": "b0d9afebb3825c3509ad80e5ebab5d72360326593407518770fe537ac3da1e10" -} diff --git a/apps/fortuna/Cargo.toml b/apps/fortuna/Cargo.toml index 80d5895dc6..0c0b66fb65 100644 --- a/apps/fortuna/Cargo.toml +++ b/apps/fortuna/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fortuna" -version = "8.1.0" +version = "8.2.0" edition = "2021" [lib] @@ -46,8 +46,16 @@ chrono = { version = "0.4.38", features = [ backoff = { version = "0.4.0", features = ["futures", "tokio"] } thiserror = "1.0.61" futures-locks = "0.7.1" -sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "chrono"] } +sqlx = { version = "0.8", features = [ + "runtime-tokio", + "tls-rustls", + "sqlite", + "any", + "postgres", + "chrono", +] } num-traits = "0.2.19" +dotenv = "0.15.0" [dev-dependencies] axum-test = "13.1.1" diff --git a/apps/fortuna/README.md b/apps/fortuna/README.md index 274f3eaf31..6b7be72185 100644 --- a/apps/fortuna/README.md +++ b/apps/fortuna/README.md @@ -10,35 +10,38 @@ Each blockchain is configured in `config.yaml`. ## Build & Test -We use sqlx query macros to check the SQL queries at compile time. This requires -a database to be available at build time. Create a `.env` file in the root of the project with the following content: +Fortuna uses Cargo for building and dependency management. +Simply run `cargo build` and `cargo test` to build and test the project. +To run Fortuna locally, see the [Local Development](#local-development) section below. +### Connect a database +Fortuna stores request history in a SQL database and serves it from its explorer API. +Any SQLite or Postgres database is supported. The database connection is sourced from the `DATABASE_URL` env var. +Create a `.env` file in the root of the project with a DB connection string. ``` DATABASE_URL="sqlite:fortuna.db?mode=rwc" ``` +If not provided, Fortuna will create and use a SQLite file-based database at `./fortuna.db`, as in the example above. + +### Database migrations +Fortuna will automatically apply the schema migrations in the `./migrations` directory when connecting to the database. +To manually administer the migrations, use the `sqlx` tool for cargo. The tool automatically uses the +database connection in the `.env` file. -Install sqlx for cargo with: +Install `sqlx`: ```bash cargo install sqlx ``` -Next, you need to create the database and apply the schema migrations. You can do this by running: - +To create the database if needed and apply the migrations: ```bash -cargo sqlx migrate run # automatically picks up the .env file +cargo sqlx migrate run ``` -This will create a SQLite database file called `fortuna.db` in the root of the project and apply the schema migrations to it. -This will allow `cargo check` to check the queries against the existing database. - -Fortuna uses Cargo for building and dependency management. -Simply run `cargo build` and `cargo test` to build and test the project. - -If you have changed any queries in the code, you need to update the .sqlx folder with the new queries: +To restore the database to a fresh state (drop, recreate, apply migrations): ```bash -cargo sqlx prepare +cargo sqlx database reset ``` -Please add the changed files in the `.sqlx` folder to your git commit. ## Command-Line Interface @@ -124,7 +127,7 @@ To start an instance of the webserver for local testing, you first need to perfo 1. Run `cargo run -- setup-provider` to register a randomness provider for this service. This command will update the on-chain contracts such that the configured provider key is a randomness provider, and its on-chain configuration matches `config.yaml`. - +1. Review the [Connect a database](#connect-a-database) section above. The default configuration will create a file-based DB. Once you've completed the setup, simply run the following command to start the service: ```bash diff --git a/apps/fortuna/migrations/20250502164500_init.down.sql b/apps/fortuna/migrations/20250502164500_init.down.sql deleted file mode 100644 index 4c2732aa68..0000000000 --- a/apps/fortuna/migrations/20250502164500_init.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE request; diff --git a/apps/fortuna/migrations/20250502164500_init.up.sql b/apps/fortuna/migrations/20250502164500_init.up.sql deleted file mode 100644 index 38bf13ea99..0000000000 --- a/apps/fortuna/migrations/20250502164500_init.up.sql +++ /dev/null @@ -1,26 +0,0 @@ --- we use VARCHAR(40) for addresses and VARCHAR(64) for tx_hashes and 32 byte numbers -CREATE TABLE request( - chain_id VARCHAR(20) NOT NULL, - network_id INTEGER NOT NULL, - provider VARCHAR(40) NOT NULL, - sequence INTEGER NOT NULL, - created_at DATETIME NOT NULL, - last_updated_at DATETIME NOT NULL, - state VARCHAR(10) NOT NULL, - request_block_number INT NOT NULL, - request_tx_hash VARCHAR(64) NOT NULL, - user_random_number VARCHAR(64) NOT NULL, - sender VARCHAR(40) NOT NULL, - reveal_block_number INT, - reveal_tx_hash VARCHAR(64), - provider_random_number VARCHAR(64), - info TEXT, - PRIMARY KEY (network_id, sequence, provider, request_tx_hash) -); - -CREATE INDEX idx_request_sequence ON request (sequence); -CREATE INDEX idx_request_network_id_created_at ON request (network_id, created_at); -CREATE INDEX idx_request_created_at ON request (created_at); -CREATE INDEX idx_request_request_tx_hash ON request (request_tx_hash) WHERE request_tx_hash IS NOT NULL; -CREATE INDEX idx_request_reveal_tx_hash ON request (reveal_tx_hash) WHERE reveal_tx_hash IS NOT NULL; -CREATE INDEX idx_request_sender ON request (sender) WHERE sender IS NOT NULL; diff --git a/apps/fortuna/migrations/20250521203448_gas.down.sql b/apps/fortuna/migrations/20250521203448_gas.down.sql deleted file mode 100644 index a031cd4767..0000000000 --- a/apps/fortuna/migrations/20250521203448_gas.down.sql +++ /dev/null @@ -1,4 +0,0 @@ -ALTER TABLE request -DROP COLUMN gas_used; -ALTER TABLE request -DROP COLUMN gas_limit; diff --git a/apps/fortuna/migrations/20250521203448_gas.up.sql b/apps/fortuna/migrations/20250521203448_gas.up.sql deleted file mode 100644 index 6604e8742f..0000000000 --- a/apps/fortuna/migrations/20250521203448_gas.up.sql +++ /dev/null @@ -1,5 +0,0 @@ --- U256 max value is 78 digits, so 100 is a safe upper bound -ALTER TABLE request -ADD COLUMN gas_used VARCHAR(100); -ALTER TABLE request -ADD COLUMN gas_limit VARCHAR(100) NOT NULL; diff --git a/apps/fortuna/migrations/20250605004757_add_indices_for_advanced_search.down.sql b/apps/fortuna/migrations/20250605004757_add_indices_for_advanced_search.down.sql deleted file mode 100644 index d7287b5c61..0000000000 --- a/apps/fortuna/migrations/20250605004757_add_indices_for_advanced_search.down.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Add down migration script here - -DROP INDEX request__network_id__state__created_at; -DROP INDEX request__network_id__created_at; -DROP INDEX request__sender__network_id__state__created_at; -DROP INDEX request__sender__network_id__created_at; -DROP INDEX request__sender__state__created_at; -DROP INDEX request__sender__created_at; -DROP INDEX request__sequence__network_id__state__created_at; -DROP INDEX request__sequence__network_id__created_at; -DROP INDEX request__sequence__state__created_at; -DROP INDEX request__sequence__created_at; -DROP INDEX request__state__created_at; -DROP INDEX request__created_at; - - -CREATE INDEX idx_request_sequence ON request (sequence); -CREATE INDEX idx_request_network_id_created_at ON request (network_id, created_at); -CREATE INDEX idx_request_created_at ON request (created_at); -CREATE INDEX idx_request_request_tx_hash ON request (request_tx_hash) WHERE request_tx_hash IS NOT NULL; -CREATE INDEX idx_request_reveal_tx_hash ON request (reveal_tx_hash) WHERE reveal_tx_hash IS NOT NULL; -CREATE INDEX idx_request_sender ON request (sender) WHERE sender IS NOT NULL; diff --git a/apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.down.sql b/apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.down.sql deleted file mode 100644 index a57615dc2c..0000000000 --- a/apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.down.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Add down migration script here - -DROP INDEX request__request_tx_hash; -DROP INDEX request__reveal_tx_hash; diff --git a/apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.up.sql b/apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.up.sql deleted file mode 100644 index 619dbdbdd2..0000000000 --- a/apps/fortuna/migrations/20250605165549_re-add_tx_hash_indices.up.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Add up migration script here - -CREATE INDEX request__request_tx_hash ON request (request_tx_hash) WHERE request_tx_hash IS NOT NULL; -CREATE INDEX request__reveal_tx_hash ON request (reveal_tx_hash) WHERE reveal_tx_hash IS NOT NULL; diff --git a/apps/fortuna/migrations/20250707000000_init.down.sql b/apps/fortuna/migrations/20250707000000_init.down.sql new file mode 100644 index 0000000000..b02621d63a --- /dev/null +++ b/apps/fortuna/migrations/20250707000000_init.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS request; diff --git a/apps/fortuna/migrations/20250605004757_add_indices_for_advanced_search.up.sql b/apps/fortuna/migrations/20250707000000_init.up.sql similarity index 54% rename from apps/fortuna/migrations/20250605004757_add_indices_for_advanced_search.up.sql rename to apps/fortuna/migrations/20250707000000_init.up.sql index 7b087384b4..609351b70f 100644 --- a/apps/fortuna/migrations/20250605004757_add_indices_for_advanced_search.up.sql +++ b/apps/fortuna/migrations/20250707000000_init.up.sql @@ -1,12 +1,23 @@ --- Add up migration script here - -DROP INDEX idx_request_sequence; -DROP INDEX idx_request_network_id_created_at; -DROP INDEX idx_request_created_at; -DROP INDEX idx_request_request_tx_hash; -DROP INDEX idx_request_reveal_tx_hash; -DROP INDEX idx_request_sender; - +CREATE TABLE request( + chain_id VARCHAR(20) NOT NULL, + network_id INTEGER NOT NULL, + provider VARCHAR(40) NOT NULL, + sequence INTEGER NOT NULL, + created_at INTEGER NOT NULL, + last_updated_at INTEGER NOT NULL, + state VARCHAR(10) NOT NULL, + request_block_number INTEGER NOT NULL, + request_tx_hash VARCHAR(64) NOT NULL, + user_random_number VARCHAR(64) NOT NULL, + sender VARCHAR(40) NOT NULL, + reveal_block_number INTEGER, + reveal_tx_hash VARCHAR(64), + provider_random_number VARCHAR(64), + info TEXT, + gas_used VARCHAR(100), + gas_limit VARCHAR(100) NOT NULL, + PRIMARY KEY (network_id, sequence, provider, request_tx_hash) +); CREATE INDEX request__network_id__state__created_at ON request(network_id, state, created_at); CREATE INDEX request__network_id__created_at ON request(network_id, created_at); @@ -20,3 +31,6 @@ CREATE INDEX request__sequence__state__created_at ON request(sequence, state, cr CREATE INDEX request__sequence__created_at ON request(sequence, created_at); CREATE INDEX request__state__created_at ON request(state, created_at); CREATE INDEX request__created_at ON request(created_at); + +CREATE INDEX request__request_tx_hash ON request (request_tx_hash) WHERE request_tx_hash IS NOT NULL; +CREATE INDEX request__reveal_tx_hash ON request (reveal_tx_hash) WHERE reveal_tx_hash IS NOT NULL; diff --git a/apps/fortuna/src/api.rs b/apps/fortuna/src/api.rs index a14e7e49fa..5f7ab2cf24 100644 --- a/apps/fortuna/src/api.rs +++ b/apps/fortuna/src/api.rs @@ -42,6 +42,16 @@ pub enum StateTag { Failed, } +impl std::fmt::Display for StateTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + StateTag::Pending => write!(f, "Pending"), + StateTag::Completed => write!(f, "Completed"), + StateTag::Failed => write!(f, "Failed"), + } + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct RequestLabel { pub value: String, diff --git a/apps/fortuna/src/api/explorer.rs b/apps/fortuna/src/api/explorer.rs index 48bc59d7a2..09593cfeb3 100644 --- a/apps/fortuna/src/api/explorer.rs +++ b/apps/fortuna/src/api/explorer.rs @@ -110,7 +110,7 @@ pub struct ExplorerQueryParams { #[derive(Debug, serde::Serialize, utoipa::ToSchema)] pub struct ExplorerResponse { pub requests: Vec, - pub total_results: u64, + pub total_results: i64, } /// Returns the logs of all requests captured by the keeper. diff --git a/apps/fortuna/src/command/run.rs b/apps/fortuna/src/command/run.rs index af588192cc..a06909de78 100644 --- a/apps/fortuna/src/command/run.rs +++ b/apps/fortuna/src/command/run.rs @@ -84,6 +84,8 @@ pub async fn run_api( } pub async fn run(opts: &RunOptions) -> Result<()> { + // Load environment variables from a .env file if present + let _ = dotenv::dotenv()?; let config = Config::load(&opts.config.config)?; let secret = config.provider.secret.load()?.ok_or(anyhow!( "Please specify a provider secret in the config file." diff --git a/apps/fortuna/src/history.rs b/apps/fortuna/src/history.rs index 245e5dea18..cff9b5d40f 100644 --- a/apps/fortuna/src/history.rs +++ b/apps/fortuna/src/history.rs @@ -1,7 +1,7 @@ use { crate::api::{ChainId, NetworkId, StateTag}, anyhow::Result, - chrono::{DateTime, NaiveDateTime}, + chrono::DateTime, ethers::{ core::utils::hex::ToHex, prelude::TxHash, @@ -10,13 +10,16 @@ use { }, serde::Serialize, serde_with::serde_as, - sqlx::{migrate, FromRow, Pool, QueryBuilder, Sqlite, SqlitePool}, + sqlx::{any::AnyPoolOptions, migrate, AnyPool, FromRow}, std::{str::FromStr, sync::Arc}, tokio::{spawn, sync::mpsc}, utoipa::ToSchema, }; const LOG_RETURN_LIMIT: u64 = 1000; +const ONE_DAY: u64 = 60 * 60 * 24; +const ONE_HOUR: u64 = 60 * 60; +const DEFAULT_DATABASE_URL: &str = "sqlite:fortuna.db?mode=rwc"; #[serde_as] #[derive(Clone, Debug, Serialize, ToSchema, PartialEq)] @@ -105,8 +108,8 @@ struct RequestRow { network_id: i64, provider: String, sequence: i64, - created_at: NaiveDateTime, - last_updated_at: NaiveDateTime, + created_at: i64, // Unix timestamp + last_updated_at: i64, // Unix timestamp state: String, request_block_number: i64, request_tx_hash: String, @@ -128,8 +131,10 @@ impl TryFrom for RequestStatus { let network_id = row.network_id as u64; let provider = row.provider.parse()?; let sequence = row.sequence as u64; - let created_at = row.created_at.and_utc(); - let last_updated_at = row.last_updated_at.and_utc(); + let created_at = DateTime::from_timestamp(row.created_at, 0) + .ok_or(anyhow::anyhow!("Invalid created_at timestamp"))?; + let last_updated_at = DateTime::from_timestamp(row.last_updated_at, 0) + .ok_or(anyhow::anyhow!("Invalid last_updated_at timestamp"))?; let request_block_number = row.request_block_number as u64; let user_random_number = hex::FromHex::from_hex(row.user_random_number)?; let request_tx_hash = row.request_tx_hash.parse()?; @@ -211,7 +216,7 @@ impl From for Option { } pub struct History { - pool: Pool, + pool: AnyPool, write_queue: mpsc::Sender, _writer_thread: Arc>, } @@ -219,20 +224,46 @@ pub struct History { impl History { const MAX_WRITE_QUEUE: usize = 1_000; pub async fn new() -> Result { - Self::new_with_url("sqlite:fortuna.db?mode=rwc").await + let database_url = + std::env::var("DATABASE_URL").unwrap_or_else(|_| DEFAULT_DATABASE_URL.to_string()); + Self::new_with_url(&database_url).await } + /// Create a History instance with an ephemeral in-memory DB. + /// Useful for testing. pub async fn new_in_memory() -> Result { - Self::new_with_url("sqlite::memory:").await + sqlx::any::install_default_drivers(); + // Connect to an in-memory SQLite database + // Don't let the pool drop the cxn, otherwise the database will be deleted + let pool = AnyPoolOptions::new() + .min_connections(1) + .max_connections(1) + .idle_timeout(None) + .max_lifetime(None) + .connect("sqlite::memory:") + .await?; + let migrator = migrate!(); // defaults to "./migrations" + migrator.run(&pool).await?; + Self::new_with_pool(pool).await } + /// Create a History instance with production DB parameters pub async fn new_with_url(url: &str) -> Result { - let pool = SqlitePool::connect(url).await?; + sqlx::any::install_default_drivers(); + let pool = AnyPoolOptions::new() + .min_connections(0) + .max_connections(10) + // Allow the cloud DB to spin down after 1 hour of inactivity (cost savings) + .idle_timeout(std::time::Duration::from_secs(ONE_HOUR)) + // Retire the connection after 1 day to avoid memory leaks in the DB + .max_lifetime(std::time::Duration::from_secs(ONE_DAY)) + .connect(url) + .await?; let migrator = migrate!("./migrations"); migrator.run(&pool).await?; Self::new_with_pool(pool).await } - pub async fn new_with_pool(pool: Pool) -> Result { + pub async fn new_with_pool(pool: AnyPool) -> Result { let (sender, mut receiver) = mpsc::channel(Self::MAX_WRITE_QUEUE); let pool_write_connection = pool.clone(); let writer_thread = spawn(async move { @@ -247,7 +278,7 @@ impl History { }) } - async fn update_request_status(pool: &Pool, new_status: RequestStatus) { + async fn update_request_status(pool: &AnyPool, new_status: RequestStatus) { let sequence = new_status.sequence as i64; let chain_id = new_status.chain_id; let network_id = new_status.network_id as i64; @@ -259,13 +290,13 @@ impl History { let block_number = new_status.request_block_number as i64; let sender: String = new_status.sender.encode_hex(); let user_random_number: String = new_status.user_random_number.encode_hex(); - sqlx::query("INSERT INTO request(chain_id, network_id, provider, sequence, created_at, last_updated_at, state, request_block_number, request_tx_hash, user_random_number, sender, gas_limit) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO request(chain_id, network_id, provider, sequence, created_at, last_updated_at, state, request_block_number, request_tx_hash, user_random_number, sender, gas_limit) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)") .bind(chain_id.clone()) .bind(network_id) .bind(provider.clone()) .bind(sequence) - .bind(new_status.created_at) - .bind(new_status.last_updated_at) + .bind(new_status.created_at.timestamp()) + .bind(new_status.last_updated_at.timestamp()) .bind("Pending") .bind(block_number) .bind(request_tx_hash.clone()) @@ -286,9 +317,9 @@ impl History { let reveal_tx_hash: String = reveal_tx_hash.encode_hex(); let provider_random_number: String = provider_random_number.encode_hex(); let gas_used: String = gas_used.to_string(); - let result = sqlx::query("UPDATE request SET state = ?, last_updated_at = ?, reveal_block_number = ?, reveal_tx_hash = ?, provider_random_number =?, gas_used = ? WHERE network_id = ? AND sequence = ? AND provider = ? AND request_tx_hash = ?") + let result = sqlx::query("UPDATE request SET state = $1, last_updated_at = $2, reveal_block_number = $3, reveal_tx_hash = $4, provider_random_number = $5, gas_used = $6 WHERE network_id = $7 AND sequence = $8 AND provider = $9 AND request_tx_hash = $10") .bind("Completed") - .bind(new_status.last_updated_at) + .bind(new_status.last_updated_at.timestamp()) .bind(reveal_block_number) .bind(reveal_tx_hash) .bind(provider_random_number) @@ -312,9 +343,9 @@ impl History { } => { let provider_random_number: Option = provider_random_number .map(|provider_random_number| provider_random_number.encode_hex()); - sqlx::query("UPDATE request SET state = ?, last_updated_at = ?, info = ?, provider_random_number = ? WHERE network_id = ? AND sequence = ? AND provider = ? AND request_tx_hash = ? AND state = 'Pending'") + sqlx::query("UPDATE request SET state = $1, last_updated_at = $2, info = $3, provider_random_number = $4 WHERE network_id = $5 AND sequence = $6 AND provider = $7 AND request_tx_hash = $8 AND state = 'Pending'") .bind("Failed") - .bind(new_status.last_updated_at) + .bind(new_status.last_updated_at.timestamp()) .bind(reason) .bind(provider_random_number) .bind(network_id) @@ -343,7 +374,7 @@ impl History { #[derive(Debug, Clone)] pub struct RequestQueryBuilder<'a> { - pool: &'a Pool, + pool: &'a AnyPool, pub search: Option, pub network_id: Option, pub state: Option, @@ -354,7 +385,7 @@ pub struct RequestQueryBuilder<'a> { } impl<'a> RequestQueryBuilder<'a> { - fn new(pool: &'a Pool) -> Self { + fn new(pool: &'a AnyPool) -> Self { Self { pool, search: None, @@ -426,14 +457,76 @@ impl<'a> RequestQueryBuilder<'a> { } pub async fn execute(&self) -> Result> { - let mut query_builder = self.build_query("*"); - query_builder.push(" LIMIT "); - query_builder.push_bind(self.limit); - query_builder.push(" OFFSET "); - query_builder.push_bind(self.offset); + let mut sql = "SELECT * FROM request WHERE created_at BETWEEN $1 AND $2".to_string(); + let mut param_count = 2; + + // Build the SQL string with parameter placeholders + match &self.search { + Some(SearchField::TxHash(_)) => { + param_count += 1; + sql.push_str(&format!(" AND (request_tx_hash = ${param_count}")); + param_count += 1; + sql.push_str(&format!(" OR reveal_tx_hash = ${param_count})")); + } + Some(SearchField::Sender(_)) => { + param_count += 1; + sql.push_str(&format!(" AND sender = ${param_count}")); + } + Some(SearchField::SequenceNumber(_)) => { + param_count += 1; + sql.push_str(&format!(" AND sequence = ${param_count}")); + } + None => (), + } + + if self.network_id.is_some() { + param_count += 1; + sql.push_str(&format!(" AND network_id = ${param_count}")); + } + + if self.state.is_some() { + param_count += 1; + sql.push_str(&format!(" AND state = ${param_count}")); + } + + sql.push_str(" ORDER BY created_at DESC"); + + param_count += 1; + sql.push_str(&format!(" LIMIT ${param_count}")); + param_count += 1; + sql.push_str(&format!(" OFFSET ${param_count}")); + + // Now bind all parameters in order + let mut query = sqlx::query_as::<_, RequestRow>(&sql) + .bind(self.min_timestamp.timestamp()) + .bind(self.max_timestamp.timestamp()); + + match &self.search { + Some(SearchField::TxHash(tx_hash)) => { + let tx_hash: String = tx_hash.encode_hex(); + query = query.bind(tx_hash.clone()).bind(tx_hash); + } + Some(SearchField::Sender(sender)) => { + let sender: String = sender.encode_hex(); + query = query.bind(sender); + } + Some(SearchField::SequenceNumber(sequence_number)) => { + query = query.bind(sequence_number); + } + None => (), + } + + if let Some(network_id) = &self.network_id { + query = query.bind(network_id); + } + + if let Some(state) = &self.state { + query = query.bind(state.to_string()); + } + + query = query.bind(self.limit).bind(self.offset); - let result: sqlx::Result> = - query_builder.build_query_as().fetch_all(self.pool).await; + let result: sqlx::Result> = query.fetch_all(self.pool).await; if let Err(e) = &result { tracing::error!("Failed to fetch request: {}", e); @@ -442,55 +535,68 @@ impl<'a> RequestQueryBuilder<'a> { Ok(result?.into_iter().filter_map(|row| row.into()).collect()) } - pub async fn count_results(&self) -> Result { - self.build_query("COUNT(*) AS count") - .build_query_scalar::() - .fetch_one(self.pool) - .await - .map_err(|err| err.into()) - } + pub async fn count_results(&self) -> Result { + let mut sql = "SELECT COUNT(*) FROM request WHERE created_at BETWEEN $1 AND $2".to_string(); + let mut param_count = 2; + + // Build the SQL string with parameter placeholders + match &self.search { + Some(SearchField::TxHash(_)) => { + param_count += 1; + sql.push_str(&format!(" AND (request_tx_hash = ${param_count}")); + param_count += 1; + sql.push_str(&format!(" OR reveal_tx_hash = ${param_count})")); + } + Some(SearchField::Sender(_)) => { + param_count += 1; + sql.push_str(&format!(" AND sender = ${param_count}")); + } + Some(SearchField::SequenceNumber(_)) => { + param_count += 1; + sql.push_str(&format!(" AND sequence = ${param_count}")); + } + None => (), + } + + if self.network_id.is_some() { + param_count += 1; + sql.push_str(&format!(" AND network_id = ${param_count}")); + } + + if self.state.is_some() { + param_count += 1; + sql.push_str(&format!(" AND state = ${param_count}")); + } - fn build_query(&self, columns: &str) -> QueryBuilder { - let mut query_builder = QueryBuilder::new(format!( - "SELECT {columns} FROM request WHERE created_at BETWEEN " - )); - query_builder.push_bind(self.min_timestamp); - query_builder.push(" AND "); - query_builder.push_bind(self.max_timestamp); + // Now bind all parameters in order + let mut query = sqlx::query_scalar::<_, i64>(&sql) + .bind(self.min_timestamp.timestamp()) + .bind(self.max_timestamp.timestamp()); match &self.search { Some(SearchField::TxHash(tx_hash)) => { let tx_hash: String = tx_hash.encode_hex(); - query_builder.push(" AND (request_tx_hash = "); - query_builder.push_bind(tx_hash.clone()); - query_builder.push(" OR reveal_tx_hash = "); - query_builder.push_bind(tx_hash); - query_builder.push(")"); + query = query.bind(tx_hash.clone()).bind(tx_hash); } Some(SearchField::Sender(sender)) => { let sender: String = sender.encode_hex(); - query_builder.push(" AND sender = "); - query_builder.push_bind(sender); + query = query.bind(sender); } Some(SearchField::SequenceNumber(sequence_number)) => { - query_builder.push(" AND sequence = "); - query_builder.push_bind(sequence_number); + query = query.bind(sequence_number); } None => (), } if let Some(network_id) = &self.network_id { - query_builder.push(" AND network_id = "); - query_builder.push_bind(network_id); + query = query.bind(network_id); } if let Some(state) = &self.state { - query_builder.push(" AND state = "); - query_builder.push_bind(state); + query = query.bind(state.to_string()); } - query_builder.push(" ORDER BY created_at DESC"); - query_builder + query.fetch_one(self.pool).await.map_err(|err| err.into()) } } @@ -510,7 +616,11 @@ pub enum SearchField { #[cfg(test)] mod test { - use {super::*, chrono::Duration, tokio::time::sleep}; + use { + super::*, + chrono::{Duration, Timelike}, + tokio::time::sleep, + }; fn get_random_request_status() -> RequestStatus { RequestStatus { @@ -518,8 +628,8 @@ mod test { network_id: 121, provider: Address::random(), sequence: 1, - created_at: chrono::Utc::now(), - last_updated_at: chrono::Utc::now(), + created_at: chrono::Utc::now().with_nanosecond(0).unwrap(), + last_updated_at: chrono::Utc::now().with_nanosecond(0).unwrap(), request_block_number: 1, request_tx_hash: TxHash::random(), user_random_number: [20; 32], From 7f6afc97fa5b12ba359a3936296d22940a0696d9 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 16:21:21 -0500 Subject: [PATCH 13/37] added test to verify multiple updates configured --- .../pyth-receiver/src/integration_tests.rs | 63 +++++++++++++++---- .../contracts/pyth-receiver/src/test_data.rs | 33 +++++++++- 2 files changed, 83 insertions(+), 13 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index 19e959facb..fb52702721 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -1,8 +1,7 @@ #[cfg(test)] mod test { use crate::error::PythReceiverError; - use crate::test_data::good_update1_results; - use crate::test_data::{self, good_update2_results, multiple_updates_results}; + use crate::test_data::*; use crate::PythReceiver; use alloy_primitives::{Address, U256}; use motsu::prelude::*; @@ -57,7 +56,7 @@ mod test { wormhole_contract: &Contract, alice: &Address, ) { - let guardians = test_data::current_guardians(); + let guardians = current_guardians(); let governance_contract = Address::from_slice(&GOVERNANCE_CONTRACT.to_be_bytes::<32>()[12..32]); wormhole_contract @@ -103,7 +102,7 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = test_data::good_update1(); + let update_data = good_update1(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -128,7 +127,7 @@ mod test { alice.fund(U256::from(200)); - let update_data = test_data::good_update1(); + let update_data = good_update1(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); let small_update_fee = update_fee / U256::from(2); @@ -140,17 +139,17 @@ mod test { } #[motsu::test] - fn test_get_price_after_multiple_updates_returns_recent_price( + fn test_get_price_after_multiple_different_updates_returns_recent_price( pyth_contract: Contract, wormhole_contract: Contract, alice: Address, ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data1 = test_data::good_update1(); + let update_data1 = good_update1(); let update_fee1 = mock_get_update_fee(update_data1.clone()).unwrap(); - let update_data2 = test_data::good_update2(); + let update_data2 = good_update2(); let update_fee2 = mock_get_update_fee(update_data2.clone()).unwrap(); alice.fund(update_fee1 + update_fee2); @@ -218,7 +217,7 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = test_data::good_update2(); + let update_data = good_update2(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -243,7 +242,7 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = test_data::good_update2(); + let update_data = good_update2(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -264,14 +263,14 @@ mod test { } #[motsu::test] - fn test_multiple_updates_different_ids_updates_both( + fn test_multiple_updates_in_same_vaa_different_ids_updates_both( pyth_contract: Contract, wormhole_contract: Contract, alice: Address, ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = test_data::multiple_updates(); + let update_data = multiple_updates_same_vaa(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -300,4 +299,44 @@ mod test { assert!(second_price_result.is_ok()); assert_eq!(second_price_result.unwrap(), multiple_updates_results()[1]); } + + #[motsu::test] + fn test_multiple_updates_different_ids_updates_both( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data = multiple_updates_diff_vaa(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + assert!(result.is_ok()); + + let first_id: [u8; 32] = [ + 0x3f, 0xa4, 0x25, 0x28, 0x48, 0xf9, 0xf0, 0xa1, 0x48, 0x0b, 0xe6, 0x27, 0x45, 0xa4, + 0x62, 0x9d, 0x9e, 0xb1, 0x32, 0x2a, 0xeb, 0xab, 0x8a, 0x79, 0x1e, 0x34, 0x4b, 0x3b, + 0x9c, 0x1a, 0xdc, 0xf5, + ]; + let second_id: [u8; 32] = TEST_PRICE_ID; + + let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); + assert!(first_price_result.is_ok()); + assert_eq!( + first_price_result.unwrap(), + multiple_updates_diff_vaa_results()[0] + ); + + let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); + assert!(second_price_result.is_ok()); + assert_eq!( + second_price_result.unwrap(), + multiple_updates_diff_vaa_results()[1] + ); + } } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index aa8cecc655..29d2ea8c9f 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -1,5 +1,6 @@ use alloy_primitives::{address, Address, I32, I64, U64}; use hex::FromHex; +use std::vec; pub fn good_update1() -> Vec> { let hex_str = "504e41550100000003b801000000040d0216f3809b6396fdb0708bc94515ddb96a3bc8fb1993916e74f522ab4c34a268836a0bb38786303b55cc40ecb50d18c92bf9fd61688a143f3d24a73a3f468e4ab0000365be4f3a330fe96bab246922d9fa4816a865dec40d7c522e37f7e3605673b8f52eb30a4dad7aee3e1ac4b21337b8831c6147ef6e359e843b307872e83b5c7fe80004b6f12766eb395d04bdb146b1b69a4b9a8831a9c9ea20ee841d55efe72c629aef17038b925584493af7882981260b09587d73b6463415f4e1f8fb0a2a2c959c8301065c18a776cf558e43494bf31dd26ac0e3aed07a01ca0063abcfca3736b892368e4bf8b957f2d2e83ff9eb3fa8f667ef97a5f48c286103f70fda4a72e077719a0200085896c930febfb864a8c292bde679092e386e53389cb2d26bd12ceed64d22c0cd27676dc5c46d3e3c5949abbce89d4cc5ec378cfbf9ab1b0c24cfeb3371df4864000a1c67e67adea9f8a71eee76765f1fe4c7d9539d21b62d0336513692897cc6acbe1c827599a1f8c73b7478e8d788443b4cfd4e373344ab1ac9f771c9c494124863000b7e08e1cc1291340e6ec7d1b04c6a9c63f74077a074ee68edf4f95cf24c9743620f8abc8213884c8e4848fb76ec319d365e0cdc746a6e534a4cb828e51719be79010c61a2fedeedf34e4c80624d80cb93e24bafe9f6d23339173004d84e61c2395bfb5f552e87b901beb192ba6f4fc905eae30317c52b8614071e08a265592f24e8de000de38d08612edc5ebc863ea5a96325cb991750a94ff0e50f86bc322cce81db7cd17cb4ed3703f7477eb111a9d881e1345f5b79d618814f31b46034196563ee6a18000e5b11510d585a2decaba0be9e71386865b6069061478f7254d7852d55116569eb426c385267e40264d16db08e0f1a9e2c44b7d1c926d3c3ba662b212572386483010fa1ad0278402104b146b6f4d1cf85ac4df961d24eea0c7948b700f9973596cad130abfff25feef37125ba38507f34308d967455651e7014e40264a0c6510d3af001101f2e4e70868d6c327c92229537f1ffa33e488da3140ccc086f8210b437b162190742bd01ad1cb495a93f20045b4bb47e1562fdd82a8a1548d87d788a492b17190111847f5095df3edefc58be0956aee19876e850516e132506fd67504afee3c8cf240f07fb607b3c8282a2e56dcad23959e6759bdf8ad345ba8150448da56ff34fcc016866baf800000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000880cea9014155575600000000000d8e5c8d0000271085e6ab1bb044f57c4cd6c1d32aa0a82a5032198301005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009f9828e420300000001284f2da6fffffff8000000006866baf8000000006866baf8000009f3b955a26000000001062cb8780c1283a49180b4986f0dec3c746d3daeb597887747b8f66a09876e1253a1ebb8a6bc4a5793daaa343db6057b82ec29715d7e1db0ffd6db523f7b80b03e3866ef5f5c80728801b92f5acecc28d8517e5615335d89c553f94b4370f3a20be0bc23e0fd401c4e5bd8bd32948a26233fc48f116428a490f087030ccfc442753e3074e2b9bbc1c61a009d86aaa200645c627a6b7f2f6597e34c60b14a58ef2583bcbb1d0e21b71a264fad2648ecc545031c7ed598772ffe875bf94a488389a49e6025e1b2a1f07ec598d0d9aa8ef7dd2733c8502c49d1d1323f1ae664e82e8a5e14978d52ab448ba9b1afc78f06c8cd17415a17"; @@ -13,12 +14,42 @@ pub fn good_update2() -> Vec> { vec![bytes] } -pub fn multiple_updates() -> Vec> { +pub fn multiple_updates_same_vaa() -> Vec> { let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); vec![bytes] } +pub fn multiple_updates_diff_vaa() -> Vec> { + let hex_str_update_1 = "504e41550100000003b801000000040d0168bad50bb1470e136368ebadeb842cf5256547a30cd65c320a0e476bca15f204326806e4290c004925040f5eea31592c55341936fe7e7537d7b4172f308fc79701033b17478509bf096ebc6107afaaf9f339f68d3ad7ebd9267585cd945b91c93f687550c07d4526dc6f2ad0335c1e849977d91d2e61be04d746a611b444af272a9f0104ce96c5a8d9e4e869c2b7ae54980bf9f315af3d93fe827a346b3b55041e019e982350498c7094c77f09567cd3f2a9961940e304666fb08bfbfac7aa1a6ec157cf010639036dea5439380e1c946f76f06480965b60edf280272ab7f649840e289e3d740e4822619e5608811c66158d22ccdea633961687b3afec2751a53a37b3fd6f4a010a370bf667e0fd0b6df27ae87e94513740d1581ca9eadc15b049b5ce4d96b57ba85f92a7bdc478fde36efd7ab80674048589421fc2644457d7b4e398bc1b5c01f0010bb5ef6559a20289cf9df4a8c076baecd3d8e3567973acf975ee52d0301b8cbddd7bb2e4348fb59020bae2caefd57945cf1107cff7f5fd068079fa59ea27e0e702000cae84bce54e40a0583d8c957ddab0388d053a85622cd83c3de49e4bc4acf515351e118f4c36b903f09fe3afd7ce5499c73c37e80d742fdfbb1a57d7f442f915f7000d90c05b57e9592b303305b0e2a88b05f5b006a5f911ac9feba74c309acbfc3af8270b4d8c0759677db601af1893ec4523f023ee6810070148cd017ca2fe2d5663010e17a7472d37425d9f1505e998736d4138f6f35c620c195340ef87ca0d155cf3aa12fe9c0a9c6341411734dacbc53e36895efa69c9895d682bc6329294a49fd928000fed2cb761a8837d87cf827141eedfeafd1901e38957930bf56cc044ebf2c34e3e06bd8197b10d356236acb2c31be6031b95caf2d7492e2744b176bf8a4a357cb70110f22c1178f5c734e83134432c2f5c76cb6462ccba9db0177640cde08b5413ce09630a9c0d5682ee2a1acca1f5d0a6a3583b9578c6e0fbb2d489e252ec7e5960a90011d984560f66ce62be4ba2512e2d62777429f37d6020f9dcb9a0ac54522f80462b4ff33ab5c832748e50cda01e34eef28271982ac22b216cac2a6bde33a83f6bfd011207131b4a1ab2f2ed984cd444210c5c4147d5da72b27d1fdbf6f1a087141ab6971b20175fd67abc937ad69a42991b780804e67e926a8ca2b218521d6572c394c100686ed88a00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000894a31a014155575600000000000da2316b00002710d957df1a464d0193bc0c9dd3b555d0544a04623e010055003fa4252848f9f0a1480be62745a4629d9eb1322aebab8a791e344b3b9c1adcf50000000002181de10000000000008db7fffffff800000000686ed88a00000000686ed88a00000000021501d40000000000008b300c29ff39535a48e8f09021a1fee7b9e887d68522dd790970609044b11d98aa7d4beb0d1e390c89ccad2fda83fd5316ad4483fc398b6534be617d42690cc27297ccbf19315c79064275612a0d16a2536fdafaca0648bd28b54a014a468906203c8a9d3deadd4ec12dd380bdd27f9a9bb41a5e841d23671743da3125f40b479518be5250080ac78df8826fb59d02dd07b310b69f954dea12b87cb5aa66af75d3da3f7327578f8ed0c0a7b98d1becad74b16e03ff52eee04497971d9985fd9cc05215490b44499df2c3a64f6fefa743421d947d34de37e185b242db065c8f89e9bb028a3710646273f0721e26c68dbd85fb20"; + let hex_str_update_2 = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; + let bytes_1 = Vec::from_hex(hex_str_update_1).expect("Invalid hex string"); + let bytes_2 = Vec::from_hex(hex_str_update_2).expect("Invalid hex string"); + + vec![bytes_1, bytes_2] +} + +pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2] { + [ + ( + U64::from(1752094858u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(35134945i64.to_le_bytes()), + U64::from(36279u64), + I64::from_le_bytes(34931156i64.to_le_bytes()), + U64::from(35632u64), + ), + ( + U64::from(1751573860u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10985663592646i64.to_le_bytes()), + U64::from(4569386330u64), + I64::from_le_bytes(10977795800000i64.to_le_bytes()), + U64::from(3919318300u64), + ), + ] +} + pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { ( U64::from(1751563000u64), From a0310a260c0a69680e5e31b3388b4e80496ff26d Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 16:32:28 -0500 Subject: [PATCH 14/37] imported mock_instant crate --- target_chains/stylus/Cargo.lock | 7 +++++++ target_chains/stylus/contracts/pyth-receiver/Cargo.toml | 1 + 2 files changed, 8 insertions(+) diff --git a/target_chains/stylus/Cargo.lock b/target_chains/stylus/Cargo.lock index 5551eab31c..adde73d5a9 100644 --- a/target_chains/stylus/Cargo.lock +++ b/target_chains/stylus/Cargo.lock @@ -3286,6 +3286,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "mock_instant" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" + [[package]] name = "motsu" version = "0.1.0" @@ -3982,6 +3988,7 @@ dependencies = [ "ethers", "eyre", "hex", + "mock_instant", "motsu 0.9.1", "pythnet-sdk", "serde", diff --git a/target_chains/stylus/contracts/pyth-receiver/Cargo.toml b/target_chains/stylus/contracts/pyth-receiver/Cargo.toml index f3d8a12ba8..8b4921655d 100644 --- a/target_chains/stylus/contracts/pyth-receiver/Cargo.toml +++ b/target_chains/stylus/contracts/pyth-receiver/Cargo.toml @@ -26,6 +26,7 @@ stylus-sdk = { version = "0.9.0", features = ["stylus-test"] } dotenv = "0.15.0" motsu = "0.9.0" wormhole-contract = { path = "../wormhole" } +mock_instant = "0.6.0" [features] default = ["mini-alloc"] From 0681af5f5336854ba97634c5eac1b75f23c15342 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 21:42:54 +0000 Subject: [PATCH 15/37] feat: implement mock_instant integration for get_current_timestamp - Modified get_current_timestamp to use MockClock::time().as_secs() in test mode - Added MockClock::set_time(Duration::from_secs(1761573860)) to three specific tests: - test_get_price_no_older_than_with_random_id_reverts_with_price_unavailable - test_get_price_no_older_than_where_update_younger_than_max_age_returns_price - test_get_price_no_older_than_reverts_too_old - Contract functionality unchanged (still uses block_timestamp in production) - All specified tests pass with mocked timestamp Co-Authored-By: ayush.suresh@dourolabs.xyz --- .../stylus/contracts/pyth-receiver/src/integration_tests.rs | 5 +++++ target_chains/stylus/contracts/pyth-receiver/src/lib.rs | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index fb52702721..ed5de43897 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -7,6 +7,8 @@ mod test { use motsu::prelude::*; use pythnet_sdk::wire::v1::{AccumulatorUpdateData, Proof}; use wormhole_contract::WormholeContract; + use mock_instant::global::MockClock; + use std::time::Duration; const TEST_PRICE_ID: [u8; 32] = [ 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6, 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, 0xe5, 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, @@ -191,6 +193,7 @@ mod test { wormhole_contract: Contract, alice: Address, ) { + MockClock::set_time(Duration::from_secs(1761573860)); pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); let random_id: [u8; 32] = [ @@ -215,6 +218,7 @@ mod test { wormhole_contract: Contract, alice: Address, ) { + MockClock::set_time(Duration::from_secs(1761573860)); pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); let update_data = good_update2(); @@ -240,6 +244,7 @@ mod test { wormhole_contract: Contract, alice: Address, ) { + MockClock::set_time(Duration::from_secs(1761573860)); pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); let update_data = good_update2(); diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 3ec35c650d..a8973d3521 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -12,6 +12,9 @@ mod structs; #[cfg(test)] mod test_data; +#[cfg(test)] +use mock_instant::global::MockClock; + use alloc::vec::Vec; use stylus_sdk::{ alloy_primitives::{Address, FixedBytes, I32, I64, U16, U256, U32, U64}, @@ -377,7 +380,7 @@ impl PythReceiver { fn get_current_timestamp(&self) -> u64 { #[cfg(test)] { - 1761573860u64 + MockClock::time().as_secs() } #[cfg(not(test))] { From ba64927b6fa7ea77bceed629094cac59ff40694a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 21:44:36 +0000 Subject: [PATCH 16/37] feat: add cfg(test) attributes to all test_data.rs functions - Added #[cfg(test)] to all 9 functions in test_data.rs: - good_update1() - good_update2() - multiple_updates_same_vaa() - multiple_updates_diff_vaa() - multiple_updates_diff_vaa_results() - good_update1_results() - multiple_updates_results() - good_update2_results() - current_guardians() - Ensures test data functions are only compiled during testing - All tests continue to pass with cfg(test) attributes Co-Authored-By: ayush.suresh@dourolabs.xyz --- .../stylus/contracts/pyth-receiver/src/test_data.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index 29d2ea8c9f..e27298a206 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -2,24 +2,28 @@ use alloy_primitives::{address, Address, I32, I64, U64}; use hex::FromHex; use std::vec; +#[cfg(test)] pub fn good_update1() -> Vec> { let hex_str = "504e41550100000003b801000000040d0216f3809b6396fdb0708bc94515ddb96a3bc8fb1993916e74f522ab4c34a268836a0bb38786303b55cc40ecb50d18c92bf9fd61688a143f3d24a73a3f468e4ab0000365be4f3a330fe96bab246922d9fa4816a865dec40d7c522e37f7e3605673b8f52eb30a4dad7aee3e1ac4b21337b8831c6147ef6e359e843b307872e83b5c7fe80004b6f12766eb395d04bdb146b1b69a4b9a8831a9c9ea20ee841d55efe72c629aef17038b925584493af7882981260b09587d73b6463415f4e1f8fb0a2a2c959c8301065c18a776cf558e43494bf31dd26ac0e3aed07a01ca0063abcfca3736b892368e4bf8b957f2d2e83ff9eb3fa8f667ef97a5f48c286103f70fda4a72e077719a0200085896c930febfb864a8c292bde679092e386e53389cb2d26bd12ceed64d22c0cd27676dc5c46d3e3c5949abbce89d4cc5ec378cfbf9ab1b0c24cfeb3371df4864000a1c67e67adea9f8a71eee76765f1fe4c7d9539d21b62d0336513692897cc6acbe1c827599a1f8c73b7478e8d788443b4cfd4e373344ab1ac9f771c9c494124863000b7e08e1cc1291340e6ec7d1b04c6a9c63f74077a074ee68edf4f95cf24c9743620f8abc8213884c8e4848fb76ec319d365e0cdc746a6e534a4cb828e51719be79010c61a2fedeedf34e4c80624d80cb93e24bafe9f6d23339173004d84e61c2395bfb5f552e87b901beb192ba6f4fc905eae30317c52b8614071e08a265592f24e8de000de38d08612edc5ebc863ea5a96325cb991750a94ff0e50f86bc322cce81db7cd17cb4ed3703f7477eb111a9d881e1345f5b79d618814f31b46034196563ee6a18000e5b11510d585a2decaba0be9e71386865b6069061478f7254d7852d55116569eb426c385267e40264d16db08e0f1a9e2c44b7d1c926d3c3ba662b212572386483010fa1ad0278402104b146b6f4d1cf85ac4df961d24eea0c7948b700f9973596cad130abfff25feef37125ba38507f34308d967455651e7014e40264a0c6510d3af001101f2e4e70868d6c327c92229537f1ffa33e488da3140ccc086f8210b437b162190742bd01ad1cb495a93f20045b4bb47e1562fdd82a8a1548d87d788a492b17190111847f5095df3edefc58be0956aee19876e850516e132506fd67504afee3c8cf240f07fb607b3c8282a2e56dcad23959e6759bdf8ad345ba8150448da56ff34fcc016866baf800000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000880cea9014155575600000000000d8e5c8d0000271085e6ab1bb044f57c4cd6c1d32aa0a82a5032198301005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009f9828e420300000001284f2da6fffffff8000000006866baf8000000006866baf8000009f3b955a26000000001062cb8780c1283a49180b4986f0dec3c746d3daeb597887747b8f66a09876e1253a1ebb8a6bc4a5793daaa343db6057b82ec29715d7e1db0ffd6db523f7b80b03e3866ef5f5c80728801b92f5acecc28d8517e5615335d89c553f94b4370f3a20be0bc23e0fd401c4e5bd8bd32948a26233fc48f116428a490f087030ccfc442753e3074e2b9bbc1c61a009d86aaa200645c627a6b7f2f6597e34c60b14a58ef2583bcbb1d0e21b71a264fad2648ecc545031c7ed598772ffe875bf94a488389a49e6025e1b2a1f07ec598d0d9aa8ef7dd2733c8502c49d1d1323f1ae664e82e8a5e14978d52ab448ba9b1afc78f06c8cd17415a17"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); vec![bytes] } +#[cfg(test)] pub fn good_update2() -> Vec> { let hex_str = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); vec![bytes] } +#[cfg(test)] pub fn multiple_updates_same_vaa() -> Vec> { let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); vec![bytes] } +#[cfg(test)] pub fn multiple_updates_diff_vaa() -> Vec> { let hex_str_update_1 = "504e41550100000003b801000000040d0168bad50bb1470e136368ebadeb842cf5256547a30cd65c320a0e476bca15f204326806e4290c004925040f5eea31592c55341936fe7e7537d7b4172f308fc79701033b17478509bf096ebc6107afaaf9f339f68d3ad7ebd9267585cd945b91c93f687550c07d4526dc6f2ad0335c1e849977d91d2e61be04d746a611b444af272a9f0104ce96c5a8d9e4e869c2b7ae54980bf9f315af3d93fe827a346b3b55041e019e982350498c7094c77f09567cd3f2a9961940e304666fb08bfbfac7aa1a6ec157cf010639036dea5439380e1c946f76f06480965b60edf280272ab7f649840e289e3d740e4822619e5608811c66158d22ccdea633961687b3afec2751a53a37b3fd6f4a010a370bf667e0fd0b6df27ae87e94513740d1581ca9eadc15b049b5ce4d96b57ba85f92a7bdc478fde36efd7ab80674048589421fc2644457d7b4e398bc1b5c01f0010bb5ef6559a20289cf9df4a8c076baecd3d8e3567973acf975ee52d0301b8cbddd7bb2e4348fb59020bae2caefd57945cf1107cff7f5fd068079fa59ea27e0e702000cae84bce54e40a0583d8c957ddab0388d053a85622cd83c3de49e4bc4acf515351e118f4c36b903f09fe3afd7ce5499c73c37e80d742fdfbb1a57d7f442f915f7000d90c05b57e9592b303305b0e2a88b05f5b006a5f911ac9feba74c309acbfc3af8270b4d8c0759677db601af1893ec4523f023ee6810070148cd017ca2fe2d5663010e17a7472d37425d9f1505e998736d4138f6f35c620c195340ef87ca0d155cf3aa12fe9c0a9c6341411734dacbc53e36895efa69c9895d682bc6329294a49fd928000fed2cb761a8837d87cf827141eedfeafd1901e38957930bf56cc044ebf2c34e3e06bd8197b10d356236acb2c31be6031b95caf2d7492e2744b176bf8a4a357cb70110f22c1178f5c734e83134432c2f5c76cb6462ccba9db0177640cde08b5413ce09630a9c0d5682ee2a1acca1f5d0a6a3583b9578c6e0fbb2d489e252ec7e5960a90011d984560f66ce62be4ba2512e2d62777429f37d6020f9dcb9a0ac54522f80462b4ff33ab5c832748e50cda01e34eef28271982ac22b216cac2a6bde33a83f6bfd011207131b4a1ab2f2ed984cd444210c5c4147d5da72b27d1fdbf6f1a087141ab6971b20175fd67abc937ad69a42991b780804e67e926a8ca2b218521d6572c394c100686ed88a00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000894a31a014155575600000000000da2316b00002710d957df1a464d0193bc0c9dd3b555d0544a04623e010055003fa4252848f9f0a1480be62745a4629d9eb1322aebab8a791e344b3b9c1adcf50000000002181de10000000000008db7fffffff800000000686ed88a00000000686ed88a00000000021501d40000000000008b300c29ff39535a48e8f09021a1fee7b9e887d68522dd790970609044b11d98aa7d4beb0d1e390c89ccad2fda83fd5316ad4483fc398b6534be617d42690cc27297ccbf19315c79064275612a0d16a2536fdafaca0648bd28b54a014a468906203c8a9d3deadd4ec12dd380bdd27f9a9bb41a5e841d23671743da3125f40b479518be5250080ac78df8826fb59d02dd07b310b69f954dea12b87cb5aa66af75d3da3f7327578f8ed0c0a7b98d1becad74b16e03ff52eee04497971d9985fd9cc05215490b44499df2c3a64f6fefa743421d947d34de37e185b242db065c8f89e9bb028a3710646273f0721e26c68dbd85fb20"; let hex_str_update_2 = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; @@ -29,6 +33,7 @@ pub fn multiple_updates_diff_vaa() -> Vec> { vec![bytes_1, bytes_2] } +#[cfg(test)] pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2] { [ ( @@ -50,6 +55,7 @@ pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2 ] } +#[cfg(test)] pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { ( U64::from(1751563000u64), @@ -61,6 +67,7 @@ pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { ) } +#[cfg(test)] pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { [ ( @@ -82,6 +89,7 @@ pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { ] } +#[cfg(test)] pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { ( U64::from(1751573860u64), @@ -93,6 +101,7 @@ pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { ) } +#[cfg(test)] pub fn current_guardians() -> Vec
{ vec![ address!("0x5893B5A76c3f739645648885bDCcC06cd70a3Cd3"), // Rockaway From 9e144fff4eea43ea29ce1d167c51470aff61c719 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 17:26:20 -0500 Subject: [PATCH 17/37] implemented parse_price_feed_updates_unique --- .../contracts/pyth-receiver/src/error.rs | 2 + .../stylus/contracts/pyth-receiver/src/lib.rs | 50 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/error.rs b/target_chains/stylus/contracts/pyth-receiver/src/error.rs index 5f0bb2ecf6..4b68178d80 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/error.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/error.rs @@ -18,6 +18,7 @@ pub enum PythReceiverError { InvalidEmitterAddress, TooManyUpdates, PriceFeedNotFoundWithinRange, + NoFreshUpdate, } impl core::fmt::Debug for PythReceiverError { @@ -45,6 +46,7 @@ impl From for Vec { PythReceiverError::InvalidEmitterAddress => 14, PythReceiverError::TooManyUpdates => 15, PythReceiverError::PriceFeedNotFoundWithinRange => 16, + PythReceiverError::NoFreshUpdate => 17, }] } } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 32a8f7e859..cb75a513aa 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -193,11 +193,30 @@ impl PythReceiver { pub fn update_price_feeds_if_necessary( &mut self, - _update_data: Vec>, - _price_ids: Vec<[u8; 32]>, - _publish_times: Vec, - ) { - // dummy implementation + update_data: Vec>, + price_ids: Vec<[u8; 32]>, + publish_times: Vec, + ) -> Result<(), PythReceiverError> { + if (price_ids.len() != publish_times.len()) + || (price_ids.is_empty() && publish_times.is_empty()) + { + return Err(PythReceiverError::InvalidUpdateData); + } + + for i in 0..price_ids.len() { + if (self.latest_price_info_publish_time(price_ids[i]) < publish_times[i]) { + self.update_price_feeds(update_data.clone())?; + return Ok(()); + } + } + + return Err(PythReceiverError::NoFreshUpdate); + } + + fn latest_price_info_publish_time(&self, price_id: [u8; 32]) -> u64 { + let price_id_fb: FixedBytes<32> = FixedBytes::from(price_id); + let recent_price_info = self.latest_price_info.get(price_id_fb); + recent_price_info.publish_time.get().to::() } fn update_price_feeds_internal( @@ -449,12 +468,21 @@ impl PythReceiver { pub fn parse_price_feed_updates_unique( &mut self, - _update_data: Vec>, - _price_ids: Vec<[u8; 32]>, - _min_publish_time: u64, - _max_publish_time: u64, - ) -> Vec { - Vec::new() + update_data: Vec>, + price_ids: Vec<[u8; 32]>, + min_publish_time: u64, + max_publish_time: u64, + ) -> Result, PythReceiverError> { + let price_feeds = self.parse_price_feed_updates_with_config( + update_data, + price_ids, + min_publish_time, + max_publish_time, + true, + false, + false, + ); + price_feeds } fn is_no_older_than(&self, publish_time: U64, max_age: u64) -> bool { From 6897096914b9edc37529c01e8228d19dad9a3c44 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Wed, 9 Jul 2025 17:52:18 -0500 Subject: [PATCH 18/37] pushing work for now --- .../stylus/contracts/pyth-receiver/src/lib.rs | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index cb75a513aa..7bc41e7e96 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -300,7 +300,7 @@ impl PythReceiver { pub fn parse_price_feed_updates_with_config( &mut self, - update_data: Vec, + update_data: Vec>, price_ids: Vec<[u8; 32]>, min_allowed_publish_time: u64, max_allowed_publish_time: u64, @@ -308,22 +308,24 @@ impl PythReceiver { check_update_data_is_minimal: bool, store_updates_if_fresh: bool, ) -> Result, PythReceiverError> { - let price_pairs; - if store_updates_if_fresh { - price_pairs = self.update_price_feeds_internal( - update_data, - price_ids.clone(), - min_allowed_publish_time, - max_allowed_publish_time, - check_uniqueness, - )?; - } else { - price_pairs = self.parse_price_feed_updates_internal( - update_data, - min_allowed_publish_time, - max_allowed_publish_time, - check_uniqueness, - )?; + let all_parsed_price_pairs = Vec::new(); + for data in &update_data { + if store_updates_if_fresh { + all_parsed_price_pairs.extend(self.update_price_feeds_internal( + data, + price_ids.clone(), + min_allowed_publish_time, + max_allowed_publish_time, + check_uniqueness, + )?); + } else { + all_parsed_price_pairs.extend(self.parse_price_feed_updates_internal( + data, + min_allowed_publish_time, + max_allowed_publish_time, + check_uniqueness, + )?); + } } if check_update_data_is_minimal && price_ids.len() != price_pairs.len() { From 2c77720df3a611955c46efaac9f14aa5cd99b8a2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 22:59:07 +0000 Subject: [PATCH 19/37] fix: modify parse_price_feed_updates_with_config to preserve existing price values - Fixed compilation errors by making all_parsed_price_pairs mutable - Modified logic after first for loop to iterate through price pairs and preserve existing values - Updated parse_price_feed_updates to wrap single update_data in vector - All tests passing Co-Authored-By: ayush.suresh@dourolabs.xyz --- .../stylus/contracts/pyth-receiver/src/lib.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 7bc41e7e96..85297d66d8 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -287,7 +287,7 @@ impl PythReceiver { max_publish_time: u64, ) -> Result, PythReceiverError> { let price_feeds = self.parse_price_feed_updates_with_config( - update_data, + vec![update_data], price_ids, min_publish_time, max_publish_time, @@ -308,11 +308,11 @@ impl PythReceiver { check_update_data_is_minimal: bool, store_updates_if_fresh: bool, ) -> Result, PythReceiverError> { - let all_parsed_price_pairs = Vec::new(); + let mut all_parsed_price_pairs = Vec::new(); for data in &update_data { if store_updates_if_fresh { all_parsed_price_pairs.extend(self.update_price_feeds_internal( - data, + data.clone(), price_ids.clone(), min_allowed_publish_time, max_allowed_publish_time, @@ -320,7 +320,7 @@ impl PythReceiver { )?); } else { all_parsed_price_pairs.extend(self.parse_price_feed_updates_internal( - data, + data.clone(), min_allowed_publish_time, max_allowed_publish_time, check_uniqueness, @@ -328,13 +328,18 @@ impl PythReceiver { } } - if check_update_data_is_minimal && price_ids.len() != price_pairs.len() { + if check_update_data_is_minimal && all_parsed_price_pairs.len() != price_ids.len() { return Err(PythReceiverError::InvalidUpdateData); } - let price_map: BTreeMap<[u8; 32], PriceInfoReturn> = price_pairs.into_iter().collect(); - let mut result: Vec = Vec::with_capacity(price_ids.len()); + let mut price_map: BTreeMap<[u8; 32], PriceInfoReturn> = BTreeMap::new(); + + for (price_id, price_info) in all_parsed_price_pairs { + if !price_map.contains_key(&price_id) { + price_map.insert(price_id, price_info); + } + } for price_id in price_ids { if let Some(price_info) = price_map.get(&price_id) { From 938c9baf9c426dd512917ad3547d85ecea2c2123 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Thu, 10 Jul 2025 13:42:04 +0100 Subject: [PATCH 20/37] chore(ci): fix lazer solana contract ci (#2847) --- lazer/contracts/solana/Cargo.lock | 2 +- .../solana/programs/pyth-lazer-solana-contract/Cargo.toml | 3 +++ lazer/sdk/rust/protocol/src/router.rs | 6 ++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lazer/contracts/solana/Cargo.lock b/lazer/contracts/solana/Cargo.lock index 70c8d18134..24cbc8e544 100644 --- a/lazer/contracts/solana/Cargo.lock +++ b/lazer/contracts/solana/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "Inflector" diff --git a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml index 62b020b6ac..84c2de8253 100644 --- a/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml +++ b/lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml @@ -6,6 +6,9 @@ description = "Pyth Lazer Solana contract and SDK." license = "Apache-2.0" repository = "https://github.com/pyth-network/pyth-crosschain" +# Forces recent toolchains to create a lockfile compatible with the current anchor/solana version. +rust-version = "1.75.0" + [lib] crate-type = ["cdylib", "lib"] name = "pyth_lazer_solana_contract" diff --git a/lazer/sdk/rust/protocol/src/router.rs b/lazer/sdk/rust/protocol/src/router.rs index 29dda4f298..b0ab7b133c 100644 --- a/lazer/sdk/rust/protocol/src/router.rs +++ b/lazer/sdk/rust/protocol/src/router.rs @@ -41,10 +41,8 @@ impl TryFrom<&Timestamp> for TimestampUs { impl From for Timestamp { fn from(value: TimestampUs) -> Self { Timestamp { - #[allow( - clippy::cast_possible_wrap, - reason = "u64 to i64 after this division can never overflow because the value cannot be too big" - )] + // u64 to i64 after this division can never overflow because the value cannot be too big + #[allow(clippy::cast_possible_wrap)] seconds: (value.0 / 1_000_000) as i64, nanos: (value.0 % 1_000_000) as i32 * 1000, special_fields: Default::default(), From e0467d649231240d3e1872bd9cafa61408a3838d Mon Sep 17 00:00:00 2001 From: Aditya Arora Date: Thu, 10 Jul 2025 18:34:02 +0200 Subject: [PATCH 21/37] chore(entropy-debugger) Updated Entropy chains (#2835) * chore(entropy-debugger) Updated Entropy chains * added Zetachain --- .../src/store/entropy-deployments.ts | 217 ++++-------------- 1 file changed, 50 insertions(+), 167 deletions(-) diff --git a/apps/entropy-debugger/src/store/entropy-deployments.ts b/apps/entropy-debugger/src/store/entropy-deployments.ts index cd5b650001..afb08c532a 100644 --- a/apps/entropy-debugger/src/store/entropy-deployments.ts +++ b/apps/entropy-debugger/src/store/entropy-deployments.ts @@ -27,24 +27,6 @@ export const EntropyDeployments = { rpc: "https://rpc.blast.io", nativeCurrency: "ETH", }, - "lightlink-phoenix": { - address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", - network: "mainnet", - explorer: "https://phoenix.lightlink.io/address/$ADDRESS", - delay: "1 block", - gasLimit: "500K", - rpc: "https://replicator.phoenix.lightlink.io/rpc/v1", - nativeCurrency: "ETH", - }, - chiliz: { - address: "0x0708325268dF9F66270F1401206434524814508b", - network: "mainnet", - explorer: "https://scan.chiliz.com/address/$ADDRESS", - delay: "12 blocks", - gasLimit: "500K", - rpc: "https://rpc.ankr.com/chiliz", - nativeCurrency: "CHZ", - }, arbitrum: { address: "0x7698E925FfC29655576D0b361D75Af579e20AdAc", network: "mainnet", @@ -60,27 +42,9 @@ export const EntropyDeployments = { explorer: "https://optimistic.etherscan.io/address/$ADDRESS", delay: "2 blocks", gasLimit: "500K", - rpc: "https://optimism.llamarpc.com", + rpc: "https://optimism.drpc.org", nativeCurrency: "ETH", }, - mode: { - address: "0x8D254a21b3C86D32F7179855531CE99164721933", - network: "mainnet", - explorer: "https://explorer.mode.network/address/$ADDRESS", - delay: "2 blocks", - gasLimit: "500K", - rpc: "https://mainnet.mode.network/", - nativeCurrency: "ETH", - }, - zetachain: { - address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", - network: "mainnet", - explorer: "https://zetachain.blockscout.com/address/$ADDRESS", - delay: "0 block", - gasLimit: "500K", - rpc: "https://zetachain-evm.blockpi.network/v1/rpc/public", - nativeCurrency: "ZETA", - }, base: { address: "0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb", network: "mainnet", @@ -90,46 +54,10 @@ export const EntropyDeployments = { rpc: "https://developer-access-mainnet.base.org/", nativeCurrency: "ETH", }, - "lightlink-pegasus": { - rpc: "https://replicator.pegasus.lightlink.io/rpc/v1", - network: "testnet", - delay: "", - address: "0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a", - explorer: "https://pegasus.lightlink.io/address/$ADDRESS", - gasLimit: "500K", - nativeCurrency: "ETH", - }, - "chiliz-spicy": { - rpc: "https://spicy-rpc.chiliz.com", - network: "testnet", - delay: "", - address: "0xD458261E832415CFd3BAE5E416FdF3230ce6F134", - explorer: "https://spicy-explorer.chiliz.com/address/$ADDRESS", - gasLimit: "500K", - nativeCurrency: "CHZ", - }, - "conflux-espace-testnet": { - rpc: "https://evmtestnet.confluxrpc.com", - network: "testnet", - delay: "", - address: "0xdF21D137Aadc95588205586636710ca2890538d5", - explorer: "https://evmtestnet.confluxscan.org/address/$ADDRESS", - gasLimit: "500K", - nativeCurrency: "CFX", - }, - "mode-sepolia": { - rpc: "https://sepolia.mode.network/", - network: "testnet", - delay: "", - address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", - explorer: "https://sepolia.explorer.mode.network/address/$ADDRESS", - gasLimit: "500K", - nativeCurrency: "ETH", - }, "sei-evm-testnet": { rpc: "https://evm-rpc-testnet.sei-apis.com", network: "testnet", - delay: "", + delay: "1 block", address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", explorer: "https://seitrace.com/address/$ADDRESS?chain=atlantic-2", gasLimit: "500K", @@ -138,7 +66,7 @@ export const EntropyDeployments = { "arbitrum-sepolia": { rpc: "https://sepolia-rollup.arbitrum.io/rpc", network: "testnet", - delay: "", + delay: "6 blocks", address: "0x549Ebba8036Ab746611B4fFA1423eb0A4Df61440", explorer: "https://sepolia.arbiscan.io/address/$ADDRESS", gasLimit: "2.5M", @@ -147,16 +75,16 @@ export const EntropyDeployments = { "blast-testnet": { rpc: "https://sepolia.blast.io", network: "testnet", - delay: "", + delay: "1 block", address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", explorer: "https://testnet.blastscan.io/address/$ADDRESS", gasLimit: "500K", nativeCurrency: "ETH", }, "optimism-sepolia": { - rpc: "https://api.zan.top/opt-sepolia", + rpc: "https://sepolia.optimism.io", network: "testnet", - delay: "", + delay: "2 blocks", address: "0x4821932D0CDd71225A6d914706A621e0389D7061", explorer: "https://optimism-sepolia.blockscout.com/address/$ADDRESS", gasLimit: "500K", @@ -165,57 +93,12 @@ export const EntropyDeployments = { "base-sepolia": { rpc: "https://sepolia.base.org", network: "testnet", - delay: "", + delay: "1 block", address: "0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c", explorer: "https://base-sepolia.blockscout.com/address/$ADDRESS", gasLimit: "500K", nativeCurrency: "ETH", }, - "berachain-testnet-v2": { - rpc: "https://evm-rpc-bera.rhino-apis.com/", - network: "testnet", - delay: "", - address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", - explorer: "https://bartio.beratrail.io/address/$ADDRESS", - gasLimit: "2.5M", - nativeCurrency: "BERA", - }, - "coredao-testnet": { - rpc: "https://rpc.test.btcs.network", - network: "testnet", - delay: "", - address: "0xf0a1b566B55e0A0CB5BeF52Eb2a57142617Bee67", - explorer: "https://scan.test.btcs.network/address/$ADDRESS", - gasLimit: "500K", - nativeCurrency: "tCORE", - }, - "zetachain-testnet": { - rpc: "https://zetachain-athens-evm.blockpi.network/v1/rpc/public", - network: "testnet", - delay: "", - address: "0x4374e5a8b9C22271E9EB878A2AA31DE97DF15DAF", - explorer: "https://explorer.zetachain.com/address/$ADDRESS", - gasLimit: "500K", - nativeCurrency: "ZETA", - }, - "taiko-hekla": { - rpc: "https://rpc.hekla.taiko.xyz/", - network: "testnet", - delay: "", - address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", - explorer: "https://hekla.taikoscan.network/address/$ADDRESS", - gasLimit: "500K", - nativeCurrency: "ETH", - }, - "orange-testnet": { - address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", - explorer: "https://subnets-test.avax.network/orangetest/address/$ADDRESS", - delay: "", - gasLimit: "500K", - network: "testnet", - rpc: "https://subnets.avax.network/orangetest/testnet/rpc", - nativeCurrency: "JUICE", - }, "sei-evm": { address: "0x98046Bd286715D3B0BC227Dd7a956b83D8978603", explorer: "https://seitrace.com/address/$ADDRESS?chain=pacific-1", @@ -225,37 +108,10 @@ export const EntropyDeployments = { rpc: "https://evm-rpc.sei-apis.com", nativeCurrency: "SEI", }, - merlin: { - address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", - explorer: "https://scan.merlinchain.io/address/$ADDRESS", - delay: "1 block", - gasLimit: "500K", - network: "mainnet", - rpc: "https://rpc.merlinchain.io", - nativeCurrency: "BTC", - }, - "merlin-testnet": { - address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", - explorer: "https://testnet-scan.merlinchain.io/address/$ADDRESS", - delay: "", - gasLimit: "500K", - network: "testnet", - rpc: "https://testnet-rpc.merlinchain.io/", - nativeCurrency: "BTC", - }, - taiko: { - address: "0x26DD80569a8B23768A1d80869Ed7339e07595E85", - explorer: "https://taikoscan.io/address/$ADDRESS", - delay: "1 block", - gasLimit: "500K", - network: "mainnet", - rpc: "https://rpc.mainnet.taiko.xyz", - nativeCurrency: "ETH", - }, "etherlink-testnet": { address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", explorer: "https://testnet.explorer.etherlink.com/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "15M", network: "testnet", rpc: "https://node.ghostnet.etherlink.com", @@ -282,7 +138,7 @@ export const EntropyDeployments = { "kaia-testnet": { address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", explorer: "https://kairos.kaiascan.io/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://rpc.ankr.com/klaytn_testnet", @@ -291,7 +147,7 @@ export const EntropyDeployments = { "tabi-testnet": { address: "0xEbe57e8045F2F230872523bbff7374986E45C486", explorer: "https://testnetv2.tabiscan.com/address/$ADDRESS", - delay: "", + delay: "0 block", gasLimit: "500K", network: "testnet", rpc: "https://rpc.testnetv2.tabichain.com", @@ -300,7 +156,7 @@ export const EntropyDeployments = { "b3-testnet": { address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", explorer: "https://sepolia.explorer.b3.fun/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://sepolia.b3.fun/http/", @@ -318,7 +174,7 @@ export const EntropyDeployments = { "apechain-testnet": { address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", explorer: "https://curtis.explorer.caldera.xyz/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://curtis.rpc.caldera.xyz/http", @@ -327,7 +183,7 @@ export const EntropyDeployments = { "soneium-minato-testnet": { address: "0x23f0e8FAeE7bbb405E7A7C3d60138FCfd43d7509", explorer: "https://explorer-testnet.soneium.org/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://rpc.minato.soneium.org/", @@ -345,7 +201,7 @@ export const EntropyDeployments = { "sanko-testnet": { address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", explorer: "https://sanko-arb-sepolia.explorer.caldera.xyz/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://sanko-arb-sepolia.rpc.caldera.xyz/http", @@ -363,7 +219,7 @@ export const EntropyDeployments = { "abstract-testnet": { address: "0x858687fD592112f7046E394A3Bf10D0C11fF9e63", explorer: "https://explorer.testnet.abs.xyz/address/$ADDRESS", - delay: "", + delay: "0 block", gasLimit: "500K", network: "testnet", rpc: "https://api.testnet.abs.xyz", @@ -372,7 +228,7 @@ export const EntropyDeployments = { "sonic-blaze-testnet": { address: "0xebe57e8045f2f230872523bbff7374986e45c486", explorer: "https://blaze.soniclabs.com/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://rpc.blaze.soniclabs.com", @@ -381,7 +237,7 @@ export const EntropyDeployments = { "unichain-sepolia": { address: "0x8D254a21b3C86D32F7179855531CE99164721933", explorer: "https://unichain-sepolia.blockscout.com/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://sepolia.unichain.org", @@ -399,7 +255,7 @@ export const EntropyDeployments = { "story-testnet": { address: "0x5744Cbf430D99456a0A8771208b674F27f8EF0Fb", explorer: "https://aeneid.storyscan.xyz/address/$ADDRESS", - delay: "", + delay: "0 block", gasLimit: "500K", network: "testnet", rpc: "https://aeneid.storyrpc.io", @@ -408,7 +264,7 @@ export const EntropyDeployments = { "monad-testnet": { address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", explorer: "https://testnet.monadexplorer.com/address/$ADDRESS", - delay: "", + delay: "2 blocks", gasLimit: "500K", network: "testnet", rpc: "https://testnet-rpc.monad.xyz", @@ -417,7 +273,7 @@ export const EntropyDeployments = { abstract: { address: "0x5a4a369F4db5df2054994AF031b7b23949b98c0e", explorer: "https://abscan.org/address/$ADDRESS", - delay: "1 block", + delay: "0 block", gasLimit: "500K", network: "mainnet", rpc: "https://api.mainnet.abs.xyz", @@ -426,7 +282,7 @@ export const EntropyDeployments = { story: { address: "0xdF21D137Aadc95588205586636710ca2890538d5", explorer: "https://storyscan.xyz/address/$ADDRESS", - delay: "", + delay: "0 block", gasLimit: "500K", network: "mainnet", rpc: "https://mainnet.storyrpc.io", @@ -435,7 +291,7 @@ export const EntropyDeployments = { "berachain-bepolia": { address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", explorer: "https://bepolia.beratrail.io/address/$ADDRESS", - delay: "", + delay: "1 block", gasLimit: "500K", network: "testnet", rpc: "https://bepolia.rpc.berachain.com", @@ -444,7 +300,7 @@ export const EntropyDeployments = { hyperevm: { address: "0xfA25E653b44586dBbe27eE9d252192F0e4956683", explorer: "https://hyperliquid.cloud.blockscout.com/address/$ADDRESS", - delay: "", + delay: "0 block", gasLimit: "500K", network: "mainnet", rpc: "https://rpc.hyperliquid.xyz/evm", @@ -459,6 +315,33 @@ export const EntropyDeployments = { rpc: "https://soneium.drpc.org", nativeCurrency: "ETH", }, + unichain: { + address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", + explorer: "https://unichain.blockscout.com/address/$ADDRESS", + delay: "1 block", + gasLimit: "500K", + network: "mainnet", + rpc: "https://mainnet.unichain.org", + nativeCurrency: "ETH", + }, + zetachain: { + address: "0x36825bf3Fbdf5a29E2d5148bfe7Dcf7B5639e320", + network: "mainnet", + explorer: "https://zetachain.blockscout.com/address/$ADDRESS", + delay: "0 block", + gasLimit: "500K", + rpc: "https://zetachain-evm.blockpi.network/v1/rpc/public", + nativeCurrency: "ZETA", + }, + "zetachain-testnet": { + rpc: "https://zetachain-athens-evm.blockpi.network/v1/rpc/public", + network: "testnet", + delay: "", + address: "0x4374e5a8b9C22271E9EB878A2AA31DE97DF15DAF", + explorer: "https://explorer.zetachain.com/address/$ADDRESS", + gasLimit: "500K", + nativeCurrency: "ZETA", + }, } as const satisfies Record; export const isValidDeployment = ( From 9f3d8093da358c3d9d29b5b3d1607e8b9fb1289b Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 13:18:18 -0500 Subject: [PATCH 22/37] set up integration tests --- .../pyth-receiver/src/integration_tests.rs | 16 ++++-- .../stylus/contracts/pyth-receiver/src/lib.rs | 30 +++++------ .../contracts/pyth-receiver/src/structs.rs | 8 +-- .../contracts/pyth-receiver/src/test_data.rs | 51 ++++++++++++------- 4 files changed, 63 insertions(+), 42 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index 1caa08c58f..a5e747c262 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -114,7 +114,9 @@ mod test { .update_price_feeds(update_data); assert!(result.is_ok()); - let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); + let price_result = pyth_contract + .sender(alice) + .get_price_unsafe(good_update1_feed_id()); assert!(price_result.is_ok()); assert_eq!(price_result.unwrap(), good_update1_results()); } @@ -166,7 +168,9 @@ mod test { .update_price_feeds(update_data2); assert!(result2.is_ok()); - let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); + let price_result = pyth_contract + .sender(alice) + .get_price_unsafe(good_update1_feed_id()); assert!(price_result.is_ok()); assert_eq!(price_result.unwrap(), good_update2_results()); } @@ -179,7 +183,9 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let price_result = pyth_contract.sender(alice).get_price_unsafe(TEST_PRICE_ID); + let price_result = pyth_contract + .sender(alice) + .get_price_unsafe(good_update1_feed_id()); assert!(price_result.is_err()); assert_eq!( price_result.unwrap_err(), @@ -233,7 +239,7 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_no_older_than(TEST_PRICE_ID, u64::MAX); + .get_price_no_older_than(good_update1_feed_id(), u64::MAX); assert!(price_result.is_ok()); assert_eq!(price_result.unwrap(), good_update2_results()); } @@ -259,7 +265,7 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_no_older_than(TEST_PRICE_ID, 1); + .get_price_no_older_than(good_update1_feed_id(), 1); assert!(price_result.is_err()); assert_eq!( price_result.unwrap_err(), diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 85297d66d8..cc404cf8c5 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -39,7 +39,7 @@ use pythnet_sdk::{ }, }, }; -use structs::{DataSource, DataSourceStorage, PriceInfoReturn, PriceInfoStorage}; +use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceInfoStorage}; use wormhole_vaas::{Readable, Vaa, Writeable}; sol_interface! { @@ -111,7 +111,7 @@ impl PythReceiver { } } - pub fn get_price_unsafe(&self, id: [u8; 32]) -> Result { + pub fn get_price_unsafe(&self, id: [u8; 32]) -> Result { let id_fb = FixedBytes::<32>::from(id); let price_info = self.latest_price_info.get(id_fb); @@ -134,7 +134,7 @@ impl PythReceiver { &self, id: [u8; 32], age: u64, - ) -> Result { + ) -> Result { let price_info = self.get_price_unsafe(id)?; if !self.is_no_older_than(price_info.0, age) { return Err(PythReceiverError::NewPriceUnavailable); @@ -142,7 +142,7 @@ impl PythReceiver { Ok(price_info) } - pub fn get_ema_price_unsafe(&self, id: [u8; 32]) -> Result { + pub fn get_ema_price_unsafe(&self, id: [u8; 32]) -> Result { let id_fb = FixedBytes::<32>::from(id); let price_info = self.latest_price_info.get(id_fb); @@ -164,7 +164,7 @@ impl PythReceiver { &self, id: [u8; 32], age: u64, - ) -> Result { + ) -> Result { let price_info = self.get_ema_price_unsafe(id)?; if !self.is_no_older_than(price_info.0, age) { return Err(PythReceiverError::NewPriceUnavailable); @@ -204,7 +204,7 @@ impl PythReceiver { } for i in 0..price_ids.len() { - if (self.latest_price_info_publish_time(price_ids[i]) < publish_times[i]) { + if self.latest_price_info_publish_time(price_ids[i]) < publish_times[i] { self.update_price_feeds(update_data.clone())?; return Ok(()); } @@ -226,7 +226,7 @@ impl PythReceiver { min_publish_time: u64, max_publish_time: u64, _unique: bool, - ) -> Result, PythReceiverError> { + ) -> Result, PythReceiverError> { let price_pairs = self.parse_price_feed_updates_internal( update_data, min_publish_time, @@ -285,7 +285,7 @@ impl PythReceiver { price_ids: Vec<[u8; 32]>, min_publish_time: u64, max_publish_time: u64, - ) -> Result, PythReceiverError> { + ) -> Result, PythReceiverError> { let price_feeds = self.parse_price_feed_updates_with_config( vec![update_data], price_ids, @@ -307,7 +307,7 @@ impl PythReceiver { check_uniqueness: bool, check_update_data_is_minimal: bool, store_updates_if_fresh: bool, - ) -> Result, PythReceiverError> { + ) -> Result, PythReceiverError> { let mut all_parsed_price_pairs = Vec::new(); for data in &update_data { if store_updates_if_fresh { @@ -332,8 +332,8 @@ impl PythReceiver { return Err(PythReceiverError::InvalidUpdateData); } - let mut result: Vec = Vec::with_capacity(price_ids.len()); - let mut price_map: BTreeMap<[u8; 32], PriceInfoReturn> = BTreeMap::new(); + let mut result: Vec = Vec::with_capacity(price_ids.len()); + let mut price_map: BTreeMap<[u8; 32], PriceFeedReturn> = BTreeMap::new(); for (price_id, price_info) in all_parsed_price_pairs { if !price_map.contains_key(&price_id) { @@ -358,7 +358,7 @@ impl PythReceiver { min_allowed_publish_time: u64, max_allowed_publish_time: u64, check_uniqueness: bool, - ) -> Result, PythReceiverError> { + ) -> Result, PythReceiverError> { let update_data_array: &[u8] = &update_data; // Check the first 4 bytes of the update_data_array for the magic header if update_data_array.len() < 4 { @@ -375,7 +375,7 @@ impl PythReceiver { let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; - let mut price_feeds: BTreeMap<[u8; 32], PriceInfoReturn> = BTreeMap::new(); + let mut price_feeds: BTreeMap<[u8; 32], PriceFeedReturn> = BTreeMap::new(); match accumulator_update.proof { Proof::WormholeMerkle { vaa, updates } => { @@ -469,7 +469,7 @@ impl PythReceiver { &mut self, _update_data: Vec>, _price_ids: Vec<[u8; 32]>, - ) -> Vec { + ) -> Vec { Vec::new() } @@ -479,7 +479,7 @@ impl PythReceiver { price_ids: Vec<[u8; 32]>, min_publish_time: u64, max_publish_time: u64, - ) -> Result, PythReceiverError> { + ) -> Result, PythReceiverError> { let price_feeds = self.parse_price_feed_updates_with_config( update_data, price_ids, diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index 13e419ca15..3f804725db 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -46,9 +46,9 @@ pub struct PriceInfoStorage { } // Addressing nit -- running into some versioning issues that preclude me -// from returning the PriceInfo struct directly. Need to figure that out. +// from returning the PriceFeed struct directly. Need to figure that out. -// pub struct PriceInfo { +// pub struct PriceFeed { // pub publish_time: U64, // pub expo: I32, // pub price: I64, @@ -57,7 +57,9 @@ pub struct PriceInfoStorage { // pub ema_conf: U64, // } -pub type PriceInfoReturn = (U64, I32, I64, U64, I64, U64); +pub type PriceFeedReturn = (U64, I32, I64, U64, I64, U64); + +pub type PriceReturn = ([u8; 32], U64, I32, I64, U64, I64, U64); #[cfg(test)] mod tests { diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index e27298a206..c241bc8da4 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -4,18 +4,31 @@ use std::vec; #[cfg(test)] pub fn good_update1() -> Vec> { - let hex_str = "504e41550100000003b801000000040d0216f3809b6396fdb0708bc94515ddb96a3bc8fb1993916e74f522ab4c34a268836a0bb38786303b55cc40ecb50d18c92bf9fd61688a143f3d24a73a3f468e4ab0000365be4f3a330fe96bab246922d9fa4816a865dec40d7c522e37f7e3605673b8f52eb30a4dad7aee3e1ac4b21337b8831c6147ef6e359e843b307872e83b5c7fe80004b6f12766eb395d04bdb146b1b69a4b9a8831a9c9ea20ee841d55efe72c629aef17038b925584493af7882981260b09587d73b6463415f4e1f8fb0a2a2c959c8301065c18a776cf558e43494bf31dd26ac0e3aed07a01ca0063abcfca3736b892368e4bf8b957f2d2e83ff9eb3fa8f667ef97a5f48c286103f70fda4a72e077719a0200085896c930febfb864a8c292bde679092e386e53389cb2d26bd12ceed64d22c0cd27676dc5c46d3e3c5949abbce89d4cc5ec378cfbf9ab1b0c24cfeb3371df4864000a1c67e67adea9f8a71eee76765f1fe4c7d9539d21b62d0336513692897cc6acbe1c827599a1f8c73b7478e8d788443b4cfd4e373344ab1ac9f771c9c494124863000b7e08e1cc1291340e6ec7d1b04c6a9c63f74077a074ee68edf4f95cf24c9743620f8abc8213884c8e4848fb76ec319d365e0cdc746a6e534a4cb828e51719be79010c61a2fedeedf34e4c80624d80cb93e24bafe9f6d23339173004d84e61c2395bfb5f552e87b901beb192ba6f4fc905eae30317c52b8614071e08a265592f24e8de000de38d08612edc5ebc863ea5a96325cb991750a94ff0e50f86bc322cce81db7cd17cb4ed3703f7477eb111a9d881e1345f5b79d618814f31b46034196563ee6a18000e5b11510d585a2decaba0be9e71386865b6069061478f7254d7852d55116569eb426c385267e40264d16db08e0f1a9e2c44b7d1c926d3c3ba662b212572386483010fa1ad0278402104b146b6f4d1cf85ac4df961d24eea0c7948b700f9973596cad130abfff25feef37125ba38507f34308d967455651e7014e40264a0c6510d3af001101f2e4e70868d6c327c92229537f1ffa33e488da3140ccc086f8210b437b162190742bd01ad1cb495a93f20045b4bb47e1562fdd82a8a1548d87d788a492b17190111847f5095df3edefc58be0956aee19876e850516e132506fd67504afee3c8cf240f07fb607b3c8282a2e56dcad23959e6759bdf8ad345ba8150448da56ff34fcc016866baf800000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000880cea9014155575600000000000d8e5c8d0000271085e6ab1bb044f57c4cd6c1d32aa0a82a5032198301005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009f9828e420300000001284f2da6fffffff8000000006866baf8000000006866baf8000009f3b955a26000000001062cb8780c1283a49180b4986f0dec3c746d3daeb597887747b8f66a09876e1253a1ebb8a6bc4a5793daaa343db6057b82ec29715d7e1db0ffd6db523f7b80b03e3866ef5f5c80728801b92f5acecc28d8517e5615335d89c553f94b4370f3a20be0bc23e0fd401c4e5bd8bd32948a26233fc48f116428a490f087030ccfc442753e3074e2b9bbc1c61a009d86aaa200645c627a6b7f2f6597e34c60b14a58ef2583bcbb1d0e21b71a264fad2648ecc545031c7ed598772ffe875bf94a488389a49e6025e1b2a1f07ec598d0d9aa8ef7dd2733c8502c49d1d1323f1ae664e82e8a5e14978d52ab448ba9b1afc78f06c8cd17415a17"; + // 0xa6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf135384 + let hex_str = "504e41550100000003b801000000040d02cfb65be46a822da31643f7bcce6c632943a0c9bab25872525c640ec1d803832936e560a4d6344c1e98f8fe2714281f882b25bc3e1f5fd3db51a9eae49afe2fb401030f87f6065777bffb27594235323e6680ec24b953ec71f0e0e3708ff28986613b2351aba32024ea226585f90c0a2680ee40786a1978b99596af6c281a947b87bc010492608d288aeaa7393d625111da17271178e739392ac87334b01388897612e63176754d044740d80f8f5511cb92c9925592ca223f8eeea70153ccac10f4f2faee0106944014d7f07eb277fe6cd5b4adf65ec547971ab43cb56a507cb974a059cf5b623e838cb899b2401efdac1d352aabab8d4c737519611f4ae22be6addfb254002c0108e9a6f762c22e639302495af910bf305269f38460360ef32cc4e5b1369d599bd33343d56a8e9b392a8cabfd9fc31bbfa32c03433d14f7b7ecdb1975e9759f3e51010ab4322508f35ca75320715c91ed4bf1b07e89730452e3c25b78469646ea61cdc27e52030b2baed4c91b24c3fa13816d95f70095d6c6cc2e110aaf92bab7e816f7010b48e2366bfbccd42b6b790d618bbe3bae90507be6ffda461a7a42ae19f674e16d359c5743540557f401ba7d33f401d5489d0933c3c6253528d1f9c7df3c51dc11010dcc82f54596cd7fc8dac985d4d1bbe410c82f1846891077783ea8da7522725f4621b51d2c09c008826e70f5b52098669d9a872de47d8544867dc381be6e10af04000eb240c11adf380871403a158fc5ce4482629ae1ca2e8b3ab6e7cf02e20435400d065a6399a49ede59db3ad05e56d5f4d4cfcc2419ce2b21dfd2c6dc955539c66d010f93cf0f4e8db5c1467b72c4d188c3c84faeab9dcb5936c2a4a8e80c8bebe8cbc75bd757243af5f109824e5afb1cafd6995c587cc0714fff8c92b1c109a7f4644d0110a89b1ccda9b5e70e823c259ef21b09ef5dcb3e0599033abdf179292f7395e62f3aead157a15d95377dfec08656f6b3f71e68e7ea8da185c38c6f84e50beb79a8001129768f9183799b4bad3b96953ea53b7ee269cd6763b56051b0fcd93e9f6b62325a811abc9d8d401c3971b864a4a9e1910f3ad6980fccd078b36399df46bd291c00121e92dc37fb15d26237137088d139d6decbb1e1d08a7e2a2acf92eaef9cbd7a93738c702ca0faa0b7764b5a28b4ffe41febeb9c2d62706844e69831b58902d88000686fff8a00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008976df2014155575600000000000da4fc6f000027106529465b0781218c3d940e5d11cebff0bc1ef15401005500a6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf1353840000000000635f4b0000000000005117fffffff800000000686fff8a00000000686fff8a00000000006202080000000000003a710c1957911bdae55923e5b2b971a731b10567748efab84bef5a2e1ac6fbfadd24896a2d225c77a260f03b06cb7c34c3ed018d02ac3fc7eefa997ffc91dc3b50508fb4b11c6c7768bc93dd3abeaf5d912ff895be0136780546fbb2f8b42855afa013d38675d811ab3118f0b0eea13dfd64ceb16649747908f859be12f8116922a05ad919df2bca555e5177ec70ab85278ad68b057e6fdbfe50bc38c3796c43199310760180b3253701d4293f32dc1e9be7f10dc3bf2bbf60ffe85b4e0b9ba3cf87af3c472f5d0372cdec973f4ddee17e89255df7d55bea696c64c7af6e8efd51ec79d1870f4d19bc4d5c65f24bdccd341a7d"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); vec![bytes] } #[cfg(test)] pub fn good_update2() -> Vec> { - let hex_str = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; + // 0x879551021853eec7a7dc827578e8e69da7e4fa8148339aa0d3d5296405be4b1a + let hex_str = "504e41550100000003b801000000040d02f11b16ae1042344f3c25ea3738f48985f16ed9ebd2e6a21c0b3ffc95f47f327d0afabe6c452b65ec71345b84ce9efb933c77c76285cb43c50bdc7c8fb6bef58d01036c7d8bb399c1fb786cf9ead5d604d4b8739f477a5d4a543ebec13309cdbc28926b4b1a84447431e818576f129d3c1a0ac42f43a4f6cc98607a8ab9173116714c0004181f8d49fc6d4943ccce639da721ccea067e049cf2be33dd4c002ec2185993ba498006814f42b8d949fe95fa1c39ff899ba694902ca0d60ad2cd6fbd86642bea0006f9bc4c86125c6ea76637f4e9afb7c4fe1b1f7669ad6454f7af2b1964cc11b7ec754709960ac5eb9165bba6e54482c9c2835ee67c99f4a1694cdfd05811c0c3630108b6805cd6aa6bd074a59bfe68c10016bc7acbbf61ca936fa1356ecd782493cb4f328964a4055938c77646cbdfc7487be8ded6f285507ba986f39c3aa584be3b67010a32f69176149696a1b503434c25456a41dd86582c5f07f305d9f3b4b14cb3eea30045e996df3fc016e9213e19b5f19a2896cd8c9aa52ba931cedc6f3672c92856010b55e38782b6df0e1926ac5708e0bd60a7f85fee986d54544a683eb8d0855d1d485a96066f773951d6106098bfdcb1c1ae8165bed84126a539747633c87a9e6cb0010d5b5d539c768dba043adb35641b5e7b1e14e30ddd3db2cb4d3b2ee540c5db04cd0eb244ad60a9fbeac1555795c1377e2e3cae70961e241e499a3404cdc7358662000e553a69464feeceb4be71ac5a55b50f60403f23d764debfdc002edef342c927fc6c161a4eebc6be95e014fcda76c537335578228a206b9715b57747a65335b9bd010f3e4971a7f76c0e7160bf236d9a62af81735f25f4067a31b836c36608073b1cf35c918b30c03bbfa2c30de7ad54bcd293f38ac7d360efe9079590e81d2b4affe00110d812f635ebb9b8faeee88405469a554792ddfcc3353137cb820c361c893b2fbf4a7a308194be096e00d3514449c3bbf3c638a6af92962cc30bf0323f0dd535cb011106306af0986424de86434aaba4dc11ce5e08a1a15722c065dd4fa3ca36d5859230e0eea8cc74b31fc024f025956121374f1354a742eec364cdf0079ca885e39d01123dc4dcfae02cbd590e7a4d85a07b2e9a107ffb95fed0c843af6b7b884eddc8d05925fb1d271e2bf3c41970d9f83bfd82cefc7d45c143c7595352ced45bd2dbef016870024200000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000897747c014155575600000000000da502fd000027102a19430cad54e27fe63ae1b4743caf77ff8ed43c01005500a6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf135384000000000062b4b2000000000000324bfffffff8000000006870024200000000687002420000000000622f4000000000000038df0c1c5dde54a2e342f851ea9041fafbcd1a6b2f1b76d54a4216f1d2441a666411a011ce756da5bf4b599f7faa456612f1810aa04f46e929405e6fd2704f4576157213ea8a656558e788f104832754f6ae3a5481df8f57e0f6ab6043a5de784086ce07e102ad4eb961b86e29ede26cd32a7dff934b7848844b4eb75f109aa8f1c411a4d2c23413fdb33a260c913c3a35df074c9a2590e8513caa5400001c9abe9a640200d17f8f18307c1d757dd8fa7aac510b57ed3cccaeba6f36e6ea506783cfb726d644e8b9f19dcb1e599ced6dd9a3b558e83124753869d08dd7fa85568c579dc2a4b9bdd0e7876994aa18cc8a57eecb"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); vec![bytes] } +#[cfg(test)] +pub fn good_update1_feed_id() -> [u8; 32] { + let hex_string = "a6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf135384"; + let bytes_vec = hex::decode(hex_string).expect("Invalid hex string"); + + let byte_array: [u8; 32] = bytes_vec + .try_into() + .expect("Hex string must decode to exactly 32 bytes"); + byte_array +} + #[cfg(test)] pub fn multiple_updates_same_vaa() -> Vec> { let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; @@ -58,12 +71,24 @@ pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2 #[cfg(test)] pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { ( - U64::from(1751563000u64), + U64::from(1752170378u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(6512459i64.to_le_bytes()), + U64::from(20759u64), + I64::from_le_bytes(6423048i64.to_le_bytes()), + U64::from(14961u64), + ) +} + +#[cfg(test)] +pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { + ( + U64::from(1752171074u64), I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(10967241867779i64.to_le_bytes()), - U64::from(4971244966u64), - I64::from_le_bytes(10942391100000i64.to_le_bytes()), - U64::from(4398561400u64), + I64::from_le_bytes(6468786i64.to_le_bytes()), + U64::from(12875u64), + I64::from_le_bytes(6434624i64.to_le_bytes()), + U64::from(14559u64), ) } @@ -89,18 +114,6 @@ pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { ] } -#[cfg(test)] -pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { - ( - U64::from(1751573860u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(10985663592646i64.to_le_bytes()), - U64::from(4569386330u64), - I64::from_le_bytes(10977795800000i64.to_le_bytes()), - U64::from(3919318300u64), - ) -} - #[cfg(test)] pub fn current_guardians() -> Vec
{ vec![ From 7f154bafedae2dc66b781b10f903240a3e19573a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 18:50:14 +0000 Subject: [PATCH 23/37] feat: modify PriceFeedReturn to use byte arrays and simplify parsing logic - Change PriceFeedReturn from hex string to byte array ([u8; 32], U64, I32, I64, U64, I64, U64) - Update parse_price_feed_updates_internal to return Vec instead of Vec<([u8; 32], PriceFeedReturn)> - Simplify parse_price_feed_updates_with_config by removing price_map and using direct indexing with find() - Simplify update_price_feeds_internal to avoid dual variable iteration and directly index structs - Update test data functions to use byte arrays from helper functions instead of hex strings - Add helper functions for different feed IDs used in multiple updates tests All tests pass successfully with these changes. Co-Authored-By: ayush.suresh@dourolabs.xyz --- .../stylus/contracts/pyth-receiver/src/lib.rs | 54 +++++++++---------- .../contracts/pyth-receiver/src/structs.rs | 2 +- .../contracts/pyth-receiver/src/test_data.rs | 41 ++++++++++++-- 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index cc404cf8c5..da088d0930 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -121,6 +121,7 @@ impl PythReceiver { } Ok(( + id, price_info.publish_time.get(), price_info.expo.get(), price_info.price.get(), @@ -136,7 +137,7 @@ impl PythReceiver { age: u64, ) -> Result { let price_info = self.get_price_unsafe(id)?; - if !self.is_no_older_than(price_info.0, age) { + if !self.is_no_older_than(price_info.1, age) { return Err(PythReceiverError::NewPriceUnavailable); } Ok(price_info) @@ -151,6 +152,7 @@ impl PythReceiver { } Ok(( + id, price_info.publish_time.get(), price_info.expo.get(), price_info.ema_price.get(), @@ -166,7 +168,7 @@ impl PythReceiver { age: u64, ) -> Result { let price_info = self.get_ema_price_unsafe(id)?; - if !self.is_no_older_than(price_info.0, age) { + if !self.is_no_older_than(price_info.1, age) { return Err(PythReceiverError::NewPriceUnavailable); } Ok(price_info) @@ -226,31 +228,31 @@ impl PythReceiver { min_publish_time: u64, max_publish_time: u64, _unique: bool, - ) -> Result, PythReceiverError> { - let price_pairs = self.parse_price_feed_updates_internal( + ) -> Result, PythReceiverError> { + let price_feeds = self.parse_price_feed_updates_internal( update_data, min_publish_time, max_publish_time, false, // check_uniqueness )?; - for (price_id, price_return) in price_pairs.clone() { - let price_id_fb: FixedBytes<32> = FixedBytes::from(price_id); + for price_return in &price_feeds { + let price_id_fb: FixedBytes<32> = FixedBytes::from(price_return.0); let mut recent_price_info = self.latest_price_info.setter(price_id_fb); - if recent_price_info.publish_time.get() < price_return.0 + if recent_price_info.publish_time.get() < price_return.1 || recent_price_info.price.get() == I64::ZERO { - recent_price_info.publish_time.set(price_return.0); - recent_price_info.expo.set(price_return.1); - recent_price_info.price.set(price_return.2); - recent_price_info.conf.set(price_return.3); - recent_price_info.ema_price.set(price_return.4); - recent_price_info.ema_conf.set(price_return.5); + recent_price_info.publish_time.set(price_return.1); + recent_price_info.expo.set(price_return.2); + recent_price_info.price.set(price_return.3); + recent_price_info.conf.set(price_return.4); + recent_price_info.ema_price.set(price_return.5); + recent_price_info.ema_conf.set(price_return.6); } } - Ok(price_pairs) + Ok(price_feeds) } fn get_update_fee(&self, update_data: Vec>) -> Result { @@ -308,10 +310,10 @@ impl PythReceiver { check_update_data_is_minimal: bool, store_updates_if_fresh: bool, ) -> Result, PythReceiverError> { - let mut all_parsed_price_pairs = Vec::new(); + let mut all_parsed_price_feeds = Vec::new(); for data in &update_data { if store_updates_if_fresh { - all_parsed_price_pairs.extend(self.update_price_feeds_internal( + all_parsed_price_feeds.extend(self.update_price_feeds_internal( data.clone(), price_ids.clone(), min_allowed_publish_time, @@ -319,7 +321,7 @@ impl PythReceiver { check_uniqueness, )?); } else { - all_parsed_price_pairs.extend(self.parse_price_feed_updates_internal( + all_parsed_price_feeds.extend(self.parse_price_feed_updates_internal( data.clone(), min_allowed_publish_time, max_allowed_publish_time, @@ -328,22 +330,15 @@ impl PythReceiver { } } - if check_update_data_is_minimal && all_parsed_price_pairs.len() != price_ids.len() { + if check_update_data_is_minimal && all_parsed_price_feeds.len() != price_ids.len() { return Err(PythReceiverError::InvalidUpdateData); } let mut result: Vec = Vec::with_capacity(price_ids.len()); - let mut price_map: BTreeMap<[u8; 32], PriceFeedReturn> = BTreeMap::new(); - - for (price_id, price_info) in all_parsed_price_pairs { - if !price_map.contains_key(&price_id) { - price_map.insert(price_id, price_info); - } - } for price_id in price_ids { - if let Some(price_info) = price_map.get(&price_id) { - result.push(*price_info); + if let Some(price_info) = all_parsed_price_feeds.iter().find(|feed| feed.0 == price_id) { + result.push(price_info.clone()); } else { return Err(PythReceiverError::PriceFeedNotFoundWithinRange); } @@ -358,7 +353,7 @@ impl PythReceiver { min_allowed_publish_time: u64, max_allowed_publish_time: u64, check_uniqueness: bool, - ) -> Result, PythReceiverError> { + ) -> Result, PythReceiverError> { let update_data_array: &[u8] = &update_data; // Check the first 4 bytes of the update_data_array for the magic header if update_data_array.len() < 4 { @@ -444,6 +439,7 @@ impl PythReceiver { } let price_info_return = ( + price_feed_message.feed_id, U64::from(publish_time), I32::from_be_bytes(price_feed_message.exponent.to_be_bytes()), I64::from_be_bytes(price_feed_message.price.to_be_bytes()), @@ -462,7 +458,7 @@ impl PythReceiver { } }; - Ok(price_feeds.into_iter().collect()) + Ok(price_feeds.into_iter().map(|(_, price_info)| price_info).collect()) } pub fn parse_twap_price_feed_updates( diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index 3f804725db..3adb8ede19 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -57,7 +57,7 @@ pub struct PriceInfoStorage { // pub ema_conf: U64, // } -pub type PriceFeedReturn = (U64, I32, I64, U64, I64, U64); +pub type PriceFeedReturn = ([u8; 32], U64, I32, I64, U64, I64, U64); pub type PriceReturn = ([u8; 32], U64, I32, I64, U64, I64, U64); diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index c241bc8da4..009459e5a9 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -29,6 +29,33 @@ pub fn good_update1_feed_id() -> [u8; 32] { byte_array } +#[cfg(test)] +pub fn multiple_updates_first_feed_id() -> [u8; 32] { + [ + 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6, 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, 0xe5, + 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, + 0x5b, 0x43, + ] +} + +#[cfg(test)] +pub fn multiple_updates_second_feed_id() -> [u8; 32] { + [ + 0xff, 0x61, 0x49, 0x1a, 0x93, 0x11, 0x12, 0xdd, 0xf1, 0xbd, 0x81, 0x47, 0xcd, 0x1b, + 0x64, 0x13, 0x75, 0xf7, 0x9f, 0x58, 0x25, 0x12, 0x6d, 0x66, 0x54, 0x80, 0x87, 0x46, + 0x34, 0xfd, 0x0a, 0xce, + ] +} + +#[cfg(test)] +pub fn multiple_updates_diff_first_feed_id() -> [u8; 32] { + [ + 0x3f, 0xa4, 0x25, 0x28, 0x48, 0xf9, 0xf0, 0xa1, 0x48, 0x0b, 0xe6, 0x27, 0x45, 0xa4, + 0x62, 0x9d, 0x9e, 0xb1, 0x32, 0x2a, 0xeb, 0xab, 0x8a, 0x79, 0x1e, 0x34, 0x4b, 0x3b, + 0x9c, 0x1a, 0xdc, 0xf5, + ] +} + #[cfg(test)] pub fn multiple_updates_same_vaa() -> Vec> { let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; @@ -47,9 +74,10 @@ pub fn multiple_updates_diff_vaa() -> Vec> { } #[cfg(test)] -pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2] { +pub fn multiple_updates_diff_vaa_results() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { [ ( + multiple_updates_diff_first_feed_id(), U64::from(1752094858u64), I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(35134945i64.to_le_bytes()), @@ -58,6 +86,7 @@ pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2 U64::from(35632u64), ), ( + multiple_updates_first_feed_id(), U64::from(1751573860u64), I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(10985663592646i64.to_le_bytes()), @@ -69,8 +98,9 @@ pub fn multiple_updates_diff_vaa_results() -> [(U64, I32, I64, U64, I64, U64); 2 } #[cfg(test)] -pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { +pub fn good_update1_results() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { ( + good_update1_feed_id(), U64::from(1752170378u64), I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(6512459i64.to_le_bytes()), @@ -81,8 +111,9 @@ pub fn good_update1_results() -> (U64, I32, I64, U64, I64, U64) { } #[cfg(test)] -pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { +pub fn good_update2_results() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { ( + good_update1_feed_id(), U64::from(1752171074u64), I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(6468786i64.to_le_bytes()), @@ -93,9 +124,10 @@ pub fn good_update2_results() -> (U64, I32, I64, U64, I64, U64) { } #[cfg(test)] -pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { +pub fn multiple_updates_results() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { [ ( + multiple_updates_first_feed_id(), U64::from(1751573123u64), I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(10990356724259i64.to_le_bytes()), @@ -104,6 +136,7 @@ pub fn multiple_updates_results() -> [(U64, I32, I64, U64, I64, U64); 2] { U64::from(3918344000u64), ), ( + multiple_updates_second_feed_id(), U64::from(1751573123u64), I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(258906787480i64.to_le_bytes()), From 9fe481a819693baa72630ccb9813baa69ebc8688 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 18:54:17 +0000 Subject: [PATCH 24/37] feat: add price_id field to PriceFeedStorage and rename from PriceInfoStorage - Add price_id field as StorageFixedBytes<32> to PriceInfoStorage struct - Rename PriceInfoStorage to PriceFeedStorage for consistency - Update import statement and storage map declaration in lib.rs - Update update_price_feeds_internal to populate price_id field when storing price data - All tests pass with structural changes Co-Authored-By: ayush.suresh@dourolabs.xyz --- target_chains/stylus/contracts/pyth-receiver/src/lib.rs | 5 +++-- target_chains/stylus/contracts/pyth-receiver/src/structs.rs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index da088d0930..b8cc4a7523 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -39,7 +39,7 @@ use pythnet_sdk::{ }, }, }; -use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceInfoStorage}; +use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceFeedStorage}; use wormhole_vaas::{Readable, Vaa, Writeable}; sol_interface! { @@ -63,7 +63,7 @@ pub struct PythReceiver { pub governance_data_source_emitter_address: StorageFixedBytes<32>, pub last_executed_governance_sequence: StorageUint<64, 1>, pub governance_data_source_index: StorageUint<32, 1>, - pub latest_price_info: StorageMap, PriceInfoStorage>, + pub latest_price_info: StorageMap, PriceFeedStorage>, pub transaction_fee_in_wei: StorageU256, } @@ -243,6 +243,7 @@ impl PythReceiver { if recent_price_info.publish_time.get() < price_return.1 || recent_price_info.price.get() == I64::ZERO { + recent_price_info.price_id.set(FixedBytes::from(price_return.0)); recent_price_info.publish_time.set(price_return.1); recent_price_info.expo.set(price_return.2); recent_price_info.price.set(price_return.3); diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index 3adb8ede19..da2ee9328e 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -36,7 +36,8 @@ impl StorageKey for DataSource { } } #[storage] -pub struct PriceInfoStorage { +pub struct PriceFeedStorage { + pub price_id: StorageFixedBytes<32>, pub publish_time: StorageU64, pub expo: StorageI32, pub price: StorageI64, From a253ae99a9602c40638febe355740984744e4e07 Mon Sep 17 00:00:00 2001 From: Connor Prussin Date: Thu, 10 Jul 2025 12:06:09 -0700 Subject: [PATCH 25/37] chore: update blocksize logo --- .../known-publishers/src/icons/monochrome/blocksize.svg | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/known-publishers/src/icons/monochrome/blocksize.svg b/packages/known-publishers/src/icons/monochrome/blocksize.svg index 1fd16271e3..deb2eb6b4b 100644 --- a/packages/known-publishers/src/icons/monochrome/blocksize.svg +++ b/packages/known-publishers/src/icons/monochrome/blocksize.svg @@ -1,6 +1,5 @@ - - - - - + + + + From 1aee40ced8999d31d0eab15f883538c27543784b Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 14:07:22 -0500 Subject: [PATCH 26/37] finished adding id back to return type, distinguishing between get return type and the overall price feed return that will be given from the query function --- .../pyth-receiver/src/integration_tests.rs | 14 +-- .../stylus/contracts/pyth-receiver/src/lib.rs | 24 ++-- .../contracts/pyth-receiver/src/structs.rs | 3 +- .../contracts/pyth-receiver/src/test_data.rs | 104 ++++++++++++++---- 4 files changed, 98 insertions(+), 47 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index a5e747c262..058a96b604 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -118,7 +118,7 @@ mod test { .sender(alice) .get_price_unsafe(good_update1_feed_id()); assert!(price_result.is_ok()); - assert_eq!(price_result.unwrap(), good_update1_results()); + assert_eq!(price_result.unwrap(), good_update1_results_get_price()); } #[motsu::test] @@ -172,7 +172,7 @@ mod test { .sender(alice) .get_price_unsafe(good_update1_feed_id()); assert!(price_result.is_ok()); - assert_eq!(price_result.unwrap(), good_update2_results()); + assert_eq!(price_result.unwrap(), good_update2_results_get_price()); } #[motsu::test] @@ -241,7 +241,7 @@ mod test { .sender(alice) .get_price_no_older_than(good_update1_feed_id(), u64::MAX); assert!(price_result.is_ok()); - assert_eq!(price_result.unwrap(), good_update2_results()); + assert_eq!(price_result.unwrap(), good_update2_results_get_price()); } #[motsu::test] @@ -304,11 +304,11 @@ mod test { let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); assert!(first_price_result.is_ok()); - assert_eq!(first_price_result.unwrap(), multiple_updates_results()[0]); + assert_eq!(first_price_result.unwrap(), multiple_updates_results_get_price()[0]); let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); assert!(second_price_result.is_ok()); - assert_eq!(second_price_result.unwrap(), multiple_updates_results()[1]); + assert_eq!(second_price_result.unwrap(), multiple_updates_results_get_price()[1]); } #[motsu::test] @@ -340,14 +340,14 @@ mod test { assert!(first_price_result.is_ok()); assert_eq!( first_price_result.unwrap(), - multiple_updates_diff_vaa_results()[0] + multiple_updates_diff_vaa_results_get_price()[0] ); let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); assert!(second_price_result.is_ok()); assert_eq!( second_price_result.unwrap(), - multiple_updates_diff_vaa_results()[1] + multiple_updates_diff_vaa_results_get_price()[1] ); } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index b8cc4a7523..8045093795 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -39,7 +39,7 @@ use pythnet_sdk::{ }, }, }; -use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceFeedStorage}; +use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceInfoStorage, PriceReturn}; use wormhole_vaas::{Readable, Vaa, Writeable}; sol_interface! { @@ -111,7 +111,7 @@ impl PythReceiver { } } - pub fn get_price_unsafe(&self, id: [u8; 32]) -> Result { + pub fn get_price_unsafe(&self, id: [u8; 32]) -> Result { let id_fb = FixedBytes::<32>::from(id); let price_info = self.latest_price_info.get(id_fb); @@ -121,13 +121,10 @@ impl PythReceiver { } Ok(( - id, - price_info.publish_time.get(), - price_info.expo.get(), price_info.price.get(), price_info.conf.get(), - price_info.ema_price.get(), - price_info.ema_conf.get(), + price_info.expo.get(), + price_info.publish_time.get(), )) } @@ -135,7 +132,7 @@ impl PythReceiver { &self, id: [u8; 32], age: u64, - ) -> Result { + ) -> Result { let price_info = self.get_price_unsafe(id)?; if !self.is_no_older_than(price_info.1, age) { return Err(PythReceiverError::NewPriceUnavailable); @@ -143,7 +140,7 @@ impl PythReceiver { Ok(price_info) } - pub fn get_ema_price_unsafe(&self, id: [u8; 32]) -> Result { + pub fn get_ema_price_unsafe(&self, id: [u8; 32]) -> Result { let id_fb = FixedBytes::<32>::from(id); let price_info = self.latest_price_info.get(id_fb); @@ -152,13 +149,10 @@ impl PythReceiver { } Ok(( - id, - price_info.publish_time.get(), - price_info.expo.get(), - price_info.ema_price.get(), - price_info.ema_conf.get(), price_info.ema_price.get(), price_info.ema_conf.get(), + price_info.expo.get(), + price_info.publish_time.get(), )) } @@ -166,7 +160,7 @@ impl PythReceiver { &self, id: [u8; 32], age: u64, - ) -> Result { + ) -> Result { let price_info = self.get_ema_price_unsafe(id)?; if !self.is_no_older_than(price_info.1, age) { return Err(PythReceiverError::NewPriceUnavailable); diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index da2ee9328e..e405994171 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -60,7 +60,8 @@ pub struct PriceFeedStorage { pub type PriceFeedReturn = ([u8; 32], U64, I32, I64, U64, I64, U64); -pub type PriceReturn = ([u8; 32], U64, I32, I64, U64, I64, U64); +// (price, conf, expo, publish_time) +pub type PriceReturn = (I64, U64, I32, U64); #[cfg(test)] mod tests { diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index 009459e5a9..1a4015b5d4 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -74,7 +74,57 @@ pub fn multiple_updates_diff_vaa() -> Vec> { } #[cfg(test)] -pub fn multiple_updates_diff_vaa_results() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { +pub fn good_update1_results_full() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { + ( + good_update1_feed_id(), + U64::from(1752170378u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(6512459i64.to_le_bytes()), + U64::from(20759u64), + I64::from_le_bytes(6423048i64.to_le_bytes()), + U64::from(14961u64), + ) +} + +#[cfg(test)] +pub fn good_update2_results_full() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { + ( + good_update1_feed_id(), + U64::from(1752171074u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(6468786i64.to_le_bytes()), + U64::from(12875u64), + I64::from_le_bytes(6434624i64.to_le_bytes()), + U64::from(14559u64), + ) +} + +#[cfg(test)] +pub fn multiple_updates_results_full() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { + [ + ( + multiple_updates_first_feed_id(), + U64::from(1751573123u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(10990356724259i64.to_le_bytes()), + U64::from(3891724259u64), + I64::from_le_bytes(10974970400000i64.to_le_bytes()), + U64::from(3918344000u64), + ), + ( + multiple_updates_second_feed_id(), + U64::from(1751573123u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + I64::from_le_bytes(258906787480i64.to_le_bytes()), + U64::from(158498649u64), + I64::from_le_bytes(258597182000i64.to_le_bytes()), + U64::from(131285914u64), + ), + ] +} + +#[cfg(test)] +pub fn multiple_updates_diff_vaa_results_full() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { [ ( multiple_updates_diff_first_feed_id(), @@ -98,51 +148,57 @@ pub fn multiple_updates_diff_vaa_results() -> [([u8; 32], U64, I32, I64, U64, I6 } #[cfg(test)] -pub fn good_update1_results() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { +pub fn good_update1_results_get_price() -> (I64, U64, I32, U64) { ( - good_update1_feed_id(), - U64::from(1752170378u64), - I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(6512459i64.to_le_bytes()), U64::from(20759u64), - I64::from_le_bytes(6423048i64.to_le_bytes()), - U64::from(14961u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + U64::from(1752170378u64), ) } #[cfg(test)] -pub fn good_update2_results() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { +pub fn good_update2_results_get_price() -> (I64, U64, I32, U64) { ( - good_update1_feed_id(), - U64::from(1752171074u64), - I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(6468786i64.to_le_bytes()), U64::from(12875u64), - I64::from_le_bytes(6434624i64.to_le_bytes()), - U64::from(14559u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + U64::from(1752171074u64), ) } #[cfg(test)] -pub fn multiple_updates_results() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { +pub fn multiple_updates_results_get_price() -> [(I64, U64, I32, U64); 2] { [ ( - multiple_updates_first_feed_id(), - U64::from(1751573123u64), - I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(10990356724259i64.to_le_bytes()), U64::from(3891724259u64), - I64::from_le_bytes(10974970400000i64.to_le_bytes()), - U64::from(3918344000u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + U64::from(1751573123u64), ), ( - multiple_updates_second_feed_id(), - U64::from(1751573123u64), - I32::from_le_bytes((-8i32).to_le_bytes()), I64::from_le_bytes(258906787480i64.to_le_bytes()), U64::from(158498649u64), - I64::from_le_bytes(258597182000i64.to_le_bytes()), - U64::from(131285914u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + U64::from(1751573123u64), + ), + ] +} + +#[cfg(test)] +pub fn multiple_updates_diff_vaa_results_get_price() -> [(I64, U64, I32, U64); 2] { + [ + ( + I64::from_le_bytes(35134945i64.to_le_bytes()), + U64::from(36279u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + U64::from(1752094858u64), + ), + ( + I64::from_le_bytes(10985663592646i64.to_le_bytes()), + U64::from(4569386330u64), + I32::from_le_bytes((-8i32).to_le_bytes()), + U64::from(1751573860u64), ), ] } From 1c82321f4022014918e400fe0fbf61e47a0e9d48 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 14:07:38 -0500 Subject: [PATCH 27/37] format --- .../pyth-receiver/src/integration_tests.rs | 10 ++++++++-- .../stylus/contracts/pyth-receiver/src/lib.rs | 14 +++++++++++--- .../contracts/pyth-receiver/src/test_data.rs | 12 ++++++------ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index 058a96b604..dff2e3571b 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -304,11 +304,17 @@ mod test { let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); assert!(first_price_result.is_ok()); - assert_eq!(first_price_result.unwrap(), multiple_updates_results_get_price()[0]); + assert_eq!( + first_price_result.unwrap(), + multiple_updates_results_get_price()[0] + ); let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); assert!(second_price_result.is_ok()); - assert_eq!(second_price_result.unwrap(), multiple_updates_results_get_price()[1]); + assert_eq!( + second_price_result.unwrap(), + multiple_updates_results_get_price()[1] + ); } #[motsu::test] diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 8045093795..ebaf925616 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -237,7 +237,9 @@ impl PythReceiver { if recent_price_info.publish_time.get() < price_return.1 || recent_price_info.price.get() == I64::ZERO { - recent_price_info.price_id.set(FixedBytes::from(price_return.0)); + recent_price_info + .price_id + .set(FixedBytes::from(price_return.0)); recent_price_info.publish_time.set(price_return.1); recent_price_info.expo.set(price_return.2); recent_price_info.price.set(price_return.3); @@ -332,7 +334,10 @@ impl PythReceiver { let mut result: Vec = Vec::with_capacity(price_ids.len()); for price_id in price_ids { - if let Some(price_info) = all_parsed_price_feeds.iter().find(|feed| feed.0 == price_id) { + if let Some(price_info) = all_parsed_price_feeds + .iter() + .find(|feed| feed.0 == price_id) + { result.push(price_info.clone()); } else { return Err(PythReceiverError::PriceFeedNotFoundWithinRange); @@ -453,7 +458,10 @@ impl PythReceiver { } }; - Ok(price_feeds.into_iter().map(|(_, price_info)| price_info).collect()) + Ok(price_feeds + .into_iter() + .map(|(_, price_info)| price_info) + .collect()) } pub fn parse_twap_price_feed_updates( diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index 1a4015b5d4..47a52e7017 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -41,18 +41,18 @@ pub fn multiple_updates_first_feed_id() -> [u8; 32] { #[cfg(test)] pub fn multiple_updates_second_feed_id() -> [u8; 32] { [ - 0xff, 0x61, 0x49, 0x1a, 0x93, 0x11, 0x12, 0xdd, 0xf1, 0xbd, 0x81, 0x47, 0xcd, 0x1b, - 0x64, 0x13, 0x75, 0xf7, 0x9f, 0x58, 0x25, 0x12, 0x6d, 0x66, 0x54, 0x80, 0x87, 0x46, - 0x34, 0xfd, 0x0a, 0xce, + 0xff, 0x61, 0x49, 0x1a, 0x93, 0x11, 0x12, 0xdd, 0xf1, 0xbd, 0x81, 0x47, 0xcd, 0x1b, 0x64, + 0x13, 0x75, 0xf7, 0x9f, 0x58, 0x25, 0x12, 0x6d, 0x66, 0x54, 0x80, 0x87, 0x46, 0x34, 0xfd, + 0x0a, 0xce, ] } #[cfg(test)] pub fn multiple_updates_diff_first_feed_id() -> [u8; 32] { [ - 0x3f, 0xa4, 0x25, 0x28, 0x48, 0xf9, 0xf0, 0xa1, 0x48, 0x0b, 0xe6, 0x27, 0x45, 0xa4, - 0x62, 0x9d, 0x9e, 0xb1, 0x32, 0x2a, 0xeb, 0xab, 0x8a, 0x79, 0x1e, 0x34, 0x4b, 0x3b, - 0x9c, 0x1a, 0xdc, 0xf5, + 0x3f, 0xa4, 0x25, 0x28, 0x48, 0xf9, 0xf0, 0xa1, 0x48, 0x0b, 0xe6, 0x27, 0x45, 0xa4, 0x62, + 0x9d, 0x9e, 0xb1, 0x32, 0x2a, 0xeb, 0xab, 0x8a, 0x79, 0x1e, 0x34, 0x4b, 0x3b, 0x9c, 0x1a, + 0xdc, 0xf5, ] } From 9bd142de873722da6ad46bfa022924881ac35cfd Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 14:10:35 -0500 Subject: [PATCH 28/37] fixed import name error --- target_chains/stylus/contracts/pyth-receiver/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index ebaf925616..f74aa1d2d9 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -39,7 +39,7 @@ use pythnet_sdk::{ }, }, }; -use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceInfoStorage, PriceReturn}; +use structs::{DataSource, DataSourceStorage, PriceFeedReturn, PriceFeedStorage, PriceReturn}; use wormhole_vaas::{Readable, Vaa, Writeable}; sol_interface! { From fce2f107e77b773ed6066572a7e8371fba31ea1e Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 14:17:54 -0500 Subject: [PATCH 29/37] finished query price feeds function --- .../contracts/pyth-receiver/src/error.rs | 2 + .../stylus/contracts/pyth-receiver/src/lib.rs | 38 ++++++++++++++----- .../contracts/pyth-receiver/src/structs.rs | 2 +- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/error.rs b/target_chains/stylus/contracts/pyth-receiver/src/error.rs index 4b68178d80..caeb2e4922 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/error.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/error.rs @@ -19,6 +19,7 @@ pub enum PythReceiverError { TooManyUpdates, PriceFeedNotFoundWithinRange, NoFreshUpdate, + PriceFeedNotFound, } impl core::fmt::Debug for PythReceiverError { @@ -47,6 +48,7 @@ impl From for Vec { PythReceiverError::TooManyUpdates => 15, PythReceiverError::PriceFeedNotFoundWithinRange => 16, PythReceiverError::NoFreshUpdate => 17, + PythReceiverError::PriceFeedNotFound => 18, }] } } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index f74aa1d2d9..caeb637a32 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -15,7 +15,7 @@ mod test_data; #[cfg(test)] use mock_instant::global::MockClock; -use alloc::{collections::BTreeMap, vec::Vec}; +use alloc::vec::Vec; use stylus_sdk::{ alloy_primitives::{Address, FixedBytes, I32, I64, U16, U256, U32, U64}, call::Call, @@ -111,6 +111,26 @@ impl PythReceiver { } } + pub fn query_price_feed(&self, id: [u8; 32]) -> Result { + let id_fb = FixedBytes::<32>::from(id); + + let price_info = self.latest_price_info.get(id_fb); + + if price_info.publish_time.get() == U64::ZERO { + return Err(PythReceiverError::PriceUnavailable); + } + + Ok(( + id_fb, + price_info.publish_time.get(), + price_info.expo.get(), + price_info.price.get(), + price_info.conf.get(), + price_info.ema_price.get(), + price_info.ema_conf.get(), + )) + } + pub fn get_price_unsafe(&self, id: [u8; 32]) -> Result { let id_fb = FixedBytes::<32>::from(id); @@ -370,7 +390,7 @@ impl PythReceiver { let accumulator_update = AccumulatorUpdateData::try_from_slice(&update_data_array) .map_err(|_| PythReceiverError::InvalidAccumulatorMessage)?; - let mut price_feeds: BTreeMap<[u8; 32], PriceFeedReturn> = BTreeMap::new(); + let mut price_feeds = Vec::new(); match accumulator_update.proof { Proof::WormholeMerkle { vaa, updates } => { @@ -424,9 +444,10 @@ impl PythReceiver { return Err(PythReceiverError::PriceFeedNotFoundWithinRange); } + let price_id_fb = + FixedBytes::<32>::from(price_feed_message.feed_id); + if check_uniqueness { - let price_id_fb = - FixedBytes::<32>::from(price_feed_message.feed_id); let prev_price_info = self.latest_price_info.get(price_id_fb); let prev_publish_time = prev_price_info.publish_time.get().to::(); @@ -439,7 +460,7 @@ impl PythReceiver { } let price_info_return = ( - price_feed_message.feed_id, + price_id_fb, U64::from(publish_time), I32::from_be_bytes(price_feed_message.exponent.to_be_bytes()), I64::from_be_bytes(price_feed_message.price.to_be_bytes()), @@ -448,7 +469,7 @@ impl PythReceiver { U64::from(price_feed_message.ema_conf), ); - price_feeds.insert(price_feed_message.feed_id, price_info_return); + price_feeds.push(price_info_return); } _ => { return Err(PythReceiverError::InvalidAccumulatorMessageType); @@ -458,10 +479,7 @@ impl PythReceiver { } }; - Ok(price_feeds - .into_iter() - .map(|(_, price_info)| price_info) - .collect()) + Ok(price_feeds) } pub fn parse_twap_price_feed_updates( diff --git a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs index e405994171..a6bfdaa448 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/structs.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/structs.rs @@ -58,7 +58,7 @@ pub struct PriceFeedStorage { // pub ema_conf: U64, // } -pub type PriceFeedReturn = ([u8; 32], U64, I32, I64, U64, I64, U64); +pub type PriceFeedReturn = (FixedBytes<32>, U64, I32, I64, U64, I64, U64); // (price, conf, expo, publish_time) pub type PriceReturn = (I64, U64, I32, U64); From d6b04e065a9d63866591bef8acf48c0cf216fb5a Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 14:18:16 -0500 Subject: [PATCH 30/37] formatted --- target_chains/stylus/contracts/pyth-receiver/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index caeb637a32..fe8677d693 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -444,9 +444,8 @@ impl PythReceiver { return Err(PythReceiverError::PriceFeedNotFoundWithinRange); } - let price_id_fb = - FixedBytes::<32>::from(price_feed_message.feed_id); - + let price_id_fb = FixedBytes::<32>::from(price_feed_message.feed_id); + if check_uniqueness { let prev_price_info = self.latest_price_info.get(price_id_fb); let prev_publish_time = From fcd55c417314777827215dc64cdf0164d78a34c6 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 14:57:08 -0500 Subject: [PATCH 31/37] finished price_feed_exists method --- target_chains/stylus/contracts/pyth-receiver/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index fe8677d693..06fb5148a9 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -111,6 +111,12 @@ impl PythReceiver { } } + pub fn price_feed_exists(&self, id: [u8; 32]) -> bool { + let id_fb = FixedBytes::<32>::from(id); + let price_info = self.latest_price_info.get(id_fb); + return price_info.publish_time.get() != U64::ZERO; + } + pub fn query_price_feed(&self, id: [u8; 32]) -> Result { let id_fb = FixedBytes::<32>::from(id); From cef812a603c853a9c152091613217a1d7a4a9c68 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 16:21:30 -0500 Subject: [PATCH 32/37] trying to clean up testing framework --- .../pyth-receiver/src/integration_tests.rs | 159 +++++++++----- .../stylus/contracts/pyth-receiver/src/lib.rs | 6 +- .../contracts/pyth-receiver/src/test_data.rs | 198 +++++------------- 3 files changed, 159 insertions(+), 204 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index dff2e3571b..5db46977c5 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -104,7 +104,7 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = good_update1(); + let update_data = ban_usd_update(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -116,9 +116,9 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_unsafe(good_update1_feed_id()); + .get_price_unsafe(ban_usd_feed_id()); assert!(price_result.is_ok()); - assert_eq!(price_result.unwrap(), good_update1_results_get_price()); + assert_eq!(price_result.unwrap(), ban_usd_results_get_price()); } #[motsu::test] @@ -131,7 +131,7 @@ mod test { alice.fund(U256::from(200)); - let update_data = good_update1(); + let update_data = ban_usd_update(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); let small_update_fee = update_fee / U256::from(2); @@ -150,10 +150,10 @@ mod test { ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data1 = good_update1(); + let update_data1 = ban_usd_update(); let update_fee1 = mock_get_update_fee(update_data1.clone()).unwrap(); - let update_data2 = good_update2(); + let update_data2 = btc_usd_update(); let update_fee2 = mock_get_update_fee(update_data2.clone()).unwrap(); alice.fund(update_fee1 + update_fee2); @@ -170,9 +170,9 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_unsafe(good_update1_feed_id()); + .get_price_unsafe(ban_usd_feed_id()); assert!(price_result.is_ok()); - assert_eq!(price_result.unwrap(), good_update2_results_get_price()); + assert_eq!(price_result.unwrap(), ban_usd_results_get_price()); } #[motsu::test] @@ -185,7 +185,7 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_unsafe(good_update1_feed_id()); + .get_price_unsafe(ban_usd_feed_id()); assert!(price_result.is_err()); assert_eq!( price_result.unwrap_err(), @@ -227,7 +227,7 @@ mod test { MockClock::set_time(Duration::from_secs(1761573860)); pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = good_update2(); + let update_data = btc_usd_update(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -239,9 +239,9 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_no_older_than(good_update1_feed_id(), u64::MAX); + .get_price_no_older_than(btc_usd_feed_id(), u64::MAX); assert!(price_result.is_ok()); - assert_eq!(price_result.unwrap(), good_update2_results_get_price()); + assert_eq!(price_result.unwrap(), btc_usd_results_get_price()); } #[motsu::test] @@ -253,7 +253,7 @@ mod test { MockClock::set_time(Duration::from_secs(1761573860)); pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = good_update2(); + let update_data = btc_usd_update(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -265,7 +265,8 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_no_older_than(good_update1_feed_id(), 1); + .get_price_no_older_than(btc_usd_feed_id(), 1); + println!("Price result: {:?}", price_result); assert!(price_result.is_err()); assert_eq!( price_result.unwrap_err(), @@ -274,14 +275,14 @@ mod test { } #[motsu::test] - fn test_multiple_updates_in_same_vaa_different_ids_updates_both( + fn test_multiple_updates_different_ids_updates_both( pyth_contract: Contract, wormhole_contract: Contract, alice: Address, ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = multiple_updates_same_vaa(); + let update_data = multiple_updates_diff_vaa(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -291,41 +292,38 @@ mod test { .update_price_feeds(update_data); assert!(result.is_ok()); - let first_id: [u8; 32] = [ - 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6, 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, - 0xe5, 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, - 0x4a, 0x41, 0x5b, 0x43, - ]; - let second_id: [u8; 32] = [ - 0xff, 0x61, 0x49, 0x1a, 0x93, 0x11, 0x12, 0xdd, 0xf1, 0xbd, 0x81, 0x47, 0xcd, 0x1b, - 0x64, 0x13, 0x75, 0xf7, 0x9f, 0x58, 0x25, 0x12, 0x6d, 0x66, 0x54, 0x80, 0x87, 0x46, - 0x34, 0xfd, 0x0a, 0xce, - ]; - - let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); + let first_price_result = pyth_contract + .sender(alice) + .get_price_unsafe(ban_usd_feed_id()); assert!(first_price_result.is_ok()); assert_eq!( first_price_result.unwrap(), - multiple_updates_results_get_price()[0] + multiple_updates_diff_vaa_results_get_price()[0] ); - let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); + let second_price_result = pyth_contract + .sender(alice) + .get_price_unsafe(btc_usd_feed_id()); assert!(second_price_result.is_ok()); assert_eq!( second_price_result.unwrap(), - multiple_updates_results_get_price()[1] + multiple_updates_diff_vaa_results_get_price()[1] ); } #[motsu::test] - fn test_multiple_updates_different_ids_updates_both( + fn test_price_feed_exists( pyth_contract: Contract, wormhole_contract: Contract, alice: Address, ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let update_data = multiple_updates_diff_vaa(); + assert!(!pyth_contract + .sender(alice) + .price_feed_exists(ban_usd_feed_id())); + + let update_data = ban_usd_update(); let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); alice.fund(update_fee); @@ -335,35 +333,94 @@ mod test { .update_price_feeds(update_data); assert!(result.is_ok()); - let first_id: [u8; 32] = [ - 0x3f, 0xa4, 0x25, 0x28, 0x48, 0xf9, 0xf0, 0xa1, 0x48, 0x0b, 0xe6, 0x27, 0x45, 0xa4, - 0x62, 0x9d, 0x9e, 0xb1, 0x32, 0x2a, 0xeb, 0xab, 0x8a, 0x79, 0x1e, 0x34, 0x4b, 0x3b, - 0x9c, 0x1a, 0xdc, 0xf5, - ]; - let second_id: [u8; 32] = TEST_PRICE_ID; + assert!(pyth_contract + .sender(alice) + .price_feed_exists(ban_usd_feed_id())); + } - let first_price_result = pyth_contract.sender(alice).get_price_unsafe(first_id); - assert!(first_price_result.is_ok()); - assert_eq!( - first_price_result.unwrap(), - multiple_updates_diff_vaa_results_get_price()[0] - ); + #[motsu::test] + fn test_query_price_feed_doesnt_exist( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - let second_price_result = pyth_contract.sender(alice).get_price_unsafe(second_id); - assert!(second_price_result.is_ok()); + let price_result = pyth_contract + .sender(alice) + .get_price_unsafe(ban_usd_feed_id()); + + assert!(price_result.is_err()); assert_eq!( - second_price_result.unwrap(), - multiple_updates_diff_vaa_results_get_price()[1] + price_result.unwrap_err(), + PythReceiverError::PriceFeedNotFound ); } #[motsu::test] - fn test_multiple_updates_same_id_updates_latest( + fn test_query_price_feed_after_one_feed_update( pyth_contract: Contract, wormhole_contract: Contract, alice: Address, ) { pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); - alice.fund(U256::from(200)); + + let update_data = ban_usd_update(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + + assert!(result.is_ok()); + + let price_result = pyth_contract + .sender(alice) + .query_price_feed(ban_usd_feed_id()); + + assert!(price_result.is_ok()); + assert_eq!(price_result.unwrap(), ban_usd_results_full()); + } + + #[motsu::test] + fn test_query_price_feed_after_multiple_updates( + pyth_contract: Contract, + wormhole_contract: Contract, + alice: Address, + ) { + pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); + + let update_data = multiple_updates_diff_vaa(); + let update_fee = mock_get_update_fee(update_data.clone()).unwrap(); + + alice.fund(update_fee); + + let result = pyth_contract + .sender_and_value(alice, update_fee) + .update_price_feeds(update_data); + + assert!(result.is_ok()); + + let price_result1 = pyth_contract + .sender(alice) + .query_price_feed(ban_usd_feed_id()); + + assert!(price_result1.is_ok()); + assert_eq!( + price_result1.unwrap(), + multiple_updates_diff_vaa_results_full()[0] + ); + + let price_result2 = pyth_contract + .sender(alice) + .query_price_feed(btc_usd_feed_id()); + + assert!(price_result2.is_ok()); + assert_eq!( + price_result2.unwrap(), + multiple_updates_diff_vaa_results_full()[1] + ); } } diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 06fb5148a9..707309bff6 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -123,7 +123,7 @@ impl PythReceiver { let price_info = self.latest_price_info.get(id_fb); if price_info.publish_time.get() == U64::ZERO { - return Err(PythReceiverError::PriceUnavailable); + return Err(PythReceiverError::PriceFeedNotFound); } Ok(( @@ -160,7 +160,7 @@ impl PythReceiver { age: u64, ) -> Result { let price_info = self.get_price_unsafe(id)?; - if !self.is_no_older_than(price_info.1, age) { + if !self.is_no_older_than(price_info.3, age) { return Err(PythReceiverError::NewPriceUnavailable); } Ok(price_info) @@ -188,7 +188,7 @@ impl PythReceiver { age: u64, ) -> Result { let price_info = self.get_ema_price_unsafe(id)?; - if !self.is_no_older_than(price_info.1, age) { + if !self.is_no_older_than(price_info.3, age) { return Err(PythReceiverError::NewPriceUnavailable); } Ok(price_info) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs index 47a52e7017..cd5316f642 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/test_data.rs @@ -1,25 +1,10 @@ -use alloy_primitives::{address, Address, I32, I64, U64}; +#[cfg(test)] +use alloy_primitives::{address, Address, FixedBytes, I32, I64, U64}; use hex::FromHex; use std::vec; #[cfg(test)] -pub fn good_update1() -> Vec> { - // 0xa6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf135384 - let hex_str = "504e41550100000003b801000000040d02cfb65be46a822da31643f7bcce6c632943a0c9bab25872525c640ec1d803832936e560a4d6344c1e98f8fe2714281f882b25bc3e1f5fd3db51a9eae49afe2fb401030f87f6065777bffb27594235323e6680ec24b953ec71f0e0e3708ff28986613b2351aba32024ea226585f90c0a2680ee40786a1978b99596af6c281a947b87bc010492608d288aeaa7393d625111da17271178e739392ac87334b01388897612e63176754d044740d80f8f5511cb92c9925592ca223f8eeea70153ccac10f4f2faee0106944014d7f07eb277fe6cd5b4adf65ec547971ab43cb56a507cb974a059cf5b623e838cb899b2401efdac1d352aabab8d4c737519611f4ae22be6addfb254002c0108e9a6f762c22e639302495af910bf305269f38460360ef32cc4e5b1369d599bd33343d56a8e9b392a8cabfd9fc31bbfa32c03433d14f7b7ecdb1975e9759f3e51010ab4322508f35ca75320715c91ed4bf1b07e89730452e3c25b78469646ea61cdc27e52030b2baed4c91b24c3fa13816d95f70095d6c6cc2e110aaf92bab7e816f7010b48e2366bfbccd42b6b790d618bbe3bae90507be6ffda461a7a42ae19f674e16d359c5743540557f401ba7d33f401d5489d0933c3c6253528d1f9c7df3c51dc11010dcc82f54596cd7fc8dac985d4d1bbe410c82f1846891077783ea8da7522725f4621b51d2c09c008826e70f5b52098669d9a872de47d8544867dc381be6e10af04000eb240c11adf380871403a158fc5ce4482629ae1ca2e8b3ab6e7cf02e20435400d065a6399a49ede59db3ad05e56d5f4d4cfcc2419ce2b21dfd2c6dc955539c66d010f93cf0f4e8db5c1467b72c4d188c3c84faeab9dcb5936c2a4a8e80c8bebe8cbc75bd757243af5f109824e5afb1cafd6995c587cc0714fff8c92b1c109a7f4644d0110a89b1ccda9b5e70e823c259ef21b09ef5dcb3e0599033abdf179292f7395e62f3aead157a15d95377dfec08656f6b3f71e68e7ea8da185c38c6f84e50beb79a8001129768f9183799b4bad3b96953ea53b7ee269cd6763b56051b0fcd93e9f6b62325a811abc9d8d401c3971b864a4a9e1910f3ad6980fccd078b36399df46bd291c00121e92dc37fb15d26237137088d139d6decbb1e1d08a7e2a2acf92eaef9cbd7a93738c702ca0faa0b7764b5a28b4ffe41febeb9c2d62706844e69831b58902d88000686fff8a00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008976df2014155575600000000000da4fc6f000027106529465b0781218c3d940e5d11cebff0bc1ef15401005500a6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf1353840000000000635f4b0000000000005117fffffff800000000686fff8a00000000686fff8a00000000006202080000000000003a710c1957911bdae55923e5b2b971a731b10567748efab84bef5a2e1ac6fbfadd24896a2d225c77a260f03b06cb7c34c3ed018d02ac3fc7eefa997ffc91dc3b50508fb4b11c6c7768bc93dd3abeaf5d912ff895be0136780546fbb2f8b42855afa013d38675d811ab3118f0b0eea13dfd64ceb16649747908f859be12f8116922a05ad919df2bca555e5177ec70ab85278ad68b057e6fdbfe50bc38c3796c43199310760180b3253701d4293f32dc1e9be7f10dc3bf2bbf60ffe85b4e0b9ba3cf87af3c472f5d0372cdec973f4ddee17e89255df7d55bea696c64c7af6e8efd51ec79d1870f4d19bc4d5c65f24bdccd341a7d"; - let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); - vec![bytes] -} - -#[cfg(test)] -pub fn good_update2() -> Vec> { - // 0x879551021853eec7a7dc827578e8e69da7e4fa8148339aa0d3d5296405be4b1a - let hex_str = "504e41550100000003b801000000040d02f11b16ae1042344f3c25ea3738f48985f16ed9ebd2e6a21c0b3ffc95f47f327d0afabe6c452b65ec71345b84ce9efb933c77c76285cb43c50bdc7c8fb6bef58d01036c7d8bb399c1fb786cf9ead5d604d4b8739f477a5d4a543ebec13309cdbc28926b4b1a84447431e818576f129d3c1a0ac42f43a4f6cc98607a8ab9173116714c0004181f8d49fc6d4943ccce639da721ccea067e049cf2be33dd4c002ec2185993ba498006814f42b8d949fe95fa1c39ff899ba694902ca0d60ad2cd6fbd86642bea0006f9bc4c86125c6ea76637f4e9afb7c4fe1b1f7669ad6454f7af2b1964cc11b7ec754709960ac5eb9165bba6e54482c9c2835ee67c99f4a1694cdfd05811c0c3630108b6805cd6aa6bd074a59bfe68c10016bc7acbbf61ca936fa1356ecd782493cb4f328964a4055938c77646cbdfc7487be8ded6f285507ba986f39c3aa584be3b67010a32f69176149696a1b503434c25456a41dd86582c5f07f305d9f3b4b14cb3eea30045e996df3fc016e9213e19b5f19a2896cd8c9aa52ba931cedc6f3672c92856010b55e38782b6df0e1926ac5708e0bd60a7f85fee986d54544a683eb8d0855d1d485a96066f773951d6106098bfdcb1c1ae8165bed84126a539747633c87a9e6cb0010d5b5d539c768dba043adb35641b5e7b1e14e30ddd3db2cb4d3b2ee540c5db04cd0eb244ad60a9fbeac1555795c1377e2e3cae70961e241e499a3404cdc7358662000e553a69464feeceb4be71ac5a55b50f60403f23d764debfdc002edef342c927fc6c161a4eebc6be95e014fcda76c537335578228a206b9715b57747a65335b9bd010f3e4971a7f76c0e7160bf236d9a62af81735f25f4067a31b836c36608073b1cf35c918b30c03bbfa2c30de7ad54bcd293f38ac7d360efe9079590e81d2b4affe00110d812f635ebb9b8faeee88405469a554792ddfcc3353137cb820c361c893b2fbf4a7a308194be096e00d3514449c3bbf3c638a6af92962cc30bf0323f0dd535cb011106306af0986424de86434aaba4dc11ce5e08a1a15722c065dd4fa3ca36d5859230e0eea8cc74b31fc024f025956121374f1354a742eec364cdf0079ca885e39d01123dc4dcfae02cbd590e7a4d85a07b2e9a107ffb95fed0c843af6b7b884eddc8d05925fb1d271e2bf3c41970d9f83bfd82cefc7d45c143c7595352ced45bd2dbef016870024200000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000897747c014155575600000000000da502fd000027102a19430cad54e27fe63ae1b4743caf77ff8ed43c01005500a6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf135384000000000062b4b2000000000000324bfffffff8000000006870024200000000687002420000000000622f4000000000000038df0c1c5dde54a2e342f851ea9041fafbcd1a6b2f1b76d54a4216f1d2441a666411a011ce756da5bf4b599f7faa456612f1810aa04f46e929405e6fd2704f4576157213ea8a656558e788f104832754f6ae3a5481df8f57e0f6ab6043a5de784086ce07e102ad4eb961b86e29ede26cd32a7dff934b7848844b4eb75f109aa8f1c411a4d2c23413fdb33a260c913c3a35df074c9a2590e8513caa5400001c9abe9a640200d17f8f18307c1d757dd8fa7aac510b57ed3cccaeba6f36e6ea506783cfb726d644e8b9f19dcb1e599ced6dd9a3b558e83124753869d08dd7fa85568c579dc2a4b9bdd0e7876994aa18cc8a57eecb"; - let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); - vec![bytes] -} - -#[cfg(test)] -pub fn good_update1_feed_id() -> [u8; 32] { +pub fn ban_usd_feed_id() -> [u8; 32] { let hex_string = "a6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf135384"; let bytes_vec = hex::decode(hex_string).expect("Invalid hex string"); @@ -30,177 +15,90 @@ pub fn good_update1_feed_id() -> [u8; 32] { } #[cfg(test)] -pub fn multiple_updates_first_feed_id() -> [u8; 32] { - [ - 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6, 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, 0xe5, - 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, - 0x5b, 0x43, - ] -} - -#[cfg(test)] -pub fn multiple_updates_second_feed_id() -> [u8; 32] { - [ - 0xff, 0x61, 0x49, 0x1a, 0x93, 0x11, 0x12, 0xdd, 0xf1, 0xbd, 0x81, 0x47, 0xcd, 0x1b, 0x64, - 0x13, 0x75, 0xf7, 0x9f, 0x58, 0x25, 0x12, 0x6d, 0x66, 0x54, 0x80, 0x87, 0x46, 0x34, 0xfd, - 0x0a, 0xce, - ] -} - -#[cfg(test)] -pub fn multiple_updates_diff_first_feed_id() -> [u8; 32] { - [ - 0x3f, 0xa4, 0x25, 0x28, 0x48, 0xf9, 0xf0, 0xa1, 0x48, 0x0b, 0xe6, 0x27, 0x45, 0xa4, 0x62, - 0x9d, 0x9e, 0xb1, 0x32, 0x2a, 0xeb, 0xab, 0x8a, 0x79, 0x1e, 0x34, 0x4b, 0x3b, 0x9c, 0x1a, - 0xdc, 0xf5, - ] -} - -#[cfg(test)] -pub fn multiple_updates_same_vaa() -> Vec> { - let hex_str = "504e41550100000003b801000000040d02e57b0f291daa4d2f02f5c4a18793b278b238338f472d17897f8f0866549f77571cfe71fa55bae7f340b9124511559b73a0cf01c72adc8a8d9963cebecc5a503801039507a96b155046ab039f7c9cad17a4927e2ff34763bca9b65d572ddc7a5f019832ffbeeb5295447bfdb989efa0314865bb4571770ad8e75ae7a083288d6de232010412e7333ab5cf0f20274b0907da43b52016d5a095bb846962d13a222e4af1e7e63f7a8db49de04feb70f07a0e274dc58acc7a4c386a099369412c6813ba39916100063da672f75cf1d397829a39461e311ca366366828be8d12b19a00c552e7c8c5e7746b36d97dccc54e5b3aeae188b372ec885dc1fbd9c2285ce458764c86f0c1bb0008863aa237e9fe339683992121249a2e520b6483a3b3b60c703a1eb09ef33266312e729ff6d398e1a60be8474a95803cd1641ef6c1de2c74f3cd7e1f2510c919f9000a3bd5ec58424b21c48552c3be0f9cccd6e6c641eee2b4e550fb88cc93cfdf10c7409344ec3e81df711a293baba565a85e620d20028d9738e53939fa52f19ce622010b000f803511f89f02610fbece34fe327afb55196cc3e522bb28d71d6e4d5523ac77ca1afbbd8a28b4fe05c7f2aa1c3f428c89fe21096ba67bc505cbfa6ead9808010c315b34c9cac03647df4e12a050f8b739763498aa23999244036e09010e2a79a46d0cbabc22c535542896bc22df05dc5480db06a370dffeb0814424870fd50c21000d4a562686000b65df4e0ca00d2e00d10db9e913b481337ee1c80bb47b25553afb693d7be0c17f6fb106909a1eed52a6c27739471b719d4c450b99b066a02bd2c9010e309508bc7128030ca4b19fc34c0ee0e62eebb549c759c2e8ccfdf062793e41e935754ae1d5356ba98446fa2eaa837ae4b413d1ccdf1af6d9060a2885f18c19e1010f3e2ff50704a6ad1b491cb93a1e4678c0f58b91540ba3ce3b4424c96abbe922562c924debb3336ab2fe835237f16912d768e6e5b739f2ab44b57a1e2607c9bb89001070d0dfac758a38342b107870b4d5761df9e785c6be589317c4b1dad3c08998f11214c29201d172b278aa6f4d57171f0f05fb7a2718e6da6df4449e8897c0c2ac0011d9e885989fa2363ec311bf4e9ebd8738d4b3ecaf9a31c09ce06f9876c3ab772034c1df9ca09c847ee81de80a1f0f8592019fa60e55b02b657b8a7c99bee04701016866e28300000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008812f80014155575600000000000d8ebd6500002710f015dfd43b23aad91dcd4a7a8a113ed2d39233f202005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fee44efa2300000000e7f6ffe3fffffff8000000006866e283000000006866e283000009fb4f364d0000000000e98d2f400c2704dab60f1b310d567acb60d77a3ce8003a6f564e8e1f567f00f004381d755e160a07372977a99288dcc9c9477cf9c1bb095403b514082aa774f7b243003e30548cbd97e8191d5ef2732796e06f84f05543a171f1e66052aa515c41a2d994a0d13e2e4016e6a28823201a52d408a5024797ec4b7629406062dd9ccc30a5d1eb4ac8b4a28a3d464bf4335ceda7646e03c29cc24b6c7c5e5924e6e69400a2c90561c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f005500ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace0000003c480c0e980000000009727f59fffffff8000000006866e283000000006866e2830000003c3597da300000000007d3439a0ccf5f10d7559e184107e994663aa0fc8f81718c0b281162b77eb09c774da30e2db5674df62494b3da820f6c986a0f32d1a195b6bc4676891d4e28cdb4e2f09dd47db3243547b37bdbb9799c82a42d6f1f18f8e17d7bed68408ef26e184f21e9b640e2c9f0416d91987acbe6fa8e72c2c99fa548f83c0eb5dd3c269ef52101521ef0b3d26f50b07dae68311bd138338881b20b78f8d21d2bfc27e9ac849b4c659d61c9a2e4555084af13fbc7eaa0a884b75d4d1197933ac174b62c4b9b1cde3dd496f5e54cd2e01cdca0ba5c5a80a2bbd0d9d5dfb7aab5b638ef883e4e55f78a1536fe79c5c3e16cda9b53e364e3bbe95f"; +pub fn ban_usd_update() -> Vec> { + let hex_str = "504e41550100000003b801000000040d01c9e767c1685410aa1ad3a221af6d257b01d98301b27b72e78f3d7a8d580a90127451c5c699cbb1ef0b5bfd57645456c2898a997ccd4de6eb79d277ce56a12d0b01027599b2f2450d4d59904dc8f738dd73825db0b5235707b9f44119a84e8632c460652e5d7b3ba142120f2510374a478bd7e5cd71b954cae9ef6ea15d7a08a1c3e90103f3a1bd74938fa7c6b2c917303a002474a5f716501c19789fece2a3f80bd05c457c8087a8b2d14e84762059daa5608e38e4e3e8ed572787a20100b8c1d69777f30104c5640a58148caeab59ed9dcc025f7b7dcdeecbfc51108cc29d3659e8b0a1c1aa4079f43e0e846ed353d45b5f436431096cd3094c2fa15e4920e2d35e33632e00010693790cbfaca431837658775a3b274c05676b272b916245f939e978789874ce0f2daa426325986f38c2ee3b9053008362b60b9851d2e9db69d718faddb96db68700098eafe76c684d04f99292d536de3e95eb446c3fac2f70aaac11d5dbda0b5a38f516b56e9f3472528b675337746653c59ed2eae9079ae7f59c004a8cbb40139a7a010ae14fce0cc71f738ec21e66edcd867db036cd5e11a9476c710d2457e025c035c84518c8750b17197d308b9faa2561ec6532c2266eb36723a9d11871b04e3b1138000ba68cde478a18ebbc8e9c2b4bbb4ff16803d5402efbdc9fc34cad9d9ed6f1609f6c81596fac2eed2b98ce6f5b5d7efba530c8b9c15c70f2f10898b38ea2f9978c000d2ecb926686379023572b64d78aef7f61e9aa3e4ceae1d2b2917c1fae6d112b3d7ad1597e6768fffa2dff61a62012562eb68a7cf5597e9bfe755c280df36aef2c000e293c5cb9c805665057bedcfeae74139f47cb5cddc4d5190bbddc4d12cd53caa972281394ac02759b860382da06e8d9b003285090a6783de21786dfcb3b669c58000f3b90618d7a63cdd7da9e730dbd0bf5b22acdc35c08a17c9a6728b1115e63ff837c3267452dc29d8f77fa0cd39428066ea8ae1fd086293e2f17b9421b59f7922f0010629f08a3a59d8187eefef92a54b9bf55fb676f4e9fea826ffb4aa3331155c2162315bd092dd01776f0e45c5d857f9de70a0cbfa9b33f96d8c752bed5c37cf05600113038bf5593427383bfd0966064dc43f7a84f8c083c1bc1b03aa24fc857008f057778ca2393ac1146bbb51588f4903f0822cb94ac0dce7cdcba3a207969d529d000687028bf00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000897d17f014155575600000000000da5600300002710bb2be176a294375c4430a694c2813c5823a56bef01005500a6320c8329924601f4d092dd3f562376f657fa0b5d0cba9e4385a24aaf1353840000000000625bef0000000000003c63fffffff800000000687028bf00000000687028bf00000000006223710000000000003c6b0c299b70feaac0e02b6430892ee820e1a0706a4099acf41781c8fa57ba6ca1f0b62d98994ccecb7d465eeae1c5a236df5ea50768f1d8e9300500a8824e608c5a02572ee89aa0f0490bd64d60482516a17a2cf6ef3140ac5e35e3ee1844aeb2fb2ab7740ed0905f80725663f8a7018025ea163ece851177137f0e1012b32a540bbaedd2be2b7ecbb6d7baa37298d5ea1e7d8b6c3e3f3c40ec0cdc546dcac1fc8fd0f16828f8d3d948e4ab67391bbca60a63de48273df382ca02f05bd3aa8a0f7513f2a722bd447d8c07a02e73f14d9bb625e82aea434b9378ffba62dd0ef4d04875b1a31cc077b7a9c58ddd0109e4b67e45"; let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); vec![bytes] } #[cfg(test)] -pub fn multiple_updates_diff_vaa() -> Vec> { - let hex_str_update_1 = "504e41550100000003b801000000040d0168bad50bb1470e136368ebadeb842cf5256547a30cd65c320a0e476bca15f204326806e4290c004925040f5eea31592c55341936fe7e7537d7b4172f308fc79701033b17478509bf096ebc6107afaaf9f339f68d3ad7ebd9267585cd945b91c93f687550c07d4526dc6f2ad0335c1e849977d91d2e61be04d746a611b444af272a9f0104ce96c5a8d9e4e869c2b7ae54980bf9f315af3d93fe827a346b3b55041e019e982350498c7094c77f09567cd3f2a9961940e304666fb08bfbfac7aa1a6ec157cf010639036dea5439380e1c946f76f06480965b60edf280272ab7f649840e289e3d740e4822619e5608811c66158d22ccdea633961687b3afec2751a53a37b3fd6f4a010a370bf667e0fd0b6df27ae87e94513740d1581ca9eadc15b049b5ce4d96b57ba85f92a7bdc478fde36efd7ab80674048589421fc2644457d7b4e398bc1b5c01f0010bb5ef6559a20289cf9df4a8c076baecd3d8e3567973acf975ee52d0301b8cbddd7bb2e4348fb59020bae2caefd57945cf1107cff7f5fd068079fa59ea27e0e702000cae84bce54e40a0583d8c957ddab0388d053a85622cd83c3de49e4bc4acf515351e118f4c36b903f09fe3afd7ce5499c73c37e80d742fdfbb1a57d7f442f915f7000d90c05b57e9592b303305b0e2a88b05f5b006a5f911ac9feba74c309acbfc3af8270b4d8c0759677db601af1893ec4523f023ee6810070148cd017ca2fe2d5663010e17a7472d37425d9f1505e998736d4138f6f35c620c195340ef87ca0d155cf3aa12fe9c0a9c6341411734dacbc53e36895efa69c9895d682bc6329294a49fd928000fed2cb761a8837d87cf827141eedfeafd1901e38957930bf56cc044ebf2c34e3e06bd8197b10d356236acb2c31be6031b95caf2d7492e2744b176bf8a4a357cb70110f22c1178f5c734e83134432c2f5c76cb6462ccba9db0177640cde08b5413ce09630a9c0d5682ee2a1acca1f5d0a6a3583b9578c6e0fbb2d489e252ec7e5960a90011d984560f66ce62be4ba2512e2d62777429f37d6020f9dcb9a0ac54522f80462b4ff33ab5c832748e50cda01e34eef28271982ac22b216cac2a6bde33a83f6bfd011207131b4a1ab2f2ed984cd444210c5c4147d5da72b27d1fdbf6f1a087141ab6971b20175fd67abc937ad69a42991b780804e67e926a8ca2b218521d6572c394c100686ed88a00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000894a31a014155575600000000000da2316b00002710d957df1a464d0193bc0c9dd3b555d0544a04623e010055003fa4252848f9f0a1480be62745a4629d9eb1322aebab8a791e344b3b9c1adcf50000000002181de10000000000008db7fffffff800000000686ed88a00000000686ed88a00000000021501d40000000000008b300c29ff39535a48e8f09021a1fee7b9e887d68522dd790970609044b11d98aa7d4beb0d1e390c89ccad2fda83fd5316ad4483fc398b6534be617d42690cc27297ccbf19315c79064275612a0d16a2536fdafaca0648bd28b54a014a468906203c8a9d3deadd4ec12dd380bdd27f9a9bb41a5e841d23671743da3125f40b479518be5250080ac78df8826fb59d02dd07b310b69f954dea12b87cb5aa66af75d3da3f7327578f8ed0c0a7b98d1becad74b16e03ff52eee04497971d9985fd9cc05215490b44499df2c3a64f6fefa743421d947d34de37e185b242db065c8f89e9bb028a3710646273f0721e26c68dbd85fb20"; - let hex_str_update_2 = "504e41550100000003b801000000040d0239010392dab908eb9903d480abf8118d887f2a0c5eaeb9062e6eabab86c1b382127604c63358a7266cc5bade7726e159403c2bf317c7b3a4d8b489bfad4e334301031823d70352d3260226cbdddab0cf7d1584c0e1d23d4358ed12f9620e18a0db2154fbb096ac4cb8d5728e2cecf2b1398d7b9b51954f3fb8b4f59990ce017b0260000495e2691d8e6a0537d8ab3f41b5eb655acde7fbeaea0fdbe1f582383680f54c8a3a697c2c0f8b4110422f1b6beb0bfb601c929148b54dbf85fb19c333ccbb833c00066993a56c5980bf17d2790b933861fffb1fd09618921a90db4ab82cc8b148301f1a55d804d14cb39f648fdb0ef8c9ef1e24edc38d30f2aea7151025240a614bca0008a64a366c59bd6c4ce9d24a0e3beef2a33d28546826b1b969af184a257d648aab5672ad8a9eaf14473da40327e12e5c18168892bcebd693c8bed3df8ee50b85db010a36daa7c639c412969283f83749af93aef2464b27b83914b6026b721a59c8a04446a655686725247bd9154c71ca66505719df5867f775863a788d8bffb1bd637c000b237772560d72da81a782e89b138caf8bf1221b929ead77ca7d178b7b7af1c9141d9e77e22c98fe41b819f023695e6feed6f5215a5cdb6436bf52dc3c4c93e309010c89f2f3c64a8c77ccea47448e7871bbd70b59ed5761e5677458dbe6f82796efa2399e9ad9bf846d88d4688f1d19f9e2adeb2299017baf015c36a811d05c539b86000d6ba11d2f9a0edfd3a4bc23024d18dd010a83803faa79d40aec10a4deee40e8dd3c4c5401118b67bd6d879683cae3ea83d4f9afa744c655775615a7ce34237a02000e09a554d70c0f8e57bb79ce41552e38b836ad7b6bd1967e60c880f831341ad412699e4a9f5346713a6db2c7032bb7d1b3cc8e42f49ba17000f9d0916a13f2debf000f1ce88af88b96aaeb0104d4c966303eb9609df1b851a0d6149d05bba82f3fd70820a26d7f9d6fe18a7653fd3e3eda94fd9184726dadd2e8d58d09a8473e919f0800104583407293c41bef15c05ac20fc45fd5f9d00639c5b1f738d1ba42cd290fe5291e05219cefa8568806bfc1de76bcf5f799c90c9c6dd54bd69f9d459e994acb7a00110638c8067b42005ae678a7619e9eaad5fb66f0630547ab252179668e60b738c479ba6ff7e1f3dcffddab15e1bfebf93e0e4cb051535bdda3ecef6620aea32132016866e56400000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000008813690014155575600000000000d8ec4750000271098d4f856e398eb41afbd0f2b24ad80e58b1f57b601005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000009fdcc9378c600000001105b4d5afffffff8000000006866e564000000006866e564000009fbf79e7bc000000000e99c0d1c0c02b95abadee324fbb6534576de1507c74c8ddef2b928c314cb3d4978a5ada03db907df05ba0fc051e659facec6479c324c276e5098fde9dcae0b462cd32d9e2e5b617b51ced85d38a8456022f3ab370d3c45a07acb686cfb39976b2f4bb1007a91e599951ed929f714a04dab0e6bd885a0c91a076f3b83ee8f765b70a3edda569876102f2c62cae15024e529a2e5e17c50411aa736c7511278a92f4d9cdda3239057c3a942a1365a58771734a982e41e1d7aa8bae87748f1becd045fcb5e1cb1993e978168147d6be8a2cba24a3cc8a2f78e7313f18c87ec2bb238510ebeb47aab50a449fd2ce3dc6b8c0d08d361c102"; - let bytes_1 = Vec::from_hex(hex_str_update_1).expect("Invalid hex string"); - let bytes_2 = Vec::from_hex(hex_str_update_2).expect("Invalid hex string"); - - vec![bytes_1, bytes_2] -} - -#[cfg(test)] -pub fn good_update1_results_full() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { +pub fn ban_usd_results_full() -> (FixedBytes<32>, U64, I32, I64, U64, I64, U64) { ( - good_update1_feed_id(), - U64::from(1752170378u64), + FixedBytes::<32>::from(ban_usd_feed_id()), + U64::from(1752180927u64), I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(6512459i64.to_le_bytes()), - U64::from(20759u64), - I64::from_le_bytes(6423048i64.to_le_bytes()), - U64::from(14961u64), + I64::from_le_bytes(6446063i64.to_le_bytes()), + U64::from(15459u64), + I64::from_le_bytes(6431601i64.to_le_bytes()), + U64::from(15467u64), ) } #[cfg(test)] -pub fn good_update2_results_full() -> ([u8; 32], U64, I32, I64, U64, I64, U64) { +pub fn ban_usd_results_get_price() -> (I64, U64, I32, U64) { ( - good_update1_feed_id(), - U64::from(1752171074u64), + I64::from_le_bytes(6446063i64.to_le_bytes()), + U64::from(15459u64), I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(6468786i64.to_le_bytes()), - U64::from(12875u64), - I64::from_le_bytes(6434624i64.to_le_bytes()), - U64::from(14559u64), + U64::from(1752180927u64), ) } #[cfg(test)] -pub fn multiple_updates_results_full() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { - [ - ( - multiple_updates_first_feed_id(), - U64::from(1751573123u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(10990356724259i64.to_le_bytes()), - U64::from(3891724259u64), - I64::from_le_bytes(10974970400000i64.to_le_bytes()), - U64::from(3918344000u64), - ), - ( - multiple_updates_second_feed_id(), - U64::from(1751573123u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(258906787480i64.to_le_bytes()), - U64::from(158498649u64), - I64::from_le_bytes(258597182000i64.to_le_bytes()), - U64::from(131285914u64), - ), - ] +pub fn btc_usd_feed_id() -> [u8; 32] { + let hex_string = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"; + let bytes_vec = hex::decode(hex_string).expect("Invalid hex string"); + + let byte_array: [u8; 32] = bytes_vec + .try_into() + .expect("Hex string must decode to exactly 32 bytes"); + byte_array } #[cfg(test)] -pub fn multiple_updates_diff_vaa_results_full() -> [([u8; 32], U64, I32, I64, U64, I64, U64); 2] { - [ - ( - multiple_updates_diff_first_feed_id(), - U64::from(1752094858u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(35134945i64.to_le_bytes()), - U64::from(36279u64), - I64::from_le_bytes(34931156i64.to_le_bytes()), - U64::from(35632u64), - ), - ( - multiple_updates_first_feed_id(), - U64::from(1751573860u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - I64::from_le_bytes(10985663592646i64.to_le_bytes()), - U64::from(4569386330u64), - I64::from_le_bytes(10977795800000i64.to_le_bytes()), - U64::from(3919318300u64), - ), - ] +pub fn btc_usd_update() -> Vec> { + let hex_str = "504e41550100000003b801000000040d0239cafe615bc0f6037d0b2b596490a0473b07ed176082e6645c773678bafd705a3d3e86a03b17c44a9d0e2429051756514c2a247f9d08767b0bf093dd8313814c0103f5be04233a4e4f169428ee7c7e153978c593db55b9fc93ef6329333671eea8c567c143e88300ef470aeb7c3f5fc3d63582fe4c9f28fc451915f0c0a5ac1920f60104883322c38070deaaa013d45ffc4aa111a070938aaed1edda6853fa3676e7fbda6ffcd6711ac4fbb9e9c0320d193b9aace0b1fc42f0060c8ab54024c8cca55e5101066624f81e56e2981619eca215e07e2e055088a2c163dd457bf7d60828b0bc93a27c359999832a0d0534642defc8197b55ffcf1d52b9fcf5a8cee3e73c2910107a01081a8e5f8715f6128642bb34204c08415f972fed334add518e672be983f4e3830b7cdfb653976a37421a86d3ea1d0b5ee2d2baa8c54309539e4e7e64d57f13816d010a0df0714ee3e9d8f741c15b50f2d2b5a80bdfbdcd62963d37efde7690ee2f35ac4a134f425ec84610decb606254c81813d3be6372e64bf7d8692c1ceb7d3dbf31000bdbdfb38a07d2efd68bc96caeb986ab39473d5080575a0f7878bfbd4456da0df25dfad1e45c30bc1eb2d81922a5355db6fbc109b9bf4520f18b2da9cbd06c1b9d010ca5a2450d2339debfb36065567196c340721e260ea524003b2e73ef3f9ed1f9d2446e8b059c91ff124e83156e826c6cb496125ef6f54a578d03de9ce7a488a4f1010d2f389063583770fef355929ec57015b9b7f4d5879f63bc1dd1e8a1605b12cfbf534fb31d56be4e54ae1eba9442045b791b0cc9b56d3e3b0b4e8952c98841bed3000e343679701f31206503ee1014842cffdd7fb6b05173d8373e9fa99bd3a1f075a77339f1a588766d716cf650b7f04c6ec38b4f146932ab19345a5b976beb7fb932010ff8ecf6e31a7588184df483d4db07875c06c009f2837e8402fd5e72fc703317f5284be8d9354b4fab80397828dcee6e8d42e384bc0900e6c09bf03d062449d79b0010804e0a59b1ceaa6b27686390cf012f9b28c3f0631d625a02c2aa61924ae9e3f750d408ba01469ceba5c69750562a8764bf3f395fb2d3651ef7e785880dbf9f6d0011d624fbfc51b33be04143af0df857bff085ec613ce45c0afac19cb7b25d1386d4748a757c1d88c084a5917c0fa29e62777f52627100f9371e17b9cf3d7761e74c006870299c00000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71000000000897d392014155575600000000000da562160000271079aec689f7474fccda868e2c953a8034004c18f001005500e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000a555e80406d00000000d970a713fffffff8000000006870299c000000006870299b00000a51d33b2f20000000010aa6a7740c9ed309739de34ae90fbadd4e31dfb63bdf4e6ce18be268a0b475c9fa239b3b3dc6413e6be59d3e081d18340d5f54f7c775bb8a3624c9b6365c377be3c7d8d95148bbeef8ed7337083c85b99f3d24fceef013b441f91efaf0d75d3054c798046ed109da12b59b60043a81139983cf1c68705c58279e7b3f122ff0562c5e8b925d8ebd8791842efc74260de6917967faea7c994b1bd8791cce3129f877447db2fe0e5436384e4e2dd7facc50127c072a25f69aad5d3c89ed0f38cb6b792c0d12c123f76b7059e9217b959969ba558430b06a4aeb769fd7a1a8cf3eb74680f0bc1b84882fb8e311d3db94ea051d2e209f6b"; + let bytes = Vec::from_hex(hex_str).expect("Invalid hex string"); + vec![bytes] } #[cfg(test)] -pub fn good_update1_results_get_price() -> (I64, U64, I32, U64) { +pub fn btc_usd_results_full() -> (FixedBytes<32>, U64, I32, I64, U64, I64, U64) { ( - I64::from_le_bytes(6512459i64.to_le_bytes()), - U64::from(20759u64), + FixedBytes::<32>::from(btc_usd_feed_id()), + U64::from(1752181148u64), I32::from_le_bytes((-8i32).to_le_bytes()), - U64::from(1752170378u64), + I64::from_le_bytes(11361773961325i64.to_le_bytes()), + U64::from(3648038675u64), + I64::from_le_bytes(11346552500000i64.to_le_bytes()), + U64::from(4473661300u64), ) } #[cfg(test)] -pub fn good_update2_results_get_price() -> (I64, U64, I32, U64) { +pub fn btc_usd_results_get_price() -> (I64, U64, I32, U64) { ( - I64::from_le_bytes(6468786i64.to_le_bytes()), - U64::from(12875u64), + I64::from_le_bytes(11361773961325i64.to_le_bytes()), + U64::from(3648038675u64), I32::from_le_bytes((-8i32).to_le_bytes()), - U64::from(1752171074u64), + U64::from(1752181148u64), ) } #[cfg(test)] -pub fn multiple_updates_results_get_price() -> [(I64, U64, I32, U64); 2] { - [ - ( - I64::from_le_bytes(10990356724259i64.to_le_bytes()), - U64::from(3891724259u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - U64::from(1751573123u64), - ), - ( - I64::from_le_bytes(258906787480i64.to_le_bytes()), - U64::from(158498649u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - U64::from(1751573123u64), - ), - ] +pub fn multiple_updates_diff_vaa() -> Vec> { + vec![ban_usd_update()[0].clone(), btc_usd_update()[0].clone()] +} + +#[cfg(test)] +pub fn multiple_updates_diff_vaa_results_full( +) -> [(FixedBytes<32>, U64, I32, I64, U64, I64, U64); 2] { + [ban_usd_results_full(), btc_usd_results_full()] } #[cfg(test)] pub fn multiple_updates_diff_vaa_results_get_price() -> [(I64, U64, I32, U64); 2] { - [ - ( - I64::from_le_bytes(35134945i64.to_le_bytes()), - U64::from(36279u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - U64::from(1752094858u64), - ), - ( - I64::from_le_bytes(10985663592646i64.to_le_bytes()), - U64::from(4569386330u64), - I32::from_le_bytes((-8i32).to_le_bytes()), - U64::from(1751573860u64), - ), - ] + [ban_usd_results_get_price(), btc_usd_results_get_price()] } #[cfg(test)] From a762be9a5430169982d40b52d93daad92ef67296 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Thu, 10 Jul 2025 16:31:19 -0500 Subject: [PATCH 33/37] fixed test error, all passing --- .../stylus/contracts/pyth-receiver/src/integration_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index 5db46977c5..dbe7c799f9 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -348,7 +348,7 @@ mod test { let price_result = pyth_contract .sender(alice) - .get_price_unsafe(ban_usd_feed_id()); + .query_price_feed(ban_usd_feed_id()); assert!(price_result.is_err()); assert_eq!( From 3f5ac86193eda3094510ab11398e91c79b01de61 Mon Sep 17 00:00:00 2001 From: Pavel Strakhov Date: Fri, 11 Jul 2025 12:16:22 +0100 Subject: [PATCH 34/37] chore(ci): fix starknet build (#2850) --- .github/workflows/ci-starknet-contract.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-starknet-contract.yml b/.github/workflows/ci-starknet-contract.yml index 633841270a..1836b798f3 100644 --- a/.github/workflows/ci-starknet-contract.yml +++ b/.github/workflows/ci-starknet-contract.yml @@ -27,7 +27,8 @@ jobs: run: curl https://get.starkli.sh | sh && . ~/.config/.starkli/env && starkliup -v $(awk '/starkli/{print $2}' .tool-versions) - uses: actions-rust-lang/setup-rust-toolchain@v1 - name: Install Devnet - run: cargo install starknet-devnet --version $(awk '/starknet-devnet/{print $2}' .tool-versions) + # it doesn't build with more recent Rust versions + run: cargo +1.85.0 install starknet-devnet --version $(awk '/starknet-devnet/{print $2}' .tool-versions) - name: Check formatting run: scarb fmt --check - name: Run tests From 5ded54c7c4813984bf8ec1b027cca3933be7d65d Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Fri, 11 Jul 2025 15:41:27 -0500 Subject: [PATCH 35/37] final fixes --- .../contracts/pyth-receiver/src/integration_tests.rs | 8 +++----- target_chains/stylus/contracts/pyth-receiver/src/lib.rs | 1 - 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs index ed5de43897..0d5cb57d0f 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/integration_tests.rs @@ -4,11 +4,11 @@ mod test { use crate::test_data::*; use crate::PythReceiver; use alloy_primitives::{Address, U256}; + use mock_instant::global::MockClock; use motsu::prelude::*; use pythnet_sdk::wire::v1::{AccumulatorUpdateData, Proof}; - use wormhole_contract::WormholeContract; - use mock_instant::global::MockClock; use std::time::Duration; + use wormhole_contract::WormholeContract; const TEST_PRICE_ID: [u8; 32] = [ 0xe6, 0x2d, 0xf6, 0xc8, 0xb4, 0xa8, 0x5f, 0xe1, 0xa6, 0x7d, 0xb4, 0x4d, 0xc1, 0x2d, 0xe5, 0xdb, 0x33, 0x0f, 0x7a, 0xc6, 0x6b, 0x72, 0xdc, 0x65, 0x8a, 0xfe, 0xdf, 0x0f, 0x4a, 0x41, @@ -81,7 +81,6 @@ mod test { let governance_chain_id = 1u16; let governance_emitter_address = [3u8; 32]; let governance_initial_sequence = 0u64; - let data = vec![]; pyth_contract.sender(*alice).initialize( wormhole_contract.address(), @@ -92,7 +91,6 @@ mod test { governance_chain_id, governance_emitter_address, governance_initial_sequence, - data, ); } @@ -193,7 +191,7 @@ mod test { wormhole_contract: Contract, alice: Address, ) { - MockClock::set_time(Duration::from_secs(1761573860)); + MockClock::set_time(Duration::from_secs(1761573860)); // less than good_update2().timestamp + 1s pyth_wormhole_init(&pyth_contract, &wormhole_contract, &alice); let random_id: [u8; 32] = [ diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index a8973d3521..aab2d43a60 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -79,7 +79,6 @@ impl PythReceiver { governance_emitter_chain_id: u16, governance_emitter_address: [u8; 32], governance_initial_sequence: u64, - _data: Vec, ) { self.wormhole.set(wormhole); self.single_update_fee_in_wei.set(single_update_fee_in_wei); From 99998e4cf6d1279355a24fb4756bfd7663fe9dd3 Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Fri, 11 Jul 2025 15:44:59 -0500 Subject: [PATCH 36/37] removed parentheses --- target_chains/stylus/contracts/pyth-receiver/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 43f907d166..8fd7c270a0 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -203,7 +203,7 @@ impl PythReceiver { } for i in 0..price_ids.len() { - if (self.latest_price_info_publish_time(price_ids[i]) < publish_times[i]) { + if self.latest_price_info_publish_time(price_ids[i]) < publish_times[i] { self.update_price_feeds(update_data.clone())?; return Ok(()); } From 8f9757115256052b06692c828c5f8d3238a51eed Mon Sep 17 00:00:00 2001 From: Ayush Suresh Date: Mon, 14 Jul 2025 10:22:04 -0500 Subject: [PATCH 37/37] addressed initial reviews --- .../stylus/contracts/pyth-receiver/src/lib.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs index 8fd7c270a0..5ccb034e87 100644 --- a/target_chains/stylus/contracts/pyth-receiver/src/lib.rs +++ b/target_chains/stylus/contracts/pyth-receiver/src/lib.rs @@ -177,7 +177,7 @@ impl PythReceiver { update_data: Vec>, ) -> Result<(), PythReceiverError> { for data in &update_data { - self.update_price_feeds_internal(data.clone(), Vec::new(), 0, 0, false)?; + self.update_price_feeds_internal(data.clone(), 0, 0, false)?; } let total_fee = self.get_update_fee(update_data)?; @@ -221,16 +221,15 @@ impl PythReceiver { fn update_price_feeds_internal( &mut self, update_data: Vec, - _price_ids: Vec<[u8; 32]>, min_publish_time: u64, max_publish_time: u64, - _unique: bool, + unique: bool, ) -> Result, PythReceiverError> { let price_pairs = self.parse_price_feed_updates_internal( update_data, min_publish_time, max_publish_time, - false, // check_uniqueness + unique, )?; for (price_id, price_return) in price_pairs.clone() { @@ -266,12 +265,8 @@ impl PythReceiver { } } } - Ok(self.get_total_fee(total_num_updates)) - } - - fn get_total_fee(&self, total_num_updates: u64) -> U256 { - U256::from(total_num_updates).saturating_mul(self.single_update_fee_in_wei.get()) - + self.transaction_fee_in_wei.get() + Ok(U256::from(total_num_updates).saturating_mul(self.single_update_fee_in_wei.get()) + + self.transaction_fee_in_wei.get()) } pub fn get_twap_update_fee(&self, _update_data: Vec>) -> U256 { @@ -312,7 +307,6 @@ impl PythReceiver { if store_updates_if_fresh { all_parsed_price_pairs.extend(self.update_price_feeds_internal( data.clone(), - price_ids.clone(), min_allowed_publish_time, max_allowed_publish_time, check_uniqueness,