[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
This commit is contained in:
optke3 2023-04-28 15:28:09 -04:00 committed by GitHub
parent 3e5600508b
commit 2bbeb03ca9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 357 additions and 0 deletions

View File

@ -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
}
}

View File

@ -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

View File

@ -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 }
}
}

View File

@ -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));
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}
}

View File

@ -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)
}
}

View File

@ -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