Skip to content

Commit ba133c7

Browse files
committed
test(pyth): use upgraded hermes envs in local net
Update the local net and upgrade test runners to pass the HC_* configuration expected by the upgraded Hermes client. The init path still writes the generated contract address and wallet secret to hermes.env, and compose still loads that file as before. The Pyth contract tests now assert only the upgraded pyth-vaa and pyth-pro artifacts for this path, and the integration docs list the matching Hermes variables. Signed-off-by: Joseph Chalabi <chalabi.joseph@gmail.com>
1 parent 457cdc5 commit ba133c7

7 files changed

Lines changed: 88 additions & 234 deletions

File tree

_docs/pyth-integration.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,21 @@ npm run cli:daemon
289289

290290
### Configuration
291291

292-
| Variable | Required | Default | Description |
293-
|----------------------|----------|-------------------------------|-----------------------------|
294-
| `RPC_ENDPOINT` | Yes || Akash RPC endpoint |
295-
| `CONTRACT_ADDRESS` | Yes || Pyth contract address |
296-
| `MNEMONIC` | Yes || Wallet mnemonic for signing |
297-
| `HERMES_ENDPOINT` | No | `https://hermes.pyth.network` | Pyth Hermes API URL |
298-
| `UPDATE_INTERVAL_MS` | No | `300000` | Update interval (5 min) |
299-
| `GAS_PRICE` | No | `0.025uakt` | Gas price for transactions |
300-
| `DENOM` | No | `uakt` | Token denomination |
292+
The upgraded Hermes client reads Akash-specific settings from `HC_*` environment variables. Local Akash compose files accept `PYTH_HERMES_API_KEY` and map it to `HC_HERMES_API_KEY`.
293+
294+
| Variable | Required | Default | Description |
295+
|--------------------------------|----------|--------------------------------------|--------------------------------------------------|
296+
| `HC_RPC_ENDPOINT` | Yes | `https://rpc.akashnet.net:443` | Akash RPC endpoint |
297+
| `HC_CONTRACT_ADDRESS` | Yes || `pyth-pro` contract address |
298+
| `HC_WALLET_SECRET` | Yes || Signing key, formatted as `mnemonic:<words>` or `privateKey:<hex>` |
299+
| `HC_HERMES_ENDPOINT` | Yes || Pyth Hermes API base URL. Set to `https://pyth.dourolabs.app/hermes` for upgraded PNAU updates |
300+
| `HC_HERMES_API_KEY` | Yes || Pyth Hermes API key for the upgraded endpoint |
301+
| `HC_PRICE_FETCHING_METHOD` | No | `polling` | Price fetch mode, `polling` or `sse` |
302+
| `HC_UPDATE_INTERVAL_MS` | No | `5000` | Polling interval in milliseconds |
303+
| `HC_PRICE_DEVIATION_TOLERANCE` | No | `0` | Absolute or percentage threshold for skipping small price changes |
304+
| `HC_GAS_PRICE` | No | `0.025uakt` | Gas price for transactions |
305+
| `HC_DENOM` | No | `uakt` | Fee token denomination |
306+
| `NODE_ENV` | No || Set to `development` for local HTTP RPC endpoints |
301307

302308
### CLI Commands
303309

@@ -332,9 +338,12 @@ docker pull ghcr.io/akash-network/hermes:latest
332338
# Run with environment variables
333339
docker run -d \
334340
--name hermes-client \
335-
-e RPC_ENDPOINT=https://rpc.akashnet.net:443 \
336-
-e CONTRACT_ADDRESS=akash1... \
337-
-e "MNEMONIC=your twelve word mnemonic here" \
341+
-e HC_RPC_ENDPOINT=https://rpc.akashnet.net:443 \
342+
-e HC_CONTRACT_ADDRESS=akash1... \
343+
-e "HC_WALLET_SECRET=mnemonic:your twelve word mnemonic here" \
344+
-e HC_HERMES_ENDPOINT=https://pyth.dourolabs.app/hermes \
345+
-e HC_HERMES_API_KEY="$PYTH_HERMES_API_KEY" \
346+
-e HC_PRICE_FETCHING_METHOD=polling \
338347
--restart unless-stopped \
339348
ghcr.io/akash-network/hermes:latest node dist/cli.js daemon
340349

