Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/ci-near-contract.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,23 @@ jobs:
name: pyth_near.wasm
path: target_chains/near/receiver/target/near/pyth_near.wasm
retention-days: 90
reproducible-build-wormhole:
name: Reproducible build (vendored wormhole)
runs-on: ubuntu-latest
defaults:
run:
working-directory: target_chains/near/wormhole
steps:
- uses: actions/checkout@v2
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache-workspaces: "target_chains/near/wormhole -> target"
- run: sudo apt-get update && sudo apt-get install -y libudev-dev
- run: cargo install --locked cargo-near@0.13.3
- run: cargo near build reproducible-wasm
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: pyth_wormhole_near.wasm
path: target_chains/near/wormhole/target/near/pyth_wormhole_near.wasm
retention-days: 90
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ exclude = [
# Near contract
"target_chains/near/example",
"target_chains/near/receiver",
"target_chains/near/wormhole",
"target_chains/near/wormhole-stub",
# Solana contracts
"target_chains/solana",
Expand Down
138 changes: 138 additions & 0 deletions pythnet/pythnet_sdk/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,141 @@ pub fn trim_vaa_signatures(vaa: Vaa<&RawMessage>, n: u8) -> Vaa<&RawMessage> {
vaa_copy.signatures.sort_by(|a, b| a.index.cmp(&b.index));
vaa_copy
}

/// Like [`create_vaa_from_payload`], but signs with an explicit guardian set rather than the 19
/// dummy mainnet guardians. The first `num_signatures` guardians sign (deterministically, sorted by
/// index), and `guardian_set_index` is stamped into the VAA header. Used to exercise the Pyth Pro
/// router set (5 keys, 3-of-5 quorum) against the vendored NEAR Wormhole core bridge.
pub fn create_vaa_from_payload_with_signers(
payload: &[u8],
emitter_address: Address,
emitter_chain: Chain,
sequence: u64,
guardians: &[SecretKey],
num_signatures: usize,
guardian_set_index: u32,
) -> Vaa<Box<RawMessage>> {
let body: Body<Box<RawMessage>> = Body {
emitter_chain,
emitter_address,
sequence,
payload: <Box<RawMessage>>::from(payload.to_vec()),
..Default::default()
};

let digest = libsecp256k1Message::parse_slice(&body.digest().unwrap().secp256k_hash).unwrap();

let mut signatures: Vec<wormhole_sdk::vaa::Signature> = guardians
.iter()
.take(num_signatures)
.enumerate()
.map(|(i, guardian)| {
let (signature, recovery_id) = libsecp256k1::sign(&digest, guardian);
let mut bytes = [0u8; 65];
bytes[..64].copy_from_slice(&signature.serialize());
bytes[64] = recovery_id.serialize();
wormhole_sdk::vaa::Signature {
index: i as u8,
signature: bytes,
}
})
.collect();
signatures.sort_by(|a, b| a.index.cmp(&b.index));

let header = Header {
version: 1,
guardian_set_index,
signatures,
};

(header, body).into()
}

/// Like [`create_accumulator_message_from_updates`], but signs the wrapping Wormhole VAA with an
/// explicit guardian set (see [`create_vaa_from_payload_with_signers`]).
#[allow(clippy::too_many_arguments)]
pub fn create_accumulator_message_from_updates_with_signers(
price_updates: Vec<MerklePriceUpdate>,
tree: MerkleTree<Keccak160>,
corrupt_wormhole_message: bool,
emitter_address: Address,
emitter_chain: Chain,
guardians: &[SecretKey],
num_signatures: usize,
guardian_set_index: u32,
) -> Vec<u8> {
let mut root_hash = [0u8; 20];
root_hash.copy_from_slice(&to_vec::<_, BigEndian>(&tree.root).unwrap()[..20]);
let wormhole_message = WormholeMessage::new(WormholePayload::Merkle(WormholeMerkleRoot {
slot: 0,
ring_size: 0,
root: root_hash,
}));

let mut vaa_payload = to_vec::<_, BigEndian>(&wormhole_message).unwrap();
if corrupt_wormhole_message {
vaa_payload[0] = 0;
}

let vaa = create_vaa_from_payload_with_signers(
&vaa_payload,
emitter_address,
emitter_chain,
DEFAULT_SEQUENCE,
guardians,
num_signatures,
guardian_set_index,
);

let accumulator_update_data = AccumulatorUpdateData::new(Proof::WormholeMerkle {
vaa: PrefixedVec::from(serde_wormhole::to_vec(&vaa).unwrap()),
updates: price_updates,
});

to_vec::<_, BigEndian>(&accumulator_update_data).unwrap()
}

/// Like [`create_accumulator_message`], but signs the wrapping Wormhole VAA with an explicit
/// guardian set (see [`create_vaa_from_payload_with_signers`]).
#[allow(clippy::too_many_arguments)]
pub fn create_accumulator_message_with_signers(
all_feeds: &[&Message],
updates: &[&Message],
corrupt_wormhole_message: bool,
data_source_override: Option<DataSource>,
guardians: &[SecretKey],
num_signatures: usize,
guardian_set_index: u32,
) -> Vec<u8> {
let all_feeds_bytes: Vec<_> = all_feeds
.iter()
.map(|f| to_vec::<_, BigEndian>(f).unwrap())
.collect();
let updates_bytes: Vec<_> = updates
.iter()
.map(|f| to_vec::<_, BigEndian>(f).unwrap())
.collect();

let all_feeds_bytes_refs: Vec<_> = all_feeds_bytes.iter().map(|f| f.as_ref()).collect();
let tree = MerkleTree::<Keccak160>::new(all_feeds_bytes_refs.as_slice()).unwrap();
let mut price_updates: Vec<MerklePriceUpdate> = vec![];
for update in updates_bytes {
let proof = tree.prove(&update).unwrap();
price_updates.push(MerklePriceUpdate {
message: PrefixedVec::from(update),
proof,
});
}

let data_source = data_source_override.unwrap_or(DEFAULT_DATA_SOURCE);
create_accumulator_message_from_updates_with_signers(
price_updates,
tree,
corrupt_wormhole_message,
data_source.address,
data_source.chain,
guardians,
num_signatures,
guardian_set_index,
)
}
24 changes: 24 additions & 0 deletions target_chains/near/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,30 @@ near call --network-id mainnet contract-url.near update_price_feeds '{ "data": "
near view --network-id mainnet contract-url.near get_price_unsafe '{ "price_identifier": "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43" }'
```

## Wormhole core bridge

The receiver verifies accumulator VAAs against a Pyth-owned Wormhole core bridge, vendored under
`wormhole/` (see `wormhole/VENDOR.md` for the upstream pin and the deltas). Its guardian set 0 is the
5 Pyth Pro routers (3-of-5 quorum); standard `UpgradeGuardianSet` VAAs rotate it going forward.

Deploy it before the receiver upgrade. Build:

```
cd wormhole
cargo near build reproducible-wasm
```

Then create its account and deploy, initializing guardian set 0 with the 5 router addresses
(20-byte secp256k1 addresses, hex):

```
near deploy wormhole-pyth.near target/near/pyth_wormhole_near.wasm --network-id mainnet \
--init-function new \
--init-args '{"initial_guardians":["<router_addr_0>","<router_addr_1>","<router_addr_2>","<router_addr_3>","<router_addr_4>"]}'
```

The receiver is then pointed at this contract via the `SetWormhole` governance action.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚩 README references SetWormhole governance action that does not yet exist

The README at target_chains/near/README.md:67 states "The receiver is then pointed at this contract via the SetWormhole governance action." However, the GovernanceAction enum in target_chains/near/receiver/src/governance.rs:75-82 has no SetWormhole variant. This appears to be documenting a future PR that will add the governance action to redirect the receiver to the vendored bridge. As-is, the README describes deployment steps that cannot be completed with the current code.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


## Further Documentation

You can find more in-depth documentation on the [Pyth Website][pyth website] for a more in-depth guide to
Expand Down
Loading
Loading