[wormhole-attester] Remove wasm in js sdk (#537)
* [wormhole-attester] Remove wasm in js sdk * Cleanup dependencies and unused codes * Cleanup rust * Add tests + minor improvements * Add ignored price status * Revert cargo lock
This commit is contained in:
parent
277599b458
commit
f9e0145352
18
Tiltfile
18
Tiltfile
|
@ -62,18 +62,6 @@ if not ci:
|
||||||
def k8s_yaml_with_ns(objects):
|
def k8s_yaml_with_ns(objects):
|
||||||
return k8s_yaml(namespace_inject(objects, namespace))
|
return k8s_yaml(namespace_inject(objects, namespace))
|
||||||
|
|
||||||
# wasm
|
|
||||||
|
|
||||||
local_resource(
|
|
||||||
name = "wasm-gen",
|
|
||||||
cmd = "tilt docker build -- -f tilt_devnet/docker_images/Dockerfile.wasm -o type=local,dest=. .",
|
|
||||||
env = {"DOCKER_BUILDKIT": "1"},
|
|
||||||
deps = "./wormhole_attester",
|
|
||||||
labels = ["wasm"],
|
|
||||||
allow_parallel=True,
|
|
||||||
trigger_mode = trigger_mode,
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_node_yaml():
|
def build_node_yaml():
|
||||||
node_yaml = read_yaml_stream("tilt_devnet/k8s/node.yaml")
|
node_yaml = read_yaml_stream("tilt_devnet/k8s/node.yaml")
|
||||||
|
|
||||||
|
@ -220,7 +208,7 @@ docker_build(
|
||||||
k8s_yaml_with_ns("tilt_devnet/k8s/p2w-terra-relay.yaml")
|
k8s_yaml_with_ns("tilt_devnet/k8s/p2w-terra-relay.yaml")
|
||||||
k8s_resource(
|
k8s_resource(
|
||||||
"p2w-terra-relay",
|
"p2w-terra-relay",
|
||||||
resource_deps = ["pyth", "p2w-attest", "spy", "terra-terrad", "wasm-gen"],
|
resource_deps = ["pyth", "p2w-attest", "spy", "terra-terrad"],
|
||||||
port_forwards = [
|
port_forwards = [
|
||||||
port_forward(4200, name = "Rest API (Status + Query) [:4200]", host = webHost),
|
port_forward(4200, name = "Rest API (Status + Query) [:4200]", host = webHost),
|
||||||
port_forward(8081, name = "Prometheus [:8081]", host = webHost)],
|
port_forward(8081, name = "Prometheus [:8081]", host = webHost)],
|
||||||
|
@ -230,7 +218,7 @@ k8s_resource(
|
||||||
k8s_yaml_with_ns("tilt_devnet/k8s/p2w-evm-relay.yaml")
|
k8s_yaml_with_ns("tilt_devnet/k8s/p2w-evm-relay.yaml")
|
||||||
k8s_resource(
|
k8s_resource(
|
||||||
"p2w-evm-relay",
|
"p2w-evm-relay",
|
||||||
resource_deps = ["pyth", "p2w-attest", "spy", "eth-devnet", "wasm-gen"],
|
resource_deps = ["pyth", "p2w-attest", "spy", "eth-devnet"],
|
||||||
port_forwards = [
|
port_forwards = [
|
||||||
port_forward(4201, container_port = 4200, name = "Rest API (Status + Query) [:4201]", host = webHost),
|
port_forward(4201, container_port = 4200, name = "Rest API (Status + Query) [:4201]", host = webHost),
|
||||||
port_forward(8082, container_port = 8081, name = "Prometheus [:8082]", host = webHost)],
|
port_forward(8082, container_port = 8081, name = "Prometheus [:8082]", host = webHost)],
|
||||||
|
@ -246,7 +234,7 @@ docker_build(
|
||||||
k8s_yaml_with_ns("tilt_devnet/k8s/pyth-price-server.yaml")
|
k8s_yaml_with_ns("tilt_devnet/k8s/pyth-price-server.yaml")
|
||||||
k8s_resource(
|
k8s_resource(
|
||||||
"pyth-price-server",
|
"pyth-price-server",
|
||||||
resource_deps = ["pyth", "p2w-attest", "spy", "eth-devnet", "wasm-gen"],
|
resource_deps = ["pyth", "p2w-attest", "spy", "eth-devnet"],
|
||||||
port_forwards = [
|
port_forwards = [
|
||||||
port_forward(4202, container_port = 4200, name = "Rest API (Status + Query) [:4202]", host = webHost),
|
port_forward(4202, container_port = 4200, name = "Rest API (Status + Query) [:4202]", host = webHost),
|
||||||
port_forward(8083, container_port = 8081, name = "Prometheus [:8083]", host = webHost)],
|
port_forward(8083, container_port = 8081, name = "Prometheus [:8083]", host = webHost)],
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
# syntax=docker.io/docker/dockerfile:1.3@sha256:42399d4635eddd7a9b8a24be879d2f9a930d0ed040a61324cfdf59ef1357b3b2
|
|
||||||
FROM docker.io/library/rust:1.49@sha256:a50165ea96983c21832578afb1c8c028674c965bc1ed43b607871b1f362e06a5 AS build
|
|
||||||
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y libssl-dev libudev-dev pkg-config zlib1g-dev llvm clang && \
|
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# We default an older nightly since current rust-toolchain makes the
|
|
||||||
# wasm-pack build unhappy, we introduce it later for our code
|
|
||||||
RUN rustup component add rustfmt && \
|
|
||||||
rustup default nightly-2022-01-02
|
|
||||||
|
|
||||||
WORKDIR /usr/src
|
|
||||||
RUN cargo install wasm-pack --vers 0.9.1
|
|
||||||
ENV RUST_LOG="solana_runtime::system_instruction_processor=trace,solana_runtime::message_processor=trace,solana_bpf_loader=debug,solana_rbpf=debug"
|
|
||||||
ENV EMITTER_ADDRESS="11111111111111111111111111111115"
|
|
||||||
ENV BRIDGE_ADDRESS="Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
|
|
||||||
|
|
||||||
COPY wormhole_attester wormhole_attester
|
|
||||||
COPY governance/remote_executor governance/remote_executor
|
|
||||||
|
|
||||||
# wasm-bindgen 0.2.74 generates JavaScript bindings for SystemInstruction exported from solana-program 1.9.4.
|
|
||||||
# The generated JavaScript references a non-existent function (wasm.__wbg_systeminstruction_free) that leads
|
|
||||||
# to an attempted import error when importing the wasm packed for bundler. SystemInstruction isn't used in the sdk,
|
|
||||||
# so we remove the non-existent function reference as a workaround.
|
|
||||||
ARG SED_REMOVE_INVALID_REFERENCE="/^\s*wasm.__wbg_systeminstruction_free(ptr);$/d"
|
|
||||||
|
|
||||||
# TODO: it appears that wasm-pack ignores our lockfiles even with --locked
|
|
||||||
|
|
||||||
# Run unit tests
|
|
||||||
WORKDIR /usr/src/wormhole_attester/sdk/rust
|
|
||||||
RUN cargo test --locked && \
|
|
||||||
/usr/local/cargo/bin/wasm-pack build --target bundler -d bundler -- --features wasm --locked && \
|
|
||||||
/usr/local/cargo/bin/wasm-pack build --target nodejs -d nodejs -- --features wasm --locked
|
|
||||||
|
|
||||||
FROM scratch AS export
|
|
||||||
|
|
||||||
COPY --from=build /usr/src/wormhole_attester/sdk/rust/bundler wormhole_attester/sdk/js/src/wasm/bundler
|
|
||||||
COPY --from=build /usr/src/wormhole_attester/sdk/rust/nodejs wormhole_attester/sdk/js/src/wasm/nodejs
|
|
|
@ -2751,7 +2751,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
"solitaire",
|
"solitaire",
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -5177,8 +5176,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
|
checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
FROM node:16-alpine@sha256:004dbac84fed48e20f9888a23e32fa7cf83c2995e174a78d41d9a9dd1e051a20
|
|
||||||
|
|
||||||
# Build ETH
|
|
||||||
WORKDIR /usr/src/ethereum
|
|
||||||
COPY ethereum/package.json ethereum/package-lock.json ./
|
|
||||||
RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
|
|
||||||
npm ci
|
|
||||||
COPY ethereum .
|
|
||||||
|
|
||||||
# Build Wormhole SDK
|
|
||||||
WORKDIR /usr/src/sdk/js
|
|
||||||
COPY sdk/js/package.json sdk/js/package-lock.json ./
|
|
||||||
RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
|
|
||||||
npm ci
|
|
||||||
COPY sdk/js .
|
|
||||||
|
|
||||||
# Build wormhole sdk attester in dir preserving directory structure
|
|
||||||
WORKDIR /usr/src/wormhole_attester/sdk/js/
|
|
||||||
COPY wormhole_attester/sdk/js/package.json wormhole_attester/sdk/js/package-lock.json ./
|
|
||||||
RUN --mount=type=cache,uid=1000,gid=1000,target=/home/node/.npm \
|
|
||||||
npm ci
|
|
||||||
COPY wormhole_attester/sdk .
|
|
||||||
RUN npm run build-test
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
|
||||||
|
module.exports = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
testEnvironment: "node",
|
||||||
|
};
|
File diff suppressed because it is too large
Load Diff
|
@ -1,19 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "@pythnetwork/wormhole-attester-sdk",
|
"name": "@pythnetwork/wormhole-attester-sdk",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "TypeScript library for interacting with Pyth2Wormhole",
|
"description": "Pyth Wormhole Attester SDk",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
"lib/**/*"
|
"lib/**/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npm run build-lib",
|
"build": "tsc",
|
||||||
"build-lib": "npm run copy-artifacts && tsc",
|
|
||||||
"build-watch": "npm run copy-artifacts && tsc --watch",
|
|
||||||
"format": "prettier --write \"src/**/*.ts\"",
|
"format": "prettier --write \"src/**/*.ts\"",
|
||||||
"copy-artifacts": "node scripts/copyWasm.cjs",
|
|
||||||
"lint": "tslint -p tsconfig.json",
|
"lint": "tslint -p tsconfig.json",
|
||||||
|
"test": "jest src/",
|
||||||
"postversion": "git push && git push --tags",
|
"postversion": "git push && git push --tags",
|
||||||
"preversion": "npm run lint",
|
"preversion": "npm run lint",
|
||||||
"version": "npm run format && git add -A src"
|
"version": "npm run format && git add -A src"
|
||||||
|
@ -22,26 +20,22 @@
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
|
"url": "git+https://github.com/pyth-network/pyth-crosschain.git"
|
||||||
},
|
},
|
||||||
"author": "https://certus.one",
|
"author": "Pyth Data Association",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@openzeppelin/contracts": "^4.2.0",
|
"@types/jest": "^29.4.0",
|
||||||
"@typechain/ethers-v5": "^7.1.2",
|
|
||||||
"@types/long": "^4.0.1",
|
"@types/long": "^4.0.1",
|
||||||
"@types/node": "^16.6.1",
|
"@types/node": "^16.6.1",
|
||||||
"copy-dir": "^1.3.0",
|
"copy-dir": "^1.3.0",
|
||||||
"find": "^0.3.0",
|
"find": "^0.3.0",
|
||||||
|
"jest": "^29.4.1",
|
||||||
"prettier": "^2.3.2",
|
"prettier": "^2.3.2",
|
||||||
|
"ts-jest": "^29.0.5",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
|
||||||
"@solana/web3.js": "^1.24.0"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@certusone/wormhole-sdk": "0.2.1",
|
|
||||||
"@improbable-eng/grpc-web-node-http-transport": "^0.14.1",
|
|
||||||
"@pythnetwork/pyth-sdk-js": "^1.1.0"
|
"@pythnetwork/pyth-sdk-js": "^1.1.0"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
const find = require("find");
|
|
||||||
const fs = require("fs");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const SOURCE_ROOT = "src";
|
|
||||||
const TARGET_ROOT = "lib";
|
|
||||||
|
|
||||||
find.eachfile(/\.wasm(\..*)?/, SOURCE_ROOT, (fname) => {
|
|
||||||
fname_copy = fname.replace(SOURCE_ROOT, TARGET_ROOT);
|
|
||||||
|
|
||||||
console.log("copyWasm:", fname, "to", fname_copy);
|
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(fname_copy), { recursive: true });
|
|
||||||
|
|
||||||
fs.copyFileSync(fname, fname_copy);
|
|
||||||
});
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
import {
|
||||||
|
parseBatchPriceAttestation,
|
||||||
|
Price,
|
||||||
|
PriceFeed,
|
||||||
|
PriceAttestation,
|
||||||
|
PriceAttestationStatus,
|
||||||
|
priceAttestationToPriceFeed,
|
||||||
|
} from "../index";
|
||||||
|
|
||||||
|
describe("Deserializing Batch Price Attestation works", () => {
|
||||||
|
test("when batch has 3 price feeds", () => {
|
||||||
|
// Generated from the rust sdk test_batch_serde
|
||||||
|
const fixture =
|
||||||
|
"50325748000300010001020003009D01010101010101010101010101010101010101010101010101010" +
|
||||||
|
"10101010101FEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFE0000002B" +
|
||||||
|
"AD2FEED70000000000000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D00" +
|
||||||
|
"000DEADBEEFFADE00000000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEFDEAD" +
|
||||||
|
"BEEFFADEDEAF0202020202020202020202020202020202020202020202020202020202020202FDFDFDF" +
|
||||||
|
"DFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFDFD0000002BAD2FEED70000000000" +
|
||||||
|
"000065FFFFFFFDFFFFFFFFFFFFFFD6000000000000002A010001E14C0004E6D00000DEADBEEFFADE000" +
|
||||||
|
"00000DADEBEEF00000000DEADBABE0000DEADFACEBEEF000000BADBADBEEFDEADBEEFFADEDEAF030303" +
|
||||||
|
"0303030303030303030303030303030303030303030303030303030303FCFCFCFCFCFCFCFCFCFCFCFCF" +
|
||||||
|
"CFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFCFC0000002BAD2FEED70000000000000065FFFFFFFDFFFF" +
|
||||||
|
"FFFFFFFFFFD6000000000000002A010001E14C0004E6D00000DEADBEEFFADE00000000DADEBEEF00000" +
|
||||||
|
"000DEADBABE0000DEADFACEBEEF000000BADBADBEEFDEADBEEFFADEDEAF";
|
||||||
|
|
||||||
|
const data = Buffer.from(fixture, "hex");
|
||||||
|
const batchPriceAttestation = parseBatchPriceAttestation(data);
|
||||||
|
|
||||||
|
expect(batchPriceAttestation.priceAttestations.length).toBe(3);
|
||||||
|
|
||||||
|
// values are from the rust sdk mock_attestation
|
||||||
|
batchPriceAttestation.priceAttestations.forEach((pa, idx) => {
|
||||||
|
expect(pa).toEqual<PriceAttestation>({
|
||||||
|
productId: Buffer.from(Array(32).fill(idx + 1)).toString("hex"),
|
||||||
|
priceId: Buffer.from(Array(32).fill(255 - idx - 1)).toString("hex"),
|
||||||
|
price: (0x2bad2feed7).toString(),
|
||||||
|
conf: "101",
|
||||||
|
emaPrice: "-42",
|
||||||
|
emaConf: "42",
|
||||||
|
expo: -3,
|
||||||
|
status: PriceAttestationStatus.Trading,
|
||||||
|
numPublishers: 123212,
|
||||||
|
maxNumPublishers: 321232,
|
||||||
|
attestationTime: 0xdeadbeeffade,
|
||||||
|
publishTime: 0xdadebeef,
|
||||||
|
prevPublishTime: 0xdeadbabe,
|
||||||
|
prevPrice: (0xdeadfacebeef).toString(),
|
||||||
|
prevConf: (0xbadbadbeef).toString(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Price Attestation to Price Feed works", () => {
|
||||||
|
test("when status is trading", () => {
|
||||||
|
const priceAttestation = {
|
||||||
|
productId: "012345",
|
||||||
|
priceId: "abcde",
|
||||||
|
price: "100",
|
||||||
|
conf: "5",
|
||||||
|
emaPrice: "103",
|
||||||
|
emaConf: "3",
|
||||||
|
expo: -3,
|
||||||
|
status: PriceAttestationStatus.Trading,
|
||||||
|
numPublishers: 1,
|
||||||
|
maxNumPublishers: 2,
|
||||||
|
attestationTime: 1000,
|
||||||
|
publishTime: 1000,
|
||||||
|
prevPublishTime: 998,
|
||||||
|
prevPrice: "101",
|
||||||
|
prevConf: "6",
|
||||||
|
};
|
||||||
|
|
||||||
|
const priceFeed = priceAttestationToPriceFeed(priceAttestation);
|
||||||
|
expect(priceFeed).toEqual(
|
||||||
|
new PriceFeed({
|
||||||
|
id: "abcde",
|
||||||
|
price: new Price({
|
||||||
|
price: "100",
|
||||||
|
conf: "5",
|
||||||
|
expo: -3,
|
||||||
|
publishTime: 1000,
|
||||||
|
}),
|
||||||
|
emaPrice: new Price({
|
||||||
|
price: "103",
|
||||||
|
conf: "3",
|
||||||
|
expo: -3,
|
||||||
|
publishTime: 1000,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("when status is not trading", () => {
|
||||||
|
const priceAttestation = {
|
||||||
|
productId: "012345",
|
||||||
|
priceId: "abcde",
|
||||||
|
price: "100",
|
||||||
|
conf: "5",
|
||||||
|
emaPrice: "103",
|
||||||
|
emaConf: "3",
|
||||||
|
expo: -3,
|
||||||
|
status: PriceAttestationStatus.Unknown,
|
||||||
|
numPublishers: 1,
|
||||||
|
maxNumPublishers: 2,
|
||||||
|
attestationTime: 1000,
|
||||||
|
publishTime: 1000,
|
||||||
|
prevPublishTime: 998,
|
||||||
|
prevPrice: "101",
|
||||||
|
prevConf: "6",
|
||||||
|
};
|
||||||
|
|
||||||
|
const priceFeed = priceAttestationToPriceFeed(priceAttestation);
|
||||||
|
expect(priceFeed).toEqual(
|
||||||
|
new PriceFeed({
|
||||||
|
id: "abcde",
|
||||||
|
price: new Price({
|
||||||
|
price: "101",
|
||||||
|
conf: "6",
|
||||||
|
expo: -3,
|
||||||
|
publishTime: 998,
|
||||||
|
}),
|
||||||
|
emaPrice: new Price({
|
||||||
|
price: "103",
|
||||||
|
conf: "3",
|
||||||
|
expo: -3,
|
||||||
|
publishTime: 998,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,19 +1,12 @@
|
||||||
import { getSignedVAA, CHAIN_ID_SOLANA } from "@certusone/wormhole-sdk";
|
|
||||||
import { zeroPad } from "ethers/lib/utils";
|
|
||||||
import { PublicKey } from "@solana/web3.js";
|
|
||||||
import { PriceFeed, Price, UnixTimestamp } from "@pythnetwork/pyth-sdk-js";
|
import { PriceFeed, Price, UnixTimestamp } from "@pythnetwork/pyth-sdk-js";
|
||||||
|
export { PriceFeed, Price, UnixTimestamp } from "@pythnetwork/pyth-sdk-js";
|
||||||
|
|
||||||
let _P2W_WASM: any;
|
export enum PriceAttestationStatus {
|
||||||
|
Unknown = 0,
|
||||||
async function importWasm() {
|
Trading = 1,
|
||||||
if (!_P2W_WASM) {
|
Halted = 2,
|
||||||
if (typeof window === "undefined") {
|
Auction = 3,
|
||||||
_P2W_WASM = await import("./wasm/nodejs/pyth_wormhole_attester_sdk");
|
IGNORED = 4,
|
||||||
} else {
|
|
||||||
_P2W_WASM = await import("./wasm/bundler/pyth_wormhole_attester_sdk");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return _P2W_WASM;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PriceAttestation = {
|
export type PriceAttestation = {
|
||||||
|
@ -24,7 +17,7 @@ export type PriceAttestation = {
|
||||||
expo: number;
|
expo: number;
|
||||||
emaPrice: string;
|
emaPrice: string;
|
||||||
emaConf: string;
|
emaConf: string;
|
||||||
status: number;
|
status: PriceAttestationStatus;
|
||||||
numPublishers: number;
|
numPublishers: number;
|
||||||
maxNumPublishers: number;
|
maxNumPublishers: number;
|
||||||
attestationTime: UnixTimestamp;
|
attestationTime: UnixTimestamp;
|
||||||
|
@ -38,13 +31,146 @@ export type BatchPriceAttestation = {
|
||||||
priceAttestations: PriceAttestation[];
|
priceAttestations: PriceAttestation[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function parseBatchPriceAttestation(
|
/// Precedes every message implementing the wormhole attester serialization format
|
||||||
arr: Buffer
|
const P2W_FORMAT_MAGIC: string = "P2WH";
|
||||||
): Promise<BatchPriceAttestation> {
|
const P2W_FORMAT_VER_MAJOR = 3;
|
||||||
const wasm = await importWasm();
|
const P2W_FORMAT_VER_MINOR = 0;
|
||||||
const rawVal = await wasm.parse_batch_attestation(arr);
|
const P2W_FORMAT_PAYLOAD_ID = 2;
|
||||||
|
|
||||||
return rawVal;
|
export function parsePriceAttestation(bytes: Buffer): PriceAttestation {
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
const productId = bytes.slice(offset, offset + 32).toString("hex");
|
||||||
|
offset += 32;
|
||||||
|
|
||||||
|
const priceId = bytes.slice(offset, offset + 32).toString("hex");
|
||||||
|
offset += 32;
|
||||||
|
|
||||||
|
const price = bytes.readBigInt64BE(offset).toString();
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const conf = bytes.readBigUint64BE(offset).toString();
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const expo = bytes.readInt32BE(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
const emaPrice = bytes.readBigInt64BE(offset).toString();
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const emaConf = bytes.readBigUint64BE(offset).toString();
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const status = bytes.readUint8(offset) as PriceAttestationStatus;
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
const numPublishers = bytes.readUint32BE(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
const maxNumPublishers = bytes.readUint32BE(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
const attestationTime = Number(bytes.readBigInt64BE(offset));
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const publishTime = Number(bytes.readBigInt64BE(offset));
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const prevPublishTime = Number(bytes.readBigInt64BE(offset));
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const prevPrice = bytes.readBigInt64BE(offset).toString();
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
const prevConf = bytes.readBigUint64BE(offset).toString();
|
||||||
|
offset += 8;
|
||||||
|
|
||||||
|
return {
|
||||||
|
productId,
|
||||||
|
priceId,
|
||||||
|
price,
|
||||||
|
conf,
|
||||||
|
expo,
|
||||||
|
emaPrice,
|
||||||
|
emaConf,
|
||||||
|
status,
|
||||||
|
numPublishers,
|
||||||
|
maxNumPublishers,
|
||||||
|
attestationTime,
|
||||||
|
publishTime,
|
||||||
|
prevPublishTime,
|
||||||
|
prevPrice,
|
||||||
|
prevConf,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the sdk/rust as the reference implementation and documentation.
|
||||||
|
export function parseBatchPriceAttestation(
|
||||||
|
bytes: Buffer
|
||||||
|
): BatchPriceAttestation {
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
const magic = bytes.slice(offset, offset + 4).toString("utf8");
|
||||||
|
offset += 4;
|
||||||
|
if (magic !== P2W_FORMAT_MAGIC) {
|
||||||
|
throw new Error(`Invalid magic: ${magic}, expected: ${P2W_FORMAT_MAGIC}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionMajor = bytes.readUInt16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
if (versionMajor !== P2W_FORMAT_VER_MAJOR) {
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported major version: ${versionMajor}, expected: ${P2W_FORMAT_VER_MAJOR}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionMinor = bytes.readUInt16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
if (versionMinor < P2W_FORMAT_VER_MINOR) {
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported minor version: ${versionMinor}, expected: ${P2W_FORMAT_VER_MINOR}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header size is added for future-compatibility
|
||||||
|
const headerSize = bytes.readUint16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
let headerOffset = 0;
|
||||||
|
|
||||||
|
const payloadId = bytes.readUint8(offset + headerOffset);
|
||||||
|
headerOffset += 1;
|
||||||
|
|
||||||
|
if (payloadId !== P2W_FORMAT_PAYLOAD_ID) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid payloadId: ${payloadId}, expected: ${P2W_FORMAT_PAYLOAD_ID}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += headerSize;
|
||||||
|
|
||||||
|
const batchLen = bytes.readUInt16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
const attestationSize = bytes.readUint16BE(offset);
|
||||||
|
offset += 2;
|
||||||
|
|
||||||
|
let priceAttestations: PriceAttestation[] = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < batchLen; i += 1) {
|
||||||
|
priceAttestations.push(
|
||||||
|
parsePriceAttestation(bytes.subarray(offset, offset + attestationSize))
|
||||||
|
);
|
||||||
|
offset += attestationSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset !== bytes.length) {
|
||||||
|
throw new Error(`Invalid length: ${bytes.length}, expected: ${offset}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
priceAttestations,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a hash of all priceIds within the batch, it can be used to identify whether there is a
|
// Returns a hash of all priceIds within the batch, it can be used to identify whether there is a
|
||||||
|
@ -75,27 +201,6 @@ export function getBatchSummary(batch: BatchPriceAttestation): string {
|
||||||
return JSON.stringify(abstractRepresentation);
|
return JSON.stringify(abstractRepresentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getSignedAttestation(
|
|
||||||
host: string,
|
|
||||||
p2wAddr: string,
|
|
||||||
sequence: number,
|
|
||||||
extraGrpcOpts = {}
|
|
||||||
): Promise<any> {
|
|
||||||
const [emitter, _] = await PublicKey.findProgramAddress(
|
|
||||||
[Buffer.from("p2w-emitter")],
|
|
||||||
new PublicKey(p2wAddr)
|
|
||||||
);
|
|
||||||
|
|
||||||
const emitterHex = sol_addr2buf(emitter).toString("hex");
|
|
||||||
return await getSignedVAA(
|
|
||||||
host,
|
|
||||||
CHAIN_ID_SOLANA,
|
|
||||||
emitterHex,
|
|
||||||
"" + sequence,
|
|
||||||
extraGrpcOpts
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function priceAttestationToPriceFeed(
|
export function priceAttestationToPriceFeed(
|
||||||
priceAttestation: PriceAttestation
|
priceAttestation: PriceAttestation
|
||||||
): PriceFeed {
|
): PriceFeed {
|
||||||
|
@ -108,7 +213,7 @@ export function priceAttestationToPriceFeed(
|
||||||
|
|
||||||
let price: Price;
|
let price: Price;
|
||||||
|
|
||||||
if (priceAttestation.status === 1) {
|
if (priceAttestation.status === PriceAttestationStatus.Trading) {
|
||||||
// 1 means trading
|
// 1 means trading
|
||||||
price = new Price({
|
price = new Price({
|
||||||
conf: priceAttestation.conf,
|
conf: priceAttestation.conf,
|
||||||
|
@ -135,7 +240,3 @@ export function priceAttestationToPriceFeed(
|
||||||
price,
|
price,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sol_addr2buf(addr: PublicKey): Buffer {
|
|
||||||
return Buffer.from(zeroPad(addr.toBytes(), 32));
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -11,14 +11,12 @@ crate-type = ["cdylib", "rlib"]
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
solana = ["solitaire", "solana-program", "pyth-sdk-solana"]
|
solana = ["solitaire", "solana-program", "pyth-sdk-solana"]
|
||||||
wasm = ["wasm-bindgen", "solana"]
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||||
pyth-sdk = {version = "0.5.0"}
|
pyth-sdk = {version = "0.5.0"}
|
||||||
pyth-sdk-solana = { version = "0.5.0", optional = true }
|
pyth-sdk-solana = { version = "0.5.0", optional = true }
|
||||||
wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"], optional = true}
|
|
||||||
solitaire = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.8.9", optional = true}
|
solitaire = { git = "https://github.com/wormhole-foundation/wormhole", tag = "v2.8.9", optional = true}
|
||||||
solana-program = { version = "=1.10.31", optional = true }
|
solana-program = { version = "=1.10.31", optional = true }
|
||||||
|
|
||||||
|
|
|
@ -34,14 +34,6 @@ use {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
||||||
pub mod wasm;
|
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
|
||||||
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
|
|
||||||
use wasm_bindgen::prelude::*;
|
|
||||||
|
|
||||||
pub type ErrBox = Box<dyn std::error::Error>;
|
pub type ErrBox = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
/// Precedes every message implementing the p2w serialization format
|
/// Precedes every message implementing the p2w serialization format
|
||||||
|
@ -527,7 +519,7 @@ mod tests {
|
||||||
status: PriceStatus::Trading,
|
status: PriceStatus::Trading,
|
||||||
num_publishers: 123212u32,
|
num_publishers: 123212u32,
|
||||||
max_num_publishers: 321232u32,
|
max_num_publishers: 321232u32,
|
||||||
attestation_time: (0xdeadbeeffadedeedu64) as i64,
|
attestation_time: (0xdeadbeeffadeu64) as i64,
|
||||||
publish_time: 0xdadebeefi64,
|
publish_time: 0xdadebeefi64,
|
||||||
prev_publish_time: 0xdeadbabei64,
|
prev_publish_time: 0xdeadbabei64,
|
||||||
prev_price: 0xdeadfacebeefi64,
|
prev_price: 0xdeadfacebeefi64,
|
||||||
|
@ -567,7 +559,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_batch_serde() -> Result<(), ErrBox> {
|
fn test_batch_serde() -> Result<(), ErrBox> {
|
||||||
let attestations: Vec<_> = (1..=10)
|
let attestations: Vec<_> = (1..=3)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
mock_attestation(
|
mock_attestation(
|
||||||
Some([(i % 256) as u8; 32]),
|
Some([(i % 256) as u8; 32]),
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
BatchPriceAttestation,
|
|
||||||
P2WEmitter,
|
|
||||||
PriceAttestation,
|
|
||||||
},
|
|
||||||
solana_program::pubkey::Pubkey,
|
|
||||||
solitaire::Seeded,
|
|
||||||
std::str::FromStr,
|
|
||||||
wasm_bindgen::prelude::*,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn get_emitter_address(program_id: String) -> Vec<u8> {
|
|
||||||
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
|
|
||||||
let emitter = P2WEmitter::key(None, &program_id);
|
|
||||||
|
|
||||||
emitter.to_bytes().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn parse_attestation(bytes: Vec<u8>) -> JsValue {
|
|
||||||
let a = PriceAttestation::deserialize(bytes.as_slice()).unwrap();
|
|
||||||
|
|
||||||
JsValue::from_serde(&a).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[wasm_bindgen]
|
|
||||||
pub fn parse_batch_attestation(bytes: Vec<u8>) -> JsValue {
|
|
||||||
let a = BatchPriceAttestation::deserialize(bytes.as_slice()).unwrap();
|
|
||||||
|
|
||||||
JsValue::from_serde(&a).unwrap()
|
|
||||||
}
|
|
Loading…
Reference in New Issue