@@ -917,7 +926,9 @@ npm run cli:daemon
917926

918927
**Test Hermes API:**
919928
```bash
920-
curl "https://hermes.pyth.network/v2/updates/price/latest?ids=<PRICE_FEED_ID>"
929+
curl \
930+
-H "Authorization: Bearer $PYTH_HERMES_API_KEY" \
931+
"https://pyth.dourolabs.app/hermes/v2/updates/price/latest?ids%5B%5D=<PRICE_FEED_ID>&encoding=base64"
921932
```
922933

923934
---

_run/init.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ write_hermes_config() {
302302
# Generated by akash-node init script
303303
# Contract deployed at $(date -u '+%Y-%m-%d %H:%M:%S UTC')
304304
305-
CONTRACT_ADDRESS="$pyth_addr"
306-
WALLET_SECRET="mnemonic:$HERMES_MNEMONIC"
305+
HC_CONTRACT_ADDRESS="$pyth_addr"
306+
HC_WALLET_SECRET="mnemonic:$HERMES_MNEMONIC"
307307
EOF
308308

309309
log "Hermes configuration written successfully"

_run/node/docker-compose.yaml

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# Docker Compose for Akash Local Development with Pyth Oracle
22
#
33
# This setup includes:
4-
# - akash-node: Single-node validator with Wormhole and Pyth contracts
5-
# - hermes-client: Price relayer that submits Pyth prices to the oracle
4+
# - akash-node: Single-node validator with pyth-vaa and pyth-pro contracts
5+
# - hermes-client: Upgraded Hermes relayer that submits Pyth PNAU data to pyth-pro
66
#
77
# Prerequisites:
88
# - Build contracts first: cd contracts && make build
9-
# - Hermes repo at ../../hermes (relative to node repo)
9+
# - PYTH_HERMES_API_KEY for the upgraded Pyth Hermes API
1010
#
1111
# Usage:
1212
# docker-compose -f _build/docker-compose.yaml up -d # Start all services
@@ -23,7 +23,7 @@
2323
---
2424
services:
2525
validator:
26-
image: ghcr.io/akash-network/node:latest-arm64
26+
image: ${AKASH_NODE_IMAGE:-ghcr.io/akash-network/node:latest-arm64}
2727
container_name: validator
2828
hostname: validator
2929
environment:
@@ -59,19 +59,22 @@ services:
5959
# Hermes Price Relayer
6060
# =============================================================================
6161
hermes-client:
62-
image: ghcr.io/akash-network/hermes:latest
62+
image: ${HERMES_IMAGE:-ghcr.io/akash-network/hermes:latest}
6363
container_name: hermes-client
6464
hostname: hermes-client
6565
environment:
6666
# RPC endpoint (internal docker network)
67-
- RPC_ENDPOINT=http://validator:26657
68-
# Pyth Hermes API
69-
- HERMES_ENDPOINT=https://hermes.pyth.network
67+
- HC_RPC_ENDPOINT=http://validator:26657
68+
# Upgraded Pyth Hermes API
69+
- HC_HERMES_ENDPOINT=${PYTH_HERMES_ENDPOINT:-https://pyth.dourolabs.app/hermes}
70+
- HC_HERMES_API_KEY=${PYTH_HERMES_API_KEY:-}
71+
- HC_PRICE_FETCHING_METHOD=polling
7072
# Update interval (1 minute for testing, use 5+ minutes in production)
71-
- UPDATE_INTERVAL_MS=10000
73+
- HC_UPDATE_INTERVAL_MS=10000
74+
- HC_PRICE_DEVIATION_TOLERANCE=0
7275
# Gas configuration
73-
- GAS_PRICE=0.025uakt
74-
- DENOM=uakt
76+
- HC_GAS_PRICE=0.025uakt
77+
- HC_DENOM=uakt
7578
- NODE_ENV=development
7679
env_file:
7780
- ${AKASH_RUN_DIR}/hermes.env
@@ -84,7 +87,7 @@ services:
8487
echo "Hermes Price Relayer - Waiting for configuration"
8588
echo "================================================"
8689
87-
echo "Contract address: $$CONTRACT_ADDRESS"
90+
echo "Contract address: $$HC_CONTRACT_ADDRESS"
8891
echo "Starting Hermes daemon..."
8992
echo ""
9093

make/test-upgrade.mk

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,13 @@ test: init
7474
.PHONY: setup-hermes
7575
.ONESHELL:
7676
setup-hermes:
77+
@if [ -z "$${PYTH_HERMES_API_KEY:-}" ]; then \
78+
echo "PYTH_HERMES_API_KEY is required for upgraded Hermes tests"; \
79+
exit 1; \
80+
fi
7781
@cat << 'EOF' > "$(AKASH_RUN_DIR)/hermes.env"
78-
CONTRACT_ADDRESS="akash1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqyagled"
79-
WALLET_SECRET="privateKey:47affbcbbcc1b68241f5090549f4ccf7bc9fdab6870ae760d1e3469fd82e828e"
82+
HC_CONTRACT_ADDRESS="akash1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqyagled"
83+
HC_WALLET_SECRET="privateKey:47affbcbbcc1b68241f5090549f4ccf7bc9fdab6870ae760d1e3469fd82e828e"
8084
EOF
8185

8286
.PHONY: test-reset

tests/e2e/pyth_contract_test.go

Lines changed: 12 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -72,79 +72,6 @@ func NetworkConfig() *network.Config {
7272
return &cfg
7373
}
7474

75-
// =====================
76-
// Wormhole Contract Types
77-
// =====================
78-
79-
// WormholeInstantiateMsg is the message to instantiate the wormhole contract
80-
type WormholeInstantiateMsg struct {
81-
GovChain uint16 `json:"gov_chain"`
82-
GovAddress string `json:"gov_address"`
83-
InitialGuardianSet GuardianSetInfo `json:"initial_guardian_set"`
84-
GuardianSetExpirity uint64 `json:"guardian_set_expirity"`
85-
ChainID uint16 `json:"chain_id"`
86-
FeeDenom string `json:"fee_denom"`
87-
}
88-
89-
// GuardianSetInfo contains guardian set data
90-
type GuardianSetInfo struct {
91-
Addresses []GuardianAddress `json:"addresses"`
92-
ExpirationTime uint64 `json:"expiration_time"`
93-
}
94-
95-
// GuardianAddress represents a guardian's Ethereum-style address
96-
type GuardianAddress struct {
97-
Bytes string `json:"bytes"` // base64 encoded
98-
}
99-
100-
// WormholeExecuteMsg is the execute message for wormhole contract
101-
type WormholeExecuteMsg struct {
102-
SubmitVAA *SubmitVAAMsg `json:"submit_v_a_a,omitempty"`
103-
PostMessage *PostMessageMsg `json:"post_message,omitempty"`
104-
}
105-
106-
type SubmitVAAMsg struct {
107-
VAA string `json:"vaa"` // base64 encoded
108-
}
109-
110-
type PostMessageMsg struct {
111-
Message string `json:"message"` // base64 encoded
112-
Nonce uint32 `json:"nonce"`
113-
}
114-
115-
// WormholeQueryMsg is the query message for wormhole contract
116-
type WormholeQueryMsg struct {
117-
GuardianSetInfo *struct{} `json:"guardian_set_info,omitempty"`
118-
VerifyVAA *VerifyVAAQuery `json:"verify_v_a_a,omitempty"`
119-
GetState *struct{} `json:"get_state,omitempty"`
120-
QueryAddressHex *QueryAddressHexMsg `json:"query_address_hex,omitempty"`
121-
}
122-
123-
type VerifyVAAQuery struct {
124-
VAA string `json:"vaa"` // base64 encoded
125-
BlockTime uint64 `json:"block_time"`
126-
}
127-
128-
type QueryAddressHexMsg struct {
129-
Address string `json:"address"`
130-
}
131-
132-
// WormholeGuardianSetInfoResponse is the response from GuardianSetInfo query
133-
type WormholeGuardianSetInfoResponse struct {
134-
GuardianSetIndex uint32 `json:"guardian_set_index"`
135-
Addresses []GuardianAddress `json:"addresses"`
136-
}
137-
138-
// WormholeGetStateResponse is the response from GetState query
139-
type WormholeGetStateResponse struct {
140-
Fee CoinResponse `json:"fee"`
141-
}
142-
143-
type CoinResponse struct {
144-
Denom string `json:"denom"`
145-
Amount string `json:"amount"`
146-
}
147-
14875
// =====================
14976
// Price Oracle Contract Types
15077
// =====================
@@ -334,65 +261,6 @@ func (s *priceOracleContractTestSuite) TestStoreContractViaGovernance() {
334261
s.T().Log("Successfully submitted store code proposal via governance")
335262
}
336263

337-
// TestWormholeContractMessageEncoding tests that Wormhole contract message types serialize correctly
338-
func (s *priceOracleContractTestSuite) TestWormholeContractMessageEncoding() {
339-
// Test WormholeInstantiateMsg encoding
340-
// Use a test guardian address (20 bytes)
341-
testGuardianAddr := make([]byte, 20)
342-
for i := range testGuardianAddr {
343-
testGuardianAddr[i] = byte(i + 1)
344-
}
345-
346-
instantiateMsg := WormholeInstantiateMsg{
347-
GovChain: 1, // Solana
348-
GovAddress: base64.StdEncoding.EncodeToString(make([]byte, 32)),
349-
InitialGuardianSet: GuardianSetInfo{
350-
Addresses: []GuardianAddress{
351-
{Bytes: base64.StdEncoding.EncodeToString(testGuardianAddr)},
352-
},
353-
ExpirationTime: 0,
354-
},
355-
GuardianSetExpirity: 86400,
356-
ChainID: 18, // Example chain ID
357-
FeeDenom: "uakt",
358-
}
359-
360-
data, err := json.Marshal(instantiateMsg)
361-
s.Require().NoError(err)
362-
s.T().Logf("Wormhole InstantiateMsg JSON: %s", string(data))
363-
364-
var decoded WormholeInstantiateMsg
365-
err = json.Unmarshal(data, &decoded)
366-
s.Require().NoError(err)
367-
s.Require().Equal(instantiateMsg.GovChain, decoded.GovChain)
368-
s.Require().Equal(instantiateMsg.ChainID, decoded.ChainID)
369-
370-
// Test WormholeQueryMsg encoding
371-
queryMsg := WormholeQueryMsg{
372-
GuardianSetInfo: &struct{}{},
373-
}
374-
375-
data, err = json.Marshal(queryMsg)
376-
s.Require().NoError(err)
377-
s.Require().Equal(`{"guardian_set_info":{}}`, string(data))
378-
379-
queryMsg = WormholeQueryMsg{
380-
GetState: &struct{}{},
381-
}
382-
383-
data, err = json.Marshal(queryMsg)
384-
s.Require().NoError(err)
385-
s.Require().Equal(`{"get_state":{}}`, string(data))
386-
387-
queryMsg = WormholeQueryMsg{
388-
QueryAddressHex: &QueryAddressHexMsg{Address: "akash1test123"},
389-
}
390-
391-
data, err = json.Marshal(queryMsg)
392-
s.Require().NoError(err)
393-
s.T().Logf("Wormhole QueryAddressHex JSON: %s", string(data))
394-
}
395-
396264
// TestPriceOracleWithVAAMessageEncoding tests that Pyth contract PNAU message types serialize correctly.
397265
func (s *priceOracleContractTestSuite) TestPriceOracleWithVAAMessageEncoding() {
398266
vaaInstantiateMsg := PythVaaInstantiateMsg{
@@ -595,42 +463,6 @@ func (s *priceOracleContractTestSuite) TestContractResponseParsing() {
595463
s.Require().Equal(uint64(100), params.LastUpdatedHeight)
596464
}
597465

598-
// TestWormholeResponseParsing tests parsing of Wormhole contract responses
599-
func (s *priceOracleContractTestSuite) TestWormholeResponseParsing() {
600-
// Test GuardianSetInfoResponse parsing
601-
testGuardianAddr := make([]byte, 20)
602-
for i := range testGuardianAddr {
603-
testGuardianAddr[i] = byte(i + 1)
604-
}
605-
606-
guardianSetJSON := `{
607-
"guardian_set_index": 3,
608-
"addresses": [
609-
{"bytes": "` + base64.StdEncoding.EncodeToString(testGuardianAddr) + `"}
610-
]
611-
}`
612-
613-
var guardianSet WormholeGuardianSetInfoResponse
614-
err := json.Unmarshal([]byte(guardianSetJSON), &guardianSet)
615-
s.Require().NoError(err)
616-
s.Require().Equal(uint32(3), guardianSet.GuardianSetIndex)
617-
s.Require().Len(guardianSet.Addresses, 1)
618-
619-
// Test GetStateResponse parsing
620-
stateJSON := `{
621-
"fee": {
622-
"denom": "uakt",
623-
"amount": "1000"
624-
}
625-
}`
626-
627-
var state WormholeGetStateResponse
628-
err = json.Unmarshal([]byte(stateJSON), &state)
629-
s.Require().NoError(err)
630-
s.Require().Equal("uakt", state.Fee.Denom)
631-
s.Require().Equal("1000", state.Fee.Amount)
632-
}
633-
634466
// TestPNAUExecuteMessageParsing tests that PNAU execute messages are properly formatted.
635467
func (s *priceOracleContractTestSuite) TestPNAUExecuteMessageParsing() {
636468
testPNAUData := []byte("PNAU" + "test_router_signed_accumulator_update")
@@ -667,8 +499,6 @@ func (s *priceOracleContractTestSuite) TestAllContractsExist() {
667499
dir string
668500
wasmFile string
669501
}{
670-
{"wormhole", "wormhole", "wormhole.wasm"},
671-
{"pyth", "pyth", "pyth.wasm"},
672502
{"pyth-vaa", "pyth-vaa", "pyth_vaa.wasm"},
673503
{"pyth-pro", "pyth-pro", "pyth_pro.wasm"},
674504
}
@@ -692,11 +522,12 @@ func (s *priceOracleContractTestSuite) TestAllContractsExist() {
692522
}
693523
}
694524

695-
// TestVAAStructure validates VAA binary structure understanding
696-
func (s *priceOracleContractTestSuite) TestVAAStructure() {
697-
// VAA header structure (for reference):
525+
// TestRouterSignedVAAStructure documents the VAA-format envelope used inside PNAU.
526+
func (s *priceOracleContractTestSuite) TestRouterSignedVAAStructure() {
527+
// The upgraded Pyth router payload keeps the VAA-format envelope, but the
528+
// signer set is the Pyth router set rather than the former guardian set.
698529
// - version (1 byte)
699-
// - guardian_set_index (4 bytes)
530+
// - router_set_index (4 bytes)
700531
// - len_signers (1 byte)
701532
// - signatures (66 bytes each)
702533
// - body:
@@ -711,7 +542,7 @@ func (s *priceOracleContractTestSuite) TestVAAStructure() {
711542
// Test that we understand the structure correctly
712543
s.T().Log("VAA Header structure:")
713544
s.T().Log(" - Version: 1 byte at offset 0")
714-
s.T().Log(" - Guardian Set Index: 4 bytes at offset 1")
545+
s.T().Log(" - Router Set Index: 4 bytes at offset 1")
715546
s.T().Log(" - Num Signers: 1 byte at offset 5")
716547
s.T().Log(" - Signatures: 66 bytes each starting at offset 6")
717548
s.T().Log("Body structure (after signatures):")
@@ -723,15 +554,14 @@ func (s *priceOracleContractTestSuite) TestVAAStructure() {
723554
s.T().Log(" - Consistency Level: 1 byte at offset 50")
724555
s.T().Log(" - Payload: variable starting at offset 51")
725556

726-
// Create a minimal test VAA structure
727-
testGuardianAddr := make([]byte, 20)
728-
for i := range testGuardianAddr {
729-
testGuardianAddr[i] = byte(i + 1)
557+
// Router addresses use the same 20-byte Ethereum-style address encoding.
558+
testRouterAddr := make([]byte, 20)
559+
for i := range testRouterAddr {
560+
testRouterAddr[i] = byte(i + 1)
730561
}
731562

732-
// Log test guardian address
733-
s.T().Logf("Test guardian address (hex): %s", hex.EncodeToString(testGuardianAddr))
734-
s.T().Logf("Test guardian address (base64): %s", base64.StdEncoding.EncodeToString(testGuardianAddr))
563+
s.T().Logf("Test router address (hex): %s", hex.EncodeToString(testRouterAddr))
564+
s.T().Logf("Test router address (base64): %s", base64.StdEncoding.EncodeToString(testRouterAddr))
735565
}
736566

737567
// findWasmPath attempts to find a wasm file for a given contract

0 commit comments

Comments
 (0)