Explorar el Código

Merge branch 'main' into feat/conformance-reports-temp

Alexandru Cambose hace 2 meses
padre
commit
d4748be4f5
Se han modificado 74 ficheros con 652 adiciones y 2018 borrados
  1. 7 0
      .github/workflows/ci-turbo-build.yml
  2. 8 0
      .github/workflows/ci-turbo-test.yml
  3. 9 1
      .github/workflows/publish-js.yml
  4. 5 5
      Cargo.lock
  5. 1 0
      apps/developer-hub/next-env.d.ts
  6. 11 9
      apps/insights/src/cache.ts
  7. 4 0
      apps/insights/src/components/PriceFeedIcon/eco.svg
  8. 5 0
      apps/insights/src/components/PriceFeedIcon/index.module.scss
  9. 21 13
      apps/insights/src/components/PriceFeedIcon/index.tsx
  10. 8 6
      apps/insights/src/server/pyth.ts
  11. 1 7
      apps/insights/src/services/pyth/get-metadata.ts
  12. 1 0
      apps/insights/src/static-data/price-feeds.tsx
  13. 13 3
      contract_manager/scripts/common.ts
  14. 1 1
      contract_manager/scripts/deploy_evm_lazer_contracts.ts
  15. 16 3
      contract_manager/scripts/transfer_balance_entropy_chains.ts
  16. 2 2
      doc/code-guidelines.md
  17. 1 1
      doc/rust-code-guidelines.md
  18. 1 0
      governance/xc_admin/packages/xc_admin_frontend/next-env.d.ts
  19. 3 2
      lazer/contracts/solana/Cargo.lock
  20. 1 1
      lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml
  21. 2 2
      lazer/publisher_sdk/rust/Cargo.toml
  22. 2 2
      lazer/sdk/rust/client/Cargo.toml
  23. 1 1
      lazer/sdk/rust/protocol/Cargo.toml
  24. 43 38
      lazer/sdk/rust/protocol/src/price.rs
  25. 6 2
      lazer/sdk/rust/protocol/src/price/tests.rs
  26. 10 10
      lazer/sdk/rust/protocol/src/rate.rs
  27. 2 581
      pnpm-lock.yaml
  28. 70 23
      target_chains/ethereum/contracts/.env.template
  29. 37 17
      target_chains/ethereum/contracts/.env.test
  30. 1 0
      target_chains/ethereum/contracts/.gitignore
  31. 59 14
      target_chains/ethereum/contracts/README.md
  32. 10 33
      target_chains/ethereum/contracts/VERIFY.md
  33. 0 14
      target_chains/ethereum/contracts/contracts/Migrations.sol
  34. 1 1
      target_chains/ethereum/contracts/contracts/pulse/README.md
  35. 5 2
      target_chains/ethereum/contracts/deploy.sh
  36. 8 3
      target_chains/ethereum/contracts/foundry.toml
  37. 0 6
      target_chains/ethereum/contracts/migrations/test/1_initial_migration.js
  38. 0 34
      target_chains/ethereum/contracts/migrations/test/2_deploy_wormhole.js
  39. 0 47
      target_chains/ethereum/contracts/migrations/test/3_deploy_pyth.js
  40. 9 14
      target_chains/ethereum/contracts/package.json
  41. 0 1
      target_chains/ethereum/contracts/remappings.txt
  42. 122 0
      target_chains/ethereum/contracts/script/Deploy.s.sol
  43. 14 0
      target_chains/ethereum/contracts/script/ReceiverSubmitGuardianSetUpgrades.s.sol
  44. 70 0
      target_chains/ethereum/contracts/script/utils/VaaUtils.sol
  45. 0 23
      target_chains/ethereum/contracts/scripts/assertVaaPayloadEquals.js
  46. 0 103
      target_chains/ethereum/contracts/scripts/createLocalnetGovernanceVaa.js
  47. 28 0
      target_chains/ethereum/contracts/setup-foundry.sh
  48. 0 0
      target_chains/ethereum/contracts/test/Echo.t.sol
  49. 0 0
      target_chains/ethereum/contracts/test/EchoGasBenchmark.t.sol
  50. 0 0
      target_chains/ethereum/contracts/test/Entropy.t.sol
  51. 0 0
      target_chains/ethereum/contracts/test/EntropyAuthorized.t.sol
  52. 0 0
      target_chains/ethereum/contracts/test/EntropyGasBenchmark.t.sol
  53. 0 0
      target_chains/ethereum/contracts/test/Executor.t.sol
  54. 0 0
      target_chains/ethereum/contracts/test/GasBenchmark.t.sol
  55. 0 0
      target_chains/ethereum/contracts/test/PRNG.t.sol
  56. 0 0
      target_chains/ethereum/contracts/test/PulseScheduler.t.sol
  57. 0 0
      target_chains/ethereum/contracts/test/PulseSchedulerGasBenchmark.t.sol
  58. 0 0
      target_chains/ethereum/contracts/test/PulseSchedulerGovernance.t.sol
  59. 0 0
      target_chains/ethereum/contracts/test/Pyth.Aave.t.sol
  60. 0 0
      target_chains/ethereum/contracts/test/Pyth.WormholeMerkleAccumulator.t.sol
  61. 0 0
      target_chains/ethereum/contracts/test/Pyth.t.sol
  62. 0 3
      target_chains/ethereum/contracts/test/PythGovernance.t.sol
  63. 0 0
      target_chains/ethereum/contracts/test/VerificationExperiments.t.sol
  64. 0 895
      target_chains/ethereum/contracts/test/pyth.js
  65. 0 0
      target_chains/ethereum/contracts/test/utils/EchoTestUtils.sol
  66. 0 0
      target_chains/ethereum/contracts/test/utils/EntropyTestUtils.t.sol
  67. 0 0
      target_chains/ethereum/contracts/test/utils/InvalidMagic.t.sol
  68. 0 0
      target_chains/ethereum/contracts/test/utils/MockPriceFeedTestUtils.sol
  69. 0 0
      target_chains/ethereum/contracts/test/utils/PulseSchedulerTestUtils.t.sol
  70. 0 0
      target_chains/ethereum/contracts/test/utils/PythTestUtils.t.sol
  71. 0 0
      target_chains/ethereum/contracts/test/utils/RandTestUtils.t.sol
  72. 0 0
      target_chains/ethereum/contracts/test/utils/WormholeTestUtils.t.sol
  73. 0 52
      target_chains/ethereum/contracts/truffle-config.js
  74. 33 43
      target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol

+ 7 - 0
.github/workflows/ci-turbo-build.yml

@@ -26,10 +26,17 @@ jobs:
       # precompiled binary isn't found.
       - name: Install libusb
         run: sudo apt-get update && sudo apt-get install -y libusb-1.0-0-dev libudev-dev
+      # Install Foundry for Ethereum contract builds
+      - name: Install Foundry
+        uses: foundry-rs/foundry-toolchain@v1
+        with:
+          version: v0.3.0
       - uses: pnpm/action-setup@v4
         name: Install pnpm
         with:
           run_install: true
+      - name: Install Forge dependencies
+        run: cd target_chains/ethereum/contracts && pnpm run install-forge-deps
       - name: Cache for Turbo
         uses: rharkor/caching-for-turbo@v1.5
       - name: Build

+ 8 - 0
.github/workflows/ci-turbo-test.yml

@@ -17,6 +17,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v4
+      - uses: actions-rust-lang/setup-rust-toolchain@v1
       - uses: actions/setup-node@v4
         with:
           node-version-file: "package.json"
@@ -25,10 +26,17 @@ jobs:
       # precompiled binary isn't found.
       - name: Install libusb
         run: sudo apt-get update && sudo apt-get install -y libusb-1.0-0-dev libudev-dev
+      # Install Foundry for Ethereum contract tests
+      - name: Install Foundry
+        uses: foundry-rs/foundry-toolchain@v1
+        with:
+          version: v0.3.0
       - uses: pnpm/action-setup@v4
         name: Install pnpm
         with:
           run_install: true
+      - name: Install Forge dependencies
+        run: cd target_chains/ethereum/contracts && pnpm run install-forge-deps
       - name: Cache for Turbo
         uses: rharkor/caching-for-turbo@v1.5
       - name: Test

+ 9 - 1
.github/workflows/publish-js.yml

@@ -9,7 +9,8 @@ jobs:
     name: Publish Javascript Packages to NPM
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
+      - uses: actions-rust-lang/setup-rust-toolchain@v1
       - uses: actions/setup-node@v4
         with:
           node-version-file: "package.json"
@@ -18,10 +19,17 @@ jobs:
       # precompiled binary isn't found.
       - name: Install libusb
         run: sudo apt-get update && sudo apt-get install -y libusb-1.0-0-dev libudev-dev
+      # Install Foundry for Ethereum contract builds
+      - name: Install Foundry
+        uses: foundry-rs/foundry-toolchain@v1
+        with:
+          version: v0.3.0
       - uses: pnpm/action-setup@v4
         name: Install pnpm
         with:
           run_install: true
+      - name: Install Forge dependencies
+        run: cd target_chains/ethereum/contracts && pnpm run install-forge-deps
       - name: Set publishing config
         run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}"
         env:

+ 5 - 5
Cargo.lock

