[PASSED] OP-PIP-132: Pyth Core Deprecation (EVM)

Abstract

Repoint Pyth Core on 33 EVM deployments (19 mainnet, 14 testnet) at the upgraded Pyth Core data source and at a Wormhole contract carrying the Pyth Pro guardian set, as the on-chain half of the Pyth Core upgrade scheduled for 26 August 2026, 16:00 UTC.

This proposal executes three governance actions per chain, in this order:

  1. UpgradeContract → new Pyth Core implementation (action 0) — replaces the dual-verification rule in SetWormholeAddress, which structurally prevents moving a Pyth Core contract to a Wormhole contract with a different guardian set.
  2. SetDataSources → upgraded Pyth Core data source (action 2) — replaces the three legacy Pythnet/Solana data sources with the single upgraded Pyth Core data source.
  3. SetWormholeAddress → new Wormhole receiver (action 6) — repoints the contract at a freshly deployed Wormhole receiver holding the Pyth Pro guardian set.

99 instructions in total (33 chains × 3 actions).

Protocol fees on EVM were already zeroed by OP-PIP-128 and OP-PIP-130. This proposal contains no SetFee and no WithdrawFee action.

Rationale

Under the Pyth Core to Pyth Pro Migration (OP-PIP-100), Pythnet is sunsetting and Pyth Core price updates move to the Pyth Pro publishing stack. On EVM the change has two on-chain parts: which emitter the contract accepts price updates from (the data source), and which guardian set signs the VAAs carrying those updates (the Wormhole contract).

Today every Pyth Core contract points at a Wormhole receiver holding Wormhole guardian set 7 (19 guardians). Pyth Pro publishes through a 5-key Pyth multisig, so each contract has to be repointed at a Wormhole receiver holding that set.

SetWormholeAddress (action 6) has existed since the contract was written, but its old implementation made this specific move impossible. It set the new Wormhole address and then re-verified the same governance VAA through the new contract.

That check is a sanity guard against bricking the contract on a bad address, and it holds only when the old and new Wormhole contracts share a guardian set. The governance VAA here is signed by the Wormhole guardians, so re-verifying it against a receiver holding the Pyth Pro guardian set always fails and the instruction always reverts. Upgrading the implementation is therefore a hard prerequisite, not a convenience.

The replacement keeps a weaker but sufficient guard — the new address must be non-zero and must have code (commit 4739bbc).

The same commit adds a test that executes post-cutover governance through the new Wormhole receiver, proving the contract is still governable after the switch.

Description

Part 1: UpgradeContract (33 chains)

Execute GovernanceAction.UpgradeContract on every chain. The new implementation contracts are already deployed; each payload carries only the 20-byte implementation address, so verifying this part means verifying that the bytecode at those addresses matches this repository.

Part 2: SetDataSources (33 chains)

Execute GovernanceAction.SetDataSources with a single data source on every chain:

emitterChain:   26 (Pythnet)
emitterAddress: 0x507974686e6574507974686e6574507974686e6574507974686e657450797468

It is the same upgraded Pyth Core data source being set on the SVM chains in the companion proposal.

This replaces the three data sources every Pyth Core contract currently carries.

After execution the contracts accept price updates from the upgraded Pyth Core data source only. Legacy Pythnet updates stop verifying.

Part 3: SetWormholeAddress (33 chains)

Execute GovernanceAction.SetWormholeAddress pointing each Pyth Core contract at its new Wormhole receiver. Every one of these receivers is already deployed and, at the time of writing, holds:

guardian set index: 0
guardians (5):
  0x41534bB176E461A3fb30479400f210549eCCE638
  0x6502987B62F21Cab7eb5cCd8f0173084b60d5B41
  0x44A3e8F6a382412CF6BB90A3F8106e68977476C9
  0xd9D7D4529577864352c9A6539a48238fCD447052
  0x1663a5A822336ece48559B1DFB1e93A017a7DAC3

The receivers run ReceiverImplementationHalf, a variant of the standard Wormhole receiver whose quorum is n / 2 + 1 rather than 2n / 3 + 1. With five guardians that is a 3-of-5 threshold, matching the minimum_signatures = 3 configured on the SVM receivers.

Execution order. Instructions are grouped per chain as UpgradeContract, SetDataSources, SetWormholeAddress. The Pyth contract enforces strictly increasing Wormhole sequence numbers for governance instructions, so on every chain the ordering is guaranteed:

  • the upgrade lands before the SetWormholeAddress that depends on it, and
  • SetDataSources lands while the contract still verifies governance through the old Wormhole receiver, which is the only receiver that can verify a VAA signed by the Wormhole guardians.

SetWormholeAddress must be the last action on each chain for that reason. Once it executes, all subsequent governance for that contract — not just price updates — is verified against the Pyth Pro guardian set.

Governance data source is unchanged: emitter 0x5635979a221c34931e32620b9293a463065555ea71fe97cd6237ade875b12e9e on chain 1, the Pythian Council multisig on Solana. What changes is the guardian set that signs its VAAs.

Verification

1. Decode the proposal.

git clone <https://github.com/pyth-network/pyth-crosschain.git>
cd pyth-crosschain
pnpm install && pnpm turbo build --filter @pythnetwork/contract-manager
cd contract_manager
pnpm tsx scripts/check_proposal.ts --cluster mainnet-beta \
  --proposal 9yuPH43L5ouV4qBKEaZV8TYavZ3TLtePzaEJb4SKWgje

This walks all 99 instructions and prints, per chain: the code digest of the implementation each UpgradeContract points at; the proposed data source next to the contract’s current data sources; and the proxy digest, implementation digest, guardian set index and guardian set digest of the Wormhole receiver each SetWormholeAddress points at.

2. Reproduce the three expected digests from source.

cd target_chains/ethereum/contracts
pnpm run install-forge-deps
forge build

# Pyth Core implementation — every UpgradeContract target must match this
jq -r .deployedBytecode.object out/PythUpgradable.sol/PythUpgradable.json \
  | tr -d '\r\n' | cast keccak
# 0x7190f97e629ac8b731b8475c7819d77423b53215deaca6ed07fb89e414da135a

# Wormhole receiver proxy — every SetWormholeAddress target must match this
jq -r .deployedBytecode.object out/WormholeReceiver.sol/WormholeReceiver.json \
  | tr -d '\r\n' | cast keccak
# 0xdc3e90fa531e085a7df6e3fce1322f2ae67b47716d606097959e5d80afa764c6

# Wormhole receiver implementation (3-of-5 quorum variant)
jq -r .deployedBytecode.object \
  out/ReceiverImplementationHalf.sol/ReceiverImplementationHalf.json \
  | tr -d '\r\n' | cast keccak
# 0x672c872ab76094a205608519efb1f34a50662ced3410fdcfd45965bf41f6bef1

The UpgradeContract digest is taken with the contract’s own address zeroed out, so it is stable across chains.

4. Cross-reference. Pyth Core proxy addresses against the Pyth Core EVM chainlist.

References

4 Likes