From 9da2a7b6b612b5bd48cff68251f02c3323d2a24c Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 12 Jan 2024 14:48:09 -0500 Subject: [PATCH 1/3] fix: wormhole additional checks --- contracts/pyth-governance-v1.clar | 2 +- .../{wormhole-core-v1.clar => wormhole-core-v2.clar} | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) rename contracts/wormhole/{wormhole-core-v1.clar => wormhole-core-v2.clar} (97%) diff --git a/contracts/pyth-governance-v1.clar b/contracts/pyth-governance-v1.clar index 4f46dcf..fbac19c 100644 --- a/contracts/pyth-governance-v1.clar +++ b/contracts/pyth-governance-v1.clar @@ -75,7 +75,7 @@ pyth-oracle-contract: .pyth-oracle-v2, pyth-decoder-contract: .pyth-pnau-decoder-v1, pyth-storage-contract: .pyth-store-v1, - wormhole-core-contract: .wormhole-core-v1 + wormhole-core-contract: .wormhole-core-v2 }) (define-read-only (check-execution-flow diff --git a/contracts/wormhole/wormhole-core-v1.clar b/contracts/wormhole/wormhole-core-v2.clar similarity index 97% rename from contracts/wormhole/wormhole-core-v1.clar rename to contracts/wormhole/wormhole-core-v2.clar index 786d1f1..6c52199 100644 --- a/contracts/wormhole/wormhole-core-v1.clar +++ b/contracts/wormhole/wormhole-core-v2.clar @@ -66,6 +66,14 @@ (define-constant ERR_GSU_CHECK_CHAIN (err u1303)) ;; Guardian Set Update new index invalid (define-constant ERR_GSU_CHECK_INDEX (err u1304)) +;; Guardian Set Update emission payload unauthorized +(define-constant ERR_GSU_CHECK_EMITTER (err u1305)) + +;; Guardian set upgrade emitting address +(define-constant GSU-EMITTING-ADDRESS 0x0000000000000000000000000000000000000000000000000000000000000004) +;; Guardian set upgrade emitting chain +(define-constant GSU-EMITTING-CHAIN u1) + (define-constant hk-cursor-v2 'SP2J933XB2CP2JQ1A4FGN8JA968BBG3NK3EKZ7Q9F.hk-cursor-v2) @@ -198,6 +206,10 @@ ;; Ensure that enough uncompressed-public-keys were provided (asserts! (is-eq (len uncompressed-public-keys) (len eth-addresses)) ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS) + ;; Check emitting address + (asserts! (is-eq (get emitter-address vaa) GSU-EMITTING-ADDRESS) ERR_GSU_CHECK_EMITTER) + ;; Check emitting address + (asserts! (is-eq (get emitter-chain vaa) GSU-EMITTING-CHAIN) ERR_GSU_CHECK_EMITTER) ;; Update storage (map-set guardian-sets { set-id: set-id } (get result consolidated-public-keys)) (var-set active-guardian-set-id set-id) From 550685c4683bcab9d40063d5d4bcae57eef590ff Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 12 Jan 2024 15:20:54 -0500 Subject: [PATCH 2/3] chore: fix broken tests --- Clarinet.toml | 4 +- unit-tests/pyth/oracle.test.ts | 2 +- unit-tests/pyth/pnau.test.ts | 2 +- unit-tests/pyth/ptgm.test.ts | 6 +- unit-tests/wormhole/helpers.ts | 9 +++ unit-tests/wormhole/vaa.test.ts | 126 ++++++++++++++++++++++++++++---- 6 files changed, 127 insertions(+), 22 deletions(-) diff --git a/Clarinet.toml b/Clarinet.toml index de8d8c1..1a88fc9 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -39,8 +39,8 @@ path = 'contracts/pyth-traits-v1.clar' clarity_version = 2 epoch = 2.4 -[contracts.wormhole-core-v1] -path = 'contracts/wormhole/wormhole-core-v1.clar' +[contracts.wormhole-core-v2] +path = 'contracts/wormhole/wormhole-core-v2.clar' clarity_version = 2 epoch = 2.4 diff --git a/unit-tests/pyth/oracle.test.ts b/unit-tests/pyth/oracle.test.ts index 88e2115..2026d8a 100644 --- a/unit-tests/pyth/oracle.test.ts +++ b/unit-tests/pyth/oracle.test.ts @@ -7,7 +7,7 @@ import { wormhole } from "../wormhole/helpers"; const pythOracleContractName = "pyth-oracle-v2"; const pythDecoderPnauContractName = "pyth-pnau-decoder-v1"; const pythStorageContractName = "pyth-store-v1"; -const wormholeCoreContractName = "wormhole-core-v1"; +const wormholeCoreContractName = "wormhole-core-v2"; describe("pyth-oracle-v2::decode-and-verify-price-feeds mainnet VAAs", () => { const accounts = simnet.getAccounts(); diff --git a/unit-tests/pyth/pnau.test.ts b/unit-tests/pyth/pnau.test.ts index aa56ded..ebb3147 100644 --- a/unit-tests/pyth/pnau.test.ts +++ b/unit-tests/pyth/pnau.test.ts @@ -7,7 +7,7 @@ const pythOracleContractName = "pyth-oracle-v2"; const pythDecoderPnauContractName = "pyth-pnau-decoder-v1"; const pythGovernanceContractName = "pyth-governance-v1"; const pythStorageContractName = "pyth-store-v1"; -const wormholeCoreContractName = "wormhole-core-v1"; +const wormholeCoreContractName = "wormhole-core-v2"; describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds success", () => { const accounts = simnet.getAccounts(); diff --git a/unit-tests/pyth/ptgm.test.ts b/unit-tests/pyth/ptgm.test.ts index c06fc68..8980613 100644 --- a/unit-tests/pyth/ptgm.test.ts +++ b/unit-tests/pyth/ptgm.test.ts @@ -2,7 +2,7 @@ import { Cl, ClarityType } from "@stacks/transactions"; import { beforeEach, describe, expect, it } from "vitest"; import { wormhole } from "../wormhole/helpers"; import { pyth } from "./helpers"; -import { hexToBytes } from "@noble/hashes/utils"; +import { bytesToHex, hexToBytes } from "@noble/hashes/utils"; import { ParsedTransactionResult } from "@hirosystems/clarinet-sdk"; import { ptgmTestnetVaas } from "./fixtures"; @@ -10,7 +10,7 @@ const pythOracleContractName = "pyth-oracle-v2"; const pythStorageContractName = "pyth-store-v1"; const pythDecoderPnauContractName = "pyth-pnau-decoder-v1"; const pythGovernanceContractName = "pyth-governance-v1"; -const wormholeCoreContractName = "wormhole-core-v1"; +const wormholeCoreContractName = "wormhole-core-v2"; const initialFeeRecipient = "ST3CRXBDXQ2N5P7E25Q39MEX1HSMRDSEAP1JST19D"; describe("pyth-governance-v1::update-fee-value mainnet VAAs", () => { @@ -244,7 +244,7 @@ describe("pyth-governance-v1::update-wormhole-core-contract", () => { const guardianSet = wormhole.generateGuardianSetKeychain(19); let updateWormholeContract = { address: "ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG", - contractName: "wormhole-core-v2", + contractName: "wormhole-core-v3", }; let ptgmVaaPayload = pyth.buildPtgmVaaPayload({ updateWormholeContract }); diff --git a/unit-tests/wormhole/helpers.ts b/unit-tests/wormhole/helpers.ts index 64598a0..fc98304 100644 --- a/unit-tests/wormhole/helpers.ts +++ b/unit-tests/wormhole/helpers.ts @@ -12,6 +12,7 @@ import { hmac } from "@noble/hashes/hmac"; import { sha256 } from "@noble/hashes/sha256"; import { gsuMainnetVaas } from "./fixtures"; import { pyth } from "../pyth/helpers"; +import { hexToBytes } from "@noble/hashes/utils"; secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)); @@ -81,6 +82,13 @@ export namespace wormhole { payload?: Uint8Array; } + export const GovernanceUpdateEmitter = { + chain: 1, + address: hexToBytes( + "0000000000000000000000000000000000000000000000000000000000000004", + ), + }; + export namespace fc_ext { // Helper for generating a VAA Body; // Wire format reminder: @@ -466,6 +474,7 @@ export namespace wormhole { ); let vaaBody = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload, + emitter: wormhole.GovernanceUpdateEmitter, }); let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { version: 1, diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts index dfb2c0e..d574024 100644 --- a/unit-tests/wormhole/vaa.test.ts +++ b/unit-tests/wormhole/vaa.test.ts @@ -3,17 +3,19 @@ import { expect, describe, beforeEach } from "vitest"; import { it, fc } from "@fast-check/vitest"; import { wormhole } from "./helpers"; import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; +import { hexToBytes } from "@noble/hashes/utils"; -const contractName = "wormhole-core-v1"; +const contractName = "wormhole-core-v2"; const verbosity = 0; -describe("wormhole-core-v1::parse-vaa success", () => { +describe("wormhole-core-v2::parse-vaa success", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const keychain = wormhole.generateGuardianSetKeychain(19); it("should succeed if the vaa is valid", () => { - let body = wormhole.buildValidVaaBodySpecs(); + let emitter = wormhole.GovernanceUpdateEmitter; + let body = wormhole.buildValidVaaBodySpecs({ emitter }); let headerSpecs = wormhole.buildValidVaaHeaderSpecs(keychain, body); fc.assert( @@ -58,7 +60,7 @@ describe("wormhole-core-v1::parse-vaa success", () => { }); }); -describe("wormhole-core-v1::update-guardians-set failures", () => { +describe("wormhole-core-v2::update-guardians-set failures", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const keychain = wormhole.generateGuardianSetKeychain(19); @@ -75,6 +77,7 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { ); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload, + emitter: wormhole.GovernanceUpdateEmitter, }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, @@ -107,6 +110,7 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { ); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload, + emitter: wormhole.GovernanceUpdateEmitter, }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, @@ -139,6 +143,7 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { ); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload, + emitter: wormhole.GovernanceUpdateEmitter, }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, @@ -174,6 +179,7 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { ); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload, + emitter: wormhole.GovernanceUpdateEmitter, }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, @@ -193,9 +199,83 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { ); expect(res.result).toBeErr(Cl.uint(1301)); }); + + it("should fail if the emitter-address is invalid", () => { + // Before performing this test, we need to setup the guardian set + let emitter = { + chain: wormhole.GovernanceUpdateEmitter.chain, + address: hexToBytes("0000000000000000000000000000000000000000000000000000000000000009"), + }; + let guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + keychain, + 2, + 0, + 1, + wormhole.validGuardianRotationModule, + ); + let body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, + emitter, + }); + let header = wormhole.buildValidVaaHeader(keychain, body, { + version: 1, + guardianSetId: 0, + signatures: [], + }); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callPublicFn( + contractName, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender, + ); + expect(res.result).toBeErr(Cl.uint(1305)); + }); + + it("should fail if the emitter-chain is invalid", () => { + // Before performing this test, we need to setup the guardian set + let emitter = { + chain: 9, + address: wormhole.GovernanceUpdateEmitter.address, + }; + let guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + keychain, + 2, + 0, + 1, + wormhole.validGuardianRotationModule, + ); + let body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, + emitter, + }); + let header = wormhole.buildValidVaaHeader(keychain, body, { + version: 1, + guardianSetId: 0, + signatures: [], + }); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callPublicFn( + contractName, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender, + ); + expect(res.result).toBeErr(Cl.uint(1305)); + }); }); -describe("wormhole-core-v1::update-guardians-set success", () => { +describe("wormhole-core-v2::update-guardians-set success", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); @@ -276,6 +356,7 @@ describe("wormhole-core-v1::update-guardians-set success", () => { ); const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload, + emitter: wormhole.GovernanceUpdateEmitter, }); const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, @@ -335,6 +416,7 @@ describe("wormhole-core-v1::update-guardians-set success", () => { ); const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload, + emitter: wormhole.GovernanceUpdateEmitter, }); const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, @@ -357,7 +439,7 @@ describe("wormhole-core-v1::update-guardians-set success", () => { }); }); -describe("wormhole-core-v1::parse-and-verify-vaa success", () => { +describe("wormhole-core-v2::parse-and-verify-vaa success", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); @@ -369,7 +451,9 @@ describe("wormhole-core-v1::parse-and-verify-vaa success", () => { }); it("should succeed if the vaa is valid", () => { - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs({ + emitter: wormhole.GovernanceUpdateEmitter, + }); let headerSpecs = wormhole.buildValidVaaHeaderSpecs(guardianSet1Keys, body); fc.assert( @@ -417,7 +501,9 @@ describe("wormhole-core-v1::parse-and-verify-vaa success", () => { // Before performing this test, we need to setup the guardian set let cutoff = 13; let guardianSet1KeysSubset = guardianSet1Keys.splice(0, cutoff); - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs({ + emitter: wormhole.GovernanceUpdateEmitter, + }); let header = wormhole.buildValidVaaHeader(guardianSet1KeysSubset, body, { version: 1, guardianSetId: 1, @@ -439,7 +525,7 @@ describe("wormhole-core-v1::parse-and-verify-vaa success", () => { }); }); -describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { +describe("wormhole-core-v2::parse-and-verify-vaa failures", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); @@ -451,7 +537,9 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { it("should fail if the version is invalid", () => { // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs({ + emitter: wormhole.GovernanceUpdateEmitter, + }); let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 2, guardianSetId: 1, @@ -472,7 +560,9 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { it("should fail if the guardianSetId is invalid", () => { // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs({ + emitter: wormhole.GovernanceUpdateEmitter, + }); let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 2, @@ -493,7 +583,9 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { it("should fail if one key is being used multiple times", () => { // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs({ + emitter: wormhole.GovernanceUpdateEmitter, + }); let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1, @@ -518,7 +610,9 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { it("should fail if too few signatures are being sent", () => { // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs({ + emitter: wormhole.GovernanceUpdateEmitter, + }); let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1, @@ -541,7 +635,9 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { it("should fail if quorum is not met", () => { // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs({ + emitter: wormhole.GovernanceUpdateEmitter, + }); let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1, @@ -566,7 +662,7 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { }); }); -describe("wormhole-core-v1::update-guardians-set mainnet guardian rotations", () => { +describe("wormhole-core-v2::update-guardians-set mainnet guardian rotations", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; let block: ParsedTransactionResult[] | undefined = undefined; From ffeb6d730eb88a01528c35a9faa8eb07012b9514 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 12 Jan 2024 15:21:28 -0500 Subject: [PATCH 3/3] chore: update README --- README.md | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index accffef..c57df18 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ - - / / ▶ Stacks Pyth Bridge + / / ▶ Stacks Pyth Bridge / --- / Bridging Pyth price feeds to the Stacks blockchain. - / / Retrieve trading pairs (BTC-USD, STX-USD, etc.) from Clarity smart contracts. - + / / Retrieve trading pairs (BTC-USD, STX-USD, etc.) from Clarity smart contracts.                                    [![Introduction](https://img.shields.io/badge/%23-%20Introduction%20-orange?labelColor=gray)](#Introduction)     [![Features](https://img.shields.io/badge/%23-Features-orange?labelColor=gray)](#Features) @@ -10,7 +8,7 @@     [![Documentation](https://img.shields.io/badge/%23-Documentation-orange?labelColor=gray)](#Documentation)     [![Contribute](https://img.shields.io/badge/%23-Contribute-orange?labelColor=gray)](#Contribute) -*** +--- # Introduction @@ -43,8 +41,8 @@ $ npm test ### Latest Deployments -| network | address | -|---------|---------------------------------------------------------------------| +| network | address | +| ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | testnet | [ST2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY1JKX06E.pyth-helper-v1](https://explorer.hiro.so/txid/0x5339f90ccdbb88e437b9b889613f1554c377d5815e3b90bbc6305b317b7bb8e8?chain=testnet) | | mainnet | [SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-helper-v1](https://explorer.hiro.so/txid/0xd86c2fda8a090c43016250c33231878673af62ac95d9c50645f6e2c303b9a173?chain=mainnet) | @@ -53,21 +51,21 @@ $ npm test The `pyth-helper-v1` contract is exposing the following method: ```clarity -(define-public (read-price +(define-public (read-price (price-feed-id (buff 32)))) ``` That can be consumed with the following invocation: ```clarity -(contract-call? +(contract-call? 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-oracle-v2 ;; Address of the helper contract read-price 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43) ;; BTC-USD price identifier { pyth-storage-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-storage-v1, pyth-decoder-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-pnau-decoder-v1, - wormhole-core-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.wormhole-core-v1 + wormhole-core-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.wormhole-core-v2 } ``` @@ -125,21 +123,21 @@ This sequence of bytes is a Verified Action Approvals (VAA) including the price This VAA can be encoded as a Clarity buffer, and submitted to the Pyth contract using the following: ```clarity -(contract-call? +(contract-call? 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-oracle-v2 ;; Address of the helper contract verify-and-update-price 0x504e41550100000003b8...a7b10321ad7c2404a910 ;; BTC-USD price update { pyth-storage-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-storage-v1, pyth-decoder-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.pyth-pnau-decoder-v1, - wormhole-core-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.wormhole-core-v1 + wormhole-core-contract: 'SP2T5JKWWP3FYYX4YRK8GK5BG2YCNGEAEY2P2PKN0.wormhole-core-v2 }) ``` If the VAA is valid, the contract call will return a payload with the following signature: ```clarity -(response +(response (list 64 { price-identifier: (buff 32), price: int, @@ -149,7 +147,7 @@ If the VAA is valid, the contract call will return a payload with the following ema-conf: uint, publish-time: uint, prev-publish-time: uint, - }) + }) uint) ```