Răsfoiți Sursa

[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
optke3 2 ani în urmă
părinte
comite
2bbeb03ca9

+ 12 - 0
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<u8>){
+        // TODO
+    }
+}

+ 73 - 0
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<u8>,
+        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<u8>,
+        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

+ 38 - 0
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 }
+    }
+}

+ 84 - 0
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<u8> = x"5054474d"; // "PTGM": Pyth Governance Message
+    const MODULE: u8 = 1;
+
+    struct GovernanceInstruction {
+        module_: u8,
+        action: GovernanceAction,
+        target_chain_id: u64,
+        payload: vector<u8>,
+    }
+
+    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<u8>): 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<u8> {
+        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));
+    }
+}

+ 43 - 0
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<DataSource>,
+    }
+
+    public(friend) fun execute(state: &mut State, payload: vector<u8>) {
+        let SetDataSources { sources } = from_byte_vec(payload);
+        state::set_data_sources(state, sources);
+    }
+
+    fun from_byte_vec(bytes: vector<u8>): 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
+        }
+    }
+}

+ 36 - 0
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<u8>) {
+        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<u8>): 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
+        }
+    }
+}

+ 25 - 0
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<u8>) {
+        let SetStalePriceThreshold { threshold } = from_byte_vec(payload);
+        state::set_stale_price_threshold_secs(state, threshold);
+    }
+
+    fun from_byte_vec(bytes: vector<u8>): SetStalePriceThreshold {
+        let cursor = cursor::new(bytes);
+        let threshold = deserialize::deserialize_u64(&mut cursor);
+        cursor::destroy_empty(cursor);
+        SetStalePriceThreshold {
+            threshold
+        }
+    }
+}

+ 40 - 0
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<u8>) {
+        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<u8>): 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)
+    }
+}

+ 6 - 0
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