Skip to content

Commit 2bbeb03

Browse files
authored
[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
1 parent 3e56005 commit 2bbeb03

File tree

9 files changed

+357
-0
lines changed

9 files changed

+357
-0
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module pyth::contract_upgrade {
2+
use pyth::state::{State};
3+
4+
use wormhole::state::{State as WormState};
5+
6+
friend pyth::governance;
7+
8+
/// Payload should be the bytes digest of the new contract.
9+
public(friend) fun execute(_worm_state: &WormState, _pyth_state: &State, _payload: vector<u8>){
10+
// TODO
11+
}
12+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
module pyth::governance {
2+
use sui::tx_context::{TxContext};
3+
4+
use pyth::data_source::{Self};
5+
use pyth::governance_instruction;
6+
use pyth::governance_action;
7+
use pyth::contract_upgrade;
8+
use pyth::set_governance_data_source;
9+
use pyth::set_data_sources;
10+
use pyth::set_stale_price_threshold;
11+
use pyth::state::{State};
12+
use pyth::set_update_fee;
13+
use pyth::state;
14+
15+
use wormhole::vaa::{Self, VAA};
16+
use wormhole::state::{State as WormState};
17+
18+
public entry fun execute_governance_instruction(
19+
pyth_state : &mut State,
20+
worm_state: &WormState,
21+
vaa_bytes: vector<u8>,
22+
ctx: &mut TxContext
23+
) {
24+
let parsed_vaa = parse_and_verify_governance_vaa(pyth_state, worm_state, vaa_bytes, ctx);
25+
let instruction = governance_instruction::from_byte_vec(vaa::take_payload(parsed_vaa));
26+
27+
// Dispatch the instruction to the appropiate handler
28+
let action = governance_instruction::get_action(&instruction);
29+
if (action == governance_action::new_contract_upgrade()) {
30+
assert!(governance_instruction::get_target_chain_id(&instruction) != 0,
31+
0); // TODO - error::governance_contract_upgrade_chain_id_zero()
32+
contract_upgrade::execute(worm_state, pyth_state, governance_instruction::destroy(instruction));
33+
} else if (action == governance_action::new_set_governance_data_source()) {
34+
set_governance_data_source::execute(pyth_state, governance_instruction::destroy(instruction));
35+
} else if (action == governance_action::new_set_data_sources()) {
36+
set_data_sources::execute(pyth_state, governance_instruction::destroy(instruction));
37+
} else if (action == governance_action::new_set_update_fee()) {
38+
set_update_fee::execute(pyth_state, governance_instruction::destroy(instruction));
39+
} else if (action == governance_action::new_set_stale_price_threshold()) {
40+
set_stale_price_threshold::execute(pyth_state, governance_instruction::destroy(instruction));
41+
} else {
42+
governance_instruction::destroy(instruction);
43+
assert!(false, 0); // TODO - error::invalid_governance_action()
44+
}
45+
}
46+
47+
fun parse_and_verify_governance_vaa(
48+
pyth_state: &mut State,
49+
worm_state: &WormState,
50+
bytes: vector<u8>,
51+
ctx: &mut TxContext
52+
): VAA {
53+
let parsed_vaa = vaa::parse_and_verify(worm_state, bytes, ctx);
54+
55+
// Check that the governance data source is valid
56+
assert!(
57+
state::is_valid_governance_data_source(
58+
pyth_state,
59+
data_source::new(
60+
(vaa::emitter_chain(&parsed_vaa) as u64),
61+
vaa::emitter_address(&parsed_vaa))),
62+
0); // TODO - error::invalid_governance_data_source()
63+
64+
// Check that the sequence number is greater than the last executed governance VAA
65+
let sequence = vaa::sequence(&parsed_vaa);
66+
assert!(sequence > state::get_last_executed_governance_sequence(pyth_state), 0); // TODO - error::invalid_governance_sequence_number()
67+
state::set_last_executed_governance_sequence(pyth_state, sequence);
68+
69+
parsed_vaa
70+
}
71+
}
72+
73+
// TODO - add tests
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
module pyth::governance_action {
2+
//use pyth::error;
3+
4+
const CONTRACT_UPGRADE: u8 = 0;
5+
const SET_GOVERNANCE_DATA_SOURCE: u8 = 1;
6+
const SET_DATA_SOURCES: u8 = 2;
7+
const SET_UPDATE_FEE: u8 = 3;
8+
const SET_STALE_PRICE_THRESHOLD: u8 = 4;
9+
10+
struct GovernanceAction has copy, drop {
11+
value: u8,
12+
}
13+
14+
public fun from_u8(value: u8): GovernanceAction {
15+
assert!(CONTRACT_UPGRADE <= value && value <= SET_STALE_PRICE_THRESHOLD, 0); //TODO - add specific error: error::invalid_governance_action()
16+
GovernanceAction { value }
17+
}
18+
19+
public fun new_contract_upgrade(): GovernanceAction {
20+
GovernanceAction { value: CONTRACT_UPGRADE }
21+
}
22+
23+
public fun new_set_governance_data_source(): GovernanceAction {
24+
GovernanceAction { value: SET_GOVERNANCE_DATA_SOURCE }
25+
}
26+
27+
public fun new_set_data_sources(): GovernanceAction {
28+
GovernanceAction { value: SET_DATA_SOURCES }
29+
}
30+
31+
public fun new_set_update_fee(): GovernanceAction {
32+
GovernanceAction { value: SET_UPDATE_FEE }
33+
}
34+
35+
public fun new_set_stale_price_threshold(): GovernanceAction {
36+
GovernanceAction { value: SET_STALE_PRICE_THRESHOLD }
37+
}
38+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
module pyth::governance_instruction {
2+
use wormhole::cursor;
3+
use pyth::deserialize;
4+
use pyth::governance_action::{Self, GovernanceAction};
5+
6+
const MAGIC: vector<u8> = x"5054474d"; // "PTGM": Pyth Governance Message
7+
const MODULE: u8 = 1;
8+
9+
struct GovernanceInstruction {
10+
module_: u8,
11+
action: GovernanceAction,
12+
target_chain_id: u64,
13+
payload: vector<u8>,
14+
}
15+
16+
fun validate(instruction: &GovernanceInstruction) {
17+
assert!(instruction.module_ == MODULE, 0); // TODO - add custom error::invalid_governance_module()
18+
let target_chain_id = instruction.target_chain_id;
19+
assert!(target_chain_id == (wormhole::state::chain_id() as u64) || target_chain_id == 0, 0); // TODO - custom error: error::invalid_governance_target_chain_id()
20+
}
21+
22+
public fun from_byte_vec(bytes: vector<u8>): GovernanceInstruction {
23+
let cursor = cursor::new(bytes);
24+
let magic = deserialize::deserialize_vector(&mut cursor, 4);
25+
assert!(magic == MAGIC, 0); // TODO error::invalid_governance_magic_value()
26+
let module_ = deserialize::deserialize_u8(&mut cursor);
27+
let action = governance_action::from_u8(deserialize::deserialize_u8(&mut cursor));
28+
let target_chain_id = deserialize::deserialize_u16(&mut cursor);
29+
let payload = cursor::take_rest(cursor);
30+
31+
let instruction = GovernanceInstruction {
32+
module_,
33+
action,
34+
target_chain_id : (target_chain_id as u64),
35+
payload
36+
};
37+
validate(&instruction);
38+
39+
instruction
40+
}
41+
42+
public fun get_module(instruction: &GovernanceInstruction): u8 {
43+
instruction.module_
44+
}
45+
46+
public fun get_action(instruction: &GovernanceInstruction): GovernanceAction {
47+
instruction.action
48+
}
49+
50+
public fun get_target_chain_id(instruction: &GovernanceInstruction): u64 {
51+
instruction.target_chain_id
52+
}
53+
54+
public fun destroy(instruction: GovernanceInstruction): vector<u8> {
55+
let GovernanceInstruction {
56+
module_: _,
57+
action: _,
58+
target_chain_id: _,
59+
payload: payload
60+
} = instruction;
61+
payload
62+
}
63+
64+
#[test]
65+
#[expected_failure]
66+
fun test_from_byte_vec_invalid_magic() {
67+
let bytes = x"5054474eb01087a85361f738f19454e66664d3c9";
68+
destroy(from_byte_vec(bytes));
69+
}
70+
71+
#[test]
72+
#[expected_failure]
73+
fun test_from_byte_vec_invalid_module() {
74+
let bytes = x"5054474db00187a85361f738f19454e66664d3c9";
75+
destroy(from_byte_vec(bytes));
76+
}
77+
78+
#[test]
79+
#[expected_failure]
80+
fun test_from_byte_vec_invalid_target_chain_id() {
81+
let bytes = x"5054474db00187a85361f738f19454e66664d3c9";
82+
destroy(from_byte_vec(bytes));
83+
}
84+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
module pyth::set_data_sources {
2+
use std::vector;
3+
4+
use wormhole::cursor;
5+
use wormhole::external_address::{Self};
6+
7+
use pyth::deserialize;
8+
use pyth::data_source::{Self, DataSource};
9+
use pyth::state::{Self, State};
10+
11+
friend pyth::governance;
12+
13+
struct SetDataSources {
14+
sources: vector<DataSource>,
15+
}
16+
17+
public(friend) fun execute(state: &mut State, payload: vector<u8>) {
18+
let SetDataSources { sources } = from_byte_vec(payload);
19+
state::set_data_sources(state, sources);
20+
}
21+
22+
fun from_byte_vec(bytes: vector<u8>): SetDataSources {
23+
let cursor = cursor::new(bytes);
24+
let data_sources_count = deserialize::deserialize_u8(&mut cursor);
25+
26+
let sources = vector::empty();
27+
28+
let i = 0;
29+
while (i < data_sources_count) {
30+
let emitter_chain_id = deserialize::deserialize_u16(&mut cursor);
31+
let emitter_address = external_address::from_bytes(deserialize::deserialize_vector(&mut cursor, 32));
32+
vector::push_back(&mut sources, data_source::new((emitter_chain_id as u64), emitter_address));
33+
34+
i = i + 1;
35+
};
36+
37+
cursor::destroy_empty(cursor);
38+
39+
SetDataSources {
40+
sources
41+
}
42+
}
43+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module pyth::set_governance_data_source {
2+
use pyth::deserialize;
3+
use pyth::data_source;
4+
use pyth::state::{Self, State};
5+
6+
use wormhole::cursor;
7+
use wormhole::external_address::{Self, ExternalAddress};
8+
//use wormhole::state::{Self}
9+
10+
friend pyth::governance;
11+
12+
struct SetGovernanceDataSource {
13+
emitter_chain_id: u64,
14+
emitter_address: ExternalAddress,
15+
initial_sequence: u64,
16+
}
17+
18+
public(friend) fun execute(pyth_state: &mut State, payload: vector<u8>) {
19+
let SetGovernanceDataSource { emitter_chain_id, emitter_address, initial_sequence } = from_byte_vec(payload);
20+
state::set_governance_data_source(pyth_state, data_source::new(emitter_chain_id, emitter_address));
21+
state::set_last_executed_governance_sequence(pyth_state, initial_sequence);
22+
}
23+
24+
fun from_byte_vec(bytes: vector<u8>): SetGovernanceDataSource {
25+
let cursor = cursor::new(bytes);
26+
let emitter_chain_id = deserialize::deserialize_u16(&mut cursor);
27+
let emitter_address = external_address::from_bytes(deserialize::deserialize_vector(&mut cursor, 32));
28+
let initial_sequence = deserialize::deserialize_u64(&mut cursor);
29+
cursor::destroy_empty(cursor);
30+
SetGovernanceDataSource {
31+
emitter_chain_id: (emitter_chain_id as u64),
32+
emitter_address,
33+
initial_sequence
34+
}
35+
}
36+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module pyth::set_stale_price_threshold {
2+
use wormhole::cursor;
3+
use pyth::deserialize;
4+
use pyth::state::{Self, State};
5+
6+
friend pyth::governance;
7+
8+
struct SetStalePriceThreshold {
9+
threshold: u64,
10+
}
11+
12+
public(friend) fun execute(state: &mut State, payload: vector<u8>) {
13+
let SetStalePriceThreshold { threshold } = from_byte_vec(payload);
14+
state::set_stale_price_threshold_secs(state, threshold);
15+
}
16+
17+
fun from_byte_vec(bytes: vector<u8>): SetStalePriceThreshold {
18+
let cursor = cursor::new(bytes);
19+
let threshold = deserialize::deserialize_u64(&mut cursor);
20+
cursor::destroy_empty(cursor);
21+
SetStalePriceThreshold {
22+
threshold
23+
}
24+
}
25+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
module pyth::set_update_fee {
2+
use sui::math::{Self};
3+
4+
use pyth::deserialize;
5+
use pyth::state::{Self, State};
6+
7+
use wormhole::cursor;
8+
9+
10+
friend pyth::governance;
11+
12+
const MAX_U64: u128 = (1 << 64) - 1;
13+
14+
struct SetUpdateFee {
15+
mantissa: u64,
16+
exponent: u64,
17+
}
18+
19+
public(friend) fun execute(pyth_state: &mut State, payload: vector<u8>) {
20+
let SetUpdateFee { mantissa, exponent } = from_byte_vec(payload);
21+
assert!(exponent <= 255, 0); // TODO - throw error that exponent does not fit in a u8
22+
let fee = apply_exponent(mantissa, (exponent as u8));
23+
state::set_base_update_fee(pyth_state, fee);
24+
}
25+
26+
fun from_byte_vec(bytes: vector<u8>): SetUpdateFee {
27+
let cursor = cursor::new(bytes);
28+
let mantissa = deserialize::deserialize_u64(&mut cursor);
29+
let exponent = deserialize::deserialize_u64(&mut cursor);
30+
cursor::destroy_empty(cursor);
31+
SetUpdateFee {
32+
mantissa,
33+
exponent,
34+
}
35+
}
36+
37+
fun apply_exponent(mantissa: u64, exponent: u8): u64 {
38+
mantissa * math::pow(10, exponent)
39+
}
40+
}

target_chains/sui/contracts/sources/state.move

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ module pyth::state {
99
use pyth::price_identifier::{PriceIdentifier};
1010

1111
friend pyth::pyth;
12+
friend pyth::governance_action;
13+
friend pyth::set_update_fee;
14+
friend pyth::set_stale_price_threshold;
15+
friend pyth::set_data_sources;
16+
friend pyth::governance;
17+
friend pyth::set_governance_data_source;
1218

1319
/// Capability for creating a bridge state object, granted to sender when this
1420
/// module is deployed

0 commit comments

Comments
 (0)