From 2bbeb03ca9f8696d34b395dc7d1e94827377abae Mon Sep 17 00:00:00 2001 From: optke3 <108488464+optke3@users.noreply.github.com> Date: Fri, 28 Apr 2023 15:28:09 -0400 Subject: [PATCH] [sui 4/x] - governance modules, update to sui `0.29.0` (#732) * state getters and setters, change Move.toml dependency to sui/integration_v2 * finish state.move * add new line to pyth * use deployer cap pattern for state module * sui pyth * update price feeds, dynamic object fields, Sui object PriceInfoObject * register price info object with pyth state after creation * sui governance * some newlines --- .../sources/governance/contract_upgrade.move | 12 +++ .../sources/governance/governance.move | 73 ++++++++++++++++ .../sources/governance/governance_action.move | 38 +++++++++ .../governance/governance_instruction.move | 84 +++++++++++++++++++ .../sources/governance/set_data_sources.move | 43 ++++++++++ .../set_governance_data_source.move | 36 ++++++++ .../governance/set_stale_price_threshold.move | 25 ++++++ .../sources/governance/set_update_fee.move | 40 +++++++++ .../sui/contracts/sources/state.move | 6 ++ 9 files changed, 357 insertions(+) create mode 100644 target_chains/sui/contracts/sources/governance/contract_upgrade.move create mode 100644 target_chains/sui/contracts/sources/governance/governance.move create mode 100644 target_chains/sui/contracts/sources/governance/governance_action.move create mode 100644 target_chains/sui/contracts/sources/governance/governance_instruction.move create mode 100644 target_chains/sui/contracts/sources/governance/set_data_sources.move create mode 100644 target_chains/sui/contracts/sources/governance/set_governance_data_source.move create mode 100644 target_chains/sui/contracts/sources/governance/set_stale_price_threshold.move create mode 100644 target_chains/sui/contracts/sources/governance/set_update_fee.move diff --git a/target_chains/sui/contracts/sources/governance/contract_upgrade.move b/target_chains/sui/contracts/sources/governance/contract_upgrade.move new file mode 100644 index 00000000..e0130c13 --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/contract_upgrade.move @@ -0,0 +1,12 @@ +module pyth::contract_upgrade { + use pyth::state::{State}; + + use wormhole::state::{State as WormState}; + + friend pyth::governance; + + /// Payload should be the bytes digest of the new contract. + public(friend) fun execute(_worm_state: &WormState, _pyth_state: &State, _payload: vector){ + // TODO + } +} diff --git a/target_chains/sui/contracts/sources/governance/governance.move b/target_chains/sui/contracts/sources/governance/governance.move new file mode 100644 index 00000000..bdebad43 --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/governance.move @@ -0,0 +1,73 @@ +module pyth::governance { + use sui::tx_context::{TxContext}; + + use pyth::data_source::{Self}; + use pyth::governance_instruction; + use pyth::governance_action; + use pyth::contract_upgrade; + use pyth::set_governance_data_source; + use pyth::set_data_sources; + use pyth::set_stale_price_threshold; + use pyth::state::{State}; + use pyth::set_update_fee; + use pyth::state; + + use wormhole::vaa::{Self, VAA}; + use wormhole::state::{State as WormState}; + + public entry fun execute_governance_instruction( + pyth_state : &mut State, + worm_state: &WormState, + vaa_bytes: vector, + ctx: &mut TxContext + ) { + let parsed_vaa = parse_and_verify_governance_vaa(pyth_state, worm_state, vaa_bytes, ctx); + let instruction = governance_instruction::from_byte_vec(vaa::take_payload(parsed_vaa)); + + // Dispatch the instruction to the appropiate handler + let action = governance_instruction::get_action(&instruction); + if (action == governance_action::new_contract_upgrade()) { + assert!(governance_instruction::get_target_chain_id(&instruction) != 0, + 0); // TODO - error::governance_contract_upgrade_chain_id_zero() + contract_upgrade::execute(worm_state, pyth_state, governance_instruction::destroy(instruction)); + } else if (action == governance_action::new_set_governance_data_source()) { + set_governance_data_source::execute(pyth_state, governance_instruction::destroy(instruction)); + } else if (action == governance_action::new_set_data_sources()) { + set_data_sources::execute(pyth_state, governance_instruction::destroy(instruction)); + } else if (action == governance_action::new_set_update_fee()) { + set_update_fee::execute(pyth_state, governance_instruction::destroy(instruction)); + } else if (action == governance_action::new_set_stale_price_threshold()) { + set_stale_price_threshold::execute(pyth_state, governance_instruction::destroy(instruction)); + } else { + governance_instruction::destroy(instruction); + assert!(false, 0); // TODO - error::invalid_governance_action() + } + } + + fun parse_and_verify_governance_vaa( + pyth_state: &mut State, + worm_state: &WormState, + bytes: vector, + ctx: &mut TxContext + ): VAA { + let parsed_vaa = vaa::parse_and_verify(worm_state, bytes, ctx); + + // Check that the governance data source is valid + assert!( + state::is_valid_governance_data_source( + pyth_state, + data_source::new( + (vaa::emitter_chain(&parsed_vaa) as u64), + vaa::emitter_address(&parsed_vaa))), + 0); // TODO - error::invalid_governance_data_source() + + // Check that the sequence number is greater than the last executed governance VAA + let sequence = vaa::sequence(&parsed_vaa); + assert!(sequence > state::get_last_executed_governance_sequence(pyth_state), 0); // TODO - error::invalid_governance_sequence_number() + state::set_last_executed_governance_sequence(pyth_state, sequence); + + parsed_vaa + } +} + +// TODO - add tests diff --git a/target_chains/sui/contracts/sources/governance/governance_action.move b/target_chains/sui/contracts/sources/governance/governance_action.move new file mode 100644 index 00000000..04260833 --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/governance_action.move @@ -0,0 +1,38 @@ +module pyth::governance_action { + //use pyth::error; + + const CONTRACT_UPGRADE: u8 = 0; + const SET_GOVERNANCE_DATA_SOURCE: u8 = 1; + const SET_DATA_SOURCES: u8 = 2; + const SET_UPDATE_FEE: u8 = 3; + const SET_STALE_PRICE_THRESHOLD: u8 = 4; + + struct GovernanceAction has copy, drop { + value: u8, + } + + public fun from_u8(value: u8): GovernanceAction { + assert!(CONTRACT_UPGRADE <= value && value <= SET_STALE_PRICE_THRESHOLD, 0); //TODO - add specific error: error::invalid_governance_action() + GovernanceAction { value } + } + + public fun new_contract_upgrade(): GovernanceAction { + GovernanceAction { value: CONTRACT_UPGRADE } + } + + public fun new_set_governance_data_source(): GovernanceAction { + GovernanceAction { value: SET_GOVERNANCE_DATA_SOURCE } + } + + public fun new_set_data_sources(): GovernanceAction { + GovernanceAction { value: SET_DATA_SOURCES } + } + + public fun new_set_update_fee(): GovernanceAction { + GovernanceAction { value: SET_UPDATE_FEE } + } + + public fun new_set_stale_price_threshold(): GovernanceAction { + GovernanceAction { value: SET_STALE_PRICE_THRESHOLD } + } +} diff --git a/target_chains/sui/contracts/sources/governance/governance_instruction.move b/target_chains/sui/contracts/sources/governance/governance_instruction.move new file mode 100644 index 00000000..841e28ad --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/governance_instruction.move @@ -0,0 +1,84 @@ +module pyth::governance_instruction { + use wormhole::cursor; + use pyth::deserialize; + use pyth::governance_action::{Self, GovernanceAction}; + + const MAGIC: vector = x"5054474d"; // "PTGM": Pyth Governance Message + const MODULE: u8 = 1; + + struct GovernanceInstruction { + module_: u8, + action: GovernanceAction, + target_chain_id: u64, + payload: vector, + } + + fun validate(instruction: &GovernanceInstruction) { + assert!(instruction.module_ == MODULE, 0); // TODO - add custom error::invalid_governance_module() + let target_chain_id = instruction.target_chain_id; + assert!(target_chain_id == (wormhole::state::chain_id() as u64) || target_chain_id == 0, 0); // TODO - custom error: error::invalid_governance_target_chain_id() + } + + public fun from_byte_vec(bytes: vector): GovernanceInstruction { + let cursor = cursor::new(bytes); + let magic = deserialize::deserialize_vector(&mut cursor, 4); + assert!(magic == MAGIC, 0); // TODO error::invalid_governance_magic_value() + let module_ = deserialize::deserialize_u8(&mut cursor); + let action = governance_action::from_u8(deserialize::deserialize_u8(&mut cursor)); + let target_chain_id = deserialize::deserialize_u16(&mut cursor); + let payload = cursor::take_rest(cursor); + + let instruction = GovernanceInstruction { + module_, + action, + target_chain_id : (target_chain_id as u64), + payload + }; + validate(&instruction); + + instruction + } + + public fun get_module(instruction: &GovernanceInstruction): u8 { + instruction.module_ + } + + public fun get_action(instruction: &GovernanceInstruction): GovernanceAction { + instruction.action + } + + public fun get_target_chain_id(instruction: &GovernanceInstruction): u64 { + instruction.target_chain_id + } + + public fun destroy(instruction: GovernanceInstruction): vector { + let GovernanceInstruction { + module_: _, + action: _, + target_chain_id: _, + payload: payload + } = instruction; + payload + } + + #[test] + #[expected_failure] + fun test_from_byte_vec_invalid_magic() { + let bytes = x"5054474eb01087a85361f738f19454e66664d3c9"; + destroy(from_byte_vec(bytes)); + } + + #[test] + #[expected_failure] + fun test_from_byte_vec_invalid_module() { + let bytes = x"5054474db00187a85361f738f19454e66664d3c9"; + destroy(from_byte_vec(bytes)); + } + + #[test] + #[expected_failure] + fun test_from_byte_vec_invalid_target_chain_id() { + let bytes = x"5054474db00187a85361f738f19454e66664d3c9"; + destroy(from_byte_vec(bytes)); + } +} diff --git a/target_chains/sui/contracts/sources/governance/set_data_sources.move b/target_chains/sui/contracts/sources/governance/set_data_sources.move new file mode 100644 index 00000000..b1d44259 --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/set_data_sources.move @@ -0,0 +1,43 @@ +module pyth::set_data_sources { + use std::vector; + + use wormhole::cursor; + use wormhole::external_address::{Self}; + + use pyth::deserialize; + use pyth::data_source::{Self, DataSource}; + use pyth::state::{Self, State}; + + friend pyth::governance; + + struct SetDataSources { + sources: vector, + } + + public(friend) fun execute(state: &mut State, payload: vector) { + let SetDataSources { sources } = from_byte_vec(payload); + state::set_data_sources(state, sources); + } + + fun from_byte_vec(bytes: vector): SetDataSources { + let cursor = cursor::new(bytes); + let data_sources_count = deserialize::deserialize_u8(&mut cursor); + + let sources = vector::empty(); + + let i = 0; + while (i < data_sources_count) { + let emitter_chain_id = deserialize::deserialize_u16(&mut cursor); + let emitter_address = external_address::from_bytes(deserialize::deserialize_vector(&mut cursor, 32)); + vector::push_back(&mut sources, data_source::new((emitter_chain_id as u64), emitter_address)); + + i = i + 1; + }; + + cursor::destroy_empty(cursor); + + SetDataSources { + sources + } + } +} diff --git a/target_chains/sui/contracts/sources/governance/set_governance_data_source.move b/target_chains/sui/contracts/sources/governance/set_governance_data_source.move new file mode 100644 index 00000000..77328a87 --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/set_governance_data_source.move @@ -0,0 +1,36 @@ +module pyth::set_governance_data_source { + use pyth::deserialize; + use pyth::data_source; + use pyth::state::{Self, State}; + + use wormhole::cursor; + use wormhole::external_address::{Self, ExternalAddress}; + //use wormhole::state::{Self} + + friend pyth::governance; + + struct SetGovernanceDataSource { + emitter_chain_id: u64, + emitter_address: ExternalAddress, + initial_sequence: u64, + } + + public(friend) fun execute(pyth_state: &mut State, payload: vector) { + let SetGovernanceDataSource { emitter_chain_id, emitter_address, initial_sequence } = from_byte_vec(payload); + state::set_governance_data_source(pyth_state, data_source::new(emitter_chain_id, emitter_address)); + state::set_last_executed_governance_sequence(pyth_state, initial_sequence); + } + + fun from_byte_vec(bytes: vector): SetGovernanceDataSource { + let cursor = cursor::new(bytes); + let emitter_chain_id = deserialize::deserialize_u16(&mut cursor); + let emitter_address = external_address::from_bytes(deserialize::deserialize_vector(&mut cursor, 32)); + let initial_sequence = deserialize::deserialize_u64(&mut cursor); + cursor::destroy_empty(cursor); + SetGovernanceDataSource { + emitter_chain_id: (emitter_chain_id as u64), + emitter_address, + initial_sequence + } + } +} diff --git a/target_chains/sui/contracts/sources/governance/set_stale_price_threshold.move b/target_chains/sui/contracts/sources/governance/set_stale_price_threshold.move new file mode 100644 index 00000000..6fdb6a2e --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/set_stale_price_threshold.move @@ -0,0 +1,25 @@ +module pyth::set_stale_price_threshold { + use wormhole::cursor; + use pyth::deserialize; + use pyth::state::{Self, State}; + + friend pyth::governance; + + struct SetStalePriceThreshold { + threshold: u64, + } + + public(friend) fun execute(state: &mut State, payload: vector) { + let SetStalePriceThreshold { threshold } = from_byte_vec(payload); + state::set_stale_price_threshold_secs(state, threshold); + } + + fun from_byte_vec(bytes: vector): SetStalePriceThreshold { + let cursor = cursor::new(bytes); + let threshold = deserialize::deserialize_u64(&mut cursor); + cursor::destroy_empty(cursor); + SetStalePriceThreshold { + threshold + } + } +} diff --git a/target_chains/sui/contracts/sources/governance/set_update_fee.move b/target_chains/sui/contracts/sources/governance/set_update_fee.move new file mode 100644 index 00000000..c300a698 --- /dev/null +++ b/target_chains/sui/contracts/sources/governance/set_update_fee.move @@ -0,0 +1,40 @@ +module pyth::set_update_fee { + use sui::math::{Self}; + + use pyth::deserialize; + use pyth::state::{Self, State}; + + use wormhole::cursor; + + + friend pyth::governance; + + const MAX_U64: u128 = (1 << 64) - 1; + + struct SetUpdateFee { + mantissa: u64, + exponent: u64, + } + + public(friend) fun execute(pyth_state: &mut State, payload: vector) { + let SetUpdateFee { mantissa, exponent } = from_byte_vec(payload); + assert!(exponent <= 255, 0); // TODO - throw error that exponent does not fit in a u8 + let fee = apply_exponent(mantissa, (exponent as u8)); + state::set_base_update_fee(pyth_state, fee); + } + + fun from_byte_vec(bytes: vector): SetUpdateFee { + let cursor = cursor::new(bytes); + let mantissa = deserialize::deserialize_u64(&mut cursor); + let exponent = deserialize::deserialize_u64(&mut cursor); + cursor::destroy_empty(cursor); + SetUpdateFee { + mantissa, + exponent, + } + } + + fun apply_exponent(mantissa: u64, exponent: u8): u64 { + mantissa * math::pow(10, exponent) + } +} diff --git a/target_chains/sui/contracts/sources/state.move b/target_chains/sui/contracts/sources/state.move index bd247496..bed995e1 100644 --- a/target_chains/sui/contracts/sources/state.move +++ b/target_chains/sui/contracts/sources/state.move @@ -9,6 +9,12 @@ module pyth::state { use pyth::price_identifier::{PriceIdentifier}; friend pyth::pyth; + friend pyth::governance_action; + friend pyth::set_update_fee; + friend pyth::set_stale_price_threshold; + friend pyth::set_data_sources; + friend pyth::governance; + friend pyth::set_governance_data_source; /// Capability for creating a bridge state object, granted to sender when this /// module is deployed