// SPDX-License-Identifier: Apache 2 /// This module implements handling a governance VAA to enact upgrading the /// Wormhole contract to a new build. The procedure to upgrade this contract /// requires a Programmable Transaction, which includes the following procedure: /// 1. Load new build. /// 2. Authorize upgrade. /// 3. Upgrade. /// 4. Commit upgrade. module wormhole::upgrade_contract { use sui::object::{ID}; use sui::package::{UpgradeReceipt, UpgradeTicket}; use wormhole::bytes32::{Self, Bytes32}; use wormhole::cursor::{Self}; use wormhole::governance_message::{Self, DecreeTicket, DecreeReceipt}; use wormhole::state::{Self, State}; friend wormhole::migrate; /// Digest is all zeros. const E_DIGEST_ZERO_BYTES: u64 = 0; /// Specific governance payload ID (action) to complete upgrading the /// contract. const ACTION_UPGRADE_CONTRACT: u8 = 1; struct GovernanceWitness has drop {} // Event reflecting package upgrade. struct ContractUpgraded has drop, copy { old_contract: ID, new_contract: ID } struct UpgradeContract { digest: Bytes32 } public fun authorize_governance( wormhole_state: &State ): DecreeTicket { governance_message::authorize_verify_local( GovernanceWitness {}, state::governance_chain(wormhole_state), state::governance_contract(wormhole_state), state::governance_module(), ACTION_UPGRADE_CONTRACT ) } /// Redeem governance VAA to issue an `UpgradeTicket` for the upgrade given /// a contract upgrade VAA. This governance message is only relevant for Sui /// because a contract upgrade is only relevant to one particular network /// (in this case Sui), whose build digest is encoded in this message. public fun authorize_upgrade( wormhole_state: &mut State, receipt: DecreeReceipt ): UpgradeTicket { // NOTE: This is the only governance method that does not enforce // current package checking when consuming VAA hashes. This is because // upgrades are protected by the Sui VM, enforcing the latest package // is the one performing the upgrade. let consumed = state::borrow_mut_consumed_vaas_unchecked(wormhole_state); // And consume. let payload = governance_message::take_payload(consumed, receipt); // Proceed with processing new implementation version. handle_upgrade_contract(wormhole_state, payload) } /// Finalize the upgrade that ran to produce the given `receipt`. This /// method invokes `state::commit_upgrade` which interacts with /// `sui::package`. public fun commit_upgrade( self: &mut State, receipt: UpgradeReceipt, ) { let (old_contract, new_contract) = state::commit_upgrade(self, receipt); // Emit an event reflecting package ID change. sui::event::emit(ContractUpgraded { old_contract, new_contract }); } /// Privileged method only to be used by this module and `migrate` module. /// /// During migration, we make sure that the digest equals what we expect by /// passing in the same VAA used to upgrade the package. public(friend) fun take_digest(governance_payload: vector): Bytes32 { // Deserialize the payload as the build digest. let UpgradeContract { digest } = deserialize(governance_payload); digest } fun handle_upgrade_contract( wormhole_state: &mut State, payload: vector ): UpgradeTicket { state::authorize_upgrade(wormhole_state, take_digest(payload)) } fun deserialize(payload: vector): UpgradeContract { let cur = cursor::new(payload); // This amount cannot be greater than max u64. let digest = bytes32::take_bytes(&mut cur); assert!(bytes32::is_nonzero(&digest), E_DIGEST_ZERO_BYTES); cursor::destroy_empty(cur); UpgradeContract { digest } } #[test_only] public fun action(): u8 { ACTION_UPGRADE_CONTRACT } } #[test_only] module wormhole::upgrade_contract_tests { // TODO }