From 2cee690de0e39dca6c29bee40fe29ffdb3b273ed Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Mon, 16 Oct 2023 09:03:15 -0400 Subject: [PATCH 01/18] fix: typo + comments --- unit-tests/utils/cursor.test.ts | 7 +++---- unit-tests/utils/helper.ts | 11 ++++++----- unit-tests/utils/merkle-proofs.test.ts | 1 - 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/unit-tests/utils/cursor.test.ts b/unit-tests/utils/cursor.test.ts index 9fc4bf8..56075a5 100644 --- a/unit-tests/utils/cursor.test.ts +++ b/unit-tests/utils/cursor.test.ts @@ -1,6 +1,5 @@ import { Cl } from "@stacks/transactions"; import { describe, expect } from "vitest"; -// import { tx } from "@hirosystems/clarinet-sdk"; import { it, fc } from '@fast-check/vitest'; import { concatTypedArrays, uint8toBytes, uint16toBytes, uint32toBytes, bigintToBuffer } from './helper'; @@ -107,7 +106,7 @@ describe("hiro-kit::cursor - buffers", () => { sender ); - // Early return: we're tryint to read more byte than the len of the buffer + // Early return: we're trying to read more byte than the len of the buffer if (toRead > data.length) { expect(res.result).toBeErr(Cl.uint(1)) return; @@ -130,7 +129,7 @@ describe("hiro-kit::cursor - buffers", () => { sender ); - // Early return: we're tryint to read more byte than the len of the buffer + // Early return: we're trying to read more byte than the len of the buffer if (toRead == data.length) { expect(res.result).toBeErr(Cl.uint(1)) return; @@ -174,7 +173,7 @@ describe("hiro-kit::cursor - buffers", () => { sender ); - // Early return: we're tryint to read more byte than the len of the buffer + // Early return: we're trying to read more byte than the len of the buffer if (toRead > data.length) { expect(res.result).toBeErr(Cl.uint(1)) return; diff --git a/unit-tests/utils/helper.ts b/unit-tests/utils/helper.ts index d760dc0..52fd2d0 100644 --- a/unit-tests/utils/helper.ts +++ b/unit-tests/utils/helper.ts @@ -25,15 +25,16 @@ export const uint32toBytes = (num: number) => { export function bigintToBuffer(bigintValue: bigint, byteLength: number) { if (bigintValue >= 0n) { - let hexString = bigintValue.toString(16); // Convert BigInt to hexadecimal string - const padding = byteLength * 2 - hexString.length; // Calculate padding - + // Convert BigInt to hexadecimal string + let hexString = bigintValue.toString(16); + // Calculate padding + const padding = byteLength * 2 - hexString.length; // Add leading zeros for padding for (let i = 0; i < padding; i++) { hexString = '0' + hexString; } - - return Buffer.from(hexString, 'hex'); // Create Buffer from padded hexadecimal string + // Create Buffer from padded hexadecimal string + return Buffer.from(hexString, 'hex'); } else { // Handle negative BigInt const twosComplement = (BigInt(1) << BigInt(byteLength * 8)) + bigintValue; diff --git a/unit-tests/utils/merkle-proofs.test.ts b/unit-tests/utils/merkle-proofs.test.ts index 3314b3c..3efe34a 100644 --- a/unit-tests/utils/merkle-proofs.test.ts +++ b/unit-tests/utils/merkle-proofs.test.ts @@ -1,6 +1,5 @@ import { Cl } from "@stacks/transactions"; import { describe, expect, it } from "vitest"; -// import { tx } from "@hirosystems/clarinet-sdk"; const merkle_contract_name = "hk-merkle-tree-keccak160-v1"; From 6e9986e83b4bf0278af937f057770a9f44aaa1b0 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Mon, 16 Oct 2023 09:12:09 -0400 Subject: [PATCH 02/18] chore: upgrade clarinet-sdk --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 72cbf53..73b0f5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@hirosystems/clarinet-sdk": "^0.10.0", + "@hirosystems/clarinet-sdk": "^1.0.2", "@stacks/transactions": "^6.9.0", "vitest": "^0.34.1", "vitest-environment-clarinet": "^0.2.0" @@ -372,11 +372,11 @@ } }, "node_modules/@hirosystems/clarinet-sdk": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@hirosystems/clarinet-sdk/-/clarinet-sdk-0.10.0.tgz", - "integrity": "sha512-LIDMtL+lETB6L0OZ3/6AeS3fYz/sLhB5uEITf78Y41BVOKFqAlTE/t8UkphPK2/WQkqoIvdTHQlFus28Thymmw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@hirosystems/clarinet-sdk/-/clarinet-sdk-1.0.2.tgz", + "integrity": "sha512-++bvBuBdGc0SWYxio+QdVc7U7YL8kydQVth7gnxrGddrS3PcQ+Fgoc/TAwUhdodKk6HMCFpA0+f84KuE6kfe+g==", "dependencies": { - "@hirosystems/clarinet-sdk-wasm": "^1.0.1", + "@hirosystems/clarinet-sdk-wasm": "^1.0.4", "@stacks/transactions": "^6.9.0", "kolorist": "^1.8.0", "prompts": "^2.4.2", @@ -391,9 +391,9 @@ } }, "node_modules/@hirosystems/clarinet-sdk-wasm": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@hirosystems/clarinet-sdk-wasm/-/clarinet-sdk-wasm-1.0.1.tgz", - "integrity": "sha512-HmQQl89YsEEWn431xq3STDgJQDQ1E8d0C458mvylT65Ajj0koEIBoP76mwvWMg6xjvwWKW4hTIc+SlulfI3Cwg==" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@hirosystems/clarinet-sdk-wasm/-/clarinet-sdk-wasm-1.0.4.tgz", + "integrity": "sha512-8695eWvbKZ2P4/z5LVPWB0e3lT58zVK4eoNDSgGivurpNLrqrkWpFJwauBnBhNo//8enkuvfgedP6CiBVfIZwQ==" }, "node_modules/@jest/schemas": { "version": "29.6.3", diff --git a/package.json b/package.json index d8fdd73..4cc85bc 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "license": "ISC", "type": "module", "dependencies": { - "@hirosystems/clarinet-sdk": "^0.10.0", + "@hirosystems/clarinet-sdk": "^1.0.2", "@stacks/transactions": "^6.9.0", "vitest": "^0.34.1", "vitest-environment-clarinet": "^0.2.0" From aac5dff0532e310d4e0349b5e32b9dca48c51bf2 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 17 Oct 2023 09:03:29 -0400 Subject: [PATCH 03/18] test: draft vaa fuzz testing --- package-lock.json | 58 ++++-- package.json | 4 +- unit-tests/wormhole/helper.ts | 354 ++++++++++++++++++++++++++++++++ unit-tests/wormhole/vaa.test.ts | 97 +++++++++ 4 files changed, 495 insertions(+), 18 deletions(-) create mode 100644 unit-tests/wormhole/helper.ts create mode 100644 unit-tests/wormhole/vaa.test.ts diff --git a/package-lock.json b/package-lock.json index 73b0f5a..e811d8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,11 @@ "license": "ISC", "dependencies": { "@hirosystems/clarinet-sdk": "^1.0.2", + "@noble/hashes": "^1.3.2", + "@noble/secp256k1": "^2.0.0", "@stacks/transactions": "^6.9.0", "vitest": "^0.34.1", - "vitest-environment-clarinet": "^0.2.0" + "vitest-environment-clarinet": "^1.0.3" }, "devDependencies": { "@fast-check/vitest": "^0.0.8", @@ -412,20 +414,20 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" }, "node_modules/@noble/hashes": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", - "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } }, "node_modules/@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz", + "integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==", "funding": [ { "type": "individual", @@ -469,6 +471,28 @@ "lodash.clonedeep": "^4.5.0" } }, + "node_modules/@stacks/transactions/node_modules/@noble/hashes": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", + "integrity": "sha512-LTMZiiLc+V4v1Yi16TD6aX2gmtKszNye0pQgbaLqkvhIqP7nVsSaJsWloGQjJfJ8offaoP5GtX3yY5swbcJxxQ==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@stacks/transactions/node_modules/@noble/secp256k1": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", + "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, "node_modules/@types/bn.js": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.2.tgz", @@ -1335,11 +1359,11 @@ } }, "node_modules/vitest-environment-clarinet": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/vitest-environment-clarinet/-/vitest-environment-clarinet-0.2.0.tgz", - "integrity": "sha512-0ensLaHs7QlST8SayRfTI6VFP5Xh7gXDKlWXh67OqCtOlNYxJ4wfxnsXf6CcUmNAqPAVHEcbqR5E3IMPZ4SCdQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/vitest-environment-clarinet/-/vitest-environment-clarinet-1.0.3.tgz", + "integrity": "sha512-h/FeWPiEBS4a359Y8ZNo8nsftsfEoyLtZpJdnvDggDzcEUNkAsssU4tQzLp+KPm2VohAleqjFGSYMOGRbgLtDA==", "peerDependencies": { - "@hirosystems/clarinet-sdk": "0", + "@hirosystems/clarinet-sdk": "1", "vitest": "0" } }, diff --git a/package.json b/package.json index 4cc85bc..8118b13 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,11 @@ "type": "module", "dependencies": { "@hirosystems/clarinet-sdk": "^1.0.2", + "@noble/hashes": "^1.3.2", + "@noble/secp256k1": "^2.0.0", "@stacks/transactions": "^6.9.0", "vitest": "^0.34.1", - "vitest-environment-clarinet": "^0.2.0" + "vitest-environment-clarinet": "^1.0.3" }, "devDependencies": { "@fast-check/vitest": "^0.0.8", diff --git a/unit-tests/wormhole/helper.ts b/unit-tests/wormhole/helper.ts new file mode 100644 index 0000000..4bdf3c1 --- /dev/null +++ b/unit-tests/wormhole/helper.ts @@ -0,0 +1,354 @@ +import { fc } from '@fast-check/vitest'; +import { bigintToBuffer } from '../utils/helper'; +import * as secp from '@noble/secp256k1'; +import { + keccak_256 + } from '@noble/hashes/sha3'; +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +import { hmac } from '@noble/hashes/hmac'; +import { sha256 } from '@noble/hashes/sha256'; +secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) + +export namespace wormhole_fc { + + export interface Guardian { + guardianId: number, + secretKey: Uint8Array, + compressedPublicKey: Uint8Array, + uncompressedPublicKey: Uint8Array, + ethereumAddress: Uint8Array, + } + + export interface VaaHeader { + validVersion?: boolean, + validGuardianSetId?: boolean, + signatures?: Uint8Array[], + } + + export interface VaaHeaderBuildOptions { + opts?: VaaHeader + } + + export const generateGuardianSetKeychain = () => { + let keychain = []; + for (let i = 0; i < 19; i++) { + let secretKey = secp.utils.randomPrivateKey(); + let uncompressedPublicKey = secp.getPublicKey(secretKey, false); + let ethereumAddress = keccak_256(uncompressedPublicKey).slice(12, 32); + keychain.push({ + guardianId: i, + secretKey: secretKey, + uncompressedPublicKey: uncompressedPublicKey, + ethereumAddress: ethereumAddress, + compressedPublicKey: secp.getPublicKey(secretKey, true), + }) + } + return keychain + } + + // Helper for generating a VAA Header; + // Wire format reminder: + // =========================== + // VAA Header + // byte version (VAA Version) + // u32 guardian_set_index (Indicates which guardian set is signing) + // u8 len_signatures (Number of signatures stored) + // [][66]byte signatures (Collection of ecdsa signatures) + export const vaaHeader = (header?: VaaHeaderBuildOptions) => { + let args = []; + if (header && header.opts && header.opts.validVersion) { + args.push(fc.constant(2)) + } else { + args.push(fc.nat({ max: 255 })) + } + + if (header && header.opts && header.opts.validGuardianSetId) { + args.push(fc.constant(0)) + } else { + args.push(fc.nat(4294967295)); + } + + + if (header && header.opts && header.opts.signatures) { + // Signatures + args.push(fc.constant(header.opts.signatures)) + // Random sequences that looks like signatures + let max = 19 - header.opts.signatures.length + let min = 0 + args.push(fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: min, maxLength: max })) + } else { + // Signatures + args.push(fc.constant([])) + // Random sequences that looks like signatures + let max = 19 + let min = 0 + args.push(fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: min, maxLength: max })) + } + return args + } + + export interface VaaBody { + timestamp: number, + emitterChain: number, + nonce: number, + emitterAddress: Uint8Array, + sequence: number, + consistencyLevel: number, + payload: Uint8Array, + } + + export interface VaaBodyBuildOptions { + timestamp?: number, + emitterChain?: number, + nonce?: number, + emitterAddress?: Uint8Array, + sequence?: number, + consistencyLevel?: number, + payload?: Uint8Array, + } + + // Helper for generating a VAA Body; + // Wire format reminder: + // =========================== + // VAA Body + // u32 timestamp (Timestamp of the block where the source transaction occurred) + // u32 nonce (A grouping number) + // u16 emitter_chain (Wormhole ChainId of emitter contract) + // [32]byte emitter_address (Emitter contract address, in Wormhole format) + // u64 sequence (Strictly increasing sequence, tied to emitter address & chain) + // u8 consistency_level (What finality level was reached before emitting this message) + // []byte payload (VAA message content) + export const vaaBody = (vaa?: VaaBodyBuildOptions) => { + let args = []; + // Timestamp + if (vaa && vaa.opts && vaa.opts.timestamp) { + args.push(fc.constant(vaa.opts.timestamp)); + } else { + // TODO: what is an invalid timestamp? + args.push(fc.nat(4294967295)); + } + + // Nonce + if (vaa && vaa.opts && vaa.opts.nonce) { + args.push(fc.constant(vaa.opts.nonce)); + } else { + // TODO: what is an invalid timestamp? + args.push(fc.nat(4294967295)); + } + + // Emitter chain + if (vaa && vaa.opts && vaa.opts.emitterChain) { + args.push(fc.constant(vaa.opts.emitterChain)) + } else { + args.push(fc.nat(65535)); + } + + // Emitter address + if (vaa && vaa.opts && vaa.opts.emitterAddress) { + args.push(fc.constant(vaa.opts.emitterAddress)) + } else { + args.push(fc.uint8Array({ minLength: 32, maxLength: 32 })); + } + + // Sequence + if (vaa && vaa.opts && vaa.opts.sequence) { + args.push(fc.constant(vaa.opts.sequence)) + } else { + args.push(fc.bigUintN(64)); + } + + // Consistency level + if (vaa && vaa.opts && vaa.opts.consistencyLevel) { + args.push(fc.constant(vaa.opts.consistencyLevel)) + } else { + args.push(fc.nat(255)); + } + + // Payload + if (vaa && vaa.opts && vaa.opts.payload) { + args.push(fc.constant(vaa.opts.payload)) + } else { + args.push(fc.uint8Array({ minLength: 20, maxLength: 2048 })); + } + + return args; + } + + export const vaa = (opts: { keyChain?: Guardian[], body: VaaBody, validSig: boolean }) => { + let signatures = []; + if (opts.validSig && opts.keyChain && opts.validSig === true) { + for (let guardian of opts.keyChain) { + const messageHash = keccak_256(keccak_256(opts.body..payload)); + const signature = secp.sign(messageHash, guardian.secretKey) + + const id = Buffer.alloc(1); + id.writeUint8(guardian.guardianId, 0); + + // v.writeUint8(signature.addRecoveryBit, 1); + if (signature.recovery) { + const rec = Buffer.alloc(1); + rec.writeUint8(signature.recovery, 0); + signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); + } else { + console.log(`-> ${guardian.guardianId} / ${guardian.compressedPublicKey}`); + const rec = Buffer.alloc(1); + rec.writeUint8(0, 0); + signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); + } + } + } + + return [ + ...vaaHeader({ opts: { + validVersion: true, + validGuardianSetId: true, + signatures: signatures, + }}), + ...vaaBody({ + opts: { + timestamp: 0, + emitterChain: 0, + nonce: 0, + emitterAddress: , + sequence: 0, + consistencyLevel: 0, + payload: opts.payload + } + })] + } + + export const serializeVaa = (version: number, guardianSetIndex: number, signatures: Uint8Array[], timestamp: number, nonce: number, emitterChain: number, emitterAddress: Uint8Array, sequence: bigint, consistencyLevel: number, payload: Uint8Array) => { + const components = []; + var v = Buffer.alloc(1); + v.writeUint8(version, 0); + components.push(v); + + v = Buffer.alloc(4); + v.writeUInt32BE(guardianSetIndex, 0); + components.push(v); + + v = Buffer.alloc(1); + v.writeUint8(signatures.length, 0); + components.push(v); + + components.push(Buffer.concat(signatures)); + + v = Buffer.alloc(4); + v.writeUInt32BE(timestamp, 0); + components.push(v); + + v = Buffer.alloc(4); + v.writeUInt32BE(nonce, 0); + components.push(v); + + v = Buffer.alloc(2); + v.writeUInt16BE(emitterChain, 0); + components.push(v); + + components.push(emitterAddress); + + components.push(bigintToBuffer(sequence, 64)); + + v = Buffer.alloc(1); + v.writeUint8(consistencyLevel, 0); + components.push(v); + + components.push(payload); + + return Buffer.concat(components); + } + + + + +// (define-public (update-guardians-set (guardian-set-vaa (buff 2048)) (uncompressed-public-keys (list 19 (buff 64)))) +// (let ((vaa (if (var-get guardian-set-initialized) +// (try! (parse-and-verify-vaa guardian-set-vaa)) +// (try! (parse-vaa guardian-set-vaa)))) +// (cursor-guardians-data (try! (parse-and-verify-guardians-set (get payload vaa)))) +// (set-id (get new-index (get value cursor-guardians-data))) +// (eth-addresses (get guardians-eth-addresses (get value cursor-guardians-data))) +// (acc (unwrap-panic (as-max-len? (list { +// compressed-public-key: (unwrap-panic (as-max-len? 0x u33)), +// uncompressed-public-key: (unwrap-panic (as-max-len? 0x u64)) +// }) u20))) +// (consolidated-public-keys (fold +// check-and-consolidate-public-keys +// uncompressed-public-keys +// { success: true, cursor: u0, eth-addresses: eth-addresses, result: acc })) +// ) +// ;; Ensure that enough uncompressed-public-keys were provided +// (asserts! (is-eq (len uncompressed-public-keys) (len eth-addresses)) +// ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS) +// ;; Check guardians uncompressed-public-keys +// (asserts! (get success consolidated-public-keys) +// ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS) + +// (map-set guardian-sets { set-id: set-id } +// (unwrap-panic (as-max-len? +// (unwrap-panic (slice? (get result consolidated-public-keys) u1 (len (get result consolidated-public-keys)))) +// u19))) +// (var-set active-guardian-set-id set-id) +// (var-set guardian-set-initialized true) +// (ok { +// vaa: vaa, +// consolidated-public-keys: consolidated-public-keys, + + +// ((cursor-module (unwrap! (contract-call? .hk-cursor-v1 read-buff-32 { bytes: bytes, pos: u0 }) +// ERR_GSU_PARSING_MODULE)) +// (cursor-action (unwrap! (contract-call? .hk-cursor-v1 read-uint-8 (get next cursor-module)) +// ERR_GSU_PARSING_ACTION)) +// (cursor-chain (unwrap! (contract-call? .hk-cursor-v1 read-uint-16 (get next cursor-action)) +// ERR_GSU_PARSING_CHAIN)) +// (cursor-new-index (unwrap! (contract-call? .hk-cursor-v1 read-uint-32 (get next cursor-chain)) +// ERR_GSU_PARSING_INDEX)) +// (cursor-guardians-count (unwrap! (contract-call? .hk-cursor-v1 read-uint-8 (get next cursor-new-index)) +// ERR_GSU_PARSING_GUARDIAN_LEN)) +// (guardians-bytes (unwrap! (slice? bytes (get pos (get next cursor-guardians-count)) (+ (get pos (get next cursor-guardians-count)) (* (get value cursor-guardians-count) u20))) +// ERR_GSU_PARSING_GUARDIANS_BYTES)) +// (guardians-cues (get result (fold is-guardian-cue guardians-bytes { cursor: u0, result: (unwrap-panic (as-max-len? (list u0) u19)) }))) +// (eth-addresses-init (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u20))) u19))) +// (eth-addresses (get result (fold parse-guardian guardians-cues { bytes: guardians-bytes, result: eth-addresses-init })))) +// ;; Ensure that this message was emitted from authorized module +// (asserts! (is-eq (get value cursor-module) 0x00000000000000000000000000000000000000000000000000000000436f7265) +// ERR_GSU_CHECK_MODULE) +// ;; Ensure that this message is matching the adequate action +// (asserts! (is-eq (get value cursor-action) u2) +// ERR_GSU_CHECK_ACTION) +// ;; Ensure that this message is matching the adequate action +// (asserts! (is-eq (get value cursor-chain) u0) +// ERR_GSU_CHECK_CHAIN) + + export const buildGuardianRotationVaaPayload = (keyChain: Guardian[], module: number, action: number, chain: number, setId: number) => { + const components = []; + var v = Buffer.alloc(4); + v.writeUInt32BE(module, 0); + components.push(v); + + v = Buffer.alloc(1); + v.writeUint8(action, 0); + components.push(v); + + v = Buffer.alloc(2); + v.writeUInt16BE(chain, 0); + components.push(v); + + v = Buffer.alloc(4); + v.writeUInt32BE(setId, 0); + components.push(v); + + v = Buffer.alloc(1); + v.writeUint8(keyChain.length, 0); + components.push(v); + + for (let guardian of keyChain) { + components.push(guardian.ethereumAddress) + } + + return Buffer.concat(components); + } +} diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts new file mode 100644 index 0000000..908e253 --- /dev/null +++ b/unit-tests/wormhole/vaa.test.ts @@ -0,0 +1,97 @@ +import { Cl, ClarityType } from "@stacks/transactions"; +import { describe, expect } from "vitest"; +import { it, fc } from '@fast-check/vitest'; +import { wormhole_fc } from './helper'; + +const contract_name = "wormhole-core-v1"; + +describe("wormhole-core::v1 - parse-vaa", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + + const keyChain = wormhole_fc.generateGuardianSetKeychain(); + + const validGuardianRotationVaaPayload = wormhole_fc.buildGuardianRotationVaaPayload(keyChain, 0, 2, 0, 1); + + it.prop(wormhole_fc.vaa({ validSig: false, validData: false }))("fail verify invalid VAA", (version, guardianSetIndex, providedSignatures, generatedSignatures, timestamp, nonce, emitterChain, emitterAddress, sequence, consistencyLevel, payload) => { + let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; + let vaa = wormhole_fc.serializeVaa( + version as number, + guardianSetIndex as number, + consolidatedSignatures, + timestamp as number, + nonce as number, + emitterChain as number, + emitterAddress as Uint8Array, + sequence as bigint, + consistencyLevel as number, + payload as Uint8Array); + + let buff = Cl.buffer(vaa) + + const res = simnet.callReadOnlyFn( + contract_name, + `parse-and-verify-vaa`, + [buff], + sender + ); + + expect(res.result).toHaveClarityType(ClarityType.ResponseErr); + }) + + it.prop(wormhole_fc.vaa({ keyChain, validSig: true, validData: true, payload: validGuardianRotationVaaPayload }))("perform guardian rotation", (version, guardianSetIndex, providedSignatures, generatedSignatures, timestamp, nonce, emitterChain, emitterAddress, sequence, consistencyLevel, payload) => { + let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; + let vaa = wormhole_fc.serializeVaa( + version as number, + guardianSetIndex as number, + consolidatedSignatures, + timestamp as number, + nonce as number, + emitterChain as number, + emitterAddress as Uint8Array, + sequence as bigint, + consistencyLevel as number, + payload as Uint8Array); + + let buff = Cl.buffer(vaa) + + const res = simnet.callReadOnlyFn( + contract_name, + `parse-vaa`, + [buff], + sender + ); + + let guardiansPublicKeys = []; + for (let guardian of keyChain) { + guardiansPublicKeys.push(Cl.tuple({ + "guardian-id": Cl.uint(guardian.guardianId), + "recovered-compressed-public-key": Cl.ok(Cl.buffer(guardian.compressedPublicKey)) + })) + } + + let guardiansSignatures = []; + for (let i = 0; i < keyChain.length; i++) { + guardiansSignatures.push(Cl.tuple({ + "guardian-id": Cl.uint(i), + "signature": Cl.buffer(consolidatedSignatures[i].slice(1, 66)) + })) + } + + expect(res.result).toBeOk(Cl.tuple({ + "consistency-level": Cl.uint(consistencyLevel as number), + "emitter-chain": Cl.uint(emitterChain as number), + "version": Cl.uint(version as number), + "guardian-set-id": Cl.uint(guardianSetIndex as number), + "signatures-len": Cl.uint(consolidatedSignatures.length), + "sequence": Cl.uint(sequence as number), + "guardians-public-keys": Cl.list(guardiansPublicKeys), + "signatures": Cl.list(guardiansSignatures), + "payload": Cl.buffer(payload as Uint8Array) + })); + + + // console.log(payload) + + }) +}) From 6376d7e779f217689676154376a4383b049936d5 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Tue, 17 Oct 2023 18:45:13 -0400 Subject: [PATCH 04/18] test: progress on wormhole's coverage - 85% --- contracts/wormhole/wormhole-core-v1.clar | 98 ++--- contracts/wormhole/wormhole-traits-v1.clar | 3 +- unit-tests/wormhole/helper.ts | 482 +++++++++++---------- unit-tests/wormhole/vaa.test.ts | 137 +++--- 4 files changed, 341 insertions(+), 379 deletions(-) diff --git a/contracts/wormhole/wormhole-core-v1.clar b/contracts/wormhole/wormhole-core-v1.clar index cfe8b42..aadb5a8 100644 --- a/contracts/wormhole/wormhole-core-v1.clar +++ b/contracts/wormhole/wormhole-core-v1.clar @@ -110,8 +110,7 @@ ERR_VAA_PARSING_GUARDIAN_SET)) (cursor-signatures-len (unwrap! (contract-call? .hk-cursor-v1 read-uint-8 (get next cursor-guardian-set-id)) ERR_VAA_PARSING_SIGNATURES_LEN)) - (cursor-signatures (fold - batch-read-signatures + (cursor-signatures (fold batch-read-signatures (list u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0 u0) { next: (get next cursor-signatures-len), @@ -134,48 +133,53 @@ ERR_VAA_PARSING_CONSISTENCY_LEVEL)) (cursor-payload (unwrap! (contract-call? .hk-cursor-v1 read-buff-8192-max (get next cursor-consistency-level) none) ERR_VAA_PARSING_PAYLOAD)) - (public-keys-results (fold - batch-recover-public-keys + (public-keys-results (fold batch-recover-public-keys (get value cursor-signatures) { message-hash: vaa-body-hash, value: (list) }))) + (print { payload: (get value cursor-payload) }) (ok { - version: (get value cursor-version), - guardian-set-id: (get value cursor-guardian-set-id), - signatures-len: (get value cursor-signatures-len), - signatures: (get value cursor-signatures), - timestamp: (get value cursor-timestamp), - nonce: (get value cursor-nonce), - emitter-chain: (get value cursor-emitter-chain), - sequence: (get value cursor-sequence), - consistency-level: (get value cursor-consistency-level), - payload: (get value cursor-payload), - guardians-public-keys: (get value public-keys-results), - vaa-body-hash: vaa-body-hash + vaa: { + version: (get value cursor-version), + guardian-set-id: (get value cursor-guardian-set-id), + signatures-len: (get value cursor-signatures-len), + signatures: (get value cursor-signatures), + timestamp: (get value cursor-timestamp), + nonce: (get value cursor-nonce), + emitter-chain: (get value cursor-emitter-chain), + emitter-address: (get value cursor-emitter-address), + sequence: (get value cursor-sequence), + consistency-level: (get value cursor-consistency-level), + payload: (get value cursor-payload), + }, + recovered-public-keys: (get value public-keys-results), }))) ;; @desc Parse and check the validity of a Verified Action Approval (VAA) ;; @param vaa-bytes: (define-read-only (parse-and-verify-vaa (vaa-bytes (buff 8192))) - (let ((vaa (try! (parse-vaa vaa-bytes))) - (active-guardians (unwrap! (map-get? guardian-sets { set-id: (get guardian-set-id vaa) }) ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY)) - (signatures-from-active-guardians (fold batch-check-active-public-keys (get guardians-public-keys vaa) + (let ((message (try! (parse-vaa vaa-bytes))) + (active-guardians (unwrap! (map-get? guardian-sets { set-id: (get guardian-set-id (get vaa message)) }) ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY)) + (signatures-from-active-guardians (fold batch-check-active-public-keys (get recovered-public-keys message) { active-guardians: active-guardians, value: (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u33))) u20)) }))) ;; Ensure that the guardian-set-id is the active one - (asserts! (is-eq (get guardian-set-id vaa) (var-get active-guardian-set-id)) ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY) + (asserts! (is-eq (get guardian-set-id (get vaa message)) (var-get active-guardian-set-id)) + ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY) ;; Ensure that version is supported (v1 only) - (asserts! (is-eq (get version vaa) u1) ERR_VAA_CHECKS_VERSION_UNSUPPORTED) + (asserts! (is-eq (get version (get vaa message)) u1) + ERR_VAA_CHECKS_VERSION_UNSUPPORTED) ;; Ensure that the count of valid signatures is >= 13 - (asserts! (>= (len (get value signatures-from-active-guardians)) u13) ERR_VAA_CHECKS_THRESHOLD_SIGNATURE) + (asserts! (>= (len (get value signatures-from-active-guardians)) u13) + ERR_VAA_CHECKS_THRESHOLD_SIGNATURE) ;; TODO: Ensure that the guardian set is not expired ;; ERR_VAA_CHECKS_GUARDIAN_SET_EXPIRED ;; Good to go! - (ok vaa))) + (ok (get vaa message)))) ;; @desc Update the active set of guardians ;; @param guardian-set-vaa: VAA embedding the Guardian Set Update informations @@ -185,7 +189,7 @@ (define-public (update-guardians-set (guardian-set-vaa (buff 2048)) (uncompressed-public-keys (list 19 (buff 64)))) (let ((vaa (if (var-get guardian-set-initialized) (try! (parse-and-verify-vaa guardian-set-vaa)) - (try! (parse-vaa guardian-set-vaa)))) + (get vaa (try! (parse-vaa guardian-set-vaa))))) (cursor-guardians-data (try! (parse-and-verify-guardians-set (get payload vaa)))) (set-id (get new-index (get value cursor-guardians-data))) (eth-addresses (get guardians-eth-addresses (get value cursor-guardians-data))) @@ -193,8 +197,7 @@ compressed-public-key: (unwrap-panic (as-max-len? 0x u33)), uncompressed-public-key: (unwrap-panic (as-max-len? 0x u64)) }) u20))) - (consolidated-public-keys (fold - check-and-consolidate-public-keys + (consolidated-public-keys (fold check-and-consolidate-public-keys uncompressed-public-keys { success: true, cursor: u0, eth-addresses: eth-addresses, result: acc })) ) @@ -245,9 +248,12 @@ ;; @desc Foldable function admitting an uncompressed 64 byts public key as an input, producing a record { uncompressed-public-key, compressed-public-key } (define-private (batch-recover-public-keys (entry { guardian-id: uint, signature: (buff 65) }) - (acc { message-hash: (buff 32), value: (list 19 { recovered-compressed-public-key: (response (buff 33) uint), guardian-id: uint }) })) + (acc { message-hash: (buff 32), value: (list 19 { recovered-compressed-public-key: (buff 33), guardian-id: uint }) })) (let ((recovered-compressed-public-key (secp256k1-recover? (get message-hash acc) (get signature entry))) - (updated-public-keys (append (get value acc) { recovered-compressed-public-key: recovered-compressed-public-key, guardian-id: (get guardian-id entry) } ))) + (updated-public-keys + (match recovered-compressed-public-key + public-key (append (get value acc) { recovered-compressed-public-key: public-key, guardian-id: (get guardian-id entry) } ) + error (get value acc)))) { message-hash: (get message-hash acc), value: (unwrap-panic (as-max-len? updated-public-keys u19)) @@ -255,19 +261,17 @@ ;; @desc Foldable function evaluating signatures from a list of { guardian-id: u8, signature: (buff 65) }, returning a list of recovered public-keys (define-private (batch-check-active-public-keys - (entry { recovered-compressed-public-key: (response (buff 33) uint), guardian-id: uint }) + (entry { recovered-compressed-public-key: (buff 33), guardian-id: uint }) (acc { active-guardians: (list 19 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64) }), value: (list 20 (buff 33))})) (let ((compressed-public-key (get compressed-public-key (unwrap-panic (element-at? (get active-guardians acc) (get guardian-id entry)))))) - (match (get recovered-compressed-public-key entry) - recovered-public-key (if (and - (is-eq recovered-public-key compressed-public-key) - (is-none (index-of? (get value acc) recovered-public-key))) + (if (and + (is-eq (get recovered-compressed-public-key entry) compressed-public-key) + (is-none (index-of? (get value acc) (get recovered-compressed-public-key entry)))) { - value: (unwrap-panic (as-max-len? (append (get value acc) recovered-public-key) u20)), + value: (unwrap-panic (as-max-len? (append (get value acc) (get recovered-compressed-public-key entry)) u20)), active-guardians: (get active-guardians acc) } - acc) - err acc))) + acc))) ;; @desc Foldable function parsing a sequence of bytes into a list of { guardian-id: u8, signature: (buff 65) } (define-private (batch-read-signatures @@ -286,11 +290,9 @@ ;; @desc Convert an uncompressed public key (64 bytes) into a compressed public key (33 bytes) (define-private (compress-public-key (uncompressed-public-key (buff 64))) - (if (is-eq (len uncompressed-public-key) u64) (let ((x-coordinate (unwrap-panic (slice? uncompressed-public-key u0 u32))) (y-coordinate-parity (buff-to-uint-be (unwrap-panic (element-at? uncompressed-public-key u63))))) - (unwrap-panic (as-max-len? (concat (if (is-eq (mod y-coordinate-parity u2) u0) 0x02 0x03) x-coordinate) u33))) - (unwrap-panic (as-max-len? 0x u33)))) + (unwrap-panic (as-max-len? (concat (if (is-eq (mod y-coordinate-parity u2) u0) 0x02 0x03) x-coordinate) u33)))) (define-private (is-eth-address-matching-public-key (uncompressed-public-key (buff 64)) (eth-address (buff 20))) (is-eq (unwrap-panic (slice? (keccak256 uncompressed-public-key) u12 u32)) eth-address)) @@ -304,18 +306,6 @@ result: (unwrap-panic (as-max-len? (append (get result acc) (get value cursor-address-bytes)) u20)) })) -;; @desc Update the active set of guardians -;; @param expiration-time: -;; @param guardians: -(define-private (insert-entry-in-guardians (expiration-time uint) (new-set-id uint) (new-guardians (list 19 { uncompressed-public-key: (buff 64), compressed-public-key: (buff 33) }))) - (let ((set-id (var-get active-guardian-set-id))) - ;; TODO: check authorization - ;; Update set - ;; (fold add-guardian-to-guardian-set guardians { id: u0, set-id: new-set-id }) - ;; Update set-id - ;; (var-set current-guardian-set-id new-set-id) - { set-id: new-set-id, guardians: new-guardians })) - ;; @desc Parse and verify payload's VAA (define-private (parse-and-verify-guardians-set (bytes (buff 8192))) (let @@ -329,11 +319,11 @@ ERR_GSU_PARSING_INDEX)) (cursor-guardians-count (unwrap! (contract-call? .hk-cursor-v1 read-uint-8 (get next cursor-new-index)) ERR_GSU_PARSING_GUARDIAN_LEN)) - (guardians-bytes (unwrap! (slice? bytes (get pos (get next cursor-guardians-count)) (+ (get pos (get next cursor-guardians-count)) (* (get value cursor-guardians-count) u20))) + (guardians-bytes (unwrap! (contract-call? .hk-cursor-v1 read-buff-8192-max (get next cursor-guardians-count) (some (* (get value cursor-guardians-count) u20))) ERR_GSU_PARSING_GUARDIANS_BYTES)) - (guardians-cues (get result (fold is-guardian-cue guardians-bytes { cursor: u0, result: (unwrap-panic (as-max-len? (list u0) u19)) }))) + (guardians-cues (get result (fold is-guardian-cue (get value guardians-bytes) { cursor: u0, result: (unwrap-panic (as-max-len? (list u0) u19)) }))) (eth-addresses-init (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u20))) u19))) - (eth-addresses (get result (fold parse-guardian guardians-cues { bytes: guardians-bytes, result: eth-addresses-init })))) + (eth-addresses (get result (fold parse-guardian guardians-cues { bytes: (get value guardians-bytes), result: eth-addresses-init })))) ;; Ensure that this message was emitted from authorized module (asserts! (is-eq (get value cursor-module) 0x00000000000000000000000000000000000000000000000000000000436f7265) ERR_GSU_CHECK_MODULE) diff --git a/contracts/wormhole/wormhole-traits-v1.clar b/contracts/wormhole/wormhole-traits-v1.clar index ac30477..4e66b94 100644 --- a/contracts/wormhole/wormhole-traits-v1.clar +++ b/contracts/wormhole/wormhole-traits-v1.clar @@ -9,11 +9,10 @@ timestamp: uint, nonce: uint, emitter-chain: uint, + emitter-address: (buff 32), sequence: uint, consistency-level: uint, payload: (buff 8192), - guardians-public-keys: (list 19 { recovered-compressed-public-key: (response (buff 33) uint), guardian-id: uint }), - vaa-body-hash: (buff 32), } uint)) ) ) \ No newline at end of file diff --git a/unit-tests/wormhole/helper.ts b/unit-tests/wormhole/helper.ts index 4bdf3c1..c4ff125 100644 --- a/unit-tests/wormhole/helper.ts +++ b/unit-tests/wormhole/helper.ts @@ -1,18 +1,19 @@ import { fc } from '@fast-check/vitest'; +import { Cl, ClarityValue } from "@stacks/transactions"; import { bigintToBuffer } from '../utils/helper'; import * as secp from '@noble/secp256k1'; import { keccak_256 - } from '@noble/hashes/sha3'; +} from '@noble/hashes/sha3'; import { webcrypto } from 'node:crypto'; // @ts-ignore if (!globalThis.crypto) globalThis.crypto = webcrypto; - + import { hmac } from '@noble/hashes/hmac'; import { sha256 } from '@noble/hashes/sha256'; secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) -export namespace wormhole_fc { +export namespace wormhole { export interface Guardian { guardianId: number, @@ -22,21 +23,11 @@ export namespace wormhole_fc { ethereumAddress: Uint8Array, } - export interface VaaHeader { - validVersion?: boolean, - validGuardianSetId?: boolean, - signatures?: Uint8Array[], - } - - export interface VaaHeaderBuildOptions { - opts?: VaaHeader - } - - export const generateGuardianSetKeychain = () => { + export const generateGuardianSetKeychain = (count = 19) => { let keychain = []; - for (let i = 0; i < 19; i++) { + for (let i = 0; i < count; i++) { let secretKey = secp.utils.randomPrivateKey(); - let uncompressedPublicKey = secp.getPublicKey(secretKey, false); + let uncompressedPublicKey = secp.getPublicKey(secretKey, false).slice(1, 65); let ethereumAddress = keccak_256(uncompressedPublicKey).slice(12, 32); keychain.push({ guardianId: i, @@ -49,45 +40,10 @@ export namespace wormhole_fc { return keychain } - // Helper for generating a VAA Header; - // Wire format reminder: - // =========================== - // VAA Header - // byte version (VAA Version) - // u32 guardian_set_index (Indicates which guardian set is signing) - // u8 len_signatures (Number of signatures stored) - // [][66]byte signatures (Collection of ecdsa signatures) - export const vaaHeader = (header?: VaaHeaderBuildOptions) => { - let args = []; - if (header && header.opts && header.opts.validVersion) { - args.push(fc.constant(2)) - } else { - args.push(fc.nat({ max: 255 })) - } - - if (header && header.opts && header.opts.validGuardianSetId) { - args.push(fc.constant(0)) - } else { - args.push(fc.nat(4294967295)); - } - - - if (header && header.opts && header.opts.signatures) { - // Signatures - args.push(fc.constant(header.opts.signatures)) - // Random sequences that looks like signatures - let max = 19 - header.opts.signatures.length - let min = 0 - args.push(fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: min, maxLength: max })) - } else { - // Signatures - args.push(fc.constant([])) - // Random sequences that looks like signatures - let max = 19 - let min = 0 - args.push(fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: min, maxLength: max })) - } - return args + export interface VaaHeader { + version: number, + guardianSetId: number, + signatures: Uint8Array[], } export interface VaaBody { @@ -95,241 +51,293 @@ export namespace wormhole_fc { emitterChain: number, nonce: number, emitterAddress: Uint8Array, - sequence: number, + sequence: bigint, consistencyLevel: number, payload: Uint8Array, } + export interface VaaHeaderBuildOptions { + version?: number, + guardianSetId?: number, + signatures?: Uint8Array[], + } + export interface VaaBodyBuildOptions { timestamp?: number, emitterChain?: number, nonce?: number, emitterAddress?: Uint8Array, - sequence?: number, + sequence?: bigint, consistencyLevel?: number, payload?: Uint8Array, } - // Helper for generating a VAA Body; - // Wire format reminder: - // =========================== - // VAA Body - // u32 timestamp (Timestamp of the block where the source transaction occurred) - // u32 nonce (A grouping number) - // u16 emitter_chain (Wormhole ChainId of emitter contract) - // [32]byte emitter_address (Emitter contract address, in Wormhole format) - // u64 sequence (Strictly increasing sequence, tied to emitter address & chain) - // u8 consistency_level (What finality level was reached before emitting this message) - // []byte payload (VAA message content) - export const vaaBody = (vaa?: VaaBodyBuildOptions) => { - let args = []; - // Timestamp - if (vaa && vaa.opts && vaa.opts.timestamp) { - args.push(fc.constant(vaa.opts.timestamp)); - } else { - // TODO: what is an invalid timestamp? - args.push(fc.nat(4294967295)); - } + export namespace fc_ext { + // Helper for generating a VAA Body; + // Wire format reminder: + // =========================== + // VAA Body + // u32 timestamp (Timestamp of the block where the source transaction occurred) + // u32 nonce (A grouping number) + // u16 emitter_chain (Wormhole ChainId of emitter contract) + // [32]byte emitter_address (Emitter contract address, in Wormhole format) + // u64 sequence (Strictly increasing sequence, tied to emitter address & chain) + // u8 consistency_level (What finality level was reached before emitting this message) + // []byte payload (VAA message content) + export const vaaBody = (opts?: VaaBodyBuildOptions) => { + // Timestamp + let timestamp = fc.nat(4294967295); + if (opts && opts.timestamp) { + timestamp = fc.constant(opts.timestamp); + } - // Nonce - if (vaa && vaa.opts && vaa.opts.nonce) { - args.push(fc.constant(vaa.opts.nonce)); - } else { - // TODO: what is an invalid timestamp? - args.push(fc.nat(4294967295)); - } + // Nonce + let nonce = fc.nat(4294967295); + if (opts && opts.nonce) { + nonce = fc.constant(opts.nonce); + } - // Emitter chain - if (vaa && vaa.opts && vaa.opts.emitterChain) { - args.push(fc.constant(vaa.opts.emitterChain)) - } else { - args.push(fc.nat(65535)); - } + // Emitter chain + let emitterChain = fc.nat(65535); + if (opts && opts.emitterChain) { + emitterChain = fc.constant(opts.emitterChain); + } - // Emitter address - if (vaa && vaa.opts && vaa.opts.emitterAddress) { - args.push(fc.constant(vaa.opts.emitterAddress)) - } else { - args.push(fc.uint8Array({ minLength: 32, maxLength: 32 })); + // Emitter address + let emitterAddress = fc.uint8Array({ minLength: 32, maxLength: 32 }); + if (opts && opts.emitterAddress) { + emitterAddress = fc.constant(opts.emitterAddress); + } + + // Sequence + let sequence = fc.bigUintN(64); + if (opts && opts.sequence) { + sequence = fc.constant(opts.sequence) + } + + // Consistency level + let consistencyLevel = fc.nat(255); + if (opts && opts.consistencyLevel) { + consistencyLevel = fc.constant(opts.consistencyLevel) + } + + // Payload + let payload = fc.uint8Array({ minLength: 20, maxLength: 2048 }); + if (opts && opts.payload) { + payload = fc.constant(opts.payload) + } + + return fc.tuple(timestamp, nonce, emitterChain, emitterAddress, sequence, consistencyLevel, payload); } - // Sequence - if (vaa && vaa.opts && vaa.opts.sequence) { - args.push(fc.constant(vaa.opts.sequence)) - } else { - args.push(fc.bigUintN(64)); + // Helper for generating a VAA Header; + // Wire format reminder: + // =========================== + // VAA Header + // byte version (VAA Version) + // u32 guardian_set_index (Indicates which guardian set is signing) + // u8 len_signatures (Number of signatures stored) + // [][66]byte signatures (Collection of ecdsa signatures) + export const vaaHeader = (opts?: VaaHeaderBuildOptions, numberOfSignatures = 19) => { + // Version + let version = fc.nat(255); + if (opts && opts.version) { + version = fc.constant(opts.version); + } + + // Guardian set id + let guardianSetId = fc.nat(255); + if (opts && opts.guardianSetId) { + guardianSetId = fc.constant(opts.guardianSetId) + } + + // Specified signatures + let specifiedSignatures = fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: 0, maxLength: 0 }) + let specifiedSignaturesLen = 0; + if (opts && opts.signatures) { + specifiedSignatures = fc.constant(opts.signatures) + specifiedSignaturesLen = opts.signatures.length + } + + // Generated signatures + let generatedSignatures = fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: 0, maxLength: (numberOfSignatures - specifiedSignaturesLen) }) + + return fc.tuple(version, guardianSetId, specifiedSignatures, generatedSignatures); } + } - // Consistency level - if (vaa && vaa.opts && vaa.opts.consistencyLevel) { - args.push(fc.constant(vaa.opts.consistencyLevel)) - } else { - args.push(fc.nat(255)); + export const buildValidVaaHeaderSpecs = (keychain: Guardian[], body: VaaBody, opts?: VaaHeaderBuildOptions): VaaHeaderBuildOptions => { + + let signatures = []; + const messageHash = keccak_256(keccak_256(serializeVaaBody(body))); + + for (let guardian of keychain) { + const signature = secp.sign(messageHash, guardian.secretKey) + + const id = Buffer.alloc(1); + id.writeUint8(guardian.guardianId, 0); + + // v.writeUint8(signature.addRecoveryBit, 1); + if (signature.recovery) { + const rec = Buffer.alloc(1); + rec.writeUint8(signature.recovery, 0); + signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); + } else { + const rec = Buffer.alloc(1); + rec.writeUint8(0, 0); + signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); + } } - - // Payload - if (vaa && vaa.opts && vaa.opts.payload) { - args.push(fc.constant(vaa.opts.payload)) - } else { - args.push(fc.uint8Array({ minLength: 20, maxLength: 2048 })); + return { + version: opts?.version, + guardianSetId: opts?.guardianSetId, + signatures: signatures, } + } - return args; + export const buildValidVaaHeader = (keychain: Guardian[], body: VaaBody, opts: VaaHeaderBuildOptions): VaaHeader => { + let specs = buildValidVaaHeaderSpecs(keychain, body, opts); + return { + version: specs.version!, + guardianSetId: specs.guardianSetId!, + signatures: specs.signatures!, + } } - export const vaa = (opts: { keyChain?: Guardian[], body: VaaBody, validSig: boolean }) => { - let signatures = []; - if (opts.validSig && opts.keyChain && opts.validSig === true) { - for (let guardian of opts.keyChain) { - const messageHash = keccak_256(keccak_256(opts.body..payload)); - const signature = secp.sign(messageHash, guardian.secretKey) - - const id = Buffer.alloc(1); - id.writeUint8(guardian.guardianId, 0); - - // v.writeUint8(signature.addRecoveryBit, 1); - if (signature.recovery) { - const rec = Buffer.alloc(1); - rec.writeUint8(signature.recovery, 0); - signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); - } else { - console.log(`-> ${guardian.guardianId} / ${guardian.compressedPublicKey}`); - const rec = Buffer.alloc(1); - rec.writeUint8(0, 0); - signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); - } + export const expectedDecodedVaa = (header: VaaHeader, body: VaaBody, keychain: Guardian[]): ClarityValue => { + let guardiansPublicKeys = []; + let guardiansSignatures = []; + for (let i = 0; i < header.signatures.length; i++) { + let guardianId = header.signatures[i][0]; + if (keychain.length > i) { + let guardian = keychain[i]; + guardiansPublicKeys.push(Cl.tuple({ + "guardian-id": Cl.uint(guardianId), + "recovered-compressed-public-key": Cl.buffer(guardian.compressedPublicKey) + })) } + guardiansSignatures.push(Cl.tuple({ + "guardian-id": Cl.uint(header.signatures[i].slice(0, 1)), + "signature": Cl.buffer(header.signatures[i].slice(1, 66)) + })) + } + + return Cl.tuple({ + "vaa": Cl.tuple({ + "consistency-level": Cl.uint(body.consistencyLevel), + "version": Cl.uint(header.version), + "guardian-set-id": Cl.uint(header.guardianSetId), + "signatures-len": Cl.uint(header.signatures.length), + "signatures": Cl.list(guardiansSignatures), + "emitter-chain": Cl.uint(body.emitterChain), + "emitter-address": Cl.buffer(body.emitterAddress), + "sequence": Cl.uint(body.sequence), + "timestamp": Cl.uint(body.timestamp), + "nonce": Cl.uint(body.nonce), + "payload": Cl.buffer(body.payload) + }), + "recovered-public-keys": Cl.list(guardiansPublicKeys), + }) + } + + export interface VaaBodySpec { + values: VaaBody, + specs: (fc.Arbitrary | fc.Arbitrary | fc.Arbitrary | fc.Arbitrary)[] + } + + export const buildValidVaaBodySpecs = (opts?: { payload?: Uint8Array }): VaaBody => { + const date = Math.floor(Date.now() / 1000); + const timestamp = date >>> 0; + const payload = (opts && opts.payload && opts.payload) || new Uint8Array(32) + let values = { + timestamp: timestamp, + nonce: 0, + emitterChain: 0, + emitterAddress: new Uint8Array(32), + sequence: 0n, + consistencyLevel: 0, + payload: payload, + }; + return values + } + + export const assembleVaaBody = (timestamp: number | bigint | Uint8Array, nonce: number | bigint | Uint8Array, emitterChain: number | bigint | Uint8Array, emitterAddress: number | bigint | Uint8Array, sequence: number | bigint | Uint8Array, consistencyLevel: number | bigint | Uint8Array, payload: number | bigint | Uint8Array): VaaBody => { + return { + timestamp: timestamp as number, + nonce: nonce as number, + emitterChain: emitterChain as number, + emitterAddress: emitterAddress as Uint8Array, + sequence: sequence as bigint, + consistencyLevel: consistencyLevel as number, + payload: payload as Uint8Array } + } + + export const assembleVaaHeader = (version: number | bigint | Uint8Array, guardianSetId: number | bigint | Uint8Array, signatures: number | bigint | Uint8Array[]): VaaHeader => { + return { + version: version as number, + guardianSetId: guardianSetId as number, + signatures: signatures as Uint8Array[], + } + } + + export const serializeVaa = (vaaHeader: VaaHeader, vaaBody: VaaBody) => { - return [ - ...vaaHeader({ opts: { - validVersion: true, - validGuardianSetId: true, - signatures: signatures, - }}), - ...vaaBody({ - opts: { - timestamp: 0, - emitterChain: 0, - nonce: 0, - emitterAddress: , - sequence: 0, - consistencyLevel: 0, - payload: opts.payload - } - })] + return Buffer.concat([serializeVaaHeader(vaaHeader), serializeVaaBody(vaaBody)]); } - export const serializeVaa = (version: number, guardianSetIndex: number, signatures: Uint8Array[], timestamp: number, nonce: number, emitterChain: number, emitterAddress: Uint8Array, sequence: bigint, consistencyLevel: number, payload: Uint8Array) => { + export const serializeVaaHeader = (vaaHeader: VaaHeader) => { const components = []; - var v = Buffer.alloc(1); - v.writeUint8(version, 0); + var v = Buffer.alloc(1); + v.writeUint8(vaaHeader.version, 0); components.push(v); - v = Buffer.alloc(4); - v.writeUInt32BE(guardianSetIndex, 0); + v = Buffer.alloc(4); + v.writeUInt32BE(vaaHeader.guardianSetId, 0); components.push(v); v = Buffer.alloc(1); - v.writeUint8(signatures.length, 0); + v.writeUint8(vaaHeader.signatures.length, 0); components.push(v); - components.push(Buffer.concat(signatures)); - - v = Buffer.alloc(4); - v.writeUInt32BE(timestamp, 0); + components.push(Buffer.concat(vaaHeader.signatures)); + return Buffer.concat(components); + } + + export const serializeVaaBody = (vaaBody: VaaBody) => { + const components = []; + let v = Buffer.alloc(4); + v.writeUInt32BE(vaaBody.timestamp, 0); components.push(v); - v = Buffer.alloc(4); - v.writeUInt32BE(nonce, 0); + v = Buffer.alloc(4); + v.writeUInt32BE(vaaBody.nonce, 0); components.push(v); - v = Buffer.alloc(2); - v.writeUInt16BE(emitterChain, 0); + v = Buffer.alloc(2); + v.writeUInt16BE(vaaBody.emitterChain, 0); components.push(v); - components.push(emitterAddress); + components.push(vaaBody.emitterAddress); - components.push(bigintToBuffer(sequence, 64)); + components.push(bigintToBuffer(vaaBody.sequence, 8)); v = Buffer.alloc(1); - v.writeUint8(consistencyLevel, 0); + v.writeUint8(vaaBody.consistencyLevel, 0); components.push(v); - components.push(payload); + components.push(vaaBody.payload); - return Buffer.concat(components); + return Buffer.concat(components); } + export const validGuardianRotationModule = Buffer.from('00000000000000000000000000000000000000000000000000000000436f7265', 'hex'); - - -// (define-public (update-guardians-set (guardian-set-vaa (buff 2048)) (uncompressed-public-keys (list 19 (buff 64)))) -// (let ((vaa (if (var-get guardian-set-initialized) -// (try! (parse-and-verify-vaa guardian-set-vaa)) -// (try! (parse-vaa guardian-set-vaa)))) -// (cursor-guardians-data (try! (parse-and-verify-guardians-set (get payload vaa)))) -// (set-id (get new-index (get value cursor-guardians-data))) -// (eth-addresses (get guardians-eth-addresses (get value cursor-guardians-data))) -// (acc (unwrap-panic (as-max-len? (list { -// compressed-public-key: (unwrap-panic (as-max-len? 0x u33)), -// uncompressed-public-key: (unwrap-panic (as-max-len? 0x u64)) -// }) u20))) -// (consolidated-public-keys (fold -// check-and-consolidate-public-keys -// uncompressed-public-keys -// { success: true, cursor: u0, eth-addresses: eth-addresses, result: acc })) -// ) -// ;; Ensure that enough uncompressed-public-keys were provided -// (asserts! (is-eq (len uncompressed-public-keys) (len eth-addresses)) -// ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS) -// ;; Check guardians uncompressed-public-keys -// (asserts! (get success consolidated-public-keys) -// ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS) - -// (map-set guardian-sets { set-id: set-id } -// (unwrap-panic (as-max-len? -// (unwrap-panic (slice? (get result consolidated-public-keys) u1 (len (get result consolidated-public-keys)))) -// u19))) -// (var-set active-guardian-set-id set-id) -// (var-set guardian-set-initialized true) -// (ok { -// vaa: vaa, -// consolidated-public-keys: consolidated-public-keys, - - -// ((cursor-module (unwrap! (contract-call? .hk-cursor-v1 read-buff-32 { bytes: bytes, pos: u0 }) -// ERR_GSU_PARSING_MODULE)) -// (cursor-action (unwrap! (contract-call? .hk-cursor-v1 read-uint-8 (get next cursor-module)) -// ERR_GSU_PARSING_ACTION)) -// (cursor-chain (unwrap! (contract-call? .hk-cursor-v1 read-uint-16 (get next cursor-action)) -// ERR_GSU_PARSING_CHAIN)) -// (cursor-new-index (unwrap! (contract-call? .hk-cursor-v1 read-uint-32 (get next cursor-chain)) -// ERR_GSU_PARSING_INDEX)) -// (cursor-guardians-count (unwrap! (contract-call? .hk-cursor-v1 read-uint-8 (get next cursor-new-index)) -// ERR_GSU_PARSING_GUARDIAN_LEN)) -// (guardians-bytes (unwrap! (slice? bytes (get pos (get next cursor-guardians-count)) (+ (get pos (get next cursor-guardians-count)) (* (get value cursor-guardians-count) u20))) -// ERR_GSU_PARSING_GUARDIANS_BYTES)) -// (guardians-cues (get result (fold is-guardian-cue guardians-bytes { cursor: u0, result: (unwrap-panic (as-max-len? (list u0) u19)) }))) -// (eth-addresses-init (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u20))) u19))) -// (eth-addresses (get result (fold parse-guardian guardians-cues { bytes: guardians-bytes, result: eth-addresses-init })))) -// ;; Ensure that this message was emitted from authorized module -// (asserts! (is-eq (get value cursor-module) 0x00000000000000000000000000000000000000000000000000000000436f7265) -// ERR_GSU_CHECK_MODULE) -// ;; Ensure that this message is matching the adequate action -// (asserts! (is-eq (get value cursor-action) u2) -// ERR_GSU_CHECK_ACTION) -// ;; Ensure that this message is matching the adequate action -// (asserts! (is-eq (get value cursor-chain) u0) -// ERR_GSU_CHECK_CHAIN) - - export const buildGuardianRotationVaaPayload = (keyChain: Guardian[], module: number, action: number, chain: number, setId: number) => { + export const buildGuardianRotationVaaPayload = (keyChain: Guardian[], action: number, chain: number, setId: number, module = validGuardianRotationModule) => { const components = []; - var v = Buffer.alloc(4); - v.writeUInt32BE(module, 0); - components.push(v); + components.push(module); - v = Buffer.alloc(1); + let v = Buffer.alloc(1); v.writeUint8(action, 0); components.push(v); @@ -337,7 +345,7 @@ export namespace wormhole_fc { v.writeUInt16BE(chain, 0); components.push(v); - v = Buffer.alloc(4); + v = Buffer.alloc(4); v.writeUInt32BE(setId, 0); components.push(v); @@ -349,6 +357,6 @@ export namespace wormhole_fc { components.push(guardian.ethereumAddress) } - return Buffer.concat(components); + return Buffer.concat(components); } } diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts index 908e253..403ca30 100644 --- a/unit-tests/wormhole/vaa.test.ts +++ b/unit-tests/wormhole/vaa.test.ts @@ -1,97 +1,62 @@ -import { Cl, ClarityType } from "@stacks/transactions"; -import { describe, expect } from "vitest"; +import { Cl } from "@stacks/transactions"; +import { expect, describe } from "vitest"; import { it, fc } from '@fast-check/vitest'; -import { wormhole_fc } from './helper'; +import { wormhole } from './helper'; const contract_name = "wormhole-core-v1"; +const verbosity = 0; -describe("wormhole-core::v1 - parse-vaa", () => { +describe("wormhole-core-v1::parse-vaa", () => { 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 headerSpecs = wormhole.buildValidVaaHeaderSpecs(keychain, body); + + fc.assert(fc.property(wormhole.fc_ext.vaaHeader(headerSpecs, 19), ([version, guardianSetIndex, providedSignatures, generatedSignatures]) => { + let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; + let header = wormhole.assembleVaaHeader(version, guardianSetIndex, consolidatedSignatures) + let vaa = wormhole.serializeVaa(header, body); + let expectedResult = wormhole.expectedDecodedVaa(header, body, keychain); + + const res = simnet.callReadOnlyFn( + contract_name, + `parse-vaa`, + [Cl.buffer(vaa)], + sender + ); + res.result + expect(res.result).toBeOk(expectedResult); + }), + { verbose: verbosity }) + }); +}) - const keyChain = wormhole_fc.generateGuardianSetKeychain(); - - const validGuardianRotationVaaPayload = wormhole_fc.buildGuardianRotationVaaPayload(keyChain, 0, 2, 0, 1); - - it.prop(wormhole_fc.vaa({ validSig: false, validData: false }))("fail verify invalid VAA", (version, guardianSetIndex, providedSignatures, generatedSignatures, timestamp, nonce, emitterChain, emitterAddress, sequence, consistencyLevel, payload) => { - let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; - let vaa = wormhole_fc.serializeVaa( - version as number, - guardianSetIndex as number, - consolidatedSignatures, - timestamp as number, - nonce as number, - emitterChain as number, - emitterAddress as Uint8Array, - sequence as bigint, - consistencyLevel as number, - payload as Uint8Array); - - let buff = Cl.buffer(vaa) - - const res = simnet.callReadOnlyFn( - contract_name, - `parse-and-verify-vaa`, - [buff], - sender - ); - - expect(res.result).toHaveClarityType(ClarityType.ResponseErr); - }) - - it.prop(wormhole_fc.vaa({ keyChain, validSig: true, validData: true, payload: validGuardianRotationVaaPayload }))("perform guardian rotation", (version, guardianSetIndex, providedSignatures, generatedSignatures, timestamp, nonce, emitterChain, emitterAddress, sequence, consistencyLevel, payload) => { - let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; - let vaa = wormhole_fc.serializeVaa( - version as number, - guardianSetIndex as number, - consolidatedSignatures, - timestamp as number, - nonce as number, - emitterChain as number, - emitterAddress as Uint8Array, - sequence as bigint, - consistencyLevel as number, - payload as Uint8Array); - - let buff = Cl.buffer(vaa) - - const res = simnet.callReadOnlyFn( +describe("wormhole-core-v1::update-guardians-set", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const keychain = wormhole.generateGuardianSetKeychain(19); + + it("should fail if the vaa is valid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 1, wormhole.validGuardianRotationModule); + console.log(guardianRotationPayload.length); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + console.log(Cl.prettyPrint(Cl.list(uncompressedPublicKey))); + const res = simnet.callPublicFn( contract_name, - `parse-vaa`, - [buff], + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], sender ); - - let guardiansPublicKeys = []; - for (let guardian of keyChain) { - guardiansPublicKeys.push(Cl.tuple({ - "guardian-id": Cl.uint(guardian.guardianId), - "recovered-compressed-public-key": Cl.ok(Cl.buffer(guardian.compressedPublicKey)) - })) - } - - let guardiansSignatures = []; - for (let i = 0; i < keyChain.length; i++) { - guardiansSignatures.push(Cl.tuple({ - "guardian-id": Cl.uint(i), - "signature": Cl.buffer(consolidatedSignatures[i].slice(1, 66)) - })) - } - - expect(res.result).toBeOk(Cl.tuple({ - "consistency-level": Cl.uint(consistencyLevel as number), - "emitter-chain": Cl.uint(emitterChain as number), - "version": Cl.uint(version as number), - "guardian-set-id": Cl.uint(guardianSetIndex as number), - "signatures-len": Cl.uint(consolidatedSignatures.length), - "sequence": Cl.uint(sequence as number), - "guardians-public-keys": Cl.list(guardiansPublicKeys), - "signatures": Cl.list(guardiansSignatures), - "payload": Cl.buffer(payload as Uint8Array) - })); - - - // console.log(payload) - - }) + expect(res.result).toBeOk(Cl.uint(1)); + }); }) From 31204d400ded20c69d6426c455ade01b89b56ba7 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Wed, 18 Oct 2023 09:37:14 -0400 Subject: [PATCH 05/18] test: wormhole-core-v1::update-guardians-set failures --- contracts/wormhole/wormhole-core-v1.clar | 6 +- unit-tests/wormhole/helper.ts | 31 +++---- unit-tests/wormhole/vaa.test.ts | 106 +++++++++++++++++++++-- 3 files changed, 117 insertions(+), 26 deletions(-) diff --git a/contracts/wormhole/wormhole-core-v1.clar b/contracts/wormhole/wormhole-core-v1.clar index aadb5a8..8056d73 100644 --- a/contracts/wormhole/wormhole-core-v1.clar +++ b/contracts/wormhole/wormhole-core-v1.clar @@ -214,9 +214,13 @@ u19))) (var-set active-guardian-set-id set-id) (var-set guardian-set-initialized true) + (print { object: "guardian-set", action: "updated", data: { guardians-eth-addresses: eth-addresses, guardians-public-keys: uncompressed-public-keys }}) (ok { vaa: vaa, - consolidated-public-keys: consolidated-public-keys, + result: { + guardians-eth-addresses: eth-addresses, + guardians-public-keys: uncompressed-public-keys + } }))) (define-public (get-active-guardian-set) diff --git a/unit-tests/wormhole/helper.ts b/unit-tests/wormhole/helper.ts index c4ff125..a302e97 100644 --- a/unit-tests/wormhole/helper.ts +++ b/unit-tests/wormhole/helper.ts @@ -204,7 +204,7 @@ export namespace wormhole { } } - export const expectedDecodedVaa = (header: VaaHeader, body: VaaBody, keychain: Guardian[]): ClarityValue => { + export const expectedDecodedVaa = (header: VaaHeader, body: VaaBody, keychain: Guardian[]): [ClarityValue, any[]] => { let guardiansPublicKeys = []; let guardiansSignatures = []; for (let i = 0; i < header.signatures.length; i++) { @@ -221,23 +221,20 @@ export namespace wormhole { "signature": Cl.buffer(header.signatures[i].slice(1, 66)) })) } - - return Cl.tuple({ - "vaa": Cl.tuple({ - "consistency-level": Cl.uint(body.consistencyLevel), - "version": Cl.uint(header.version), - "guardian-set-id": Cl.uint(header.guardianSetId), - "signatures-len": Cl.uint(header.signatures.length), - "signatures": Cl.list(guardiansSignatures), - "emitter-chain": Cl.uint(body.emitterChain), - "emitter-address": Cl.buffer(body.emitterAddress), - "sequence": Cl.uint(body.sequence), - "timestamp": Cl.uint(body.timestamp), - "nonce": Cl.uint(body.nonce), - "payload": Cl.buffer(body.payload) - }), - "recovered-public-keys": Cl.list(guardiansPublicKeys), + let value = Cl.tuple({ + "consistency-level": Cl.uint(body.consistencyLevel), + "version": Cl.uint(header.version), + "guardian-set-id": Cl.uint(header.guardianSetId), + "signatures-len": Cl.uint(header.signatures.length), + "signatures": Cl.list(guardiansSignatures), + "emitter-chain": Cl.uint(body.emitterChain), + "emitter-address": Cl.buffer(body.emitterAddress), + "sequence": Cl.uint(body.sequence), + "timestamp": Cl.uint(body.timestamp), + "nonce": Cl.uint(body.nonce), + "payload": Cl.buffer(body.payload) }) + return [value, guardiansPublicKeys] } export interface VaaBodySpec { diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts index 403ca30..7d6c48f 100644 --- a/unit-tests/wormhole/vaa.test.ts +++ b/unit-tests/wormhole/vaa.test.ts @@ -6,7 +6,7 @@ import { wormhole } from './helper'; const contract_name = "wormhole-core-v1"; const verbosity = 0; -describe("wormhole-core-v1::parse-vaa", () => { +describe("wormhole-core-v1::parse-vaa success", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const keychain = wormhole.generateGuardianSetKeychain(19); @@ -19,7 +19,7 @@ describe("wormhole-core-v1::parse-vaa", () => { let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; let header = wormhole.assembleVaaHeader(version, guardianSetIndex, consolidatedSignatures) let vaa = wormhole.serializeVaa(header, body); - let expectedResult = wormhole.expectedDecodedVaa(header, body, keychain); + let [decodedVaa, guardiansPublicKeys] = wormhole.expectedDecodedVaa(header, body, keychain); const res = simnet.callReadOnlyFn( contract_name, @@ -28,21 +28,105 @@ describe("wormhole-core-v1::parse-vaa", () => { sender ); res.result - expect(res.result).toBeOk(expectedResult); + expect(res.result).toBeOk(Cl.tuple({ + "vaa": decodedVaa, + "recovered-public-keys": Cl.list(guardiansPublicKeys) + })); }), { verbose: verbosity }) }); }) -describe("wormhole-core-v1::update-guardians-set", () => { +describe("wormhole-core-v1::update-guardians-set failures", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const keychain = wormhole.generateGuardianSetKeychain(19); - it("should fail if the vaa is valid", () => { + it("should fail if the chain is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 1, 1, wormhole.validGuardianRotationModule); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callPublicFn( + contract_name, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1303)); + }); + + it("should fail if the action is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 0, 0, 1, wormhole.validGuardianRotationModule); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callPublicFn( + contract_name, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1302)); + }); + + it("should fail if the set id is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 0, wormhole.validGuardianRotationModule); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callPublicFn( + contract_name, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1304)); + }); + + it("should fail if the module is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 1, Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callPublicFn( + contract_name, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1301)); + }); +}) + +describe("wormhole-core-v1::update-guardians-set success", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const keychain = wormhole.generateGuardianSetKeychain(19); + + it("should succeed if the vaa is valid", () => { // Before performing this test, we need to setup the guardian set let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 1, wormhole.validGuardianRotationModule); - console.log(guardianRotationPayload.length); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); let vaa = wormhole.serializeVaa(header, body); @@ -50,13 +134,19 @@ describe("wormhole-core-v1::update-guardians-set", () => { for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } - console.log(Cl.prettyPrint(Cl.list(uncompressedPublicKey))); + let [expectedDecodedVaa, _] = wormhole.expectedDecodedVaa(header, body, keychain); const res = simnet.callPublicFn( contract_name, `update-guardians-set`, [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], sender ); - expect(res.result).toBeOk(Cl.uint(1)); + expect(res.result).toBeOk(Cl.tuple({ + 'result': Cl.tuple({ + 'guardians-eth-addresses': Cl.list(keychain.map((g) => Cl.buffer(g.ethereumAddress))), + 'guardians-public-keys': Cl.list(keychain.map((g) => Cl.buffer(g.uncompressedPublicKey))), + }), + 'vaa': expectedDecodedVaa + })); }); }) From 65ae13e6e26e55f0ae7f4afc3f415e140723b88d Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Wed, 18 Oct 2023 17:38:49 -0400 Subject: [PATCH 06/18] test: progress on wormhole's coverage - 97% --- contracts/wormhole/wormhole-core-v1.clar | 98 ++++--- unit-tests/wormhole/helper.ts | 23 +- unit-tests/wormhole/vaa.test.ts | 314 ++++++++++++++++++++--- 3 files changed, 348 insertions(+), 87 deletions(-) diff --git a/contracts/wormhole/wormhole-core-v1.clar b/contracts/wormhole/wormhole-core-v1.clar index 8056d73..6d05cf0 100644 --- a/contracts/wormhole/wormhole-core-v1.clar +++ b/contracts/wormhole/wormhole-core-v1.clar @@ -42,8 +42,6 @@ (define-constant ERR_VAA_CHECKS_THRESHOLD_SIGNATURE (err u1102)) ;; Multiple signatures were issued by the same guardian (define-constant ERR_VAA_CHECKS_REDUNDANT_SIGNATURE (err u1103)) -;; Guardian set specified is expired -(define-constant ERR_VAA_CHECKS_GUARDIAN_SET_EXPIRED (err u1104)) ;; Guardian signature not comprised in guardian set specified (define-constant ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY (err u1105)) ;; Guardian Set Update initiated by an unauthorized module @@ -68,6 +66,8 @@ (define-constant ERR_GSU_CHECK_CHAIN (err u1303)) ;; Guardian Set Update new index invalid (define-constant ERR_GSU_CHECK_INDEX (err u1304)) +;; Quorum of addresses required +(define-constant QUORUM u13) ;;;; Data vars @@ -160,26 +160,24 @@ ;; @desc Parse and check the validity of a Verified Action Approval (VAA) ;; @param vaa-bytes: (define-read-only (parse-and-verify-vaa (vaa-bytes (buff 8192))) - (let ((message (try! (parse-vaa vaa-bytes))) - (active-guardians (unwrap! (map-get? guardian-sets { set-id: (get guardian-set-id (get vaa message)) }) ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY)) - (signatures-from-active-guardians (fold batch-check-active-public-keys (get recovered-public-keys message) - { - active-guardians: active-guardians, - value: (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u33))) u20)) - }))) - ;; Ensure that the guardian-set-id is the active one - (asserts! (is-eq (get guardian-set-id (get vaa message)) (var-get active-guardian-set-id)) - ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY) - ;; Ensure that version is supported (v1 only) - (asserts! (is-eq (get version (get vaa message)) u1) - ERR_VAA_CHECKS_VERSION_UNSUPPORTED) - ;; Ensure that the count of valid signatures is >= 13 - (asserts! (>= (len (get value signatures-from-active-guardians)) u13) - ERR_VAA_CHECKS_THRESHOLD_SIGNATURE) - ;; TODO: Ensure that the guardian set is not expired - ;; ERR_VAA_CHECKS_GUARDIAN_SET_EXPIRED - ;; Good to go! - (ok (get vaa message)))) + (let ((message (try! (parse-vaa vaa-bytes)))) + ;; Ensure that the guardian-set-id is the active one + (asserts! (is-eq (get guardian-set-id (get vaa message)) (var-get active-guardian-set-id)) + ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY) + (let ((active-guardians (unwrap! (map-get? guardian-sets { set-id: (get guardian-set-id (get vaa message)) }) ERR_VAA_CHECKS_GUARDIAN_SET_CONSISTENCY)) + (signatures-from-active-guardians (fold batch-check-active-public-keys (get recovered-public-keys message) + { + active-guardians: active-guardians, + result: (list) + }))) + ;; Ensure that version is supported (v1 only) + (asserts! (is-eq (get version (get vaa message)) u1) + ERR_VAA_CHECKS_VERSION_UNSUPPORTED) + ;; Ensure that the count of valid signatures is >= 13 + (asserts! (>= (len (get result signatures-from-active-guardians)) QUORUM) + ERR_VAA_CHECKS_THRESHOLD_SIGNATURE) + ;; Good to go! + (ok (get vaa message))))) ;; @desc Update the active set of guardians ;; @param guardian-set-vaa: VAA embedding the Guardian Set Update informations @@ -193,28 +191,22 @@ (cursor-guardians-data (try! (parse-and-verify-guardians-set (get payload vaa)))) (set-id (get new-index (get value cursor-guardians-data))) (eth-addresses (get guardians-eth-addresses (get value cursor-guardians-data))) - (acc (unwrap-panic (as-max-len? (list { - compressed-public-key: (unwrap-panic (as-max-len? 0x u33)), - uncompressed-public-key: (unwrap-panic (as-max-len? 0x u64)) - }) u20))) (consolidated-public-keys (fold check-and-consolidate-public-keys uncompressed-public-keys - { success: true, cursor: u0, eth-addresses: eth-addresses, result: acc })) + { cursor: u0, eth-addresses: eth-addresses, result: (list) })) ) ;; Ensure that enough uncompressed-public-keys were provided (asserts! (is-eq (len uncompressed-public-keys) (len eth-addresses)) ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS) - ;; Check guardians uncompressed-public-keys - (asserts! (get success consolidated-public-keys) - ERR_GSU_UNCOMPRESSED_PUBLIC_KEYS) - - (map-set guardian-sets { set-id: set-id } - (unwrap-panic (as-max-len? - (unwrap-panic (slice? (get result consolidated-public-keys) u1 (len (get result consolidated-public-keys)))) - u19))) + ;; Update storage + (map-set guardian-sets { set-id: set-id } (get result consolidated-public-keys)) (var-set active-guardian-set-id set-id) (var-set guardian-set-initialized true) - (print { object: "guardian-set", action: "updated", data: { guardians-eth-addresses: eth-addresses, guardians-public-keys: uncompressed-public-keys }}) + ;; Emit Event + (print { + object: "guardian-set", + action: "updated", + data: { guardians-eth-addresses: eth-addresses, guardians-public-keys: uncompressed-public-keys }}) (ok { vaa: vaa, result: { @@ -236,7 +228,11 @@ ;; @desc Foldable function admitting an uncompressed 64 byts public key as an input, producing a record { uncompressed-public-key, compressed-public-key } (define-private (check-and-consolidate-public-keys (uncompressed-public-key (buff 64)) - (acc { success: bool, cursor: uint, eth-addresses: (list 19 (buff 20)), result: (list 20 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64)})})) + (acc { + cursor: uint, + eth-addresses: (list 19 (buff 20)), + result: (list 19 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64)}) + })) (let ((eth-address (unwrap-panic (element-at? (get eth-addresses acc) (get cursor acc)))) (compressed-public-key (compress-public-key uncompressed-public-key)) (entry (if (is-eth-address-matching-public-key uncompressed-public-key eth-address) @@ -245,8 +241,7 @@ { cursor: (+ u1 (get cursor acc)), eth-addresses: (get eth-addresses acc), - success: true, - result: (unwrap-panic (as-max-len? (append (get result acc) entry) u20)), + result: (unwrap-panic (as-max-len? (append (get result acc) entry) u19)), })) ;; @desc Foldable function admitting an uncompressed 64 byts public key as an input, producing a record { uncompressed-public-key, compressed-public-key } @@ -254,8 +249,7 @@ (entry { guardian-id: uint, signature: (buff 65) }) (acc { message-hash: (buff 32), value: (list 19 { recovered-compressed-public-key: (buff 33), guardian-id: uint }) })) (let ((recovered-compressed-public-key (secp256k1-recover? (get message-hash acc) (get signature entry))) - (updated-public-keys - (match recovered-compressed-public-key + (updated-public-keys (match recovered-compressed-public-key public-key (append (get value acc) { recovered-compressed-public-key: public-key, guardian-id: (get guardian-id entry) } ) error (get value acc)))) { @@ -266,13 +260,16 @@ ;; @desc Foldable function evaluating signatures from a list of { guardian-id: u8, signature: (buff 65) }, returning a list of recovered public-keys (define-private (batch-check-active-public-keys (entry { recovered-compressed-public-key: (buff 33), guardian-id: uint }) - (acc { active-guardians: (list 19 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64) }), value: (list 20 (buff 33))})) + (acc { + active-guardians: (list 19 { compressed-public-key: (buff 33), uncompressed-public-key: (buff 64) }), + result: (list 19 (buff 33)) + })) (let ((compressed-public-key (get compressed-public-key (unwrap-panic (element-at? (get active-guardians acc) (get guardian-id entry)))))) (if (and (is-eq (get recovered-compressed-public-key entry) compressed-public-key) - (is-none (index-of? (get value acc) (get recovered-compressed-public-key entry)))) + (is-none (index-of? (get result acc) (get recovered-compressed-public-key entry)))) { - value: (unwrap-panic (as-max-len? (append (get value acc) (get recovered-compressed-public-key entry)) u20)), + result: (unwrap-panic (as-max-len? (append (get result acc) (get recovered-compressed-public-key entry)) u19)), active-guardians: (get active-guardians acc) } acc))) @@ -301,13 +298,13 @@ (define-private (is-eth-address-matching-public-key (uncompressed-public-key (buff 64)) (eth-address (buff 20))) (is-eq (unwrap-panic (slice? (keccak256 uncompressed-public-key) u12 u32)) eth-address)) -(define-private (parse-guardian (cue-position uint) (acc { bytes: (buff 8192), result: (list 20 (buff 20))})) +(define-private (parse-guardian (cue-position uint) (acc { bytes: (buff 8192), result: (list 19 (buff 20))})) (let ( (cursor-address-bytes (unwrap-panic (contract-call? .hk-cursor-v1 read-buff-20 { bytes: (get bytes acc), pos: cue-position }))) ) { bytes: (get bytes acc), - result: (unwrap-panic (as-max-len? (append (get result acc) (get value cursor-address-bytes)) u20)) + result: (unwrap-panic (as-max-len? (append (get result acc) (get value cursor-address-bytes)) u19)) })) ;; @desc Parse and verify payload's VAA @@ -325,9 +322,8 @@ ERR_GSU_PARSING_GUARDIAN_LEN)) (guardians-bytes (unwrap! (contract-call? .hk-cursor-v1 read-buff-8192-max (get next cursor-guardians-count) (some (* (get value cursor-guardians-count) u20))) ERR_GSU_PARSING_GUARDIANS_BYTES)) - (guardians-cues (get result (fold is-guardian-cue (get value guardians-bytes) { cursor: u0, result: (unwrap-panic (as-max-len? (list u0) u19)) }))) - (eth-addresses-init (unwrap-panic (as-max-len? (list (unwrap-panic (as-max-len? 0x u20))) u19))) - (eth-addresses (get result (fold parse-guardian guardians-cues { bytes: (get value guardians-bytes), result: eth-addresses-init })))) + (guardians-cues (get result (fold is-guardian-cue (get value guardians-bytes) { cursor: u0, result: (list) }))) + (eth-addresses (get result (fold parse-guardian guardians-cues { bytes: (get value guardians-bytes), result: (list) })))) ;; Ensure that this message was emitted from authorized module (asserts! (is-eq (get value cursor-module) 0x00000000000000000000000000000000000000000000000000000000436f7265) ERR_GSU_CHECK_MODULE) @@ -343,7 +339,7 @@ ;; Good to go! (ok { value: { - guardians-eth-addresses: (unwrap-panic (as-max-len? (unwrap-panic (slice? eth-addresses u1 (+ u1 (get value cursor-guardians-count)))) u19)), + guardians-eth-addresses: eth-addresses, module: (get value cursor-module), action: (get value cursor-action), chain: (get value cursor-chain), @@ -357,7 +353,7 @@ }))) (define-private (is-guardian-cue (byte (buff 1)) (acc { cursor: uint, result: (list 19 uint) })) - (if (and (is-eq u0 (mod (get cursor acc) u20)) (> (get cursor acc) u0) ) + (if (is-eq u0 (mod (get cursor acc) u20)) { cursor: (+ u1 (get cursor acc)), result: (unwrap-panic (as-max-len? (append (get result acc) (get cursor acc)) u19)), diff --git a/unit-tests/wormhole/helper.ts b/unit-tests/wormhole/helper.ts index a302e97..f70526b 100644 --- a/unit-tests/wormhole/helper.ts +++ b/unit-tests/wormhole/helper.ts @@ -1,4 +1,5 @@ import { fc } from '@fast-check/vitest'; +import { tx } from "@hirosystems/clarinet-sdk"; import { Cl, ClarityValue } from "@stacks/transactions"; import { bigintToBuffer } from '../utils/helper'; import * as secp from '@noble/secp256k1'; @@ -23,7 +24,7 @@ export namespace wormhole { ethereumAddress: Uint8Array, } - export const generateGuardianSetKeychain = (count = 19) => { + export const generateGuardianSetKeychain = (count = 19): Guardian[] => { let keychain = []; for (let i = 0; i < count; i++) { let secretKey = secp.utils.randomPrivateKey(); @@ -356,4 +357,24 @@ export namespace wormhole { return Buffer.concat(components); } + +export function applyGuardianSetUpdate(keychain: wormhole.Guardian[], guardianSetId: number, txSenderAddress: string, contract_name: string) { + let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, guardianSetId, wormhole.validGuardianRotationModule); + let vaaBody = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { version: 1, guardianSetId: guardianSetId - 1 }); + let vaa = wormhole.serializeVaa(vaaHeader, vaaBody); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + let result = simnet.mineBlock([ + tx.callPublicFn( + contract_name, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + txSenderAddress + ), + ])[0].result; + return [result, vaaHeader, vaaBody] +} } diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts index 7d6c48f..07b4342 100644 --- a/unit-tests/wormhole/vaa.test.ts +++ b/unit-tests/wormhole/vaa.test.ts @@ -1,9 +1,10 @@ import { Cl } from "@stacks/transactions"; -import { expect, describe } from "vitest"; +import { expect, describe, it as vit, beforeEach } from "vitest"; import { it, fc } from '@fast-check/vitest'; import { wormhole } from './helper'; +import { tx } from "@hirosystems/clarinet-sdk"; -const contract_name = "wormhole-core-v1"; +const contractName = "wormhole-core-v1"; const verbosity = 0; describe("wormhole-core-v1::parse-vaa success", () => { @@ -12,7 +13,7 @@ describe("wormhole-core-v1::parse-vaa success", () => { const keychain = wormhole.generateGuardianSetKeychain(19); it("should succeed if the vaa is valid", () => { - let body = wormhole.buildValidVaaBodySpecs(); + let body = wormhole.buildValidVaaBodySpecs(); let headerSpecs = wormhole.buildValidVaaHeaderSpecs(keychain, body); fc.assert(fc.property(wormhole.fc_ext.vaaHeader(headerSpecs, 19), ([version, guardianSetIndex, providedSignatures, generatedSignatures]) => { @@ -22,7 +23,7 @@ describe("wormhole-core-v1::parse-vaa success", () => { let [decodedVaa, guardiansPublicKeys] = wormhole.expectedDecodedVaa(header, body, keychain); const res = simnet.callReadOnlyFn( - contract_name, + contractName, `parse-vaa`, [Cl.buffer(vaa)], sender @@ -32,8 +33,8 @@ describe("wormhole-core-v1::parse-vaa success", () => { "vaa": decodedVaa, "recovered-public-keys": Cl.list(guardiansPublicKeys) })); - }), - { verbose: verbosity }) + }), + { verbose: verbosity }) }); }) @@ -45,15 +46,15 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the chain is invalid", () => { // Before performing this test, we need to setup the guardian set let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 1, 1, wormhole.validGuardianRotationModule); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); let vaa = wormhole.serializeVaa(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } const res = simnet.callPublicFn( - contract_name, + contractName, `update-guardians-set`, [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], sender @@ -64,15 +65,15 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the action is invalid", () => { // Before performing this test, we need to setup the guardian set let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 0, 0, 1, wormhole.validGuardianRotationModule); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); let vaa = wormhole.serializeVaa(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } const res = simnet.callPublicFn( - contract_name, + contractName, `update-guardians-set`, [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], sender @@ -83,15 +84,15 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the set id is invalid", () => { // Before performing this test, we need to setup the guardian set let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 0, wormhole.validGuardianRotationModule); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); let vaa = wormhole.serializeVaa(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } const res = simnet.callPublicFn( - contract_name, + contractName, `update-guardians-set`, [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], sender @@ -102,15 +103,15 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the module is invalid", () => { // Before performing this test, we need to setup the guardian set let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 1, Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); let vaa = wormhole.serializeVaa(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } const res = simnet.callPublicFn( - contract_name, + contractName, `update-guardians-set`, [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], sender @@ -122,31 +123,274 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { describe("wormhole-core-v1::update-guardians-set success", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; - const keychain = wormhole.generateGuardianSetKeychain(19); + const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); + const guardianSet2Keys = wormhole.generateGuardianSetKeychain(19); + let guardianSet1 = Cl.tuple({}); + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + let [result, vaaHeader, vaaBody] = wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName) + let [expectedDecodedVaa, _] = wormhole.expectedDecodedVaa( + vaaHeader as wormhole.VaaHeader, + vaaBody as wormhole.VaaBody, + guardianSet1Keys); + + guardianSet1 = Cl.tuple({ + "set-id": Cl.uint(1), + "guardians": Cl.list(guardianSet1Keys.map((g) => Cl.tuple({ + "compressed-public-key": Cl.buffer(g.compressedPublicKey), + "uncompressed-public-key": Cl.buffer(g.uncompressedPublicKey) + }))) + }); + + expect(result).toBeOk(Cl.tuple({ + 'result': Cl.tuple({ + 'guardians-eth-addresses': Cl.list(guardianSet1Keys.map((g) => Cl.buffer(g.ethereumAddress))), + 'guardians-public-keys': Cl.list(guardianSet1Keys.map((g) => Cl.buffer(g.uncompressedPublicKey))), + }), + 'vaa': expectedDecodedVaa + })); + }) + + it("should return the set 1 as active", () => { + let res = simnet.callPublicFn( + contractName, + `get-active-guardian-set`, + [], + sender + ).result; + expect(res).toBeOk(guardianSet1); + }); + + it("should successfully handle subsequent rotation", () => { + const guardianSet2 = Cl.tuple({ + "set-id": Cl.uint(2), + "guardians": Cl.list(guardianSet2Keys.map((g) => Cl.tuple({ + "compressed-public-key": Cl.buffer(g.compressedPublicKey), + "uncompressed-public-key": Cl.buffer(g.uncompressedPublicKey) + }))) + }); + // Before performing this test, we need to setup the guardian set + const guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(guardianSet2Keys, 2, 0, 2, wormhole.validGuardianRotationModule); + const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); + const vaa = wormhole.serializeVaa(header, body); + const uncompressedPublicKey = []; + for (let guardian of guardianSet2Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const [expectedDecodedVaa, _] = wormhole.expectedDecodedVaa(header, body, guardianSet1Keys); + let res = simnet.mineBlock([ + tx.callPublicFn( + contractName, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender + ) + ])[0].result + expect(res).toBeOk(Cl.tuple({ + 'result': Cl.tuple({ + 'guardians-eth-addresses': Cl.list(guardianSet2Keys.map((g) => Cl.buffer(g.ethereumAddress))), + 'guardians-public-keys': Cl.list(guardianSet2Keys.map((g) => Cl.buffer(g.uncompressedPublicKey))), + }), + 'vaa': expectedDecodedVaa + })); + + res = simnet.callPublicFn( + contractName, + `get-active-guardian-set`, + [], + sender + ).result; + expect(res).toBeOk(guardianSet2); + }); + + it("should reject subsequent update if there is a eth address / uncompressed public keys mismatch", () => { + let guardianSet2KeysSubset = guardianSet2Keys.splice(0, 12); + // Before performing this test, we need to setup the guardian set + const guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(guardianSet2KeysSubset, 2, 0, 2, wormhole.validGuardianRotationModule); + const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); + const vaa = wormhole.serializeVaa(header, body); + const uncompressedPublicKey = []; + for (let guardian of guardianSet2Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + let res = simnet.mineBlock([ + tx.callPublicFn( + contractName, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender + ) + ])[0].result + expect(res).toBeErr(Cl.uint(1207)); + }); +}) + +describe("wormhole-core-v1::parse-and-verify-vaa success", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); + // const guardianSet2Keys = wormhole.generateGuardianSetKeychain(19); + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName) + }) it("should succeed if the vaa is valid", () => { + let body = wormhole.buildValidVaaBodySpecs(); + let headerSpecs = wormhole.buildValidVaaHeaderSpecs(guardianSet1Keys, body); + + fc.assert(fc.property(wormhole.fc_ext.vaaHeader(headerSpecs, 19), ([version, guardianSetIndex, providedSignatures, generatedSignatures]) => { + let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; + let header = wormhole.assembleVaaHeader(version, guardianSetIndex, consolidatedSignatures) + let vaa = wormhole.serializeVaa(header, body); + let [decodedVaa, guardiansPublicKeys] = wormhole.expectedDecodedVaa(header, body, guardianSet1Keys); + + const res = simnet.callReadOnlyFn( + contractName, + `parse-vaa`, + [Cl.buffer(vaa)], + sender + ); + res.result + expect(res.result).toBeOk(Cl.tuple({ + "vaa": decodedVaa, + "recovered-public-keys": Cl.list(guardiansPublicKeys) + })); + }), + { verbose: verbosity }) + }); + + it("should succeed if quorum is just met", () => { + // 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 header = wormhole.buildValidVaaHeader(guardianSet1KeysSubset, body, { version: 1, guardianSetId: 1 }); + let vaa = wormhole.serializeVaa(header, body); + let [decodedVaa, _] = wormhole.expectedDecodedVaa(header, body, guardianSet1KeysSubset); + + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender + ); + expect(res.result).toBeOk(decodedVaa) + }); +}) + +describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName) + }) + + it("should fail if the version is invalid", () => { // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 1, wormhole.validGuardianRotationModule); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - let header = wormhole.buildValidVaaHeader(keychain, body, { version: 2, guardianSetId: 0, signatures: [] }); + let body = wormhole.buildValidVaaBodySpecs(); + let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 2, guardianSetId: 1 }); let vaa = wormhole.serializeVaa(header, body); let uncompressedPublicKey = []; - for (let guardian of keychain) { + for (let guardian of guardianSet1Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } - let [expectedDecodedVaa, _] = wormhole.expectedDecodedVaa(header, body, keychain); - const res = simnet.callPublicFn( - contract_name, - `update-guardians-set`, - [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], sender ); - expect(res.result).toBeOk(Cl.tuple({ - 'result': Cl.tuple({ - 'guardians-eth-addresses': Cl.list(keychain.map((g) => Cl.buffer(g.ethereumAddress))), - 'guardians-public-keys': Cl.list(keychain.map((g) => Cl.buffer(g.uncompressedPublicKey))), - }), - 'vaa': expectedDecodedVaa - })); + expect(res.result).toBeErr(Cl.uint(1101)); + }); + + it("should fail if the guardianSetId is invalid", () => { + // Before performing this test, we need to setup the guardian set + let body = wormhole.buildValidVaaBodySpecs(); + let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 2 }); + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1105)); + }); + + 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 header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); + // Override signatures + for (let i = 0; i < header.signatures.length; i++) { + header.signatures[i] = header.signatures[0]; + } + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1102)); + }); + + 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 header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); + // Override signatures + header.signatures = header.signatures.slice(0, 12); + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1102)); + }); + + it("should fail if quorum is not met", () => { + // Before performing this test, we need to setup the guardian set + let body = wormhole.buildValidVaaBodySpecs(); + let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); + // Override signatures + let cutoff = 12; + for (let i = cutoff; i < header.signatures.length; i++) { + header.signatures[i] = header.signatures[0]; + } + let vaa = wormhole.serializeVaa(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender + ); + expect(res.result).toBeErr(Cl.uint(1102)); }); }) From 0d2fba0a90cc3fe0866a52e72c86bd63143dc7cf Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Wed, 18 Oct 2023 18:10:40 -0400 Subject: [PATCH 07/18] test: progress on cursor's coverage - 100% --- unit-tests/utils/cursor.test.ts | 94 ++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 14 deletions(-) diff --git a/unit-tests/utils/cursor.test.ts b/unit-tests/utils/cursor.test.ts index 56075a5..c3957fd 100644 --- a/unit-tests/utils/cursor.test.ts +++ b/unit-tests/utils/cursor.test.ts @@ -8,7 +8,7 @@ const cursor_contract_name = "hk-cursor-v1"; describe("hiro-kit::cursor - buffers", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; - + const buff_n = (n: number) => { return fc.uint8Array({ minLength: 0, maxLength: n }) } @@ -21,6 +21,72 @@ describe("hiro-kit::cursor - buffers", () => { return fc.uint8Array({ minLength: 0, maxLength: n }) } + it.prop([buff_n(8192)])("new, no offset", (numbers) => { + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `new`, + [Cl.buffer(numbers), Cl.none()], + sender + ); + expect(res.result).toBeTuple({ + "value": Cl.none(), + "next": Cl.tuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(0) + }) + }) + }) + + it.prop([buff_n(8192), fc.nat()])("new, offset", (numbers, offset) => { + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `new`, + [Cl.buffer(numbers), Cl.some(Cl.uint(offset))], + sender + ); + expect(res.result).toBeTuple({ + "value": Cl.none(), + "next": Cl.tuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(offset) + }) + }) + }) + + it.prop([buff_n(8192), fc.nat()])("advance", (numbers, offset) => { + let cursor = Cl.tuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(0) + }) + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `advance`, + [cursor, Cl.uint(offset)], + sender + ); + expect(res.result).toBeTuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(offset) + }) + }) + + it.prop([buff_n(8192), fc.nat(2048), fc.nat(2048)])("slice", (bytes, pos, size) => { + let cursor = Cl.tuple({ + bytes: Cl.buffer(bytes), + pos: Cl.uint(pos) + }) + + if (pos + size < bytes.length) { + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `slice`, + [cursor, Cl.some(Cl.uint(size))], + sender + ); + expect(res.result).toBeBuff(bytes.slice(pos, pos+size)) + } + }) + it.prop([buff_n(8192)])("read-buff-1", (numbers) => { var data = new Uint8Array(); for (let n of numbers) { @@ -110,8 +176,8 @@ describe("hiro-kit::cursor - buffers", () => { if (toRead > data.length) { expect(res.result).toBeErr(Cl.uint(1)) return; - } - + } + let expectedCursor = Cl.tuple({ bytes: Cl.buffer(data), pos: Cl.uint(toRead) @@ -177,8 +243,8 @@ describe("hiro-kit::cursor - buffers", () => { if (toRead > data.length) { expect(res.result).toBeErr(Cl.uint(1)) return; - } - + } + let expectedCursor = Cl.tuple({ bytes: Cl.buffer(data), pos: Cl.uint(toRead) @@ -220,7 +286,7 @@ describe("hiro-kit::cursor - buffers", () => { describe("hiro-kit::cursor - unsigned integers", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; - + let arrayOfUint8 = fc.uint8Array({ minLength: 0, maxLength: 8192 }); it.prop([arrayOfUint8])("read-uint-8", (numbers) => { var data = new Uint8Array(); @@ -319,7 +385,7 @@ describe("hiro-kit::cursor - unsigned integers", () => { it.prop([arrayOfUint64])("read-uint-64", (numbers) => { var data = Buffer.from([]); for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); } let nextInput = Cl.tuple({ bytes: Cl.buffer(data), @@ -350,7 +416,7 @@ describe("hiro-kit::cursor - unsigned integers", () => { it.prop([arrayOfUint128])("read-uint-128", (numbers) => { var data = Buffer.from([]); for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); } let nextInput = Cl.tuple({ bytes: Cl.buffer(data), @@ -381,12 +447,12 @@ describe("hiro-kit::cursor - unsigned integers", () => { describe("hiro-kit::cursor - signed integers", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; - + let arrayOfInt8 = fc.array(fc.bigIntN(8), { minLength: 0, maxLength: 1023 }); it.prop([arrayOfInt8])("read-int-8", (numbers) => { var data = Buffer.from([]); for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 1))]); + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 1))]); } let nextInput = Cl.tuple({ bytes: Cl.buffer(data), @@ -417,7 +483,7 @@ describe("hiro-kit::cursor - signed integers", () => { it.prop([arrayOfInt16])("read-int-16", (numbers) => { var data = Buffer.from([]); for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 2))]); + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 2))]); } let nextInput = Cl.tuple({ bytes: Cl.buffer(data), @@ -448,7 +514,7 @@ describe("hiro-kit::cursor - signed integers", () => { it.prop([arrayOfInt32])("read-int-32", (numbers) => { var data = Buffer.from([]); for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 4))]); + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 4))]); } let nextInput = Cl.tuple({ bytes: Cl.buffer(data), @@ -479,7 +545,7 @@ describe("hiro-kit::cursor - signed integers", () => { it.prop([arrayOfInt64])("read-int-64", (numbers) => { var data = Buffer.from([]); for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); } let nextInput = Cl.tuple({ bytes: Cl.buffer(data), @@ -510,7 +576,7 @@ describe("hiro-kit::cursor - signed integers", () => { it.prop([arrayOfInt128])("read-int-128", (numbers) => { var data = Buffer.from([]); for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); } let nextInput = Cl.tuple({ bytes: Cl.buffer(data), From 8f728b61047ac9a3cb59ee38deaaa8680ace3546 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Thu, 19 Oct 2023 10:13:06 -0400 Subject: [PATCH 08/18] refactor: handling fixtures --- unit-tests/pyth-pnau.test.ts | 76 ----------------- unit-tests/{constants.ts => pyth/fixtures.ts} | 83 +------------------ unit-tests/pyth/oracle.test.ts | 48 +++++++++++ unit-tests/wormhole.test.ts | 49 ----------- unit-tests/wormhole/fixtures.ts | 74 +++++++++++++++++ unit-tests/wormhole/helper.ts | 72 +++++++++++----- unit-tests/wormhole/vaa.test.ts | 24 +++++- 7 files changed, 200 insertions(+), 226 deletions(-) delete mode 100644 unit-tests/pyth-pnau.test.ts rename unit-tests/{constants.ts => pyth/fixtures.ts} (76%) create mode 100644 unit-tests/pyth/oracle.test.ts delete mode 100644 unit-tests/wormhole.test.ts create mode 100644 unit-tests/wormhole/fixtures.ts diff --git a/unit-tests/pyth-pnau.test.ts b/unit-tests/pyth-pnau.test.ts deleted file mode 100644 index 2d030f8..0000000 --- a/unit-tests/pyth-pnau.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { Cl, ClarityType, ResponseOkCV } from "@stacks/transactions"; -import { describe, expect, it } from "vitest"; -import { tx } from "@hirosystems/clarinet-sdk"; - -import { mainnet_valid_guardians_set_upgrades, mainnet_valid_au } from "./constants"; - -const pyth_oracle_v1_contract_name = "pyth-oracle-v1"; -const pyth_decoder_pnau_v1_contract_name = "pyth-pnau-decoder-v1"; -const pyth_storage_v1_contract_name = "pyth-store-v1"; -const wormhole_core_v1_contract_name = "wormhole-core-v1"; - -describe("Pyth (PNAU) testsuite", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - - it("ensure that legitimate price attestations are validated", () => { - const vaaRotation1 = Cl.bufferFromHex(mainnet_valid_guardians_set_upgrades[0].vaa); - let publicKeysRotation1 = mainnet_valid_guardians_set_upgrades[0].keys.map(Cl.bufferFromHex); - - const vaaRotation2 = Cl.bufferFromHex(mainnet_valid_guardians_set_upgrades[1].vaa); - let publicKeysRotation2 = mainnet_valid_guardians_set_upgrades[1].keys.map(Cl.bufferFromHex); - - const vaaRotation3 = Cl.bufferFromHex(mainnet_valid_guardians_set_upgrades[2].vaa); - let publicKeysRotation3 = mainnet_valid_guardians_set_upgrades[2].keys.map(Cl.bufferFromHex); - - const block1 = simnet.mineBlock([ - tx.callPublicFn( - wormhole_core_v1_contract_name, - "update-guardians-set", - [vaaRotation1, Cl.list(publicKeysRotation1)], - sender - ), - tx.callPublicFn( - wormhole_core_v1_contract_name, - "update-guardians-set", - [vaaRotation2, Cl.list(publicKeysRotation2)], - sender - ), - tx.callPublicFn( - wormhole_core_v1_contract_name, - "update-guardians-set", - [vaaRotation3, Cl.list(publicKeysRotation3)], - sender - ), - ]); - - expect(block1).toHaveLength(3); - block1.forEach((b) => { - expect(b.result).toHaveClarityType(ClarityType.ResponseOk); - }); - - const vaaBytes = Cl.bufferFromHex(mainnet_valid_au[0]); - const executionPlan = Cl.tuple({ - 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pyth_storage_v1_contract_name), - 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pyth_decoder_pnau_v1_contract_name), - 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormhole_core_v1_contract_name), - }); - - let res = simnet.callPublicFn( - pyth_oracle_v1_contract_name, - "verify-and-update-price-feeds", - [vaaBytes, executionPlan], - sender - ); - - // console.log(res.result); - const result = res.result; - // expect(result).toHaveClarityType(ClarityType.ResponseOk); - // expect(result as ResponseOkCV).toHaveClarityType(ClarityType.ResponseOk); - console.log(Cl.prettyPrint(result, 2)); - - // expect((result as ResponseOkCV).value).toBeList([ - // // Cl.bufferFromHex("e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"), - // ]); - }); -}); diff --git a/unit-tests/constants.ts b/unit-tests/pyth/fixtures.ts similarity index 76% rename from unit-tests/constants.ts rename to unit-tests/pyth/fixtures.ts index f63bf56..1b415fa 100644 --- a/unit-tests/constants.ts +++ b/unit-tests/pyth/fixtures.ts @@ -1,4 +1,4 @@ -export const mainnet_valid_pfs = [ +export const p2whMainnetVaas = [ "01000000030d005b26a5ad9468407b98767abd7148f6d1c02a316b27001c1443a7a104928835e651ea5ef173752cfaa4859db565e16ab3ad6a28bb3793ace209dbad04c5ca7a7001016456ac96ec1439907736135b04b3cac5eeacba3c0d30503f97034ff84301ace93063120f56ed92c7a9707aa2016ae3f9dca4963596de87ba5983b840f796319e010233265dc219325dcff680fc5b632ada2dc5249ef437af22a386bc5caaadd1c66f5c2f7fe418dff397ca8609b3fe0d9b4a9ae08f2923e58095254ce7fbf717811b0103f11b0aec9c6265eea4e666ee5e3b1bf12236439dba0978dc8af738731ea81c416d4a5ca5e99344f95ad9e0e11a0ba7b5210bc4e9c13315251ecd8f2e2e6c006300049faaab5a536bad6e33dc63a643a2a0179744d50ab45e3e005e61d05a09bfb99a7fd3b35e71c8c18e9e45023425e7b145ece170b6872ff1b523664643f531fa01000a104723c46b41d6088675bbbc66704e49592cafd73ced3397d784c7b1ea917e1136a3dbae246ef9ad686a177bd021e82632e379f48e077fba86fb53330de8fcb1010b005daa1a9f81d27e0a8819f30e67d27a5f43e1fb10dfbbc65124007cb464ee331b9597ed11138fed943285fea688207e3132dd974498b76262f35abf3f908928000da0bfde1eb247b3fdeda6282ecbd0aeae0d75383bce4c54c18520d35d515c763517c24bdd9afb6b5f5f00812bc10f86cf98c051415dbd6a0eb6fe31067d06d386000e6782f1714277a13cf2dc13494b503e809ad015b1a78164a4e54bd3e68a2f164568bdfbb1b1a4f220e0b4d15448fe3adfd1fcda48b91869f3fa39f9d537d271d0010feb0b88a3a60bf1e9a451f7aa8839e493b6d919bb30762d1218b777e68cbb48e34f3f1f67b6ccb355bf11f96d1237c7aa8057b5bc7aab2bb1bf0986a23e93349801109063ab9b968b6498d27b4aba8b4c9d5b65f0b1ffc7bb9778868c141436f35820359a0c4935a71cf2bd49d14fbe95f0eb4e9462425a7be5b6188787f5e430b493001152ae6fe0525a7564aa1cb4e8447abc27336d11ff25f7cfb1cd67616b7e30a27a215119da7531947dde0ed4efbca877e824bc203c40f5c8d9bbe7a705155478d20112f10d3eae21de379d4a091bd4162965ac21b3c51aec144be676e803cf78f4846c172d35a17c86dce051f42c1233c69ffa1986a1ee34eafde1a22e5ccd0e42b18d01648b5aad00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd3110150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f0000000573acaada00000000015d02adfffffff8000000056e343a28000000000181eb6201000000170000001b00000000648b5aad00000000648b5aad00000000648b5aac0000000573acaada00000000015d02ad00000000648b5aac48d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a600fd80000000000cb227afffffff80000000268e1b6fc0000000000c37400010000001b0000002000000000648b5aad00000000648b5aad00000000648b5aac000000026a600fd80000000000cb227a00000000648b5aac3515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000249acd54e7e000000007b0d5582fffffff800000247ba887a800000000070c4e666010000001a0000001e00000000648b5aad00000000648b5aad00000000648b5aac00000249acd54e7e000000007b0d558200000000648b5aac9b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae80000000003922a7d00000000000155d3fffffff800000000038e900a0000000000012a51010000000f0000001100000000648b5aad00000000648b5aad00000000648b5aac0000000003924d74000000000001385800000000648b5aace876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d46e800000000000015f4fffffff800000000005cef1c0000000000001543010000001b0000002000000000648b5aad00000000648b5aad00000000648b5aac00000000005d46e800000000000015f400000000648b5aac", "01000000030d0076f4e3badfe434e501f376a24f84d4636082b6a8fb21bbf5cc8ee7c3a42b2dcc640c4f77027a935aa08ed030b409447452af7fcead6ec24549c03e552b9ed3b201018feee678abc925d9a758445d3efb7a7e58314d166f736f2ce5ec7567ec00295e1307b803dc74d1b567014d27ce5bcc7968c91f3d035abbe8b398afa3334d6a540003a18e6686ea73ada65799e9e0297ee0b00139e04703a6c67449c0b3918fa92c4274fab0f56180f1d9ee1ddbe76acf40937016955addf6e76e8e94aae502d4c0f60004961dc7f51e657961c94a632702e929d12434fb2fb80438f1b1e90a5fefa8d5f102a7c70046652b761a173c78de351587284b378abbadbcb2a44a031f3a3c78b00109375fdcf1472e853682cd7b80ff78818568bea166e752b4cd2334a1f725d2d0c85498ec27055c1a43b6717673c556bf7c4d78efb102d5372a4b009687ab45b9cc010a72426aa3045b64127bf0e765aff8644cceeecb9739fa19acb2d2b0936c4517932882fe0fec0217ad794def938580c15e46459183e723135332d0876ea998b05e010bb32a6f4cf571e8a1f4efeeb9bf97e5fa1399d6f5faaf9c243c5fe3746ffa79f27bda866de8a9ef18052c24d37c355bdc41b9750a94fe339fa4608e58e143991a010cce634e42ea68664dcb59717d58150f032b82654ccf1ef08fd9fd17703d68b7ca5609a8d1f883ca5f2d82eca4ef2fe97f933d76ae28b30295869deba9440af1d8000df19654675ba8bbc6d178a460c353ae9cca7895dd1cfe638882ffcc45d6046dfb6880a6a61d039e1906e5ce87e1e0275a551a74069c6b78544996ed56adc66385000f260c57ebd6f2e526e386436ac2eef3b0305d0de52e28ed876894783129c75f5721be2a30dc9478316b7efd5815c4690350a59ec178990188e4d1cfd525c62a7e00101178b0215e4f6f29fc54924a3bfa123d4037760c22e98cb29b3e2f2a61b3b00f66482446f177b0808ec735558b3488510f06ce5a2dad7839934006ce33015f6a001186b81e3dbeb05e0c31ba68e20e18092375bc62824034758411fb13cecc4bc2c5564ff7404fe8528367a6b06d40bebb3456e897dfbbbb220fcf85b59c3d2c052501129ef799c5a009006015f28f83191cbfe662cf2f1d86c91a286ec048fe6040869515e6fbd4301fc8c3ee31a27e7d5b6b1b95875d17f6f21368ef015837ce582c3401648b5aaa00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd2df0150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5aaa00000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5aaa00000000648b5aaa000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5aaa00000000648b5aaa00000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5aaa00000000648b5aaa0000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5aaa00000000648b5aaa0000000000000000000000000000000000000000000000000000000000000000", "01000000030d00221ee8e172ff8113223b83cc7eedf280b1dcacc37df5a024116d1a940b12fb5b76bb2535dfbd8af8c38239514381ae37cf2062aefdb41b196dd5c6dcfc94361d01021ba85a74a8a60e7ebfe20721b8e733d2fb853af98684b5c193cc3f9b20c757ad651326d6b135a4838b113adaae462f387c3c2afb249f1f882c770903495e8b4701033b9d2e19502e2f415cef825c15c3f6cc9de749564587ffa23bae53bbadf0676e3dec7e3bccca30c69d76300ef07849cc70677d5d5dcabbea61b912bf91ca7d290004f9c224e447d697229753d9fb5bc617762f7a5248e1fca13ac605a5371f19ddc46268331d4c2d197d80db0cf6c2b7f9203eb08c193dfd928018c0592c227f4cd8010839b7e8f6b4a7e7a2bfd058742a0e237f4d2bfb768c9df35fada8a6ddf18e04d05fbddc91cc53d429fbc2265836885e4ac251c3cad49a94816cf2e09bb394c627000a770a381d7368b7e8abb51fc619f0c56c0fdd261f99ce553d3160f2a669e7f27043d3b30e53c8a2b40ef7f11d8140b201bbeaf4bc03c5b6d9655292a71bd1e423010be6b61ad60af4e8fec918ba4a2ca11fcb7cf94035a54a0ced906ae7c2e77ae17d6cdeac4a511a2127da22869694a9c5804d3cd5eca4cfd39daa587091f85b9d98010cdc14d6ff68b64a99b09774fdbf30509f8767fd42ac511b08dc68e53d6a6eea875c53d272274e20f7fae47f6ce386a53c6c1c18243ec0ea2c6b29074b9b14df57000ddf03bb2a85f8c7dd7d4c771cdf503e294a0f2c92d3dad87dcdeb0b9455f16405167f5042b5dbe12e553570f43fa86eb720ce2fe70927b9c5e7bba70bcd2583ce010f62c6b4a1a0ca48542d35238caf9ea9fa3f6265dc81072773cf5bac9349c4fb5f179fb0cf7f31e73918894d43af0ca359d2458fdae2484271bc72c0766a2c42b90110ec732ead991211c67e1c44e92d37666abe7a24a788f9a0619bb536529b243251522ec65e847b9640c36c8336e8e4ede3f7e0718c1b60a0c15afdccf9ce0ecad90111106ce16dc68177e4a3daa28218b97f132f6361beaecfca4431227910fcd91e8e02473a424114696d21d7cbd3b5287cf83c4a6e60b7f158b2a39112ab37197c59011241b470bcd3d560fefee3ad56c1478813ae54ee75afb1ffd608e8e550c6ee1e2c6ccbd3cff9b56580b7ed5d36794876559aabe5dc06bfaca16322bf71d891b18701648b5ab900000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd4140150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000057358408000000000019c682cfffffff8000000056e39ad78000000000181e6fb01000000170000001b00000000648b5ab900000000648b5ab800000000648b5ab8000000057358408000000000019c682c00000000648b5ab848d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a02c1200000000000af79e0fffffff80000000268e319ac0000000000c36fb4010000001b0000002000000000648b5ab900000000648b5ab900000000648b5ab8000000026a02c1200000000000af79e000000000648b5ab83515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000002498cb46220000000007c363a70fffffff800000247bc41ea500000000070d2a8aa01000000190000001e00000000648b5ab900000000648b5ab900000000648b5ab8000002498d5dbae2000000007b8ce1ae00000000648b5ab89b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae8000000000391efa300000000000102e3fffffff800000000038e941e0000000000012a15010000000f0000001100000000648b5ab900000000648b5ab800000000648b5ab8000000000391efa300000000000102e300000000648b5ab8e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d42220000000000001272fffffff800000000005cef740000000000001542010000001b0000002000000000648b5ab900000000648b5ab900000000648b5ab800000000005d436d000000000000119c00000000648b5ab8", @@ -10,11 +10,7 @@ export const mainnet_valid_pfs = [ "01000000030d00ad5cfb79320e3f1c3d0929ac96b7db51ab7dfa5813ea8f1a8d4614dae430c7b041115f958622235a4af5e8e8513ed4acc1e3640610e6d433252ed01fcdc9338301011c653dda631cc45adb2b422382c11fd87b724f97a05ce3ec4dc038390f77fa5108c2132e6d5375e90e03aa8988223d167a2dee3286073cf7d3eeab468f3f8a0a0102ae87fa528ba7881a716223f356f3a246b1138c8a94bb5a8be8c1dea235909f716e7fe41d6d8014928895a2ac88b0481135eaad04f57bceaf51e64b46786496bf0003de49e30959d737517d6bb17ef6a2c8475ccb356db080f2fff222b4e2b4b77c9b6505052eea218353ade471d90f519fc84dc12ea4574429eaf50023ea5401ee1e00048ca6369ea9b87e8474e120c6093d9378bb0720e8114f57e52aac992c82627f864f9432bf0f1edbe3302d0abc27887c1b889b4fa81e013bc0eb14193f4e96854c010831be80ded7a9eed3cfec2f5da4cc7a88f823a06fa543e8d9aa2ada382f758a41173f2057c0287521c5378067cbc98f61b0f31bcbe500894dd5c8540cc5f3ab6e0109055ce3838a27258c67028aba8ac3f4289c33b4e6e371bded5f9404d07ff422306b0fc4dc022d36968e18e4922461baafb322ccaae359ef69f11c733cf13a1100000adf759df8103523f9e812373a43d4a02ff0c05b9cc2407cb38724df5be554738b180fabd96a730de0ceed327f8ba6cf4b78dbd0751f6270dd02b1087d2f7aa140010b8c0d22f8251f9b7b2e1e5df5e0521224b186a4c1f0776b62ca880f1c4447db22258b794aec2c5e6b5845a1bbe8b67c615465a6b56ef6da1830bd84f243cfe005010d247aee3d0d0dab3e33b317e2611d6e52df34bd5abf5a0cdd4937455832b1b0634b289ad4f96ea2946550c7dc94808dcfbdfad982f26827008c398b2e613ff728010e2162d1f86a6fe71314353527f64ba7397685bdc789cc7ebd50680625834ad2c554e5c57c26561774d528419a710b40f3a030fc46ac70f1dd79a39200a64215c3010f352d1943fea3821e3d07f53843ea7dc650d65a9eda692a26d5b8c691c3b7f1c44b4d3d1570525de2fdda4960c9fec5779b5cf6640dab19b13cac5477dcf3529b0112a0d821a4592cd3f4d8f071547efb6e6bb2500f4c8ef751943ab64c72c7a86bee38c82caad5d30edf9d981a21614e5cb8a1032e9a40f07a1db32345cb8b328c9a01648b5add00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd72f0150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000057246017800000000019230f0fffffff8000000056e462864000000000182251c01000000170000001b00000000648b5add00000000648b5add00000000648b5adc000000057246017800000000019230f000000000648b5adc48d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a03186a0000000000b24171fffffff80000000268e6624c0000000000c34c3d010000001b0000002000000000648b5add00000000648b5add00000000648b5adc000000026a01e6920000000000b0546e00000000648b5adc3515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b430000024969e00abd000000007d0a84c3fffffff800000247c0dccf300000000070e4cda0010000001a0000001e00000000648b5add00000000648b5add00000000648b5adc000002496be5d2df000000007b04bca100000000648b5adc9b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae800000000039338e00000000000011170fffffff800000000038e9fbe00000000000129ed010000000f0000001100000000648b5add00000000648b5add00000000648b5adc00000000039338e0000000000001117000000000648b5adbe876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d3222000000000000133bfffffff800000000005cf05b000000000000153a010000001b0000002000000000648b5add00000000648b5add00000000648b5adc00000000005d3222000000000000133b00000000648b5adc", ]; -export const mainnet_valid_au = [ - "504e41550100000003b801000000030d002c9e33a703a8c86012c117474849ae41118270cf56e2db61c7af28f64ce322391d33cb738930dc3ffcfe5ae35dbd99c4e08a8ac4b390c858d94dc1f0b91e24d00101901948561c54c17b7a21a9885d3f60a85b410e18209b02ebe098b0dd422b02667565e3b5011203303a6445cf629d20cec3d4771f0b391ab03bfd54364ac049f70002a030ed0060f1ba123ddf5473bef4c1c8f4aec8737b47f11023f94d5339b72c4c3aa325515956ea702537a3950d74f86134396de437bfc97a5365fc090dbb382c0103f1f64ba806fe8ba7b7db3846d85fccbe7d803865e7b757301f4a5de6ebde40f923ee925a7df78871d39299d70fd117635263b91fe53f792bfff450d210431316000439e31501d7b661a1b075a9302b57a6fc500c1e760de7e949e30994055b061b280cefcab3eb3b5c780f56cd555e88ee3c674b798ddeb8713bd27a361b8b8f1c730008db908c61e4158d1a7a7d53b2ec14027a4a2b2f3207edf0db2fbb0f431f3cdc020ee9fc9f38e812d094dcf5dd18f3a8d1d1ee94e840f504b00e08d0fbd607ed6d0109cf5c52aed9f4a42691c3c33bc366fde4a3ad23aacdd46c14669ad321106cf8d257b9379fc45cbe91a26ee87f4800951c5ca89ff0069e4a47299f988b368c6b74000b1241dea623dd874e13135682d760ee48ac1888149bddcfac54ff3787f4b89629063d89e084350ff1168999365918f9bdf469323b817a25491e56341a0da5c498000d538baecc668df092ce3cfeebfa5968447584916cb503ce50e7f77443bbd2a3441bb1d962a7f3401bfcf30f444a3301ab72dcb87cc04b681bdada93d45001f53d010e2fd97eeac94ceb3fd87a6e4ab3b785c64db221d522e58230a6d9eb10cba3b53a086ba6989e4285ab0816a26ff64c1a3a05175d0ffa61f4f744f928d6c5b3f92600107f999fa2f5cd826520cdf36f2e3434f4619eca4c2fb944bdec79d84903f76aaa02727844ec451378ab165910140c866ebdc65aea2cc6717da4747d4ed8a7338700115ce8f97f4a39ccdf5a61c9425d8a306a35b598db9d0f936bece6f2d92e79f9b1306d4c4789fb73ba5bd29bf0d306f2e8079aae818bc70aa9db111b745f1f4ab900126933d7f1317e3e45d98687e66ae033d871e1026e92f1ad168ec197a92ad653571837b78ee8e2263f2aca1dfa112fc78de63de9e2231f62ab79db2d956704146c0165131de200000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000000df92280141555756000000000005e1b46c00002710dbdd79b598d499596393b24358343774fd54e63c01005500ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c170000000002bf687c00000000000091effffffff80000000065131de10000000065131de00000000002c073dc0000000000008d5f09014cff5b7c5413efc667d2bca5549edadde5324814fed82e330d4110d610f5d0325deddb5541eacccd70da0a613768335768226657b201a456613163b0b4a21cf17276c2be6d02280ff05d08a6d4cf481c00c544f4adff1c86c6845a21871b75b4cffdc9314a2b46bddcb2bd44861fa48d4cc429a0783cb122a3eda67b7a13ad6298995c3bddd6cfa1ba547ddd38f0da18fcb0f7ead4d1de56db6c0442b94883a78f40b3541982a58d2d400dbfb761cce7bf780c", -]; - -export const testnet_valid_pfs = [ +export const p2whTestnetVaas = [ "01000000000100888fa734558a251d97d73318de190a281dd2913c412e2656fc5c5a4023f5e2e26e8e4b7bf884880e5acc119f207248cdd62f65cca8f422b3f624a707af765ac701648b651c00000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf1670150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a00000005810dc6000000000001a63b48fffffff80000000577e64610000000000185330c01000000010000000200000000648b651c00000000648b651c00000000648b651a00000005813a13cd000000000179ed7b00000000648b651a6a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002719137720000000000da2f1ffffffff8000000026c540eac0000000000c2702501000000010000000200000000648b651c00000000648b651c00000000648b651a000000027192eddb0000000000d878b600000000648b651a28fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f477ea9b1000000005e977a96fffffff80000024b91f168800000000064b5bb4801000000010000000200000000648b651c00000000648b651c00000000648b651a0000024f4dc86f84000000006418cbfc00000000648b651a8b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aaffab000000000001698cfffffff800000000039cca0c00000000000130a901000000010000000200000000648b651c00000000648b651c00000000648b651a0000000003ab5b030000000000013f8e00000000648b651a3b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005dfcd10000000000001343fffffff800000000005d7613000000000000151301000000010000000200000000648b651c00000000648b651c00000000648b651a00000000005dfd96000000000000170400000000648b651a", "01000000000100a55ed74e8b21f57ea14988bb7b5afd51c2c7bf91c2987e0d03b9c48cb1e0473c6e019e7ae2a665270d4a2a00dab47a7abed19e94b832989215791b7eba8f289e00648b64f800000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cee960150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580ba2e960000000001642c3ffffffff80000000577cbf1d4000000000185567801000000010000000200000000648b64f800000000648b64f800000000648b64f700000005807657b600000000019c41c500000000648b64f76a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002713911600000000000af5fb3fffffff8000000026c451af00000000000c2889701000000010000000200000000648b64f800000000648b64f800000000648b64f7000000027140b2800000000000b71b0000000000648b64f728fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f0d240e4f00000000750e2531fffffff80000024b87f3bae00000000064b0efc801000000010000000200000000648b64f800000000648b64f800000000648b64f80000024f10f80e6000000000713a252000000000648b64f88b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aa552e0000000000010600fffffff800000000039ca25d00000000000130be01000000010000000200000000648b64f800000000648b64f800000000648b64f70000000003aa552e000000000000e3c800000000648b64f73b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005df1380000000000001324fffffff800000000005d749b000000000000151501000000010000000200000000648b64f800000000648b64f800000000648b64f700000000005deee0000000000000119400000000648b64f7", "010000000001006aca274ebd019f90267f120bcf72935309f69ea651362dee74e312fa9e19ccbc0d5eb3e60fe5847bfd5fb5fe4b2fbb3c4dfb20b37b91739c679096512ac9093d00648b64f000000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cedda0150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b0000000003016d5e0000000000016ff6fffffff80000000002ef5d46000000000001777001000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000003016d5e0000000000016ff600000000648b64eaebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e700000000084825a70000000000057b0afffffff80000000008368f3c000000000005725e01000000010000000200000000648b64f000000000648b64ef00000000648b64ed00000000084825a70000000000057b0a00000000648b64ea2bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037573000000000000017bfffffff8000000000003766700000000000002e901000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000000037573000000000000017b00000000648b64ea5da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000f012260000000000013c19fffffff80000000000efe716000000000001387f01000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000000f012260000000000013c1900000000648b64eafad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023b84970000000000089f06fffffff80000000002359f41000000000008c44201000000010000000200000000648b64f000000000648b64ef00000000648b64ee00000000023b84970000000000089f0600000000648b64ea", @@ -26,77 +22,6 @@ export const testnet_valid_pfs = [ "01000000000100970313d82d9bf3fcfdaf8ed0b38c45cec7bad7093624ef5e7651cabfff706bc469f69951334e8555dbbbed3b1c0ceb3e4e9a65ab6558bd3655249dc7ee74d60500648b652500000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf2220150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580ea12ea000000000154e016fffffff80000000577ec9eac0000000001851c6c01000000010000000200000000648b652500000000648b652400000000648b65220000000580ea12ea000000000154e01600000000648b65246a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002718d17970000000000cf0b8efffffff8000000026c5719f80000000000c27cea01000000010000000200000000648b652500000000648b652400000000648b652300000002718fc1fb0000000000e21d8500000000648b652428fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f23ee6d200000000077306ce7fffffff80000024b93e12a900000000064bfcfde01000000010000000200000000648b652500000000648b652400000000648b65230000024f23ee6d20000000007aaf1ef700000000648b65248b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aaac0a0000000000014abefffffff800000000039cd2dd00000000000130b001000000010000000200000000648b652500000000648b652400000000648b65230000000003aac2380000000000012ec800000000648b65243b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005df7350000000000001794fffffff800000000005d7673000000000000151201000000010000000200000000648b652500000000648b652400000000648b652300000000005dfa98000000000000119400000000648b6523", ]; -export const mainnet_valid_guardians_set_upgrades = [ - { - vaa: "010000000001007ac31b282c2aeeeb37f3385ee0de5f8e421d30b9e5ae8ba3d4375c1c77a86e77159bb697d9c456d6f8c02d22a94b1279b65b0d6a9957e7d3857423845ac758e300610ac1d2000000030001000000000000000000000000000000000000000000000000000000000000000400000000000005390000000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", - keys: [ - "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", - "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", - "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", - "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", - "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", - "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", - "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", - "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", - "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", - "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", - "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", - "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", - "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", - "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", - "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", - "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", - "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", - "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", - "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", - ] - }, - { - vaa: "01000000010d0012e6b39c6da90c5dfd3c228edbb78c7a4c97c488ff8a346d161a91db067e51d638c17216f368aa9bdf4836b8645a98018ca67d2fec87d769cabfdf2406bf790a0002ef42b288091a670ef3556596f4f47323717882881eaf38e03345078d07a156f312b785b64dae6e9a87e3d32872f59cb1931f728cecf511762981baf48303668f0103cef2616b84c4e511ff03329e0853f1bd7ee9ac5ba71d70a4d76108bddf94f69c2a8a84e4ee94065e8003c334e899184943634e12043d0dda78d93996da073d190104e76d166b9dac98f602107cc4b44ac82868faf00b63df7d24f177aa391e050902413b71046434e67c770b19aecdf7fce1d1435ea0be7262e3e4c18f50ddc8175c0105d9450e8216d741e0206a50f93b750a47e0a258b80eb8fed1314cc300b3d905092de25cd36d366097b7103ae2d184121329ba3aa2d7c6cc53273f11af14798110010687477c8deec89d36a23e7948feb074df95362fc8dcbd8ae910ac556a1dee1e755c56b9db5d710c940938ed79bc1895a3646523a58bc55f475a23435a373ecfdd0107fb06734864f79def4e192497362513171530daea81f07fbb9f698afe7e66c6d44db21323144f2657d4a5386a954bb94eef9f64148c33aef6e477eafa2c5c984c01088769e82216310d1827d9bd48645ec23e90de4ef8a8de99e2d351d1df318608566248d80cdc83bdcac382b3c30c670352be87f9069aab5037d0b747208eae9c650109e9796497ff9106d0d1c62e184d83716282870cef61a1ee13d6fc485b521adcce255c96f7d1bca8d8e7e7d454b65783a830bddc9d94092091a268d311ecd84c26010c468c9fb6d41026841ff9f8d7368fa309d4dbea3ea4bbd2feccf94a92cc8a20a226338a8e2126cd16f70eaf15b4fc9be2c3fa19def14e071956a605e9d1ac4162010e23fcb6bd445b7c25afb722250c1acbc061ed964ba9de1326609ae012acdfb96942b2a102a2de99ab96327859a34a2b49a767dbdb62e0a1fb26af60fe44fd496a00106bb0bac77ac68b347645f2fb1ad789ea9bd76fb9b2324f25ae06f97e65246f142df717f662e73948317182c62ce87d79c73def0dba12e5242dfc038382812cfe00126da03c5e56cb15aeeceadc1e17a45753ab4dc0ec7bf6a75ca03143ed4a294f6f61bc3f478a457833e43084ecd7c985bf2f55a55f168aac0e030fc49e845e497101626e9d9a5d9e343f00010000000000000000000000000000000000000000000000000000000000000004c1759167c43f501c2000000000000000000000000000000000000000000000000000000000436f7265020000000000021358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd66b9590e1c41e0b226937bf9217d1d67fd4e91f574a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", - keys: [ - "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", - "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", - "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", - "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", - "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", - "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", - "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", - /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", - "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", - "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", - "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", - "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", - "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", - "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", - "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", - "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", - "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", - "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", - "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", - ] - }, - { - vaa: "01000000020d00ce45474d9e1b1e7790a2d210871e195db53a70ffd6f237cfe70e2686a32859ac43c84a332267a8ef66f59719cf91cc8df0101fd7c36aa1878d5139241660edc0010375cc906156ae530786661c0cd9aef444747bc3d8d5aa84cac6a6d2933d4e1a031cffa30383d4af8131e929d9f203f460b07309a647d6cd32ab1cc7724089392c000452305156cfc90343128f97e499311b5cae174f488ff22fbc09591991a0a73d8e6af3afb8a5968441d3ab8437836407481739e9850ad5c95e6acfcc871e951bc30105a7956eefc23e7c945a1966d5ddbe9e4be376c2f54e45e3d5da88c2f8692510c7429b1ea860ae94d929bd97e84923a18187e777aa3db419813a80deb84cc8d22b00061b2a4f3d2666608e0aa96737689e3ba5793810ff3a52ff28ad57d8efb20967735dc5537a2e43ef10f583d144c12a1606542c207f5b79af08c38656d3ac40713301086b62c8e130af3411b3c0d91b5b50dcb01ed5f293963f901fc36e7b0e50114dce203373b32eb45971cef8288e5d928d0ed51cd86e2a3006b0af6a65c396c009080009e93ab4d2c8228901a5f4525934000b2c26d1dc679a05e47fdf0ff3231d98fbc207103159ff4116df2832eea69b38275283434e6cd4a4af04d25fa7a82990b707010aa643f4cf615dfff06ffd65830f7f6cf6512dabc3690d5d9e210fdc712842dc2708b8b2c22e224c99280cd25e5e8bfb40e3d1c55b8c41774e287c1e2c352aecfc010b89c1e85faa20a30601964ccc6a79c0ae53cfd26fb10863db37783428cd91390a163346558239db3cd9d420cfe423a0df84c84399790e2e308011b4b63e6b8015010ca31dcb564ac81a053a268d8090e72097f94f366711d0c5d13815af1ec7d47e662e2d1bde22678113d15963da100b668ba26c0c325970d07114b83c5698f46097010dc9fda39c0d592d9ed92cd22b5425cc6b37430e236f02d0d1f8a2ef45a00bde26223c0a6eb363c8b25fd3bf57234a1d9364976cefb8360e755a267cbbb674b39501108db01e444ab1003dd8b6c96f8eb77958b40ba7a85fefecf32ad00b7a47c0ae7524216262495977e09c0989dd50f280c21453d3756843608eacd17f4fdfe47600001261025228ef5af837cb060bcd986fcfa84ccef75b3fa100468cfd24e7fadf99163938f3b841a33496c2706d0208faab088bd155b2e20fd74c625bb1cc8c43677a0163c53c409e0c5dfa000100000000000000000000000000000000000000000000000000000000000000046c5a054d7833d1e42000000000000000000000000000000000000000000000000000000000436f7265020000000000031358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd15e7caf07c4e3dc8e7c469f92c8cd88fb8005a2074a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", - keys: [ - "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", - "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", - "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", - "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", - "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", - "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", - "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", - /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", - "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", - "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", - "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", - "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", - "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", - "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", - "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", - "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", - "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", - "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", - "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", - ] - }, +export const apnuMainnetVaas = [ + "504e41550100000003b801000000030d002c9e33a703a8c86012c117474849ae41118270cf56e2db61c7af28f64ce322391d33cb738930dc3ffcfe5ae35dbd99c4e08a8ac4b390c858d94dc1f0b91e24d00101901948561c54c17b7a21a9885d3f60a85b410e18209b02ebe098b0dd422b02667565e3b5011203303a6445cf629d20cec3d4771f0b391ab03bfd54364ac049f70002a030ed0060f1ba123ddf5473bef4c1c8f4aec8737b47f11023f94d5339b72c4c3aa325515956ea702537a3950d74f86134396de437bfc97a5365fc090dbb382c0103f1f64ba806fe8ba7b7db3846d85fccbe7d803865e7b757301f4a5de6ebde40f923ee925a7df78871d39299d70fd117635263b91fe53f792bfff450d210431316000439e31501d7b661a1b075a9302b57a6fc500c1e760de7e949e30994055b061b280cefcab3eb3b5c780f56cd555e88ee3c674b798ddeb8713bd27a361b8b8f1c730008db908c61e4158d1a7a7d53b2ec14027a4a2b2f3207edf0db2fbb0f431f3cdc020ee9fc9f38e812d094dcf5dd18f3a8d1d1ee94e840f504b00e08d0fbd607ed6d0109cf5c52aed9f4a42691c3c33bc366fde4a3ad23aacdd46c14669ad321106cf8d257b9379fc45cbe91a26ee87f4800951c5ca89ff0069e4a47299f988b368c6b74000b1241dea623dd874e13135682d760ee48ac1888149bddcfac54ff3787f4b89629063d89e084350ff1168999365918f9bdf469323b817a25491e56341a0da5c498000d538baecc668df092ce3cfeebfa5968447584916cb503ce50e7f77443bbd2a3441bb1d962a7f3401bfcf30f444a3301ab72dcb87cc04b681bdada93d45001f53d010e2fd97eeac94ceb3fd87a6e4ab3b785c64db221d522e58230a6d9eb10cba3b53a086ba6989e4285ab0816a26ff64c1a3a05175d0ffa61f4f744f928d6c5b3f92600107f999fa2f5cd826520cdf36f2e3434f4619eca4c2fb944bdec79d84903f76aaa02727844ec451378ab165910140c866ebdc65aea2cc6717da4747d4ed8a7338700115ce8f97f4a39ccdf5a61c9425d8a306a35b598db9d0f936bece6f2d92e79f9b1306d4c4789fb73ba5bd29bf0d306f2e8079aae818bc70aa9db111b745f1f4ab900126933d7f1317e3e45d98687e66ae033d871e1026e92f1ad168ec197a92ad653571837b78ee8e2263f2aca1dfa112fc78de63de9e2231f62ab79db2d956704146c0165131de200000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000000df92280141555756000000000005e1b46c00002710dbdd79b598d499596393b24358343774fd54e63c01005500ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c170000000002bf687c00000000000091effffffff80000000065131de10000000065131de00000000002c073dc0000000000008d5f09014cff5b7c5413efc667d2bca5549edadde5324814fed82e330d4110d610f5d0325deddb5541eacccd70da0a613768335768226657b201a456613163b0b4a21cf17276c2be6d02280ff05d08a6d4cf481c00c544f4adff1c86c6845a21871b75b4cffdc9314a2b46bddcb2bd44861fa48d4cc429a0783cb122a3eda67b7a13ad6298995c3bddd6cfa1ba547ddd38f0da18fcb0f7ead4d1de56db6c0442b94883a78f40b3541982a58d2d400dbfb761cce7bf780c", ]; diff --git a/unit-tests/pyth/oracle.test.ts b/unit-tests/pyth/oracle.test.ts new file mode 100644 index 0000000..2bbc95f --- /dev/null +++ b/unit-tests/pyth/oracle.test.ts @@ -0,0 +1,48 @@ +import { Cl, ClarityType } from "@stacks/transactions"; +import { beforeEach, describe, expect, it } from "vitest"; +import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; +import { apnuMainnetVaas } from "./fixtures"; +import { wormhole } from "../wormhole/helper"; + +const pythOracleContractName = "pyth-oracle-v1"; +const pythDecoderPnauContractName = "pyth-pnau-decoder-v1"; +const pythStorageContractName = "pyth-store-v1"; +const wormholeCoreContractName = "wormhole-core-v1"; + +describe("pyth-oracle-v1::decode-and-verify-price-feeds mainnet VAAs", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + + let block: ParsedTransactionResult[] | undefined = undefined; + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + block = wormhole.applyMainnetGuardianSetUpdates(sender, wormholeCoreContractName) + }) + + it("should succeed handling the 3 guardians rotations", () => { + expect(block!).toHaveLength(3); + block!.forEach((b: ParsedTransactionResult) => { + expect(b.result).toHaveClarityType(ClarityType.ResponseOk); + }); + }); + + it("should succeed handling PNAU mainnet payloads", () => { + const vaaBytes = Cl.bufferFromHex(apnuMainnetVaas[0]); + const executionPlan = Cl.tuple({ + 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), + 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pythDecoderPnauContractName), + 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormholeCoreContractName), + }); + + let res = simnet.callPublicFn( + pythOracleContractName, + "verify-and-update-price-feeds", + [vaaBytes, executionPlan], + sender + ); + + const result = res.result; + console.log(Cl.prettyPrint(result, 2)); + }); +}); diff --git a/unit-tests/wormhole.test.ts b/unit-tests/wormhole.test.ts deleted file mode 100644 index c8d8143..0000000 --- a/unit-tests/wormhole.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Cl, ClarityType } from "@stacks/transactions"; -import { describe, expect, it } from "vitest"; -import { tx } from "@hirosystems/clarinet-sdk"; - -import { mainnet_valid_guardians_set_upgrades } from "./constants"; - -const wormhole_core_v1_contract_name = "wormhole-core-v1"; - -describe("Wormhole testsuite", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - - it("ensure that guardians set can be rotated", () => { - const vaaRotation1 = Cl.bufferFromHex(mainnet_valid_guardians_set_upgrades[0].vaa); - let publicKeysRotation1 = mainnet_valid_guardians_set_upgrades[0].keys.map(Cl.bufferFromHex); - - const vaaRotation2 = Cl.bufferFromHex(mainnet_valid_guardians_set_upgrades[1].vaa); - let publicKeysRotation2 = mainnet_valid_guardians_set_upgrades[1].keys.map(Cl.bufferFromHex); - - const vaaRotation3 = Cl.bufferFromHex(mainnet_valid_guardians_set_upgrades[2].vaa); - let publicKeysRotation3 = mainnet_valid_guardians_set_upgrades[2].keys.map(Cl.bufferFromHex); - - const block1 = simnet.mineBlock([ - tx.callPublicFn( - wormhole_core_v1_contract_name, - "update-guardians-set", - [vaaRotation1, Cl.list(publicKeysRotation1)], - sender - ), - tx.callPublicFn( - wormhole_core_v1_contract_name, - "update-guardians-set", - [vaaRotation2, Cl.list(publicKeysRotation2)], - sender - ), - tx.callPublicFn( - wormhole_core_v1_contract_name, - "update-guardians-set", - [vaaRotation3, Cl.list(publicKeysRotation3)], - sender - ), - ]); - - expect(block1).toHaveLength(3); - block1.forEach((b) => { - expect(b.result).toHaveClarityType(ClarityType.ResponseOk); - }); - }); -}); diff --git a/unit-tests/wormhole/fixtures.ts b/unit-tests/wormhole/fixtures.ts new file mode 100644 index 0000000..f907f29 --- /dev/null +++ b/unit-tests/wormhole/fixtures.ts @@ -0,0 +1,74 @@ +export const gsuMainnetVaas = [ + { + vaa: "010000000001007ac31b282c2aeeeb37f3385ee0de5f8e421d30b9e5ae8ba3d4375c1c77a86e77159bb697d9c456d6f8c02d22a94b1279b65b0d6a9957e7d3857423845ac758e300610ac1d2000000030001000000000000000000000000000000000000000000000000000000000000000400000000000005390000000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", + keys: [ + "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", + "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", + "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", + "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", + "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", + "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", + "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", + "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", + "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", + "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", + "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", + "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", + "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", + "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", + "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", + "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", + "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", + "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", + "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", + ] + }, + { + vaa: "01000000010d0012e6b39c6da90c5dfd3c228edbb78c7a4c97c488ff8a346d161a91db067e51d638c17216f368aa9bdf4836b8645a98018ca67d2fec87d769cabfdf2406bf790a0002ef42b288091a670ef3556596f4f47323717882881eaf38e03345078d07a156f312b785b64dae6e9a87e3d32872f59cb1931f728cecf511762981baf48303668f0103cef2616b84c4e511ff03329e0853f1bd7ee9ac5ba71d70a4d76108bddf94f69c2a8a84e4ee94065e8003c334e899184943634e12043d0dda78d93996da073d190104e76d166b9dac98f602107cc4b44ac82868faf00b63df7d24f177aa391e050902413b71046434e67c770b19aecdf7fce1d1435ea0be7262e3e4c18f50ddc8175c0105d9450e8216d741e0206a50f93b750a47e0a258b80eb8fed1314cc300b3d905092de25cd36d366097b7103ae2d184121329ba3aa2d7c6cc53273f11af14798110010687477c8deec89d36a23e7948feb074df95362fc8dcbd8ae910ac556a1dee1e755c56b9db5d710c940938ed79bc1895a3646523a58bc55f475a23435a373ecfdd0107fb06734864f79def4e192497362513171530daea81f07fbb9f698afe7e66c6d44db21323144f2657d4a5386a954bb94eef9f64148c33aef6e477eafa2c5c984c01088769e82216310d1827d9bd48645ec23e90de4ef8a8de99e2d351d1df318608566248d80cdc83bdcac382b3c30c670352be87f9069aab5037d0b747208eae9c650109e9796497ff9106d0d1c62e184d83716282870cef61a1ee13d6fc485b521adcce255c96f7d1bca8d8e7e7d454b65783a830bddc9d94092091a268d311ecd84c26010c468c9fb6d41026841ff9f8d7368fa309d4dbea3ea4bbd2feccf94a92cc8a20a226338a8e2126cd16f70eaf15b4fc9be2c3fa19def14e071956a605e9d1ac4162010e23fcb6bd445b7c25afb722250c1acbc061ed964ba9de1326609ae012acdfb96942b2a102a2de99ab96327859a34a2b49a767dbdb62e0a1fb26af60fe44fd496a00106bb0bac77ac68b347645f2fb1ad789ea9bd76fb9b2324f25ae06f97e65246f142df717f662e73948317182c62ce87d79c73def0dba12e5242dfc038382812cfe00126da03c5e56cb15aeeceadc1e17a45753ab4dc0ec7bf6a75ca03143ed4a294f6f61bc3f478a457833e43084ecd7c985bf2f55a55f168aac0e030fc49e845e497101626e9d9a5d9e343f00010000000000000000000000000000000000000000000000000000000000000004c1759167c43f501c2000000000000000000000000000000000000000000000000000000000436f7265020000000000021358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd66b9590e1c41e0b226937bf9217d1d67fd4e91f574a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", + keys: [ + "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", + "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", + "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", + "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", + "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", + "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", + "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", + /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", + "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", + "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", + "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", + "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", + "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", + "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", + "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", + "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", + "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", + "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", + "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", + ] + }, + { + vaa: "01000000020d00ce45474d9e1b1e7790a2d210871e195db53a70ffd6f237cfe70e2686a32859ac43c84a332267a8ef66f59719cf91cc8df0101fd7c36aa1878d5139241660edc0010375cc906156ae530786661c0cd9aef444747bc3d8d5aa84cac6a6d2933d4e1a031cffa30383d4af8131e929d9f203f460b07309a647d6cd32ab1cc7724089392c000452305156cfc90343128f97e499311b5cae174f488ff22fbc09591991a0a73d8e6af3afb8a5968441d3ab8437836407481739e9850ad5c95e6acfcc871e951bc30105a7956eefc23e7c945a1966d5ddbe9e4be376c2f54e45e3d5da88c2f8692510c7429b1ea860ae94d929bd97e84923a18187e777aa3db419813a80deb84cc8d22b00061b2a4f3d2666608e0aa96737689e3ba5793810ff3a52ff28ad57d8efb20967735dc5537a2e43ef10f583d144c12a1606542c207f5b79af08c38656d3ac40713301086b62c8e130af3411b3c0d91b5b50dcb01ed5f293963f901fc36e7b0e50114dce203373b32eb45971cef8288e5d928d0ed51cd86e2a3006b0af6a65c396c009080009e93ab4d2c8228901a5f4525934000b2c26d1dc679a05e47fdf0ff3231d98fbc207103159ff4116df2832eea69b38275283434e6cd4a4af04d25fa7a82990b707010aa643f4cf615dfff06ffd65830f7f6cf6512dabc3690d5d9e210fdc712842dc2708b8b2c22e224c99280cd25e5e8bfb40e3d1c55b8c41774e287c1e2c352aecfc010b89c1e85faa20a30601964ccc6a79c0ae53cfd26fb10863db37783428cd91390a163346558239db3cd9d420cfe423a0df84c84399790e2e308011b4b63e6b8015010ca31dcb564ac81a053a268d8090e72097f94f366711d0c5d13815af1ec7d47e662e2d1bde22678113d15963da100b668ba26c0c325970d07114b83c5698f46097010dc9fda39c0d592d9ed92cd22b5425cc6b37430e236f02d0d1f8a2ef45a00bde26223c0a6eb363c8b25fd3bf57234a1d9364976cefb8360e755a267cbbb674b39501108db01e444ab1003dd8b6c96f8eb77958b40ba7a85fefecf32ad00b7a47c0ae7524216262495977e09c0989dd50f280c21453d3756843608eacd17f4fdfe47600001261025228ef5af837cb060bcd986fcfa84ccef75b3fa100468cfd24e7fadf99163938f3b841a33496c2706d0208faab088bd155b2e20fd74c625bb1cc8c43677a0163c53c409e0c5dfa000100000000000000000000000000000000000000000000000000000000000000046c5a054d7833d1e42000000000000000000000000000000000000000000000000000000000436f7265020000000000031358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd15e7caf07c4e3dc8e7c469f92c8cd88fb8005a2074a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", + keys: [ + "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", + "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", + "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", + "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", + "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", + "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", + "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", + /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", + "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", + "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", + "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", + "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", + "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", + "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", + "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", + "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", + "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", + "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", + "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", + ] + }, +]; diff --git a/unit-tests/wormhole/helper.ts b/unit-tests/wormhole/helper.ts index f70526b..93c0ebd 100644 --- a/unit-tests/wormhole/helper.ts +++ b/unit-tests/wormhole/helper.ts @@ -12,6 +12,8 @@ if (!globalThis.crypto) globalThis.crypto = webcrypto; import { hmac } from '@noble/hashes/hmac'; import { sha256 } from '@noble/hashes/sha256'; +import { gsuMainnetVaas } from './fixtures'; + secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) export namespace wormhole { @@ -280,7 +282,6 @@ export namespace wormhole { } export const serializeVaa = (vaaHeader: VaaHeader, vaaBody: VaaBody) => { - return Buffer.concat([serializeVaaHeader(vaaHeader), serializeVaaBody(vaaBody)]); } @@ -358,23 +359,56 @@ export namespace wormhole { return Buffer.concat(components); } -export function applyGuardianSetUpdate(keychain: wormhole.Guardian[], guardianSetId: number, txSenderAddress: string, contract_name: string) { - let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, guardianSetId, wormhole.validGuardianRotationModule); - let vaaBody = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { version: 1, guardianSetId: guardianSetId - 1 }); - let vaa = wormhole.serializeVaa(vaaHeader, vaaBody); - let uncompressedPublicKey = []; - for (let guardian of keychain) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + export function applyGuardianSetUpdate(keychain: wormhole.Guardian[], guardianSetId: number, txSenderAddress: string, contract_name: string) { + let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, guardianSetId, wormhole.validGuardianRotationModule); + let vaaBody = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); + let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { version: 1, guardianSetId: guardianSetId - 1 }); + let vaa = wormhole.serializeVaa(vaaHeader, vaaBody); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + let result = simnet.mineBlock([ + tx.callPublicFn( + contract_name, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + txSenderAddress + ), + ])[0].result; + return [result, vaaHeader, vaaBody] + } + + export function applyMainnetGuardianSetUpdates(txSenderAddress: string, contractName: string) { + const vaaRotation1 = Cl.bufferFromHex(gsuMainnetVaas[0].vaa); + let publicKeysRotation1 = gsuMainnetVaas[0].keys.map(Cl.bufferFromHex); + + const vaaRotation2 = Cl.bufferFromHex(gsuMainnetVaas[1].vaa); + let publicKeysRotation2 = gsuMainnetVaas[1].keys.map(Cl.bufferFromHex); + + const vaaRotation3 = Cl.bufferFromHex(gsuMainnetVaas[2].vaa); + let publicKeysRotation3 = gsuMainnetVaas[2].keys.map(Cl.bufferFromHex); + + const block = simnet.mineBlock([ + tx.callPublicFn( + contractName, + "update-guardians-set", + [vaaRotation1, Cl.list(publicKeysRotation1)], + txSenderAddress + ), + tx.callPublicFn( + contractName, + "update-guardians-set", + [vaaRotation2, Cl.list(publicKeysRotation2)], + txSenderAddress + ), + tx.callPublicFn( + contractName, + "update-guardians-set", + [vaaRotation3, Cl.list(publicKeysRotation3)], + txSenderAddress + ), + ]); + return block; } - let result = simnet.mineBlock([ - tx.callPublicFn( - contract_name, - `update-guardians-set`, - [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], - txSenderAddress - ), - ])[0].result; - return [result, vaaHeader, vaaBody] -} } diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts index 07b4342..d3d6951 100644 --- a/unit-tests/wormhole/vaa.test.ts +++ b/unit-tests/wormhole/vaa.test.ts @@ -1,8 +1,8 @@ -import { Cl } from "@stacks/transactions"; -import { expect, describe, it as vit, beforeEach } from "vitest"; +import { Cl, ClarityType } from "@stacks/transactions"; +import { expect, describe, beforeEach } from "vitest"; import { it, fc } from '@fast-check/vitest'; import { wormhole } from './helper'; -import { tx } from "@hirosystems/clarinet-sdk"; +import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; const contractName = "wormhole-core-v1"; const verbosity = 0; @@ -394,3 +394,21 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { expect(res.result).toBeErr(Cl.uint(1102)); }); }) + +describe("wormhole-core-v1::update-guardians-set mainnet guardian rotations", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + let block: ParsedTransactionResult[] | undefined = undefined; + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + block = wormhole.applyMainnetGuardianSetUpdates(sender, contractName) + }) + + it("should succeed handling the 3 guardians rotations", () => { + expect(block!).toHaveLength(3); + block!.forEach((b: ParsedTransactionResult) => { + expect(b.result).toHaveClarityType(ClarityType.ResponseOk); + }); + }); +}); From 7abded7c88565531af7351f1f932ece0d61bcdb3 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Thu, 19 Oct 2023 12:20:00 -0400 Subject: [PATCH 09/18] test: progress on oracle's coverage - 100% --- contracts/pyth-governance-v1.clar | 15 +- contracts/pyth-pnau-decoder-v1.clar | 6 +- contracts/pyth-store-v1.clar | 55 ++- contracts/pyth-traits-v1.clar | 2 +- package-lock.json | 652 ++++++++++++++++++++++++++++ package.json | 3 + unit-tests/pyth/oracle.test.ts | 47 +- 7 files changed, 757 insertions(+), 23 deletions(-) diff --git a/contracts/pyth-governance-v1.clar b/contracts/pyth-governance-v1.clar index a24f2d6..911a4f2 100644 --- a/contracts/pyth-governance-v1.clar +++ b/contracts/pyth-governance-v1.clar @@ -25,6 +25,9 @@ (define-constant GOVERNANCE_UPGRADE_CONTRACT_WORMHOLE_CORE 0x06) ;; Fee is charged when you submit a new price (define-constant GOVERNANCE_SET_RECIPIENT_ADDRESS 0xa0) +;; Error unauthorized control flow +(define-constant ERR_UNAUTHORIZED_ACCESS (err u404)) + (define-data-var fee-value { mantissa: uint, exponent: uint } @@ -72,16 +75,16 @@ ;; Other contract (if (is-eq contract-caller (get pyth-decoder-contract expected-execution-plan)) ;; The decoding contract is checking its execution flow - (let ((execution-plan (unwrap! execution-plan-opt (err u10)))) + (let ((execution-plan (unwrap! execution-plan-opt ERR_UNAUTHORIZED_ACCESS))) ;; Must always be invoked by the proxy (try! (expect-contract-call-performed-by-expected-oracle-contract former-contract-caller expected-execution-plan)) ;; Ensure that wormhole contract is the one expected (try! (expect-active-wormhole-contract (get wormhole-core-contract execution-plan) expected-execution-plan))) (if (is-eq contract-caller (get pyth-oracle-contract expected-execution-plan)) ;; The proxy contract is checking its execution flow - (let ((execution-plan (unwrap! execution-plan-opt (err u10)))) + (let ((execution-plan (unwrap! execution-plan-opt ERR_UNAUTHORIZED_ACCESS))) ;; This contract must always be invoked by the proxy - (try! (expect-contract-call-performed-by-expected-oracle-contract former-contract-caller expected-execution-plan)) + ;; (try! (expect-contract-call-performed-by-expected-oracle-contract former-contract-caller expected-execution-plan)) ;; Ensure that storage contract is the one expected (try! (expect-active-storage-contract (get pyth-storage-contract execution-plan) expected-execution-plan)) ;; Ensure that decoder contract is the one expected @@ -123,7 +126,7 @@ (asserts! (is-eq (contract-of storage-contract) - (get pyth-storage-contract expected-plan)) (err u1)) + (get pyth-storage-contract expected-plan)) ERR_UNAUTHORIZED_ACCESS) (ok true))) (define-private (expect-active-decoder-contract @@ -138,7 +141,7 @@ (asserts! (is-eq (contract-of decoder-contract) - (get pyth-decoder-contract expected-plan)) (err u2)) + (get pyth-decoder-contract expected-plan)) ERR_UNAUTHORIZED_ACCESS) (ok true))) (define-private (expect-active-wormhole-contract @@ -153,7 +156,7 @@ (asserts! (is-eq (contract-of wormhole-contract) - (get wormhole-core-contract expected-plan)) (err u3)) + (get wormhole-core-contract expected-plan)) ERR_UNAUTHORIZED_ACCESS) (ok true))) (define-read-only (get-current-execution-plan) diff --git a/contracts/pyth-pnau-decoder-v1.clar b/contracts/pyth-pnau-decoder-v1.clar index 02c8e0b..a647e8c 100644 --- a/contracts/pyth-pnau-decoder-v1.clar +++ b/contracts/pyth-pnau-decoder-v1.clar @@ -35,12 +35,14 @@ (define-constant MERKLE_ROOT_MISMATCH (err u2006)) ;; Price not found (define-constant ERR_NOT_FOUND (err u0)) +;; Price not found +(define-constant ERR_UNAUTHORIZED_FLOW (err u2404)) ;;;; Public functions (define-public (decode-and-verify-price-feeds (pnau-bytes (buff 8192)) (wormhole-core-address )) (begin - ;; Ensure that updates are always coming from the proxy contract - (asserts! (is-eq contract-caller .pyth-proxy-v1) (err u1)) + ;; Ensure that updates are always coming from the oracle contract + (asserts! (is-eq contract-caller .pyth-oracle-v1) ERR_UNAUTHORIZED_FLOW) ;; Proceed to update (let ((prices-updates (try! (decode-pnau-price-update pnau-bytes wormhole-core-address)))) (ok prices-updates)))) diff --git a/contracts/pyth-store-v1.clar b/contracts/pyth-store-v1.clar index 6b5c217..29c0c69 100644 --- a/contracts/pyth-store-v1.clar +++ b/contracts/pyth-store-v1.clar @@ -15,7 +15,7 @@ prev-publish-time: uint, }) -(define-data-var timestamps uint u0) +(define-map timestamps (buff 32) uint) (define-public (read (price-identifier (buff 32))) (let ((entry (unwrap! (map-get? prices price-identifier) (err u404)))) @@ -30,9 +30,13 @@ publish-time: uint, prev-publish-time: uint, })) - (ok u1)) + (begin + ;; Ensure that updates are always coming from the right contract + (try! (contract-call? .pyth-governance-v1 check-execution-flow contract-caller none)) + ;; Update storage + (ok (write-update price-identifier data)))) -(define-public (write-batch (batch (list 64 { +(define-public (write-batch (batch-updates (list 64 { price-identifier: (buff 32), price: int, conf: uint, @@ -42,4 +46,47 @@ publish-time: uint, prev-publish-time: uint, }))) - (ok u1)) + (begin + ;; Ensure that updates are always coming from the right contract + (try! (contract-call? .pyth-governance-v1 check-execution-flow contract-caller none)) + ;; Update storage, count the number of updates + (ok (fold + (map write-batch-entry batch-updates) u0)))) + +(define-private (write-batch-entry (entry { + price-identifier: (buff 32), + price: int, + conf: uint, + expo: int, + ema-price: int, + ema-conf: uint, + publish-time: uint, + prev-publish-time: uint, + })) + (if (write-update (get price-identifier entry) { + price: (get price entry), + conf: (get conf entry), + expo: (get expo entry), + ema-price: (get ema-price entry), + ema-conf: (get ema-conf entry), + publish-time: (get publish-time entry), + prev-publish-time: (get prev-publish-time entry) + }) + u1 + u0)) + +(define-private (write-update (price-identifier (buff 32)) (data { + price: int, + conf: uint, + expo: int, + ema-price: int, + ema-conf: uint, + publish-time: uint, + prev-publish-time: uint, + })) + (begin + (if (not (is-price-update-outdated price-identifier (get publish-time data))) + (map-set prices price-identifier data) + false))) + +(define-private (is-price-update-outdated (price-identifier (buff 32)) (publish-time uint)) + (< publish-time (default-to u0 (map-get? timestamps price-identifier)))) diff --git a/contracts/pyth-traits-v1.clar b/contracts/pyth-traits-v1.clar index be76581..7db89dc 100644 --- a/contracts/pyth-traits-v1.clar +++ b/contracts/pyth-traits-v1.clar @@ -40,7 +40,7 @@ ema-conf: uint, publish-time: uint, prev-publish-time: uint, - }) (response uint uint)) + }) (response bool uint)) (write-batch ((list 64 { price-identifier: (buff 32), diff --git a/package-lock.json b/package-lock.json index e811d8a..c7e1852 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,8 @@ }, "devDependencies": { "@fast-check/vitest": "^0.0.8", + "@vitest/ui": "^0.34.6", + "chokidar-cli": "^3.0.0", "fast-check": "^3.13.1" } }, @@ -435,6 +437,47 @@ } ] }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "devOptional": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "devOptional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "devOptional": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.23", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.23.tgz", + "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==", + "devOptional": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -569,6 +612,27 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/ui": { + "version": "0.34.6", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-0.34.6.tgz", + "integrity": "sha512-/fxnCwGC0Txmr3tF3BwAbo3v6U2SkBTGR9UB8zo0Ztlx0BTOXHucE0gDHY7SjwEktCOHatiGmli9kZD6gYSoWQ==", + "devOptional": true, + "dependencies": { + "@vitest/utils": "0.34.6", + "fast-glob": "^3.3.0", + "fflate": "^0.8.0", + "flatted": "^3.2.7", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "sirv": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": ">=0.30.1 <1" + } + }, "node_modules/@vitest/utils": { "version": "0.34.6", "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz", @@ -620,6 +684,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -633,6 +710,27 @@ "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "devOptional": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/c32check": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/c32check/-/c32check-2.0.0.tgz", @@ -653,6 +751,15 @@ "node": ">=8" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "4.3.10", "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.10.tgz", @@ -681,6 +788,187 @@ "node": "*" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar-cli": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chokidar-cli/-/chokidar-cli-3.0.0.tgz", + "integrity": "sha512-xVW+Qeh7z15uZRxHOkP93Ux8A0xbPzwK4GaqD8dQOYc34TlkqUhVSS59fK36DOp5WdJlrRzlYSy02Ht99FjZqQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "lodash.debounce": "^4.0.8", + "lodash.throttle": "^4.1.1", + "yargs": "^13.3.0" + }, + "bin": { + "chokidar": "index.js" + }, + "engines": { + "node": ">= 8.10.0" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/chokidar-cli/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/chokidar-cli/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/chokidar-cli/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/chokidar-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar-cli/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/chokidar-cli/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/chokidar-cli/node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/chokidar-cli/node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -734,6 +1022,15 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/deep-eql": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -824,6 +1121,67 @@ "node": ">=8.0.0" } }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "devOptional": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "devOptional": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fflate": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.1.tgz", + "integrity": "sha512-/exOvEuc+/iaUm105QIiOt4LpBdMTWsXxqR0HDF35vx3fmaKzw7354gTilCh5rkzEt8WYyG//ku3h3nRmd7CHQ==", + "devOptional": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "devOptional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "devOptional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -853,6 +1211,39 @@ "node": "*" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "devOptional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -861,6 +1252,27 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/jsonc-parser": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", @@ -890,11 +1302,36 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true + }, "node_modules/loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -914,6 +1351,28 @@ "node": ">=12" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "devOptional": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "devOptional": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mlly": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.4.2.tgz", @@ -925,6 +1384,15 @@ "ufo": "^1.3.0" } }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "devOptional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -966,6 +1434,15 @@ } } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/p-limit": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", @@ -980,6 +1457,51 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/pathe": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", @@ -998,6 +1520,18 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -1076,11 +1610,43 @@ } ] }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -1089,6 +1655,22 @@ "node": ">=0.10.0" } }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "devOptional": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "3.29.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", @@ -1104,11 +1686,54 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==" }, + "node_modules/sirv": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", + "devOptional": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -1188,6 +1813,27 @@ "node": ">=14.0.0" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -1381,6 +2027,12 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true + }, "node_modules/why-is-node-running": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", diff --git a/package.json b/package.json index 8118b13..e23224a 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "scripts": { "test": "vitest run", + "test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:coverage\"", "test:coverage": "vitest run -- --coverage true" }, "author": "", @@ -23,6 +24,8 @@ }, "devDependencies": { "@fast-check/vitest": "^0.0.8", + "@vitest/ui": "^0.34.6", + "chokidar-cli": "^3.0.0", "fast-check": "^3.13.1" } } diff --git a/unit-tests/pyth/oracle.test.ts b/unit-tests/pyth/oracle.test.ts index 2bbc95f..7167452 100644 --- a/unit-tests/pyth/oracle.test.ts +++ b/unit-tests/pyth/oracle.test.ts @@ -29,20 +29,47 @@ describe("pyth-oracle-v1::decode-and-verify-price-feeds mainnet VAAs", () => { it("should succeed handling PNAU mainnet payloads", () => { const vaaBytes = Cl.bufferFromHex(apnuMainnetVaas[0]); - const executionPlan = Cl.tuple({ - 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), - 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pythDecoderPnauContractName), - 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormholeCoreContractName), - }); - + let priceIdentifier = Cl.bufferFromHex('ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17'); + let priceUpdated = Cl.tuple({ + "price-identifier": priceIdentifier, + "conf": Cl.uint(37359), + "ema-conf": Cl.uint(36191), + "ema-price": Cl.int(46167004), + "expo": Cl.int(-8), + "prev-publish-time": Cl.uint(1695751648), + "price": Cl.int(46098556), + "publish-time": Cl.uint(1695751649) + }) + let executionPlan = Cl.tuple({ + 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), + 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pythDecoderPnauContractName), + 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormholeCoreContractName), + }); + let res = simnet.callPublicFn( pythOracleContractName, "verify-and-update-price-feeds", [vaaBytes, executionPlan], sender - ); - - const result = res.result; - console.log(Cl.prettyPrint(result, 2)); + ).result; + expect(res).toBeOk( + Cl.list([priceUpdated]) + ) + + res = simnet.callPublicFn( + pythOracleContractName, + "read-price-feed", + [priceIdentifier, Cl.contractPrincipal(simnet.deployer, pythStorageContractName)], + sender + ).result; + expect(res).toBeOk(Cl.tuple({ + "conf": Cl.uint(37359), + "ema-conf": Cl.uint(36191), + "ema-price": Cl.int(46167004), + "expo": Cl.int(-8), + "prev-publish-time": Cl.uint(1695751648), + "price": Cl.int(46098556), + "publish-time": Cl.uint(1695751649) + })) }); }); From 0f54a3633005fdac49076b1bceea31ba8029a614 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Thu, 19 Oct 2023 18:42:05 -0400 Subject: [PATCH 10/18] test: infrastructure for PNAU generation --- contracts/pyth-pnau-decoder-v1.clar | 2 - contracts/pyth-traits-v1.clar | 1 - unit-tests/pyth/helpers.ts | 342 ++++++++++++++++++ unit-tests/pyth/oracle.test.ts | 2 +- unit-tests/pyth/pnau.test.ts | 92 +++++ unit-tests/utils/cursor.test.ts | 2 +- unit-tests/utils/{helper.ts => helpers.ts} | 17 +- unit-tests/wormhole/{helper.ts => helpers.ts} | 20 +- unit-tests/wormhole/vaa.test.ts | 56 +-- 9 files changed, 488 insertions(+), 46 deletions(-) create mode 100644 unit-tests/pyth/helpers.ts create mode 100644 unit-tests/pyth/pnau.test.ts rename unit-tests/utils/{helper.ts => helpers.ts} (72%) rename unit-tests/wormhole/{helper.ts => helpers.ts} (94%) diff --git a/contracts/pyth-pnau-decoder-v1.clar b/contracts/pyth-pnau-decoder-v1.clar index a647e8c..b65d14a 100644 --- a/contracts/pyth-pnau-decoder-v1.clar +++ b/contracts/pyth-pnau-decoder-v1.clar @@ -60,8 +60,6 @@ (contract-call? .hk-cursor-v1 slice (get next cursor-pnau-vaa) none) (get merkle-root-hash (get value cursor-merkle-root-data))))) (prices-updates (map cast-decoded-price decoded-prices-updates))) - ;; (watched-prices-feeds (var-get watched-price-feeds)) - ;; (updated-prices-feeds (get updated-prices-feeds (fold process-prices-attestations-batch decoded-prices-attestations-batches { input: watched-prices-feeds, updated-prices-feeds: (list) })))) (ok prices-updates))) (define-private (parse-merkle-root-data-from-vaa-payload (payload-vaa-bytes (buff 8192))) diff --git a/contracts/pyth-traits-v1.clar b/contracts/pyth-traits-v1.clar index 7db89dc..0c4caf0 100644 --- a/contracts/pyth-traits-v1.clar +++ b/contracts/pyth-traits-v1.clar @@ -52,7 +52,6 @@ publish-time: uint, prev-publish-time: uint, })) (response uint uint)) - ) ) diff --git a/unit-tests/pyth/helpers.ts b/unit-tests/pyth/helpers.ts new file mode 100644 index 0000000..25bf032 --- /dev/null +++ b/unit-tests/pyth/helpers.ts @@ -0,0 +1,342 @@ +import { fc } from '@fast-check/vitest'; +import { Cl, ClarityValue } from "@stacks/transactions"; +import { bigintToBuffer, bufferToBigint } from '../utils/helpers'; +import * as secp from '@noble/secp256k1'; +import { + keccak_256 +} from '@noble/hashes/sha3'; +import { webcrypto } from 'node:crypto'; +// @ts-ignore +if (!globalThis.crypto) globalThis.crypto = webcrypto; + +import { hmac } from '@noble/hashes/hmac'; +import { sha256 } from '@noble/hashes/sha256'; + +secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) + +export namespace pyth { + + export interface PriceUpdate { + priceIdentifier: Uint8Array, + price: bigint, + conf: bigint, + emaPrice: bigint, + emaConf: bigint, + expo: number, + publishTime: bigint, + prevPublishTime: bigint, + proof: Uint8Array, + } + + export interface PriceUpdateBuildOptions { + price?: bigint, + conf?: bigint, + emaPrice?: bigint, + emaConf?: bigint, + expo?: number, + publishTime?: bigint, + prevPublishTime?: bigint, + } + + export interface AuwvVaaPayload { + payloadType: Uint8Array, + updateType: number, + merkleRootSlot: bigint, + merkleRootRingSize: number, + merkleRootHash: Uint8Array + } + + export interface AuwvVaaPayloadBuildOptions { + payloadType?: Uint8Array, + updateType?: number, + merkleRootSlot?: bigint, + merkleRootRingSize?: number, + merkleRootHash?: Uint8Array + } + + export interface PnauHeader { + magicBytes: Uint8Array, + versionMaj: number, + versionMin: number, + trailingSize: number, + proofType: number, + } + + export interface PnauHeaderBuildOptions { + magicBytes?: Uint8Array, + versionMaj?: number, + versionMin?: number, + trailingSize?: number, + proofType?: number, + } + + export interface PnauBody { + vaa: Uint8Array, + pricesUpdates: PriceUpdateBatch, + } + + export interface PriceUpdateBatch { + decoded: PriceUpdate[], + serialized: Uint8Array[], + hashed: Uint8Array[], + } + + export const BtcPriceIdentifier = Buffer.from('e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43', 'hex') + export const StxPriceIdentifier = Buffer.from('ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17', 'hex') + + export function serializePriceUpdateToClarityValue(priceUpdate: PriceUpdate): ClarityValue { + return Cl.tuple({ + "price-identifier": Cl.buffer(priceUpdate.priceIdentifier), + "conf": Cl.uint(priceUpdate.conf), + "ema-conf": Cl.uint(priceUpdate.emaConf), + "ema-price": Cl.int(priceUpdate.emaPrice), + "expo": Cl.int(priceUpdate.expo), + "prev-publish-time": Cl.uint(priceUpdate.prevPublishTime), + "price": Cl.int(priceUpdate.price), + "publish-time": Cl.uint(priceUpdate.publishTime) + }) + } + + export function preserializePriceUpdateToBuffer(priceUpdate: PriceUpdate): Uint8Array { + const components = []; + components.push(priceUpdate.priceIdentifier); + components.push(bigintToBuffer(priceUpdate.price, 8)); + components.push(bigintToBuffer(priceUpdate.conf, 8)); + let v = Buffer.alloc(4); + v.writeInt32BE(priceUpdate.expo, 0); + components.push(v); + components.push(bigintToBuffer(priceUpdate.publishTime, 8)); + components.push(bigintToBuffer(priceUpdate.prevPublishTime, 8)); + components.push(bigintToBuffer(priceUpdate.emaPrice, 8)); + components.push(bigintToBuffer(priceUpdate.emaConf, 8)); + return Buffer.concat(components); + } + + export function serializePriceUpdateToBuffer(priceUpdate: PriceUpdate, proof: Uint8Array): Uint8Array { + const components = []; + + // Update type + var v = Buffer.alloc(1); + v.writeUint8(1, 0); + components.push(v); + + // Size + let priceUpdateData = preserializePriceUpdateToBuffer(priceUpdate); + let messageSize = priceUpdateData.length; + var v = Buffer.alloc(2); + v.writeUint16BE(messageSize, 0); + components.push(v); + + // Price update data + components.push(priceUpdateData) + + // Proof size + var v = Buffer.alloc(1); + v.writeUint8(proof.length, 0); + components.push(v); + + // Proof + components.push(proof) + return Buffer.concat(components); + } + + export function serializeAuwvVaaPayloadToBuffer(payload: AuwvVaaPayload): Uint8Array { + const components = []; + // Magic bytes ('AUWV') + components.push(payload.payloadType); + // Update type + let v = Buffer.alloc(1); + v.writeUint8(payload.updateType, 0); + components.push(v); + // Merkle root slot + components.push(bigintToBuffer(payload.merkleRootSlot, 8)); + // Merkle ring size + v = Buffer.alloc(4); + v.writeUint16BE(payload.merkleRootRingSize, 0); + components.push(v); + // Merkle root + components.push(payload.merkleRootHash); + return Buffer.concat(components) + } + + export function buildPriceUpdateBatch(pricesUpdatesSpecs: [priceIdentifier: Uint8Array, opts?: PriceUpdateBuildOptions][]): PriceUpdateBatch { + let decoded = []; + let serialized = []; + let hashed = []; + for (let [priceIdentifier, opts] of pricesUpdatesSpecs) { + let p = buildPriceUpdate(priceIdentifier, opts); + let s = preserializePriceUpdateToBuffer(p); + decoded.push(p) + serialized.push(s) + hashed.push(keccak160HashLeaf(s)) + } + return { + decoded, + serialized, + hashed + } + } + + export function buildAuwvVaaPayload(batch: PriceUpdateBatch, merkleRootData?: AuwvVaaPayloadBuildOptions) { + let merkleLeaves = [...batch.hashed]; + // Compute merkle tree + while (merkleLeaves.length > 1) { + let newLeaves = []; + // Loop through adjacent pairs + for (let i = 0; i < merkleLeaves.length; i += 2) { + const leftLeaf = merkleLeaves[i]; + // Duplicate the last one if odd number of leaves + const rightLeaf = merkleLeaves[i + 1] || leftLeaf; + newLeaves.push(keccak160HashNodes(leftLeaf, rightLeaf)); + } + merkleLeaves = newLeaves; + } + + return { + payloadType: merkleRootData?.payloadType || new Uint8Array(Buffer.from('41555756', 'hex')), + updateType: merkleRootData?.updateType || 0, + merkleRootRingSize: merkleRootData?.merkleRootRingSize || 0, + merkleRootSlot: merkleRootData?.merkleRootSlot || 0n, + merkleRootHash: merkleLeaves[0] + } + } + + export function buildPnauHeader(opts?: PnauHeaderBuildOptions) { + return { + magicBytes: opts?.magicBytes || new Uint8Array(Buffer.from('504e4155', 'hex')), + versionMaj: opts?.versionMaj || 1, + versionMin: opts?.versionMin || 0, + trailingSize: opts?.trailingSize || 0, + proofType: opts?.proofType || 0, + } + } + + export function buildPnauBody(serializedVaa: Uint8Array, batch: PriceUpdateBatch): PnauBody { + // let merkleLeaves = [...batch.hashed]; + // // Compute merkle tree + // while (merkleLeaves.length > 1) { + // let newLeaves = []; + // // Loop through adjacent pairs + // for (let i = 0; i < merkleLeaves.length; i += 2) { + // const leftLeaf = merkleLeaves[i]; + // // Duplicate the last one if odd number of leaves + // const rightLeaf = merkleLeaves[i + 1] || leftLeaf; + // newLeaves.push(keccak160HashNodes(leftLeaf, rightLeaf)); + // } + // merkleLeaves = newLeaves; + // } + + // TODO: For each price in batch, build the proof + + return { + vaa: serializedVaa, + pricesUpdates: batch + } + } + + export function serializePnauToBuffer(pnauHeader: PnauHeader, pnauBody: PnauBody) { + const components = []; + // Magic bytes + components.push(pnauHeader.magicBytes); + // Version Maj + let v = Buffer.alloc(1); + v.writeUint8(pnauHeader.versionMaj, 0); + components.push(v); + // Version Min + v = Buffer.alloc(1); + v.writeUint8(pnauHeader.versionMin, 0); + components.push(v); + // Trailing header + v = Buffer.alloc(1); + v.writeUint8(pnauHeader.trailingSize, 0); + components.push(v); + // Proof type + v = Buffer.alloc(1); + v.writeUint8(pnauHeader.proofType, 0); + components.push(v); + // VAA size + v = Buffer.alloc(2); + v.writeUint16BE(pnauBody.vaa.length, 0); + components.push(v); + // Vaa + components.push(pnauBody.vaa); + return Buffer.concat(components) + } + + function keccak160HashNodes(node1: Uint8Array, node2: Uint8Array): Uint8Array { + let prefix = new Uint8Array([1]); + if (bufferToBigint(node2) < bufferToBigint(node1)) { + return keccak_256(Buffer.concat([prefix, node2, node1])).slice(0, 20) + } else { + return keccak_256(Buffer.concat([prefix, node1, node2])).slice(0, 20) + } + } + + function keccak160HashLeaf(leaf: Uint8Array): Uint8Array { + let prefix = new Uint8Array([0]); + return keccak_256(Buffer.concat([prefix, leaf])).slice(0, 20) + } + + export function buildPriceUpdate(priceIdentifier: Uint8Array, opts?: PriceUpdateBuildOptions):PriceUpdate { + return { + priceIdentifier: priceIdentifier, + price: opts?.price || 100n, + conf: opts?.conf || 10n, + emaPrice: opts?.emaPrice || 95n, + emaConf: opts?.emaConf || 9n, + expo: -4, + publishTime: opts?.publishTime || 10000001n, + prevPublishTime: opts?.prevPublishTime || 10000000n, + proof: new Uint8Array(0), + } + } + + export namespace fc_ext { + + export const priceUpdate = (opts?: PriceUpdateBuildOptions) => { + // price + let price = fc.bigIntN(64); + if (opts && opts.price) { + price = fc.constant(opts.price); + } + + // conf + let conf = fc.bigUintN(64); + if (opts && opts.conf) { + conf = fc.constant(opts.conf); + } + + // emaPrice + let emaPrice = fc.bigIntN(64); + if (opts && opts.emaPrice) { + emaPrice = fc.constant(opts.emaPrice); + } + + // emaConf + let emaConf = fc.bigUintN(64); + if (opts && opts.emaConf) { + emaConf = fc.constant(opts.emaConf); + } + + // expo + let expo = fc.nat(4294967295); + if (opts && opts.expo) { + expo = fc.constant(opts.expo); + } + + // prevPublishTime + let prevPublishTime = fc.bigUintN(64); + if (opts && opts.prevPublishTime) { + prevPublishTime = fc.constant(opts.prevPublishTime); + } + + // prevPublishTime + let publishTime = prevPublishTime.chain((t: bigint) => fc.constant(t + 10n)); + if (opts && opts.publishTime) { + publishTime = fc.constant(opts.publishTime); + } + + return fc.tuple(price, conf, emaPrice, emaConf, expo, prevPublishTime, publishTime); + } + } +} diff --git a/unit-tests/pyth/oracle.test.ts b/unit-tests/pyth/oracle.test.ts index 7167452..330f0e1 100644 --- a/unit-tests/pyth/oracle.test.ts +++ b/unit-tests/pyth/oracle.test.ts @@ -2,7 +2,7 @@ import { Cl, ClarityType } from "@stacks/transactions"; import { beforeEach, describe, expect, it } from "vitest"; import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; import { apnuMainnetVaas } from "./fixtures"; -import { wormhole } from "../wormhole/helper"; +import { wormhole } from "../wormhole/helpers"; const pythOracleContractName = "pyth-oracle-v1"; const pythDecoderPnauContractName = "pyth-pnau-decoder-v1"; diff --git a/unit-tests/pyth/pnau.test.ts b/unit-tests/pyth/pnau.test.ts new file mode 100644 index 0000000..0679ea1 --- /dev/null +++ b/unit-tests/pyth/pnau.test.ts @@ -0,0 +1,92 @@ +import { Cl, ClarityType } from "@stacks/transactions"; +import { beforeEach, describe, expect, it } from "vitest"; +import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; +import { apnuMainnetVaas } from "./fixtures"; +import { wormhole } from "../wormhole/helpers"; +import { pyth } from "./helpers"; + +const pythOracleContractName = "pyth-oracle-v1"; +const pythDecoderPnauContractName = "pyth-pnau-decoder-v1"; +const pythStorageContractName = "pyth-store-v1"; +const wormholeCoreContractName = "wormhole-core-v1"; + +describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds success", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const guardianSet = wormhole.generateGuardianSetKeychain(19); + let priceUpdateBatch = pyth.buildPriceUpdateBatch([ + [pyth.BtcPriceIdentifier], + [pyth.StxPriceIdentifier] + ]) + let priceUpdateBatchVaaPayload = pyth.buildAuwvVaaPayload(priceUpdateBatch); + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + wormhole.applyGuardianSetUpdate(guardianSet, 1, sender, wormholeCoreContractName) + }) + + it("should produce a successfully verifiable attestation", () => { + let payload = pyth.serializeAuwvVaaPayloadToBuffer(priceUpdateBatchVaaPayload); + let body = wormhole.buildValidVaaBodySpecs({ payload }); + let header = wormhole.buildValidVaaHeader(guardianSet, body, { version: 1, guardianSetId: 1 }); + let vaa = wormhole.serializeVaaToBuffer(header, body); + + let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet); + const res = simnet.callReadOnlyFn( + wormholeCoreContractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender + ); + expect(res.result).toBeOk(decodedVaa) + }); + + it("should produce a correct verifiable empty vaa", () => { + let payload = pyth.serializeAuwvVaaPayloadToBuffer(priceUpdateBatchVaaPayload); + let vaaBody = wormhole.buildValidVaaBodySpecs({ payload }); + let vaaHeader = wormhole.buildValidVaaHeader(guardianSet, vaaBody, { version: 1, guardianSetId: 1 }); + let vaa = wormhole.serializeVaaToBuffer(vaaHeader, vaaBody); + let pnauHeader = pyth.buildPnauHeader(); + let pnauBody = pyth.buildPnauBody(vaa, priceUpdateBatch); + let pnau = pyth.serializePnauToBuffer(pnauHeader, pnauBody); + + let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(vaaHeader, vaaBody, guardianSet); + + let executionPlan = Cl.tuple({ + 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), + 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pythDecoderPnauContractName), + 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormholeCoreContractName), + }); + const res = simnet.callPublicFn( + pythOracleContractName, + `verify-and-update-price-feeds`, + [Cl.buffer(pnau), executionPlan], + sender + ); + + expect(res.result).toBeErr(decodedVaa) + }); + + + // it("should produced a correct empty vaa", () => { + // let priceUpdateBatch = pyth.buildPriceUpdateBatch([ + // [pyth.BtcPriceIdentifier], + // [pyth.StxPriceIdentifier] + // ]) + // let payload = pyth.buildPnauVaa(priceUpdateBatch) + // let body = wormhole.buildValidVaaBodySpecs({ payload }); + // let header = wormhole.buildValidVaaHeader(guardianSet, body, { version: 1, guardianSetId: 1 }); + // let vaa = wormhole.serializeVaaToBuffer(header, body); + + // let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet); + + // const res = simnet.callReadOnlyFn( + // wormholeCoreContractName, + // `parse-and-verify-vaa`, + // [Cl.buffer(vaa)], + // sender + // ); + // expect(res.result).toBeOk(decodedVaa) + // }); + +}); diff --git a/unit-tests/utils/cursor.test.ts b/unit-tests/utils/cursor.test.ts index c3957fd..072eb1f 100644 --- a/unit-tests/utils/cursor.test.ts +++ b/unit-tests/utils/cursor.test.ts @@ -1,7 +1,7 @@ import { Cl } from "@stacks/transactions"; import { describe, expect } from "vitest"; import { it, fc } from '@fast-check/vitest'; -import { concatTypedArrays, uint8toBytes, uint16toBytes, uint32toBytes, bigintToBuffer } from './helper'; +import { concatTypedArrays, uint8toBytes, uint16toBytes, uint32toBytes, bigintToBuffer } from './helpers'; const cursor_contract_name = "hk-cursor-v1"; diff --git a/unit-tests/utils/helper.ts b/unit-tests/utils/helpers.ts similarity index 72% rename from unit-tests/utils/helper.ts rename to unit-tests/utils/helpers.ts index 52fd2d0..3e41faa 100644 --- a/unit-tests/utils/helper.ts +++ b/unit-tests/utils/helpers.ts @@ -5,19 +5,19 @@ export function concatTypedArrays(a: any, b: any) { return c; } -export const uint8toBytes = (num: number) => { +export function uint8toBytes(num: number) { let b = new ArrayBuffer(1); new DataView(b).setUint8(0, num); return new Uint8Array(b); } -export const uint16toBytes = (num: number) => { +export function uint16toBytes(num: number) { let b = new ArrayBuffer(2); new DataView(b).setUint16(0, num); return new Uint8Array(b); } -export const uint32toBytes = (num: number) => { +export function uint32toBytes(num: number) { let b = new ArrayBuffer(4); new DataView(b).setUint32(0, num); return new Uint8Array(b); @@ -41,3 +41,14 @@ export function bigintToBuffer(bigintValue: bigint, byteLength: number) { return bigintToBuffer(twosComplement, byteLength); } } + +export function bufferToHexString(bytes: Uint8Array) { + return Array.from(bytes, function(byte) { + return ('0' + (byte & 0xFF).toString(16)).slice(-2); + }).join(''); +} + +export function bufferToBigint(bytes: Uint8Array) { + const hexString = bufferToHexString(bytes); + return BigInt('0x' + hexString); +} diff --git a/unit-tests/wormhole/helper.ts b/unit-tests/wormhole/helpers.ts similarity index 94% rename from unit-tests/wormhole/helper.ts rename to unit-tests/wormhole/helpers.ts index 93c0ebd..efbdaa4 100644 --- a/unit-tests/wormhole/helper.ts +++ b/unit-tests/wormhole/helpers.ts @@ -1,7 +1,7 @@ import { fc } from '@fast-check/vitest'; import { tx } from "@hirosystems/clarinet-sdk"; import { Cl, ClarityValue } from "@stacks/transactions"; -import { bigintToBuffer } from '../utils/helper'; +import { bigintToBuffer } from '../utils/helpers'; import * as secp from '@noble/secp256k1'; import { keccak_256 @@ -172,7 +172,7 @@ export namespace wormhole { export const buildValidVaaHeaderSpecs = (keychain: Guardian[], body: VaaBody, opts?: VaaHeaderBuildOptions): VaaHeaderBuildOptions => { let signatures = []; - const messageHash = keccak_256(keccak_256(serializeVaaBody(body))); + const messageHash = keccak_256(keccak_256(serializeVaaBodyToBuffer(body))); for (let guardian of keychain) { const signature = secp.sign(messageHash, guardian.secretKey) @@ -207,7 +207,7 @@ export namespace wormhole { } } - export const expectedDecodedVaa = (header: VaaHeader, body: VaaBody, keychain: Guardian[]): [ClarityValue, any[]] => { + export const serializeVaaToClarityValue = (header: VaaHeader, body: VaaBody, keychain: Guardian[]): [ClarityValue, any[]] => { let guardiansPublicKeys = []; let guardiansSignatures = []; for (let i = 0; i < header.signatures.length; i++) { @@ -281,11 +281,11 @@ export namespace wormhole { } } - export const serializeVaa = (vaaHeader: VaaHeader, vaaBody: VaaBody) => { - return Buffer.concat([serializeVaaHeader(vaaHeader), serializeVaaBody(vaaBody)]); + export const serializeVaaToBuffer = (vaaHeader: VaaHeader, vaaBody: VaaBody) => { + return Buffer.concat([serializeVaaHeaderToBuffer(vaaHeader), serializeVaaBodyToBuffer(vaaBody)]); } - export const serializeVaaHeader = (vaaHeader: VaaHeader) => { + export const serializeVaaHeaderToBuffer = (vaaHeader: VaaHeader) => { const components = []; var v = Buffer.alloc(1); v.writeUint8(vaaHeader.version, 0); @@ -303,7 +303,7 @@ export namespace wormhole { return Buffer.concat(components); } - export const serializeVaaBody = (vaaBody: VaaBody) => { + export const serializeVaaBodyToBuffer = (vaaBody: VaaBody) => { const components = []; let v = Buffer.alloc(4); v.writeUInt32BE(vaaBody.timestamp, 0); @@ -332,7 +332,7 @@ export namespace wormhole { export const validGuardianRotationModule = Buffer.from('00000000000000000000000000000000000000000000000000000000436f7265', 'hex'); - export const buildGuardianRotationVaaPayload = (keyChain: Guardian[], action: number, chain: number, setId: number, module = validGuardianRotationModule) => { + export const serializeGuardianUpdateVaaPayloadToBuffer = (keyChain: Guardian[], action: number, chain: number, setId: number, module = validGuardianRotationModule) => { const components = []; components.push(module); @@ -360,10 +360,10 @@ export namespace wormhole { } export function applyGuardianSetUpdate(keychain: wormhole.Guardian[], guardianSetId: number, txSenderAddress: string, contract_name: string) { - let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, guardianSetId, wormhole.validGuardianRotationModule); + let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 0, guardianSetId, wormhole.validGuardianRotationModule); let vaaBody = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { version: 1, guardianSetId: guardianSetId - 1 }); - let vaa = wormhole.serializeVaa(vaaHeader, vaaBody); + let vaa = wormhole.serializeVaaToBuffer(vaaHeader, vaaBody); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts index d3d6951..e348a1f 100644 --- a/unit-tests/wormhole/vaa.test.ts +++ b/unit-tests/wormhole/vaa.test.ts @@ -1,7 +1,7 @@ import { Cl, ClarityType } from "@stacks/transactions"; import { expect, describe, beforeEach } from "vitest"; import { it, fc } from '@fast-check/vitest'; -import { wormhole } from './helper'; +import { wormhole } from './helpers'; import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; const contractName = "wormhole-core-v1"; @@ -19,8 +19,8 @@ describe("wormhole-core-v1::parse-vaa success", () => { fc.assert(fc.property(wormhole.fc_ext.vaaHeader(headerSpecs, 19), ([version, guardianSetIndex, providedSignatures, generatedSignatures]) => { let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; let header = wormhole.assembleVaaHeader(version, guardianSetIndex, consolidatedSignatures) - let vaa = wormhole.serializeVaa(header, body); - let [decodedVaa, guardiansPublicKeys] = wormhole.expectedDecodedVaa(header, body, keychain); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let [decodedVaa, guardiansPublicKeys] = wormhole.serializeVaaToClarityValue(header, body, keychain); const res = simnet.callReadOnlyFn( contractName, @@ -45,10 +45,10 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the chain is invalid", () => { // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 1, 1, wormhole.validGuardianRotationModule); + let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 1, 1, wormhole.validGuardianRotationModule); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -64,10 +64,10 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the action is invalid", () => { // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 0, 0, 1, wormhole.validGuardianRotationModule); + let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 0, 0, 1, wormhole.validGuardianRotationModule); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -83,10 +83,10 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the set id is invalid", () => { // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 0, wormhole.validGuardianRotationModule); + let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 0, 0, wormhole.validGuardianRotationModule); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -102,10 +102,10 @@ describe("wormhole-core-v1::update-guardians-set failures", () => { it("should fail if the module is invalid", () => { // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(keychain, 2, 0, 1, Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')); + let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 0, 1, Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')); let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); let header = wormhole.buildValidVaaHeader(keychain, body, { version: 1, guardianSetId: 0, signatures: [] }); - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of keychain) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -130,7 +130,7 @@ describe("wormhole-core-v1::update-guardians-set success", () => { // Before starting the test suite, we have to setup the guardian set. beforeEach(async () => { let [result, vaaHeader, vaaBody] = wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName) - let [expectedDecodedVaa, _] = wormhole.expectedDecodedVaa( + let [serializeVaaToClarityValue, _] = wormhole.serializeVaaToClarityValue( vaaHeader as wormhole.VaaHeader, vaaBody as wormhole.VaaBody, guardianSet1Keys); @@ -148,7 +148,7 @@ describe("wormhole-core-v1::update-guardians-set success", () => { 'guardians-eth-addresses': Cl.list(guardianSet1Keys.map((g) => Cl.buffer(g.ethereumAddress))), 'guardians-public-keys': Cl.list(guardianSet1Keys.map((g) => Cl.buffer(g.uncompressedPublicKey))), }), - 'vaa': expectedDecodedVaa + 'vaa': serializeVaaToClarityValue })); }) @@ -171,15 +171,15 @@ describe("wormhole-core-v1::update-guardians-set success", () => { }))) }); // Before performing this test, we need to setup the guardian set - const guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(guardianSet2Keys, 2, 0, 2, wormhole.validGuardianRotationModule); + const guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(guardianSet2Keys, 2, 0, 2, wormhole.validGuardianRotationModule); const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); - const vaa = wormhole.serializeVaa(header, body); + const vaa = wormhole.serializeVaaToBuffer(header, body); const uncompressedPublicKey = []; for (let guardian of guardianSet2Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } - const [expectedDecodedVaa, _] = wormhole.expectedDecodedVaa(header, body, guardianSet1Keys); + const [serializeVaaToClarityValue, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet1Keys); let res = simnet.mineBlock([ tx.callPublicFn( contractName, @@ -193,7 +193,7 @@ describe("wormhole-core-v1::update-guardians-set success", () => { 'guardians-eth-addresses': Cl.list(guardianSet2Keys.map((g) => Cl.buffer(g.ethereumAddress))), 'guardians-public-keys': Cl.list(guardianSet2Keys.map((g) => Cl.buffer(g.uncompressedPublicKey))), }), - 'vaa': expectedDecodedVaa + 'vaa': serializeVaaToClarityValue })); res = simnet.callPublicFn( @@ -208,10 +208,10 @@ describe("wormhole-core-v1::update-guardians-set success", () => { it("should reject subsequent update if there is a eth address / uncompressed public keys mismatch", () => { let guardianSet2KeysSubset = guardianSet2Keys.splice(0, 12); // Before performing this test, we need to setup the guardian set - const guardianRotationPayload = wormhole.buildGuardianRotationVaaPayload(guardianSet2KeysSubset, 2, 0, 2, wormhole.validGuardianRotationModule); + const guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(guardianSet2KeysSubset, 2, 0, 2, wormhole.validGuardianRotationModule); const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); - const vaa = wormhole.serializeVaa(header, body); + const vaa = wormhole.serializeVaaToBuffer(header, body); const uncompressedPublicKey = []; for (let guardian of guardianSet2Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -246,8 +246,8 @@ describe("wormhole-core-v1::parse-and-verify-vaa success", () => { fc.assert(fc.property(wormhole.fc_ext.vaaHeader(headerSpecs, 19), ([version, guardianSetIndex, providedSignatures, generatedSignatures]) => { let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; let header = wormhole.assembleVaaHeader(version, guardianSetIndex, consolidatedSignatures) - let vaa = wormhole.serializeVaa(header, body); - let [decodedVaa, guardiansPublicKeys] = wormhole.expectedDecodedVaa(header, body, guardianSet1Keys); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let [decodedVaa, guardiansPublicKeys] = wormhole.serializeVaaToClarityValue(header, body, guardianSet1Keys); const res = simnet.callReadOnlyFn( contractName, @@ -270,8 +270,8 @@ describe("wormhole-core-v1::parse-and-verify-vaa success", () => { let guardianSet1KeysSubset = guardianSet1Keys.splice(0, cutoff); let body = wormhole.buildValidVaaBodySpecs(); let header = wormhole.buildValidVaaHeader(guardianSet1KeysSubset, body, { version: 1, guardianSetId: 1 }); - let vaa = wormhole.serializeVaa(header, body); - let [decodedVaa, _] = wormhole.expectedDecodedVaa(header, body, guardianSet1KeysSubset); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet1KeysSubset); const res = simnet.callReadOnlyFn( contractName, @@ -297,7 +297,7 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { // Before performing this test, we need to setup the guardian set let body = wormhole.buildValidVaaBodySpecs(); let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 2, guardianSetId: 1 }); - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of guardianSet1Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -315,7 +315,7 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { // Before performing this test, we need to setup the guardian set let body = wormhole.buildValidVaaBodySpecs(); let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 2 }); - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of guardianSet1Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -337,7 +337,7 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { for (let i = 0; i < header.signatures.length; i++) { header.signatures[i] = header.signatures[0]; } - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of guardianSet1Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -357,7 +357,7 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); // Override signatures header.signatures = header.signatures.slice(0, 12); - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of guardianSet1Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); @@ -380,7 +380,7 @@ describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { for (let i = cutoff; i < header.signatures.length; i++) { header.signatures[i] = header.signatures[0]; } - let vaa = wormhole.serializeVaa(header, body); + let vaa = wormhole.serializeVaaToBuffer(header, body); let uncompressedPublicKey = []; for (let guardian of guardianSet1Keys) { uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); From 589cde91b75b61441986136b8018d417228fec4b Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Thu, 19 Oct 2023 18:44:44 -0400 Subject: [PATCH 11/18] fix: ts warnings --- unit-tests/pyth/oracle.test.ts | 2 +- unit-tests/pyth/pnau.test.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/unit-tests/pyth/oracle.test.ts b/unit-tests/pyth/oracle.test.ts index 330f0e1..9a41272 100644 --- a/unit-tests/pyth/oracle.test.ts +++ b/unit-tests/pyth/oracle.test.ts @@ -1,6 +1,6 @@ import { Cl, ClarityType } from "@stacks/transactions"; import { beforeEach, describe, expect, it } from "vitest"; -import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; +import { ParsedTransactionResult } from "@hirosystems/clarinet-sdk"; import { apnuMainnetVaas } from "./fixtures"; import { wormhole } from "../wormhole/helpers"; diff --git a/unit-tests/pyth/pnau.test.ts b/unit-tests/pyth/pnau.test.ts index 0679ea1..ee43bc8 100644 --- a/unit-tests/pyth/pnau.test.ts +++ b/unit-tests/pyth/pnau.test.ts @@ -1,7 +1,5 @@ -import { Cl, ClarityType } from "@stacks/transactions"; +import { Cl } from "@stacks/transactions"; import { beforeEach, describe, expect, it } from "vitest"; -import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; -import { apnuMainnetVaas } from "./fixtures"; import { wormhole } from "../wormhole/helpers"; import { pyth } from "./helpers"; From af794e5fe92773fb1452de72cdcd46431106ec7d Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 20 Oct 2023 01:14:23 -0400 Subject: [PATCH 12/18] fix: cursor - return buff instead of panic --- contracts/hiro-kit/hk-cursor-v1.clar | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/hiro-kit/hk-cursor-v1.clar b/contracts/hiro-kit/hk-cursor-v1.clar index 85eae10..32cdf77 100644 --- a/contracts/hiro-kit/hk-cursor-v1.clar +++ b/contracts/hiro-kit/hk-cursor-v1.clar @@ -125,9 +125,9 @@ { bytes: (get bytes cursor), pos: (+ (get pos cursor) offset) }) (define-read-only (slice (cursor { bytes: (buff 8192), pos: uint }) (size (optional uint))) - (unwrap-panic (slice? - (get bytes cursor) - (get pos cursor) - (match size value - (+ (get pos cursor) value) - (len (get bytes cursor)))))) + (match (slice? (get bytes cursor) + (get pos cursor) + (match size value + (+ (get pos cursor) value) + (len (get bytes cursor)))) + bytes bytes 0x)) From 24ea872e36e1f071e03b7f35a6b92c33368c8662 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 20 Oct 2023 01:14:38 -0400 Subject: [PATCH 13/18] feat: tweak error codes --- contracts/pyth-governance-v1.clar | 4 ++-- contracts/pyth-oracle-v1.clar | 5 +++-- contracts/pyth-pnau-decoder-v1.clar | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/pyth-governance-v1.clar b/contracts/pyth-governance-v1.clar index 911a4f2..6e4a282 100644 --- a/contracts/pyth-governance-v1.clar +++ b/contracts/pyth-governance-v1.clar @@ -92,7 +92,7 @@ ;; Ensure that wormhole contract is the one expected (try! (expect-active-wormhole-contract (get wormhole-core-contract execution-plan) expected-execution-plan))) false))))) - (if success (ok true) (err u0)))) + (if success (ok true) ERR_UNAUTHORIZED_ACCESS))) (define-read-only (check-storage-contract (storage-contract )) @@ -111,7 +111,7 @@ (begin (asserts! (is-eq former-contract-caller (get pyth-oracle-contract expected-plan)) - (err u0)) + ERR_UNAUTHORIZED_ACCESS) (ok true))) (define-private (expect-active-storage-contract diff --git a/contracts/pyth-oracle-v1.clar b/contracts/pyth-oracle-v1.clar index 313f942..0725de4 100644 --- a/contracts/pyth-oracle-v1.clar +++ b/contracts/pyth-oracle-v1.clar @@ -37,9 +37,10 @@ (pyth-storage-contract (get pyth-storage-contract execution-plan)) (prices-updates (try! (contract-call? pyth-decoder-contract decode-and-verify-price-feeds price-feed-bytes wormhole-core-contract))) (fee-info (contract-call? .pyth-governance-v1 get-fee-info)) - (fee-amount (* (get mantissa fee-info) (pow u10 (get exponent fee-info))))) + (fee-amount (+ u1 ;; Dust fee + (* (len prices-updates) (* (get mantissa fee-info) (pow u10 (get exponent fee-info))))))) ;; Charge fee - (unwrap! (stx-transfer? (* (len prices-updates) fee-amount) tx-sender (get address fee-info)) (err u0)) + (unwrap! (stx-transfer? fee-amount tx-sender (get address fee-info)) ERR_BALANCE_INSUFFICIENT) ;; Update storage (try! (contract-call? pyth-storage-contract write-batch prices-updates)) (ok prices-updates)))) diff --git a/contracts/pyth-pnau-decoder-v1.clar b/contracts/pyth-pnau-decoder-v1.clar index b65d14a..ebdc31b 100644 --- a/contracts/pyth-pnau-decoder-v1.clar +++ b/contracts/pyth-pnau-decoder-v1.clar @@ -12,7 +12,6 @@ ;; Price Feeds Ids (https://pyth.network/developers/price-feed-ids#pyth-evm-mainnet) (define-constant STX_USD 0xec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17) (define-constant BTC_USD 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43) -(define-constant ETH_USD 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace) (define-constant PNAU_MAGIC 0x504e4155) ;; 'PNAU': Pyth Network Accumulator Update (define-constant AUWV_MAGIC 0x41555756) ;; 'AUWV': Accumulator Update Wormhole Verficiation @@ -76,7 +75,7 @@ ;; Check payload type (asserts! (is-eq (get value cursor-payload-type) AUWV_MAGIC) ERR_MAGIC_BYTES) ;; Check update type - (asserts! (is-eq (get value cursor-wh-update-type) u0) (err u999)) + (asserts! (is-eq (get value cursor-wh-update-type) u0) ERR_PROOF_TYPE) (ok { value: { merkle-root-slot: (get value cursor-merkle-root-slot), From 657bba8a9773273c1fafcc9a50121eb3dc7650e0 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 20 Oct 2023 01:15:42 -0400 Subject: [PATCH 14/18] fix: parsing offset calculations --- contracts/pyth-pnau-decoder-v1.clar | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/contracts/pyth-pnau-decoder-v1.clar b/contracts/pyth-pnau-decoder-v1.clar index ebdc31b..d5aa3de 100644 --- a/contracts/pyth-pnau-decoder-v1.clar +++ b/contracts/pyth-pnau-decoder-v1.clar @@ -204,7 +204,7 @@ (cursor-ema-conf (unwrap-panic (contract-call? .hk-cursor-v1 read-uint-64 (get next cursor-ema-price)))) (cursor-proof (contract-call? .hk-cursor-v1 advance (get next cursor-message-size) (get value cursor-message-size))) (cursor-proof-size (unwrap-panic (contract-call? .hk-cursor-v1 read-uint-8 cursor-proof))) - (proof-bytes (contract-call? .hk-cursor-v1 slice (get next cursor-proof-size) none)) + (proof-bytes (contract-call? .hk-cursor-v1 slice (get next cursor-proof-size) (some (* u20 (get value cursor-proof-size))))) (leaf-bytes (contract-call? .hk-cursor-v1 slice (get next cursor-message-size) (some (get value cursor-message-size)))) (proof (get result (fold parse-proof proof-bytes { result: (list), @@ -218,7 +218,13 @@ { cursor: { index: (+ (get index (get cursor acc)) u1), - next-update-index: (+ (get index (get cursor acc)) (+ (get pos (get next cursor-proof-size)) (get value cursor-proof-size))), + next-update-index: + (+ + (get index (get cursor acc)) + u2 + (get value cursor-message-size) + u1 + (* (get value cursor-proof-size) u20)), }, bytes: (get bytes acc), result: (unwrap-panic (as-max-len? (append (get result acc) { From 9b652d4289db38f4af597394dadeb95cfad81d83 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 20 Oct 2023 01:23:11 -0400 Subject: [PATCH 15/18] test: progress on decoder's coverage - 100% --- unit-tests/pyth/helpers.ts | 114 +++++++++++++++++++-------------- unit-tests/pyth/oracle.test.ts | 14 ++-- unit-tests/pyth/pnau.test.ts | 30 +++++---- 3 files changed, 91 insertions(+), 67 deletions(-) diff --git a/unit-tests/pyth/helpers.ts b/unit-tests/pyth/helpers.ts index 25bf032..cf84c7d 100644 --- a/unit-tests/pyth/helpers.ts +++ b/unit-tests/pyth/helpers.ts @@ -25,7 +25,6 @@ export namespace pyth { expo: number, publishTime: bigint, prevPublishTime: bigint, - proof: Uint8Array, } export interface PriceUpdateBuildOptions { @@ -73,16 +72,24 @@ export namespace pyth { export interface PnauBody { vaa: Uint8Array, pricesUpdates: PriceUpdateBatch, + pricesUpdatesToSubmit: Uint8Array[] } export interface PriceUpdateBatch { decoded: PriceUpdate[], serialized: Uint8Array[], hashed: Uint8Array[], + proofs: Uint8Array[][], } export const BtcPriceIdentifier = Buffer.from('e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43', 'hex') export const StxPriceIdentifier = Buffer.from('ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17', 'hex') + export const BatPriceIdentifer = Buffer.from('8e860fb74e60e5736b455d82f60b3728049c348e94961add5f961b02fdee2535', 'hex') + export const DaiPriceIdentifer = Buffer.from('b0948a5e5313200c632b51bb5ca32f6de0d36e9950a942d19751e833f70dabfd', 'hex') + export const UsdcPriceIdentifer = Buffer.from('eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a', 'hex') + export const UsdtPriceIdentifer = Buffer.from('2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b', 'hex') + export const WbtcPriceIdentifer = Buffer.from('c9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33', 'hex') + export const TbtcPriceIdentifer = Buffer.from('56a3121958b01f99fdc4e1fd01e81050602c7ace3a571918bb55c6a96657cca9', 'hex') export function serializePriceUpdateToClarityValue(priceUpdate: PriceUpdate): ClarityValue { return Cl.tuple({ @@ -99,10 +106,14 @@ export namespace pyth { export function preserializePriceUpdateToBuffer(priceUpdate: PriceUpdate): Uint8Array { const components = []; + // Update type + var v = Buffer.alloc(1); + v.writeUint8(0, 0); + components.push(v); components.push(priceUpdate.priceIdentifier); components.push(bigintToBuffer(priceUpdate.price, 8)); components.push(bigintToBuffer(priceUpdate.conf, 8)); - let v = Buffer.alloc(4); + v = Buffer.alloc(4); v.writeInt32BE(priceUpdate.expo, 0); components.push(v); components.push(bigintToBuffer(priceUpdate.publishTime, 8)); @@ -112,31 +123,21 @@ export namespace pyth { return Buffer.concat(components); } - export function serializePriceUpdateToBuffer(priceUpdate: PriceUpdate, proof: Uint8Array): Uint8Array { + export function serializePriceUpdateToBuffer(priceUpdateData: Uint8Array, proof: Uint8Array[]): Uint8Array { const components = []; - - // Update type - var v = Buffer.alloc(1); - v.writeUint8(1, 0); - components.push(v); - // Size - let priceUpdateData = preserializePriceUpdateToBuffer(priceUpdate); let messageSize = priceUpdateData.length; var v = Buffer.alloc(2); v.writeUint16BE(messageSize, 0); components.push(v); - // Price update data components.push(priceUpdateData) - // Proof size var v = Buffer.alloc(1); v.writeUint8(proof.length, 0); components.push(v); - // Proof - components.push(proof) + components.push(...proof) return Buffer.concat(components); } @@ -170,11 +171,16 @@ export namespace pyth { serialized.push(s) hashed.push(keccak160HashLeaf(s)) } + let proofs = []; + for (let hash of hashed) { + proofs.push(computeMerkleProof(hash, hashed)) + } return { decoded, serialized, - hashed - } + hashed, + proofs + } } export function buildAuwvVaaPayload(batch: PriceUpdateBatch, merkleRootData?: AuwvVaaPayloadBuildOptions) { @@ -184,10 +190,10 @@ export namespace pyth { let newLeaves = []; // Loop through adjacent pairs for (let i = 0; i < merkleLeaves.length; i += 2) { - const leftLeaf = merkleLeaves[i]; - // Duplicate the last one if odd number of leaves - const rightLeaf = merkleLeaves[i + 1] || leftLeaf; - newLeaves.push(keccak160HashNodes(leftLeaf, rightLeaf)); + const leftLeaf = merkleLeaves[i]; + // Duplicate the last one if odd number of leaves + const rightLeaf = merkleLeaves[i + 1] || leftLeaf; + newLeaves.push(keccak160HashNodes(leftLeaf, rightLeaf)); } merkleLeaves = newLeaves; } @@ -211,29 +217,6 @@ export namespace pyth { } } - export function buildPnauBody(serializedVaa: Uint8Array, batch: PriceUpdateBatch): PnauBody { - // let merkleLeaves = [...batch.hashed]; - // // Compute merkle tree - // while (merkleLeaves.length > 1) { - // let newLeaves = []; - // // Loop through adjacent pairs - // for (let i = 0; i < merkleLeaves.length; i += 2) { - // const leftLeaf = merkleLeaves[i]; - // // Duplicate the last one if odd number of leaves - // const rightLeaf = merkleLeaves[i + 1] || leftLeaf; - // newLeaves.push(keccak160HashNodes(leftLeaf, rightLeaf)); - // } - // merkleLeaves = newLeaves; - // } - - // TODO: For each price in batch, build the proof - - return { - vaa: serializedVaa, - pricesUpdates: batch - } - } - export function serializePnauToBuffer(pnauHeader: PnauHeader, pnauBody: PnauBody) { const components = []; // Magic bytes @@ -260,6 +243,17 @@ export namespace pyth { components.push(v); // Vaa components.push(pnauBody.vaa); + // Number of prices updates + v = Buffer.alloc(1); + v.writeUint8(pnauBody.pricesUpdatesToSubmit.length, 0); + components.push(v); + // Loop on prices updates + for (let i = 0; i < pnauBody.pricesUpdates.serialized.length; i++) { + if (pnauBody.pricesUpdatesToSubmit.includes(pnauBody.pricesUpdates.decoded[i].priceIdentifier)) { + let priceUpdateData = pnauBody.pricesUpdates.serialized[i]; + components.push(serializePriceUpdateToBuffer(priceUpdateData, pnauBody.pricesUpdates.proofs[i])) + } + } return Buffer.concat(components) } @@ -277,7 +271,34 @@ export namespace pyth { return keccak_256(Buffer.concat([prefix, leaf])).slice(0, 20) } - export function buildPriceUpdate(priceIdentifier: Uint8Array, opts?: PriceUpdateBuildOptions):PriceUpdate { + function computeMerkleProof(targetLeaf: Uint8Array, batch: Uint8Array[]): Uint8Array[] { + let merkleLeaves = [...batch]; + const proof = []; + let targetHash = targetLeaf; + + while (merkleLeaves.length > 1) { + let newLeaves: Uint8Array[] = []; + for (let i = 0; i < merkleLeaves.length; i += 2) { + const leftLeaf = merkleLeaves[i]; + const rightLeaf = merkleLeaves[i + 1] || leftLeaf; // Duplicate the last one if odd number of leaves + const parentNode = keccak160HashNodes(leftLeaf, rightLeaf); + newLeaves.push(parentNode); + + // Capture the sibling for the proof if either leftLeaf or rightLeaf is the target + if (leftLeaf === targetHash) { + proof.push(rightLeaf); + targetHash = parentNode; + } else if (rightLeaf === targetHash) { + proof.push(leftLeaf); + targetHash = parentNode; + } + } + merkleLeaves = newLeaves; + } + return proof; + } + + export function buildPriceUpdate(priceIdentifier: Uint8Array, opts?: PriceUpdateBuildOptions): PriceUpdate { return { priceIdentifier: priceIdentifier, price: opts?.price || 100n, @@ -287,7 +308,6 @@ export namespace pyth { expo: -4, publishTime: opts?.publishTime || 10000001n, prevPublishTime: opts?.prevPublishTime || 10000000n, - proof: new Uint8Array(0), } } @@ -331,11 +351,11 @@ export namespace pyth { } // prevPublishTime - let publishTime = prevPublishTime.chain((t: bigint) => fc.constant(t + 10n)); + let publishTime = prevPublishTime.chain((t: bigint) => fc.constant(t + 10n)); if (opts && opts.publishTime) { publishTime = fc.constant(opts.publishTime); } - + return fc.tuple(price, conf, emaPrice, emaConf, expo, prevPublishTime, publishTime); } } diff --git a/unit-tests/pyth/oracle.test.ts b/unit-tests/pyth/oracle.test.ts index 9a41272..223aaa6 100644 --- a/unit-tests/pyth/oracle.test.ts +++ b/unit-tests/pyth/oracle.test.ts @@ -1,7 +1,7 @@ import { Cl, ClarityType } from "@stacks/transactions"; import { beforeEach, describe, expect, it } from "vitest"; import { ParsedTransactionResult } from "@hirosystems/clarinet-sdk"; -import { apnuMainnetVaas } from "./fixtures"; +import { pnauMainnetVaas } from "./fixtures"; import { wormhole } from "../wormhole/helpers"; const pythOracleContractName = "pyth-oracle-v1"; @@ -28,7 +28,7 @@ describe("pyth-oracle-v1::decode-and-verify-price-feeds mainnet VAAs", () => { }); it("should succeed handling PNAU mainnet payloads", () => { - const vaaBytes = Cl.bufferFromHex(apnuMainnetVaas[0]); + const pnauBytes = Cl.bufferFromHex(pnauMainnetVaas[0]); let priceIdentifier = Cl.bufferFromHex('ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17'); let priceUpdated = Cl.tuple({ "price-identifier": priceIdentifier, @@ -44,13 +44,13 @@ describe("pyth-oracle-v1::decode-and-verify-price-feeds mainnet VAAs", () => { 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pythDecoderPnauContractName), 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormholeCoreContractName), - }); - + }); + let res = simnet.callPublicFn( pythOracleContractName, - "verify-and-update-price-feeds", - [vaaBytes, executionPlan], - sender + "verify-and-update-price-feeds", + [pnauBytes, executionPlan], + sender ).result; expect(res).toBeOk( Cl.list([priceUpdated]) diff --git a/unit-tests/pyth/pnau.test.ts b/unit-tests/pyth/pnau.test.ts index ee43bc8..fd3f3a4 100644 --- a/unit-tests/pyth/pnau.test.ts +++ b/unit-tests/pyth/pnau.test.ts @@ -1,4 +1,4 @@ -import { Cl } from "@stacks/transactions"; +import { Cl, ClarityType } from "@stacks/transactions"; import { beforeEach, describe, expect, it } from "vitest"; import { wormhole } from "../wormhole/helpers"; import { pyth } from "./helpers"; @@ -12,19 +12,26 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds success", () => { const accounts = simnet.getAccounts(); const sender = accounts.get("wallet_1")!; const guardianSet = wormhole.generateGuardianSetKeychain(19); - let priceUpdateBatch = pyth.buildPriceUpdateBatch([ + let pricesUpdates = pyth.buildPriceUpdateBatch([ [pyth.BtcPriceIdentifier], - [pyth.StxPriceIdentifier] + [pyth.StxPriceIdentifier], + [pyth.BatPriceIdentifer], + [pyth.DaiPriceIdentifer], + [pyth.TbtcPriceIdentifer], + [pyth.UsdcPriceIdentifer], + [pyth.UsdtPriceIdentifer], + [pyth.WbtcPriceIdentifer] ]) - let priceUpdateBatchVaaPayload = pyth.buildAuwvVaaPayload(priceUpdateBatch); + let pricesUpdatesToSubmit = [pyth.BtcPriceIdentifier, pyth.StxPriceIdentifier, pyth.UsdcPriceIdentifer]; + let pricesUpdatesVaaPayload = pyth.buildAuwvVaaPayload(pricesUpdates); // Before starting the test suite, we have to setup the guardian set. beforeEach(async () => { wormhole.applyGuardianSetUpdate(guardianSet, 1, sender, wormholeCoreContractName) }) - it("should produce a successfully verifiable attestation", () => { - let payload = pyth.serializeAuwvVaaPayloadToBuffer(priceUpdateBatchVaaPayload); + it("should parse and verify the Vaa a a Pnau message", () => { + let payload = pyth.serializeAuwvVaaPayloadToBuffer(pricesUpdatesVaaPayload); let body = wormhole.buildValidVaaBodySpecs({ payload }); let header = wormhole.buildValidVaaHeader(guardianSet, body, { version: 1, guardianSetId: 1 }); let vaa = wormhole.serializeVaaToBuffer(header, body); @@ -40,15 +47,12 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds success", () => { }); it("should produce a correct verifiable empty vaa", () => { - let payload = pyth.serializeAuwvVaaPayloadToBuffer(priceUpdateBatchVaaPayload); + let payload = pyth.serializeAuwvVaaPayloadToBuffer(pricesUpdatesVaaPayload); let vaaBody = wormhole.buildValidVaaBodySpecs({ payload }); let vaaHeader = wormhole.buildValidVaaHeader(guardianSet, vaaBody, { version: 1, guardianSetId: 1 }); let vaa = wormhole.serializeVaaToBuffer(vaaHeader, vaaBody); let pnauHeader = pyth.buildPnauHeader(); - let pnauBody = pyth.buildPnauBody(vaa, priceUpdateBatch); - let pnau = pyth.serializePnauToBuffer(pnauHeader, pnauBody); - - let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(vaaHeader, vaaBody, guardianSet); + let pnau = pyth.serializePnauToBuffer(pnauHeader, { vaa, pricesUpdates, pricesUpdatesToSubmit }); let executionPlan = Cl.tuple({ 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), @@ -57,12 +61,12 @@ describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds success", () => { }); const res = simnet.callPublicFn( pythOracleContractName, - `verify-and-update-price-feeds`, + "verify-and-update-price-feeds", [Cl.buffer(pnau), executionPlan], sender ); - expect(res.result).toBeErr(decodedVaa) + expect(res.result).toHaveClarityType(ClarityType.ResponseOk); }); From 0fc0294aad83d795b41699c64f20f9a8a5f95098 Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 20 Oct 2023 09:52:53 -0400 Subject: [PATCH 16/18] feat: update store trait, fix test --- contracts/pyth-oracle-v1.clar | 2 +- contracts/pyth-store-v1.clar | 17 +---------------- contracts/pyth-traits-v1.clar | 12 +----------- unit-tests/pyth/fixtures.ts | 2 +- 4 files changed, 4 insertions(+), 29 deletions(-) diff --git a/contracts/pyth-oracle-v1.clar b/contracts/pyth-oracle-v1.clar index 0725de4..ffbb837 100644 --- a/contracts/pyth-oracle-v1.clar +++ b/contracts/pyth-oracle-v1.clar @@ -42,5 +42,5 @@ ;; Charge fee (unwrap! (stx-transfer? fee-amount tx-sender (get address fee-info)) ERR_BALANCE_INSUFFICIENT) ;; Update storage - (try! (contract-call? pyth-storage-contract write-batch prices-updates)) + (try! (contract-call? pyth-storage-contract write prices-updates)) (ok prices-updates)))) diff --git a/contracts/pyth-store-v1.clar b/contracts/pyth-store-v1.clar index 29c0c69..2b83ea4 100644 --- a/contracts/pyth-store-v1.clar +++ b/contracts/pyth-store-v1.clar @@ -21,22 +21,7 @@ (let ((entry (unwrap! (map-get? prices price-identifier) (err u404)))) (ok entry))) -(define-public (write (price-identifier (buff 32)) (data { - price: int, - conf: uint, - expo: int, - ema-price: int, - ema-conf: uint, - publish-time: uint, - prev-publish-time: uint, - })) - (begin - ;; Ensure that updates are always coming from the right contract - (try! (contract-call? .pyth-governance-v1 check-execution-flow contract-caller none)) - ;; Update storage - (ok (write-update price-identifier data)))) - -(define-public (write-batch (batch-updates (list 64 { +(define-public (write (batch-updates (list 64 { price-identifier: (buff 32), price: int, conf: uint, diff --git a/contracts/pyth-traits-v1.clar b/contracts/pyth-traits-v1.clar index 0c4caf0..10f353b 100644 --- a/contracts/pyth-traits-v1.clar +++ b/contracts/pyth-traits-v1.clar @@ -32,17 +32,7 @@ prev-publish-time: uint, } uint)) - (write ((buff 32) { - price: int, - conf: uint, - expo: int, - ema-price: int, - ema-conf: uint, - publish-time: uint, - prev-publish-time: uint, - }) (response bool uint)) - - (write-batch ((list 64 { + (write ((list 64 { price-identifier: (buff 32), price: int, conf: uint, diff --git a/unit-tests/pyth/fixtures.ts b/unit-tests/pyth/fixtures.ts index 1b415fa..4c8a3de 100644 --- a/unit-tests/pyth/fixtures.ts +++ b/unit-tests/pyth/fixtures.ts @@ -22,6 +22,6 @@ export const p2whTestnetVaas = [ "01000000000100970313d82d9bf3fcfdaf8ed0b38c45cec7bad7093624ef5e7651cabfff706bc469f69951334e8555dbbbed3b1c0ceb3e4e9a65ab6558bd3655249dc7ee74d60500648b652500000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf2220150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580ea12ea000000000154e016fffffff80000000577ec9eac0000000001851c6c01000000010000000200000000648b652500000000648b652400000000648b65220000000580ea12ea000000000154e01600000000648b65246a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002718d17970000000000cf0b8efffffff8000000026c5719f80000000000c27cea01000000010000000200000000648b652500000000648b652400000000648b652300000002718fc1fb0000000000e21d8500000000648b652428fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f23ee6d200000000077306ce7fffffff80000024b93e12a900000000064bfcfde01000000010000000200000000648b652500000000648b652400000000648b65230000024f23ee6d20000000007aaf1ef700000000648b65248b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aaac0a0000000000014abefffffff800000000039cd2dd00000000000130b001000000010000000200000000648b652500000000648b652400000000648b65230000000003aac2380000000000012ec800000000648b65243b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005df7350000000000001794fffffff800000000005d7673000000000000151201000000010000000200000000648b652500000000648b652400000000648b652300000000005dfa98000000000000119400000000648b6523", ]; -export const apnuMainnetVaas = [ +export const pnauMainnetVaas = [ "504e41550100000003b801000000030d002c9e33a703a8c86012c117474849ae41118270cf56e2db61c7af28f64ce322391d33cb738930dc3ffcfe5ae35dbd99c4e08a8ac4b390c858d94dc1f0b91e24d00101901948561c54c17b7a21a9885d3f60a85b410e18209b02ebe098b0dd422b02667565e3b5011203303a6445cf629d20cec3d4771f0b391ab03bfd54364ac049f70002a030ed0060f1ba123ddf5473bef4c1c8f4aec8737b47f11023f94d5339b72c4c3aa325515956ea702537a3950d74f86134396de437bfc97a5365fc090dbb382c0103f1f64ba806fe8ba7b7db3846d85fccbe7d803865e7b757301f4a5de6ebde40f923ee925a7df78871d39299d70fd117635263b91fe53f792bfff450d210431316000439e31501d7b661a1b075a9302b57a6fc500c1e760de7e949e30994055b061b280cefcab3eb3b5c780f56cd555e88ee3c674b798ddeb8713bd27a361b8b8f1c730008db908c61e4158d1a7a7d53b2ec14027a4a2b2f3207edf0db2fbb0f431f3cdc020ee9fc9f38e812d094dcf5dd18f3a8d1d1ee94e840f504b00e08d0fbd607ed6d0109cf5c52aed9f4a42691c3c33bc366fde4a3ad23aacdd46c14669ad321106cf8d257b9379fc45cbe91a26ee87f4800951c5ca89ff0069e4a47299f988b368c6b74000b1241dea623dd874e13135682d760ee48ac1888149bddcfac54ff3787f4b89629063d89e084350ff1168999365918f9bdf469323b817a25491e56341a0da5c498000d538baecc668df092ce3cfeebfa5968447584916cb503ce50e7f77443bbd2a3441bb1d962a7f3401bfcf30f444a3301ab72dcb87cc04b681bdada93d45001f53d010e2fd97eeac94ceb3fd87a6e4ab3b785c64db221d522e58230a6d9eb10cba3b53a086ba6989e4285ab0816a26ff64c1a3a05175d0ffa61f4f744f928d6c5b3f92600107f999fa2f5cd826520cdf36f2e3434f4619eca4c2fb944bdec79d84903f76aaa02727844ec451378ab165910140c866ebdc65aea2cc6717da4747d4ed8a7338700115ce8f97f4a39ccdf5a61c9425d8a306a35b598db9d0f936bece6f2d92e79f9b1306d4c4789fb73ba5bd29bf0d306f2e8079aae818bc70aa9db111b745f1f4ab900126933d7f1317e3e45d98687e66ae033d871e1026e92f1ad168ec197a92ad653571837b78ee8e2263f2aca1dfa112fc78de63de9e2231f62ab79db2d956704146c0165131de200000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000000df92280141555756000000000005e1b46c00002710dbdd79b598d499596393b24358343774fd54e63c01005500ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c170000000002bf687c00000000000091effffffff80000000065131de10000000065131de00000000002c073dc0000000000008d5f09014cff5b7c5413efc667d2bca5549edadde5324814fed82e330d4110d610f5d0325deddb5541eacccd70da0a613768335768226657b201a456613163b0b4a21cf17276c2be6d02280ff05d08a6d4cf481c00c544f4adff1c86c6845a21871b75b4cffdc9314a2b46bddcb2bd44861fa48d4cc429a0783cb122a3eda67b7a13ad6298995c3bddd6cfa1ba547ddd38f0da18fcb0f7ead4d1de56db6c0442b94883a78f40b3541982a58d2d400dbfb761cce7bf780c", ]; From 9bf00b08f25f5e5a9b0662810b1ce60022f9ff4f Mon Sep 17 00:00:00 2001 From: Hugo Caillard <911307+hugocaillard@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:30:10 +0100 Subject: [PATCH 17/18] chore: apply prettier --- .prettierrc | 3 + package-lock.json | 18 +- package.json | 6 +- unit-tests/pyth/fixtures.ts | 38 +- unit-tests/pyth/helpers.ts | 764 ++++++++------- unit-tests/pyth/oracle.test.ts | 127 +-- unit-tests/pyth/pnau.test.ts | 172 ++-- unit-tests/utils/cursor.test.ts | 1242 +++++++++++++----------- unit-tests/utils/helpers.ts | 66 +- unit-tests/utils/merkle-proofs.test.ts | 350 ++++--- unit-tests/wormhole/fixtures.ts | 144 +-- unit-tests/wormhole/helpers.ts | 899 +++++++++-------- unit-tests/wormhole/vaa.test.ts | 925 +++++++++++------- 13 files changed, 2629 insertions(+), 2125 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..bf357fb --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "trailingComma": "all" +} diff --git a/package-lock.json b/package-lock.json index c7e1852..eef980a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,8 @@ "@fast-check/vitest": "^0.0.8", "@vitest/ui": "^0.34.6", "chokidar-cli": "^3.0.0", - "fast-check": "^3.13.1" + "fast-check": "^3.13.1", + "prettier": "^3.0.3" } }, "node_modules/@esbuild/android-arm": { @@ -1569,6 +1570,21 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-format": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", diff --git a/package.json b/package.json index e23224a..bc05fcc 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "scripts": { "test": "vitest run", "test:watch": "chokidar \"tests/**/*.ts\" \"contracts/**/*.clar\" -c \"npm run test:coverage\"", - "test:coverage": "vitest run -- --coverage true" + "test:coverage": "vitest run -- --coverage true", + "format": "prettier ./unit-tests --write" }, "author": "", "license": "ISC", @@ -26,6 +27,7 @@ "@fast-check/vitest": "^0.0.8", "@vitest/ui": "^0.34.6", "chokidar-cli": "^3.0.0", - "fast-check": "^3.13.1" + "fast-check": "^3.13.1", + "prettier": "^3.0.3" } } diff --git a/unit-tests/pyth/fixtures.ts b/unit-tests/pyth/fixtures.ts index 4c8a3de..235a8ed 100644 --- a/unit-tests/pyth/fixtures.ts +++ b/unit-tests/pyth/fixtures.ts @@ -1,27 +1,27 @@ export const p2whMainnetVaas = [ - "01000000030d005b26a5ad9468407b98767abd7148f6d1c02a316b27001c1443a7a104928835e651ea5ef173752cfaa4859db565e16ab3ad6a28bb3793ace209dbad04c5ca7a7001016456ac96ec1439907736135b04b3cac5eeacba3c0d30503f97034ff84301ace93063120f56ed92c7a9707aa2016ae3f9dca4963596de87ba5983b840f796319e010233265dc219325dcff680fc5b632ada2dc5249ef437af22a386bc5caaadd1c66f5c2f7fe418dff397ca8609b3fe0d9b4a9ae08f2923e58095254ce7fbf717811b0103f11b0aec9c6265eea4e666ee5e3b1bf12236439dba0978dc8af738731ea81c416d4a5ca5e99344f95ad9e0e11a0ba7b5210bc4e9c13315251ecd8f2e2e6c006300049faaab5a536bad6e33dc63a643a2a0179744d50ab45e3e005e61d05a09bfb99a7fd3b35e71c8c18e9e45023425e7b145ece170b6872ff1b523664643f531fa01000a104723c46b41d6088675bbbc66704e49592cafd73ced3397d784c7b1ea917e1136a3dbae246ef9ad686a177bd021e82632e379f48e077fba86fb53330de8fcb1010b005daa1a9f81d27e0a8819f30e67d27a5f43e1fb10dfbbc65124007cb464ee331b9597ed11138fed943285fea688207e3132dd974498b76262f35abf3f908928000da0bfde1eb247b3fdeda6282ecbd0aeae0d75383bce4c54c18520d35d515c763517c24bdd9afb6b5f5f00812bc10f86cf98c051415dbd6a0eb6fe31067d06d386000e6782f1714277a13cf2dc13494b503e809ad015b1a78164a4e54bd3e68a2f164568bdfbb1b1a4f220e0b4d15448fe3adfd1fcda48b91869f3fa39f9d537d271d0010feb0b88a3a60bf1e9a451f7aa8839e493b6d919bb30762d1218b777e68cbb48e34f3f1f67b6ccb355bf11f96d1237c7aa8057b5bc7aab2bb1bf0986a23e93349801109063ab9b968b6498d27b4aba8b4c9d5b65f0b1ffc7bb9778868c141436f35820359a0c4935a71cf2bd49d14fbe95f0eb4e9462425a7be5b6188787f5e430b493001152ae6fe0525a7564aa1cb4e8447abc27336d11ff25f7cfb1cd67616b7e30a27a215119da7531947dde0ed4efbca877e824bc203c40f5c8d9bbe7a705155478d20112f10d3eae21de379d4a091bd4162965ac21b3c51aec144be676e803cf78f4846c172d35a17c86dce051f42c1233c69ffa1986a1ee34eafde1a22e5ccd0e42b18d01648b5aad00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd3110150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f0000000573acaada00000000015d02adfffffff8000000056e343a28000000000181eb6201000000170000001b00000000648b5aad00000000648b5aad00000000648b5aac0000000573acaada00000000015d02ad00000000648b5aac48d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a600fd80000000000cb227afffffff80000000268e1b6fc0000000000c37400010000001b0000002000000000648b5aad00000000648b5aad00000000648b5aac000000026a600fd80000000000cb227a00000000648b5aac3515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000249acd54e7e000000007b0d5582fffffff800000247ba887a800000000070c4e666010000001a0000001e00000000648b5aad00000000648b5aad00000000648b5aac00000249acd54e7e000000007b0d558200000000648b5aac9b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae80000000003922a7d00000000000155d3fffffff800000000038e900a0000000000012a51010000000f0000001100000000648b5aad00000000648b5aad00000000648b5aac0000000003924d74000000000001385800000000648b5aace876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d46e800000000000015f4fffffff800000000005cef1c0000000000001543010000001b0000002000000000648b5aad00000000648b5aad00000000648b5aac00000000005d46e800000000000015f400000000648b5aac", - "01000000030d0076f4e3badfe434e501f376a24f84d4636082b6a8fb21bbf5cc8ee7c3a42b2dcc640c4f77027a935aa08ed030b409447452af7fcead6ec24549c03e552b9ed3b201018feee678abc925d9a758445d3efb7a7e58314d166f736f2ce5ec7567ec00295e1307b803dc74d1b567014d27ce5bcc7968c91f3d035abbe8b398afa3334d6a540003a18e6686ea73ada65799e9e0297ee0b00139e04703a6c67449c0b3918fa92c4274fab0f56180f1d9ee1ddbe76acf40937016955addf6e76e8e94aae502d4c0f60004961dc7f51e657961c94a632702e929d12434fb2fb80438f1b1e90a5fefa8d5f102a7c70046652b761a173c78de351587284b378abbadbcb2a44a031f3a3c78b00109375fdcf1472e853682cd7b80ff78818568bea166e752b4cd2334a1f725d2d0c85498ec27055c1a43b6717673c556bf7c4d78efb102d5372a4b009687ab45b9cc010a72426aa3045b64127bf0e765aff8644cceeecb9739fa19acb2d2b0936c4517932882fe0fec0217ad794def938580c15e46459183e723135332d0876ea998b05e010bb32a6f4cf571e8a1f4efeeb9bf97e5fa1399d6f5faaf9c243c5fe3746ffa79f27bda866de8a9ef18052c24d37c355bdc41b9750a94fe339fa4608e58e143991a010cce634e42ea68664dcb59717d58150f032b82654ccf1ef08fd9fd17703d68b7ca5609a8d1f883ca5f2d82eca4ef2fe97f933d76ae28b30295869deba9440af1d8000df19654675ba8bbc6d178a460c353ae9cca7895dd1cfe638882ffcc45d6046dfb6880a6a61d039e1906e5ce87e1e0275a551a74069c6b78544996ed56adc66385000f260c57ebd6f2e526e386436ac2eef3b0305d0de52e28ed876894783129c75f5721be2a30dc9478316b7efd5815c4690350a59ec178990188e4d1cfd525c62a7e00101178b0215e4f6f29fc54924a3bfa123d4037760c22e98cb29b3e2f2a61b3b00f66482446f177b0808ec735558b3488510f06ce5a2dad7839934006ce33015f6a001186b81e3dbeb05e0c31ba68e20e18092375bc62824034758411fb13cecc4bc2c5564ff7404fe8528367a6b06d40bebb3456e897dfbbbb220fcf85b59c3d2c052501129ef799c5a009006015f28f83191cbfe662cf2f1d86c91a286ec048fe6040869515e6fbd4301fc8c3ee31a27e7d5b6b1b95875d17f6f21368ef015837ce582c3401648b5aaa00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd2df0150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5aaa00000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5aaa00000000648b5aaa000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5aaa00000000648b5aaa00000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5aaa00000000648b5aaa0000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5aaa00000000648b5aaa0000000000000000000000000000000000000000000000000000000000000000", - "01000000030d00221ee8e172ff8113223b83cc7eedf280b1dcacc37df5a024116d1a940b12fb5b76bb2535dfbd8af8c38239514381ae37cf2062aefdb41b196dd5c6dcfc94361d01021ba85a74a8a60e7ebfe20721b8e733d2fb853af98684b5c193cc3f9b20c757ad651326d6b135a4838b113adaae462f387c3c2afb249f1f882c770903495e8b4701033b9d2e19502e2f415cef825c15c3f6cc9de749564587ffa23bae53bbadf0676e3dec7e3bccca30c69d76300ef07849cc70677d5d5dcabbea61b912bf91ca7d290004f9c224e447d697229753d9fb5bc617762f7a5248e1fca13ac605a5371f19ddc46268331d4c2d197d80db0cf6c2b7f9203eb08c193dfd928018c0592c227f4cd8010839b7e8f6b4a7e7a2bfd058742a0e237f4d2bfb768c9df35fada8a6ddf18e04d05fbddc91cc53d429fbc2265836885e4ac251c3cad49a94816cf2e09bb394c627000a770a381d7368b7e8abb51fc619f0c56c0fdd261f99ce553d3160f2a669e7f27043d3b30e53c8a2b40ef7f11d8140b201bbeaf4bc03c5b6d9655292a71bd1e423010be6b61ad60af4e8fec918ba4a2ca11fcb7cf94035a54a0ced906ae7c2e77ae17d6cdeac4a511a2127da22869694a9c5804d3cd5eca4cfd39daa587091f85b9d98010cdc14d6ff68b64a99b09774fdbf30509f8767fd42ac511b08dc68e53d6a6eea875c53d272274e20f7fae47f6ce386a53c6c1c18243ec0ea2c6b29074b9b14df57000ddf03bb2a85f8c7dd7d4c771cdf503e294a0f2c92d3dad87dcdeb0b9455f16405167f5042b5dbe12e553570f43fa86eb720ce2fe70927b9c5e7bba70bcd2583ce010f62c6b4a1a0ca48542d35238caf9ea9fa3f6265dc81072773cf5bac9349c4fb5f179fb0cf7f31e73918894d43af0ca359d2458fdae2484271bc72c0766a2c42b90110ec732ead991211c67e1c44e92d37666abe7a24a788f9a0619bb536529b243251522ec65e847b9640c36c8336e8e4ede3f7e0718c1b60a0c15afdccf9ce0ecad90111106ce16dc68177e4a3daa28218b97f132f6361beaecfca4431227910fcd91e8e02473a424114696d21d7cbd3b5287cf83c4a6e60b7f158b2a39112ab37197c59011241b470bcd3d560fefee3ad56c1478813ae54ee75afb1ffd608e8e550c6ee1e2c6ccbd3cff9b56580b7ed5d36794876559aabe5dc06bfaca16322bf71d891b18701648b5ab900000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd4140150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000057358408000000000019c682cfffffff8000000056e39ad78000000000181e6fb01000000170000001b00000000648b5ab900000000648b5ab800000000648b5ab8000000057358408000000000019c682c00000000648b5ab848d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a02c1200000000000af79e0fffffff80000000268e319ac0000000000c36fb4010000001b0000002000000000648b5ab900000000648b5ab900000000648b5ab8000000026a02c1200000000000af79e000000000648b5ab83515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000002498cb46220000000007c363a70fffffff800000247bc41ea500000000070d2a8aa01000000190000001e00000000648b5ab900000000648b5ab900000000648b5ab8000002498d5dbae2000000007b8ce1ae00000000648b5ab89b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae8000000000391efa300000000000102e3fffffff800000000038e941e0000000000012a15010000000f0000001100000000648b5ab900000000648b5ab800000000648b5ab8000000000391efa300000000000102e300000000648b5ab8e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d42220000000000001272fffffff800000000005cef740000000000001542010000001b0000002000000000648b5ab900000000648b5ab900000000648b5ab800000000005d436d000000000000119c00000000648b5ab8", - "01000000030d008ab5f79662dcfaad986e71d9fd8b8af862353fbc84f6bfe13bd41cb01d407bfb1c21bc8464b7919257216ad80c84b5a884253cd1cdb8be5bce072db961d786dd00014662da52a53ad933f432f6b5df809a33ffdf41a8ca9c03b69b543e83d772b18b341a70952ffa79b7132e018665b5324953e81169fe918285363350447910b96a01022b65866f375006eba69a3fdc5d68519771c2ba1b7bf0df117d4a164b4390ffaa13c36f647db1ba1c4e2856c846cd5ecd1a6d52d79d3fcdc29ffcf8912bb8a8b60103455afa6aaad3830d2de7b5c78754f6fad20136f6cf16a63ec566ec7e21f5ec1414722357823158589025e17c1a3a1c8f4f4801a5713bbc0f0ab42b5dabef29f300041ec4cd4ca407fc5fd80c1a60cd0ecdef3a12fabe71ace1e1d2babb8594694f4143b536ac74061b81535d597c59dbe98e771888823a6b2c2ec7e4a5efe63116e60108d4eded020cce69cfaee4d72020a46bfbf913fbec2092a6da1dfc1777976aef4c32c840cdc23de32ecd4fe7dc6a5b0178fcc471b63fefd1e299cf65eb678dd28c010af6f5eaf19e7acabb087301ce2c2c93699b9fb4e3e8932a7dcce9c100d0250a45303042f939c5cf880df3770cfae74fbbbccf9ac00fcffd3344e76af9b2122e89010b10789636a4204b59ff0f6700915a39e3b185e2f2b36e13898579a10ceb61bddb2237dd311665b0938962b0b02e38a434931c9102dab69108494ffb8e6c6f86e4000c7c3784becffef400d3d21b52ff6d7f18123200f0059f7147a3a1f39f3f602ee31c5549e5058b5f00d0f9225f79a1a53160a01beb6dd1bf58186325f293a816fc000d389231ce8f5ffddfc4dcb198f64aab2426d3820b2c0496966f115867abdb5ba200e7a81701e49c8a66a7f1e646fd97d3a20b5931cebd39d564f0704898410372000fe702cd49563343b099894c4036ae43d99a48aefb25726037acd4072cca092d9f52144d22f66739f85bfec1dcca4ffbc2b160f216e897c9b329ff6f179cf3bb5e0111d73b1e9cdc60348a5524ba4ba619049e381a38a9b800bc578b0d64981db8eb405dc5b4ef4d97d9b31e3b717de5f485e3d384fc7e9a1fadcad971fe26bfb747070112780e544d00cf6181147f2bc67eb908943581952960d9a40f9063162000e958d460dcae60a5720857423b57113ae0a045d5f8755129b09c4643a58b7ff47b090a00648b5ab900000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd40f0150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5ab900000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5ab900000000648b5ab8000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5ab900000000648b5ab800000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5ab900000000648b5ab80000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5ab900000000648b5ab80000000000000000000000000000000000000000000000000000000000000000", - "01000000030d00efe9314b41350a515cdb08b4c0055e9a8e7748a106d3c01439205afd8a22502a635122818cd5ae89eab68906bbc4f3d296eda7183aafdd8c73179315a2fafa920101b769624886bbf5d33f54ffe56d7cd233ad58f52c365e613d87864b08c039cc9b47dadfeff561d68b4ab18f37163a35567119d5ca9311a2d640cfc4df96025f420002db2a89cb70c6048a8f545b4ef163eb9d99c84b0a064031d4d70a1ef13194800c533d0171135b781a7237aaed56b5721c86aeaf04683017cde445721a0429176a0103db7ab0abdca5c30d695ac34fbdbb8bbf9dfdb85874871739c94f72de788ba730210688624727b9b41aadf571d8e9e06b3de1074adaebd6d247010c9f22f6637801040588a0cc61f3a2781fa9b31fb297e0fbf27cc59c5634fb66434faec64b7573ba4e4cb0ea17fba1daba1f05e70c5cd80600b257e489d4cd8f73a87a9027077876000a88efeb0348264fd12f7ab0365cc963a303252b7e69182384e7c8f1c5b70484a027bedccd826b7288e16a4462980ad68e239ed17ef3604e3adfc92d86d38c3bf2000bc53bc144339a737cb953f63f89664c3389bb00fb70bfaa77701848207aada1ed2349e0058c0cd72bd0ec63c74b2f6bc6dca4953c0fbac25584ec14e0b7ade0fd000c9c97ff3e448175f23cbf63dee6547f285fc990cf46682845f70a670540856a5064d52ead743cd8beac07ad8216f1a91677d9792c6d3cec5e8f19ac65ad030b14000d1c38365533858ebe968c50e14a3fc500b6e19c385993a887f2d54766ccfec7976283dd2c894e8c8ba6361380e73c7929d6580d96cf6cfd460844f8be089fed2c010fac714c0b78f42322992261052bd3d13fbb5fdd89ea7c43218fa61dc3c05384d53e6edd7833b15e821b18944e1ef724fc57e2a073b121f00c1ff81f6cea0df44f0110188bccd5de8e1c602f1a75d5363b99c56c78de421bc56691d871fed8ea45716a39e14bb3eee6021b933c13700eaff0bf320767cbbb324f0bea02a82363fe68dd0011b52b49d13bfc2b95c178f57c2e06e2f189edcc4611d6c16f43fbbd9065d12531598a48e1725d87e14ed5ae79abb27fda10c88d9b6fa238afd89477eadeaa504600123feb36283522a03e8bceca34cbd0f73ff775ba3344e6635e949c88c3c7a550956d90b04ec0fee272b48e3842b15321ab8bd264993e9d75ab54e524dbe564f1ea00648b5ac500000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd5160150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f0000000572aed43c0000000001ad3236fffffff8000000056e3df018000000000181fdf001000000170000001b00000000648b5ac500000000648b5ac400000000648b5ac40000000572aed43c0000000001ad323600000000648b5ac448d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a30000000269ea264d0000000000c89bb1fffffff80000000268e428600000000000c36501010000001b0000002000000000648b5ac500000000648b5ac400000000648b5ac40000000269f2a49d0000000000c01d6100000000648b5ac43515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000249634be7cb00000000823b16b7fffffff800000247bdba59800000000070d9aae2010000001a0000001e00000000648b5ac500000000648b5ac400000000648b5ac400000249634be7cb00000000823b16b700000000648b5ac49b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae8000000000391f86b0000000000010c0bfffffff800000000038e97bd00000000000129e4010000000f0000001100000000648b5ac500000000648b5ac400000000648b5ac4000000000391f86b0000000000010c0b00000000648b5ac4e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d3c330000000000001285fffffff800000000005cefc7000000000000153e010000001b0000002000000000648b5ac500000000648b5ac400000000648b5ac400000000005d3c33000000000000136300000000648b5ac4", - "01000000030d00a8d61b80eb325c2b34be51f5e02d6bc28a31c290fc2158d970310c7b8b9354ef27d956ba2d01a349bdf60699d5ef946edeaafc1f5021713e8ab0fdc8a8a9b30b000213e8596ab484000d8b23df322782ab2c2b53414b8a0f726f74e00907567861dd60c7df8ab13fb11cfc52e0ccdbdb47e646ab999c9436414198b99c746f0f022c0103483e492d5d0be7d84f9fbb7a6ec84dd3c3a98583219c185aa65148cdcfa06abc2149d445383cfe177f3e3f34550c7f790328e110bbb91889671b9084e229eab60104563134e3deba6c6dc998aafbc0d41a2877b339e1e3e89d261a43838b35ddfc7831236a76e96652e3f950cf936d1a5d190f205b52f6c96c32998d5d4cc6e9a244010a7aa3abd9a41221db19540d6f3cfdead2a7791f97b6501e1e7a20daa719bf571c7608ffac59c4a40ecd5cdbf68d3d7909d52eb4eee4b99edb3bc7532e33ef747a000be4fa3030ac6e16df839260912300e32abefde86d0d6cb1cbd2da8618f68a8aa055a082b91a96ccbad7a3dbe91c1a9ea408782161d75be03d3d7946bbc0ddcbdd000c99d8a7785ab49118e62c3798cc776455d75e6cafdebdf1cacf74c3b35349493255727db49d158b1c4ff6d599c59e065a8787fc6a28a32592c952f6323e521edb010d9a0d2314f5579279f4cd22d9276bac88e1da3065fdbbe797e6aea426c391eda859e8d2af1456c0d63c763c20dc7d29b95f30586d228b2cf7416077f415c8ae8f000e6520ab9d85bec828b20b9285ff3a4b42d0e8a16355949379592fe87ffe9faa2809ac052817d28f03a64c99cb28d2f7eb14cbceebda3a44ad9ab9c334e0bf6ea0000f350c592188634248d13dffdcd01ad79f35ea400635f3a8fcd43141cd3d22e36425cf8e2072caec9e4a1c6c8d652c8c80db13a3de99a47aab2d993426e9457fa201103e13dd0e3842cae5700354ff5014a408e5aae26c3058eb448b7e6126c9ddef89105fa08de28c390d92e8a57a1cb73ba12c673a287476c9fe6ba22f7fcc7ff252001170801ceed606f2b4b67756c2181d8335a433456416a3eb3348a91f1c39da627971fa4630f88ae22f46e6aec8654c1877c5fb24e6d9f3046b852a2339cb9b7d410012a85acc87e227180d7b9291009a2dc4846f3c5f17f476a9616ef908f07338eb457ed3bf996c63be891a9dc48f1d7294a9d87eb12ee91566a86e061442d71f7ac900648b5ac300000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd4f50150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5ac300000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5ac300000000648b5ac3000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5ac300000000648b5ac300000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5ac300000000648b5ac30000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5ac300000000648b5ac30000000000000000000000000000000000000000000000000000000000000000", - "01000000030d00f178d45b9d3ad2d9fa81ed97486b98df8e42a92036b20564dc6dbdf2c7f9981c799cf4eb5def8d00d4722f1feb812840b4f16109331e18dcebb1359c2a31db710101b2ea2219eca61627b59e21562e26af975d47958822ea1932d0b9f9e79ac2878434721b8ca1fc72012a70b1aaddc551cfb141cd367576927a32597e0293ae364f0002f84de3103f961565b76516cb14f57624a98bbc34f2bb5b4c1bb87d927d84056b04e18d6b103322e960a7f9fca8ed29747467ee245f3e89d748221c96c1c7907b000367f957ea177cc69c3c805bd7ae2428fbeb0ada04f496284976cef795a278deb95d6cb1108a07ce78a04225ec78ee242f67abfdf46b8fb9412cb1db8ce70b449b00046cc31156255356d335dab76df9c07b749b3490edab45dfb579408aca756b6c25088d357f90fad7a6d720cbe8cb741ab0367cb0dff13bff42a171ca6d02c0c5c7000855bc88c18b4146ab7da3543075d3b70e0ecaafb5b3169b4834e3024825e6811148ee751e5b962c6f8366f0e225912ee79a60e4fac0a3be0fae8140076af2f9fc010af8805ed0b86b796634735b53c6bcfb6e65e167ad5de1cf9bbfef7f21d03747a80d4b9ba9397e5ac352f7b8e4f208300a0b3fc57dfff5a0d7d6d90a95c1c0ee01010bf55b74837e453b7435f8a033bc6af8a63f932f8c88c10011102e917add7b4a651c772ce350b7ec5c924023b41fdd0ebbe0b1ed28338859dcf498f54a7698c93a000c19683a41f5a6ea4ce9c6b0ae7469d06ec64deb6f15947532e804dc4af4422c3e73cd74740b7dd52cde6491e90b56eb8c03f5326dd7713d20484b1a060e1b5419000dfa7329fa30a534b8b245d8a6cbc8d22862f05211cbab68c995fc6df451f02c757476e23031c13cc234d910fb3c1d1d8bcdbe97b94369e0f4d3c71d6ddd2238b7010e49d90fc4ea8f1d0d845d2765cd1efdf1d3764a7800bae6513f3441dd5d20151f10bc49bc08290b89178df30c8f74f8cdb876515dfb6a6c128e9c59e5cf241c950110ad8e69b349a9c94b7370259418731f774d6676d76c2c77685bdefa07d1a85b1520698b8de553d173fe0d32902eca18b84a75300205f7d316f7abc1c0622bd6e40012fc85d1186428bc1a6ce793e8dc1df41df30369df2103b5f116b4bf435de14a5c05d2bd233fb631190a05447804895eff891fed7a28693255b2e129e3bcfa492b00648b5ad100000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd6120150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f0000000572b152200000000001aab452fffffff8000000056e421508000000000182124201000000170000001b00000000648b5ad100000000648b5ad000000000648b5ad00000000572b152200000000001aab45200000000648b5ad048d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a072c600000000000ab0ea0fffffff80000000268e545880000000000c35949010000001b0000002000000000648b5ad100000000648b5ad000000000648b5ad0000000026a072c600000000000ab0ea000000000648b5ad03515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000249707b58c0000000007707cd40fffffff800000247bf4e18e00000000070de4b1a010000001a0000001e00000000648b5ad100000000648b5ad000000000648b5ad000000249707b58c000000000745595ec00000000648b5ad09b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae80000000003931e320000000000012c1efffffff800000000038e9ba700000000000129e7010000000f0000001100000000648b5ad100000000648b5ad100000000648b5ad0000000000392c7eb000000000001826500000000648b5ad0e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d37ac000000000000136ffffffff800000000005cf013000000000000153d010000001b0000002000000000648b5ad100000000648b5ad100000000648b5ad000000000005d3749000000000000137800000000648b5ad0", - "01000000030d0035a94926588f79b1dfce264c6915258819fa68de2f969d04e38c447d6c03523835519ca606093c83cf8e5b891da5f5765eef06c6fefdcf440112ed65ac5ef36601013e9b1d7d2ad19b560214ae65efa3bdf9d003df3f5d905d3b52de10c728d3402e5f4a169e090db71ee1c418c5e403b46d09808901020326c9540b2ee2f869b55900025d9e0c873f6503c9440f8f6959c11b5785395fd85dde27c8b3dd30c4cff51e672ea9457b2eb233e19a08bacba40fbc4334cbe0140e4d9856bd367d627b13c9b10003db1b6d886b7d80bfcedd9b719ec71ce7b5aff03fb5a0c754190d431df6d7bd615786f99a63f7974edccdeb6686e5ec50234bbc6f785cbc115d847b8676e48c880104878ec42ad0c4430f0b298e426d1f0c12194d37a05f3f166fd8ae09da3f2ea3b326a5f3974fe23180b9cc4a0a229278798dfe5e6084942f887047f6199d0db1ca010a92bffd4605ad6066ec532758e599722b4eba5b89536c4025b25b3404834a45d204f8b6a34724b066810768683a794a9782d7145b73e068fc07e237fcd7a10a08010b27cfdf49b87e744421c4577f87a18d5a68860fa600ed1f2dfb55a8bb21dfbbf26da8d18c9062be4527edd0d01028ac8384e51ae1405a4c51a3d3ba98f96ef510010d9919bfbec41e683fe4a41d52aba2bbdf666c7328452287237001be6d169133df3a499ee8e3b3183aabf780734c0df917767a2be5b75a2129a47e66ff9fbbf839000eb5d28032e4ee8bfd376a976b65ede1e8053add16d63a1955620b7383299870585906efbc4becd3cbcd66de307f8bac1c9f740aaa181cf91d445ba1708c974941000fcd5a3f67c7851db812ee7ff227d0dfd4f4c38aa083b7476a68b92853384267a84d771fd87af6a36237480aefa610f92b42e62b1f489c1bf1889376c4a34d70060010da9559f4ca3a7b2c4fba904650996303135af2cd9c32f822706336c95de490216feea8f47a3ed8e821c8a9102a6fc32a4a6cdb5fdcc552b857828e46d230d4600011c4ea1eeb4fa9a174cb7b7b768a4ce53f148bbaec5e80d570731dd9bb91cb245f30f6dafbdb59e0367321e8a5ed7005ea965ef0a4fd4c709f222ca9d36d5f71d70012ec3184202a29a92ecfc80790df895dee37a3b62ba509fd191599784233d31d72090961d5515f30f10f206c61e51786f9a237bfafc192ef91e53485c27a1568a100648b5ace00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd5d40150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5ace00000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5ace00000000648b5acd000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5ace00000000648b5acd00000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5ace00000000648b5acd0000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5ace00000000648b5acd0000000000000000000000000000000000000000000000000000000000000000", - "01000000030d00ad5cfb79320e3f1c3d0929ac96b7db51ab7dfa5813ea8f1a8d4614dae430c7b041115f958622235a4af5e8e8513ed4acc1e3640610e6d433252ed01fcdc9338301011c653dda631cc45adb2b422382c11fd87b724f97a05ce3ec4dc038390f77fa5108c2132e6d5375e90e03aa8988223d167a2dee3286073cf7d3eeab468f3f8a0a0102ae87fa528ba7881a716223f356f3a246b1138c8a94bb5a8be8c1dea235909f716e7fe41d6d8014928895a2ac88b0481135eaad04f57bceaf51e64b46786496bf0003de49e30959d737517d6bb17ef6a2c8475ccb356db080f2fff222b4e2b4b77c9b6505052eea218353ade471d90f519fc84dc12ea4574429eaf50023ea5401ee1e00048ca6369ea9b87e8474e120c6093d9378bb0720e8114f57e52aac992c82627f864f9432bf0f1edbe3302d0abc27887c1b889b4fa81e013bc0eb14193f4e96854c010831be80ded7a9eed3cfec2f5da4cc7a88f823a06fa543e8d9aa2ada382f758a41173f2057c0287521c5378067cbc98f61b0f31bcbe500894dd5c8540cc5f3ab6e0109055ce3838a27258c67028aba8ac3f4289c33b4e6e371bded5f9404d07ff422306b0fc4dc022d36968e18e4922461baafb322ccaae359ef69f11c733cf13a1100000adf759df8103523f9e812373a43d4a02ff0c05b9cc2407cb38724df5be554738b180fabd96a730de0ceed327f8ba6cf4b78dbd0751f6270dd02b1087d2f7aa140010b8c0d22f8251f9b7b2e1e5df5e0521224b186a4c1f0776b62ca880f1c4447db22258b794aec2c5e6b5845a1bbe8b67c615465a6b56ef6da1830bd84f243cfe005010d247aee3d0d0dab3e33b317e2611d6e52df34bd5abf5a0cdd4937455832b1b0634b289ad4f96ea2946550c7dc94808dcfbdfad982f26827008c398b2e613ff728010e2162d1f86a6fe71314353527f64ba7397685bdc789cc7ebd50680625834ad2c554e5c57c26561774d528419a710b40f3a030fc46ac70f1dd79a39200a64215c3010f352d1943fea3821e3d07f53843ea7dc650d65a9eda692a26d5b8c691c3b7f1c44b4d3d1570525de2fdda4960c9fec5779b5cf6640dab19b13cac5477dcf3529b0112a0d821a4592cd3f4d8f071547efb6e6bb2500f4c8ef751943ab64c72c7a86bee38c82caad5d30edf9d981a21614e5cb8a1032e9a40f07a1db32345cb8b328c9a01648b5add00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd72f0150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000057246017800000000019230f0fffffff8000000056e462864000000000182251c01000000170000001b00000000648b5add00000000648b5add00000000648b5adc000000057246017800000000019230f000000000648b5adc48d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a03186a0000000000b24171fffffff80000000268e6624c0000000000c34c3d010000001b0000002000000000648b5add00000000648b5add00000000648b5adc000000026a01e6920000000000b0546e00000000648b5adc3515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b430000024969e00abd000000007d0a84c3fffffff800000247c0dccf300000000070e4cda0010000001a0000001e00000000648b5add00000000648b5add00000000648b5adc000002496be5d2df000000007b04bca100000000648b5adc9b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae800000000039338e00000000000011170fffffff800000000038e9fbe00000000000129ed010000000f0000001100000000648b5add00000000648b5add00000000648b5adc00000000039338e0000000000001117000000000648b5adbe876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d3222000000000000133bfffffff800000000005cf05b000000000000153a010000001b0000002000000000648b5add00000000648b5add00000000648b5adc00000000005d3222000000000000133b00000000648b5adc", + "01000000030d005b26a5ad9468407b98767abd7148f6d1c02a316b27001c1443a7a104928835e651ea5ef173752cfaa4859db565e16ab3ad6a28bb3793ace209dbad04c5ca7a7001016456ac96ec1439907736135b04b3cac5eeacba3c0d30503f97034ff84301ace93063120f56ed92c7a9707aa2016ae3f9dca4963596de87ba5983b840f796319e010233265dc219325dcff680fc5b632ada2dc5249ef437af22a386bc5caaadd1c66f5c2f7fe418dff397ca8609b3fe0d9b4a9ae08f2923e58095254ce7fbf717811b0103f11b0aec9c6265eea4e666ee5e3b1bf12236439dba0978dc8af738731ea81c416d4a5ca5e99344f95ad9e0e11a0ba7b5210bc4e9c13315251ecd8f2e2e6c006300049faaab5a536bad6e33dc63a643a2a0179744d50ab45e3e005e61d05a09bfb99a7fd3b35e71c8c18e9e45023425e7b145ece170b6872ff1b523664643f531fa01000a104723c46b41d6088675bbbc66704e49592cafd73ced3397d784c7b1ea917e1136a3dbae246ef9ad686a177bd021e82632e379f48e077fba86fb53330de8fcb1010b005daa1a9f81d27e0a8819f30e67d27a5f43e1fb10dfbbc65124007cb464ee331b9597ed11138fed943285fea688207e3132dd974498b76262f35abf3f908928000da0bfde1eb247b3fdeda6282ecbd0aeae0d75383bce4c54c18520d35d515c763517c24bdd9afb6b5f5f00812bc10f86cf98c051415dbd6a0eb6fe31067d06d386000e6782f1714277a13cf2dc13494b503e809ad015b1a78164a4e54bd3e68a2f164568bdfbb1b1a4f220e0b4d15448fe3adfd1fcda48b91869f3fa39f9d537d271d0010feb0b88a3a60bf1e9a451f7aa8839e493b6d919bb30762d1218b777e68cbb48e34f3f1f67b6ccb355bf11f96d1237c7aa8057b5bc7aab2bb1bf0986a23e93349801109063ab9b968b6498d27b4aba8b4c9d5b65f0b1ffc7bb9778868c141436f35820359a0c4935a71cf2bd49d14fbe95f0eb4e9462425a7be5b6188787f5e430b493001152ae6fe0525a7564aa1cb4e8447abc27336d11ff25f7cfb1cd67616b7e30a27a215119da7531947dde0ed4efbca877e824bc203c40f5c8d9bbe7a705155478d20112f10d3eae21de379d4a091bd4162965ac21b3c51aec144be676e803cf78f4846c172d35a17c86dce051f42c1233c69ffa1986a1ee34eafde1a22e5ccd0e42b18d01648b5aad00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd3110150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f0000000573acaada00000000015d02adfffffff8000000056e343a28000000000181eb6201000000170000001b00000000648b5aad00000000648b5aad00000000648b5aac0000000573acaada00000000015d02ad00000000648b5aac48d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a600fd80000000000cb227afffffff80000000268e1b6fc0000000000c37400010000001b0000002000000000648b5aad00000000648b5aad00000000648b5aac000000026a600fd80000000000cb227a00000000648b5aac3515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000249acd54e7e000000007b0d5582fffffff800000247ba887a800000000070c4e666010000001a0000001e00000000648b5aad00000000648b5aad00000000648b5aac00000249acd54e7e000000007b0d558200000000648b5aac9b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae80000000003922a7d00000000000155d3fffffff800000000038e900a0000000000012a51010000000f0000001100000000648b5aad00000000648b5aad00000000648b5aac0000000003924d74000000000001385800000000648b5aace876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d46e800000000000015f4fffffff800000000005cef1c0000000000001543010000001b0000002000000000648b5aad00000000648b5aad00000000648b5aac00000000005d46e800000000000015f400000000648b5aac", + "01000000030d0076f4e3badfe434e501f376a24f84d4636082b6a8fb21bbf5cc8ee7c3a42b2dcc640c4f77027a935aa08ed030b409447452af7fcead6ec24549c03e552b9ed3b201018feee678abc925d9a758445d3efb7a7e58314d166f736f2ce5ec7567ec00295e1307b803dc74d1b567014d27ce5bcc7968c91f3d035abbe8b398afa3334d6a540003a18e6686ea73ada65799e9e0297ee0b00139e04703a6c67449c0b3918fa92c4274fab0f56180f1d9ee1ddbe76acf40937016955addf6e76e8e94aae502d4c0f60004961dc7f51e657961c94a632702e929d12434fb2fb80438f1b1e90a5fefa8d5f102a7c70046652b761a173c78de351587284b378abbadbcb2a44a031f3a3c78b00109375fdcf1472e853682cd7b80ff78818568bea166e752b4cd2334a1f725d2d0c85498ec27055c1a43b6717673c556bf7c4d78efb102d5372a4b009687ab45b9cc010a72426aa3045b64127bf0e765aff8644cceeecb9739fa19acb2d2b0936c4517932882fe0fec0217ad794def938580c15e46459183e723135332d0876ea998b05e010bb32a6f4cf571e8a1f4efeeb9bf97e5fa1399d6f5faaf9c243c5fe3746ffa79f27bda866de8a9ef18052c24d37c355bdc41b9750a94fe339fa4608e58e143991a010cce634e42ea68664dcb59717d58150f032b82654ccf1ef08fd9fd17703d68b7ca5609a8d1f883ca5f2d82eca4ef2fe97f933d76ae28b30295869deba9440af1d8000df19654675ba8bbc6d178a460c353ae9cca7895dd1cfe638882ffcc45d6046dfb6880a6a61d039e1906e5ce87e1e0275a551a74069c6b78544996ed56adc66385000f260c57ebd6f2e526e386436ac2eef3b0305d0de52e28ed876894783129c75f5721be2a30dc9478316b7efd5815c4690350a59ec178990188e4d1cfd525c62a7e00101178b0215e4f6f29fc54924a3bfa123d4037760c22e98cb29b3e2f2a61b3b00f66482446f177b0808ec735558b3488510f06ce5a2dad7839934006ce33015f6a001186b81e3dbeb05e0c31ba68e20e18092375bc62824034758411fb13cecc4bc2c5564ff7404fe8528367a6b06d40bebb3456e897dfbbbb220fcf85b59c3d2c052501129ef799c5a009006015f28f83191cbfe662cf2f1d86c91a286ec048fe6040869515e6fbd4301fc8c3ee31a27e7d5b6b1b95875d17f6f21368ef015837ce582c3401648b5aaa00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd2df0150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5aaa00000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5aaa00000000648b5aaa000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5aaa00000000648b5aaa00000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5aaa00000000648b5aaa0000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5aaa00000000648b5aaa0000000000000000000000000000000000000000000000000000000000000000", + "01000000030d00221ee8e172ff8113223b83cc7eedf280b1dcacc37df5a024116d1a940b12fb5b76bb2535dfbd8af8c38239514381ae37cf2062aefdb41b196dd5c6dcfc94361d01021ba85a74a8a60e7ebfe20721b8e733d2fb853af98684b5c193cc3f9b20c757ad651326d6b135a4838b113adaae462f387c3c2afb249f1f882c770903495e8b4701033b9d2e19502e2f415cef825c15c3f6cc9de749564587ffa23bae53bbadf0676e3dec7e3bccca30c69d76300ef07849cc70677d5d5dcabbea61b912bf91ca7d290004f9c224e447d697229753d9fb5bc617762f7a5248e1fca13ac605a5371f19ddc46268331d4c2d197d80db0cf6c2b7f9203eb08c193dfd928018c0592c227f4cd8010839b7e8f6b4a7e7a2bfd058742a0e237f4d2bfb768c9df35fada8a6ddf18e04d05fbddc91cc53d429fbc2265836885e4ac251c3cad49a94816cf2e09bb394c627000a770a381d7368b7e8abb51fc619f0c56c0fdd261f99ce553d3160f2a669e7f27043d3b30e53c8a2b40ef7f11d8140b201bbeaf4bc03c5b6d9655292a71bd1e423010be6b61ad60af4e8fec918ba4a2ca11fcb7cf94035a54a0ced906ae7c2e77ae17d6cdeac4a511a2127da22869694a9c5804d3cd5eca4cfd39daa587091f85b9d98010cdc14d6ff68b64a99b09774fdbf30509f8767fd42ac511b08dc68e53d6a6eea875c53d272274e20f7fae47f6ce386a53c6c1c18243ec0ea2c6b29074b9b14df57000ddf03bb2a85f8c7dd7d4c771cdf503e294a0f2c92d3dad87dcdeb0b9455f16405167f5042b5dbe12e553570f43fa86eb720ce2fe70927b9c5e7bba70bcd2583ce010f62c6b4a1a0ca48542d35238caf9ea9fa3f6265dc81072773cf5bac9349c4fb5f179fb0cf7f31e73918894d43af0ca359d2458fdae2484271bc72c0766a2c42b90110ec732ead991211c67e1c44e92d37666abe7a24a788f9a0619bb536529b243251522ec65e847b9640c36c8336e8e4ede3f7e0718c1b60a0c15afdccf9ce0ecad90111106ce16dc68177e4a3daa28218b97f132f6361beaecfca4431227910fcd91e8e02473a424114696d21d7cbd3b5287cf83c4a6e60b7f158b2a39112ab37197c59011241b470bcd3d560fefee3ad56c1478813ae54ee75afb1ffd608e8e550c6ee1e2c6ccbd3cff9b56580b7ed5d36794876559aabe5dc06bfaca16322bf71d891b18701648b5ab900000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd4140150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000057358408000000000019c682cfffffff8000000056e39ad78000000000181e6fb01000000170000001b00000000648b5ab900000000648b5ab800000000648b5ab8000000057358408000000000019c682c00000000648b5ab848d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a02c1200000000000af79e0fffffff80000000268e319ac0000000000c36fb4010000001b0000002000000000648b5ab900000000648b5ab900000000648b5ab8000000026a02c1200000000000af79e000000000648b5ab83515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43000002498cb46220000000007c363a70fffffff800000247bc41ea500000000070d2a8aa01000000190000001e00000000648b5ab900000000648b5ab900000000648b5ab8000002498d5dbae2000000007b8ce1ae00000000648b5ab89b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae8000000000391efa300000000000102e3fffffff800000000038e941e0000000000012a15010000000f0000001100000000648b5ab900000000648b5ab800000000648b5ab8000000000391efa300000000000102e300000000648b5ab8e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d42220000000000001272fffffff800000000005cef740000000000001542010000001b0000002000000000648b5ab900000000648b5ab900000000648b5ab800000000005d436d000000000000119c00000000648b5ab8", + "01000000030d008ab5f79662dcfaad986e71d9fd8b8af862353fbc84f6bfe13bd41cb01d407bfb1c21bc8464b7919257216ad80c84b5a884253cd1cdb8be5bce072db961d786dd00014662da52a53ad933f432f6b5df809a33ffdf41a8ca9c03b69b543e83d772b18b341a70952ffa79b7132e018665b5324953e81169fe918285363350447910b96a01022b65866f375006eba69a3fdc5d68519771c2ba1b7bf0df117d4a164b4390ffaa13c36f647db1ba1c4e2856c846cd5ecd1a6d52d79d3fcdc29ffcf8912bb8a8b60103455afa6aaad3830d2de7b5c78754f6fad20136f6cf16a63ec566ec7e21f5ec1414722357823158589025e17c1a3a1c8f4f4801a5713bbc0f0ab42b5dabef29f300041ec4cd4ca407fc5fd80c1a60cd0ecdef3a12fabe71ace1e1d2babb8594694f4143b536ac74061b81535d597c59dbe98e771888823a6b2c2ec7e4a5efe63116e60108d4eded020cce69cfaee4d72020a46bfbf913fbec2092a6da1dfc1777976aef4c32c840cdc23de32ecd4fe7dc6a5b0178fcc471b63fefd1e299cf65eb678dd28c010af6f5eaf19e7acabb087301ce2c2c93699b9fb4e3e8932a7dcce9c100d0250a45303042f939c5cf880df3770cfae74fbbbccf9ac00fcffd3344e76af9b2122e89010b10789636a4204b59ff0f6700915a39e3b185e2f2b36e13898579a10ceb61bddb2237dd311665b0938962b0b02e38a434931c9102dab69108494ffb8e6c6f86e4000c7c3784becffef400d3d21b52ff6d7f18123200f0059f7147a3a1f39f3f602ee31c5549e5058b5f00d0f9225f79a1a53160a01beb6dd1bf58186325f293a816fc000d389231ce8f5ffddfc4dcb198f64aab2426d3820b2c0496966f115867abdb5ba200e7a81701e49c8a66a7f1e646fd97d3a20b5931cebd39d564f0704898410372000fe702cd49563343b099894c4036ae43d99a48aefb25726037acd4072cca092d9f52144d22f66739f85bfec1dcca4ffbc2b160f216e897c9b329ff6f179cf3bb5e0111d73b1e9cdc60348a5524ba4ba619049e381a38a9b800bc578b0d64981db8eb405dc5b4ef4d97d9b31e3b717de5f485e3d384fc7e9a1fadcad971fe26bfb747070112780e544d00cf6181147f2bc67eb908943581952960d9a40f9063162000e958d460dcae60a5720857423b57113ae0a045d5f8755129b09c4643a58b7ff47b090a00648b5ab900000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd40f0150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5ab900000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5ab900000000648b5ab8000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5ab900000000648b5ab800000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5ab900000000648b5ab80000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5ab900000000648b5ab80000000000000000000000000000000000000000000000000000000000000000", + "01000000030d00efe9314b41350a515cdb08b4c0055e9a8e7748a106d3c01439205afd8a22502a635122818cd5ae89eab68906bbc4f3d296eda7183aafdd8c73179315a2fafa920101b769624886bbf5d33f54ffe56d7cd233ad58f52c365e613d87864b08c039cc9b47dadfeff561d68b4ab18f37163a35567119d5ca9311a2d640cfc4df96025f420002db2a89cb70c6048a8f545b4ef163eb9d99c84b0a064031d4d70a1ef13194800c533d0171135b781a7237aaed56b5721c86aeaf04683017cde445721a0429176a0103db7ab0abdca5c30d695ac34fbdbb8bbf9dfdb85874871739c94f72de788ba730210688624727b9b41aadf571d8e9e06b3de1074adaebd6d247010c9f22f6637801040588a0cc61f3a2781fa9b31fb297e0fbf27cc59c5634fb66434faec64b7573ba4e4cb0ea17fba1daba1f05e70c5cd80600b257e489d4cd8f73a87a9027077876000a88efeb0348264fd12f7ab0365cc963a303252b7e69182384e7c8f1c5b70484a027bedccd826b7288e16a4462980ad68e239ed17ef3604e3adfc92d86d38c3bf2000bc53bc144339a737cb953f63f89664c3389bb00fb70bfaa77701848207aada1ed2349e0058c0cd72bd0ec63c74b2f6bc6dca4953c0fbac25584ec14e0b7ade0fd000c9c97ff3e448175f23cbf63dee6547f285fc990cf46682845f70a670540856a5064d52ead743cd8beac07ad8216f1a91677d9792c6d3cec5e8f19ac65ad030b14000d1c38365533858ebe968c50e14a3fc500b6e19c385993a887f2d54766ccfec7976283dd2c894e8c8ba6361380e73c7929d6580d96cf6cfd460844f8be089fed2c010fac714c0b78f42322992261052bd3d13fbb5fdd89ea7c43218fa61dc3c05384d53e6edd7833b15e821b18944e1ef724fc57e2a073b121f00c1ff81f6cea0df44f0110188bccd5de8e1c602f1a75d5363b99c56c78de421bc56691d871fed8ea45716a39e14bb3eee6021b933c13700eaff0bf320767cbbb324f0bea02a82363fe68dd0011b52b49d13bfc2b95c178f57c2e06e2f189edcc4611d6c16f43fbbd9065d12531598a48e1725d87e14ed5ae79abb27fda10c88d9b6fa238afd89477eadeaa504600123feb36283522a03e8bceca34cbd0f73ff775ba3344e6635e949c88c3c7a550956d90b04ec0fee272b48e3842b15321ab8bd264993e9d75ab54e524dbe564f1ea00648b5ac500000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd5160150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f0000000572aed43c0000000001ad3236fffffff8000000056e3df018000000000181fdf001000000170000001b00000000648b5ac500000000648b5ac400000000648b5ac40000000572aed43c0000000001ad323600000000648b5ac448d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a30000000269ea264d0000000000c89bb1fffffff80000000268e428600000000000c36501010000001b0000002000000000648b5ac500000000648b5ac400000000648b5ac40000000269f2a49d0000000000c01d6100000000648b5ac43515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000249634be7cb00000000823b16b7fffffff800000247bdba59800000000070d9aae2010000001a0000001e00000000648b5ac500000000648b5ac400000000648b5ac400000249634be7cb00000000823b16b700000000648b5ac49b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae8000000000391f86b0000000000010c0bfffffff800000000038e97bd00000000000129e4010000000f0000001100000000648b5ac500000000648b5ac400000000648b5ac4000000000391f86b0000000000010c0b00000000648b5ac4e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d3c330000000000001285fffffff800000000005cefc7000000000000153e010000001b0000002000000000648b5ac500000000648b5ac400000000648b5ac400000000005d3c33000000000000136300000000648b5ac4", + "01000000030d00a8d61b80eb325c2b34be51f5e02d6bc28a31c290fc2158d970310c7b8b9354ef27d956ba2d01a349bdf60699d5ef946edeaafc1f5021713e8ab0fdc8a8a9b30b000213e8596ab484000d8b23df322782ab2c2b53414b8a0f726f74e00907567861dd60c7df8ab13fb11cfc52e0ccdbdb47e646ab999c9436414198b99c746f0f022c0103483e492d5d0be7d84f9fbb7a6ec84dd3c3a98583219c185aa65148cdcfa06abc2149d445383cfe177f3e3f34550c7f790328e110bbb91889671b9084e229eab60104563134e3deba6c6dc998aafbc0d41a2877b339e1e3e89d261a43838b35ddfc7831236a76e96652e3f950cf936d1a5d190f205b52f6c96c32998d5d4cc6e9a244010a7aa3abd9a41221db19540d6f3cfdead2a7791f97b6501e1e7a20daa719bf571c7608ffac59c4a40ecd5cdbf68d3d7909d52eb4eee4b99edb3bc7532e33ef747a000be4fa3030ac6e16df839260912300e32abefde86d0d6cb1cbd2da8618f68a8aa055a082b91a96ccbad7a3dbe91c1a9ea408782161d75be03d3d7946bbc0ddcbdd000c99d8a7785ab49118e62c3798cc776455d75e6cafdebdf1cacf74c3b35349493255727db49d158b1c4ff6d599c59e065a8787fc6a28a32592c952f6323e521edb010d9a0d2314f5579279f4cd22d9276bac88e1da3065fdbbe797e6aea426c391eda859e8d2af1456c0d63c763c20dc7d29b95f30586d228b2cf7416077f415c8ae8f000e6520ab9d85bec828b20b9285ff3a4b42d0e8a16355949379592fe87ffe9faa2809ac052817d28f03a64c99cb28d2f7eb14cbceebda3a44ad9ab9c334e0bf6ea0000f350c592188634248d13dffdcd01ad79f35ea400635f3a8fcd43141cd3d22e36425cf8e2072caec9e4a1c6c8d652c8c80db13a3de99a47aab2d993426e9457fa201103e13dd0e3842cae5700354ff5014a408e5aae26c3058eb448b7e6126c9ddef89105fa08de28c390d92e8a57a1cb73ba12c673a287476c9fe6ba22f7fcc7ff252001170801ceed606f2b4b67756c2181d8335a433456416a3eb3348a91f1c39da627971fa4630f88ae22f46e6aec8654c1877c5fb24e6d9f3046b852a2339cb9b7d410012a85acc87e227180d7b9291009a2dc4846f3c5f17f476a9616ef908f07338eb457ed3bf996c63be891a9dc48f1d7294a9d87eb12ee91566a86e061442d71f7ac900648b5ac300000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd4f50150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5ac300000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5ac300000000648b5ac3000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5ac300000000648b5ac300000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5ac300000000648b5ac30000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5ac300000000648b5ac30000000000000000000000000000000000000000000000000000000000000000", + "01000000030d00f178d45b9d3ad2d9fa81ed97486b98df8e42a92036b20564dc6dbdf2c7f9981c799cf4eb5def8d00d4722f1feb812840b4f16109331e18dcebb1359c2a31db710101b2ea2219eca61627b59e21562e26af975d47958822ea1932d0b9f9e79ac2878434721b8ca1fc72012a70b1aaddc551cfb141cd367576927a32597e0293ae364f0002f84de3103f961565b76516cb14f57624a98bbc34f2bb5b4c1bb87d927d84056b04e18d6b103322e960a7f9fca8ed29747467ee245f3e89d748221c96c1c7907b000367f957ea177cc69c3c805bd7ae2428fbeb0ada04f496284976cef795a278deb95d6cb1108a07ce78a04225ec78ee242f67abfdf46b8fb9412cb1db8ce70b449b00046cc31156255356d335dab76df9c07b749b3490edab45dfb579408aca756b6c25088d357f90fad7a6d720cbe8cb741ab0367cb0dff13bff42a171ca6d02c0c5c7000855bc88c18b4146ab7da3543075d3b70e0ecaafb5b3169b4834e3024825e6811148ee751e5b962c6f8366f0e225912ee79a60e4fac0a3be0fae8140076af2f9fc010af8805ed0b86b796634735b53c6bcfb6e65e167ad5de1cf9bbfef7f21d03747a80d4b9ba9397e5ac352f7b8e4f208300a0b3fc57dfff5a0d7d6d90a95c1c0ee01010bf55b74837e453b7435f8a033bc6af8a63f932f8c88c10011102e917add7b4a651c772ce350b7ec5c924023b41fdd0ebbe0b1ed28338859dcf498f54a7698c93a000c19683a41f5a6ea4ce9c6b0ae7469d06ec64deb6f15947532e804dc4af4422c3e73cd74740b7dd52cde6491e90b56eb8c03f5326dd7713d20484b1a060e1b5419000dfa7329fa30a534b8b245d8a6cbc8d22862f05211cbab68c995fc6df451f02c757476e23031c13cc234d910fb3c1d1d8bcdbe97b94369e0f4d3c71d6ddd2238b7010e49d90fc4ea8f1d0d845d2765cd1efdf1d3764a7800bae6513f3441dd5d20151f10bc49bc08290b89178df30c8f74f8cdb876515dfb6a6c128e9c59e5cf241c950110ad8e69b349a9c94b7370259418731f774d6676d76c2c77685bdefa07d1a85b1520698b8de553d173fe0d32902eca18b84a75300205f7d316f7abc1c0622bd6e40012fc85d1186428bc1a6ce793e8dc1df41df30369df2103b5f116b4bf435de14a5c05d2bd233fb631190a05447804895eff891fed7a28693255b2e129e3bcfa492b00648b5ad100000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd6120150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f0000000572b152200000000001aab452fffffff8000000056e421508000000000182124201000000170000001b00000000648b5ad100000000648b5ad000000000648b5ad00000000572b152200000000001aab45200000000648b5ad048d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a072c600000000000ab0ea0fffffff80000000268e545880000000000c35949010000001b0000002000000000648b5ad100000000648b5ad000000000648b5ad0000000026a072c600000000000ab0ea000000000648b5ad03515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b4300000249707b58c0000000007707cd40fffffff800000247bf4e18e00000000070de4b1a010000001a0000001e00000000648b5ad100000000648b5ad000000000648b5ad000000249707b58c000000000745595ec00000000648b5ad09b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae80000000003931e320000000000012c1efffffff800000000038e9ba700000000000129e7010000000f0000001100000000648b5ad100000000648b5ad100000000648b5ad0000000000392c7eb000000000001826500000000648b5ad0e876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d37ac000000000000136ffffffff800000000005cf013000000000000153d010000001b0000002000000000648b5ad100000000648b5ad100000000648b5ad000000000005d3749000000000000137800000000648b5ad0", + "01000000030d0035a94926588f79b1dfce264c6915258819fa68de2f969d04e38c447d6c03523835519ca606093c83cf8e5b891da5f5765eef06c6fefdcf440112ed65ac5ef36601013e9b1d7d2ad19b560214ae65efa3bdf9d003df3f5d905d3b52de10c728d3402e5f4a169e090db71ee1c418c5e403b46d09808901020326c9540b2ee2f869b55900025d9e0c873f6503c9440f8f6959c11b5785395fd85dde27c8b3dd30c4cff51e672ea9457b2eb233e19a08bacba40fbc4334cbe0140e4d9856bd367d627b13c9b10003db1b6d886b7d80bfcedd9b719ec71ce7b5aff03fb5a0c754190d431df6d7bd615786f99a63f7974edccdeb6686e5ec50234bbc6f785cbc115d847b8676e48c880104878ec42ad0c4430f0b298e426d1f0c12194d37a05f3f166fd8ae09da3f2ea3b326a5f3974fe23180b9cc4a0a229278798dfe5e6084942f887047f6199d0db1ca010a92bffd4605ad6066ec532758e599722b4eba5b89536c4025b25b3404834a45d204f8b6a34724b066810768683a794a9782d7145b73e068fc07e237fcd7a10a08010b27cfdf49b87e744421c4577f87a18d5a68860fa600ed1f2dfb55a8bb21dfbbf26da8d18c9062be4527edd0d01028ac8384e51ae1405a4c51a3d3ba98f96ef510010d9919bfbec41e683fe4a41d52aba2bbdf666c7328452287237001be6d169133df3a499ee8e3b3183aabf780734c0df917767a2be5b75a2129a47e66ff9fbbf839000eb5d28032e4ee8bfd376a976b65ede1e8053add16d63a1955620b7383299870585906efbc4becd3cbcd66de307f8bac1c9f740aaa181cf91d445ba1708c974941000fcd5a3f67c7851db812ee7ff227d0dfd4f4c38aa083b7476a68b92853384267a84d771fd87af6a36237480aefa610f92b42e62b1f489c1bf1889376c4a34d70060010da9559f4ca3a7b2c4fba904650996303135af2cd9c32f822706336c95de490216feea8f47a3ed8e821c8a9102a6fc32a4a6cdb5fdcc552b857828e46d230d4600011c4ea1eeb4fa9a174cb7b7b768a4ce53f148bbaec5e80d570731dd9bb91cb245f30f6dafbdb59e0367321e8a5ed7005ea965ef0a4fd4c709f222ca9d36d5f71d70012ec3184202a29a92ecfc80790df895dee37a3b62ba509fd191599784233d31d72090961d5515f30f10f206c61e51786f9a237bfafc192ef91e53485c27a1568a100648b5ace00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd5d40150325748000300010001020005009dd191f2e7d61c0eeeb661e9d07084d395815518e17e4ccc32c9b0bf9086e90a3b1ae76ce538326a90bf3b4f7e37082ce605585e30932e82d112b023ce8c33e01c00000000000000000000000000000000fffffff80000000000000000000000000000000000000000000000001a00000000648b5ace00000000000000000000000000000000000000000000000000000000000000000000000000000000e7234913d80bf9bf77509d211bf5e73bc67e8abe6b16a4b36d5afb33e1a1a12cec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c1700000000000000000000000000000000fffffff80000000000000000000000000000000000000000080000001c00000000648b5ace00000000648b5acd000000000000000000000000000000000000000000000000000000000000000023941ab3d481fbe313e94a122937f7b4ee29acee62e4d9eb4005afed932b173f8963217838ab4cf5cadc172203c1f0b763fbaa45f346d8ee50ba994bbcac302600000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001b00000000648b5ace00000000648b5acd00000000000000000000000000000000000000000000000000000000000000006b3980174a173c504dc5dd8b3397062db3c67e06f52d53b481e65b6c1c4cc848f9c2e890443dd995d0baafc08eea3358be1ffb874f93f99c30b3816c460bbac300000000000000000000000000000000fffffff80000000000000000000000000000000000000000030000001a00000000648b5ace00000000648b5acd0000000000000000000000000000000000000000000000000000000000000000b19ce5c168c433e14e522c6918f2eee1450ac96ff06c7a58acd158f9af100c929f520bbc39aad043e3bbb46b05079222d63bd94c150205f2a8889b6b36a0f37600000000000000000000000000000000fffffff80000000000000000000000000000000000000000040000001b00000000648b5ace00000000648b5acd0000000000000000000000000000000000000000000000000000000000000000", + "01000000030d00ad5cfb79320e3f1c3d0929ac96b7db51ab7dfa5813ea8f1a8d4614dae430c7b041115f958622235a4af5e8e8513ed4acc1e3640610e6d433252ed01fcdc9338301011c653dda631cc45adb2b422382c11fd87b724f97a05ce3ec4dc038390f77fa5108c2132e6d5375e90e03aa8988223d167a2dee3286073cf7d3eeab468f3f8a0a0102ae87fa528ba7881a716223f356f3a246b1138c8a94bb5a8be8c1dea235909f716e7fe41d6d8014928895a2ac88b0481135eaad04f57bceaf51e64b46786496bf0003de49e30959d737517d6bb17ef6a2c8475ccb356db080f2fff222b4e2b4b77c9b6505052eea218353ade471d90f519fc84dc12ea4574429eaf50023ea5401ee1e00048ca6369ea9b87e8474e120c6093d9378bb0720e8114f57e52aac992c82627f864f9432bf0f1edbe3302d0abc27887c1b889b4fa81e013bc0eb14193f4e96854c010831be80ded7a9eed3cfec2f5da4cc7a88f823a06fa543e8d9aa2ada382f758a41173f2057c0287521c5378067cbc98f61b0f31bcbe500894dd5c8540cc5f3ab6e0109055ce3838a27258c67028aba8ac3f4289c33b4e6e371bded5f9404d07ff422306b0fc4dc022d36968e18e4922461baafb322ccaae359ef69f11c733cf13a1100000adf759df8103523f9e812373a43d4a02ff0c05b9cc2407cb38724df5be554738b180fabd96a730de0ceed327f8ba6cf4b78dbd0751f6270dd02b1087d2f7aa140010b8c0d22f8251f9b7b2e1e5df5e0521224b186a4c1f0776b62ca880f1c4447db22258b794aec2c5e6b5845a1bbe8b67c615465a6b56ef6da1830bd84f243cfe005010d247aee3d0d0dab3e33b317e2611d6e52df34bd5abf5a0cdd4937455832b1b0634b289ad4f96ea2946550c7dc94808dcfbdfad982f26827008c398b2e613ff728010e2162d1f86a6fe71314353527f64ba7397685bdc789cc7ebd50680625834ad2c554e5c57c26561774d528419a710b40f3a030fc46ac70f1dd79a39200a64215c3010f352d1943fea3821e3d07f53843ea7dc650d65a9eda692a26d5b8c691c3b7f1c44b4d3d1570525de2fdda4960c9fec5779b5cf6640dab19b13cac5477dcf3529b0112a0d821a4592cd3f4d8f071547efb6e6bb2500f4c8ef751943ab64c72c7a86bee38c82caad5d30edf9d981a21614e5cb8a1032e9a40f07a1db32345cb8b328c9a01648b5add00000000001af8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0000000001c5bd72f0150325748000300010001020005009d2efa1235ab86c0935cb424b102be4f217e74d1109df9e75dfa8338fc0f0908782f95862b045670cd22bee3114c39763a4a08beeb663b145d283c31d7d1101c4f000000057246017800000000019230f0fffffff8000000056e462864000000000182251c01000000170000001b00000000648b5add00000000648b5add00000000648b5adc000000057246017800000000019230f000000000648b5adc48d6033d733e27950c2e0351e2505491cd9154824f716d9513514c74b9f98f583dd2b63686a450ec7290df3a1e0b583c0481f651351edfa7636f39aed55cf8a3000000026a03186a0000000000b24171fffffff80000000268e6624c0000000000c34c3d010000001b0000002000000000648b5add00000000648b5add00000000648b5adc000000026a01e6920000000000b0546e00000000648b5adc3515b3861e8fe93e5f540ba4077c216404782b86d5e78077b3cbfd27313ab3bce62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b430000024969e00abd000000007d0a84c3fffffff800000247c0dccf300000000070e4cda0010000001a0000001e00000000648b5add00000000648b5add00000000648b5adc000002496be5d2df000000007b04bca100000000648b5adc9b5f73e0075e7d70376012180ddba94272f68d85eae4104e335561c982253d41a19d04ac696c7a6616d291c7e5d1377cc8be437c327b75adb5dc1bad745fcae800000000039338e00000000000011170fffffff800000000038e9fbe00000000000129ed010000000f0000001100000000648b5add00000000648b5add00000000648b5adc00000000039338e0000000000001117000000000648b5adbe876fcd130add8984a33aab52af36bc1b9f822c9ebe376f3aa72d630974e15f0dcef50dd0a4cd2dcc17e45df1676dcb336a11a61c69df7a0299b0150c672d25c00000000005d3222000000000000133bfffffff800000000005cf05b000000000000153a010000001b0000002000000000648b5add00000000648b5add00000000648b5adc00000000005d3222000000000000133b00000000648b5adc", ]; export const p2whTestnetVaas = [ - "01000000000100888fa734558a251d97d73318de190a281dd2913c412e2656fc5c5a4023f5e2e26e8e4b7bf884880e5acc119f207248cdd62f65cca8f422b3f624a707af765ac701648b651c00000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf1670150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a00000005810dc6000000000001a63b48fffffff80000000577e64610000000000185330c01000000010000000200000000648b651c00000000648b651c00000000648b651a00000005813a13cd000000000179ed7b00000000648b651a6a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002719137720000000000da2f1ffffffff8000000026c540eac0000000000c2702501000000010000000200000000648b651c00000000648b651c00000000648b651a000000027192eddb0000000000d878b600000000648b651a28fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f477ea9b1000000005e977a96fffffff80000024b91f168800000000064b5bb4801000000010000000200000000648b651c00000000648b651c00000000648b651a0000024f4dc86f84000000006418cbfc00000000648b651a8b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aaffab000000000001698cfffffff800000000039cca0c00000000000130a901000000010000000200000000648b651c00000000648b651c00000000648b651a0000000003ab5b030000000000013f8e00000000648b651a3b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005dfcd10000000000001343fffffff800000000005d7613000000000000151301000000010000000200000000648b651c00000000648b651c00000000648b651a00000000005dfd96000000000000170400000000648b651a", - "01000000000100a55ed74e8b21f57ea14988bb7b5afd51c2c7bf91c2987e0d03b9c48cb1e0473c6e019e7ae2a665270d4a2a00dab47a7abed19e94b832989215791b7eba8f289e00648b64f800000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cee960150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580ba2e960000000001642c3ffffffff80000000577cbf1d4000000000185567801000000010000000200000000648b64f800000000648b64f800000000648b64f700000005807657b600000000019c41c500000000648b64f76a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002713911600000000000af5fb3fffffff8000000026c451af00000000000c2889701000000010000000200000000648b64f800000000648b64f800000000648b64f7000000027140b2800000000000b71b0000000000648b64f728fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f0d240e4f00000000750e2531fffffff80000024b87f3bae00000000064b0efc801000000010000000200000000648b64f800000000648b64f800000000648b64f80000024f10f80e6000000000713a252000000000648b64f88b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aa552e0000000000010600fffffff800000000039ca25d00000000000130be01000000010000000200000000648b64f800000000648b64f800000000648b64f70000000003aa552e000000000000e3c800000000648b64f73b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005df1380000000000001324fffffff800000000005d749b000000000000151501000000010000000200000000648b64f800000000648b64f800000000648b64f700000000005deee0000000000000119400000000648b64f7", - "010000000001006aca274ebd019f90267f120bcf72935309f69ea651362dee74e312fa9e19ccbc0d5eb3e60fe5847bfd5fb5fe4b2fbb3c4dfb20b37b91739c679096512ac9093d00648b64f000000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cedda0150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b0000000003016d5e0000000000016ff6fffffff80000000002ef5d46000000000001777001000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000003016d5e0000000000016ff600000000648b64eaebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e700000000084825a70000000000057b0afffffff80000000008368f3c000000000005725e01000000010000000200000000648b64f000000000648b64ef00000000648b64ed00000000084825a70000000000057b0a00000000648b64ea2bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037573000000000000017bfffffff8000000000003766700000000000002e901000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000000037573000000000000017b00000000648b64ea5da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000f012260000000000013c19fffffff80000000000efe716000000000001387f01000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000000f012260000000000013c1900000000648b64eafad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023b84970000000000089f06fffffff80000000002359f41000000000008c44201000000010000000200000000648b64f000000000648b64ef00000000648b64ee00000000023b84970000000000089f0600000000648b64ea", - "01000000000100300f986bbe61faa6dd2ba34d4c8e2b37b7bbf0e9f04ac95e95b5e5ffb59815ab11f9419bd561553557c488470e6da92db5d3018e1e94b8d319cd40b400b0b0a700648b650600000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cefb20150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580a1890500000000017cd1d0fffffff80000000577d5caec0000000001854a9801000000010000000200000000648b650600000000648b650600000000648b65050000000580a189050000000001886ee300000000648b65056a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002711bce9e0000000000bd7a62fffffff8000000026c4aea0c0000000000c2747f01000000010000000200000000648b650600000000648b650600000000648b650500000002712b01440000000000b1e46a00000000648b650528fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f05c1aa0f000000007bd7f2f1fffffff80000024b8b2042b00000000064c1e62001000000010000000200000000648b650600000000648b650600000000648b65050000024f0414405f000000007c542fa100000000648b65058b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aa4657000000000000e759fffffff800000000039cb22a00000000000130a801000000010000000200000000648b650600000000648b650600000000648b65050000000003aa3a68000000000001071100000000648b65053b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005defaf00000000000014adfffffff800000000005d7526000000000000151401000000010000000200000000648b650600000000648b650600000000648b650500000000005df138000000000000145000000000648b6505", - "010000000001004c05a8cdad079c8a66982bf65fb976618105163d581faa3d8bddfa964a496e466f1f0b41f003e692df5d0d14b6e60041161ecee7089f4b5f41558fbcc78abea901648b64fd00000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052ceeed0150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b0000000003018aa10000000000016641fffffff80000000002ef71a5000000000001776001000000010000000200000000648b64fd00000000648b64fd00000000648b64fb0000000003018aa1000000000001664100000000648b64f4ebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e7000000000846b373000000000006ed3ffffffff80000000008369e65000000000005734a01000000010000000200000000648b64fd00000000648b64fd00000000648b64fb000000000846b373000000000006ed3f00000000648b64f42bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037573000000000000017bfffffff8000000000003766500000000000002e601000000010000000200000000648b64fd00000000648b64fd00000000648b64fb0000000000037573000000000000017b00000000648b64f45da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000f025c300000000000176aefffffff80000000000efe74b000000000001389701000000010000000200000000648b64fd00000000648b64fc00000000648b64fb0000000000f025c300000000000176ae00000000648b64f4fad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023b84970000000000089f06fffffff8000000000235a5bd000000000008c41901000000010000000200000000648b64fd00000000648b64fd00000000648b64fb00000000023b84970000000000089f0600000000648b64f4", - "01000000000100e6ddeb64ac14d79c059744144001515ca58d3d23b1bec423310d3760ea4e001a2f0e235b15da59a023c70a5625848d0d907eacc4e70bb18a69d1727b56553e2101648b651200000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf09d0150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a00000005810dc60000000000019d75b3fffffff80000000577de660c0000000001852cce01000000010000000200000000648b651200000000648b651100000000648b651000000005810dc60000000000019d75b300000000648b65106a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002718d874a0000000000c6e6cafffffff8000000026c4fa9480000000000c26ab601000000010000000200000000648b651200000000648b651100000000648b651000000002718d37da0000000000b4886800000000648b651028fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f40bf5076000000006556d3d1fffffff80000024b8e8343d00000000064bb8fe601000000010000000200000000648b651200000000648b651100000000648b65100000024f40bf5076000000006556d3d100000000648b65108b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aa55c40000000000012f26fffffff800000000039cbe90000000000001309a01000000010000000200000000648b651200000000648b651100000000648b65100000000003aa55c40000000000012f2600000000648b65103b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005dfaea000000000000171efffffff800000000005d759e000000000000151301000000010000000200000000648b651200000000648b651100000000648b651000000000005dfc7400000000000013a000000000648b6510", - "01000000000100cd52d0c580cc7478f1b58eec17b6ba9b10a37f505531e987ecb4cfae04a7fdb8276d7b12fa542191a84dc5a60472b7f926450a089449e0777f0c136ba16d1e1301648b651100000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf0840150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b0000000003026d1300000000000185dafffffff80000000002ef8d48000000000001776401000000010000000200000000648b651100000000648b651000000000648b650e0000000003026d1300000000000185da00000000648b650cebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e700000000084a21ec0000000000077750fffffff8000000000836b70e00000000000573ca01000000010000000200000000648b651100000000648b651000000000648b650e00000000084a21ec000000000007775000000000648b650c2bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037573000000000000017bfffffff8000000000003766200000000000002e201000000010000000200000000648b651100000000648b651000000000648b650e0000000000037573000000000000017b00000000648b650c5da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000f025c300000000000176aefffffff80000000000efe7a000000000000138e601000000010000000200000000648b651100000000648b651000000000648b650e0000000000f025c300000000000176ae00000000648b650bfad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023bbf30000000000008b28ffffffff8000000000235aedb000000000008c3e401000000010000000200000000648b651100000000648b651000000000648b650e00000000023baba80000000000089f0700000000648b650c", - "010000000001006ab585e921b81d01fb6528cf6734d95fd869706f59d287a6b743b5fdbf0ebdbd103b04e480132a024225241f496b40cc008daf9471cd668e0583cc906e77069f01648b651600000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf0f50150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b000000000302ad570000000000017c17fffffff80000000002ef9525000000000001776c01000000010000000200000000648b651600000000648b651500000000648b6514000000000302c4ec00000000000193a900000000648b6510ebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e7000000000848e17100000000000636d5fffffff8000000000836bd64000000000005745601000000010000000200000000648b651600000000648b651500000000648b651400000000084a21ec000000000007775000000000648b65102bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037707000000000000030ffffffff8000000000003766200000000000002e101000000010000000200000000648b651600000000648b651500000000648b65140000000000037573000000000000017b00000000648b65105da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000effb930000000000014c7efffffff80000000000efe7ac00000000000138f201000000010000000200000000648b651600000000648b651500000000648b65140000000000effeaa0000000000014f9500000000648b6510fad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023bbf30000000000008b28ffffffff8000000000235b183000000000008c3dd01000000010000000200000000648b651600000000648b651500000000648b651400000000023bbf30000000000008b28f00000000648b6510", - "01000000000100970313d82d9bf3fcfdaf8ed0b38c45cec7bad7093624ef5e7651cabfff706bc469f69951334e8555dbbbed3b1c0ceb3e4e9a65ab6558bd3655249dc7ee74d60500648b652500000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf2220150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580ea12ea000000000154e016fffffff80000000577ec9eac0000000001851c6c01000000010000000200000000648b652500000000648b652400000000648b65220000000580ea12ea000000000154e01600000000648b65246a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002718d17970000000000cf0b8efffffff8000000026c5719f80000000000c27cea01000000010000000200000000648b652500000000648b652400000000648b652300000002718fc1fb0000000000e21d8500000000648b652428fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f23ee6d200000000077306ce7fffffff80000024b93e12a900000000064bfcfde01000000010000000200000000648b652500000000648b652400000000648b65230000024f23ee6d20000000007aaf1ef700000000648b65248b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aaac0a0000000000014abefffffff800000000039cd2dd00000000000130b001000000010000000200000000648b652500000000648b652400000000648b65230000000003aac2380000000000012ec800000000648b65243b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005df7350000000000001794fffffff800000000005d7673000000000000151201000000010000000200000000648b652500000000648b652400000000648b652300000000005dfa98000000000000119400000000648b6523", + "01000000000100888fa734558a251d97d73318de190a281dd2913c412e2656fc5c5a4023f5e2e26e8e4b7bf884880e5acc119f207248cdd62f65cca8f422b3f624a707af765ac701648b651c00000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf1670150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a00000005810dc6000000000001a63b48fffffff80000000577e64610000000000185330c01000000010000000200000000648b651c00000000648b651c00000000648b651a00000005813a13cd000000000179ed7b00000000648b651a6a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002719137720000000000da2f1ffffffff8000000026c540eac0000000000c2702501000000010000000200000000648b651c00000000648b651c00000000648b651a000000027192eddb0000000000d878b600000000648b651a28fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f477ea9b1000000005e977a96fffffff80000024b91f168800000000064b5bb4801000000010000000200000000648b651c00000000648b651c00000000648b651a0000024f4dc86f84000000006418cbfc00000000648b651a8b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aaffab000000000001698cfffffff800000000039cca0c00000000000130a901000000010000000200000000648b651c00000000648b651c00000000648b651a0000000003ab5b030000000000013f8e00000000648b651a3b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005dfcd10000000000001343fffffff800000000005d7613000000000000151301000000010000000200000000648b651c00000000648b651c00000000648b651a00000000005dfd96000000000000170400000000648b651a", + "01000000000100a55ed74e8b21f57ea14988bb7b5afd51c2c7bf91c2987e0d03b9c48cb1e0473c6e019e7ae2a665270d4a2a00dab47a7abed19e94b832989215791b7eba8f289e00648b64f800000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cee960150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580ba2e960000000001642c3ffffffff80000000577cbf1d4000000000185567801000000010000000200000000648b64f800000000648b64f800000000648b64f700000005807657b600000000019c41c500000000648b64f76a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002713911600000000000af5fb3fffffff8000000026c451af00000000000c2889701000000010000000200000000648b64f800000000648b64f800000000648b64f7000000027140b2800000000000b71b0000000000648b64f728fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f0d240e4f00000000750e2531fffffff80000024b87f3bae00000000064b0efc801000000010000000200000000648b64f800000000648b64f800000000648b64f80000024f10f80e6000000000713a252000000000648b64f88b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aa552e0000000000010600fffffff800000000039ca25d00000000000130be01000000010000000200000000648b64f800000000648b64f800000000648b64f70000000003aa552e000000000000e3c800000000648b64f73b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005df1380000000000001324fffffff800000000005d749b000000000000151501000000010000000200000000648b64f800000000648b64f800000000648b64f700000000005deee0000000000000119400000000648b64f7", + "010000000001006aca274ebd019f90267f120bcf72935309f69ea651362dee74e312fa9e19ccbc0d5eb3e60fe5847bfd5fb5fe4b2fbb3c4dfb20b37b91739c679096512ac9093d00648b64f000000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cedda0150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b0000000003016d5e0000000000016ff6fffffff80000000002ef5d46000000000001777001000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000003016d5e0000000000016ff600000000648b64eaebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e700000000084825a70000000000057b0afffffff80000000008368f3c000000000005725e01000000010000000200000000648b64f000000000648b64ef00000000648b64ed00000000084825a70000000000057b0a00000000648b64ea2bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037573000000000000017bfffffff8000000000003766700000000000002e901000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000000037573000000000000017b00000000648b64ea5da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000f012260000000000013c19fffffff80000000000efe716000000000001387f01000000010000000200000000648b64f000000000648b64ef00000000648b64ed0000000000f012260000000000013c1900000000648b64eafad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023b84970000000000089f06fffffff80000000002359f41000000000008c44201000000010000000200000000648b64f000000000648b64ef00000000648b64ee00000000023b84970000000000089f0600000000648b64ea", + "01000000000100300f986bbe61faa6dd2ba34d4c8e2b37b7bbf0e9f04ac95e95b5e5ffb59815ab11f9419bd561553557c488470e6da92db5d3018e1e94b8d319cd40b400b0b0a700648b650600000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cefb20150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580a1890500000000017cd1d0fffffff80000000577d5caec0000000001854a9801000000010000000200000000648b650600000000648b650600000000648b65050000000580a189050000000001886ee300000000648b65056a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002711bce9e0000000000bd7a62fffffff8000000026c4aea0c0000000000c2747f01000000010000000200000000648b650600000000648b650600000000648b650500000002712b01440000000000b1e46a00000000648b650528fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f05c1aa0f000000007bd7f2f1fffffff80000024b8b2042b00000000064c1e62001000000010000000200000000648b650600000000648b650600000000648b65050000024f0414405f000000007c542fa100000000648b65058b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aa4657000000000000e759fffffff800000000039cb22a00000000000130a801000000010000000200000000648b650600000000648b650600000000648b65050000000003aa3a68000000000001071100000000648b65053b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005defaf00000000000014adfffffff800000000005d7526000000000000151401000000010000000200000000648b650600000000648b650600000000648b650500000000005df138000000000000145000000000648b6505", + "010000000001004c05a8cdad079c8a66982bf65fb976618105163d581faa3d8bddfa964a496e466f1f0b41f003e692df5d0d14b6e60041161ecee7089f4b5f41558fbcc78abea901648b64fd00000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052ceeed0150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b0000000003018aa10000000000016641fffffff80000000002ef71a5000000000001776001000000010000000200000000648b64fd00000000648b64fd00000000648b64fb0000000003018aa1000000000001664100000000648b64f4ebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e7000000000846b373000000000006ed3ffffffff80000000008369e65000000000005734a01000000010000000200000000648b64fd00000000648b64fd00000000648b64fb000000000846b373000000000006ed3f00000000648b64f42bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037573000000000000017bfffffff8000000000003766500000000000002e601000000010000000200000000648b64fd00000000648b64fd00000000648b64fb0000000000037573000000000000017b00000000648b64f45da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000f025c300000000000176aefffffff80000000000efe74b000000000001389701000000010000000200000000648b64fd00000000648b64fc00000000648b64fb0000000000f025c300000000000176ae00000000648b64f4fad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023b84970000000000089f06fffffff8000000000235a5bd000000000008c41901000000010000000200000000648b64fd00000000648b64fd00000000648b64fb00000000023b84970000000000089f0600000000648b64f4", + "01000000000100e6ddeb64ac14d79c059744144001515ca58d3d23b1bec423310d3760ea4e001a2f0e235b15da59a023c70a5625848d0d907eacc4e70bb18a69d1727b56553e2101648b651200000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf09d0150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a00000005810dc60000000000019d75b3fffffff80000000577de660c0000000001852cce01000000010000000200000000648b651200000000648b651100000000648b651000000005810dc60000000000019d75b300000000648b65106a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002718d874a0000000000c6e6cafffffff8000000026c4fa9480000000000c26ab601000000010000000200000000648b651200000000648b651100000000648b651000000002718d37da0000000000b4886800000000648b651028fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f40bf5076000000006556d3d1fffffff80000024b8e8343d00000000064bb8fe601000000010000000200000000648b651200000000648b651100000000648b65100000024f40bf5076000000006556d3d100000000648b65108b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aa55c40000000000012f26fffffff800000000039cbe90000000000001309a01000000010000000200000000648b651200000000648b651100000000648b65100000000003aa55c40000000000012f2600000000648b65103b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005dfaea000000000000171efffffff800000000005d759e000000000000151301000000010000000200000000648b651200000000648b651100000000648b651000000000005dfc7400000000000013a000000000648b6510", + "01000000000100cd52d0c580cc7478f1b58eec17b6ba9b10a37f505531e987ecb4cfae04a7fdb8276d7b12fa542191a84dc5a60472b7f926450a089449e0777f0c136ba16d1e1301648b651100000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf0840150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b0000000003026d1300000000000185dafffffff80000000002ef8d48000000000001776401000000010000000200000000648b651100000000648b651000000000648b650e0000000003026d1300000000000185da00000000648b650cebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e700000000084a21ec0000000000077750fffffff8000000000836b70e00000000000573ca01000000010000000200000000648b651100000000648b651000000000648b650e00000000084a21ec000000000007775000000000648b650c2bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037573000000000000017bfffffff8000000000003766200000000000002e201000000010000000200000000648b651100000000648b651000000000648b650e0000000000037573000000000000017b00000000648b650c5da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000f025c300000000000176aefffffff80000000000efe7a000000000000138e601000000010000000200000000648b651100000000648b651000000000648b650e0000000000f025c300000000000176ae00000000648b650bfad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023bbf30000000000008b28ffffffff8000000000235aedb000000000008c3e401000000010000000200000000648b651100000000648b651000000000648b650e00000000023baba80000000000089f0700000000648b650c", + "010000000001006ab585e921b81d01fb6528cf6734d95fd869706f59d287a6b743b5fdbf0ebdbd103b04e480132a024225241f496b40cc008daf9471cd668e0583cc906e77069f01648b651600000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf0f50150325748000300010001020005009d3ca26715facde96b90a2e1e223fa85342f48561da88b7d60379587f6454a7966c2703fcc925ad32b6256afc3ebad634970d1b1ffb3f4143e36b2d055b1dcd29b000000000302ad570000000000017c17fffffff80000000002ef9525000000000001776c01000000010000000200000000648b651600000000648b651500000000648b6514000000000302c4ec00000000000193a900000000648b6510ebcc2747d96e7f9f41d7b21f764db2263ff84353da58ecab5e4623bea6ce195919546a41867d2e2d5ff2c181524f71f618477244bb14fb28dd4c22885ba4d8e7000000000848e17100000000000636d5fffffff8000000000836bd64000000000005745601000000010000000200000000648b651600000000648b651500000000648b651400000000084a21ec000000000007775000000000648b65102bc389a755a022a5305bc6d13d7445483a3367e66d90ca468cb909f8270b16e13f545e3f4ec9fd8eb3b9d9d6071a1da361f6729fa1b93d1d1baca3379551d99e0000000000037707000000000000030ffffffff8000000000003766200000000000002e101000000010000000200000000648b651600000000648b651500000000648b65140000000000037573000000000000017b00000000648b65105da18437b2798113769bd3dff735d8fc068445f58c24daf29d5a88ebd0a7c3f2334a4429edb95cd4a3455d5133bbb568f9013b3ecb68962c89ee99c8e24818ad0000000000effb930000000000014c7efffffff80000000000efe7ac00000000000138f201000000010000000200000000648b651600000000648b651500000000648b65140000000000effeaa0000000000014f9500000000648b6510fad307e1a5982b429896f172b86dba53ca31d5b09d373d929c7ba31026f99ea122d0b475cfe70ef0ff58d08efc3d72a78c7949d2c9f00dd1c8f8074e5bbf99e700000000023bbf30000000000008b28ffffffff8000000000235b183000000000008c3dd01000000010000000200000000648b651600000000648b651500000000648b651400000000023bbf30000000000008b28f00000000648b6510", + "01000000000100970313d82d9bf3fcfdaf8ed0b38c45cec7bad7093624ef5e7651cabfff706bc469f69951334e8555dbbbed3b1c0ceb3e4e9a65ab6558bd3655249dc7ee74d60500648b652500000000001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b600000000052cf2220150325748000300010001020005009d1cdb1a5e1e3456d2977ee0d3d70765239f08a42855b9508fd479e15c6dc4d1feecf553770d9b10965f8fb64771e93f5690a182edc32be4a3236e0caaa6e0581a0000000580ea12ea000000000154e016fffffff80000000577ec9eac0000000001851c6c01000000010000000200000000648b652500000000648b652400000000648b65220000000580ea12ea000000000154e01600000000648b65246a20671c0e3f8cb219ce3f46e5ae096a4f2fdf936d2bd4da8925f70087d51dd830029479598797290e3638a1712c29bde2367d0eca794f778b25b5a472f192de00000002718d17970000000000cf0b8efffffff8000000026c5719f80000000000c27cea01000000010000000200000000648b652500000000648b652400000000648b652300000002718fc1fb0000000000e21d8500000000648b652428fe05d2708c6571182a7c9d1ff457a221b465edf5ea9af1373f9562d16b8d15f9c0172ba10dfa4d19088d94f5bf61d3b54d5bd7483a322a982e1373ee8ea31b0000024f23ee6d200000000077306ce7fffffff80000024b93e12a900000000064bfcfde01000000010000000200000000648b652500000000648b652400000000648b65230000024f23ee6d20000000007aaf1ef700000000648b65248b38db700e8b34640e681ec9a73e89608bda29415547a224f96585192b4b9dc794bce4aee88fdfa5b58d81090bd6b3784717fa6df85419d9f04433bb3d615d5c0000000003aaac0a0000000000014abefffffff800000000039cd2dd00000000000130b001000000010000000200000000648b652500000000648b652400000000648b65230000000003aac2380000000000012ec800000000648b65243b69a3cf075646c5fd8148b705b8107e61a1a253d5d8a84355dcb628b3f1d12031775e1d6897129e8a84eeba975778fb50015b88039e9bc140bbd839694ac0ae00000000005df7350000000000001794fffffff800000000005d7673000000000000151201000000010000000200000000648b652500000000648b652400000000648b652300000000005dfa98000000000000119400000000648b6523", ]; export const pnauMainnetVaas = [ - "504e41550100000003b801000000030d002c9e33a703a8c86012c117474849ae41118270cf56e2db61c7af28f64ce322391d33cb738930dc3ffcfe5ae35dbd99c4e08a8ac4b390c858d94dc1f0b91e24d00101901948561c54c17b7a21a9885d3f60a85b410e18209b02ebe098b0dd422b02667565e3b5011203303a6445cf629d20cec3d4771f0b391ab03bfd54364ac049f70002a030ed0060f1ba123ddf5473bef4c1c8f4aec8737b47f11023f94d5339b72c4c3aa325515956ea702537a3950d74f86134396de437bfc97a5365fc090dbb382c0103f1f64ba806fe8ba7b7db3846d85fccbe7d803865e7b757301f4a5de6ebde40f923ee925a7df78871d39299d70fd117635263b91fe53f792bfff450d210431316000439e31501d7b661a1b075a9302b57a6fc500c1e760de7e949e30994055b061b280cefcab3eb3b5c780f56cd555e88ee3c674b798ddeb8713bd27a361b8b8f1c730008db908c61e4158d1a7a7d53b2ec14027a4a2b2f3207edf0db2fbb0f431f3cdc020ee9fc9f38e812d094dcf5dd18f3a8d1d1ee94e840f504b00e08d0fbd607ed6d0109cf5c52aed9f4a42691c3c33bc366fde4a3ad23aacdd46c14669ad321106cf8d257b9379fc45cbe91a26ee87f4800951c5ca89ff0069e4a47299f988b368c6b74000b1241dea623dd874e13135682d760ee48ac1888149bddcfac54ff3787f4b89629063d89e084350ff1168999365918f9bdf469323b817a25491e56341a0da5c498000d538baecc668df092ce3cfeebfa5968447584916cb503ce50e7f77443bbd2a3441bb1d962a7f3401bfcf30f444a3301ab72dcb87cc04b681bdada93d45001f53d010e2fd97eeac94ceb3fd87a6e4ab3b785c64db221d522e58230a6d9eb10cba3b53a086ba6989e4285ab0816a26ff64c1a3a05175d0ffa61f4f744f928d6c5b3f92600107f999fa2f5cd826520cdf36f2e3434f4619eca4c2fb944bdec79d84903f76aaa02727844ec451378ab165910140c866ebdc65aea2cc6717da4747d4ed8a7338700115ce8f97f4a39ccdf5a61c9425d8a306a35b598db9d0f936bece6f2d92e79f9b1306d4c4789fb73ba5bd29bf0d306f2e8079aae818bc70aa9db111b745f1f4ab900126933d7f1317e3e45d98687e66ae033d871e1026e92f1ad168ec197a92ad653571837b78ee8e2263f2aca1dfa112fc78de63de9e2231f62ab79db2d956704146c0165131de200000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000000df92280141555756000000000005e1b46c00002710dbdd79b598d499596393b24358343774fd54e63c01005500ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c170000000002bf687c00000000000091effffffff80000000065131de10000000065131de00000000002c073dc0000000000008d5f09014cff5b7c5413efc667d2bca5549edadde5324814fed82e330d4110d610f5d0325deddb5541eacccd70da0a613768335768226657b201a456613163b0b4a21cf17276c2be6d02280ff05d08a6d4cf481c00c544f4adff1c86c6845a21871b75b4cffdc9314a2b46bddcb2bd44861fa48d4cc429a0783cb122a3eda67b7a13ad6298995c3bddd6cfa1ba547ddd38f0da18fcb0f7ead4d1de56db6c0442b94883a78f40b3541982a58d2d400dbfb761cce7bf780c", + "504e41550100000003b801000000030d002c9e33a703a8c86012c117474849ae41118270cf56e2db61c7af28f64ce322391d33cb738930dc3ffcfe5ae35dbd99c4e08a8ac4b390c858d94dc1f0b91e24d00101901948561c54c17b7a21a9885d3f60a85b410e18209b02ebe098b0dd422b02667565e3b5011203303a6445cf629d20cec3d4771f0b391ab03bfd54364ac049f70002a030ed0060f1ba123ddf5473bef4c1c8f4aec8737b47f11023f94d5339b72c4c3aa325515956ea702537a3950d74f86134396de437bfc97a5365fc090dbb382c0103f1f64ba806fe8ba7b7db3846d85fccbe7d803865e7b757301f4a5de6ebde40f923ee925a7df78871d39299d70fd117635263b91fe53f792bfff450d210431316000439e31501d7b661a1b075a9302b57a6fc500c1e760de7e949e30994055b061b280cefcab3eb3b5c780f56cd555e88ee3c674b798ddeb8713bd27a361b8b8f1c730008db908c61e4158d1a7a7d53b2ec14027a4a2b2f3207edf0db2fbb0f431f3cdc020ee9fc9f38e812d094dcf5dd18f3a8d1d1ee94e840f504b00e08d0fbd607ed6d0109cf5c52aed9f4a42691c3c33bc366fde4a3ad23aacdd46c14669ad321106cf8d257b9379fc45cbe91a26ee87f4800951c5ca89ff0069e4a47299f988b368c6b74000b1241dea623dd874e13135682d760ee48ac1888149bddcfac54ff3787f4b89629063d89e084350ff1168999365918f9bdf469323b817a25491e56341a0da5c498000d538baecc668df092ce3cfeebfa5968447584916cb503ce50e7f77443bbd2a3441bb1d962a7f3401bfcf30f444a3301ab72dcb87cc04b681bdada93d45001f53d010e2fd97eeac94ceb3fd87a6e4ab3b785c64db221d522e58230a6d9eb10cba3b53a086ba6989e4285ab0816a26ff64c1a3a05175d0ffa61f4f744f928d6c5b3f92600107f999fa2f5cd826520cdf36f2e3434f4619eca4c2fb944bdec79d84903f76aaa02727844ec451378ab165910140c866ebdc65aea2cc6717da4747d4ed8a7338700115ce8f97f4a39ccdf5a61c9425d8a306a35b598db9d0f936bece6f2d92e79f9b1306d4c4789fb73ba5bd29bf0d306f2e8079aae818bc70aa9db111b745f1f4ab900126933d7f1317e3e45d98687e66ae033d871e1026e92f1ad168ec197a92ad653571837b78ee8e2263f2aca1dfa112fc78de63de9e2231f62ab79db2d956704146c0165131de200000000001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa710000000000df92280141555756000000000005e1b46c00002710dbdd79b598d499596393b24358343774fd54e63c01005500ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c170000000002bf687c00000000000091effffffff80000000065131de10000000065131de00000000002c073dc0000000000008d5f09014cff5b7c5413efc667d2bca5549edadde5324814fed82e330d4110d610f5d0325deddb5541eacccd70da0a613768335768226657b201a456613163b0b4a21cf17276c2be6d02280ff05d08a6d4cf481c00c544f4adff1c86c6845a21871b75b4cffdc9314a2b46bddcb2bd44861fa48d4cc429a0783cb122a3eda67b7a13ad6298995c3bddd6cfa1ba547ddd38f0da18fcb0f7ead4d1de56db6c0442b94883a78f40b3541982a58d2d400dbfb761cce7bf780c", ]; diff --git a/unit-tests/pyth/helpers.ts b/unit-tests/pyth/helpers.ts index cf84c7d..c81d6bf 100644 --- a/unit-tests/pyth/helpers.ts +++ b/unit-tests/pyth/helpers.ts @@ -1,362 +1,434 @@ -import { fc } from '@fast-check/vitest'; +import { fc } from "@fast-check/vitest"; import { Cl, ClarityValue } from "@stacks/transactions"; -import { bigintToBuffer, bufferToBigint } from '../utils/helpers'; -import * as secp from '@noble/secp256k1'; -import { - keccak_256 -} from '@noble/hashes/sha3'; -import { webcrypto } from 'node:crypto'; +import { bigintToBuffer, bufferToBigint } from "../utils/helpers"; +import * as secp from "@noble/secp256k1"; +import { keccak_256 } from "@noble/hashes/sha3"; +import { webcrypto } from "node:crypto"; // @ts-ignore if (!globalThis.crypto) globalThis.crypto = webcrypto; -import { hmac } from '@noble/hashes/hmac'; -import { sha256 } from '@noble/hashes/sha256'; +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; -secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) +secp.etc.hmacSha256Sync = (k, ...m) => + hmac(sha256, k, secp.etc.concatBytes(...m)); export namespace pyth { - - export interface PriceUpdate { - priceIdentifier: Uint8Array, - price: bigint, - conf: bigint, - emaPrice: bigint, - emaConf: bigint, - expo: number, - publishTime: bigint, - prevPublishTime: bigint, - } - - export interface PriceUpdateBuildOptions { - price?: bigint, - conf?: bigint, - emaPrice?: bigint, - emaConf?: bigint, - expo?: number, - publishTime?: bigint, - prevPublishTime?: bigint, - } - - export interface AuwvVaaPayload { - payloadType: Uint8Array, - updateType: number, - merkleRootSlot: bigint, - merkleRootRingSize: number, - merkleRootHash: Uint8Array - } - - export interface AuwvVaaPayloadBuildOptions { - payloadType?: Uint8Array, - updateType?: number, - merkleRootSlot?: bigint, - merkleRootRingSize?: number, - merkleRootHash?: Uint8Array - } - - export interface PnauHeader { - magicBytes: Uint8Array, - versionMaj: number, - versionMin: number, - trailingSize: number, - proofType: number, - } - - export interface PnauHeaderBuildOptions { - magicBytes?: Uint8Array, - versionMaj?: number, - versionMin?: number, - trailingSize?: number, - proofType?: number, - } - - export interface PnauBody { - vaa: Uint8Array, - pricesUpdates: PriceUpdateBatch, - pricesUpdatesToSubmit: Uint8Array[] - } - - export interface PriceUpdateBatch { - decoded: PriceUpdate[], - serialized: Uint8Array[], - hashed: Uint8Array[], - proofs: Uint8Array[][], - } - - export const BtcPriceIdentifier = Buffer.from('e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43', 'hex') - export const StxPriceIdentifier = Buffer.from('ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17', 'hex') - export const BatPriceIdentifer = Buffer.from('8e860fb74e60e5736b455d82f60b3728049c348e94961add5f961b02fdee2535', 'hex') - export const DaiPriceIdentifer = Buffer.from('b0948a5e5313200c632b51bb5ca32f6de0d36e9950a942d19751e833f70dabfd', 'hex') - export const UsdcPriceIdentifer = Buffer.from('eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a', 'hex') - export const UsdtPriceIdentifer = Buffer.from('2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b', 'hex') - export const WbtcPriceIdentifer = Buffer.from('c9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33', 'hex') - export const TbtcPriceIdentifer = Buffer.from('56a3121958b01f99fdc4e1fd01e81050602c7ace3a571918bb55c6a96657cca9', 'hex') - - export function serializePriceUpdateToClarityValue(priceUpdate: PriceUpdate): ClarityValue { - return Cl.tuple({ - "price-identifier": Cl.buffer(priceUpdate.priceIdentifier), - "conf": Cl.uint(priceUpdate.conf), - "ema-conf": Cl.uint(priceUpdate.emaConf), - "ema-price": Cl.int(priceUpdate.emaPrice), - "expo": Cl.int(priceUpdate.expo), - "prev-publish-time": Cl.uint(priceUpdate.prevPublishTime), - "price": Cl.int(priceUpdate.price), - "publish-time": Cl.uint(priceUpdate.publishTime) - }) - } - - export function preserializePriceUpdateToBuffer(priceUpdate: PriceUpdate): Uint8Array { - const components = []; - // Update type - var v = Buffer.alloc(1); - v.writeUint8(0, 0); - components.push(v); - components.push(priceUpdate.priceIdentifier); - components.push(bigintToBuffer(priceUpdate.price, 8)); - components.push(bigintToBuffer(priceUpdate.conf, 8)); - v = Buffer.alloc(4); - v.writeInt32BE(priceUpdate.expo, 0); - components.push(v); - components.push(bigintToBuffer(priceUpdate.publishTime, 8)); - components.push(bigintToBuffer(priceUpdate.prevPublishTime, 8)); - components.push(bigintToBuffer(priceUpdate.emaPrice, 8)); - components.push(bigintToBuffer(priceUpdate.emaConf, 8)); - return Buffer.concat(components); - } - - export function serializePriceUpdateToBuffer(priceUpdateData: Uint8Array, proof: Uint8Array[]): Uint8Array { - const components = []; - // Size - let messageSize = priceUpdateData.length; - var v = Buffer.alloc(2); - v.writeUint16BE(messageSize, 0); - components.push(v); - // Price update data - components.push(priceUpdateData) - // Proof size - var v = Buffer.alloc(1); - v.writeUint8(proof.length, 0); - components.push(v); - // Proof - components.push(...proof) - return Buffer.concat(components); - } - - export function serializeAuwvVaaPayloadToBuffer(payload: AuwvVaaPayload): Uint8Array { - const components = []; - // Magic bytes ('AUWV') - components.push(payload.payloadType); - // Update type - let v = Buffer.alloc(1); - v.writeUint8(payload.updateType, 0); - components.push(v); - // Merkle root slot - components.push(bigintToBuffer(payload.merkleRootSlot, 8)); - // Merkle ring size - v = Buffer.alloc(4); - v.writeUint16BE(payload.merkleRootRingSize, 0); - components.push(v); - // Merkle root - components.push(payload.merkleRootHash); - return Buffer.concat(components) + export interface PriceUpdate { + priceIdentifier: Uint8Array; + price: bigint; + conf: bigint; + emaPrice: bigint; + emaConf: bigint; + expo: number; + publishTime: bigint; + prevPublishTime: bigint; + } + + export interface PriceUpdateBuildOptions { + price?: bigint; + conf?: bigint; + emaPrice?: bigint; + emaConf?: bigint; + expo?: number; + publishTime?: bigint; + prevPublishTime?: bigint; + } + + export interface AuwvVaaPayload { + payloadType: Uint8Array; + updateType: number; + merkleRootSlot: bigint; + merkleRootRingSize: number; + merkleRootHash: Uint8Array; + } + + export interface AuwvVaaPayloadBuildOptions { + payloadType?: Uint8Array; + updateType?: number; + merkleRootSlot?: bigint; + merkleRootRingSize?: number; + merkleRootHash?: Uint8Array; + } + + export interface PnauHeader { + magicBytes: Uint8Array; + versionMaj: number; + versionMin: number; + trailingSize: number; + proofType: number; + } + + export interface PnauHeaderBuildOptions { + magicBytes?: Uint8Array; + versionMaj?: number; + versionMin?: number; + trailingSize?: number; + proofType?: number; + } + + export interface PnauBody { + vaa: Uint8Array; + pricesUpdates: PriceUpdateBatch; + pricesUpdatesToSubmit: Uint8Array[]; + } + + export interface PriceUpdateBatch { + decoded: PriceUpdate[]; + serialized: Uint8Array[]; + hashed: Uint8Array[]; + proofs: Uint8Array[][]; + } + + export const BtcPriceIdentifier = Buffer.from( + "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43", + "hex", + ); + export const StxPriceIdentifier = Buffer.from( + "ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17", + "hex", + ); + export const BatPriceIdentifer = Buffer.from( + "8e860fb74e60e5736b455d82f60b3728049c348e94961add5f961b02fdee2535", + "hex", + ); + export const DaiPriceIdentifer = Buffer.from( + "b0948a5e5313200c632b51bb5ca32f6de0d36e9950a942d19751e833f70dabfd", + "hex", + ); + export const UsdcPriceIdentifer = Buffer.from( + "eaa020c61cc479712813461ce153894a96a6c00b21ed0cfc2798d1f9a9e9c94a", + "hex", + ); + export const UsdtPriceIdentifer = Buffer.from( + "2b89b9dc8fdf9f34709a5b106b472f0f39bb6ca9ce04b0fd7f2e971688e2e53b", + "hex", + ); + export const WbtcPriceIdentifer = Buffer.from( + "c9d8b075a5c69303365ae23633d4e085199bf5c520a3b90fed1322a0342ffc33", + "hex", + ); + export const TbtcPriceIdentifer = Buffer.from( + "56a3121958b01f99fdc4e1fd01e81050602c7ace3a571918bb55c6a96657cca9", + "hex", + ); + + export function serializePriceUpdateToClarityValue( + priceUpdate: PriceUpdate, + ): ClarityValue { + return Cl.tuple({ + "price-identifier": Cl.buffer(priceUpdate.priceIdentifier), + conf: Cl.uint(priceUpdate.conf), + "ema-conf": Cl.uint(priceUpdate.emaConf), + "ema-price": Cl.int(priceUpdate.emaPrice), + expo: Cl.int(priceUpdate.expo), + "prev-publish-time": Cl.uint(priceUpdate.prevPublishTime), + price: Cl.int(priceUpdate.price), + "publish-time": Cl.uint(priceUpdate.publishTime), + }); + } + + export function preserializePriceUpdateToBuffer( + priceUpdate: PriceUpdate, + ): Uint8Array { + const components = []; + // Update type + var v = Buffer.alloc(1); + v.writeUint8(0, 0); + components.push(v); + components.push(priceUpdate.priceIdentifier); + components.push(bigintToBuffer(priceUpdate.price, 8)); + components.push(bigintToBuffer(priceUpdate.conf, 8)); + v = Buffer.alloc(4); + v.writeInt32BE(priceUpdate.expo, 0); + components.push(v); + components.push(bigintToBuffer(priceUpdate.publishTime, 8)); + components.push(bigintToBuffer(priceUpdate.prevPublishTime, 8)); + components.push(bigintToBuffer(priceUpdate.emaPrice, 8)); + components.push(bigintToBuffer(priceUpdate.emaConf, 8)); + return Buffer.concat(components); + } + + export function serializePriceUpdateToBuffer( + priceUpdateData: Uint8Array, + proof: Uint8Array[], + ): Uint8Array { + const components = []; + // Size + let messageSize = priceUpdateData.length; + var v = Buffer.alloc(2); + v.writeUint16BE(messageSize, 0); + components.push(v); + // Price update data + components.push(priceUpdateData); + // Proof size + var v = Buffer.alloc(1); + v.writeUint8(proof.length, 0); + components.push(v); + // Proof + components.push(...proof); + return Buffer.concat(components); + } + + export function serializeAuwvVaaPayloadToBuffer( + payload: AuwvVaaPayload, + ): Uint8Array { + const components = []; + // Magic bytes ('AUWV') + components.push(payload.payloadType); + // Update type + let v = Buffer.alloc(1); + v.writeUint8(payload.updateType, 0); + components.push(v); + // Merkle root slot + components.push(bigintToBuffer(payload.merkleRootSlot, 8)); + // Merkle ring size + v = Buffer.alloc(4); + v.writeUint16BE(payload.merkleRootRingSize, 0); + components.push(v); + // Merkle root + components.push(payload.merkleRootHash); + return Buffer.concat(components); + } + + export function buildPriceUpdateBatch( + pricesUpdatesSpecs: [ + priceIdentifier: Uint8Array, + opts?: PriceUpdateBuildOptions, + ][], + ): PriceUpdateBatch { + let decoded = []; + let serialized = []; + let hashed = []; + for (let [priceIdentifier, opts] of pricesUpdatesSpecs) { + let p = buildPriceUpdate(priceIdentifier, opts); + let s = preserializePriceUpdateToBuffer(p); + decoded.push(p); + serialized.push(s); + hashed.push(keccak160HashLeaf(s)); } - - export function buildPriceUpdateBatch(pricesUpdatesSpecs: [priceIdentifier: Uint8Array, opts?: PriceUpdateBuildOptions][]): PriceUpdateBatch { - let decoded = []; - let serialized = []; - let hashed = []; - for (let [priceIdentifier, opts] of pricesUpdatesSpecs) { - let p = buildPriceUpdate(priceIdentifier, opts); - let s = preserializePriceUpdateToBuffer(p); - decoded.push(p) - serialized.push(s) - hashed.push(keccak160HashLeaf(s)) - } - let proofs = []; - for (let hash of hashed) { - proofs.push(computeMerkleProof(hash, hashed)) - } - return { - decoded, - serialized, - hashed, - proofs - } + let proofs = []; + for (let hash of hashed) { + proofs.push(computeMerkleProof(hash, hashed)); } - - export function buildAuwvVaaPayload(batch: PriceUpdateBatch, merkleRootData?: AuwvVaaPayloadBuildOptions) { - let merkleLeaves = [...batch.hashed]; - // Compute merkle tree - while (merkleLeaves.length > 1) { - let newLeaves = []; - // Loop through adjacent pairs - for (let i = 0; i < merkleLeaves.length; i += 2) { - const leftLeaf = merkleLeaves[i]; - // Duplicate the last one if odd number of leaves - const rightLeaf = merkleLeaves[i + 1] || leftLeaf; - newLeaves.push(keccak160HashNodes(leftLeaf, rightLeaf)); - } - merkleLeaves = newLeaves; - } - - return { - payloadType: merkleRootData?.payloadType || new Uint8Array(Buffer.from('41555756', 'hex')), - updateType: merkleRootData?.updateType || 0, - merkleRootRingSize: merkleRootData?.merkleRootRingSize || 0, - merkleRootSlot: merkleRootData?.merkleRootSlot || 0n, - merkleRootHash: merkleLeaves[0] - } + return { + decoded, + serialized, + hashed, + proofs, + }; + } + + export function buildAuwvVaaPayload( + batch: PriceUpdateBatch, + merkleRootData?: AuwvVaaPayloadBuildOptions, + ) { + let merkleLeaves = [...batch.hashed]; + // Compute merkle tree + while (merkleLeaves.length > 1) { + let newLeaves = []; + // Loop through adjacent pairs + for (let i = 0; i < merkleLeaves.length; i += 2) { + const leftLeaf = merkleLeaves[i]; + // Duplicate the last one if odd number of leaves + const rightLeaf = merkleLeaves[i + 1] || leftLeaf; + newLeaves.push(keccak160HashNodes(leftLeaf, rightLeaf)); + } + merkleLeaves = newLeaves; } - export function buildPnauHeader(opts?: PnauHeaderBuildOptions) { - return { - magicBytes: opts?.magicBytes || new Uint8Array(Buffer.from('504e4155', 'hex')), - versionMaj: opts?.versionMaj || 1, - versionMin: opts?.versionMin || 0, - trailingSize: opts?.trailingSize || 0, - proofType: opts?.proofType || 0, - } + return { + payloadType: + merkleRootData?.payloadType || + new Uint8Array(Buffer.from("41555756", "hex")), + updateType: merkleRootData?.updateType || 0, + merkleRootRingSize: merkleRootData?.merkleRootRingSize || 0, + merkleRootSlot: merkleRootData?.merkleRootSlot || 0n, + merkleRootHash: merkleLeaves[0], + }; + } + + export function buildPnauHeader(opts?: PnauHeaderBuildOptions) { + return { + magicBytes: + opts?.magicBytes || new Uint8Array(Buffer.from("504e4155", "hex")), + versionMaj: opts?.versionMaj || 1, + versionMin: opts?.versionMin || 0, + trailingSize: opts?.trailingSize || 0, + proofType: opts?.proofType || 0, + }; + } + + export function serializePnauToBuffer( + pnauHeader: PnauHeader, + pnauBody: PnauBody, + ) { + const components = []; + // Magic bytes + components.push(pnauHeader.magicBytes); + // Version Maj + let v = Buffer.alloc(1); + v.writeUint8(pnauHeader.versionMaj, 0); + components.push(v); + // Version Min + v = Buffer.alloc(1); + v.writeUint8(pnauHeader.versionMin, 0); + components.push(v); + // Trailing header + v = Buffer.alloc(1); + v.writeUint8(pnauHeader.trailingSize, 0); + components.push(v); + // Proof type + v = Buffer.alloc(1); + v.writeUint8(pnauHeader.proofType, 0); + components.push(v); + // VAA size + v = Buffer.alloc(2); + v.writeUint16BE(pnauBody.vaa.length, 0); + components.push(v); + // Vaa + components.push(pnauBody.vaa); + // Number of prices updates + v = Buffer.alloc(1); + v.writeUint8(pnauBody.pricesUpdatesToSubmit.length, 0); + components.push(v); + // Loop on prices updates + for (let i = 0; i < pnauBody.pricesUpdates.serialized.length; i++) { + if ( + pnauBody.pricesUpdatesToSubmit.includes( + pnauBody.pricesUpdates.decoded[i].priceIdentifier, + ) + ) { + let priceUpdateData = pnauBody.pricesUpdates.serialized[i]; + components.push( + serializePriceUpdateToBuffer( + priceUpdateData, + pnauBody.pricesUpdates.proofs[i], + ), + ); + } } - - export function serializePnauToBuffer(pnauHeader: PnauHeader, pnauBody: PnauBody) { - const components = []; - // Magic bytes - components.push(pnauHeader.magicBytes); - // Version Maj - let v = Buffer.alloc(1); - v.writeUint8(pnauHeader.versionMaj, 0); - components.push(v); - // Version Min - v = Buffer.alloc(1); - v.writeUint8(pnauHeader.versionMin, 0); - components.push(v); - // Trailing header - v = Buffer.alloc(1); - v.writeUint8(pnauHeader.trailingSize, 0); - components.push(v); - // Proof type - v = Buffer.alloc(1); - v.writeUint8(pnauHeader.proofType, 0); - components.push(v); - // VAA size - v = Buffer.alloc(2); - v.writeUint16BE(pnauBody.vaa.length, 0); - components.push(v); - // Vaa - components.push(pnauBody.vaa); - // Number of prices updates - v = Buffer.alloc(1); - v.writeUint8(pnauBody.pricesUpdatesToSubmit.length, 0); - components.push(v); - // Loop on prices updates - for (let i = 0; i < pnauBody.pricesUpdates.serialized.length; i++) { - if (pnauBody.pricesUpdatesToSubmit.includes(pnauBody.pricesUpdates.decoded[i].priceIdentifier)) { - let priceUpdateData = pnauBody.pricesUpdates.serialized[i]; - components.push(serializePriceUpdateToBuffer(priceUpdateData, pnauBody.pricesUpdates.proofs[i])) - } - } - return Buffer.concat(components) + return Buffer.concat(components); + } + + function keccak160HashNodes( + node1: Uint8Array, + node2: Uint8Array, + ): Uint8Array { + let prefix = new Uint8Array([1]); + if (bufferToBigint(node2) < bufferToBigint(node1)) { + return keccak_256(Buffer.concat([prefix, node2, node1])).slice(0, 20); + } else { + return keccak_256(Buffer.concat([prefix, node1, node2])).slice(0, 20); } - - function keccak160HashNodes(node1: Uint8Array, node2: Uint8Array): Uint8Array { - let prefix = new Uint8Array([1]); - if (bufferToBigint(node2) < bufferToBigint(node1)) { - return keccak_256(Buffer.concat([prefix, node2, node1])).slice(0, 20) - } else { - return keccak_256(Buffer.concat([prefix, node1, node2])).slice(0, 20) - } - } - - function keccak160HashLeaf(leaf: Uint8Array): Uint8Array { - let prefix = new Uint8Array([0]); - return keccak_256(Buffer.concat([prefix, leaf])).slice(0, 20) - } - - function computeMerkleProof(targetLeaf: Uint8Array, batch: Uint8Array[]): Uint8Array[] { - let merkleLeaves = [...batch]; - const proof = []; - let targetHash = targetLeaf; - - while (merkleLeaves.length > 1) { - let newLeaves: Uint8Array[] = []; - for (let i = 0; i < merkleLeaves.length; i += 2) { - const leftLeaf = merkleLeaves[i]; - const rightLeaf = merkleLeaves[i + 1] || leftLeaf; // Duplicate the last one if odd number of leaves - const parentNode = keccak160HashNodes(leftLeaf, rightLeaf); - newLeaves.push(parentNode); - - // Capture the sibling for the proof if either leftLeaf or rightLeaf is the target - if (leftLeaf === targetHash) { - proof.push(rightLeaf); - targetHash = parentNode; - } else if (rightLeaf === targetHash) { - proof.push(leftLeaf); - targetHash = parentNode; - } - } - merkleLeaves = newLeaves; - } - return proof; - } - - export function buildPriceUpdate(priceIdentifier: Uint8Array, opts?: PriceUpdateBuildOptions): PriceUpdate { - return { - priceIdentifier: priceIdentifier, - price: opts?.price || 100n, - conf: opts?.conf || 10n, - emaPrice: opts?.emaPrice || 95n, - emaConf: opts?.emaConf || 9n, - expo: -4, - publishTime: opts?.publishTime || 10000001n, - prevPublishTime: opts?.prevPublishTime || 10000000n, - } - } - - export namespace fc_ext { - - export const priceUpdate = (opts?: PriceUpdateBuildOptions) => { - // price - let price = fc.bigIntN(64); - if (opts && opts.price) { - price = fc.constant(opts.price); - } - - // conf - let conf = fc.bigUintN(64); - if (opts && opts.conf) { - conf = fc.constant(opts.conf); - } - - // emaPrice - let emaPrice = fc.bigIntN(64); - if (opts && opts.emaPrice) { - emaPrice = fc.constant(opts.emaPrice); - } - - // emaConf - let emaConf = fc.bigUintN(64); - if (opts && opts.emaConf) { - emaConf = fc.constant(opts.emaConf); - } - - // expo - let expo = fc.nat(4294967295); - if (opts && opts.expo) { - expo = fc.constant(opts.expo); - } - - // prevPublishTime - let prevPublishTime = fc.bigUintN(64); - if (opts && opts.prevPublishTime) { - prevPublishTime = fc.constant(opts.prevPublishTime); - } - - // prevPublishTime - let publishTime = prevPublishTime.chain((t: bigint) => fc.constant(t + 10n)); - if (opts && opts.publishTime) { - publishTime = fc.constant(opts.publishTime); - } - - return fc.tuple(price, conf, emaPrice, emaConf, expo, prevPublishTime, publishTime); + } + + function keccak160HashLeaf(leaf: Uint8Array): Uint8Array { + let prefix = new Uint8Array([0]); + return keccak_256(Buffer.concat([prefix, leaf])).slice(0, 20); + } + + function computeMerkleProof( + targetLeaf: Uint8Array, + batch: Uint8Array[], + ): Uint8Array[] { + let merkleLeaves = [...batch]; + const proof = []; + let targetHash = targetLeaf; + + while (merkleLeaves.length > 1) { + let newLeaves: Uint8Array[] = []; + for (let i = 0; i < merkleLeaves.length; i += 2) { + const leftLeaf = merkleLeaves[i]; + const rightLeaf = merkleLeaves[i + 1] || leftLeaf; // Duplicate the last one if odd number of leaves + const parentNode = keccak160HashNodes(leftLeaf, rightLeaf); + newLeaves.push(parentNode); + + // Capture the sibling for the proof if either leftLeaf or rightLeaf is the target + if (leftLeaf === targetHash) { + proof.push(rightLeaf); + targetHash = parentNode; + } else if (rightLeaf === targetHash) { + proof.push(leftLeaf); + targetHash = parentNode; } + } + merkleLeaves = newLeaves; } + return proof; + } + + export function buildPriceUpdate( + priceIdentifier: Uint8Array, + opts?: PriceUpdateBuildOptions, + ): PriceUpdate { + return { + priceIdentifier: priceIdentifier, + price: opts?.price || 100n, + conf: opts?.conf || 10n, + emaPrice: opts?.emaPrice || 95n, + emaConf: opts?.emaConf || 9n, + expo: -4, + publishTime: opts?.publishTime || 10000001n, + prevPublishTime: opts?.prevPublishTime || 10000000n, + }; + } + + export namespace fc_ext { + export const priceUpdate = (opts?: PriceUpdateBuildOptions) => { + // price + let price = fc.bigIntN(64); + if (opts && opts.price) { + price = fc.constant(opts.price); + } + + // conf + let conf = fc.bigUintN(64); + if (opts && opts.conf) { + conf = fc.constant(opts.conf); + } + + // emaPrice + let emaPrice = fc.bigIntN(64); + if (opts && opts.emaPrice) { + emaPrice = fc.constant(opts.emaPrice); + } + + // emaConf + let emaConf = fc.bigUintN(64); + if (opts && opts.emaConf) { + emaConf = fc.constant(opts.emaConf); + } + + // expo + let expo = fc.nat(4294967295); + if (opts && opts.expo) { + expo = fc.constant(opts.expo); + } + + // prevPublishTime + let prevPublishTime = fc.bigUintN(64); + if (opts && opts.prevPublishTime) { + prevPublishTime = fc.constant(opts.prevPublishTime); + } + + // prevPublishTime + let publishTime = prevPublishTime.chain((t: bigint) => + fc.constant(t + 10n), + ); + if (opts && opts.publishTime) { + publishTime = fc.constant(opts.publishTime); + } + + return fc.tuple( + price, + conf, + emaPrice, + emaConf, + expo, + prevPublishTime, + publishTime, + ); + }; + } } diff --git a/unit-tests/pyth/oracle.test.ts b/unit-tests/pyth/oracle.test.ts index 223aaa6..fa9d04d 100644 --- a/unit-tests/pyth/oracle.test.ts +++ b/unit-tests/pyth/oracle.test.ts @@ -10,66 +10,83 @@ const pythStorageContractName = "pyth-store-v1"; const wormholeCoreContractName = "wormhole-core-v1"; describe("pyth-oracle-v1::decode-and-verify-price-feeds mainnet VAAs", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; - let block: ParsedTransactionResult[] | undefined = undefined; + let block: ParsedTransactionResult[] | undefined = undefined; - // Before starting the test suite, we have to setup the guardian set. - beforeEach(async () => { - block = wormhole.applyMainnetGuardianSetUpdates(sender, wormholeCoreContractName) - }) + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + block = wormhole.applyMainnetGuardianSetUpdates( + sender, + wormholeCoreContractName, + ); + }); - it("should succeed handling the 3 guardians rotations", () => { - expect(block!).toHaveLength(3); - block!.forEach((b: ParsedTransactionResult) => { - expect(b.result).toHaveClarityType(ClarityType.ResponseOk); - }); + it("should succeed handling the 3 guardians rotations", () => { + expect(block!).toHaveLength(3); + block!.forEach((b: ParsedTransactionResult) => { + expect(b.result).toHaveClarityType(ClarityType.ResponseOk); }); + }); - it("should succeed handling PNAU mainnet payloads", () => { - const pnauBytes = Cl.bufferFromHex(pnauMainnetVaas[0]); - let priceIdentifier = Cl.bufferFromHex('ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17'); - let priceUpdated = Cl.tuple({ - "price-identifier": priceIdentifier, - "conf": Cl.uint(37359), - "ema-conf": Cl.uint(36191), - "ema-price": Cl.int(46167004), - "expo": Cl.int(-8), - "prev-publish-time": Cl.uint(1695751648), - "price": Cl.int(46098556), - "publish-time": Cl.uint(1695751649) - }) - let executionPlan = Cl.tuple({ - 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), - 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pythDecoderPnauContractName), - 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormholeCoreContractName), - }); + it("should succeed handling PNAU mainnet payloads", () => { + const pnauBytes = Cl.bufferFromHex(pnauMainnetVaas[0]); + let priceIdentifier = Cl.bufferFromHex( + "ec7a775f46379b5e943c3526b1c8d54cd49749176b0b98e02dde68d1bd335c17", + ); + let priceUpdated = Cl.tuple({ + "price-identifier": priceIdentifier, + conf: Cl.uint(37359), + "ema-conf": Cl.uint(36191), + "ema-price": Cl.int(46167004), + expo: Cl.int(-8), + "prev-publish-time": Cl.uint(1695751648), + price: Cl.int(46098556), + "publish-time": Cl.uint(1695751649), + }); + let executionPlan = Cl.tuple({ + "pyth-storage-contract": Cl.contractPrincipal( + simnet.deployer, + pythStorageContractName, + ), + "pyth-decoder-contract": Cl.contractPrincipal( + simnet.deployer, + pythDecoderPnauContractName, + ), + "wormhole-core-contract": Cl.contractPrincipal( + simnet.deployer, + wormholeCoreContractName, + ), + }); - let res = simnet.callPublicFn( - pythOracleContractName, - "verify-and-update-price-feeds", - [pnauBytes, executionPlan], - sender - ).result; - expect(res).toBeOk( - Cl.list([priceUpdated]) - ) + let res = simnet.callPublicFn( + pythOracleContractName, + "verify-and-update-price-feeds", + [pnauBytes, executionPlan], + sender, + ).result; + expect(res).toBeOk(Cl.list([priceUpdated])); - res = simnet.callPublicFn( - pythOracleContractName, - "read-price-feed", - [priceIdentifier, Cl.contractPrincipal(simnet.deployer, pythStorageContractName)], - sender - ).result; - expect(res).toBeOk(Cl.tuple({ - "conf": Cl.uint(37359), - "ema-conf": Cl.uint(36191), - "ema-price": Cl.int(46167004), - "expo": Cl.int(-8), - "prev-publish-time": Cl.uint(1695751648), - "price": Cl.int(46098556), - "publish-time": Cl.uint(1695751649) - })) - }); + res = simnet.callPublicFn( + pythOracleContractName, + "read-price-feed", + [ + priceIdentifier, + Cl.contractPrincipal(simnet.deployer, pythStorageContractName), + ], + sender, + ).result; + expect(res).toBeOk( + Cl.tuple({ + conf: Cl.uint(37359), + "ema-conf": Cl.uint(36191), + "ema-price": Cl.int(46167004), + expo: Cl.int(-8), + "prev-publish-time": Cl.uint(1695751648), + price: Cl.int(46098556), + "publish-time": Cl.uint(1695751649), + }), + ); + }); }); diff --git a/unit-tests/pyth/pnau.test.ts b/unit-tests/pyth/pnau.test.ts index fd3f3a4..e0e5b27 100644 --- a/unit-tests/pyth/pnau.test.ts +++ b/unit-tests/pyth/pnau.test.ts @@ -9,86 +9,116 @@ const pythStorageContractName = "pyth-store-v1"; const wormholeCoreContractName = "wormhole-core-v1"; describe("pyth-pnau-decoder-v1::decode-and-verify-price-feeds success", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - const guardianSet = wormhole.generateGuardianSetKeychain(19); - let pricesUpdates = pyth.buildPriceUpdateBatch([ - [pyth.BtcPriceIdentifier], - [pyth.StxPriceIdentifier], - [pyth.BatPriceIdentifer], - [pyth.DaiPriceIdentifer], - [pyth.TbtcPriceIdentifer], - [pyth.UsdcPriceIdentifer], - [pyth.UsdtPriceIdentifer], - [pyth.WbtcPriceIdentifer] - ]) - let pricesUpdatesToSubmit = [pyth.BtcPriceIdentifier, pyth.StxPriceIdentifier, pyth.UsdcPriceIdentifer]; - let pricesUpdatesVaaPayload = pyth.buildAuwvVaaPayload(pricesUpdates); + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const guardianSet = wormhole.generateGuardianSetKeychain(19); + let pricesUpdates = pyth.buildPriceUpdateBatch([ + [pyth.BtcPriceIdentifier], + [pyth.StxPriceIdentifier], + [pyth.BatPriceIdentifer], + [pyth.DaiPriceIdentifer], + [pyth.TbtcPriceIdentifer], + [pyth.UsdcPriceIdentifer], + [pyth.UsdtPriceIdentifer], + [pyth.WbtcPriceIdentifer], + ]); + let pricesUpdatesToSubmit = [ + pyth.BtcPriceIdentifier, + pyth.StxPriceIdentifier, + pyth.UsdcPriceIdentifer, + ]; + let pricesUpdatesVaaPayload = pyth.buildAuwvVaaPayload(pricesUpdates); - // Before starting the test suite, we have to setup the guardian set. - beforeEach(async () => { - wormhole.applyGuardianSetUpdate(guardianSet, 1, sender, wormholeCoreContractName) - }) + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + wormhole.applyGuardianSetUpdate( + guardianSet, + 1, + sender, + wormholeCoreContractName, + ); + }); - it("should parse and verify the Vaa a a Pnau message", () => { - let payload = pyth.serializeAuwvVaaPayloadToBuffer(pricesUpdatesVaaPayload); - let body = wormhole.buildValidVaaBodySpecs({ payload }); - let header = wormhole.buildValidVaaHeader(guardianSet, body, { version: 1, guardianSetId: 1 }); - let vaa = wormhole.serializeVaaToBuffer(header, body); - - let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet); - const res = simnet.callReadOnlyFn( - wormholeCoreContractName, - `parse-and-verify-vaa`, - [Cl.buffer(vaa)], - sender - ); - expect(res.result).toBeOk(decodedVaa) + it("should parse and verify the Vaa a a Pnau message", () => { + let payload = pyth.serializeAuwvVaaPayloadToBuffer(pricesUpdatesVaaPayload); + let body = wormhole.buildValidVaaBodySpecs({ payload }); + let header = wormhole.buildValidVaaHeader(guardianSet, body, { + version: 1, + guardianSetId: 1, }); + let vaa = wormhole.serializeVaaToBuffer(header, body); - it("should produce a correct verifiable empty vaa", () => { - let payload = pyth.serializeAuwvVaaPayloadToBuffer(pricesUpdatesVaaPayload); - let vaaBody = wormhole.buildValidVaaBodySpecs({ payload }); - let vaaHeader = wormhole.buildValidVaaHeader(guardianSet, vaaBody, { version: 1, guardianSetId: 1 }); - let vaa = wormhole.serializeVaaToBuffer(vaaHeader, vaaBody); - let pnauHeader = pyth.buildPnauHeader(); - let pnau = pyth.serializePnauToBuffer(pnauHeader, { vaa, pricesUpdates, pricesUpdatesToSubmit }); - - let executionPlan = Cl.tuple({ - 'pyth-storage-contract': Cl.contractPrincipal(simnet.deployer, pythStorageContractName), - 'pyth-decoder-contract': Cl.contractPrincipal(simnet.deployer, pythDecoderPnauContractName), - 'wormhole-core-contract': Cl.contractPrincipal(simnet.deployer, wormholeCoreContractName), - }); - const res = simnet.callPublicFn( - pythOracleContractName, - "verify-and-update-price-feeds", - [Cl.buffer(pnau), executionPlan], - sender - ); + let [decodedVaa, _] = wormhole.serializeVaaToClarityValue( + header, + body, + guardianSet, + ); + const res = simnet.callReadOnlyFn( + wormholeCoreContractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender, + ); + expect(res.result).toBeOk(decodedVaa); + }); - expect(res.result).toHaveClarityType(ClarityType.ResponseOk); + it("should produce a correct verifiable empty vaa", () => { + let payload = pyth.serializeAuwvVaaPayloadToBuffer(pricesUpdatesVaaPayload); + let vaaBody = wormhole.buildValidVaaBodySpecs({ payload }); + let vaaHeader = wormhole.buildValidVaaHeader(guardianSet, vaaBody, { + version: 1, + guardianSetId: 1, + }); + let vaa = wormhole.serializeVaaToBuffer(vaaHeader, vaaBody); + let pnauHeader = pyth.buildPnauHeader(); + let pnau = pyth.serializePnauToBuffer(pnauHeader, { + vaa, + pricesUpdates, + pricesUpdatesToSubmit, }); + let executionPlan = Cl.tuple({ + "pyth-storage-contract": Cl.contractPrincipal( + simnet.deployer, + pythStorageContractName, + ), + "pyth-decoder-contract": Cl.contractPrincipal( + simnet.deployer, + pythDecoderPnauContractName, + ), + "wormhole-core-contract": Cl.contractPrincipal( + simnet.deployer, + wormholeCoreContractName, + ), + }); + const res = simnet.callPublicFn( + pythOracleContractName, + "verify-and-update-price-feeds", + [Cl.buffer(pnau), executionPlan], + sender, + ); - // it("should produced a correct empty vaa", () => { - // let priceUpdateBatch = pyth.buildPriceUpdateBatch([ - // [pyth.BtcPriceIdentifier], - // [pyth.StxPriceIdentifier] - // ]) - // let payload = pyth.buildPnauVaa(priceUpdateBatch) - // let body = wormhole.buildValidVaaBodySpecs({ payload }); - // let header = wormhole.buildValidVaaHeader(guardianSet, body, { version: 1, guardianSetId: 1 }); - // let vaa = wormhole.serializeVaaToBuffer(header, body); + expect(res.result).toHaveClarityType(ClarityType.ResponseOk); + }); - // let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet); + // it("should produced a correct empty vaa", () => { + // let priceUpdateBatch = pyth.buildPriceUpdateBatch([ + // [pyth.BtcPriceIdentifier], + // [pyth.StxPriceIdentifier] + // ]) + // let payload = pyth.buildPnauVaa(priceUpdateBatch) + // let body = wormhole.buildValidVaaBodySpecs({ payload }); + // let header = wormhole.buildValidVaaHeader(guardianSet, body, { version: 1, guardianSetId: 1 }); + // let vaa = wormhole.serializeVaaToBuffer(header, body); - // const res = simnet.callReadOnlyFn( - // wormholeCoreContractName, - // `parse-and-verify-vaa`, - // [Cl.buffer(vaa)], - // sender - // ); - // expect(res.result).toBeOk(decodedVaa) - // }); + // let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet); + // const res = simnet.callReadOnlyFn( + // wormholeCoreContractName, + // `parse-and-verify-vaa`, + // [Cl.buffer(vaa)], + // sender + // ); + // expect(res.result).toBeOk(decodedVaa) + // }); }); diff --git a/unit-tests/utils/cursor.test.ts b/unit-tests/utils/cursor.test.ts index 072eb1f..ad83a26 100644 --- a/unit-tests/utils/cursor.test.ts +++ b/unit-tests/utils/cursor.test.ts @@ -1,605 +1,669 @@ import { Cl } from "@stacks/transactions"; import { describe, expect } from "vitest"; -import { it, fc } from '@fast-check/vitest'; -import { concatTypedArrays, uint8toBytes, uint16toBytes, uint32toBytes, bigintToBuffer } from './helpers'; +import { it, fc } from "@fast-check/vitest"; +import { + concatTypedArrays, + uint8toBytes, + uint16toBytes, + uint32toBytes, + bigintToBuffer, +} from "./helpers"; const cursor_contract_name = "hk-cursor-v1"; describe("hiro-kit::cursor - buffers", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - - const buff_n = (n: number) => { - return fc.uint8Array({ minLength: 0, maxLength: n }) + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + + const buff_n = (n: number) => { + return fc.uint8Array({ minLength: 0, maxLength: n }); + }; + + const bytesToRead = () => { + return fc.constantFrom(1, 2, 4, 8, 16, 20, 64, 65); + }; + + const bytesToGenerate = (n: number) => { + return fc.uint8Array({ minLength: 0, maxLength: n }); + }; + + it.prop([buff_n(8192)])("new, no offset", (numbers) => { + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `new`, + [Cl.buffer(numbers), Cl.none()], + sender, + ); + expect(res.result).toBeTuple({ + value: Cl.none(), + next: Cl.tuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(0), + }), + }); + }); + + it.prop([buff_n(8192), fc.nat()])("new, offset", (numbers, offset) => { + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `new`, + [Cl.buffer(numbers), Cl.some(Cl.uint(offset))], + sender, + ); + expect(res.result).toBeTuple({ + value: Cl.none(), + next: Cl.tuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(offset), + }), + }); + }); + + it.prop([buff_n(8192), fc.nat()])("advance", (numbers, offset) => { + let cursor = Cl.tuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(0), + }); + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `advance`, + [cursor, Cl.uint(offset)], + sender, + ); + expect(res.result).toBeTuple({ + bytes: Cl.buffer(numbers), + pos: Cl.uint(offset), + }); + }); + + it.prop([buff_n(8192), fc.nat(2048), fc.nat(2048)])( + "slice", + (bytes, pos, size) => { + let cursor = Cl.tuple({ + bytes: Cl.buffer(bytes), + pos: Cl.uint(pos), + }); + + if (pos + size < bytes.length) { + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `slice`, + [cursor, Cl.some(Cl.uint(size))], + sender, + ); + expect(res.result).toBeBuff(bytes.slice(pos, pos + size)); + } + }, + ); + + it.prop([buff_n(8192)])("read-buff-1", (numbers) => { + var data = new Uint8Array(); + for (let n of numbers) { + data = concatTypedArrays(data, uint8toBytes(n)); } - - const bytesToRead = () => { - return fc.constantFrom(1, 2, 4, 8, 16, 20, 64, 65) + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 1; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-buff-1", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.buffer(uint8toBytes(n)), + next: nextInput, + }), + ); } - - const bytesToGenerate = (n: number) => { - return fc.uint8Array({ minLength: 0, maxLength: n }) + }); + + it.prop([fc.uniqueArray(bytesToRead())])("read-buff-n", (buffer) => { + var data = new Uint8Array(); + let segments = []; + for (let value of buffer) { + var segment = new Uint8Array(); + for (let i = 0; i < value; i++) { + segment = concatTypedArrays(segment, uint8toBytes(value)); + } + segments.push(segment); + data = concatTypedArrays(data, segment); } - it.prop([buff_n(8192)])("new, no offset", (numbers) => { - let res = simnet.callReadOnlyFn( - cursor_contract_name, - `new`, - [Cl.buffer(numbers), Cl.none()], - sender - ); - expect(res.result).toBeTuple({ - "value": Cl.none(), - "next": Cl.tuple({ - bytes: Cl.buffer(numbers), - pos: Cl.uint(0) - }) - }) - }) - - it.prop([buff_n(8192), fc.nat()])("new, offset", (numbers, offset) => { - let res = simnet.callReadOnlyFn( - cursor_contract_name, - `new`, - [Cl.buffer(numbers), Cl.some(Cl.uint(offset))], - sender - ); - expect(res.result).toBeTuple({ - "value": Cl.none(), - "next": Cl.tuple({ - bytes: Cl.buffer(numbers), - pos: Cl.uint(offset) - }) - }) - }) - - it.prop([buff_n(8192), fc.nat()])("advance", (numbers, offset) => { - let cursor = Cl.tuple({ - bytes: Cl.buffer(numbers), - pos: Cl.uint(0) - }) - let res = simnet.callReadOnlyFn( - cursor_contract_name, - `advance`, - [cursor, Cl.uint(offset)], - sender - ); - expect(res.result).toBeTuple({ - bytes: Cl.buffer(numbers), - pos: Cl.uint(offset) - }) - }) - - it.prop([buff_n(8192), fc.nat(2048), fc.nat(2048)])("slice", (bytes, pos, size) => { - let cursor = Cl.tuple({ - bytes: Cl.buffer(bytes), - pos: Cl.uint(pos) - }) - - if (pos + size < bytes.length) { - let res = simnet.callReadOnlyFn( - cursor_contract_name, - `slice`, - [cursor, Cl.some(Cl.uint(size))], - sender - ); - expect(res.result).toBeBuff(bytes.slice(pos, pos+size)) - } - }) - - it.prop([buff_n(8192)])("read-buff-1", (numbers) => { - var data = new Uint8Array(); - for (let n of numbers) { - data = concatTypedArrays(data, uint8toBytes(n)); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 1; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-buff-1", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.buffer(uint8toBytes(n)), - next: nextInput - })) - } - }) - - it.prop([fc.uniqueArray(bytesToRead())])("read-buff-n", (buffer) => { - var data = new Uint8Array(); - let segments = []; - for (let value of buffer) { - var segment = new Uint8Array(); - for (let i = 0; i < value; i++) { - segment = concatTypedArrays(segment, uint8toBytes(value)); - } - segments.push(segment); - data = concatTypedArrays(data, segment); - } - - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let segment of segments) { - let len = segment.length; - pos += len; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - `read-buff-${len}`, - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.buffer(segment), - next: nextInput - })) - } - }) - - it.prop([bytesToRead(), bytesToGenerate(8192)])("read-buff-8192-max and slice", (toRead, toGenerate) => { - var data = new Uint8Array(); - for (let n of toGenerate) { - data = concatTypedArrays(data, uint8toBytes(n)); - } - let input = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let res = simnet.callReadOnlyFn( - cursor_contract_name, - `read-buff-${toRead}`, - [input], - sender - ); - - // Early return: we're trying to read more byte than the len of the buffer - if (toRead > data.length) { - expect(res.result).toBeErr(Cl.uint(1)) - return; - } - - let expectedCursor = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(toRead) - }); - - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.buffer(data.subarray(0, toRead)), - next: expectedCursor - })) - - res = simnet.callReadOnlyFn( - cursor_contract_name, - `read-buff-8192-max`, - [expectedCursor, Cl.none()], - sender - ); - - // Early return: we're trying to read more byte than the len of the buffer - if (toRead == data.length) { - expect(res.result).toBeErr(Cl.uint(1)) - return; - } - - let finalCursor = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(data.length) - }); - - let expectedBuffer = Cl.buffer(data.subarray(toRead, data.length)); - - expect(res.result).toBeOk(Cl.tuple({ - value: expectedBuffer, - next: finalCursor - })) - - res = simnet.callReadOnlyFn( - cursor_contract_name, - `slice`, - [expectedCursor, Cl.none()], - sender - ); - expect(Cl.ok(res.result)).toBeOk(expectedBuffer); - }) - - it.prop([bytesToRead(), bytesToRead(), bytesToGenerate(8192)])("read-buff-8192-max with limit and slice", (toRead, toIsolate, toGenerate) => { - var data = new Uint8Array(); - for (let n of toGenerate) { - data = concatTypedArrays(data, uint8toBytes(n)); - } - let input = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let res = simnet.callReadOnlyFn( - cursor_contract_name, - `read-buff-${toRead}`, - [input], - sender - ); - - // Early return: we're trying to read more byte than the len of the buffer - if (toRead > data.length) { - expect(res.result).toBeErr(Cl.uint(1)) - return; - } - - let expectedCursor = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(toRead) - }); - - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.buffer(data.subarray(0, toRead)), - next: expectedCursor - })) - - res = simnet.callReadOnlyFn( - cursor_contract_name, - `read-buff-8192-max`, - [expectedCursor, Cl.some(Cl.uint(toIsolate))], - sender - ); - - // Early return: we're tryint to read more byte than the len of the buffer - if (toRead + toIsolate > data.length) { - expect(res.result).toBeErr(Cl.uint(1)) - return; - } - - let finalCursor = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(toRead + toIsolate) - }); + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let segment of segments) { + let len = segment.length; + pos += len; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `read-buff-${len}`, + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.buffer(segment), + next: nextInput, + }), + ); + } + }); + + it.prop([bytesToRead(), bytesToGenerate(8192)])( + "read-buff-8192-max and slice", + (toRead, toGenerate) => { + var data = new Uint8Array(); + for (let n of toGenerate) { + data = concatTypedArrays(data, uint8toBytes(n)); + } + let input = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `read-buff-${toRead}`, + [input], + sender, + ); + + // Early return: we're trying to read more byte than the len of the buffer + if (toRead > data.length) { + expect(res.result).toBeErr(Cl.uint(1)); + return; + } + + let expectedCursor = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(toRead), + }); + + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.buffer(data.subarray(0, toRead)), + next: expectedCursor, + }), + ); + + res = simnet.callReadOnlyFn( + cursor_contract_name, + `read-buff-8192-max`, + [expectedCursor, Cl.none()], + sender, + ); + + // Early return: we're trying to read more byte than the len of the buffer + if (toRead == data.length) { + expect(res.result).toBeErr(Cl.uint(1)); + return; + } + + let finalCursor = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(data.length), + }); + + let expectedBuffer = Cl.buffer(data.subarray(toRead, data.length)); + + expect(res.result).toBeOk( + Cl.tuple({ + value: expectedBuffer, + next: finalCursor, + }), + ); + + res = simnet.callReadOnlyFn( + cursor_contract_name, + `slice`, + [expectedCursor, Cl.none()], + sender, + ); + expect(Cl.ok(res.result)).toBeOk(expectedBuffer); + }, + ); + + it.prop([bytesToRead(), bytesToRead(), bytesToGenerate(8192)])( + "read-buff-8192-max with limit and slice", + (toRead, toIsolate, toGenerate) => { + var data = new Uint8Array(); + for (let n of toGenerate) { + data = concatTypedArrays(data, uint8toBytes(n)); + } + let input = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let res = simnet.callReadOnlyFn( + cursor_contract_name, + `read-buff-${toRead}`, + [input], + sender, + ); + + // Early return: we're trying to read more byte than the len of the buffer + if (toRead > data.length) { + expect(res.result).toBeErr(Cl.uint(1)); + return; + } + + let expectedCursor = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(toRead), + }); + + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.buffer(data.subarray(0, toRead)), + next: expectedCursor, + }), + ); + + res = simnet.callReadOnlyFn( + cursor_contract_name, + `read-buff-8192-max`, + [expectedCursor, Cl.some(Cl.uint(toIsolate))], + sender, + ); + + // Early return: we're tryint to read more byte than the len of the buffer + if (toRead + toIsolate > data.length) { + expect(res.result).toBeErr(Cl.uint(1)); + return; + } + + let finalCursor = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(toRead + toIsolate), + }); + + let expectedBuffer = Cl.buffer(data.subarray(toRead, toRead + toIsolate)); + + expect(res.result).toBeOk( + Cl.tuple({ + value: expectedBuffer, + next: finalCursor, + }), + ); + }, + ); +}); - let expectedBuffer = Cl.buffer(data.subarray(toRead, toRead + toIsolate)); +describe("hiro-kit::cursor - unsigned integers", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + + let arrayOfUint8 = fc.uint8Array({ minLength: 0, maxLength: 8192 }); + it.prop([arrayOfUint8])("read-uint-8", (numbers) => { + var data = new Uint8Array(); + for (let n of numbers) { + data = concatTypedArrays(data, uint8toBytes(n)); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 1; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-uint-8", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.uint(n), + next: nextInput, + }), + ); + } + }); - expect(res.result).toBeOk(Cl.tuple({ - value: expectedBuffer, - next: finalCursor - })) - }) + let arrayOfUint16 = fc.uint16Array({ minLength: 0, maxLength: 4196 }); + it.prop([arrayOfUint16])("read-uint-16", (numbers) => { + var data = new Uint8Array(); + for (let n of numbers) { + data = concatTypedArrays(data, uint16toBytes(n)); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 2; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-uint-16", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.uint(n), + next: nextInput, + }), + ); + } + }); -}) + let arrayOfUint32 = fc.uint32Array({ minLength: 0, maxLength: 2048 }); + it.prop([arrayOfUint32])("read-uint-32", (numbers) => { + var data = new Uint8Array(); -describe("hiro-kit::cursor - unsigned integers", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - - let arrayOfUint8 = fc.uint8Array({ minLength: 0, maxLength: 8192 }); - it.prop([arrayOfUint8])("read-uint-8", (numbers) => { - var data = new Uint8Array(); - for (let n of numbers) { - data = concatTypedArrays(data, uint8toBytes(n)); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 1; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-uint-8", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.uint(n), - next: nextInput - })) - } - }) - - let arrayOfUint16 = fc.uint16Array({ minLength: 0, maxLength: 4196 }); - it.prop([arrayOfUint16])("read-uint-16", (numbers) => { - var data = new Uint8Array(); - for (let n of numbers) { - data = concatTypedArrays(data, uint16toBytes(n)); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 2; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-uint-16", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.uint(n), - next: nextInput - })) - } - }) - - let arrayOfUint32 = fc.uint32Array({ minLength: 0, maxLength: 2048 }); - it.prop([arrayOfUint32])("read-uint-32", (numbers) => { - var data = new Uint8Array(); - - for (let n of numbers) { - data = concatTypedArrays(data, uint32toBytes(n)); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 4; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-uint-32", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.uint(n), - next: nextInput - })) - } - }) - - let arrayOfUint64 = fc.array(fc.bigUintN(64), { minLength: 0, maxLength: 1024 }); - it.prop([arrayOfUint64])("read-uint-64", (numbers) => { - var data = Buffer.from([]); - for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 8; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-uint-64", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.uint(n), - next: nextInput - })) - } - }) - - let arrayOfUint128 = fc.array(fc.bigUintN(128), { minLength: 0, maxLength: 512 }); - it.prop([arrayOfUint128])("read-uint-128", (numbers) => { - var data = Buffer.from([]); - for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 16; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-uint-128", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.uint(n), - next: nextInput - })) - } - }) -}) + for (let n of numbers) { + data = concatTypedArrays(data, uint32toBytes(n)); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 4; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-uint-32", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.uint(n), + next: nextInput, + }), + ); + } + }); + + let arrayOfUint64 = fc.array(fc.bigUintN(64), { + minLength: 0, + maxLength: 1024, + }); + it.prop([arrayOfUint64])("read-uint-64", (numbers) => { + var data = Buffer.from([]); + for (let n of numbers) { + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 8; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-uint-64", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.uint(n), + next: nextInput, + }), + ); + } + }); + + let arrayOfUint128 = fc.array(fc.bigUintN(128), { + minLength: 0, + maxLength: 512, + }); + it.prop([arrayOfUint128])("read-uint-128", (numbers) => { + var data = Buffer.from([]); + for (let n of numbers) { + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 16; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-uint-128", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.uint(n), + next: nextInput, + }), + ); + } + }); +}); describe("hiro-kit::cursor - signed integers", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - - let arrayOfInt8 = fc.array(fc.bigIntN(8), { minLength: 0, maxLength: 1023 }); - it.prop([arrayOfInt8])("read-int-8", (numbers) => { - var data = Buffer.from([]); - for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 1))]); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 1; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-int-8", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.int(n), - next: nextInput - })) - } - }) - - let arrayOfInt16 = fc.array(fc.bigIntN(16), { minLength: 0, maxLength: 1023 }); - it.prop([arrayOfInt16])("read-int-16", (numbers) => { - var data = Buffer.from([]); - for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 2))]); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 2; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-int-16", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.int(n), - next: nextInput - })) - } - }) - - let arrayOfInt32 = fc.array(fc.bigIntN(32), { minLength: 0, maxLength: 1023 }); - it.prop([arrayOfInt32])("read-int-32", (numbers) => { - var data = Buffer.from([]); - for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 4))]); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 4; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-int-32", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.int(n), - next: nextInput - })) - } - }) - - let arrayOfInt64 = fc.array(fc.bigIntN(64), { minLength: 0, maxLength: 1023 }); - it.prop([arrayOfInt64])("read-int-64", (numbers) => { - var data = Buffer.from([]); - for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 8; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-int-64", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.int(n), - next: nextInput - })) - } - }) - - let arrayOfInt128 = fc.array(fc.bigIntN(128), { minLength: 0, maxLength: 1023 }); - it.prop([arrayOfInt128])("read-int-128", (numbers) => { - var data = Buffer.from([]); - for (let n of numbers) { - data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); - } - let nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(0) - }) - - let pos = 0; - for (let n of numbers) { - pos += 16; - let res = simnet.callReadOnlyFn( - cursor_contract_name, - "read-int-128", - [nextInput], - sender - ); - nextInput = Cl.tuple({ - bytes: Cl.buffer(data), - pos: Cl.uint(pos) - }); - expect(res.result).toBeOk(Cl.tuple({ - value: Cl.int(n), - next: nextInput - })) - } - }) -}) + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + + let arrayOfInt8 = fc.array(fc.bigIntN(8), { minLength: 0, maxLength: 1023 }); + it.prop([arrayOfInt8])("read-int-8", (numbers) => { + var data = Buffer.from([]); + for (let n of numbers) { + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 1))]); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 1; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-int-8", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.int(n), + next: nextInput, + }), + ); + } + }); + + let arrayOfInt16 = fc.array(fc.bigIntN(16), { + minLength: 0, + maxLength: 1023, + }); + it.prop([arrayOfInt16])("read-int-16", (numbers) => { + var data = Buffer.from([]); + for (let n of numbers) { + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 2))]); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 2; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-int-16", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.int(n), + next: nextInput, + }), + ); + } + }); + + let arrayOfInt32 = fc.array(fc.bigIntN(32), { + minLength: 0, + maxLength: 1023, + }); + it.prop([arrayOfInt32])("read-int-32", (numbers) => { + var data = Buffer.from([]); + for (let n of numbers) { + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 4))]); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 4; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-int-32", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.int(n), + next: nextInput, + }), + ); + } + }); + + let arrayOfInt64 = fc.array(fc.bigIntN(64), { + minLength: 0, + maxLength: 1023, + }); + it.prop([arrayOfInt64])("read-int-64", (numbers) => { + var data = Buffer.from([]); + for (let n of numbers) { + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 8))]); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 8; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-int-64", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.int(n), + next: nextInput, + }), + ); + } + }); + + let arrayOfInt128 = fc.array(fc.bigIntN(128), { + minLength: 0, + maxLength: 1023, + }); + it.prop([arrayOfInt128])("read-int-128", (numbers) => { + var data = Buffer.from([]); + for (let n of numbers) { + data = Buffer.concat([data, Buffer.from(bigintToBuffer(n, 16))]); + } + let nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(0), + }); + + let pos = 0; + for (let n of numbers) { + pos += 16; + let res = simnet.callReadOnlyFn( + cursor_contract_name, + "read-int-128", + [nextInput], + sender, + ); + nextInput = Cl.tuple({ + bytes: Cl.buffer(data), + pos: Cl.uint(pos), + }); + expect(res.result).toBeOk( + Cl.tuple({ + value: Cl.int(n), + next: nextInput, + }), + ); + } + }); +}); diff --git a/unit-tests/utils/helpers.ts b/unit-tests/utils/helpers.ts index 3e41faa..21562e0 100644 --- a/unit-tests/utils/helpers.ts +++ b/unit-tests/utils/helpers.ts @@ -1,54 +1,54 @@ export function concatTypedArrays(a: any, b: any) { - var c = new (a.constructor)(a.length + b.length); - c.set(a, 0); - c.set(b, a.length); - return c; + var c = new a.constructor(a.length + b.length); + c.set(a, 0); + c.set(b, a.length); + return c; } export function uint8toBytes(num: number) { - let b = new ArrayBuffer(1); - new DataView(b).setUint8(0, num); - return new Uint8Array(b); + let b = new ArrayBuffer(1); + new DataView(b).setUint8(0, num); + return new Uint8Array(b); } export function uint16toBytes(num: number) { - let b = new ArrayBuffer(2); - new DataView(b).setUint16(0, num); - return new Uint8Array(b); + let b = new ArrayBuffer(2); + new DataView(b).setUint16(0, num); + return new Uint8Array(b); } export function uint32toBytes(num: number) { - let b = new ArrayBuffer(4); - new DataView(b).setUint32(0, num); - return new Uint8Array(b); + let b = new ArrayBuffer(4); + new DataView(b).setUint32(0, num); + return new Uint8Array(b); } export function bigintToBuffer(bigintValue: bigint, byteLength: number) { - if (bigintValue >= 0n) { - // Convert BigInt to hexadecimal string - let hexString = bigintValue.toString(16); - // Calculate padding - const padding = byteLength * 2 - hexString.length; - // Add leading zeros for padding - for (let i = 0; i < padding; i++) { - hexString = '0' + hexString; - } - // Create Buffer from padded hexadecimal string - return Buffer.from(hexString, 'hex'); - } else { - // Handle negative BigInt - const twosComplement = (BigInt(1) << BigInt(byteLength * 8)) + bigintValue; - return bigintToBuffer(twosComplement, byteLength); + if (bigintValue >= 0n) { + // Convert BigInt to hexadecimal string + let hexString = bigintValue.toString(16); + // Calculate padding + const padding = byteLength * 2 - hexString.length; + // Add leading zeros for padding + for (let i = 0; i < padding; i++) { + hexString = "0" + hexString; } + // Create Buffer from padded hexadecimal string + return Buffer.from(hexString, "hex"); + } else { + // Handle negative BigInt + const twosComplement = (BigInt(1) << BigInt(byteLength * 8)) + bigintValue; + return bigintToBuffer(twosComplement, byteLength); + } } export function bufferToHexString(bytes: Uint8Array) { - return Array.from(bytes, function(byte) { - return ('0' + (byte & 0xFF).toString(16)).slice(-2); - }).join(''); + return Array.from(bytes, function (byte) { + return ("0" + (byte & 0xff).toString(16)).slice(-2); + }).join(""); } export function bufferToBigint(bytes: Uint8Array) { - const hexString = bufferToHexString(bytes); - return BigInt('0x' + hexString); + const hexString = bufferToHexString(bytes); + return BigInt("0x" + hexString); } diff --git a/unit-tests/utils/merkle-proofs.test.ts b/unit-tests/utils/merkle-proofs.test.ts index 3efe34a..96d3cb5 100644 --- a/unit-tests/utils/merkle-proofs.test.ts +++ b/unit-tests/utils/merkle-proofs.test.ts @@ -4,166 +4,194 @@ import { describe, expect, it } from "vitest"; const merkle_contract_name = "hk-merkle-tree-keccak160-v1"; // The test vectors executed in the following test suite are coming from: -// https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/aptos/contracts/sources/merkle.move +// https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/aptos/contracts/sources/merkle.move describe("hiro-kit::hk-merkle-tree-keccak160", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - - const setupTree = () => { - // - // h1 h2 h3 h4 - // \ / \ / - // h5 h6 - // \ / - // h7 - // - let h1 = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-leaf", - [Cl.bufferFromHex("adad11")], - sender - ).result; - let h2 = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-leaf", - [Cl.bufferFromHex("adad12")], - sender - ).result; - let h3 = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-leaf", - [Cl.bufferFromHex("adad13")], - sender - ).result; - let h4 = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-leaf", - [Cl.bufferFromHex("adad14")], - sender - ).result; - let h5 = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-nodes", - [h1, h2], - sender - ).result; - let h6 = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-nodes", - [h3, h4], - sender - ).result; - let h7 = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-nodes", - [h5, h6], - sender - ).result; - return [h1, h2, h3, h4, h5, h6, h7] - } - - it("hash leaf", () => { - let res = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-leaf", - [Cl.bufferFromHex("00640000000000000000000000000000000000000000000000000000000000000000000000000000640000000000000064000000640000000000000064000000000000006400000000000000640000000000000064")], - sender - ); - expect(res.result).toStrictEqual(Cl.bufferFromHex("afc6a8ac466430f35895055f8a4c951785dad5ce")) - }); - - it("hash node", () => { - let h1 = Cl.bufferFromHex("05c51b04b820c0f704e3fdd2e4fc1e70aff26dff"); - let h2 = Cl.bufferFromHex("1e108841c8d21c7a5c4860c8c3499c918ea9e0ac"); - let res = simnet.callReadOnlyFn( - merkle_contract_name, - "hash-nodes", - [h1, h2], - sender - ); - expect(res.result).toStrictEqual(Cl.bufferFromHex("2d0e4fde68184c7ce8af426a0865bd41ef84dfa4")) - }); - - it("check valid proofs", () => { - let [h1, h2, h3, h4, h5, h6, h7] = setupTree(); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h7, Cl.bufferFromHex("adad11"), Cl.list([h2, h6])], - sender - ).result).toStrictEqual(Cl.bool(true)); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h7, Cl.bufferFromHex("adad14"), Cl.list([h3, h5])], - sender - ).result).toStrictEqual(Cl.bool(true)); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h7, Cl.bufferFromHex("adad14"), Cl.list([h1, h4])], - sender - ).result).toStrictEqual(Cl.bool(false)); - }); - - it("check valid proofs subtree", () => { - let [h1, h2, h3, h4, h5, h6, _] = setupTree(); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h5, Cl.bufferFromHex("adad12"), Cl.list([h1])], - sender - ).result).toStrictEqual(Cl.bool(true)); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h5, Cl.bufferFromHex("adad11"), Cl.list([h2])], - sender - ).result).toStrictEqual(Cl.bool(true)); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h6, Cl.bufferFromHex("adad14"), Cl.list([h3])], - sender - ).result).toStrictEqual(Cl.bool(true)); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h6, Cl.bufferFromHex("adad13"), Cl.list([h4])], - sender - ).result).toStrictEqual(Cl.bool(true)); - }); - - it("check invalid proofs", () => { - let [h1, h2, h3, h4, h5, h6, h7] = setupTree(); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h7, Cl.bufferFromHex("dead"), Cl.list([h2, h6])], - sender - ).result).toStrictEqual(Cl.bool(false)); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h7, Cl.bufferFromHex("dead"), Cl.list([h3, h5])], - sender - ).result).toStrictEqual(Cl.bool(false)); - - expect(simnet.callReadOnlyFn( - merkle_contract_name, - "check-proof", - [h7, Cl.bufferFromHex("dead"), Cl.list([h1, h4])], - sender - ).result).toStrictEqual(Cl.bool(false)); - }); -}) + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + + const setupTree = () => { + // + // h1 h2 h3 h4 + // \ / \ / + // h5 h6 + // \ / + // h7 + // + let h1 = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-leaf", + [Cl.bufferFromHex("adad11")], + sender, + ).result; + let h2 = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-leaf", + [Cl.bufferFromHex("adad12")], + sender, + ).result; + let h3 = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-leaf", + [Cl.bufferFromHex("adad13")], + sender, + ).result; + let h4 = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-leaf", + [Cl.bufferFromHex("adad14")], + sender, + ).result; + let h5 = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-nodes", + [h1, h2], + sender, + ).result; + let h6 = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-nodes", + [h3, h4], + sender, + ).result; + let h7 = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-nodes", + [h5, h6], + sender, + ).result; + return [h1, h2, h3, h4, h5, h6, h7]; + }; + + it("hash leaf", () => { + let res = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-leaf", + [ + Cl.bufferFromHex( + "00640000000000000000000000000000000000000000000000000000000000000000000000000000640000000000000064000000640000000000000064000000000000006400000000000000640000000000000064", + ), + ], + sender, + ); + expect(res.result).toStrictEqual( + Cl.bufferFromHex("afc6a8ac466430f35895055f8a4c951785dad5ce"), + ); + }); + + it("hash node", () => { + let h1 = Cl.bufferFromHex("05c51b04b820c0f704e3fdd2e4fc1e70aff26dff"); + let h2 = Cl.bufferFromHex("1e108841c8d21c7a5c4860c8c3499c918ea9e0ac"); + let res = simnet.callReadOnlyFn( + merkle_contract_name, + "hash-nodes", + [h1, h2], + sender, + ); + expect(res.result).toStrictEqual( + Cl.bufferFromHex("2d0e4fde68184c7ce8af426a0865bd41ef84dfa4"), + ); + }); + + it("check valid proofs", () => { + let [h1, h2, h3, h4, h5, h6, h7] = setupTree(); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h7, Cl.bufferFromHex("adad11"), Cl.list([h2, h6])], + sender, + ).result, + ).toStrictEqual(Cl.bool(true)); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h7, Cl.bufferFromHex("adad14"), Cl.list([h3, h5])], + sender, + ).result, + ).toStrictEqual(Cl.bool(true)); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h7, Cl.bufferFromHex("adad14"), Cl.list([h1, h4])], + sender, + ).result, + ).toStrictEqual(Cl.bool(false)); + }); + + it("check valid proofs subtree", () => { + let [h1, h2, h3, h4, h5, h6, _] = setupTree(); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h5, Cl.bufferFromHex("adad12"), Cl.list([h1])], + sender, + ).result, + ).toStrictEqual(Cl.bool(true)); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h5, Cl.bufferFromHex("adad11"), Cl.list([h2])], + sender, + ).result, + ).toStrictEqual(Cl.bool(true)); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h6, Cl.bufferFromHex("adad14"), Cl.list([h3])], + sender, + ).result, + ).toStrictEqual(Cl.bool(true)); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h6, Cl.bufferFromHex("adad13"), Cl.list([h4])], + sender, + ).result, + ).toStrictEqual(Cl.bool(true)); + }); + + it("check invalid proofs", () => { + let [h1, h2, h3, h4, h5, h6, h7] = setupTree(); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h7, Cl.bufferFromHex("dead"), Cl.list([h2, h6])], + sender, + ).result, + ).toStrictEqual(Cl.bool(false)); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h7, Cl.bufferFromHex("dead"), Cl.list([h3, h5])], + sender, + ).result, + ).toStrictEqual(Cl.bool(false)); + + expect( + simnet.callReadOnlyFn( + merkle_contract_name, + "check-proof", + [h7, Cl.bufferFromHex("dead"), Cl.list([h1, h4])], + sender, + ).result, + ).toStrictEqual(Cl.bool(false)); + }); +}); diff --git a/unit-tests/wormhole/fixtures.ts b/unit-tests/wormhole/fixtures.ts index f907f29..c308c31 100644 --- a/unit-tests/wormhole/fixtures.ts +++ b/unit-tests/wormhole/fixtures.ts @@ -1,74 +1,74 @@ export const gsuMainnetVaas = [ - { - vaa: "010000000001007ac31b282c2aeeeb37f3385ee0de5f8e421d30b9e5ae8ba3d4375c1c77a86e77159bb697d9c456d6f8c02d22a94b1279b65b0d6a9957e7d3857423845ac758e300610ac1d2000000030001000000000000000000000000000000000000000000000000000000000000000400000000000005390000000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", - keys: [ - "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", - "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", - "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", - "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", - "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", - "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", - "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", - "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", - "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", - "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", - "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", - "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", - "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", - "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", - "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", - "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", - "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", - "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", - "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", - ] - }, - { - vaa: "01000000010d0012e6b39c6da90c5dfd3c228edbb78c7a4c97c488ff8a346d161a91db067e51d638c17216f368aa9bdf4836b8645a98018ca67d2fec87d769cabfdf2406bf790a0002ef42b288091a670ef3556596f4f47323717882881eaf38e03345078d07a156f312b785b64dae6e9a87e3d32872f59cb1931f728cecf511762981baf48303668f0103cef2616b84c4e511ff03329e0853f1bd7ee9ac5ba71d70a4d76108bddf94f69c2a8a84e4ee94065e8003c334e899184943634e12043d0dda78d93996da073d190104e76d166b9dac98f602107cc4b44ac82868faf00b63df7d24f177aa391e050902413b71046434e67c770b19aecdf7fce1d1435ea0be7262e3e4c18f50ddc8175c0105d9450e8216d741e0206a50f93b750a47e0a258b80eb8fed1314cc300b3d905092de25cd36d366097b7103ae2d184121329ba3aa2d7c6cc53273f11af14798110010687477c8deec89d36a23e7948feb074df95362fc8dcbd8ae910ac556a1dee1e755c56b9db5d710c940938ed79bc1895a3646523a58bc55f475a23435a373ecfdd0107fb06734864f79def4e192497362513171530daea81f07fbb9f698afe7e66c6d44db21323144f2657d4a5386a954bb94eef9f64148c33aef6e477eafa2c5c984c01088769e82216310d1827d9bd48645ec23e90de4ef8a8de99e2d351d1df318608566248d80cdc83bdcac382b3c30c670352be87f9069aab5037d0b747208eae9c650109e9796497ff9106d0d1c62e184d83716282870cef61a1ee13d6fc485b521adcce255c96f7d1bca8d8e7e7d454b65783a830bddc9d94092091a268d311ecd84c26010c468c9fb6d41026841ff9f8d7368fa309d4dbea3ea4bbd2feccf94a92cc8a20a226338a8e2126cd16f70eaf15b4fc9be2c3fa19def14e071956a605e9d1ac4162010e23fcb6bd445b7c25afb722250c1acbc061ed964ba9de1326609ae012acdfb96942b2a102a2de99ab96327859a34a2b49a767dbdb62e0a1fb26af60fe44fd496a00106bb0bac77ac68b347645f2fb1ad789ea9bd76fb9b2324f25ae06f97e65246f142df717f662e73948317182c62ce87d79c73def0dba12e5242dfc038382812cfe00126da03c5e56cb15aeeceadc1e17a45753ab4dc0ec7bf6a75ca03143ed4a294f6f61bc3f478a457833e43084ecd7c985bf2f55a55f168aac0e030fc49e845e497101626e9d9a5d9e343f00010000000000000000000000000000000000000000000000000000000000000004c1759167c43f501c2000000000000000000000000000000000000000000000000000000000436f7265020000000000021358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd66b9590e1c41e0b226937bf9217d1d67fd4e91f574a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", - keys: [ - "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", - "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", - "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", - "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", - "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", - "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", - "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", - /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", - "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", - "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", - "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", - "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", - "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", - "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", - "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", - "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", - "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", - "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", - "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", - ] - }, - { - vaa: "01000000020d00ce45474d9e1b1e7790a2d210871e195db53a70ffd6f237cfe70e2686a32859ac43c84a332267a8ef66f59719cf91cc8df0101fd7c36aa1878d5139241660edc0010375cc906156ae530786661c0cd9aef444747bc3d8d5aa84cac6a6d2933d4e1a031cffa30383d4af8131e929d9f203f460b07309a647d6cd32ab1cc7724089392c000452305156cfc90343128f97e499311b5cae174f488ff22fbc09591991a0a73d8e6af3afb8a5968441d3ab8437836407481739e9850ad5c95e6acfcc871e951bc30105a7956eefc23e7c945a1966d5ddbe9e4be376c2f54e45e3d5da88c2f8692510c7429b1ea860ae94d929bd97e84923a18187e777aa3db419813a80deb84cc8d22b00061b2a4f3d2666608e0aa96737689e3ba5793810ff3a52ff28ad57d8efb20967735dc5537a2e43ef10f583d144c12a1606542c207f5b79af08c38656d3ac40713301086b62c8e130af3411b3c0d91b5b50dcb01ed5f293963f901fc36e7b0e50114dce203373b32eb45971cef8288e5d928d0ed51cd86e2a3006b0af6a65c396c009080009e93ab4d2c8228901a5f4525934000b2c26d1dc679a05e47fdf0ff3231d98fbc207103159ff4116df2832eea69b38275283434e6cd4a4af04d25fa7a82990b707010aa643f4cf615dfff06ffd65830f7f6cf6512dabc3690d5d9e210fdc712842dc2708b8b2c22e224c99280cd25e5e8bfb40e3d1c55b8c41774e287c1e2c352aecfc010b89c1e85faa20a30601964ccc6a79c0ae53cfd26fb10863db37783428cd91390a163346558239db3cd9d420cfe423a0df84c84399790e2e308011b4b63e6b8015010ca31dcb564ac81a053a268d8090e72097f94f366711d0c5d13815af1ec7d47e662e2d1bde22678113d15963da100b668ba26c0c325970d07114b83c5698f46097010dc9fda39c0d592d9ed92cd22b5425cc6b37430e236f02d0d1f8a2ef45a00bde26223c0a6eb363c8b25fd3bf57234a1d9364976cefb8360e755a267cbbb674b39501108db01e444ab1003dd8b6c96f8eb77958b40ba7a85fefecf32ad00b7a47c0ae7524216262495977e09c0989dd50f280c21453d3756843608eacd17f4fdfe47600001261025228ef5af837cb060bcd986fcfa84ccef75b3fa100468cfd24e7fadf99163938f3b841a33496c2706d0208faab088bd155b2e20fd74c625bb1cc8c43677a0163c53c409e0c5dfa000100000000000000000000000000000000000000000000000000000000000000046c5a054d7833d1e42000000000000000000000000000000000000000000000000000000000436f7265020000000000031358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd15e7caf07c4e3dc8e7c469f92c8cd88fb8005a2074a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", - keys: [ - "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", - "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", - "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", - "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", - "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", - "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", - "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", - /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", - "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", - "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", - "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", - "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", - "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", - "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", - "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", - "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", - "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", - "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", - "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", - ] - }, + { + vaa: "010000000001007ac31b282c2aeeeb37f3385ee0de5f8e421d30b9e5ae8ba3d4375c1c77a86e77159bb697d9c456d6f8c02d22a94b1279b65b0d6a9957e7d3857423845ac758e300610ac1d2000000030001000000000000000000000000000000000000000000000000000000000000000400000000000005390000000000000000000000000000000000000000000000000000000000436f7265020000000000011358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cdeb5f7389fa26941519f0863349c223b73a6ddee774a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", + keys: [ + "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", + "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", + "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", + "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", + "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", + "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", + "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", + "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", + "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", + "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", + "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", + "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", + "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", + "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", + "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", + "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", + "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", + "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", + "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", + ], + }, + { + vaa: "01000000010d0012e6b39c6da90c5dfd3c228edbb78c7a4c97c488ff8a346d161a91db067e51d638c17216f368aa9bdf4836b8645a98018ca67d2fec87d769cabfdf2406bf790a0002ef42b288091a670ef3556596f4f47323717882881eaf38e03345078d07a156f312b785b64dae6e9a87e3d32872f59cb1931f728cecf511762981baf48303668f0103cef2616b84c4e511ff03329e0853f1bd7ee9ac5ba71d70a4d76108bddf94f69c2a8a84e4ee94065e8003c334e899184943634e12043d0dda78d93996da073d190104e76d166b9dac98f602107cc4b44ac82868faf00b63df7d24f177aa391e050902413b71046434e67c770b19aecdf7fce1d1435ea0be7262e3e4c18f50ddc8175c0105d9450e8216d741e0206a50f93b750a47e0a258b80eb8fed1314cc300b3d905092de25cd36d366097b7103ae2d184121329ba3aa2d7c6cc53273f11af14798110010687477c8deec89d36a23e7948feb074df95362fc8dcbd8ae910ac556a1dee1e755c56b9db5d710c940938ed79bc1895a3646523a58bc55f475a23435a373ecfdd0107fb06734864f79def4e192497362513171530daea81f07fbb9f698afe7e66c6d44db21323144f2657d4a5386a954bb94eef9f64148c33aef6e477eafa2c5c984c01088769e82216310d1827d9bd48645ec23e90de4ef8a8de99e2d351d1df318608566248d80cdc83bdcac382b3c30c670352be87f9069aab5037d0b747208eae9c650109e9796497ff9106d0d1c62e184d83716282870cef61a1ee13d6fc485b521adcce255c96f7d1bca8d8e7e7d454b65783a830bddc9d94092091a268d311ecd84c26010c468c9fb6d41026841ff9f8d7368fa309d4dbea3ea4bbd2feccf94a92cc8a20a226338a8e2126cd16f70eaf15b4fc9be2c3fa19def14e071956a605e9d1ac4162010e23fcb6bd445b7c25afb722250c1acbc061ed964ba9de1326609ae012acdfb96942b2a102a2de99ab96327859a34a2b49a767dbdb62e0a1fb26af60fe44fd496a00106bb0bac77ac68b347645f2fb1ad789ea9bd76fb9b2324f25ae06f97e65246f142df717f662e73948317182c62ce87d79c73def0dba12e5242dfc038382812cfe00126da03c5e56cb15aeeceadc1e17a45753ab4dc0ec7bf6a75ca03143ed4a294f6f61bc3f478a457833e43084ecd7c985bf2f55a55f168aac0e030fc49e845e497101626e9d9a5d9e343f00010000000000000000000000000000000000000000000000000000000000000004c1759167c43f501c2000000000000000000000000000000000000000000000000000000000436f7265020000000000021358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd66b9590e1c41e0b226937bf9217d1d67fd4e91f574a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", + keys: [ + "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", + "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", + "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", + "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", + "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", + "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", + "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", + /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", + "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", + "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", + "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", + "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", + "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", + "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", + "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", + "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", + "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", + "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", + "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", + ], + }, + { + vaa: "01000000020d00ce45474d9e1b1e7790a2d210871e195db53a70ffd6f237cfe70e2686a32859ac43c84a332267a8ef66f59719cf91cc8df0101fd7c36aa1878d5139241660edc0010375cc906156ae530786661c0cd9aef444747bc3d8d5aa84cac6a6d2933d4e1a031cffa30383d4af8131e929d9f203f460b07309a647d6cd32ab1cc7724089392c000452305156cfc90343128f97e499311b5cae174f488ff22fbc09591991a0a73d8e6af3afb8a5968441d3ab8437836407481739e9850ad5c95e6acfcc871e951bc30105a7956eefc23e7c945a1966d5ddbe9e4be376c2f54e45e3d5da88c2f8692510c7429b1ea860ae94d929bd97e84923a18187e777aa3db419813a80deb84cc8d22b00061b2a4f3d2666608e0aa96737689e3ba5793810ff3a52ff28ad57d8efb20967735dc5537a2e43ef10f583d144c12a1606542c207f5b79af08c38656d3ac40713301086b62c8e130af3411b3c0d91b5b50dcb01ed5f293963f901fc36e7b0e50114dce203373b32eb45971cef8288e5d928d0ed51cd86e2a3006b0af6a65c396c009080009e93ab4d2c8228901a5f4525934000b2c26d1dc679a05e47fdf0ff3231d98fbc207103159ff4116df2832eea69b38275283434e6cd4a4af04d25fa7a82990b707010aa643f4cf615dfff06ffd65830f7f6cf6512dabc3690d5d9e210fdc712842dc2708b8b2c22e224c99280cd25e5e8bfb40e3d1c55b8c41774e287c1e2c352aecfc010b89c1e85faa20a30601964ccc6a79c0ae53cfd26fb10863db37783428cd91390a163346558239db3cd9d420cfe423a0df84c84399790e2e308011b4b63e6b8015010ca31dcb564ac81a053a268d8090e72097f94f366711d0c5d13815af1ec7d47e662e2d1bde22678113d15963da100b668ba26c0c325970d07114b83c5698f46097010dc9fda39c0d592d9ed92cd22b5425cc6b37430e236f02d0d1f8a2ef45a00bde26223c0a6eb363c8b25fd3bf57234a1d9364976cefb8360e755a267cbbb674b39501108db01e444ab1003dd8b6c96f8eb77958b40ba7a85fefecf32ad00b7a47c0ae7524216262495977e09c0989dd50f280c21453d3756843608eacd17f4fdfe47600001261025228ef5af837cb060bcd986fcfa84ccef75b3fa100468cfd24e7fadf99163938f3b841a33496c2706d0208faab088bd155b2e20fd74c625bb1cc8c43677a0163c53c409e0c5dfa000100000000000000000000000000000000000000000000000000000000000000046c5a054d7833d1e42000000000000000000000000000000000000000000000000000000000436f7265020000000000031358cc3ae5c097b213ce3c81979e1b9f9570746aa5ff6cb952589bde862c25ef4392132fb9d4a42157114de8460193bdf3a2fcf81f86a09765f4762fd1107a0086b32d7a0977926a205131d8731d39cbeb8c82b2fd82faed2711d59af0f2499d16e726f6b211b39756c042441be6d8650b69b54ebe715e234354ce5b4d348fb74b958e8966e2ec3dbd4958a7cd15e7caf07c4e3dc8e7c469f92c8cd88fb8005a2074a3bf913953d695260d88bc1aa25a4eee363ef0000ac0076727b35fbea2dac28fee5ccb0fea768eaf45ced136b9d9e24903464ae889f5c8a723fc14f93124b7c738843cbb89e864c862c38cddcccf95d2cc37a4dc036a8d232b48f62cdd4731412f4890da798f6896a3331f64b48c12d1d57fd9cbe7081171aa1be1d36cafe3867910f99c09e347899c19c38192b6e7387ccd768277c17dab1b7a5027c0b3cf178e21ad2e77ae06711549cfbb1f9c7a9d8096e85e1487f35515d02a92753504a8d75471b9f49edb6fbebc898f403e4773e95feb15e80c9a99c8348d", + keys: [ + "2a953a2e8b1052eb70c1d7b556b087deed598b55608396686c1c811b9796c763078687ce10459f4f25fb7a0fbf8727bb0fb51e00820e93a123f652ee843cf08d", + "2766db08820e311b22e109801ab8ea505b12e3df3d91ebc87c999ffb6929d1abb0ade987c74aa37db26eea4086ee738a2f34a5594edb8760da0eac5be356b731", + "54177ff4a8329520b76efd86f8bfce5c942554db16e673267dc1133b3f5e230b2d8cbf90fe274946045d4491de288d736680edc2ee9ee5b1b15416b0a34806c4", + "7fa3e98fcc2621337b217b61408a98facaabd25bad2b158438728ce863c14708cfcda1f3b50a16ca0211199079fb338d479a54546ec3c5f775af23a7d7f4fb24", + "0bdcbccc0297c2a4f92a7c39358c42f22a8ed700a78bd05c39c8b61aaf2338e825b6c0d26d1f2a2ae4129cd751201f73d7234c753bd0735212a5288b19748fd2", + "cfd90084be68de514fe14a7c281f492223f045566f859ea5c166d6e60bc650c23940909a8e96c2fbffbd15a598b4e6a5b5aa14c126bf58cc1a9e396fe7771965", + "8edf3f9d997357a0e2c916ee090392c3a645ebac4f6cd8f826d3ecc0173b33bf06b7c14e8002fc9a5d01af9824a5cb3778472cd477e0ab378091448bca6f0417", + /**/ "6200deeadd3b4e2cd0a168d42e720a3b88d3ce4769ff2808086e6fd36110fc05c591ccbf91368609dc3b21203e58c9883d4f580085469bef8d5bdcd26eb8ad69", + "d5225476d7849b362226952ffc561bab99832f3f8b99741f6d81bbeaffa8e7f6e54a85e5029a3b510707eaa9684df496e4b1268075ad0328693a30bf1b1e0033", + "d9fa78b5b958bea1929080b8ad96dc555d34b051a27aebf711eb1186b807b0448316d994606ac807121838d6c41a58f308bc6307acdf69491fa4b17282f3e66f", + "cc64af75ec2e2741fb9af9f6191cb9ee187d6d26af4d1e96d7bab47e6ec09be12d3192030dc4bbf54d1da319a7a2acfc7a9dd4c644af6646a4aaa02b1024bbab", + "b5943b6e284682ad2e011d6962d41febf86af2f5fc0c9c8f4b81358ff077f9c96ba0880eaf93541eae94b4fa41dba66dab7fb0201cc9af7c75681e5719b0c95f", + "0cfc9d5b5dcf702a1525f9d4ed1841e8eb8b34434cc82470dd35435f1dbdc73ffb51544b7500394eac9c7fa567868b495326075147a2d809ebbfd43273eeec91", + "0aa78894d894a15933969f5826347439e2c309f2049277a10066c9197840499498ad19ee3d1b291f932ec0890bbdafcec292c4f02a446670cd0084f997e25e2f", + "00f400e3fe40f64032485aad9240ead45a8e1fc83ec08c96db861c0eca155ac898df8673e778e3ccaae8a0f9e6af415fe40e99b0cbc88d7610e536b6041b07fb", + "604f384174c7ed3a0dc5f476569a978266a7943bd775449d1b8b27f4eb8beb99cdf095f9200a2dabb1bc5d68c3d96ea3d47f4d34499d59953669b6c8c093d578", + "4881345cbb299fa7c60ab2d16cb7fe7bf8d14675506ef6eb6037038b5b7092ea0a9e4d0b53ba3904edd99f86717d6ba81dffe44eb5b23c6fd22c91ab73c33021", + "ee3d4cc17633afe7e1794fcfd728e0643325e3d130eb1daa39c0c5cb05a200b43876117a182cabdcc3795632aa529473a0c8245f9e4f6e43e54c3f1da28bcb82", + "21f338444e96af31cf44958acf5764844efbddace3b823ed761c340c59ed2685d829818c83eebe8f00f783f1048a53515845536668a9e0c059ade7579a0f4204", + ], + }, ]; diff --git a/unit-tests/wormhole/helpers.ts b/unit-tests/wormhole/helpers.ts index efbdaa4..4bf2d4b 100644 --- a/unit-tests/wormhole/helpers.ts +++ b/unit-tests/wormhole/helpers.ts @@ -1,414 +1,515 @@ -import { fc } from '@fast-check/vitest'; +import { fc } from "@fast-check/vitest"; import { tx } from "@hirosystems/clarinet-sdk"; import { Cl, ClarityValue } from "@stacks/transactions"; -import { bigintToBuffer } from '../utils/helpers'; -import * as secp from '@noble/secp256k1'; -import { - keccak_256 -} from '@noble/hashes/sha3'; -import { webcrypto } from 'node:crypto'; +import { bigintToBuffer } from "../utils/helpers"; +import * as secp from "@noble/secp256k1"; +import { keccak_256 } from "@noble/hashes/sha3"; +import { webcrypto } from "node:crypto"; // @ts-ignore if (!globalThis.crypto) globalThis.crypto = webcrypto; -import { hmac } from '@noble/hashes/hmac'; -import { sha256 } from '@noble/hashes/sha256'; -import { gsuMainnetVaas } from './fixtures'; +import { hmac } from "@noble/hashes/hmac"; +import { sha256 } from "@noble/hashes/sha256"; +import { gsuMainnetVaas } from "./fixtures"; -secp.etc.hmacSha256Sync = (k, ...m) => hmac(sha256, k, secp.etc.concatBytes(...m)) +secp.etc.hmacSha256Sync = (k, ...m) => + hmac(sha256, k, secp.etc.concatBytes(...m)); export namespace wormhole { - - export interface Guardian { - guardianId: number, - secretKey: Uint8Array, - compressedPublicKey: Uint8Array, - uncompressedPublicKey: Uint8Array, - ethereumAddress: Uint8Array, - } - - export const generateGuardianSetKeychain = (count = 19): Guardian[] => { - let keychain = []; - for (let i = 0; i < count; i++) { - let secretKey = secp.utils.randomPrivateKey(); - let uncompressedPublicKey = secp.getPublicKey(secretKey, false).slice(1, 65); - let ethereumAddress = keccak_256(uncompressedPublicKey).slice(12, 32); - keychain.push({ - guardianId: i, - secretKey: secretKey, - uncompressedPublicKey: uncompressedPublicKey, - ethereumAddress: ethereumAddress, - compressedPublicKey: secp.getPublicKey(secretKey, true), - }) - } - return keychain - } - - export interface VaaHeader { - version: number, - guardianSetId: number, - signatures: Uint8Array[], - } - - export interface VaaBody { - timestamp: number, - emitterChain: number, - nonce: number, - emitterAddress: Uint8Array, - sequence: bigint, - consistencyLevel: number, - payload: Uint8Array, - } - - export interface VaaHeaderBuildOptions { - version?: number, - guardianSetId?: number, - signatures?: Uint8Array[], - } - - export interface VaaBodyBuildOptions { - timestamp?: number, - emitterChain?: number, - nonce?: number, - emitterAddress?: Uint8Array, - sequence?: bigint, - consistencyLevel?: number, - payload?: Uint8Array, - } - - export namespace fc_ext { - // Helper for generating a VAA Body; - // Wire format reminder: - // =========================== - // VAA Body - // u32 timestamp (Timestamp of the block where the source transaction occurred) - // u32 nonce (A grouping number) - // u16 emitter_chain (Wormhole ChainId of emitter contract) - // [32]byte emitter_address (Emitter contract address, in Wormhole format) - // u64 sequence (Strictly increasing sequence, tied to emitter address & chain) - // u8 consistency_level (What finality level was reached before emitting this message) - // []byte payload (VAA message content) - export const vaaBody = (opts?: VaaBodyBuildOptions) => { - // Timestamp - let timestamp = fc.nat(4294967295); - if (opts && opts.timestamp) { - timestamp = fc.constant(opts.timestamp); - } - - // Nonce - let nonce = fc.nat(4294967295); - if (opts && opts.nonce) { - nonce = fc.constant(opts.nonce); - } - - // Emitter chain - let emitterChain = fc.nat(65535); - if (opts && opts.emitterChain) { - emitterChain = fc.constant(opts.emitterChain); - } - - // Emitter address - let emitterAddress = fc.uint8Array({ minLength: 32, maxLength: 32 }); - if (opts && opts.emitterAddress) { - emitterAddress = fc.constant(opts.emitterAddress); - } - - // Sequence - let sequence = fc.bigUintN(64); - if (opts && opts.sequence) { - sequence = fc.constant(opts.sequence) - } - - // Consistency level - let consistencyLevel = fc.nat(255); - if (opts && opts.consistencyLevel) { - consistencyLevel = fc.constant(opts.consistencyLevel) - } - - // Payload - let payload = fc.uint8Array({ minLength: 20, maxLength: 2048 }); - if (opts && opts.payload) { - payload = fc.constant(opts.payload) - } - - return fc.tuple(timestamp, nonce, emitterChain, emitterAddress, sequence, consistencyLevel, payload); - } - - // Helper for generating a VAA Header; - // Wire format reminder: - // =========================== - // VAA Header - // byte version (VAA Version) - // u32 guardian_set_index (Indicates which guardian set is signing) - // u8 len_signatures (Number of signatures stored) - // [][66]byte signatures (Collection of ecdsa signatures) - export const vaaHeader = (opts?: VaaHeaderBuildOptions, numberOfSignatures = 19) => { - // Version - let version = fc.nat(255); - if (opts && opts.version) { - version = fc.constant(opts.version); - } - - // Guardian set id - let guardianSetId = fc.nat(255); - if (opts && opts.guardianSetId) { - guardianSetId = fc.constant(opts.guardianSetId) - } - - // Specified signatures - let specifiedSignatures = fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: 0, maxLength: 0 }) - let specifiedSignaturesLen = 0; - if (opts && opts.signatures) { - specifiedSignatures = fc.constant(opts.signatures) - specifiedSignaturesLen = opts.signatures.length - } - - // Generated signatures - let generatedSignatures = fc.array(fc.uint8Array({ minLength: 66, maxLength: 66 }), { minLength: 0, maxLength: (numberOfSignatures - specifiedSignaturesLen) }) - - return fc.tuple(version, guardianSetId, specifiedSignatures, generatedSignatures); - } - } - - export const buildValidVaaHeaderSpecs = (keychain: Guardian[], body: VaaBody, opts?: VaaHeaderBuildOptions): VaaHeaderBuildOptions => { - - let signatures = []; - const messageHash = keccak_256(keccak_256(serializeVaaBodyToBuffer(body))); - - for (let guardian of keychain) { - const signature = secp.sign(messageHash, guardian.secretKey) - - const id = Buffer.alloc(1); - id.writeUint8(guardian.guardianId, 0); - - // v.writeUint8(signature.addRecoveryBit, 1); - if (signature.recovery) { - const rec = Buffer.alloc(1); - rec.writeUint8(signature.recovery, 0); - signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); - } else { - const rec = Buffer.alloc(1); - rec.writeUint8(0, 0); - signatures.push(Buffer.concat([id, signature.toCompactRawBytes(), rec])); - } - } - return { - version: opts?.version, - guardianSetId: opts?.guardianSetId, - signatures: signatures, - } + export interface Guardian { + guardianId: number; + secretKey: Uint8Array; + compressedPublicKey: Uint8Array; + uncompressedPublicKey: Uint8Array; + ethereumAddress: Uint8Array; + } + + export const generateGuardianSetKeychain = (count = 19): Guardian[] => { + let keychain = []; + for (let i = 0; i < count; i++) { + let secretKey = secp.utils.randomPrivateKey(); + let uncompressedPublicKey = secp + .getPublicKey(secretKey, false) + .slice(1, 65); + let ethereumAddress = keccak_256(uncompressedPublicKey).slice(12, 32); + keychain.push({ + guardianId: i, + secretKey: secretKey, + uncompressedPublicKey: uncompressedPublicKey, + ethereumAddress: ethereumAddress, + compressedPublicKey: secp.getPublicKey(secretKey, true), + }); } - - export const buildValidVaaHeader = (keychain: Guardian[], body: VaaBody, opts: VaaHeaderBuildOptions): VaaHeader => { - let specs = buildValidVaaHeaderSpecs(keychain, body, opts); - return { - version: specs.version!, - guardianSetId: specs.guardianSetId!, - signatures: specs.signatures!, - } - } - - export const serializeVaaToClarityValue = (header: VaaHeader, body: VaaBody, keychain: Guardian[]): [ClarityValue, any[]] => { - let guardiansPublicKeys = []; - let guardiansSignatures = []; - for (let i = 0; i < header.signatures.length; i++) { - let guardianId = header.signatures[i][0]; - if (keychain.length > i) { - let guardian = keychain[i]; - guardiansPublicKeys.push(Cl.tuple({ - "guardian-id": Cl.uint(guardianId), - "recovered-compressed-public-key": Cl.buffer(guardian.compressedPublicKey) - })) - } - guardiansSignatures.push(Cl.tuple({ - "guardian-id": Cl.uint(header.signatures[i].slice(0, 1)), - "signature": Cl.buffer(header.signatures[i].slice(1, 66)) - })) - } - let value = Cl.tuple({ - "consistency-level": Cl.uint(body.consistencyLevel), - "version": Cl.uint(header.version), - "guardian-set-id": Cl.uint(header.guardianSetId), - "signatures-len": Cl.uint(header.signatures.length), - "signatures": Cl.list(guardiansSignatures), - "emitter-chain": Cl.uint(body.emitterChain), - "emitter-address": Cl.buffer(body.emitterAddress), - "sequence": Cl.uint(body.sequence), - "timestamp": Cl.uint(body.timestamp), - "nonce": Cl.uint(body.nonce), - "payload": Cl.buffer(body.payload) - }) - return [value, guardiansPublicKeys] - } - - export interface VaaBodySpec { - values: VaaBody, - specs: (fc.Arbitrary | fc.Arbitrary | fc.Arbitrary | fc.Arbitrary)[] - } - - export const buildValidVaaBodySpecs = (opts?: { payload?: Uint8Array }): VaaBody => { - const date = Math.floor(Date.now() / 1000); - const timestamp = date >>> 0; - const payload = (opts && opts.payload && opts.payload) || new Uint8Array(32) - let values = { - timestamp: timestamp, - nonce: 0, - emitterChain: 0, - emitterAddress: new Uint8Array(32), - sequence: 0n, - consistencyLevel: 0, - payload: payload, - }; - return values + return keychain; + }; + + export interface VaaHeader { + version: number; + guardianSetId: number; + signatures: Uint8Array[]; + } + + export interface VaaBody { + timestamp: number; + emitterChain: number; + nonce: number; + emitterAddress: Uint8Array; + sequence: bigint; + consistencyLevel: number; + payload: Uint8Array; + } + + export interface VaaHeaderBuildOptions { + version?: number; + guardianSetId?: number; + signatures?: Uint8Array[]; + } + + export interface VaaBodyBuildOptions { + timestamp?: number; + emitterChain?: number; + nonce?: number; + emitterAddress?: Uint8Array; + sequence?: bigint; + consistencyLevel?: number; + payload?: Uint8Array; + } + + export namespace fc_ext { + // Helper for generating a VAA Body; + // Wire format reminder: + // =========================== + // VAA Body + // u32 timestamp (Timestamp of the block where the source transaction occurred) + // u32 nonce (A grouping number) + // u16 emitter_chain (Wormhole ChainId of emitter contract) + // [32]byte emitter_address (Emitter contract address, in Wormhole format) + // u64 sequence (Strictly increasing sequence, tied to emitter address & chain) + // u8 consistency_level (What finality level was reached before emitting this message) + // []byte payload (VAA message content) + export const vaaBody = (opts?: VaaBodyBuildOptions) => { + // Timestamp + let timestamp = fc.nat(4294967295); + if (opts && opts.timestamp) { + timestamp = fc.constant(opts.timestamp); + } + + // Nonce + let nonce = fc.nat(4294967295); + if (opts && opts.nonce) { + nonce = fc.constant(opts.nonce); + } + + // Emitter chain + let emitterChain = fc.nat(65535); + if (opts && opts.emitterChain) { + emitterChain = fc.constant(opts.emitterChain); + } + + // Emitter address + let emitterAddress = fc.uint8Array({ minLength: 32, maxLength: 32 }); + if (opts && opts.emitterAddress) { + emitterAddress = fc.constant(opts.emitterAddress); + } + + // Sequence + let sequence = fc.bigUintN(64); + if (opts && opts.sequence) { + sequence = fc.constant(opts.sequence); + } + + // Consistency level + let consistencyLevel = fc.nat(255); + if (opts && opts.consistencyLevel) { + consistencyLevel = fc.constant(opts.consistencyLevel); + } + + // Payload + let payload = fc.uint8Array({ minLength: 20, maxLength: 2048 }); + if (opts && opts.payload) { + payload = fc.constant(opts.payload); + } + + return fc.tuple( + timestamp, + nonce, + emitterChain, + emitterAddress, + sequence, + consistencyLevel, + payload, + ); + }; + + // Helper for generating a VAA Header; + // Wire format reminder: + // =========================== + // VAA Header + // byte version (VAA Version) + // u32 guardian_set_index (Indicates which guardian set is signing) + // u8 len_signatures (Number of signatures stored) + // [][66]byte signatures (Collection of ecdsa signatures) + export const vaaHeader = ( + opts?: VaaHeaderBuildOptions, + numberOfSignatures = 19, + ) => { + // Version + let version = fc.nat(255); + if (opts && opts.version) { + version = fc.constant(opts.version); + } + + // Guardian set id + let guardianSetId = fc.nat(255); + if (opts && opts.guardianSetId) { + guardianSetId = fc.constant(opts.guardianSetId); + } + + // Specified signatures + let specifiedSignatures = fc.array( + fc.uint8Array({ minLength: 66, maxLength: 66 }), + { minLength: 0, maxLength: 0 }, + ); + let specifiedSignaturesLen = 0; + if (opts && opts.signatures) { + specifiedSignatures = fc.constant(opts.signatures); + specifiedSignaturesLen = opts.signatures.length; + } + + // Generated signatures + let generatedSignatures = fc.array( + fc.uint8Array({ minLength: 66, maxLength: 66 }), + { + minLength: 0, + maxLength: numberOfSignatures - specifiedSignaturesLen, + }, + ); + + return fc.tuple( + version, + guardianSetId, + specifiedSignatures, + generatedSignatures, + ); + }; + } + + export const buildValidVaaHeaderSpecs = ( + keychain: Guardian[], + body: VaaBody, + opts?: VaaHeaderBuildOptions, + ): VaaHeaderBuildOptions => { + let signatures = []; + const messageHash = keccak_256(keccak_256(serializeVaaBodyToBuffer(body))); + + for (let guardian of keychain) { + const signature = secp.sign(messageHash, guardian.secretKey); + + const id = Buffer.alloc(1); + id.writeUint8(guardian.guardianId, 0); + + // v.writeUint8(signature.addRecoveryBit, 1); + if (signature.recovery) { + const rec = Buffer.alloc(1); + rec.writeUint8(signature.recovery, 0); + signatures.push( + Buffer.concat([id, signature.toCompactRawBytes(), rec]), + ); + } else { + const rec = Buffer.alloc(1); + rec.writeUint8(0, 0); + signatures.push( + Buffer.concat([id, signature.toCompactRawBytes(), rec]), + ); + } } - - export const assembleVaaBody = (timestamp: number | bigint | Uint8Array, nonce: number | bigint | Uint8Array, emitterChain: number | bigint | Uint8Array, emitterAddress: number | bigint | Uint8Array, sequence: number | bigint | Uint8Array, consistencyLevel: number | bigint | Uint8Array, payload: number | bigint | Uint8Array): VaaBody => { - return { - timestamp: timestamp as number, - nonce: nonce as number, - emitterChain: emitterChain as number, - emitterAddress: emitterAddress as Uint8Array, - sequence: sequence as bigint, - consistencyLevel: consistencyLevel as number, - payload: payload as Uint8Array - } - } - - export const assembleVaaHeader = (version: number | bigint | Uint8Array, guardianSetId: number | bigint | Uint8Array, signatures: number | bigint | Uint8Array[]): VaaHeader => { - return { - version: version as number, - guardianSetId: guardianSetId as number, - signatures: signatures as Uint8Array[], - } - } - - export const serializeVaaToBuffer = (vaaHeader: VaaHeader, vaaBody: VaaBody) => { - return Buffer.concat([serializeVaaHeaderToBuffer(vaaHeader), serializeVaaBodyToBuffer(vaaBody)]); - } - - export const serializeVaaHeaderToBuffer = (vaaHeader: VaaHeader) => { - const components = []; - var v = Buffer.alloc(1); - v.writeUint8(vaaHeader.version, 0); - components.push(v); - - v = Buffer.alloc(4); - v.writeUInt32BE(vaaHeader.guardianSetId, 0); - components.push(v); - - v = Buffer.alloc(1); - v.writeUint8(vaaHeader.signatures.length, 0); - components.push(v); - - components.push(Buffer.concat(vaaHeader.signatures)); - return Buffer.concat(components); - } - - export const serializeVaaBodyToBuffer = (vaaBody: VaaBody) => { - const components = []; - let v = Buffer.alloc(4); - v.writeUInt32BE(vaaBody.timestamp, 0); - components.push(v); - - v = Buffer.alloc(4); - v.writeUInt32BE(vaaBody.nonce, 0); - components.push(v); - - v = Buffer.alloc(2); - v.writeUInt16BE(vaaBody.emitterChain, 0); - components.push(v); - - components.push(vaaBody.emitterAddress); - - components.push(bigintToBuffer(vaaBody.sequence, 8)); - - v = Buffer.alloc(1); - v.writeUint8(vaaBody.consistencyLevel, 0); - components.push(v); - - components.push(vaaBody.payload); - - return Buffer.concat(components); - } - - export const validGuardianRotationModule = Buffer.from('00000000000000000000000000000000000000000000000000000000436f7265', 'hex'); - - export const serializeGuardianUpdateVaaPayloadToBuffer = (keyChain: Guardian[], action: number, chain: number, setId: number, module = validGuardianRotationModule) => { - const components = []; - components.push(module); - - let v = Buffer.alloc(1); - v.writeUint8(action, 0); - components.push(v); - - v = Buffer.alloc(2); - v.writeUInt16BE(chain, 0); - components.push(v); - - v = Buffer.alloc(4); - v.writeUInt32BE(setId, 0); - components.push(v); - - v = Buffer.alloc(1); - v.writeUint8(keyChain.length, 0); - components.push(v); - - for (let guardian of keyChain) { - components.push(guardian.ethereumAddress) - } - - return Buffer.concat(components); - } - - export function applyGuardianSetUpdate(keychain: wormhole.Guardian[], guardianSetId: number, txSenderAddress: string, contract_name: string) { - let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 0, guardianSetId, wormhole.validGuardianRotationModule); - let vaaBody = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { version: 1, guardianSetId: guardianSetId - 1 }); - let vaa = wormhole.serializeVaaToBuffer(vaaHeader, vaaBody); - let uncompressedPublicKey = []; - for (let guardian of keychain) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - let result = simnet.mineBlock([ - tx.callPublicFn( - contract_name, - `update-guardians-set`, - [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], - txSenderAddress + return { + version: opts?.version, + guardianSetId: opts?.guardianSetId, + signatures: signatures, + }; + }; + + export const buildValidVaaHeader = ( + keychain: Guardian[], + body: VaaBody, + opts: VaaHeaderBuildOptions, + ): VaaHeader => { + let specs = buildValidVaaHeaderSpecs(keychain, body, opts); + return { + version: specs.version!, + guardianSetId: specs.guardianSetId!, + signatures: specs.signatures!, + }; + }; + + export const serializeVaaToClarityValue = ( + header: VaaHeader, + body: VaaBody, + keychain: Guardian[], + ): [ClarityValue, any[]] => { + let guardiansPublicKeys = []; + let guardiansSignatures = []; + for (let i = 0; i < header.signatures.length; i++) { + let guardianId = header.signatures[i][0]; + if (keychain.length > i) { + let guardian = keychain[i]; + guardiansPublicKeys.push( + Cl.tuple({ + "guardian-id": Cl.uint(guardianId), + "recovered-compressed-public-key": Cl.buffer( + guardian.compressedPublicKey, ), - ])[0].result; - return [result, vaaHeader, vaaBody] + }), + ); + } + guardiansSignatures.push( + Cl.tuple({ + "guardian-id": Cl.uint(header.signatures[i].slice(0, 1)), + signature: Cl.buffer(header.signatures[i].slice(1, 66)), + }), + ); + } + let value = Cl.tuple({ + "consistency-level": Cl.uint(body.consistencyLevel), + version: Cl.uint(header.version), + "guardian-set-id": Cl.uint(header.guardianSetId), + "signatures-len": Cl.uint(header.signatures.length), + signatures: Cl.list(guardiansSignatures), + "emitter-chain": Cl.uint(body.emitterChain), + "emitter-address": Cl.buffer(body.emitterAddress), + sequence: Cl.uint(body.sequence), + timestamp: Cl.uint(body.timestamp), + nonce: Cl.uint(body.nonce), + payload: Cl.buffer(body.payload), + }); + return [value, guardiansPublicKeys]; + }; + + export interface VaaBodySpec { + values: VaaBody; + specs: ( + | fc.Arbitrary + | fc.Arbitrary + | fc.Arbitrary + | fc.Arbitrary + )[]; + } + + export const buildValidVaaBodySpecs = (opts?: { + payload?: Uint8Array; + }): VaaBody => { + const date = Math.floor(Date.now() / 1000); + const timestamp = date >>> 0; + const payload = + (opts && opts.payload && opts.payload) || new Uint8Array(32); + let values = { + timestamp: timestamp, + nonce: 0, + emitterChain: 0, + emitterAddress: new Uint8Array(32), + sequence: 0n, + consistencyLevel: 0, + payload: payload, + }; + return values; + }; + + export const assembleVaaBody = ( + timestamp: number | bigint | Uint8Array, + nonce: number | bigint | Uint8Array, + emitterChain: number | bigint | Uint8Array, + emitterAddress: number | bigint | Uint8Array, + sequence: number | bigint | Uint8Array, + consistencyLevel: number | bigint | Uint8Array, + payload: number | bigint | Uint8Array, + ): VaaBody => { + return { + timestamp: timestamp as number, + nonce: nonce as number, + emitterChain: emitterChain as number, + emitterAddress: emitterAddress as Uint8Array, + sequence: sequence as bigint, + consistencyLevel: consistencyLevel as number, + payload: payload as Uint8Array, + }; + }; + + export const assembleVaaHeader = ( + version: number | bigint | Uint8Array, + guardianSetId: number | bigint | Uint8Array, + signatures: number | bigint | Uint8Array[], + ): VaaHeader => { + return { + version: version as number, + guardianSetId: guardianSetId as number, + signatures: signatures as Uint8Array[], + }; + }; + + export const serializeVaaToBuffer = ( + vaaHeader: VaaHeader, + vaaBody: VaaBody, + ) => { + return Buffer.concat([ + serializeVaaHeaderToBuffer(vaaHeader), + serializeVaaBodyToBuffer(vaaBody), + ]); + }; + + export const serializeVaaHeaderToBuffer = (vaaHeader: VaaHeader) => { + const components = []; + var v = Buffer.alloc(1); + v.writeUint8(vaaHeader.version, 0); + components.push(v); + + v = Buffer.alloc(4); + v.writeUInt32BE(vaaHeader.guardianSetId, 0); + components.push(v); + + v = Buffer.alloc(1); + v.writeUint8(vaaHeader.signatures.length, 0); + components.push(v); + + components.push(Buffer.concat(vaaHeader.signatures)); + return Buffer.concat(components); + }; + + export const serializeVaaBodyToBuffer = (vaaBody: VaaBody) => { + const components = []; + let v = Buffer.alloc(4); + v.writeUInt32BE(vaaBody.timestamp, 0); + components.push(v); + + v = Buffer.alloc(4); + v.writeUInt32BE(vaaBody.nonce, 0); + components.push(v); + + v = Buffer.alloc(2); + v.writeUInt16BE(vaaBody.emitterChain, 0); + components.push(v); + + components.push(vaaBody.emitterAddress); + + components.push(bigintToBuffer(vaaBody.sequence, 8)); + + v = Buffer.alloc(1); + v.writeUint8(vaaBody.consistencyLevel, 0); + components.push(v); + + components.push(vaaBody.payload); + + return Buffer.concat(components); + }; + + export const validGuardianRotationModule = Buffer.from( + "00000000000000000000000000000000000000000000000000000000436f7265", + "hex", + ); + + export const serializeGuardianUpdateVaaPayloadToBuffer = ( + keyChain: Guardian[], + action: number, + chain: number, + setId: number, + module = validGuardianRotationModule, + ) => { + const components = []; + components.push(module); + + let v = Buffer.alloc(1); + v.writeUint8(action, 0); + components.push(v); + + v = Buffer.alloc(2); + v.writeUInt16BE(chain, 0); + components.push(v); + + v = Buffer.alloc(4); + v.writeUInt32BE(setId, 0); + components.push(v); + + v = Buffer.alloc(1); + v.writeUint8(keyChain.length, 0); + components.push(v); + + for (let guardian of keyChain) { + components.push(guardian.ethereumAddress); } - export function applyMainnetGuardianSetUpdates(txSenderAddress: string, contractName: string) { - const vaaRotation1 = Cl.bufferFromHex(gsuMainnetVaas[0].vaa); - let publicKeysRotation1 = gsuMainnetVaas[0].keys.map(Cl.bufferFromHex); - - const vaaRotation2 = Cl.bufferFromHex(gsuMainnetVaas[1].vaa); - let publicKeysRotation2 = gsuMainnetVaas[1].keys.map(Cl.bufferFromHex); - - const vaaRotation3 = Cl.bufferFromHex(gsuMainnetVaas[2].vaa); - let publicKeysRotation3 = gsuMainnetVaas[2].keys.map(Cl.bufferFromHex); - - const block = simnet.mineBlock([ - tx.callPublicFn( - contractName, - "update-guardians-set", - [vaaRotation1, Cl.list(publicKeysRotation1)], - txSenderAddress - ), - tx.callPublicFn( - contractName, - "update-guardians-set", - [vaaRotation2, Cl.list(publicKeysRotation2)], - txSenderAddress - ), - tx.callPublicFn( - contractName, - "update-guardians-set", - [vaaRotation3, Cl.list(publicKeysRotation3)], - txSenderAddress - ), - ]); - return block; + return Buffer.concat(components); + }; + + export function applyGuardianSetUpdate( + keychain: wormhole.Guardian[], + guardianSetId: number, + txSenderAddress: string, + contract_name: string, + ) { + let guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + keychain, + 2, + 0, + guardianSetId, + wormhole.validGuardianRotationModule, + ); + let vaaBody = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, + }); + let vaaHeader = wormhole.buildValidVaaHeader(keychain, vaaBody, { + version: 1, + guardianSetId: guardianSetId - 1, + }); + let vaa = wormhole.serializeVaaToBuffer(vaaHeader, vaaBody); + let uncompressedPublicKey = []; + for (let guardian of keychain) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); } + let result = simnet.mineBlock([ + tx.callPublicFn( + contract_name, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + txSenderAddress, + ), + ])[0].result; + return [result, vaaHeader, vaaBody]; + } + + export function applyMainnetGuardianSetUpdates( + txSenderAddress: string, + contractName: string, + ) { + const vaaRotation1 = Cl.bufferFromHex(gsuMainnetVaas[0].vaa); + let publicKeysRotation1 = gsuMainnetVaas[0].keys.map(Cl.bufferFromHex); + + const vaaRotation2 = Cl.bufferFromHex(gsuMainnetVaas[1].vaa); + let publicKeysRotation2 = gsuMainnetVaas[1].keys.map(Cl.bufferFromHex); + + const vaaRotation3 = Cl.bufferFromHex(gsuMainnetVaas[2].vaa); + let publicKeysRotation3 = gsuMainnetVaas[2].keys.map(Cl.bufferFromHex); + + const block = simnet.mineBlock([ + tx.callPublicFn( + contractName, + "update-guardians-set", + [vaaRotation1, Cl.list(publicKeysRotation1)], + txSenderAddress, + ), + tx.callPublicFn( + contractName, + "update-guardians-set", + [vaaRotation2, Cl.list(publicKeysRotation2)], + txSenderAddress, + ), + tx.callPublicFn( + contractName, + "update-guardians-set", + [vaaRotation3, Cl.list(publicKeysRotation3)], + txSenderAddress, + ), + ]); + return block; + } } diff --git a/unit-tests/wormhole/vaa.test.ts b/unit-tests/wormhole/vaa.test.ts index e348a1f..75da0cc 100644 --- a/unit-tests/wormhole/vaa.test.ts +++ b/unit-tests/wormhole/vaa.test.ts @@ -1,414 +1,585 @@ import { Cl, ClarityType } from "@stacks/transactions"; import { expect, describe, beforeEach } from "vitest"; -import { it, fc } from '@fast-check/vitest'; -import { wormhole } from './helpers'; +import { it, fc } from "@fast-check/vitest"; +import { wormhole } from "./helpers"; import { ParsedTransactionResult, tx } from "@hirosystems/clarinet-sdk"; const contractName = "wormhole-core-v1"; const verbosity = 0; describe("wormhole-core-v1::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 headerSpecs = wormhole.buildValidVaaHeaderSpecs(keychain, body); - - fc.assert(fc.property(wormhole.fc_ext.vaaHeader(headerSpecs, 19), ([version, guardianSetIndex, providedSignatures, generatedSignatures]) => { - let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; - let header = wormhole.assembleVaaHeader(version, guardianSetIndex, consolidatedSignatures) - let vaa = wormhole.serializeVaaToBuffer(header, body); - let [decodedVaa, guardiansPublicKeys] = wormhole.serializeVaaToClarityValue(header, body, keychain); - - const res = simnet.callReadOnlyFn( - contractName, - `parse-vaa`, - [Cl.buffer(vaa)], - sender - ); - res.result - expect(res.result).toBeOk(Cl.tuple({ - "vaa": decodedVaa, - "recovered-public-keys": Cl.list(guardiansPublicKeys) - })); - }), - { verbose: verbosity }) - }); -}) + 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 headerSpecs = wormhole.buildValidVaaHeaderSpecs(keychain, body); + + fc.assert( + fc.property( + wormhole.fc_ext.vaaHeader(headerSpecs, 19), + ([ + version, + guardianSetIndex, + providedSignatures, + generatedSignatures, + ]) => { + let consolidatedSignatures = [ + ...(providedSignatures as Uint8Array[]), + ...(generatedSignatures as Uint8Array[]), + ]; + let header = wormhole.assembleVaaHeader( + version, + guardianSetIndex, + consolidatedSignatures, + ); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let [decodedVaa, guardiansPublicKeys] = + wormhole.serializeVaaToClarityValue(header, body, keychain); + + const res = simnet.callReadOnlyFn( + contractName, + `parse-vaa`, + [Cl.buffer(vaa)], + sender, + ); + res.result; + expect(res.result).toBeOk( + Cl.tuple({ + vaa: decodedVaa, + "recovered-public-keys": Cl.list(guardiansPublicKeys), + }), + ); + }, + ), + { verbose: verbosity }, + ); + }); +}); describe("wormhole-core-v1::update-guardians-set failures", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - const keychain = wormhole.generateGuardianSetKeychain(19); - - it("should fail if the chain is invalid", () => { - // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 1, 1, wormhole.validGuardianRotationModule); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - 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(1303)); + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const keychain = wormhole.generateGuardianSetKeychain(19); + + it("should fail if the chain is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + keychain, + 2, + 1, + 1, + wormhole.validGuardianRotationModule, + ); + let body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, }); - - it("should fail if the action is invalid", () => { - // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 0, 0, 1, wormhole.validGuardianRotationModule); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - 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(1302)); + let header = wormhole.buildValidVaaHeader(keychain, body, { + version: 1, + guardianSetId: 0, + signatures: [], }); - - it("should fail if the set id is invalid", () => { - // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 0, 0, wormhole.validGuardianRotationModule); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - 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(1304)); + 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(1303)); + }); + + it("should fail if the action is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + keychain, + 0, + 0, + 1, + wormhole.validGuardianRotationModule, + ); + let body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, }); - - it("should fail if the module is invalid", () => { - // Before performing this test, we need to setup the guardian set - let guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(keychain, 2, 0, 1, Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')); - let body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - 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(1301)); + 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(1302)); + }); + + it("should fail if the set id is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + keychain, + 2, + 0, + 0, + wormhole.validGuardianRotationModule, + ); + let body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, + }); + 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(1304)); + }); + + it("should fail if the module is invalid", () => { + // Before performing this test, we need to setup the guardian set + let guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + keychain, + 2, + 0, + 1, + Buffer.from( + "0000000000000000000000000000000000000000000000000000000000000000", + "hex", + ), + ); + let body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, + }); + 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(1301)); + }); +}); describe("wormhole-core-v1::update-guardians-set success", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); - const guardianSet2Keys = wormhole.generateGuardianSetKeychain(19); - let guardianSet1 = Cl.tuple({}); - - // Before starting the test suite, we have to setup the guardian set. - beforeEach(async () => { - let [result, vaaHeader, vaaBody] = wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName) - let [serializeVaaToClarityValue, _] = wormhole.serializeVaaToClarityValue( - vaaHeader as wormhole.VaaHeader, - vaaBody as wormhole.VaaBody, - guardianSet1Keys); - - guardianSet1 = Cl.tuple({ - "set-id": Cl.uint(1), - "guardians": Cl.list(guardianSet1Keys.map((g) => Cl.tuple({ - "compressed-public-key": Cl.buffer(g.compressedPublicKey), - "uncompressed-public-key": Cl.buffer(g.uncompressedPublicKey) - }))) - }); - - expect(result).toBeOk(Cl.tuple({ - 'result': Cl.tuple({ - 'guardians-eth-addresses': Cl.list(guardianSet1Keys.map((g) => Cl.buffer(g.ethereumAddress))), - 'guardians-public-keys': Cl.list(guardianSet1Keys.map((g) => Cl.buffer(g.uncompressedPublicKey))), - }), - 'vaa': serializeVaaToClarityValue - })); - }) - - it("should return the set 1 as active", () => { - let res = simnet.callPublicFn( - contractName, - `get-active-guardian-set`, - [], - sender - ).result; - expect(res).toBeOk(guardianSet1); + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); + const guardianSet2Keys = wormhole.generateGuardianSetKeychain(19); + let guardianSet1 = Cl.tuple({}); + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + let [result, vaaHeader, vaaBody] = wormhole.applyGuardianSetUpdate( + guardianSet1Keys, + 1, + sender, + contractName, + ); + let [serializeVaaToClarityValue, _] = wormhole.serializeVaaToClarityValue( + vaaHeader as wormhole.VaaHeader, + vaaBody as wormhole.VaaBody, + guardianSet1Keys, + ); + + guardianSet1 = Cl.tuple({ + "set-id": Cl.uint(1), + guardians: Cl.list( + guardianSet1Keys.map((g) => + Cl.tuple({ + "compressed-public-key": Cl.buffer(g.compressedPublicKey), + "uncompressed-public-key": Cl.buffer(g.uncompressedPublicKey), + }), + ), + ), }); - it("should successfully handle subsequent rotation", () => { - const guardianSet2 = Cl.tuple({ - "set-id": Cl.uint(2), - "guardians": Cl.list(guardianSet2Keys.map((g) => Cl.tuple({ - "compressed-public-key": Cl.buffer(g.compressedPublicKey), - "uncompressed-public-key": Cl.buffer(g.uncompressedPublicKey) - }))) - }); - // Before performing this test, we need to setup the guardian set - const guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(guardianSet2Keys, 2, 0, 2, wormhole.validGuardianRotationModule); - const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); - const vaa = wormhole.serializeVaaToBuffer(header, body); - const uncompressedPublicKey = []; - for (let guardian of guardianSet2Keys) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - const [serializeVaaToClarityValue, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet1Keys); - let res = simnet.mineBlock([ - tx.callPublicFn( - contractName, - `update-guardians-set`, - [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], - sender - ) - ])[0].result - expect(res).toBeOk(Cl.tuple({ - 'result': Cl.tuple({ - 'guardians-eth-addresses': Cl.list(guardianSet2Keys.map((g) => Cl.buffer(g.ethereumAddress))), - 'guardians-public-keys': Cl.list(guardianSet2Keys.map((g) => Cl.buffer(g.uncompressedPublicKey))), - }), - 'vaa': serializeVaaToClarityValue - })); - - res = simnet.callPublicFn( - contractName, - `get-active-guardian-set`, - [], - sender - ).result; - expect(res).toBeOk(guardianSet2); + expect(result).toBeOk( + Cl.tuple({ + result: Cl.tuple({ + "guardians-eth-addresses": Cl.list( + guardianSet1Keys.map((g) => Cl.buffer(g.ethereumAddress)), + ), + "guardians-public-keys": Cl.list( + guardianSet1Keys.map((g) => Cl.buffer(g.uncompressedPublicKey)), + ), + }), + vaa: serializeVaaToClarityValue, + }), + ); + }); + + it("should return the set 1 as active", () => { + let res = simnet.callPublicFn( + contractName, + `get-active-guardian-set`, + [], + sender, + ).result; + expect(res).toBeOk(guardianSet1); + }); + + it("should successfully handle subsequent rotation", () => { + const guardianSet2 = Cl.tuple({ + "set-id": Cl.uint(2), + guardians: Cl.list( + guardianSet2Keys.map((g) => + Cl.tuple({ + "compressed-public-key": Cl.buffer(g.compressedPublicKey), + "uncompressed-public-key": Cl.buffer(g.uncompressedPublicKey), + }), + ), + ), }); - - it("should reject subsequent update if there is a eth address / uncompressed public keys mismatch", () => { - let guardianSet2KeysSubset = guardianSet2Keys.splice(0, 12); - // Before performing this test, we need to setup the guardian set - const guardianRotationPayload = wormhole.serializeGuardianUpdateVaaPayloadToBuffer(guardianSet2KeysSubset, 2, 0, 2, wormhole.validGuardianRotationModule); - const body = wormhole.buildValidVaaBodySpecs({ payload: guardianRotationPayload }); - const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); - const vaa = wormhole.serializeVaaToBuffer(header, body); - const uncompressedPublicKey = []; - for (let guardian of guardianSet2Keys) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - let res = simnet.mineBlock([ - tx.callPublicFn( - contractName, - `update-guardians-set`, - [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], - sender - ) - ])[0].result - expect(res).toBeErr(Cl.uint(1207)); + // Before performing this test, we need to setup the guardian set + const guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + guardianSet2Keys, + 2, + 0, + 2, + wormhole.validGuardianRotationModule, + ); + const body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, }); -}) - -describe("wormhole-core-v1::parse-and-verify-vaa success", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); - // const guardianSet2Keys = wormhole.generateGuardianSetKeychain(19); - - // Before starting the test suite, we have to setup the guardian set. - beforeEach(async () => { - wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName) - }) - - it("should succeed if the vaa is valid", () => { - let body = wormhole.buildValidVaaBodySpecs(); - let headerSpecs = wormhole.buildValidVaaHeaderSpecs(guardianSet1Keys, body); - - fc.assert(fc.property(wormhole.fc_ext.vaaHeader(headerSpecs, 19), ([version, guardianSetIndex, providedSignatures, generatedSignatures]) => { - let consolidatedSignatures = [...(providedSignatures as Uint8Array[]), ...(generatedSignatures as Uint8Array[])]; - let header = wormhole.assembleVaaHeader(version, guardianSetIndex, consolidatedSignatures) - let vaa = wormhole.serializeVaaToBuffer(header, body); - let [decodedVaa, guardiansPublicKeys] = wormhole.serializeVaaToClarityValue(header, body, guardianSet1Keys); - - const res = simnet.callReadOnlyFn( - contractName, - `parse-vaa`, - [Cl.buffer(vaa)], - sender - ); - res.result - expect(res.result).toBeOk(Cl.tuple({ - "vaa": decodedVaa, - "recovered-public-keys": Cl.list(guardiansPublicKeys) - })); + const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { + version: 1, + guardianSetId: 1, + }); + const vaa = wormhole.serializeVaaToBuffer(header, body); + const uncompressedPublicKey = []; + for (let guardian of guardianSet2Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const [serializeVaaToClarityValue, _] = wormhole.serializeVaaToClarityValue( + header, + body, + guardianSet1Keys, + ); + let res = simnet.mineBlock([ + tx.callPublicFn( + contractName, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender, + ), + ])[0].result; + expect(res).toBeOk( + Cl.tuple({ + result: Cl.tuple({ + "guardians-eth-addresses": Cl.list( + guardianSet2Keys.map((g) => Cl.buffer(g.ethereumAddress)), + ), + "guardians-public-keys": Cl.list( + guardianSet2Keys.map((g) => Cl.buffer(g.uncompressedPublicKey)), + ), }), - { verbose: verbosity }) + vaa: serializeVaaToClarityValue, + }), + ); + + res = simnet.callPublicFn( + contractName, + `get-active-guardian-set`, + [], + sender, + ).result; + expect(res).toBeOk(guardianSet2); + }); + + it("should reject subsequent update if there is a eth address / uncompressed public keys mismatch", () => { + let guardianSet2KeysSubset = guardianSet2Keys.splice(0, 12); + // Before performing this test, we need to setup the guardian set + const guardianRotationPayload = + wormhole.serializeGuardianUpdateVaaPayloadToBuffer( + guardianSet2KeysSubset, + 2, + 0, + 2, + wormhole.validGuardianRotationModule, + ); + const body = wormhole.buildValidVaaBodySpecs({ + payload: guardianRotationPayload, }); + const header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { + version: 1, + guardianSetId: 1, + }); + const vaa = wormhole.serializeVaaToBuffer(header, body); + const uncompressedPublicKey = []; + for (let guardian of guardianSet2Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + let res = simnet.mineBlock([ + tx.callPublicFn( + contractName, + `update-guardians-set`, + [Cl.buffer(vaa), Cl.list(uncompressedPublicKey)], + sender, + ), + ])[0].result; + expect(res).toBeErr(Cl.uint(1207)); + }); +}); - it("should succeed if quorum is just met", () => { - // 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 header = wormhole.buildValidVaaHeader(guardianSet1KeysSubset, body, { version: 1, guardianSetId: 1 }); - let vaa = wormhole.serializeVaaToBuffer(header, body); - let [decodedVaa, _] = wormhole.serializeVaaToClarityValue(header, body, guardianSet1KeysSubset); - - const res = simnet.callReadOnlyFn( +describe("wormhole-core-v1::parse-and-verify-vaa success", () => { + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); + // const guardianSet2Keys = wormhole.generateGuardianSetKeychain(19); + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName); + }); + + it("should succeed if the vaa is valid", () => { + let body = wormhole.buildValidVaaBodySpecs(); + let headerSpecs = wormhole.buildValidVaaHeaderSpecs(guardianSet1Keys, body); + + fc.assert( + fc.property( + wormhole.fc_ext.vaaHeader(headerSpecs, 19), + ([ + version, + guardianSetIndex, + providedSignatures, + generatedSignatures, + ]) => { + let consolidatedSignatures = [ + ...(providedSignatures as Uint8Array[]), + ...(generatedSignatures as Uint8Array[]), + ]; + let header = wormhole.assembleVaaHeader( + version, + guardianSetIndex, + consolidatedSignatures, + ); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let [decodedVaa, guardiansPublicKeys] = + wormhole.serializeVaaToClarityValue(header, body, guardianSet1Keys); + + const res = simnet.callReadOnlyFn( contractName, - `parse-and-verify-vaa`, + `parse-vaa`, [Cl.buffer(vaa)], - sender - ); - expect(res.result).toBeOk(decodedVaa) + sender, + ); + res.result; + expect(res.result).toBeOk( + Cl.tuple({ + vaa: decodedVaa, + "recovered-public-keys": Cl.list(guardiansPublicKeys), + }), + ); + }, + ), + { verbose: verbosity }, + ); + }); + + it("should succeed if quorum is just met", () => { + // 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 header = wormhole.buildValidVaaHeader(guardianSet1KeysSubset, body, { + version: 1, + guardianSetId: 1, }); -}) + let vaa = wormhole.serializeVaaToBuffer(header, body); + let [decodedVaa, _] = wormhole.serializeVaaToClarityValue( + header, + body, + guardianSet1KeysSubset, + ); + + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender, + ); + expect(res.result).toBeOk(decodedVaa); + }); +}); describe("wormhole-core-v1::parse-and-verify-vaa failures", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); - - // Before starting the test suite, we have to setup the guardian set. - beforeEach(async () => { - wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName) - }) - - it("should fail if the version is invalid", () => { - // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); - let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 2, guardianSetId: 1 }); - let vaa = wormhole.serializeVaaToBuffer(header, body); - let uncompressedPublicKey = []; - for (let guardian of guardianSet1Keys) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - const res = simnet.callReadOnlyFn( - contractName, - `parse-and-verify-vaa`, - [Cl.buffer(vaa)], - sender - ); - expect(res.result).toBeErr(Cl.uint(1101)); + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + const guardianSet1Keys = wormhole.generateGuardianSetKeychain(19); + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + wormhole.applyGuardianSetUpdate(guardianSet1Keys, 1, sender, contractName); + }); + + it("should fail if the version is invalid", () => { + // Before performing this test, we need to setup the guardian set + let body = wormhole.buildValidVaaBodySpecs(); + let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { + version: 2, + guardianSetId: 1, }); - - it("should fail if the guardianSetId is invalid", () => { - // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); - let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 2 }); - let vaa = wormhole.serializeVaaToBuffer(header, body); - let uncompressedPublicKey = []; - for (let guardian of guardianSet1Keys) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - const res = simnet.callReadOnlyFn( - contractName, - `parse-and-verify-vaa`, - [Cl.buffer(vaa)], - sender - ); - expect(res.result).toBeErr(Cl.uint(1105)); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender, + ); + expect(res.result).toBeErr(Cl.uint(1101)); + }); + + it("should fail if the guardianSetId is invalid", () => { + // Before performing this test, we need to setup the guardian set + let body = wormhole.buildValidVaaBodySpecs(); + let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { + version: 1, + guardianSetId: 2, }); - - 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 header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); - // Override signatures - for (let i = 0; i < header.signatures.length; i++) { - header.signatures[i] = header.signatures[0]; - } - let vaa = wormhole.serializeVaaToBuffer(header, body); - let uncompressedPublicKey = []; - for (let guardian of guardianSet1Keys) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - const res = simnet.callReadOnlyFn( - contractName, - `parse-and-verify-vaa`, - [Cl.buffer(vaa)], - sender - ); - expect(res.result).toBeErr(Cl.uint(1102)); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender, + ); + expect(res.result).toBeErr(Cl.uint(1105)); + }); + + 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 header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { + version: 1, + guardianSetId: 1, }); - - 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 header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); - // Override signatures - header.signatures = header.signatures.slice(0, 12); - let vaa = wormhole.serializeVaaToBuffer(header, body); - let uncompressedPublicKey = []; - for (let guardian of guardianSet1Keys) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - const res = simnet.callReadOnlyFn( - contractName, - `parse-and-verify-vaa`, - [Cl.buffer(vaa)], - sender - ); - expect(res.result).toBeErr(Cl.uint(1102)); + // Override signatures + for (let i = 0; i < header.signatures.length; i++) { + header.signatures[i] = header.signatures[0]; + } + let vaa = wormhole.serializeVaaToBuffer(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender, + ); + expect(res.result).toBeErr(Cl.uint(1102)); + }); + + 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 header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { + version: 1, + guardianSetId: 1, }); - - it("should fail if quorum is not met", () => { - // Before performing this test, we need to setup the guardian set - let body = wormhole.buildValidVaaBodySpecs(); - let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { version: 1, guardianSetId: 1 }); - // Override signatures - let cutoff = 12; - for (let i = cutoff; i < header.signatures.length; i++) { - header.signatures[i] = header.signatures[0]; - } - let vaa = wormhole.serializeVaaToBuffer(header, body); - let uncompressedPublicKey = []; - for (let guardian of guardianSet1Keys) { - uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); - } - const res = simnet.callReadOnlyFn( - contractName, - `parse-and-verify-vaa`, - [Cl.buffer(vaa)], - sender - ); - expect(res.result).toBeErr(Cl.uint(1102)); + // Override signatures + header.signatures = header.signatures.slice(0, 12); + let vaa = wormhole.serializeVaaToBuffer(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender, + ); + expect(res.result).toBeErr(Cl.uint(1102)); + }); + + it("should fail if quorum is not met", () => { + // Before performing this test, we need to setup the guardian set + let body = wormhole.buildValidVaaBodySpecs(); + let header = wormhole.buildValidVaaHeader(guardianSet1Keys, body, { + version: 1, + guardianSetId: 1, }); -}) + // Override signatures + let cutoff = 12; + for (let i = cutoff; i < header.signatures.length; i++) { + header.signatures[i] = header.signatures[0]; + } + let vaa = wormhole.serializeVaaToBuffer(header, body); + let uncompressedPublicKey = []; + for (let guardian of guardianSet1Keys) { + uncompressedPublicKey.push(Cl.buffer(guardian.uncompressedPublicKey)); + } + const res = simnet.callReadOnlyFn( + contractName, + `parse-and-verify-vaa`, + [Cl.buffer(vaa)], + sender, + ); + expect(res.result).toBeErr(Cl.uint(1102)); + }); +}); describe("wormhole-core-v1::update-guardians-set mainnet guardian rotations", () => { - const accounts = simnet.getAccounts(); - const sender = accounts.get("wallet_1")!; - let block: ParsedTransactionResult[] | undefined = undefined; - - // Before starting the test suite, we have to setup the guardian set. - beforeEach(async () => { - block = wormhole.applyMainnetGuardianSetUpdates(sender, contractName) - }) - - it("should succeed handling the 3 guardians rotations", () => { - expect(block!).toHaveLength(3); - block!.forEach((b: ParsedTransactionResult) => { - expect(b.result).toHaveClarityType(ClarityType.ResponseOk); - }); + const accounts = simnet.getAccounts(); + const sender = accounts.get("wallet_1")!; + let block: ParsedTransactionResult[] | undefined = undefined; + + // Before starting the test suite, we have to setup the guardian set. + beforeEach(async () => { + block = wormhole.applyMainnetGuardianSetUpdates(sender, contractName); + }); + + it("should succeed handling the 3 guardians rotations", () => { + expect(block!).toHaveLength(3); + block!.forEach((b: ParsedTransactionResult) => { + expect(b.result).toHaveClarityType(ClarityType.ResponseOk); }); + }); }); From 01149e4029894fbb6d5eb426e13cc8b7c12f19db Mon Sep 17 00:00:00 2001 From: Ludo Galabru Date: Fri, 20 Oct 2023 10:32:57 -0400 Subject: [PATCH 18/18] doc: update README.md --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1aff7cc..1c2d898 100644 --- a/README.md +++ b/README.md @@ -26,20 +26,25 @@ Price feeds are available on multiple blockchains and can be used in off-chain a [Wormhole](https://wormhole.com) is a decentralized attestation engine that leverages its network of guardians to trustlessly bridge information between the chains it supports. Wormhole has a simple, elegant, and pragmatic design that has enabled it to be the first real solution to ship to market and has received wide recognition and support from its member chains. -## Setup and Test a Devnet Bridge - -The bridge is being operated through an off-chain service, `stacks-pyth-relayer`, and a set of contracts implementing the core functionalities specified by the Wormhole protocol. +## Setup and and run the tests -The contracts are developed in Clarity and use Clarinet for its test harnessing. -This guide assumes that a recent installation of Clarinet (available on brew and winget) is available locally. +The contracts are developed in Clarity and use [clarinet-sdk](https://www.npmjs.com/package/@hirosystems/clarinet-sdk) for its test harnessing. Git clone and compile **stacks-pyth-relayer** ```bash $ git clone https://github.com/hirosystems/stacks-pyth-bridge.git $ cd stacks-pyth-bridge +$ npm install +$ npm test ``` +## Setup and Test a Devnet Bridge + +This guide assumes that a recent installation of Clarinet (available on brew and winget) is available locally. + +The bridge can be operated through an off-chain service, `stacks-pyth-relayer`, and a set of contracts implementing the core functionalities specified by the Wormhole protocol. + Start a local Devnet using the command: ```bash $ clarinet integrate @@ -183,8 +188,6 @@ These events can be observed using [Chainhook](https://github.com/hirosystems/ch ## Todos: -- [ ] Resolve todos, mostly aiming at adding more checks in both wormhole and pyth contracts -- [ ] Address insufficient test coverage -- [ ] Resolve build warnings -- [ ] Improve documentation -- [ ] Add example +- [ ] Resolve remaining todo +- [ ] Document usage +- [ ] Document example/cbtc