Skip to content

Commit f541ffa

Browse files
authored
[sui 5/x] - Sui clock, error codes, fee computation (#740)
* 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 * error codes * update and comment
1 parent 2bbeb03 commit f541ffa

File tree

9 files changed

+93
-54
lines changed

9 files changed

+93
-54
lines changed

target_chains/sui/contracts/sources/batch_price_attestation.move

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
11
module pyth::batch_price_attestation {
2-
3-
use sui::tx_context::{Self, TxContext};
2+
use std::vector::{Self};
3+
use sui::clock::{Self, Clock};
44

55
use pyth::price_feed::{Self};
66
use pyth::price_info::{Self, PriceInfo};
77
use pyth::price_identifier::{Self};
88
use pyth::price_status;
99
use pyth::deserialize::{Self};
10-
// TODO - Import Sui clock and use it for timekeeping instead of tx_context::epoch.
11-
// Replace epoch in deserialize_price_info with sui clock timestamp, and usage
12-
// of epoch in test_deserialize_batch_price_attestation.
13-
// TODO - Use specific error messages in this module, specifically
14-
// for invalid_attestation_magic_value and invalid_batch_attestation_header_size.
10+
1511
use wormhole::cursor::{Self, Cursor};
1612
use wormhole::bytes::{Self};
1713

18-
use std::vector::{Self};
19-
2014
#[test_only]
2115
use pyth::price;
2216
#[test_only]
2317
use pyth::i64;
2418

2519
const MAGIC: u64 = 0x50325748; // "P2WH" (Pyth2Wormhole) raw ASCII bytes
20+
const E_INVALID_ATTESTATION_MAGIC_VALUE: u64 = 0;
21+
const E_INVALID_BATCH_ATTESTATION_HEADER_SIZE: u64 = 1;
2622

2723
struct BatchPriceAttestation {
2824
header: Header,
@@ -41,13 +37,13 @@ module pyth::batch_price_attestation {
4137

4238
fun deserialize_header(cur: &mut Cursor<u8>): Header {
4339
let magic = (deserialize::deserialize_u32(cur) as u64);
44-
assert!(magic == MAGIC, 0); // TODO - add specific error value - error::invalid_attestation_magic_value()
40+
assert!(magic == MAGIC, E_INVALID_ATTESTATION_MAGIC_VALUE);
4541
let version_major = deserialize::deserialize_u16(cur);
4642
let version_minor = deserialize::deserialize_u16(cur);
4743
let header_size = deserialize::deserialize_u16(cur);
4844
let payload_id = deserialize::deserialize_u8(cur);
4945

50-
assert!(header_size >= 1, 0); // TODO - add specific error value - error::invalid_batch_attestation_header_size()
46+
assert!(header_size >= 1, E_INVALID_BATCH_ATTESTATION_HEADER_SIZE);
5147
let unknown_header_bytes = header_size - 1;
5248
let _unknown = bytes::take_bytes(cur, (unknown_header_bytes as u64));
5349

@@ -84,7 +80,7 @@ module pyth::batch_price_attestation {
8480
vector::borrow(&batch.price_infos, index)
8581
}
8682

87-
public fun deserialize(bytes: vector<u8>, ctx: &mut TxContext): BatchPriceAttestation {
83+
public fun deserialize(bytes: vector<u8>, clock: &Clock): BatchPriceAttestation {
8884
let cur = cursor::new(bytes);
8985
let header = deserialize_header(&mut cur);
9086

@@ -94,7 +90,7 @@ module pyth::batch_price_attestation {
9490

9591
let i = 0;
9692
while (i < attestation_count) {
97-
let price_info = deserialize_price_info(&mut cur, ctx);
93+
let price_info = deserialize_price_info(&mut cur, clock);
9894
vector::push_back(&mut price_infos, price_info);
9995

10096
// Consume any excess bytes
@@ -113,7 +109,7 @@ module pyth::batch_price_attestation {
113109
}
114110
}
115111

116-
fun deserialize_price_info(cur: &mut Cursor<u8>, ctx: &mut TxContext): PriceInfo {
112+
fun deserialize_price_info(cur: &mut Cursor<u8>, clock: &Clock): PriceInfo {
117113

118114
// Skip obselete field
119115
let _product_identifier = deserialize::deserialize_vector(cur, 32);
@@ -155,7 +151,7 @@ module pyth::batch_price_attestation {
155151

156152
price_info::new_price_info(
157153
attestation_time,
158-
tx_context::epoch(ctx), //TODO - use Sui Clock to get timestamp in seconds
154+
clock::timestamp_ms(clock) / 1000, // Divide by 1000 to get timestamp in seconds
159155
price_feed::new(
160156
price_identifier,
161157
current_price,
@@ -167,21 +163,30 @@ module pyth::batch_price_attestation {
167163
#[test]
168164
#[expected_failure]
169165
fun test_deserialize_batch_price_attestation_invalid_magic() {
170-
use sui::test_scenario::{Self, ctx};
166+
use sui::test_scenario::{Self, take_shared, return_shared, ctx};
171167
let test = test_scenario::begin(@0x1234);
168+
clock::create_for_testing(ctx(&mut test));
169+
test_scenario::next_tx(&mut test, @0x1234);
170+
let test_clock = take_shared<Clock>(&test);
172171

173172
// A batch price attestation with a magic number of 0x50325749
174173
let bytes = x"5032574900030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001";
175-
let _ = destroy(deserialize(bytes, ctx(&mut test)));
174+
let _ = destroy(deserialize(bytes, &test_clock));
175+
return_shared(test_clock);
176176
test_scenario::end(test);
177177
}
178178

179179
#[test]
180180
fun test_deserialize_batch_price_attestation() {
181-
use sui::test_scenario::{Self, ctx};
181+
use sui::test_scenario::{Self, take_shared, return_shared, ctx};
182182
// Set the arrival time
183183
let test = test_scenario::begin(@0x1234);
184-
let arrival_time = tx_context::epoch(ctx(&mut test));
184+
clock::create_for_testing(ctx(&mut test));
185+
test_scenario::next_tx(&mut test, @0x1234);
186+
let test_clock = take_shared<Clock>(&test);
187+
let arrival_time_in_seconds = clock::timestamp_ms(&test_clock) / 1000;
188+
189+
// let arrival_time = tx_context::epoch(ctx(&mut test));
185190

186191
// A raw batch price attestation
187192
// The first attestation has a status of UNKNOWN
@@ -200,45 +205,46 @@ module pyth::batch_price_attestation {
200205
price_infos: vector<PriceInfo>[
201206
price_info::new_price_info(
202207
1663680747,
203-
arrival_time,
208+
arrival_time_in_seconds,
204209
price_feed::new(
205210
price_identifier::from_byte_vec(x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1"),
206211
price::new(i64::new(1557, false), 7, i64::new(5, true), 1663680740),
207212
price::new(i64::new(1500, false), 3, i64::new(5, true), 1663680740),
208213
) ),
209214
price_info::new_price_info(
210215
1663680747,
211-
arrival_time,
216+
arrival_time_in_seconds,
212217
price_feed::new(
213218
price_identifier::from_byte_vec(x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe"),
214219
price::new(i64::new(1050, false), 3, i64::new(5, true), 1663680745),
215220
price::new(i64::new(1483, false), 3, i64::new(5, true), 1663680745),
216221
) ),
217222
price_info::new_price_info(
218223
1663680747,
219-
arrival_time,
224+
arrival_time_in_seconds,
220225
price_feed::new(
221226
price_identifier::from_byte_vec(x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d"),
222227
price::new(i64::new(1010, false), 2, i64::new(5, true), 1663680745),
223228
price::new(i64::new(1511, false), 3, i64::new(5, true), 1663680745),
224229
) ),
225230
price_info::new_price_info(
226231
1663680747,
227-
arrival_time,
232+
arrival_time_in_seconds,
228233
price_feed::new(
229234
price_identifier::from_byte_vec(x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8"),
230235
price::new(i64::new(1739, false), 1, i64::new(5, true), 1663680745),
231236
price::new(i64::new(1508, false), 3, i64::new(5, true), 1663680745),
232-
) ),
237+
)
238+
),
233239
],
234240
};
235241

236-
let deserialized = deserialize(bytes, ctx(&mut test));
242+
let deserialized = deserialize(bytes, &test_clock);
237243

238244
assert!(&expected == &deserialized, 1);
239245
destroy(expected);
240246
destroy(deserialized);
241-
247+
return_shared(test_clock);
242248
test_scenario::end(test);
243249
}
244250
}

target_chains/sui/contracts/sources/data_source.move

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module pyth::data_source {
88
use wormhole::external_address::ExternalAddress;
99

1010
const KEY: vector<u8> = b"data_sources";
11+
const E_DATA_SOURCE_REGISTRY_ALREADY_EXISTS: u64 = 0;
12+
const E_DATA_SOURCE_ALREADY_REGISTERED: u64 = 1;
1113

1214
struct DataSource has copy, drop, store {
1315
emitter_chain: u64,
@@ -17,7 +19,7 @@ module pyth::data_source {
1719
public fun new_data_source_registry(parent_id: &mut UID, ctx: &mut TxContext) {
1820
assert!(
1921
!dynamic_field::exists_(parent_id, KEY),
20-
0 // TODO - add custom error type
22+
E_DATA_SOURCE_REGISTRY_ALREADY_EXISTS // TODO - add custom error type
2123
);
2224
dynamic_field::add(
2325
parent_id,
@@ -29,7 +31,7 @@ module pyth::data_source {
2931
public fun add(parent_id: &mut UID, data_source: DataSource) {
3032
assert!(
3133
!contains(parent_id, data_source),
32-
0 // TODO - add custom error message
34+
E_DATA_SOURCE_ALREADY_REGISTERED
3335
);
3436
set::add(
3537
dynamic_field::borrow_mut(parent_id, KEY),

target_chains/sui/contracts/sources/event.move

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ module pyth::event {
1515
timestamp: u64,
1616
}
1717

18-
public(friend) fun emit_price_feed_update(price_feed: PriceFeed, timestamp: u64) {
18+
public(friend) fun emit_price_feed_update(price_feed: PriceFeed, timestamp: u64 /* in seconds */) {
1919
event::emit(
2020
PriceFeedUpdateEvent {
2121
price_feed,

target_chains/sui/contracts/sources/governance/governance.move

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ module pyth::governance {
1515
use wormhole::vaa::{Self, VAA};
1616
use wormhole::state::{State as WormState};
1717

18+
const E_GOVERNANCE_CONTRACT_UPGRADE_CHAIN_ID_ZERO: u64 = 0;
19+
const E_INVALID_GOVERNANCE_ACTION: u64 = 1;
20+
const E_INVALID_GOVERNANCE_DATA_SOURCE: u64 = 2;
21+
const E_INVALID_GOVERNANCE_SEQUENCE_NUMBER: u64 = 3;
22+
1823
public entry fun execute_governance_instruction(
1924
pyth_state : &mut State,
2025
worm_state: &WormState,
@@ -28,7 +33,7 @@ module pyth::governance {
2833
let action = governance_instruction::get_action(&instruction);
2934
if (action == governance_action::new_contract_upgrade()) {
3035
assert!(governance_instruction::get_target_chain_id(&instruction) != 0,
31-
0); // TODO - error::governance_contract_upgrade_chain_id_zero()
36+
E_GOVERNANCE_CONTRACT_UPGRADE_CHAIN_ID_ZERO);
3237
contract_upgrade::execute(worm_state, pyth_state, governance_instruction::destroy(instruction));
3338
} else if (action == governance_action::new_set_governance_data_source()) {
3439
set_governance_data_source::execute(pyth_state, governance_instruction::destroy(instruction));
@@ -40,7 +45,7 @@ module pyth::governance {
4045
set_stale_price_threshold::execute(pyth_state, governance_instruction::destroy(instruction));
4146
} else {
4247
governance_instruction::destroy(instruction);
43-
assert!(false, 0); // TODO - error::invalid_governance_action()
48+
assert!(false, E_INVALID_GOVERNANCE_ACTION);
4449
}
4550
}
4651

@@ -59,11 +64,11 @@ module pyth::governance {
5964
data_source::new(
6065
(vaa::emitter_chain(&parsed_vaa) as u64),
6166
vaa::emitter_address(&parsed_vaa))),
62-
0); // TODO - error::invalid_governance_data_source()
67+
E_INVALID_GOVERNANCE_DATA_SOURCE);
6368

6469
// Check that the sequence number is greater than the last executed governance VAA
6570
let sequence = vaa::sequence(&parsed_vaa);
66-
assert!(sequence > state::get_last_executed_governance_sequence(pyth_state), 0); // TODO - error::invalid_governance_sequence_number()
71+
assert!(sequence > state::get_last_executed_governance_sequence(pyth_state), E_INVALID_GOVERNANCE_SEQUENCE_NUMBER);
6772
state::set_last_executed_governance_sequence(pyth_state, sequence);
6873

6974
parsed_vaa

target_chains/sui/contracts/sources/governance/governance_action.move

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ module pyth::governance_action {
66
const SET_DATA_SOURCES: u8 = 2;
77
const SET_UPDATE_FEE: u8 = 3;
88
const SET_STALE_PRICE_THRESHOLD: u8 = 4;
9+
const E_INVALID_GOVERNANCE_ACTION: u64 = 5;
910

1011
struct GovernanceAction has copy, drop {
1112
value: u8,
1213
}
1314

1415
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+
assert!(CONTRACT_UPGRADE <= value && value <= SET_STALE_PRICE_THRESHOLD, E_INVALID_GOVERNANCE_ACTION);
1617
GovernanceAction { value }
1718
}
1819

target_chains/sui/contracts/sources/governance/governance_instruction.move

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ module pyth::governance_instruction {
66
const MAGIC: vector<u8> = x"5054474d"; // "PTGM": Pyth Governance Message
77
const MODULE: u8 = 1;
88

9+
const E_INVALID_GOVERNANCE_MODULE: u64 = 0;
10+
const E_INVALID_GOVERNANCE_MAGIC_VALUE: u64 = 1;
11+
const E_TARGET_CHAIN_MISMATCH: u64 = 2;
12+
913
struct GovernanceInstruction {
1014
module_: u8,
1115
action: GovernanceAction,
@@ -14,15 +18,16 @@ module pyth::governance_instruction {
1418
}
1519

1620
fun validate(instruction: &GovernanceInstruction) {
17-
assert!(instruction.module_ == MODULE, 0); // TODO - add custom error::invalid_governance_module()
21+
assert!(instruction.module_ == MODULE, E_INVALID_GOVERNANCE_MODULE);
1822
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()
23+
assert!(target_chain_id == (wormhole::state::chain_id() as u64) || target_chain_id == 0, E_TARGET_CHAIN_MISMATCH);
2024
}
2125

2226
public fun from_byte_vec(bytes: vector<u8>): GovernanceInstruction {
2327
let cursor = cursor::new(bytes);
2428
let magic = deserialize::deserialize_vector(&mut cursor, 4);
25-
assert!(magic == MAGIC, 0); // TODO error::invalid_governance_magic_value()
29+
assert!(magic == MAGIC, E_INVALID_GOVERNANCE_MAGIC_VALUE);
30+
// "module" is a reserved keyword, so we use "module_" instead.
2631
let module_ = deserialize::deserialize_u8(&mut cursor);
2732
let action = governance_action::from_u8(deserialize::deserialize_u8(&mut cursor));
2833
let target_chain_id = deserialize::deserialize_u16(&mut cursor);

target_chains/sui/contracts/sources/governance/set_update_fee.move

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module pyth::set_update_fee {
1010
friend pyth::governance;
1111

1212
const MAX_U64: u128 = (1 << 64) - 1;
13+
const E_EXPONENT_DOES_NOT_FIT_IN_U8: u64 = 0;
1314

1415
struct SetUpdateFee {
1516
mantissa: u64,
@@ -18,7 +19,7 @@ module pyth::set_update_fee {
1819

1920
public(friend) fun execute(pyth_state: &mut State, payload: vector<u8>) {
2021
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+
assert!(exponent <= 255, E_EXPONENT_DOES_NOT_FIT_IN_U8);
2223
let fee = apply_exponent(mantissa, (exponent as u8));
2324
state::set_base_update_fee(pyth_state, fee);
2425
}

target_chains/sui/contracts/sources/price_info.move

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module pyth::price_info {
88
use pyth::price_identifier::{PriceIdentifier};
99

1010
const KEY: vector<u8> = b"price_info";
11+
const E_PRICE_INFO_REGISTRY_ALREADY_EXISTS: u64 = 0;
12+
const E_PRICE_IDENTIFIER_ALREADY_REGISTERED: u64 = 1;
1113

1214
friend pyth::pyth;
1315

@@ -30,7 +32,7 @@ module pyth::price_info {
3032
public fun new_price_info_registry(parent_id: &mut UID, ctx: &mut TxContext) {
3133
assert!(
3234
!dynamic_object_field::exists_(parent_id, KEY),
33-
0 // TODO - add custom error message
35+
E_PRICE_INFO_REGISTRY_ALREADY_EXISTS
3436
);
3537
dynamic_object_field::add(
3638
parent_id,
@@ -42,7 +44,7 @@ module pyth::price_info {
4244
public fun add(parent_id: &mut UID, price_identifier: PriceIdentifier, id: ID) {
4345
assert!(
4446
!contains(parent_id, price_identifier),
45-
0 // TODO - add custom error message
47+
E_PRICE_IDENTIFIER_ALREADY_REGISTERED
4648
);
4749
table::add(
4850
dynamic_object_field::borrow_mut(parent_id, KEY),

0 commit comments

Comments
 (0)