@@ -5675,7 +5675,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-client"
-version = "5.0.0"
+version = "6.0.0"
 dependencies = [
  "alloy-primitives 0.8.25",
  "anyhow",
@@ -5688,7 +5688,7 @@ dependencies = [
  "futures-util",
  "hex",
  "libsecp256k1 0.7.2",
- "pyth-lazer-protocol 0.13.0",
+ "pyth-lazer-protocol 0.14.0",
  "serde",
  "serde_json",
  "tokio",
@@ -5721,7 +5721,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-protocol"
-version = "0.13.0"
+version = "0.14.0"
 dependencies = [
  "alloy-primitives 0.8.25",
  "anyhow",
@@ -5761,13 +5761,13 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-publisher-sdk"
-version = "0.8.0"
+version = "0.9.0"
 dependencies = [
  "anyhow",
  "fs-err",
  "protobuf",
  "protobuf-codegen",
- "pyth-lazer-protocol 0.13.0",
+ "pyth-lazer-protocol 0.14.0",
  "serde_json",
 ]
 

+ 1 - 0
apps/developer-hub/next-env.d.ts

@@ -1,5 +1,6 @@
 /// <reference types="next" />
 /// <reference types="next/image-types/global" />
+/// <reference path="./.next/types/routes.d.ts" />
 
 // NOTE: This file should not be edited
 // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

+ 11 - 9
apps/insights/src/cache.ts

@@ -9,13 +9,20 @@ const transformer = {
   deserialize: parse,
 };
 
-export const DEFAULT_CACHE_TTL = 3600; // 1 hour
-export const DEFAULT_CACHE_STALE = 86_400; // 24 hours
+/**
+ * - API routes will be cached for 1 hour
+ * - Cached function will be cached for 10 minutes,
+ * If the function is called within 1 hour, it will
+ * still be served from the cache, but also fetch the latest data
+ */
+export const DEFAULT_NEXT_FETCH_TTL = 3600; // 1 hour
+export const DEFAULT_REDIS_CACHE_TTL = 60 * 10; // 10 minutes
+export const DEFAULT_REDIS_CACHE_STALE = 3600; // 1 hour
 
 export const redisCache: ACDCache = createCache({
   transformer,
-  stale: DEFAULT_CACHE_STALE,
-  ttl: DEFAULT_CACHE_TTL,
+  stale: DEFAULT_REDIS_CACHE_STALE,
+  ttl: DEFAULT_REDIS_CACHE_TTL,
   storage: {
     type: "redis",
     options: {
@@ -23,8 +30,3 @@ export const redisCache: ACDCache = createCache({
     },
   },
 });
-
-export const memoryOnlyCache: ACDCache = createCache({
-  ttl: DEFAULT_CACHE_TTL,
-  stale: DEFAULT_CACHE_STALE,
-});

+ 4 - 0
apps/insights/src/components/PriceFeedIcon/eco.svg

@@ -0,0 +1,4 @@
+<svg width="1em" height="1em" viewBox="0 0 36 36" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
+  <path d="M9.75 9a.75.75 0 0 0-.75.75v8.371a4.704 4.877 0 0 1 1.5-.814V9.75A.75.75 0 0 0 9.75 9zm12 2.25a.75.75 0 0 0 0 1.5h1.94l-4.94 4.94-2.469-2.471a.75.75 0 0 0-1.004-.051l-.058.05-2.047 2.05a4.704 4.877 0 0 1 1.422.699l1.156-1.156 2.469 2.47a.752.752 0 0 0 1.062 0l5.469-5.47v1.939a.75.75 0 0 0 1.5 0V12a.754.754 0 0 0-.014-.133.74.74 0 0 0-.043-.154c-.012-.03-.029-.055-.045-.082a.751.751 0 0 0-.117-.162.74.74 0 0 0-.162-.117c-.027-.016-.055-.033-.084-.045-.032-.014-.064-.021-.098-.03-.013-.003-.025-.01-.039-.013l-.017-.002a.742.742 0 0 0-.131-.012h-3.75zM16.172 24a4.704 4.877 0 0 1-1.035 1.5H27.75a.75.75 0 0 0 0-1.5H16.172z"/>
+  <path d="M12 17a5 5 0 1 0 0 9.999A5 5 0 0 0 12 17Zm.577 7.308h-.192v.384a.385.385 0 0 1-.77 0v-.384h-.769a.384.384 0 1 1 0-.77h1.73a.577.577 0 0 0 0-1.153h-1.153a1.346 1.346 0 1 1 0-2.693h.192v-.384a.385.385 0 0 1 .77 0v.384h.769a.384.384 0 1 1 0 .77h-1.73a.577.577 0 0 0 0 1.153h1.153a1.346 1.346 0 1 1 0 2.693Z"/>
+</svg>

+ 5 - 0
apps/insights/src/components/PriceFeedIcon/index.module.scss

@@ -42,4 +42,9 @@
     background: theme.pallette-color("emerald", 300);
     fill: theme.pallette-color("stone", 700);
   }
+
+  &[data-asset-class="ECO"] {
+    background: theme.pallette-color("sky", 200);
+    fill: theme.pallette-color("sky", 800);
+  }
 }

+ 21 - 13
apps/insights/src/components/PriceFeedIcon/index.tsx

@@ -4,6 +4,7 @@ import Commodities from "./commodities.svg";
 import CryptoIndex from "./crypto-index.svg";
 import CryptoRedemptionRate from "./crypto-redemption-rate.svg";
 import Crypto from "./crypto.svg";
+import Eco from "./eco.svg";
 import Equity from "./equity.svg";
 import Fx from "./fx.svg";
 import { icons } from "./icons";
@@ -19,19 +20,25 @@ type Props = Omit<SVGProps, keyof OwnProps | "width" | "height" | "viewBox"> &
   OwnProps;
 
 export const PriceFeedIcon = ({ assetClass, symbol, ...props }: Props) => {
-  if (assetClass === "Crypto") {
-    const firstPart = symbol.split(".")[1]?.split("/")[0];
-    const Icon = firstPart ? (icons as SVGRecord)[firstPart] : undefined;
-    return Icon ? (
-      <Icon width="100%" height="100%" viewBox="0 0 32 32" {...props} />
-    ) : (
-      <GenericIcon assetClass="Crypto" {...props} />
-    );
-  } else {
-    return assetClassHasIcon(assetClass) ? (
-      <GenericIcon assetClass={assetClass} {...props} />
-    ) : // eslint-disable-next-line unicorn/no-null
-    null;
+  switch (assetClass) {
+    case "Crypto": {
+      const firstPart = symbol.split(".")[1]?.split("/")[0];
+      const Icon = firstPart ? (icons as SVGRecord)[firstPart] : undefined;
+      return Icon ? (
+        <Icon width="100%" height="100%" viewBox="0 0 32 32" {...props} />
+      ) : (
+        <GenericIcon assetClass="Crypto" {...props} />
+      );
+    }
+    case "Crypto NAV": {
+      return <GenericIcon assetClass="Crypto" {...props} />;
+    }
+    default: {
+      return assetClassHasIcon(assetClass) ? (
+        <GenericIcon assetClass={assetClass} {...props} />
+      ) : // eslint-disable-next-line unicorn/no-null
+      null;
+    }
   }
 };
 
@@ -64,6 +71,7 @@ const ASSET_CLASS_TO_ICON = {
   "Crypto Index": CryptoIndex,
   "Crypto Redemption Rate": CryptoRedemptionRate,
   Crypto,
+  ECO: Eco,
   Equity,
   FX: Fx,
   Metal,

+ 8 - 6
apps/insights/src/server/pyth.ts

@@ -1,7 +1,7 @@
 import { parse } from "superjson";
 import { z } from "zod";
 
-import { DEFAULT_CACHE_TTL } from "../cache";
+import { DEFAULT_NEXT_FETCH_TTL } from "../cache";
 import { VERCEL_REQUEST_HEADERS } from "../config/server";
 import { getHost } from "../get-host";
 import { priceFeedsSchema } from "../schemas/pyth/price-feeds-schema";
@@ -19,7 +19,7 @@ export async function getPublishersForFeedRequest(
 
   const data = await fetch(url, {
     next: {
-      revalidate: DEFAULT_CACHE_TTL,
+      revalidate: DEFAULT_NEXT_FETCH_TTL,
     },
     headers: VERCEL_REQUEST_HEADERS,
   });
@@ -39,7 +39,7 @@ export async function getFeedsForPublisherRequest(
 
   const data = await fetch(url, {
     next: {
-      revalidate: DEFAULT_CACHE_TTL,
+      revalidate: DEFAULT_NEXT_FETCH_TTL,
     },
     headers: VERCEL_REQUEST_HEADERS,
   });
@@ -55,7 +55,7 @@ export const getFeedsRequest = async (cluster: Cluster) => {
 
   const data = await fetch(url, {
     next: {
-      revalidate: DEFAULT_CACHE_TTL,
+      revalidate: DEFAULT_NEXT_FETCH_TTL,
     },
     headers: VERCEL_REQUEST_HEADERS,
   });
@@ -83,7 +83,7 @@ export const getFeedForSymbolRequest = async ({
 
   const data = await fetch(url, {
     next: {
-      revalidate: DEFAULT_CACHE_TTL,
+      revalidate: DEFAULT_NEXT_FETCH_TTL,
     },
     headers: VERCEL_REQUEST_HEADERS,
   });
@@ -94,5 +94,7 @@ export const getFeedForSymbolRequest = async ({
 
   const rawData = await data.text();
   const parsedData = parse(rawData);
-  return priceFeedsSchema.element.parse(parsedData);
+  return parsedData === undefined
+    ? undefined
+    : priceFeedsSchema.element.parse(parsedData);
 };

+ 1 - 7
apps/insights/src/services/pyth/get-metadata.ts

@@ -1,11 +1,5 @@
 import { clients, Cluster } from ".";
-import { memoryOnlyCache } from "../../cache";
 
-const _getPythMetadata = async (cluster: Cluster) => {
+export const getPythMetadata = async (cluster: Cluster) => {
   return clients[cluster].getData();
 };
-
-export const getPythMetadata = memoryOnlyCache.define(
-  "getPythMetadata",
-  _getPythMetadata,
-).getPythMetadata;

+ 1 - 0
apps/insights/src/static-data/price-feeds.tsx

@@ -1,6 +1,7 @@
 export const priceFeeds = {
   updateFrequency: "400ms",
   featuredFeeds: [
+    "ECO.US.GDP",
     "Crypto.ARC/USD",
     "Crypto.CDXUSD/USD",
     "Equity.GB.CSPX/USD",

+ 13 - 3
contract_manager/scripts/common.ts

@@ -35,7 +35,14 @@ export async function deployIfNotCached(
   const key = cacheKey ?? `${chain.getId()}-${artifactName}`;
   return runIfNotCached(key, async () => {
     const artifact = JSON.parse(
-      readFileSync(join(config.jsonOutputDir, `${artifactName}.json`), "utf8"),
+      readFileSync(
+        join(
+          config.jsonOutputDir,
+          `${artifactName}.sol`,
+          `${artifactName}.json`,
+        ),
+        "utf8",
+      ),
     );
 
     // Handle bytecode which can be either a string or an object with an 'object' property
@@ -74,7 +81,10 @@ export function getWeb3Contract(
   address: string,
 ): Contract {
   const artifact = JSON.parse(
-    readFileSync(join(jsonOutputDir, `${artifactName}.json`), "utf8"),
+    readFileSync(
+      join(jsonOutputDir, `${artifactName}.sol`, `${artifactName}.json`),
+      "utf8",
+    ),
   );
   const web3 = new Web3();
   return new web3.eth.Contract(artifact["abi"], address);
@@ -84,7 +94,7 @@ export const COMMON_DEPLOY_OPTIONS = {
   "std-output-dir": {
     type: "string",
     demandOption: true,
-    desc: "Path to the standard JSON output of the contracts (build artifact) directory",
+    desc: "Path to the Foundry output directory (contains Contract.sol/Contract.json structure)",
   },
   "private-key": {
     type: "string",

+ 1 - 1
contract_manager/scripts/deploy_evm_lazer_contracts.ts

@@ -323,7 +323,7 @@ export async function main() {
       console.log(`\n✅ Contract deployed at: ${deployedAddress}`);
       console.log(`Trusted signer updated: ${argv["update-signer"]}`);
       console.log(
-        `Expires at: ${new Date(argv["expires-at"]! * 1000).toISOString()}`,
+        `Expires at: ${new Date((argv["expires-at"] ?? 0) * 1000).toISOString()}`,
       );
     } else if (argv.deploy) {
       console.log(`Contract deployed at ${deployedAddress}`);

+ 16 - 3
contract_manager/scripts/transfer_balance_entropy_chains.ts

@@ -157,7 +157,12 @@ async function transferOnChain(
       transferAmountEth = transferAmount;
     } else {
       // transferRatio is guaranteed to be defined at this point
-      transferAmountEth = (balanceEth - gasCostEth) * transferRatio!;
+      if (transferRatio === undefined) {
+        throw new Error(
+          "Transfer ratio must be defined when amount is not specified",
+        );
+      }
+      transferAmountEth = (balanceEth - gasCostEth) * transferRatio;
     }
 
     // Round to 10 decimal places to avoid Web3 conversion errors
@@ -299,7 +304,12 @@ function getSelectedChains(argv: {
     );
     selectedChains = [];
 
-    for (const chainId of argv.chain!) {
+    if (!argv.chain) {
+      throw new Error(
+        "Chain argument must be defined for specific chain selection",
+      );
+    }
+    for (const chainId of argv.chain) {
       if (!entropyChainIds.has(chainId)) {
         throw new Error(
           `Chain ${chainId} does not have entropy contracts deployed`,
@@ -360,7 +370,10 @@ async function main() {
   if (argv.amount !== undefined) {
     transferMethod = `${argv.amount} ETH (fixed amount)`;
   } else {
-    transferMethod = `${(argv.ratio! * 100).toFixed(1)}% of available balance`;
+    if (argv.ratio === undefined) {
+      throw new Error("Ratio must be defined when amount is not specified");
+    }
+    transferMethod = `${(argv.ratio * 100).toFixed(1)}% of available balance`;
   }
 
   console.log(`\nConfiguration:`);

+ 2 - 2
doc/code-guidelines.md

@@ -76,7 +76,7 @@
       ```tsx
       function handleEvent(e) {
           // Many inlined state tracking vars. Not much better than globals.
-          var latstPythNetupdateTime = DateTime.now();
+          var latestPythNetUpdateTime = DateTime.now();
           var clientsWaiting         = {};
           var ...
 
@@ -110,7 +110,7 @@
       // main.ts
       const { db } = require('db');
       function() {
-          initDb(); // Databaes not passed, implies global use.
+          initDb(); // Database not passed, implies global use.
       }
 
       ```

+ 1 - 1
doc/rust-code-guidelines.md

@@ -60,7 +60,7 @@ The recommendations on this page should help with dealing with these lints.
 
 Refer also to [Clippy lints documentation](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about the lints.
 
-If the lint is a false positive, put `#[allow(lint_name, reason = "...")]` on the relevant line or block and and specify the reason why the code is correct.
+If the lint is a false positive, put `#[allow(lint_name, reason = "...")]` on the relevant line or block and specify the reason why the code is correct.
 
 Many of the lints (e.g. most of the panic-related lints) can be allowed globally for tests and other non-production code.
 

+ 1 - 0
governance/xc_admin/packages/xc_admin_frontend/next-env.d.ts

@@ -1,5 +1,6 @@
 /// <reference types="next" />
 /// <reference types="next/image-types/global" />
+/// <reference path="./.next/types/routes.d.ts" />
 
 // NOTE: This file should not be edited
 // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.

+ 3 - 2
lazer/contracts/solana/Cargo.lock

@@ -3293,7 +3293,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-protocol"
-version = "0.10.2"
+version = "0.12.0"
 dependencies = [
  "anyhow",
  "byteorder",
@@ -3307,11 +3307,12 @@ dependencies = [
  "rust_decimal",
  "serde",
  "serde_json",
+ "thiserror 2.0.12",
 ]
 
 [[package]]
 name = "pyth-lazer-solana-contract"
-version = "0.5.0"
+version = "0.6.0"
 dependencies = [
  "anchor-lang",
  "bytemuck",

+ 1 - 1
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.13.0" }
+pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.14.0" }
 
 anchor-lang = "0.31.1"
 bytemuck = { version = "1.20.0", features = ["derive"] }

+ 2 - 2
lazer/publisher_sdk/rust/Cargo.toml

@@ -1,13 +1,13 @@
 [package]
 name = "pyth-lazer-publisher-sdk"
-version = "0.8.0"
+version = "0.9.0"
 edition = "2021"
 description = "Pyth Lazer Publisher SDK types."
 license = "Apache-2.0"
 repository = "https://github.com/pyth-network/pyth-crosschain"
 
 [dependencies]
-pyth-lazer-protocol = { version = "0.13.0", path = "../../sdk/rust/protocol" }
+pyth-lazer-protocol = { version = "0.14.0", path = "../../sdk/rust/protocol" }
 anyhow = "1.0.98"
 protobuf = "3.7.2"
 serde_json = "1.0.140"

+ 2 - 2
lazer/sdk/rust/client/Cargo.toml

@@ -1,12 +1,12 @@
 [package]
 name = "pyth-lazer-client"
-version = "5.0.0"
+version = "6.0.0"
 edition = "2021"
 description = "A Rust client for Pyth Lazer"
 license = "Apache-2.0"
 
 [dependencies]
-pyth-lazer-protocol = { path = "../protocol", version = "0.13.0" }
+pyth-lazer-protocol = { path = "../protocol", version = "0.14.0" }
 tokio = { version = "1", features = ["full"] }
 tokio-tungstenite = { version = "0.20", features = ["native-tls"] }
 futures-util = "0.3"

+ 1 - 1
lazer/sdk/rust/protocol/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "pyth-lazer-protocol"
-version = "0.13.0"
+version = "0.14.0"
 edition = "2021"
 description = "Pyth Lazer SDK - protocol types."
 license = "Apache-2.0"

+ 43 - 38
lazer/sdk/rust/protocol/src/price.rs

@@ -27,17 +27,17 @@ pub struct Price(NonZeroI64);
 
 impl Price {
     pub fn from_integer(value: i64, exponent: i16) -> Result<Price, PriceError> {
-        let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
+        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
             ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(PriceError::Overflow)?,
             ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(PriceError::Overflow)?,
         };
-        let value = NonZeroI64::new(value).ok_or(PriceError::ZeroPriceUnsupported)?;
-        Ok(Self(value))
+        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?;
+        Ok(Self(mantissa))
     }
 
     pub fn parse_str(value: &str, exponent: i16) -> Result<Price, PriceError> {
         let value: Decimal = value.parse()?;
-        let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
+        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
             ExponentFactor::Mul(coef) => value
                 .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
                 .ok_or(PriceError::Overflow)?,
@@ -45,12 +45,12 @@ impl Price {
                 .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
                 .ok_or(PriceError::Overflow)?,
         };
-        if !value.is_integer() {
+        if !mantissa.is_integer() {
             return Err(PriceError::TooPrecise);
         }
-        let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?;
-        let value = NonZeroI64::new(value).ok_or(PriceError::Overflow)?;
-        Ok(Self(value))
+        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
+        let mantissa = NonZeroI64::new(mantissa).ok_or(PriceError::Overflow)?;
+        Ok(Self(mantissa))
     }
 
     pub const fn from_nonzero_mantissa(mantissa: NonZeroI64) -> Self {
@@ -58,8 +58,8 @@ impl Price {
     }
 
     pub const fn from_mantissa(mantissa: i64) -> Result<Self, PriceError> {
-        if let Some(value) = NonZeroI64::new(mantissa) {
-            Ok(Self(value))
+        if let Some(mantissa) = NonZeroI64::new(mantissa) {
+            Ok(Self(mantissa))
         } else {
             Err(PriceError::ZeroPriceUnsupported)
         }
@@ -75,7 +75,7 @@ impl Price {
 
     pub fn to_f64(self, exponent: i16) -> Result<f64, PriceError> {
         match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
-            // Mul/div is reversed for this conversion
+            // Mul/div is reversed for converting mantissa to value
             ExponentFactor::Mul(coef) => Ok(self.0.get() as f64 / coef as f64),
             ExponentFactor::Div(coef) => Ok(self.0.get() as f64 * coef as f64),
         }
@@ -83,7 +83,7 @@ impl Price {
 
     pub fn from_f64(value: f64, exponent: i16) -> Result<Self, PriceError> {
         let value = Decimal::from_f64(value).ok_or(PriceError::Overflow)?;
-        let value = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
+        let mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
             ExponentFactor::Mul(coef) => value
                 .checked_mul(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
                 .ok_or(PriceError::Overflow)?,
@@ -91,65 +91,70 @@ impl Price {
                 .checked_div(Decimal::from_i64(coef).ok_or(PriceError::Overflow)?)
                 .ok_or(PriceError::Overflow)?,
         };
-        let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?;
+        let mantissa: i64 = mantissa.try_into().map_err(|_| PriceError::Overflow)?;
         Ok(Self(
-            NonZeroI64::new(value).ok_or(PriceError::ZeroPriceUnsupported)?,
+            NonZeroI64::new(mantissa).ok_or(PriceError::ZeroPriceUnsupported)?,
         ))
     }
 
-    pub fn add_with_same_mantissa(self, other: Price) -> Result<Self, PriceError> {
-        let value = self
+    pub fn add_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
+        let mantissa = self
             .0
             .get()
             .checked_add(other.0.get())
             .ok_or(PriceError::Overflow)?;
-        Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported)
+        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
     }
 
-    pub fn sub_with_same_mantissa(self, other: Price) -> Result<Self, PriceError> {
-        let value = self
+    pub fn sub_with_same_exponent(self, other: Price) -> Result<Self, PriceError> {
+        let mantissa = self
             .0
             .get()
             .checked_sub(other.0.get())
             .ok_or(PriceError::Overflow)?;
-        Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported)
+        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
     }
 
     pub fn mul_integer(self, factor: i64) -> Result<Self, PriceError> {
-        let value = self
+        let mantissa = self
             .0
             .get()
             .checked_mul(factor)
             .ok_or(PriceError::Overflow)?;
-        Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported)
+        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
     }
 
     pub fn div_integer(self, factor: i64) -> Result<Self, PriceError> {
-        let value = self
+        let mantissa = self
             .0
             .get()
             .checked_div(factor)
             .ok_or(PriceError::Overflow)?;
-        Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported)
+        Self::from_mantissa(mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
     }
 
-    pub fn mul_decimal(self, mantissa: i64, rhs_exponent: i16) -> Result<Self, PriceError> {
-        let left_value = i128::from(self.0.get());
-        let right_value = i128::from(mantissa);
+    pub fn mul_decimal(self, mantissa: i64, exponent: i16) -> Result<Self, PriceError> {
+        let left_mantissa = i128::from(self.0.get());
+        let right_mantissa = i128::from(mantissa);
 
-        let value = left_value
-            .checked_mul(right_value)
+        // multiplied_mantissas = left_mantissa * right_mantissa
+        let multiplied_mantissas = left_mantissa
+            .checked_mul(right_mantissa)
             .ok_or(PriceError::Overflow)?;
 
-        let value = match ExponentFactor::get(rhs_exponent).ok_or(PriceError::Overflow)? {
-            ExponentFactor::Mul(coef) => {
-                value.checked_div(coef.into()).ok_or(PriceError::Overflow)?
-            }
-            ExponentFactor::Div(coef) => {
-                value.checked_mul(coef.into()).ok_or(PriceError::Overflow)?
-            }
+        // result_mantissa = left_mantissa * right_mantissa * 10^exponent
+        // Mul/div is reversed for multiplying 10^exponent
+        let result_mantissa = match ExponentFactor::get(exponent).ok_or(PriceError::Overflow)? {
+            ExponentFactor::Mul(coef) => multiplied_mantissas
+                .checked_div(coef.into())
+                .ok_or(PriceError::Overflow)?,
+            ExponentFactor::Div(coef) => multiplied_mantissas
+                .checked_mul(coef.into())
+                .ok_or(PriceError::Overflow)?,
         };
-        let value: i64 = value.try_into().map_err(|_| PriceError::Overflow)?;
-        Self::from_mantissa(value).map_err(|_| PriceError::ZeroPriceUnsupported)
+        let result_mantissa: i64 = result_mantissa
+            .try_into()
+            .map_err(|_| PriceError::Overflow)?;
+        Self::from_mantissa(result_mantissa).map_err(|_| PriceError::ZeroPriceUnsupported)
     }
 }

+ 6 - 2
lazer/sdk/rust/protocol/src/price/tests.rs

@@ -105,7 +105,7 @@ fn price_ops() {
     let price2 = Price::parse_str("23.45", -8).unwrap();
     assert_float_absolute_eq!(
         price1
-            .add_with_same_mantissa(price2)
+            .add_with_same_exponent(price2)
             .unwrap()
             .to_f64(-8)
             .unwrap(),
@@ -113,7 +113,7 @@ fn price_ops() {
     );
     assert_float_absolute_eq!(
         price1
-            .sub_with_same_mantissa(price2)
+            .sub_with_same_exponent(price2)
             .unwrap()
             .to_f64(-8)
             .unwrap(),
@@ -133,6 +133,10 @@ fn price_ops() {
         12.34 * 34.56
     );
 
+    assert_eq!(
+        price1.mul_decimal(34, 2).unwrap().mantissa_i64(),
+        1234000000 * 3400
+    );
     let price2 = Price::parse_str("42_000", 3).unwrap();
     assert_float_absolute_eq!(
         price2.mul_integer(2).unwrap().to_f64(3).unwrap(),

+ 10 - 10
lazer/sdk/rust/protocol/src/rate.rs

@@ -24,16 +24,16 @@ pub struct Rate(i64);
 
 impl Rate {
     pub fn from_integer(value: i64, exponent: i16) -> Result<Self, RateError> {
-        let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
+        let mantissa = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
             ExponentFactor::Mul(coef) => value.checked_mul(coef).ok_or(RateError::Overflow)?,
             ExponentFactor::Div(coef) => value.checked_div(coef).ok_or(RateError::Overflow)?,
         };
-        Ok(Self(value))
+        Ok(Self(mantissa))
     }
 
     pub fn parse_str(value: &str, exponent: i16) -> Result<Self, RateError> {
         let value: Decimal = value.parse()?;
-        let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
+        let mantissa = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
             ExponentFactor::Mul(coef) => value
                 .checked_mul(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
                 .ok_or(RateError::Overflow)?,
@@ -41,11 +41,11 @@ impl Rate {
                 .checked_div(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
                 .ok_or(RateError::Overflow)?,
         };
-        if !value.is_integer() {
+        if !mantissa.is_integer() {
             return Err(RateError::TooPrecise);
         }
-        let value: i64 = value.try_into().map_err(|_| RateError::Overflow)?;
-        Ok(Self(value))
+        let mantissa: i64 = mantissa.try_into().map_err(|_| RateError::Overflow)?;
+        Ok(Self(mantissa))
     }
 
     pub const fn from_mantissa(mantissa: i64) -> Self {
@@ -54,7 +54,7 @@ impl Rate {
 
     pub fn from_f64(value: f64, exponent: i16) -> Result<Self, RateError> {
         let value = Decimal::from_f64(value).ok_or(RateError::Overflow)?;
-        let value = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
+        let mantissa = match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
             ExponentFactor::Mul(coef) => value
                 .checked_mul(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
                 .ok_or(RateError::Overflow)?,
@@ -62,8 +62,8 @@ impl Rate {
                 .checked_div(Decimal::from_i64(coef).ok_or(RateError::Overflow)?)
                 .ok_or(RateError::Overflow)?,
         };
-        let value: i64 = value.try_into().map_err(|_| RateError::Overflow)?;
-        Ok(Self(value))
+        let mantissa: i64 = mantissa.try_into().map_err(|_| RateError::Overflow)?;
+        Ok(Self(mantissa))
     }
 
     pub fn mantissa(self) -> i64 {
@@ -72,7 +72,7 @@ impl Rate {
 
     pub fn to_f64(self, exponent: i16) -> Result<f64, RateError> {
         match ExponentFactor::get(exponent).ok_or(RateError::Overflow)? {
-            // Mul/div is reversed for this conversion
+            // Mul/div is reversed for converting mantissa to value
             ExponentFactor::Mul(coef) => Ok(self.0 as f64 / coef as f64),
             ExponentFactor::Div(coef) => Ok(self.0 as f64 * coef as f64),
         }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 2 - 581
pnpm-lock.yaml


+ 70 - 23
target_chains/ethereum/contracts/.env.template

@@ -1,23 +1,70 @@
-# Network Config
-MIGRATIONS_DIR=             # ./migrations/prod-receiver
-MIGRATIONS_NETWORK=         # xyz
-MAINNET=
-
-# The duration that a price feed stored in the contract is considered to be
-# valid, after this duration, the price feed is stale and will be invalid.
-# This value should derive from Pyth to wormhole latency, and target chain blocktime and latency.
-VALID_TIME_PERIOD_SECONDS= # 60
-
-WORMHOLE_CHAIN_NAME= # ethereum, defined in <repo-root>/governance/xc_admin/packages/xc_admin_common/src/chains.ts
-
-CLUSTER= #mainnet/testnet The configs below are read from the cluster file
-
-# Pyth Migrations           # Example Format. If deployed on mainnet/testnet it is available in env.cluster.{cluster}
-SOLANA_CHAIN_ID=  # 0x1
-SOLANA_EMITTER=   # 0xf346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0
-PYTHNET_CHAIN_ID= # 0x1a
-PYTHNET_EMITTER= # 0xa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6
-GOVERNANCE_CHAIN_ID= # 0x1
-GOVERNANCE_EMITTER= # 0x63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385
-GOVERNANCE_INITIAL_SEQUENCE=0 # This is optional and default is 0
-SINGLE_UPDATE_FEE_IN_WEI=0
+# =============================================================================
+# FOUNDRY DEPLOYMENT CONFIGURATION TEMPLATE
+# Copy this file to .env and fill in your actual values
+# =============================================================================
+
+# Deployment Configuration
+# Example: PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+PRIVATE_KEY=YOUR_PRIVATE_KEY_HERE
+# Example: RPC_URL=https://rpc.ankr.com/eth (or http://localhost:8545 for local)
+RPC_URL=YOUR_RPC_URL_HERE
+# Example: ETHERSCAN_API_KEY=your_etherscan_api_key_here
+ETHERSCAN_API_KEY=YOUR_ETHERSCAN_API_KEY_HERE
+
+# =============================================================================
+# WORMHOLE CONFIGURATION  
+# =============================================================================
+
+# Initial Guardian Configuration (comma-separated addresses - Foundry native)
+# For single guardian: INIT_SIGNERS=0xYourGuardianAddress
+# For multiple guardians: INIT_SIGNERS=0xAddress1,0xAddress2,0xAddress3
+# Example: INIT_SIGNERS=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5
+INIT_SIGNERS=YOUR_GUARDIAN_ADDRESSES_HERE
+# Example: INIT_CHAIN_ID=1
+INIT_CHAIN_ID=YOUR_CHAIN_ID_HERE
+# Example: INIT_GOV_CHAIN_ID=1
+INIT_GOV_CHAIN_ID=YOUR_GOV_CHAIN_ID_HERE
+# Example: INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
+INIT_GOV_CONTRACT=YOUR_GOV_CONTRACT_ADDRESS_HERE
+
+# Guardian Set Sync Configuration (set after deployment)
+# Example: WORMHOLE_ADDRESS=0x0000000000000000000000000000000000000000
+WORMHOLE_ADDRESS=DEPLOYED_WORMHOLE_ADDRESS_HERE
+
+# =============================================================================
+# PYTH CONFIGURATION
+# =============================================================================
+
+# Data Source Configuration
+# Example: SOLANA_CHAIN_ID=1
+SOLANA_CHAIN_ID=YOUR_SOLANA_CHAIN_ID_HERE
+# Example: SOLANA_EMITTER=0xf346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0
+SOLANA_EMITTER=YOUR_SOLANA_EMITTER_ADDRESS_HERE
+# Example: PYTHNET_CHAIN_ID=26
+PYTHNET_CHAIN_ID=YOUR_PYTHNET_CHAIN_ID_HERE
+# Example: PYTHNET_EMITTER=0xa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6
+PYTHNET_EMITTER=YOUR_PYTHNET_EMITTER_ADDRESS_HERE
+
+# Governance Configuration
+# Example: GOVERNANCE_CHAIN_ID=1
+GOVERNANCE_CHAIN_ID=YOUR_GOVERNANCE_CHAIN_ID_HERE
+# Example: GOVERNANCE_EMITTER=0x63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385
+GOVERNANCE_EMITTER=YOUR_GOVERNANCE_EMITTER_ADDRESS_HERE
+# Example: GOVERNANCE_INITIAL_SEQUENCE=0
+GOVERNANCE_INITIAL_SEQUENCE=YOUR_INITIAL_SEQUENCE_HERE
+
+# Price Feed Configuration
+# Example: VALID_TIME_PERIOD_SECONDS=60
+VALID_TIME_PERIOD_SECONDS=YOUR_VALID_TIME_PERIOD_HERE
+# Example: SINGLE_UPDATE_FEE_IN_WEI=1000000000000000
+SINGLE_UPDATE_FEE_IN_WEI=YOUR_UPDATE_FEE_HERE
+
+# =============================================================================
+# LEGACY CONFIGURATION (for backward compatibility)
+# =============================================================================
+
+# Example: WORMHOLE_CHAIN_NAME=ethereum
+WORMHOLE_CHAIN_NAME=YOUR_CHAIN_NAME_HERE
+# Example: CLUSTER=mainnet
+CLUSTER=YOUR_CLUSTER_HERE
+

+ 37 - 17
target_chains/ethereum/contracts/.env.test

@@ -1,27 +1,47 @@
-# Migrations Metadata
-MIGRATIONS_DIR=./migrations/test
+# =============================================================================
+# FOUNDRY TEST CONFIGURATION
+# =============================================================================
 
-# Wormhole Core Migrations
-INIT_SIGNERS=["0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"]
-INIT_CHAIN_ID=0x2
-INIT_GOV_CHAIN_ID=0x1
+# Deployment Configuration
+PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
+RPC_URL=http://localhost:8545
+ETHERSCAN_API_KEY=test_api_key_not_needed_for_local
+
+# =============================================================================
+# WORMHOLE TEST CONFIGURATION  
+# =============================================================================
+
+# Guardian Configuration (comma-separated addresses)
+INIT_SIGNERS=0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5,0x025ceeba2ab2a27d53d963393999eeebe83dc4ae
+INIT_CHAIN_ID=2
+INIT_GOV_CHAIN_ID=1
 INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
 
-# Bridge Migrations
-BRIDGE_INIT_CHAIN_ID=0x2
-BRIDGE_INIT_GOV_CHAIN_ID=0x1
-BRIDGE_INIT_GOV_CONTRACT=0x0000000000000000000000000000000000000000000000000000000000000004
-BRIDGE_INIT_WETH=0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E
+# Guardian Set Sync Configuration (will be set after deployment)
+WORMHOLE_ADDRESS=0x0000000000000000000000000000000000000000
+
+# =============================================================================
+# PYTH TEST CONFIGURATION
+# =============================================================================
 
-#Pyth
-SOLANA_CHAIN_ID=0x1
+# Test Data Source Configuration
+SOLANA_CHAIN_ID=1
 SOLANA_EMITTER=0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b
+PYTHNET_CHAIN_ID=26
+PYTHNET_EMITTER=0xa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6
 
-VALID_TIME_PERIOD_SECONDS=60
+# Test Governance Configuration
+GOVERNANCE_CHAIN_ID=1
+GOVERNANCE_EMITTER=0x0000000000000000000000000000000000000000000000000000000000001234
+GOVERNANCE_INITIAL_SEQUENCE=0
 
+# Test Price Feed Configuration
+VALID_TIME_PERIOD_SECONDS=60
+SINGLE_UPDATE_FEE_IN_WEI=1
 
-GOVERNANCE_CHAIN_ID=0x1
-GOVERNANCE_EMITTER=0x0000000000000000000000000000000000000000000000000000000000001234
+# =============================================================================
+# LEGACY CONFIGURATION (for backward compatibility)
+# =============================================================================
 
 WORMHOLE_CHAIN_NAME=ethereum
-SINGLE_UPDATE_FEE_IN_WEI=1
+CLUSTER=testnet

+ 1 - 0
target_chains/ethereum/contracts/.gitignore

@@ -11,3 +11,4 @@ artifacts
 lcov.info
 filtered-lcov.info
 coverage
+broadcast/*

+ 59 - 14
target_chains/ethereum/contracts/README.md

@@ -4,25 +4,31 @@ This directory contains The Pyth contract on Ethereum and utilities to deploy it
 
 ## Installation
 
-The contracts are built and tested using Foundry. Follow the [Foundry installation instructions](https://book.getfoundry.sh/getting-started/installation) to install it if you do not already have it.
+The contracts are built and tested using Foundry. You can either:
 
-Next, run the following command from the repo root to install required dependencies for the contract:
+1. **Use the setup script (recommended)**:
+   Go to the `contracts` directory and Run `npm run setup` to automatically install Foundry v0.3.0 AND all forge dependencies.
+
+2. **Manual installation**:
+   a) Follow the [Foundry installation instructions](https://book.getfoundry.sh/getting-started/installation) and install version v0.3.0.
+
+b) Next, from the `contracts` directory, run the following command to install forge dependencies:
 
 ```
-pnpm i
-pnpm turbo build --filter @pythnetwork/pyth-evm-contract
+npm run install-forge-deps
 ```
 
-Next, from the `contracts` directory, run the following command to install forge dependencies:
+Next, run the following command from the repo root to install required dependencies for the contract:
 
 ```
-npm run install-forge-deps
+pnpm i
+pnpm turbo build --filter @pythnetwork/pyth-evm-contract
 ```
 
 ## Testing
 
 Run `forge build` to build the contracts and `forge test` to run the contract unit tests.
-The unit tests live in the `forge-test` directory.
+The unit tests live in the `test` directory.
 
 ### Code Coverage
 
@@ -34,28 +40,67 @@ npm run coverage
 
 Open `coverage/index.html` in your web browser to see the results.
 
-### Governance tests
+## Deployment and Governance tests
 
-There is a separate test suite executed by truffle for testing governance messages and contract upgrades. You can run ganache-cli as a blockchain instance and test it manually. To do the latter, run the following commands in the `contracts` folder:
+To deploy the contracts, you'll need to set up your environment variables and use the Foundry deployment script.
 
-1. Spawn a new network on a seperate terminal (do not close it while running tests):
+1. Copy the environment template and fill in your values:
 
 ```bash
-pnpm dlx ganache-cli -e 10000 --deterministic --time="1970-01-02T00:00:00+00:00" --host=0.0.0.0
+cp .env.test .env
+# Edit .env with your configuration
 ```
 
-2. deploy the contracts:
+2. Deploy to a local network (for testing):
 
 ```bash
-cp .env.test .env && pnpm exec truffle compile --all && pnpm exec truffle migrate --network development
+# Start a local node
+anvil
+
+# Anvil shows a list of default accounts and their corresponding private keys.
+# Fetch any one of the private key from the anvil terminal and update in .env file.
+
+# In another terminal, deploy the contracts
+npm run deploy-local
 ```
 
 3. Run the test suite:
 
 ```bash
-npm run test-contract
+npm run test
+```
+
+4. Deploy to a live network:
+
+```bash
+# Make sure your .env file has the correct RPC_URL and PRIVATE_KEY
+npm run deploy
+```
+
+The deployment script will:
+
+- Deploy the Wormhole contracts (Setup, Implementation, and Wormhole proxy)
+- Deploy the Pyth contracts (PythUpgradable with ERC1967 proxy)
+- Configure all necessary parameters from environment variables
+
+5. Deploy and Verify the contracts (on live network)
+
+```bash
+# Make sure your .env file has the correct ETHERSCAN_API_KEY
+npm run deploy-and-verify
 ```
 
+### Guardian Set Sync
+
+After deploying Wormhole contracts on mainnet, you need to sync the guardian sets to match the current mainnet state:
+
+```bash
+# Set WORMHOLE_ADDRESS in your .env file to the deployed Wormhole contract address
+npm run receiver-submit-guardian-sets
+```
+
+This script submits the pre-configured mainnet guardian set upgrade VAAs to bring your contract up to date with the current mainnet guardian set.
+
 ### Gas Benchmarks
 
 You can use foundry to run gas benchmark tests (which can be found in the `forge-test` directory). To run the tests with gas report

+ 10 - 33
target_chains/ethereum/contracts/VERIFY.md

@@ -17,12 +17,12 @@ Most of the explorers accept the standard json-input format, while some only acc
 Pyth contract address refers to the proxy contract. After verifying the proxy contract, the implementation contract needs to be verified as well.
 
 Use the verification files in the release to verify the contracts on the explorer sites.
-You can find the compiler configurations in [`truffle-config.js`](./truffle-config.js) by searching for `compilers`.
+You can find the compiler configurations in [`foundry.toml`](./foundry.toml).
 The required configurations are the _solc version_ and the _optimizer runs_.
 
-## Verifying the contract via truffle or hardhat
+## Verifying the contract via Foundry or Hardhat
 
-Although, verification via explorers is the recommended way, you can also verify the contracts using truffle or hardhat.
+Although, verification via explorers is the recommended way, you can also verify the contracts using Foundry or Hardhat.
 In general, you will need an API key for the relevant explorer (this can be obtained by creating an account),
 and also know which address the contract code lives. The API key is expected to
 be set in the `ETHERSCAN_KEY` environment variable for all APIs (not just etherscan, bit of a misnomer).
@@ -31,44 +31,21 @@ be set in the `ETHERSCAN_KEY` environment variable for all APIs (not just ethers
 ETHERSCAN_KEY=... npm run verify --module=PythUpgradable --contract_address=0x0e082F06FF657D94310cB8cE8B0D9a04541d8052 --network=avalanche
 ```
 
-(Note: the network name comes from the `truffle-config.json`).
 (Note: In this case, the `ETHERSCAN_KEY` is your snowtrace API key).
 
-**You might need to add the explorer api keys in [the truffle config](./truffle-config.js) `api_keys`.** Please look at
-`truffle-plugin-verify/utils.js` to find the key names. Here is an example:
+For Foundry verification, you can use the built-in verification feature by adding `--verify` to your deployment command:
 
-```js
-{
-    compilers: [...],
-
-    api_keys: {
-        etherscan: process.env.ETHERSCAN_KEY,
-        bscscan: process.env.BSCSCAN_KEY,
-        snowtrace: process.env.SNOWTRACE_KEY,
-    },
-
-    plugins: [...]
-}
+```bash
+forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast --verify --etherscan-api-key $ETHERSCAN_API_KEY
 ```
 
-# Note
-
-The `npm run verify` script uses the `truffle-plugin-verify` plugin under the
-hood. The version of `truffle-plugin-verify` pinned in the repo (`^0.5.11` at
-the time of writing) doesn't support the avalanche RPC. In later versions of the
-plugin, support was added, but other stuff has changed as well in the transitive
-dependencies, so it fails to parse the `HDWallet` arguments in our
-`truffle-config.json`. As a quick workaround, we backport the patch to `0.5.11`
-by applying the `truffle-verify-constants.patch` file, which the `npm run verify` script does transparently. Once the toolchain has been upgraded and the
-errors fixed, this patch can be removed.
-
-### Verifying with hardhat
+### Verifying with Hardhat
 
-Some chains might require users to verify with hardhat. Here are the additional steps :
+Some chains might require users to verify with Hardhat. Here are the additional steps:
 
-- Add the chain to `networks` in `hardhat.config.ts` (equivalent of `truffle-config.js`)
+- Add the chain to `networks` in `hardhat.config.ts`
 - Add the explorer parameters to `etherscan` in `hardhat.config.ts`
-- Run :
+- Run:
 
 ```
 MNEMONIC=... pnpm exec hardhat verify 0x354bF866A4B006C9AF9d9e06d9364217A8616E12 --network shimmer_testnet

+ 0 - 14
target_chains/ethereum/contracts/contracts/Migrations.sol

@@ -1,14 +0,0 @@
-// SPDX-License-Identifier: MIT
-pragma solidity >=0.4.22 <0.9.0;
-
-import "@openzeppelin/contracts/access/Ownable.sol";
-
-// Needed for truffle migrate to work correctly.
-// Simply stores the last completed migration.
-contract Migrations is Ownable {
-    uint public last_completed_migration;
-
-    function setCompleted(uint completed) public onlyOwner {
-        last_completed_migration = completed;
-    }
-}

+ 1 - 1
target_chains/ethereum/contracts/contracts/pulse/README.md

@@ -9,7 +9,7 @@ Pulse replaces the service formerly known as "scheduler" or "price pusher," and
 ## Build and Test
 
 Run `forge build` to build the contracts and `forge test` to run the contract unit tests.
-The unit tests live in the `../../forge-test` directory.
+The unit tests live in the `../../test` directory.
 
 Gas benchmarks that cover the most frequent usage patterns are in `PulseSchedulerGasBenchmark.t.sol`. Run the benchmark with -vv to to see the gas usage for the operations under test, without setup costs.
 

+ 5 - 2
target_chains/ethereum/contracts/deploy.sh

@@ -21,7 +21,7 @@ shift
 
 if [ "$version" = "latest" ]; then
   echo "Deploying latest version"
-  stdoutputdir="../target_chains/ethereum/contracts/build/contracts"
+  stdoutputdir="$(pwd)/out"
 else
   # make sure version has format of vX.Y.Z
   if [[ ! "$version" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
@@ -47,7 +47,10 @@ echo "=========== Deploying the contracts ==========="
 pnpm --filter=@pythnetwork/contract-manager exec ts-node scripts/deploy_evm_pricefeed_contracts.ts --std-output-dir $stdoutputdir --private-key $PK --chain "$@"
 
 echo "=========== Cleaning up ==========="
-rm -rf $tmpdir
+if [ -n "${tmpdir:-}" ]; then
+  rm -rf $tmpdir
+fi
+
 
 if [ "$version" != "latest" ]; then
     echo "Verify the contracts by using the std-input artifacts of the contracts in https://github.com/pyth-network/pyth-crosschain/releases/tag/pyth-evm-contract-$version"

+ 8 - 3
target_chains/ethereum/contracts/foundry.toml

@@ -4,9 +4,8 @@ evm_version = "paris"
 optimizer = true
 optimizer_runs = 200
 src = 'contracts'
-# We put the tests into the forge-test directory (instead of test) so that
-# truffle doesn't try to build them
-test = 'forge-test'
+# Tests are in the test directory
+test = 'test'
 
 libs = ['lib', 'node_modules']
 
@@ -20,3 +19,9 @@ ignored_warnings_from = [
     "node_modules/",
     "/home/runner/work/pyth-crosschain/pyth-crosschain/node_modules",
 ]
+
+[rpc_endpoints]
+testnet="${RPC_URL}"
+local = "http://localhost:8545"
+
+etherscan_api_key="${ETHERSCAN_API_KEY}"

+ 0 - 6
target_chains/ethereum/contracts/migrations/test/1_initial_migration.js

@@ -1,6 +0,0 @@
-var Migrations = artifacts.require("Migrations");
-
-module.exports = function (deployer) {
-  // Deploy the Migrations contract as our only task
-  deployer.deploy(Migrations);
-};

+ 0 - 34
target_chains/ethereum/contracts/migrations/test/2_deploy_wormhole.js

@@ -1,34 +0,0 @@
-require("dotenv").config();
-
-const Setup = artifacts.require("Setup");
-const Implementation = artifacts.require("Implementation");
-const Wormhole = artifacts.require("Wormhole");
-
-// CONFIG
-const initialSigners = JSON.parse(process.env.INIT_SIGNERS);
-const chainId = process.env.INIT_CHAIN_ID;
-const governanceChainId = process.env.INIT_GOV_CHAIN_ID;
-const governanceContract = process.env.INIT_GOV_CONTRACT; // bytes32
-
-module.exports = async function (deployer) {
-  // deploy setup
-  await deployer.deploy(Setup);
-
-  // deploy implementation
-  await deployer.deploy(Implementation);
-
-  // encode initialisation data
-  const setup = new web3.eth.Contract(Setup.abi, Setup.address);
-  const initData = setup.methods
-    .setup(
-      Implementation.address,
-      initialSigners,
-      chainId,
-      governanceChainId,
-      governanceContract,
-    )
-    .encodeABI();
-
-  // deploy proxy
-  await deployer.deploy(Wormhole, Setup.address, initData);
-};

+ 0 - 47
target_chains/ethereum/contracts/migrations/test/3_deploy_pyth.js

@@ -1,47 +0,0 @@
-require("dotenv").config();
-
-const bs58 = require("bs58");
-
-const PythUpgradable = artifacts.require("PythUpgradable");
-const Wormhole = artifacts.require("Wormhole");
-
-const pyth2WormholeChainId = process.env.SOLANA_CHAIN_ID;
-const pyth2WormholeEmitter = process.env.SOLANA_EMITTER;
-
-const governanceChainId = process.env.GOVERNANCE_CHAIN_ID;
-const governanceEmitter = process.env.GOVERNANCE_EMITTER;
-// Default value for this field is 0
-const governanceInitialSequence = Number(
-  process.env.GOVERNANCE_INITIAL_SEQUENCE ?? "0",
-);
-
-const validTimePeriodSeconds = Number(process.env.VALID_TIME_PERIOD_SECONDS);
-const singleUpdateFeeInWei = Number(process.env.SINGLE_UPDATE_FEE_IN_WEI);
-
-const { deployProxy } = require("@openzeppelin/truffle-upgrades");
-
-console.log("pyth2WormholeChainId: " + pyth2WormholeChainId);
-console.log("pyth2WormholeEmitter: " + pyth2WormholeEmitter);
-console.log("governanceEmitter: " + governanceEmitter);
-console.log("governanceChainId: " + governanceChainId);
-console.log("governanceInitialSequence: " + governanceInitialSequence);
-console.log("validTimePeriodSeconds: " + validTimePeriodSeconds);
-console.log("singleUpdateFeeInWei: " + singleUpdateFeeInWei);
-
-module.exports = async function (deployer) {
-  // Deploy the proxy script
-  await deployProxy(
-    PythUpgradable,
-    [
-      (await Wormhole.deployed()).address,
-      [pyth2WormholeChainId],
-      [pyth2WormholeEmitter],
-      governanceChainId,
-      governanceEmitter,
-      governanceInitialSequence,
-      validTimePeriodSeconds,
-      singleUpdateFeeInWei,
-    ],
-    { deployer },
-  );
-};

+ 9 - 14
target_chains/ethereum/contracts/package.json

@@ -4,25 +4,21 @@
   "description": "",
   "private": "true",
   "devDependencies": {
-    "@openzeppelin/test-helpers": "^0.5.15",
-    "@openzeppelin/truffle-upgrades": "^1.14.0",
-    "@truffle/hdwallet-provider": "^2.1.5",
     "@types/chai": "^4.3.4",
     "chai": "^4.2.0",
     "mocha": "^8.2.1",
     "prettier": "catalog:",
-    "prettier-plugin-solidity": "catalog:",
-    "truffle": "^5.7.4",
-    "truffle-deploy-registry": "^0.5.1",
-    "truffle-plugin-stdjsonin": "github:mhrsalehi/truffle-plugin-stdjsonin",
-    "truffle-plugin-verify": "^0.6.1"
+    "prettier-plugin-solidity": "catalog:"
   },
   "scripts": {
-    "build": "rm -rf build && truffle compile --all",
-    "test-contract": "truffle test",
-    "migrate": "truffle migrate",
-    "receiver-submit-guardian-sets": "truffle exec scripts/receiverSubmitGuardianSetUpgrades.js",
-    "verify": "truffle run verify $npm_config_module@$npm_config_contract_address --network $npm_config_network",
+    "setup-foundry": "./setup-foundry.sh",
+    "setup": "npm run setup-foundry && npm run install-forge-deps",
+    "build": "forge build",
+    "test": "forge test",
+    "deploy": "forge script script/Deploy.s.sol --rpc-url testnet --broadcast",
+    "deploy-and-verify": "forge script script/Deploy.s.sol --rpc-url testnet --broadcast --verify",
+    "deploy-local": "ETHERSCAN_API_KEY= forge script script/Deploy.s.sol --slow  --rpc-url local --broadcast -vvvv",
+    "receiver-submit-guardian-sets": "forge script script/ReceiverSubmitGuardianSetUpgrades.s.sol --rpc-url $RPC_URL --broadcast",
     "install-forge-deps": "forge install foundry-rs/forge-std@v1.7.6 --no-git",
     "coverage": "./coverage.sh",
     "test:format": "prettier --check .",
@@ -53,7 +49,6 @@
     "jsonfile": "^4.0.0",
     "lodash": "^4.17.21",
     "solc": "0.8.4",
-    "truffle-contract-size": "^2.0.1",
     "ts-node": "catalog:",
     "typescript": "catalog:",
     "web3": "^1.2.2",

+ 0 - 1
target_chains/ethereum/contracts/remappings.txt

@@ -4,4 +4,3 @@
 ds-test/=lib/forge-std/lib/ds-test/src/
 forge-std/=lib/forge-std/src/
 @nomad-xyz=./node_modules/@nomad-xyz/
-truffle/=./node_modules/truffle/

+ 122 - 0
target_chains/ethereum/contracts/script/Deploy.s.sol

@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import "forge-std/Script.sol";
+import "forge-std/console.sol";
+import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
+
+// Wormhole contracts
+import "../contracts/wormhole/Setup.sol";
+import "../contracts/wormhole/Implementation.sol";
+import "../contracts/wormhole/Wormhole.sol";
+
+// Pyth contracts
+import "../contracts/pyth/PythUpgradable.sol";
+
+contract DeployScript is Script {
+    function run() external {
+        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+        vm.startBroadcast(deployerPrivateKey);
+
+        // Deploy Wormhole first
+        address wormholeAddress = deployWormhole();
+        console.log("Wormhole deployed at:", wormholeAddress);
+
+        // Deploy Pyth
+        address pythAddress = deployPyth(wormholeAddress);
+        console.log("Pyth deployed at:", pythAddress);
+
+        vm.stopBroadcast();
+    }
+
+    function deployWormhole() internal returns (address) {
+        // Read environment variables
+        address[] memory initialSigners = vm.envAddress("INIT_SIGNERS", ",");
+        uint16 chainId = uint16(vm.envUint("INIT_CHAIN_ID"));
+        uint16 governanceChainId = uint16(vm.envUint("INIT_GOV_CHAIN_ID"));
+        bytes32 governanceContract = vm.envBytes32("INIT_GOV_CONTRACT");
+
+        console.log("Deploying Wormhole with chainId:", chainId);
+        console.log("Governance chainId:", governanceChainId);
+
+        // Deploy Setup contract
+        Setup setup = new Setup();
+        console.log("Setup deployed at:", address(setup));
+
+        // Deploy Implementation contract
+        Implementation implementation = new Implementation();
+        console.log("Implementation deployed at:", address(implementation));
+
+        // Encode initialization data
+        bytes memory initData = abi.encodeWithSignature(
+            "setup(address,address[],uint16,uint16,bytes32)",
+            address(implementation),
+            initialSigners,
+            chainId,
+            governanceChainId,
+            governanceContract
+        );
+
+        // Deploy Wormhole proxy
+        Wormhole wormhole = new Wormhole(address(setup), initData);
+
+        return address(wormhole);
+    }
+
+    function deployPyth(address wormholeAddress) internal returns (address) {
+        // Read environment variables
+        uint16 pyth2WormholeChainId = uint16(vm.envUint("SOLANA_CHAIN_ID"));
+        bytes32 pyth2WormholeEmitter = vm.envBytes32("SOLANA_EMITTER");
+        uint16 governanceChainId = uint16(vm.envUint("GOVERNANCE_CHAIN_ID"));
+        bytes32 governanceEmitter = vm.envBytes32("GOVERNANCE_EMITTER");
+        uint64 governanceInitialSequence = uint64(
+            vm.envOr("GOVERNANCE_INITIAL_SEQUENCE", uint256(0))
+        );
+        uint256 validTimePeriodSeconds = vm.envUint(
+            "VALID_TIME_PERIOD_SECONDS"
+        );
+        uint256 singleUpdateFeeInWei = vm.envUint("SINGLE_UPDATE_FEE_IN_WEI");
+
+        console.log("pyth2WormholeChainId:", pyth2WormholeChainId);
+        console.log("pyth2WormholeEmitter:", uint256(pyth2WormholeEmitter));
+        console.log("governanceEmitter:", uint256(governanceEmitter));
+        console.log("governanceChainId:", governanceChainId);
+        console.log("governanceInitialSequence:", governanceInitialSequence);
+        console.log("validTimePeriodSeconds:", validTimePeriodSeconds);
+        console.log("singleUpdateFeeInWei:", singleUpdateFeeInWei);
+
+        // Deploy PythUpgradable implementation
+        PythUpgradable pythImpl = new PythUpgradable();
+        console.log(
+            "PythUpgradable implementation deployed at:",
+            address(pythImpl)
+        );
+
+        // Prepare initialization data
+        uint16[] memory dataSourceChainIds = new uint16[](1);
+        dataSourceChainIds[0] = pyth2WormholeChainId;
+
+        bytes32[] memory dataSourceEmitterAddresses = new bytes32[](1);
+        dataSourceEmitterAddresses[0] = pyth2WormholeEmitter;
+
+        bytes memory pythInitData = abi.encodeWithSignature(
+            "initialize(address,uint16[],bytes32[],uint16,bytes32,uint64,uint256,uint256)",
+            wormholeAddress,
+            dataSourceChainIds,
+            dataSourceEmitterAddresses,
+            governanceChainId,
+            governanceEmitter,
+            governanceInitialSequence,
+            validTimePeriodSeconds,
+            singleUpdateFeeInWei
+        );
+
+        // Deploy ERC1967 proxy
+        ERC1967Proxy pythProxy = new ERC1967Proxy(
+            address(pythImpl),
+            pythInitData
+        );
+
+        return address(pythProxy);
+    }
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 14 - 0
target_chains/ethereum/contracts/script/ReceiverSubmitGuardianSetUpgrades.s.sol


+ 70 - 0
target_chains/ethereum/contracts/script/utils/VaaUtils.sol

@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: Apache-2.0
+pragma solidity ^0.8.0;
+
+import "forge-std/Test.sol";
+
+library VaaUtils {
+    /**
+     * @dev Assert that a VAA has the expected payload
+     * @param vaaBytes The VAA bytes to check
+     * @param expectedPayload The expected payload bytes
+     */
+    function assertVaaPayloadEquals(
+        bytes memory vaaBytes,
+        bytes memory expectedPayload
+    ) internal pure {
+        // VAA structure: version (1) + guardianSetIndex (4) + signaturesLength (1) + signatures + body
+        // Body structure: timestamp (4) + nonce (4) + emitterChainId (2) + emitterAddress (32) + sequence (8) + consistencyLevel (1) + payload
+
+        require(vaaBytes.length >= 51, "VAA too short"); // Minimum VAA length
+
+        uint256 offset = 6; // Skip version (1) + guardianSetIndex (4) + signaturesLength (1)
+
+        // Skip signatures (each signature is 66 bytes: guardianIndex (1) + r (32) + s (32) + v (1))
+        uint8 signaturesLength = uint8(vaaBytes[5]);
+        offset += signaturesLength * 66;
+
+        // Skip to payload (skip timestamp (4) + nonce (4) + emitterChainId (2) + emitterAddress (32) + sequence (8) + consistencyLevel (1))
+        offset += 51;
+
+        require(offset < vaaBytes.length, "Invalid VAA structure");
+
+        // Extract payload
+        bytes memory actualPayload = new bytes(vaaBytes.length - offset);
+        for (uint256 i = 0; i < actualPayload.length; i++) {
+            actualPayload[i] = vaaBytes[offset + i];
+        }
+
+        require(
+            keccak256(actualPayload) == keccak256(expectedPayload),
+            "VAA payload does not match expected payload"
+        );
+    }
+
+    /**
+     * @dev Extract payload from VAA bytes
+     * @param vaaBytes The VAA bytes
+     * @return payload The extracted payload
+     */
+    function extractPayload(
+        bytes memory vaaBytes
+    ) internal pure returns (bytes memory payload) {
+        require(vaaBytes.length >= 51, "VAA too short");
+
+        uint256 offset = 6; // Skip version (1) + guardianSetIndex (4) + signaturesLength (1)
+
+        // Skip signatures
+        uint8 signaturesLength = uint8(vaaBytes[5]);
+        offset += signaturesLength * 66;
+
+        // Skip to payload
+        offset += 51;
+
+        require(offset < vaaBytes.length, "Invalid VAA structure");
+
+        payload = new bytes(vaaBytes.length - offset);
+        for (uint256 i = 0; i < payload.length; i++) {
+            payload[i] = vaaBytes[offset + i];
+        }
+    }
+}

+ 0 - 23
target_chains/ethereum/contracts/scripts/assertVaaPayloadEquals.js

@@ -1,23 +0,0 @@
-const { parseVaa } = require("@certusone/wormhole-sdk");
-const { assert } = require("chai");
-
-/**
- * Assert the VAA has payload equal to `expectedPayload`
- * @param {string} vaaHex
- * @param {Buffer} expectedPayload
- */
-module.exports = async function assertVaaPayloadEquals(
-  vaaHex,
-  expectedPayload,
-) {
-  if (vaaHex.startsWith("0x")) {
-    vaaHex = vaaHex.substring(2);
-  }
-
-  const vaaPayload = Buffer.from(parseVaa(Buffer.from(vaaHex, "hex")).payload);
-
-  assert(
-    expectedPayload.equals(vaaPayload),
-    "The VAA payload is not equal to the expected payload",
-  );
-};

+ 0 - 103
target_chains/ethereum/contracts/scripts/createLocalnetGovernanceVaa.js

@@ -1,103 +0,0 @@
-const abi = require("web3-eth-abi");
-const utils = require("web3-utils");
-const elliptic = require("elliptic");
-
-const testSigner1PK =
-  "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
-
-const testGovernanceChain = "1"; // ethereum
-const testGovernanceEmitter =
-  "0x0000000000000000000000000000000000000000000000000000000000001234";
-
-function zeroPadBytes(value, length) {
-  while (value.length < 2 * length) {
-    value = "0" + value;
-  }
-  return value;
-}
-
-const signAndEncodeVM = function (
-  timestamp,
-  nonce,
-  emitterChainId,
-  emitterAddress,
-  sequence,
-  data,
-  signers,
-  guardianSetIndex,
-  consistencyLevel,
-) {
-  const body = [
-    abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
-    abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
-    abi.encodeParameter("uint16", emitterChainId).substring(2 + (64 - 4)),
-    abi.encodeParameter("bytes32", emitterAddress).substring(2),
-    abi.encodeParameter("uint64", sequence).substring(2 + (64 - 16)),
-    abi.encodeParameter("uint8", consistencyLevel).substring(2 + (64 - 2)),
-    data.substr(2),
-  ];
-
-  const hash = utils.soliditySha3(utils.soliditySha3("0x" + body.join("")));
-
-  let signatures = "";
-
-  for (let i in signers) {
-    const ec = new elliptic.ec("secp256k1");
-    const key = ec.keyFromPrivate(signers[i]);
-    const signature = key.sign(hash.substr(2), { canonical: true });
-
-    const packSig = [
-      abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
-      zeroPadBytes(signature.r.toString(16), 32),
-      zeroPadBytes(signature.s.toString(16), 32),
-      abi
-        .encodeParameter("uint8", signature.recoveryParam)
-        .substr(2 + (64 - 2)),
-    ];
-
-    signatures += packSig.join("");
-  }
-
-  const vm = [
-    abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
-    abi.encodeParameter("uint32", guardianSetIndex).substring(2 + (64 - 8)),
-    abi.encodeParameter("uint8", signers.length).substring(2 + (64 - 2)),
-
-    signatures,
-    body.join(""),
-  ].join("");
-
-  return vm;
-};
-
-function createVAAFromUint8Array(
-  dataBuffer,
-  emitterChainId,
-  emitterAddress,
-  sequence,
-) {
-  const dataHex = "0x" + dataBuffer.toString("hex");
-  return (
-    "0x" +
-    signAndEncodeVM(
-      0,
-      0,
-      emitterChainId,
-      emitterAddress,
-      sequence,
-      dataHex,
-      [testSigner1PK],
-      0,
-      0,
-    )
-  );
-}
-
-module.exports = function createLocalnetGovernanceVAA(dataBuffer, sequence) {
-  return createVAAFromUint8Array(
-    dataBuffer,
-    testGovernanceChain,
-    testGovernanceEmitter,
-    sequence,
-  );
-};

+ 28 - 0
target_chains/ethereum/contracts/setup-foundry.sh

@@ -0,0 +1,28 @@
+#!/bin/bash
+# Complete Forge setup script - installs Foundry and dependencies
+set -e
+
+FOUNDRY_VERSION="v0.3.0"
+
+echo "Setting up complete Forge environment with Foundry ${FOUNDRY_VERSION}..."
+
+# Check if foundryup is available
+if ! command -v foundryup &> /dev/null; then
+    echo "Installing foundryup..."
+    curl -L https://foundry.paradigm.xyz | bash
+    export PATH="$HOME/.foundry/bin:$PATH"
+fi
+
+# Install the specific version
+echo "Installing Foundry ${FOUNDRY_VERSION}..."
+foundryup --version $FOUNDRY_VERSION
+
+# Verify installation
+echo "Verifying installation..."
+forge --version
+cast --version
+anvil --version
+
+echo "Foundry ${FOUNDRY_VERSION} installed successfully!"
+
+echo "Setup complete! You can now run install-forge-deps."

+ 0 - 0
target_chains/ethereum/contracts/forge-test/Echo.t.sol → target_chains/ethereum/contracts/test/Echo.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/EchoGasBenchmark.t.sol → target_chains/ethereum/contracts/test/EchoGasBenchmark.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/Entropy.t.sol → target_chains/ethereum/contracts/test/Entropy.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/EntropyAuthorized.t.sol → target_chains/ethereum/contracts/test/EntropyAuthorized.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/EntropyGasBenchmark.t.sol → target_chains/ethereum/contracts/test/EntropyGasBenchmark.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/Executor.t.sol → target_chains/ethereum/contracts/test/Executor.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/GasBenchmark.t.sol → target_chains/ethereum/contracts/test/GasBenchmark.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/PRNG.t.sol → target_chains/ethereum/contracts/test/PRNG.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/PulseScheduler.t.sol → target_chains/ethereum/contracts/test/PulseScheduler.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/PulseSchedulerGasBenchmark.t.sol → target_chains/ethereum/contracts/test/PulseSchedulerGasBenchmark.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/PulseSchedulerGovernance.t.sol → target_chains/ethereum/contracts/test/PulseSchedulerGovernance.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/Pyth.Aave.t.sol → target_chains/ethereum/contracts/test/Pyth.Aave.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/Pyth.WormholeMerkleAccumulator.t.sol → target_chains/ethereum/contracts/test/Pyth.WormholeMerkleAccumulator.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/Pyth.t.sol → target_chains/ethereum/contracts/test/Pyth.t.sol


+ 0 - 3
target_chains/ethereum/contracts/forge-test/PythGovernance.t.sol → target_chains/ethereum/contracts/test/PythGovernance.t.sol

@@ -1,8 +1,5 @@
 // SPDX-License-Identifier: Apache 2
 
-// NOTE: These tests were migrated from target_chains/ethereum/contracts/test/pyth.js but exclude the Wormhole-specific tests,
-// which remain in the original JavaScript test file.
-
 pragma solidity ^0.8.0;
 
 import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

+ 0 - 0
target_chains/ethereum/contracts/forge-test/VerificationExperiments.t.sol → target_chains/ethereum/contracts/test/VerificationExperiments.t.sol


+ 0 - 895
target_chains/ethereum/contracts/test/pyth.js

@@ -1,895 +0,0 @@
-const elliptic = require("elliptic");
-const governance = require("@pythnetwork/xc-admin-common");
-
-const { deployProxy, upgradeProxy } = require("@openzeppelin/truffle-upgrades");
-const {
-  expectRevert,
-  expectEvent,
-  time,
-} = require("@openzeppelin/test-helpers");
-const { assert, expect } = require("chai");
-const { EvmSetWormholeAddress } = require("@pythnetwork/xc-admin-common");
-
-// Use "WormholeReceiver" if you are testing with Wormhole Receiver
-const Setup = artifacts.require("Setup");
-const Implementation = artifacts.require("Implementation");
-const Wormhole = artifacts.require("Wormhole");
-
-const ReceiverSetup = artifacts.require("ReceiverSetup");
-const ReceiverImplementation = artifacts.require("ReceiverImplementation");
-const WormholeReceiver = artifacts.require("WormholeReceiver");
-
-const wormholeGovernanceChainId = governance.CHAINS.solana;
-const wormholeGovernanceContract =
-  "0x0000000000000000000000000000000000000000000000000000000000000004";
-
-const PythUpgradable = artifacts.require("PythUpgradable");
-const MockPythUpgrade = artifacts.require("MockPythUpgrade");
-const MockUpgradeableProxy = artifacts.require("MockUpgradeableProxy");
-
-const testSigner1PK =
-  "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0";
-const testSigner2PK =
-  "892330666a850761e7370376430bb8c2aa1494072d3bfeaed0c4fa3d5a9135fe";
-
-contract("Pyth", function () {
-  const testSigner1 = web3.eth.accounts.privateKeyToAccount(testSigner1PK);
-  const testSigner2 = web3.eth.accounts.privateKeyToAccount(testSigner2PK);
-  const testGovernanceChainId = "1";
-  const testGovernanceEmitter =
-    "0x0000000000000000000000000000000000000000000000000000000000001234";
-  const testPyth2WormholeChainId = "1";
-  const testPyth2WormholeEmitter =
-    "0x71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b";
-
-  // Place all atomic operations that are done within migrations here.
-  beforeEach(async function () {
-    this.pythProxy = await deployProxy(PythUpgradable, [
-      (await Wormhole.deployed()).address,
-      [testPyth2WormholeChainId],
-      [testPyth2WormholeEmitter],
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      0, // Initial governance sequence
-      60, // Validity time in seconds
-      0, // single update fee in wei
-    ]);
-  });
-
-  it("should be initialized with the correct signers and values", async function () {
-    await this.pythProxy.isValidDataSource(
-      testPyth2WormholeChainId,
-      testPyth2WormholeEmitter,
-    );
-  });
-
-  it("there should be no owner", async function () {
-    // Check that the ownership is renounced.
-    const owner = await this.pythProxy.owner();
-    assert.equal(owner, "0x0000000000000000000000000000000000000000");
-  });
-
-  it("deployer cannot upgrade the contract", async function () {
-    // upgrade proxy should fail
-    await expectRevert(
-      upgradeProxy(this.pythProxy.address, MockPythUpgrade),
-      "Ownable: caller is not the owner.",
-    );
-  });
-
-  async function updatePriceFeeds(
-    contract,
-    batches,
-    valueInWei,
-    chainId,
-    emitter,
-  ) {
-    let updateData = [];
-    for (let data of batches) {
-      const vm = await signAndEncodeVM(
-        1,
-        1,
-        chainId || testPyth2WormholeChainId,
-        emitter || testPyth2WormholeEmitter,
-        0,
-        data,
-        [testSigner1PK],
-        0,
-        0,
-      );
-      updateData.push("0x" + vm);
-    }
-    return await contract.updatePriceFeeds(updateData, { value: valueInWei });
-  }
-
-  /**
-   * Create a governance instruction VAA from the Instruction object. Then
-   * Submit and execute it on the contract.
-   * @param contract Pyth contract
-   * @param {governance.PythGovernanceAction} governanceInstruction
-   * @param {number} sequence
-   */
-  async function createAndThenSubmitGovernanceInstructionVaa(
-    contract,
-    governanceInstruction,
-    sequence,
-  ) {
-    await contract.executeGovernanceInstruction(
-      await createVAAFromUint8Array(
-        governanceInstruction.encode(),
-        testGovernanceChainId,
-        testGovernanceEmitter,
-        sequence,
-      ),
-    );
-  }
-
-  it("should attest price updates empty", async function () {
-    const receipt = await updatePriceFeeds(this.pythProxy, []);
-    expectEvent.notEmitted(receipt, "PriceFeedUpdate");
-  });
-
-  /**
-   * Set fee to `newFee` by creating and submitting a governance instruction for it.
-   * @param contarct Pyth contract
-   * @param {number} newFee
-   * @param {number=} governanceSequence Sequence number of the governance instruction. Defaults to 1.
-   */
-  async function setFeeTo(contract, newFee, governanceSequence) {
-    await createAndThenSubmitGovernanceInstructionVaa(
-      contract,
-      new governance.SetFee("ethereum", BigInt(newFee), BigInt(0)),
-      governanceSequence ?? 1,
-    );
-  }
-
-  it("should fail transaction if a price is not found", async function () {
-    await expectRevertCustomError(
-      this.pythProxy.queryPriceFeed(
-        "0xdeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeeddeadfeed",
-      ),
-      "PriceFeedNotFound",
-    );
-  });
-
-  /**
-   * Set valid time period to `newValidPeriod` by creating and submitting a
-   * governance instruction for it.
-   * @param contract Pyth contract
-   * @param {number} newValidPeriod
-   * @param {number=} governanceSequence Sequence number of the governance instruction. Defaults to 1.
-   */
-  async function setValidPeriodTo(
-    contract,
-    newValidPeriod,
-    governanceSequence,
-  ) {
-    await createAndThenSubmitGovernanceInstructionVaa(
-      contract,
-      new governance.SetValidPeriod("ethereum", BigInt(newValidPeriod)),
-      governanceSequence ?? 1,
-    );
-  }
-
-  // Governance
-
-  // Logics that apply to all governance messages
-  it("Make sure invalid magic and module won't work", async function () {
-    // First 4 bytes of data are magic and the second byte after that is module
-    const data = new governance.SetValidPeriod("ethereum", BigInt(10)).encode();
-
-    const wrongMagic = Buffer.from(data);
-    wrongMagic[1] = 0;
-
-    const vaaWrongMagic = await createVAAFromUint8Array(
-      wrongMagic,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(vaaWrongMagic),
-      "InvalidGovernanceMessage",
-    );
-
-    const wrongModule = Buffer.from(data);
-    wrongModule[4] = 0;
-
-    const vaaWrongModule = await createVAAFromUint8Array(
-      wrongModule,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(vaaWrongModule),
-      "InvalidGovernanceTarget",
-    );
-
-    const outOfBoundModule = Buffer.from(data);
-    outOfBoundModule[4] = 20;
-
-    const vaaOutOfBoundModule = await createVAAFromUint8Array(
-      outOfBoundModule,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await expectRevert(
-      this.pythProxy.executeGovernanceInstruction(vaaOutOfBoundModule),
-      "Panic: Enum value out of bounds.",
-    );
-  });
-
-  it("Make sure governance with wrong sender won't work", async function () {
-    const data = new governance.SetValidPeriod("ethereum", BigInt(10)).encode();
-
-    const vaaWrongEmitter = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      "0x0000000000000000000000000000000000000000000000000000000000001111",
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(vaaWrongEmitter),
-      "InvalidGovernanceDataSource",
-    );
-
-    const vaaWrongChain = await createVAAFromUint8Array(
-      data,
-      governance.CHAINS.karura,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(vaaWrongChain),
-      "InvalidGovernanceDataSource",
-    );
-  });
-
-  it("Make sure governance with only target chain id and 0 work", async function () {
-    const wrongChainData = new governance.SetValidPeriod(
-      "solana",
-      BigInt(10),
-    ).encode();
-
-    const wrongChainVaa = await createVAAFromUint8Array(
-      wrongChainData,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(wrongChainVaa),
-      "InvalidGovernanceTarget",
-    );
-
-    const dataForAllChains = new governance.SetValidPeriod(
-      "unset",
-      BigInt(10),
-    ).encode();
-
-    const vaaForAllChains = await createVAAFromUint8Array(
-      dataForAllChains,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await this.pythProxy.executeGovernanceInstruction(vaaForAllChains);
-
-    const dataForEth = new governance.SetValidPeriod(
-      "ethereum",
-      BigInt(10),
-    ).encode();
-
-    const vaaForEth = await createVAAFromUint8Array(
-      dataForEth,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      2,
-    );
-
-    await this.pythProxy.executeGovernanceInstruction(vaaForEth);
-  });
-
-  it("Make sure that governance messages are executed in order and cannot be reused", async function () {
-    const data = new governance.SetValidPeriod("ethereum", BigInt(10)).encode();
-
-    const vaaSeq1 = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await this.pythProxy.executeGovernanceInstruction(vaaSeq1),
-      // Replaying shouldn't work
-      await expectRevertCustomError(
-        this.pythProxy.executeGovernanceInstruction(vaaSeq1),
-        "OldGovernanceMessage",
-      );
-
-    const vaaSeq2 = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      2,
-    );
-
-    await this.pythProxy.executeGovernanceInstruction(vaaSeq2),
-      // Replaying shouldn't work
-      await expectRevertCustomError(
-        this.pythProxy.executeGovernanceInstruction(vaaSeq1),
-        "OldGovernanceMessage",
-      );
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(vaaSeq2),
-      "OldGovernanceMessage",
-    );
-  });
-
-  // Per governance type logic
-  it("Upgrading the contract with chain id 0 is invalid", async function () {
-    const newImplementation = await PythUpgradable.new();
-
-    const data = new governance.EvmUpgradeContract(
-      "unset", // 0
-      newImplementation.address.replace("0x", ""),
-    ).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(vaa),
-      "InvalidGovernanceTarget",
-    );
-  });
-
-  it("Upgrading the contract should work", async function () {
-    const newImplementation = await PythUpgradable.new();
-
-    const data = new governance.EvmUpgradeContract(
-      "ethereum",
-      newImplementation.address.replace("0x", ""),
-    ).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
-
-    // Couldn't get the oldImplementation address.
-    expectEvent(receipt, "ContractUpgraded", {
-      newImplementation: newImplementation.address,
-    });
-    expectEvent(receipt, "Upgraded", {
-      implementation: newImplementation.address,
-    });
-  });
-
-  it("Upgrading the contract to a non-pyth contract won't work", async function () {
-    const newImplementation = await MockUpgradeableProxy.new();
-
-    const data = new governance.EvmUpgradeContract(
-      "ethereum",
-      newImplementation.address.replace("0x", ""),
-    ).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    // Calling a non-existing method will cause a revert with no explanation.
-    await expectRevert(
-      this.pythProxy.executeGovernanceInstruction(vaa),
-      "revert",
-    );
-  });
-
-  it("Transferring governance data source should work", async function () {
-    const newEmitterAddress =
-      "0x0000000000000000000000000000000000000000000000000000000000001111";
-    const newEmitterChain = governance.CHAINS.acala;
-
-    const claimInstructionData =
-      new governance.RequestGovernanceDataSourceTransfer("unset", 1).encode();
-
-    const claimVaaHexString = await createVAAFromUint8Array(
-      claimInstructionData,
-      newEmitterChain,
-      newEmitterAddress,
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(claimVaaHexString),
-      "InvalidGovernanceDataSource",
-    );
-
-    const claimVaa = Buffer.from(claimVaaHexString.substring(2), "hex");
-
-    const data = new governance.AuthorizeGovernanceDataSourceTransfer(
-      "unset",
-      claimVaa,
-    ).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    const oldGovernanceDataSource = await this.pythProxy.governanceDataSource();
-
-    const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
-
-    const newGovernanceDataSource = await this.pythProxy.governanceDataSource();
-
-    expectEvent(receipt, "GovernanceDataSourceSet", {
-      oldDataSource: oldGovernanceDataSource,
-      newDataSource: newGovernanceDataSource,
-    });
-
-    expect(newGovernanceDataSource.chainId).equal(newEmitterChain.toString());
-    expect(newGovernanceDataSource.emitterAddress).equal(newEmitterAddress);
-
-    // Verifies the data source has changed.
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(vaa),
-      "InvalidGovernanceDataSource",
-    );
-
-    // Make sure a claim vaa does not get executed
-
-    const claimLonely = new governance.RequestGovernanceDataSourceTransfer(
-      "unset",
-      2,
-    ).encode();
-
-    const claimLonelyVaa = await createVAAFromUint8Array(
-      claimLonely,
-      newEmitterChain,
-      newEmitterAddress,
-      2,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(claimLonelyVaa),
-      "InvalidGovernanceMessage",
-    );
-
-    // Transfer back the ownership to the old governance data source without increasing
-    // the governance index should not work
-
-    // A wrong vaa that does not move the governance index
-    const transferBackClaimInstructionDataWrong =
-      new governance.RequestGovernanceDataSourceTransfer(
-        "unset",
-        1, // The same governance data source index => Should fail
-      ).encode();
-
-    const transferBackClaimVaaHexStringWrong = await createVAAFromUint8Array(
-      transferBackClaimInstructionDataWrong,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      2,
-    );
-
-    const transferBackClaimVaaWrong = Buffer.from(
-      transferBackClaimVaaHexStringWrong.substring(2),
-      "hex",
-    );
-
-    const transferBackDataWrong =
-      new governance.AuthorizeGovernanceDataSourceTransfer(
-        "unset",
-        transferBackClaimVaaWrong,
-      ).encode();
-
-    const transferBackVaaWrong = await createVAAFromUint8Array(
-      transferBackDataWrong,
-      newEmitterChain,
-      newEmitterAddress,
-      2,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(transferBackVaaWrong),
-      "OldGovernanceMessage",
-    );
-  });
-
-  it("Setting data sources should work", async function () {
-    const data = new governance.SetDataSources("ethereum", [
-      {
-        emitterChain: governance.CHAINS.acala,
-        emitterAddress:
-          "0000000000000000000000000000000000000000000000000000000000001111",
-      },
-    ]).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    const oldDataSources = await this.pythProxy.validDataSources();
-
-    const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
-    expectEvent(receipt, "DataSourcesSet", {
-      oldDataSources: oldDataSources,
-      newDataSources: await this.pythProxy.validDataSources(),
-    });
-
-    assert.isTrue(
-      await this.pythProxy.isValidDataSource(
-        governance.CHAINS.acala,
-        "0x0000000000000000000000000000000000000000000000000000000000001111",
-      ),
-    );
-    assert.isFalse(
-      await this.pythProxy.isValidDataSource(
-        testPyth2WormholeChainId,
-        testPyth2WormholeEmitter,
-      ),
-    );
-
-    // TODO: try to publish prices
-  });
-
-  it("Setting fee should work", async function () {
-    const data = new governance.SetFee(
-      "ethereum",
-      BigInt(5),
-      BigInt(3), // 5*10**3 = 5000
-    ).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    const oldFee = await this.pythProxy.singleUpdateFeeInWei();
-
-    const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
-    expectEvent(receipt, "FeeSet", {
-      oldFee: oldFee,
-      newFee: await this.pythProxy.singleUpdateFeeInWei(),
-    });
-
-    assert.equal(await this.pythProxy.singleUpdateFeeInWei(), "5000");
-
-    // TODO: check that fee is applied
-  });
-
-  it("Setting valid period should work", async function () {
-    const data = new governance.SetValidPeriod("ethereum", BigInt(0)).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    const oldValidPeriod = await this.pythProxy.validTimePeriodSeconds();
-
-    const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
-    expectEvent(receipt, "ValidPeriodSet", {
-      oldValidPeriod: oldValidPeriod,
-      newValidPeriod: await this.pythProxy.validTimePeriodSeconds(),
-    });
-
-    assert.equal(await this.pythProxy.validTimePeriodSeconds(), "0");
-
-    // The behaviour of valid time period is extensively tested before,
-    // and adding it here will cause more complexity (and is not so short).
-  });
-
-  it("Setting wormhole address should work", async function () {
-    // Deploy a new wormhole contract
-    const newSetup = await Setup.new();
-    const newImpl = await Implementation.new();
-
-    // encode initialisation data
-    const initData = newSetup.contract.methods
-      .setup(
-        newImpl.address,
-        [testSigner1.address],
-        governance.CHAINS.polygon, // changing the chain id to polygon
-        wormholeGovernanceChainId,
-        wormholeGovernanceContract,
-      )
-      .encodeABI();
-
-    const newWormhole = await Wormhole.new(newSetup.address, initData);
-
-    // Creating the vaa to set the new wormhole address
-    const data = new governance.EvmSetWormholeAddress(
-      "ethereum",
-      newWormhole.address.replace("0x", ""),
-    ).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    assert.equal(await this.pythProxy.chainId(), governance.CHAINS.ethereum);
-
-    const oldWormholeAddress = await this.pythProxy.wormhole();
-
-    const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
-    expectEvent(receipt, "WormholeAddressSet", {
-      oldWormholeAddress: oldWormholeAddress,
-      newWormholeAddress: newWormhole.address,
-    });
-
-    assert.equal(await this.pythProxy.wormhole(), newWormhole.address);
-    assert.equal(await this.pythProxy.chainId(), governance.CHAINS.polygon);
-  });
-
-  it("Setting wormhole address to WormholeReceiver should work", async function () {
-    // Deploy a new wormhole receiver contract
-    const newReceiverSetup = await ReceiverSetup.new();
-    const newReceiverImpl = await ReceiverImplementation.new();
-
-    // encode initialisation data
-    const initData = newReceiverSetup.contract.methods
-      .setup(
-        newReceiverImpl.address,
-        [testSigner1.address],
-        governance.CHAINS.polygon, // changing the chain id to polygon
-        wormholeGovernanceChainId,
-        wormholeGovernanceContract,
-      )
-      .encodeABI();
-
-    const newWormholeReceiver = await WormholeReceiver.new(
-      newReceiverSetup.address,
-      initData,
-    );
-
-    // Creating the vaa to set the new wormhole address
-    const data = new governance.EvmSetWormholeAddress(
-      "ethereum",
-      newWormholeReceiver.address.replace("0x", ""),
-    ).encode();
-
-    const vaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    assert.equal(await this.pythProxy.chainId(), governance.CHAINS.ethereum);
-
-    const oldWormholeAddress = await this.pythProxy.wormhole();
-
-    const receipt = await this.pythProxy.executeGovernanceInstruction(vaa);
-    expectEvent(receipt, "WormholeAddressSet", {
-      oldWormholeAddress: oldWormholeAddress,
-      newWormholeAddress: newWormholeReceiver.address,
-    });
-
-    assert.equal(await this.pythProxy.wormhole(), newWormholeReceiver.address);
-    assert.equal(await this.pythProxy.chainId(), governance.CHAINS.polygon);
-  });
-
-  it("Setting wormhole address to a wrong contract should reject", async function () {
-    // Deploy a new wormhole contract
-    const newSetup = await Setup.new();
-    const newImpl = await Implementation.new();
-
-    // encode initialisation data
-    const initData = newSetup.contract.methods
-      .setup(
-        newImpl.address,
-        [testSigner2.address], // A wrong signer
-        governance.CHAINS.ethereum,
-        wormholeGovernanceChainId,
-        wormholeGovernanceContract,
-      )
-      .encodeABI();
-
-    const newWormhole = await Wormhole.new(newSetup.address, initData);
-
-    // Creating the vaa to set the new wormhole address
-    const data = new governance.EvmSetWormholeAddress(
-      "ethereum",
-      newWormhole.address.replace("0x", ""),
-    ).encode();
-
-    const wrongVaa = await createVAAFromUint8Array(
-      data,
-      testGovernanceChainId,
-      testGovernanceEmitter,
-      1,
-    );
-
-    await expectRevertCustomError(
-      this.pythProxy.executeGovernanceInstruction(wrongVaa),
-      "InvalidGovernanceMessage",
-    );
-  });
-
-  // Version
-
-  it("Make sure version is the npm package version", async function () {
-    const contractVersion = await this.pythProxy.version();
-    const { version } = require("../package.json");
-
-    expect(contractVersion).equal(version);
-  });
-});
-
-const signAndEncodeVM = async function (
-  timestamp,
-  nonce,
-  emitterChainId,
-  emitterAddress,
-  sequence,
-  data,
-  signers,
-  guardianSetIndex,
-  consistencyLevel,
-) {
-  const body = [
-    web3.eth.abi.encodeParameter("uint32", timestamp).substring(2 + (64 - 8)),
-    web3.eth.abi.encodeParameter("uint32", nonce).substring(2 + (64 - 8)),
-    web3.eth.abi
-      .encodeParameter("uint16", emitterChainId)
-      .substring(2 + (64 - 4)),
-    web3.eth.abi.encodeParameter("bytes32", emitterAddress).substring(2),
-    web3.eth.abi.encodeParameter("uint64", sequence).substring(2 + (64 - 16)),
-    web3.eth.abi
-      .encodeParameter("uint8", consistencyLevel)
-      .substring(2 + (64 - 2)),
-    data.substr(2),
-  ];
-
-  const hash = web3.utils.soliditySha3(
-    web3.utils.soliditySha3("0x" + body.join("")),
-  );
-
-  let signatures = "";
-
-  for (let i in signers) {
-    const ec = new elliptic.ec("secp256k1");
-    const key = ec.keyFromPrivate(signers[i]);
-    const signature = key.sign(hash.substr(2), { canonical: true });
-
-    const packSig = [
-      web3.eth.abi.encodeParameter("uint8", i).substring(2 + (64 - 2)),
-      zeroPadBytes(signature.r.toString(16), 32),
-      zeroPadBytes(signature.s.toString(16), 32),
-      web3.eth.abi
-        .encodeParameter("uint8", signature.recoveryParam)
-        .substr(2 + (64 - 2)),
-    ];
-
-    signatures += packSig.join("");
-  }
-
-  const vm = [
-    web3.eth.abi.encodeParameter("uint8", 1).substring(2 + (64 - 2)),
-    web3.eth.abi
-      .encodeParameter("uint32", guardianSetIndex)
-      .substring(2 + (64 - 8)),
-    web3.eth.abi
-      .encodeParameter("uint8", signers.length)
-      .substring(2 + (64 - 2)),
-
-    signatures,
-    body.join(""),
-  ].join("");
-
-  return vm;
-};
-
-function zeroPadBytes(value, length) {
-  while (value.length < 2 * length) {
-    value = "0" + value;
-  }
-  return value;
-}
-
-async function createVAAFromUint8Array(
-  dataBuffer,
-  emitterChainId,
-  emitterAddress,
-  sequence,
-) {
-  const dataHex = "0x" + dataBuffer.toString("hex");
-  return (
-    "0x" +
-    (await signAndEncodeVM(
-      0,
-      0,
-      emitterChainId.toString(),
-      emitterAddress,
-      sequence,
-      dataHex,
-      [testSigner1PK],
-      0,
-      0,
-    ))
-  );
-}
-
-// There is no way to check event with given args has not emitted with expectEvent
-// or how many times an event was emitted. This function is implemented to count
-// the matching events and is used for the mentioned purposes.
-function getNumMatchingEvents(receipt, eventName, args) {
-  let matchCnt = 0;
-  for (let log of receipt.logs) {
-    if (log.event === eventName) {
-      let match = true;
-      for (let argKey in args) {
-        if (log.args[argKey].toString() !== args[argKey].toString()) {
-          match = false;
-          break;
-        }
-      }
-      if (match) {
-        matchCnt++;
-      }
-    }
-  }
-  return matchCnt;
-}
-
-function expectEventNotEmittedWithArgs(receipt, eventName, args) {
-  const matches = getNumMatchingEvents(receipt, eventName, args);
-  assert(
-    matches === 0,
-    `Expected no matching emitted event. But found ${matches}.`,
-  );
-}
-
-function expectEventMultipleTimes(receipt, eventName, args, cnt) {
-  const matches = getNumMatchingEvents(receipt, eventName, args);
-  assert(matches === cnt, `Expected ${cnt} event matches, found ${matches}.`);
-}
-
-async function expectRevertCustomError(promise, reason) {
-  try {
-    await promise;
-    expect.fail("Expected promise to throw but it didn't");
-  } catch (revert) {
-    if (reason) {
-      const reasonId = web3.utils.keccak256(reason + "()").substr(0, 10);
-      expect(
-        JSON.stringify(revert),
-        `Expected custom error ${reason} (${reasonId})`,
-      ).to.include(reasonId);
-    }
-  }
-}

+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/EchoTestUtils.sol → target_chains/ethereum/contracts/test/utils/EchoTestUtils.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/EntropyTestUtils.t.sol → target_chains/ethereum/contracts/test/utils/EntropyTestUtils.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/InvalidMagic.t.sol → target_chains/ethereum/contracts/test/utils/InvalidMagic.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/MockPriceFeedTestUtils.sol → target_chains/ethereum/contracts/test/utils/MockPriceFeedTestUtils.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/PulseSchedulerTestUtils.t.sol → target_chains/ethereum/contracts/test/utils/PulseSchedulerTestUtils.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/PythTestUtils.t.sol → target_chains/ethereum/contracts/test/utils/PythTestUtils.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/RandTestUtils.t.sol → target_chains/ethereum/contracts/test/utils/RandTestUtils.t.sol


+ 0 - 0
target_chains/ethereum/contracts/forge-test/utils/WormholeTestUtils.t.sol → target_chains/ethereum/contracts/test/utils/WormholeTestUtils.t.sol


+ 0 - 52
target_chains/ethereum/contracts/truffle-config.js

@@ -1,52 +0,0 @@
-require("dotenv").config({ path: ".env" });
-const HDWalletProvider = require("@truffle/hdwallet-provider");
-
-/**
- *
- * @param {string} url
- * @returns {HDWalletProvider} An instance of HDWalletProvider
- */
-function payerProvider(url) {
-  return () =>
-    new HDWalletProvider({
-      mnemonic: process.env.MNEMONIC,
-      providerOrUrl: url,
-      // This option makes deployments more reliable (by avoiding rate limiting errors) at the cost of
-      // taking a little longer.
-      pollingInterval: 12000,
-    });
-}
-
-module.exports = {
-  migrations_directory: process.env.MIGRATIONS_DIR,
-  networks: {
-    development: {
-      host: "127.0.0.1",
-      port: 8545,
-      network_id: "*",
-    },
-    [process.env.MIGRATIONS_NETWORK]: {
-      provider: payerProvider(process.env.RPC_URL),
-      network_id: process.env.NETWORK_ID,
-    },
-  },
-
-  compilers: {
-    solc: {
-      version: "0.8.29",
-      evmVersion: "paris",
-      settings: {
-        optimizer: {
-          enabled: true,
-          runs: 200,
-        },
-      },
-    },
-  },
-
-  plugins: [
-    "truffle-plugin-verify",
-    "truffle-contract-size",
-    "truffle-plugin-stdjsonin",
-  ],
-};

+ 33 - 43
target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol

@@ -6,6 +6,8 @@ import "./EntropyEventsV2.sol";
 import "./EntropyStructsV2.sol";
 import "./IEntropyV2.sol";
 
+/// @notice DEPRECATED: This interface is deprecated. Please use IEntropyV2 instead for new implementations.
+/// IEntropyV2 provides better callback handling and improved functionality.
 interface IEntropy is EntropyEvents, EntropyEventsV2, IEntropyV2 {
     // Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters
     // and initial commitment. Re-registering the same provider rotates the provider's commitment (and updates
@@ -38,17 +40,22 @@ interface IEntropy is EntropyEvents, EntropyEventsV2, IEntropyV2 {
     // their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
     // number. The user should then call fulfillRequest to construct the final random number.
     //
+    // WARNING: This method does NOT invoke a user callback. If you need callback functionality,
+    // use requestV2 from the IEntropyV2 interface instead.
+    //
     // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
     // Note that excess value is *not* refunded to the caller.
-    function request(
-        address provider,
-        bytes32 userCommitment,
-        bool useBlockHash
-    ) external payable returns (uint64 assignedSequenceNumber);
+    function request(address provider, bytes32 userCommitment, bool useBlockHash)
+        external
+        payable
+        returns (uint64 assignedSequenceNumber);
 
     // Request a random number. The method expects the provider address and a secret random number
     // in the arguments. It returns a sequence number.
     //
+    // DEPRECATED: This method is deprecated. Please use requestV2 from the IEntropyV2 interface instead,
+    // which provides better callback handling and gas limit control.
+    //
     // The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
     // The `entropyCallback` method on that interface will receive a callback with the generated random number.
     // `entropyCallback` will be run with the provider's default gas limit (see `getProviderInfo(provider).defaultGasLimit`).
@@ -57,10 +64,10 @@ interface IEntropy is EntropyEvents, EntropyEventsV2, IEntropyV2 {
     //
     // This method will revert unless the caller provides a sufficient fee (at least `getFee(provider)`) as msg.value.
     // Note that excess value is *not* refunded to the caller.
-    function requestWithCallback(
-        address provider,
-        bytes32 userRandomNumber
-    ) external payable returns (uint64 assignedSequenceNumber);
+    function requestWithCallback(address provider, bytes32 userRandomNumber)
+        external
+        payable
+        returns (uint64 assignedSequenceNumber);
 
     // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
     // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
@@ -69,12 +76,9 @@ interface IEntropy is EntropyEvents, EntropyEventsV2, IEntropyV2 {
     // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
     // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
     // If you need to use the returned random number more than once, you are responsible for storing it.
-    function reveal(
-        address provider,
-        uint64 sequenceNumber,
-        bytes32 userRevelation,
-        bytes32 providerRevelation
-    ) external returns (bytes32 randomNumber);
+    function reveal(address provider, uint64 sequenceNumber, bytes32 userRevelation, bytes32 providerRevelation)
+        external
+        returns (bytes32 randomNumber);
 
     // Fulfill a request for a random number. This method validates the provided userRandomness
     // and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated
@@ -93,30 +97,22 @@ interface IEntropy is EntropyEvents, EntropyEventsV2, IEntropyV2 {
         bytes32 providerRevelation
     ) external;
 
-    function getProviderInfo(
-        address provider
-    ) external view returns (EntropyStructs.ProviderInfo memory info);
+    function getProviderInfo(address provider) external view returns (EntropyStructs.ProviderInfo memory info);
 
-    function getRequest(
-        address provider,
-        uint64 sequenceNumber
-    ) external view returns (EntropyStructs.Request memory req);
+    function getRequest(address provider, uint64 sequenceNumber)
+        external
+        view
+        returns (EntropyStructs.Request memory req);
 
     // Get the fee charged by provider for a request with the default gasLimit (`request` or `requestWithCallback`).
     // If you are calling any of the `requestV2` methods, please use `getFeeV2`.
     function getFee(address provider) external view returns (uint128 feeAmount);
 
-    function getAccruedPythFees()
-        external
-        view
-        returns (uint128 accruedPythFeesInWei);
+    function getAccruedPythFees() external view returns (uint128 accruedPythFeesInWei);
 
     function setProviderFee(uint128 newFeeInWei) external;
 
-    function setProviderFeeAsFeeManager(
-        address provider,
-        uint128 newFeeInWei
-    ) external;
+    function setProviderFeeAsFeeManager(address provider, uint128 newFeeInWei) external;
 
     function setProviderUri(bytes calldata newUri) external;
 
@@ -135,19 +131,13 @@ interface IEntropy is EntropyEvents, EntropyEventsV2, IEntropyV2 {
 
     // Advance the provider commitment and increase the sequence number.
     // This is used to reduce the `numHashes` required for future requests which leads to reduced gas usage.
-    function advanceProviderCommitment(
-        address provider,
-        uint64 advancedSequenceNumber,
-        bytes32 providerRevelation
-    ) external;
+    function advanceProviderCommitment(address provider, uint64 advancedSequenceNumber, bytes32 providerRevelation)
+        external;
 
-    function constructUserCommitment(
-        bytes32 userRandomness
-    ) external pure returns (bytes32 userCommitment);
+    function constructUserCommitment(bytes32 userRandomness) external pure returns (bytes32 userCommitment);
 
-    function combineRandomValues(
-        bytes32 userRandomness,
-        bytes32 providerRandomness,
-        bytes32 blockHash
-    ) external pure returns (bytes32 combinedRandomness);
+    function combineRandomValues(bytes32 userRandomness, bytes32 providerRandomness, bytes32 blockHash)
+        external
+        pure
+        returns (bytes32 combinedRandomness);
 }

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio