diff --git a/.github/workflows/ci-near-contract.yml b/.github/workflows/ci-near-contract.yml index aedf1d63c6..04aa82703f 100644 --- a/.github/workflows/ci-near-contract.yml +++ b/.github/workflows/ci-near-contract.yml @@ -16,7 +16,8 @@ env: CARGO_TERM_COLOR: always jobs: - build: + test: + name: Workspace test runs-on: ubuntu-latest defaults: run: @@ -25,3 +26,14 @@ jobs: - uses: actions/checkout@v2 - name: Test run: ./workspace-test.sh + reproducible-build: + name: Reproducible build + runs-on: ubuntu-latest + defaults: + run: + working-directory: target_chains/near/receiver + steps: + - uses: actions/checkout@v2 + - run: sudo apt-get install -y libudev-dev + - run: cargo +stable install --locked cargo-near@0.13.3 + - run: cargo near build reproducible-wasm diff --git a/target_chains/near/README.md b/target_chains/near/README.md index 195200716f..622245a139 100644 --- a/target_chains/near/README.md +++ b/target_chains/near/README.md @@ -7,15 +7,40 @@ of how to manually submit a price update from the CLI. ## Deployment -Deploying the NEAR contract has three steps: - -1. Create a NEAR key with `near generate-key` -2. Fetch NEAR tokens from an available faucet, at last deploy around 100~ NEAR were needed. -3. See the example deploy script in `scripts/deploy.sh` to deploy the contract. You can find a codehash by: - - `sha256sum pyth.wasm` after building the contract. - - `list(bytes.fromhex(hash))` in Python to get a byte array. - - Replace the `codehash` field in deploy.sh for the initial codehash. - - Replace the sources with the keys expected by the network you're deploying on (testnet vs mainnet). +Deploying the NEAR contract has the following steps: + +1. Create an account for the new contract: + +``` +near create-account contract-url.near --use-account --public-key ed25519: --initial-balance "2.5" --network-id mainnet +``` + +2. Build the contract: + +``` +cd receiver +cargo near build reproducible-wasm +``` + +3. Deploy the contract code: + +``` +near deploy contract-url.near target/near/pyth_near.wasm --network-id mainnet --init-function new --init-args '{"wormhole":"contract.wormhole_crypto.near","initial_source":{"emitter":[225,1,250,237,172,88,81,227,43,155,35,181,249,65,26,140,43,172,74,174,62,212,221,123,129,29,209,167,46,164,170,113],"chain":26},"gov_source":{"emitter":[86,53,151,154,34,28,52,147,30,50,98,11,146,147,164,99,6,85,85,234,113,254,151,205,98,55,173,232,117,177,46,158],"chain":1},"update_fee":"1","stale_threshold":60}' +``` + +To check the contract: + +1. Update price feeds: + +``` +near call --network-id mainnet contract-url.near update_price_feeds '{ "data": "504e415501..." }' --use-account --gas 300000000000000 --deposit 0.01 +``` + +2. Query price feed: + +``` +near view --network-id mainnet contract-url.near get_price_unsafe '{ "price_identifier": "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" }' +``` ## Further Documentation diff --git a/target_chains/near/receiver/Cargo.lock b/target_chains/near/receiver/Cargo.lock index 86e9a11e03..907983c2f0 100644 --- a/target_chains/near/receiver/Cargo.lock +++ b/target_chains/near/receiver/Cargo.lock @@ -2929,6 +2929,7 @@ dependencies = [ "pyth-sdk 0.7.0", "pyth-wormhole-attester-sdk", "pythnet-sdk", + "schemars", "serde", "serde_json", "serde_wormhole 0.1.0 (git+https://github.com/wormhole-foundation/wormhole?tag=rust-sdk-2024-01-25)", @@ -2975,7 +2976,7 @@ dependencies = [ [[package]] name = "pythnet-sdk" -version = "2.3.0" +version = "2.3.1" dependencies = [ "bincode", "borsh 0.10.4", diff --git a/target_chains/near/receiver/Cargo.toml b/target_chains/near/receiver/Cargo.toml index 2ba57ca1b6..f90089a070 100644 --- a/target_chains/near/receiver/Cargo.toml +++ b/target_chains/near/receiver/Cargo.toml @@ -4,9 +4,10 @@ version = "0.1.0" authors = ["Pyth Data Association"] edition = "2021" description = "A Pyth Receiver for Near" +repository = "https://github.com/pyth-network/pyth-crosschain" [lib] -name = "pyth" +name = "pyth_near" crate-type = ["cdylib", "lib"] [features] @@ -22,6 +23,7 @@ num-derive = { version = "0.3.3" } pyth-wormhole-attester-sdk = { path = "../../../wormhole_attester/sdk/rust" } pyth-sdk = { version = "0.7.0" } pythnet-sdk = { path = "../../../pythnet/pythnet_sdk" } +schemars = { version = "0.8.21" } serde_wormhole = { git = "https://github.com/wormhole-foundation/wormhole", tag="rust-sdk-2024-01-25" } strum = { version = "0.24.1", features = ["derive"] } thiserror = { version = "1.0.38" } @@ -47,3 +49,9 @@ lto = "fat" debug = false panic = "abort" overflow-checks = true + +[package.metadata.near.reproducible_build] +image = "sourcescan/cargo-near:0.13.3-rust-1.84.1" +image_digest = "sha256:baa712c5d2b7522d38175e36330d336ad2c4ce32bfaaa41af94ce40407ecd803" +passed_env = [] +container_build_command = ["cargo", "near", "build", "non-reproducible-wasm", "--locked"] diff --git a/target_chains/near/receiver/build.rs b/target_chains/near/receiver/build.rs new file mode 100644 index 0000000000..59bb9561b8 --- /dev/null +++ b/target_chains/near/receiver/build.rs @@ -0,0 +1,10 @@ +fn main() { + // CARGO_NEAR_ABI_GENERATION env var is set by cargo-near when generating ABI. + // We need to expose it as a cfg option to allow conditional compilation + // of our JsonSchema impls. + println!("cargo::rerun-if-env-changed=CARGO_NEAR_ABI_GENERATION"); + println!("cargo::rustc-check-cfg=cfg(abi)"); + if std::env::var("CARGO_NEAR_ABI_GENERATION").as_deref() == Ok("true") { + println!("cargo::rustc-cfg=abi"); + } +} diff --git a/target_chains/near/receiver/src/state.rs b/target_chains/near/receiver/src/state.rs index f6c0c6cc6a..db8c6ac11b 100644 --- a/target_chains/near/receiver/src/state.rs +++ b/target_chains/near/receiver/src/state.rs @@ -6,6 +6,7 @@ use { }, pyth_wormhole_attester_sdk::PriceAttestation, pythnet_sdk::messages::PriceFeedMessage, + schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema}, wormhole_sdk::Chain as WormholeChain, }; @@ -68,6 +69,20 @@ impl near_sdk::serde::Serialize for PriceIdentifier { } } +impl JsonSchema for PriceIdentifier { + fn is_referenceable() -> bool { + false + } + + fn schema_name() -> String { + String::schema_name() + } + + fn json_schema(gen: &mut SchemaGenerator) -> Schema { + String::json_schema(gen) + } +} + /// A price with a degree of uncertainty, represented as a price +- a confidence interval. /// /// The confidence interval roughly corresponds to the standard error of a normal distribution. @@ -79,6 +94,10 @@ impl near_sdk::serde::Serialize for PriceIdentifier { #[derive(BorshDeserialize, BorshSerialize, Debug, Deserialize, Serialize, PartialEq, Eq)] #[borsh(crate = "near_sdk::borsh")] #[serde(crate = "near_sdk::serde")] +// I64 and U64 only implement JsonSchema when "abi" feature is enabled in near_sdk, +// but unconditionally enabling this feature doesn't work, so we have to make this impl +// conditional. +#[cfg_attr(abi, derive(JsonSchema))] pub struct Price { pub price: I64, /// Confidence interval around the price @@ -161,6 +180,7 @@ impl From<&PriceFeedMessage> for PriceFeed { PartialEq, PartialOrd, Serialize, + JsonSchema, )] #[borsh(crate = "near_sdk::borsh")] #[serde(crate = "near_sdk::serde")] @@ -197,6 +217,7 @@ impl From for u16 { PartialEq, PartialOrd, Serialize, + JsonSchema, )] #[borsh(crate = "near_sdk::borsh")] #[serde(crate = "near_sdk::serde")] diff --git a/target_chains/near/receiver/tests/workspaces.rs b/target_chains/near/receiver/tests/workspaces.rs index 68abee2856..ba26fb9333 100644 --- a/target_chains/near/receiver/tests/workspaces.rs +++ b/target_chains/near/receiver/tests/workspaces.rs @@ -1,7 +1,7 @@ use { near_sdk::json_types::{I64, U128, U64}, near_workspaces::types::{Gas, NearToken}, - pyth::{ + pyth_near::{ governance::{GovernanceAction, GovernanceInstruction, GovernanceModule}, state::{Chain, Price, PriceIdentifier, Source}, }, @@ -27,9 +27,9 @@ async fn initialize_chain() -> ( // Deploy Pyth let contract = worker - .dev_deploy(&std::fs::read("pyth.wasm").expect("Failed to find pyth.wasm")) + .dev_deploy(&std::fs::read("pyth_near.wasm").expect("Failed to find pyth_near.wasm")) .await - .expect("Failed to deploy pyth.wasm"); + .expect("Failed to deploy pyth_near.wasm"); // Deploy Wormhole Stub, this is a dummy contract that always verifies VAA's correctly so we // can test the ext_wormhole API. diff --git a/target_chains/near/receiver/workspace-test.sh b/target_chains/near/receiver/workspace-test.sh index 077f93fbc9..88ae1c1375 100755 --- a/target_chains/near/receiver/workspace-test.sh +++ b/target_chains/near/receiver/workspace-test.sh @@ -10,7 +10,7 @@ set -euo pipefail rustup target add wasm32-unknown-unknown cargo build --release --target wasm32-unknown-unknown -cp target/wasm32-unknown-unknown/release/pyth.wasm . +cp target/wasm32-unknown-unknown/release/pyth_near.wasm . ( cd ../wormhole-stub diff --git a/target_chains/near/scripts/deploy.sh b/target_chains/near/scripts/deploy.sh deleted file mode 100755 index f66405e8bb..0000000000 --- a/target_chains/near/scripts/deploy.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -# This is an example payload for deploying the NEAR receiver contract. Note that the codehash can be obtained -# by `sha256sum` on the compiled contract. The initial and governance sources are the PythNet emitters for -# governance payloads. -INIT_ARGS=$( - cat <<-EOF - { - "wormhole": "wormhole.wormhole.testnet", - "codehash": [113, 49, 20, 252, 226, 220, 48, 15, 139, 92, 255, 117, 94, 178, 130, 162, 252, 5, 252, 188, 87, 122, 50, 175, 109, 12, 26, 189, 9, 107, 214, 116], - "initial_source": { - "emitter": [225, 1, 250, 237, 172, 88, 81, 227, 43, 155, 35, 181, 249, 65, 26, 140, 43, 172, 74, 174, 62, 212, 221, 123, 129, 29, 209, 167, 46, 164, 170, 113], - "chain": 26 - }, - "gov_source": { - "emitter": [86, 53, 151, 154, 34, 28, 52, 147, 30, 50, 98, 11, 146, 147, 164, 99, 6, 85, 85, 234, 113, 254, 151, 205, 98, 55, 173, 232, 117, 177, 46, 158], - "chain": 1 - }, - "update_fee": "1", - "stale_threshold": 60 - } - EOF -) - -# Feed through jq to get compressed JSON to avoid CLI weirdness. -INIT_JSON=$(echo "$INIT_ARGS" | jq -c '.' -M) - -# Deploy.. -near deploy \ - --accountId "pyth.testnet" \ - --wasmFile pyth.wasm \ - --initFunction new \ - --initArgs "$INIT_JSON"