diff --git a/target_chains/sui/contracts/.prettierrc b/target_chains/sui/contracts/.prettierrc new file mode 100644 index 0000000000..eb37309fa6 --- /dev/null +++ b/target_chains/sui/contracts/.prettierrc @@ -0,0 +1,6 @@ +{ + "printWidth": 100, + "tabWidth": 4, + "autoGroupImports": "module", + "useModuleLabel": true +} diff --git a/target_chains/sui/contracts/Move.lock b/target_chains/sui/contracts/Move.lock index c1788d4ba6..3759c136de 100644 --- a/target_chains/sui/contracts/Move.lock +++ b/target_chains/sui/contracts/Move.lock @@ -1,36 +1,36 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 0 -manifest_digest = "320300697C11C4D84BF6ED32C1DB48A4EE830B6B44F4DFDA4E89345875C5EA11" +version = 3 +manifest_digest = "B3029D9D1CE7117C8666ADFA33DCF00F71D32B3A6D848E3A7DBCE5A792B691C2" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "Sui" }, - { name = "Wormhole" }, + { id = "Sui", name = "Sui" }, + { id = "Wormhole", name = "Wormhole" }, ] [[move.package]] -name = "MoveStdlib" +id = "MoveStdlib" source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/move-stdlib" } [[move.package]] -name = "Sui" +id = "Sui" source = { git = "https://github.com/MystenLabs/sui.git", rev = "041c5f2bae2fe52079e44b70514333532d69f4e6", subdir = "crates/sui-framework/packages/sui-framework" } dependencies = [ - { name = "MoveStdlib" }, + { id = "MoveStdlib", name = "MoveStdlib" }, ] [[move.package]] -name = "Wormhole" +id = "Wormhole" source = { git = "https://github.com/wormhole-foundation/wormhole.git", rev = "82d82bffd5a8566e4b5d94be4e4678ad55ab1f4f", subdir = "sui/wormhole" } dependencies = [ - { name = "Sui" }, + { id = "Sui", name = "Sui" }, ] [move.toolchain-version] -compiler-version = "1.27.2" -edition = "legacy" +compiler-version = "1.48.1" +edition = "2024" flavor = "sui" diff --git a/target_chains/sui/contracts/Move.toml b/target_chains/sui/contracts/Move.toml index fabdea676e..24075c5606 100644 --- a/target_chains/sui/contracts/Move.toml +++ b/target_chains/sui/contracts/Move.toml @@ -1,11 +1,9 @@ [package] name = "Pyth" version = "0.0.2" +edition = "2024" -[dependencies.Sui] -git = "https://github.com/MystenLabs/sui.git" -subdir = "crates/sui-framework/packages/sui-framework" -rev = "041c5f2bae2fe52079e44b70514333532d69f4e6" +# Sui is replaced with implicit dependency [dependencies.Wormhole] git = "https://github.com/wormhole-foundation/wormhole.git" diff --git a/target_chains/sui/contracts/migration.patch b/target_chains/sui/contracts/migration.patch new file mode 100644 index 0000000000..416cc8301c --- /dev/null +++ b/target_chains/sui/contracts/migration.patch @@ -0,0 +1,858 @@ +--- ./sources/batch_price_attestation.move ++++ ./sources/batch_price_attestation.move +@@ -24 +24 @@ +- struct BatchPriceAttestation { ++ public struct BatchPriceAttestation { +@@ -31 +31 @@ +- struct Header { ++ public struct Header { +@@ -85 +85 @@ +- let cur = cursor::new(bytes); ++ let mut cur = cursor::new(bytes); +@@ -90 +90 @@ +- let price_infos = vector::empty(); ++ let mut price_infos = vector::empty(); +@@ -92 +92 @@ +- let i = 0; ++ let mut i = 0; +@@ -140 +140 @@ +- let current_price = pyth::price::new(price, conf, expo, publish_time); ++ let mut current_price = pyth::price::new(price, conf, expo, publish_time); +@@ -148 +148 @@ +- let ema_timestamp = publish_time; ++ let mut ema_timestamp = publish_time; +@@ -168 +168 @@ +- let test = test_scenario::begin(@0x1234); ++ let mut test = test_scenario::begin(@0x1234); +@@ -181 +181 @@ +- let test = test_scenario::begin(@0x1234); ++ let mut test = test_scenario::begin(@0x1234); +--- ./sources/data_source.move ++++ ./sources/data_source.move +@@ -10,9 +10,9 @@ +- friend pyth::state; +- friend pyth::set_data_sources; +- friend pyth::pyth; +- friend pyth::set_governance_data_source; +- friend pyth::governance; +- #[test_only] +- friend pyth::pyth_tests; +- #[test_only] +- friend pyth::set_data_sources_tests; ++ /* friend pyth::state; */ ++ /* friend pyth::set_data_sources; */ ++ /* friend pyth::pyth; */ ++ /* friend pyth::set_governance_data_source; */ ++ /* friend pyth::governance; */ ++ /* #[test_only] */ ++ /* friend pyth::pyth_tests; */ ++ /* #[test_only] */ ++ /* friend pyth::set_data_sources_tests; */ +@@ -24 +24 @@ +- struct DataSource has copy, drop, store { ++ public struct DataSource has copy, drop, store { +@@ -29 +29 @@ +- public(friend) fun new_data_source_registry(parent_id: &mut UID, ctx: &mut TxContext) { ++ public(package) fun new_data_source_registry(parent_id: &mut UID, ctx: &mut TxContext) { +@@ -41 +41 @@ +- public(friend) fun add(parent_id: &mut UID, data_source: DataSource) { ++ public(package) fun add(parent_id: &mut UID, data_source: DataSource) { +@@ -52 +52 @@ +- public(friend) fun empty(parent_id: &mut UID){ ++ public(package) fun empty(parent_id: &mut UID){ +@@ -63 +63 @@ +- public(friend) fun new(emitter_chain: u64, emitter_address: ExternalAddress): DataSource { ++ public(package) fun new(emitter_chain: u64, emitter_address: ExternalAddress): DataSource { +--- ./sources/deserialize.move ++++ ./sources/deserialize.move +@@ -50 +50 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +@@ -62 +62 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +@@ -74 +74 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +@@ -86 +86 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +@@ -99 +99 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +@@ -111 +111 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +@@ -123 +123 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +@@ -135 +135 @@ +- let cursor = cursor::new(input); ++ let mut cursor = cursor::new(input); +--- ./sources/event.move ++++ ./sources/event.move +@@ -5,2 +5,2 @@ +- friend pyth::pyth; +- friend pyth::state; ++ /* friend pyth::pyth; */ ++ /* friend pyth::state; */ +@@ -8 +8 @@ +- struct PythInitializationEvent has copy, drop {} ++ public struct PythInitializationEvent has copy, drop {} +@@ -11 +11 @@ +- struct PriceFeedUpdateEvent has copy, store, drop { ++ public struct PriceFeedUpdateEvent has copy, store, drop { +@@ -18 +18 @@ +- public(friend) fun emit_price_feed_update(price_feed: PriceFeed, timestamp: u64 /* in seconds */) { ++ public(package) fun emit_price_feed_update(price_feed: PriceFeed, timestamp: u64 /* in seconds */) { +@@ -27 +27 @@ +- public(friend) fun emit_pyth_initialization_event() { ++ public(package) fun emit_pyth_initialization_event() { +--- ./sources/governance/contract_upgrade.move ++++ ./sources/governance/contract_upgrade.move +@@ -22 +22 @@ +- friend pyth::migrate; ++ /* friend pyth::migrate; */ +@@ -31 +31 @@ +- struct ContractUpgraded has drop, copy { ++ public struct ContractUpgraded has drop, copy { +@@ -36 +36 @@ +- struct UpgradeContract { ++ public struct UpgradeContract { +@@ -66 +66 @@ +- public(friend) fun take_upgrade_digest(receipt: WormholeVAAVerificationReceipt): Bytes32 { ++ public(package) fun take_upgrade_digest(receipt: WormholeVAAVerificationReceipt): Bytes32 { +@@ -104 +104 @@ +- public(friend) fun take_digest(governance_payload: vector): Bytes32 { ++ public(package) fun take_digest(governance_payload: vector): Bytes32 { +@@ -119 +119 @@ +- let cur = cursor::new(payload); ++ let mut cur = cursor::new(payload); +--- ./sources/governance/governance.move ++++ ./sources/governance/governance.move +@@ -21 +21 @@ +- struct WormholeVAAVerificationReceipt{ ++ public struct WormholeVAAVerificationReceipt{ +--- ./sources/governance/governance_action.move ++++ ./sources/governance/governance_action.move +@@ -12 +12 @@ +- struct GovernanceAction has copy, drop { ++ public struct GovernanceAction has copy, drop { +--- ./sources/governance/governance_instruction.move ++++ ./sources/governance/governance_instruction.move +@@ -13 +13 @@ +- struct GovernanceInstruction { ++ public struct GovernanceInstruction { +@@ -27 +27 @@ +- let cursor = cursor::new(bytes); ++ let mut cursor = cursor::new(bytes); +--- ./sources/governance/set_data_sources.move ++++ ./sources/governance/set_data_sources.move +@@ -12 +12 @@ +- friend pyth::governance; ++ /* friend pyth::governance; */ +@@ -14 +14 @@ +- struct DataSources { ++ public struct DataSources { +@@ -18 +18 @@ +- public(friend) fun execute( ++ public(package) fun execute( +@@ -28 +28 @@ +- let cursor = cursor::new(bytes); ++ let mut cursor = cursor::new(bytes); +@@ -31 +31 @@ +- let sources = vector::empty(); ++ let mut sources = vector::empty(); +@@ -33 +33 @@ +- let i = 0; ++ let mut i = 0; +@@ -75 +75 @@ +- let (scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -77 +77 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +--- ./sources/governance/set_fee_recipient.move ++++ ./sources/governance/set_fee_recipient.move +@@ -9 +9 @@ +- friend pyth::governance; ++ /* friend pyth::governance; */ +@@ -11 +11 @@ +- struct PythFeeRecipient { ++ public struct PythFeeRecipient { +@@ -15 +15 @@ +- public(friend) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { ++ public(package) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { +@@ -21 +21 @@ +- let cur = cursor::new(payload); ++ let mut cur = cursor::new(payload); +--- ./sources/governance/set_governance_data_source.move ++++ ./sources/governance/set_governance_data_source.move +@@ -10 +10 @@ +- friend pyth::governance; ++ /* friend pyth::governance; */ +@@ -12 +12 @@ +- struct GovernanceDataSource { ++ public struct GovernanceDataSource { +@@ -18 +18 @@ +- public(friend) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { ++ public(package) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { +@@ -25 +25 @@ +- let cursor = cursor::new(bytes); ++ let mut cursor = cursor::new(bytes); +--- ./sources/governance/set_stale_price_threshold.move ++++ ./sources/governance/set_stale_price_threshold.move +@@ -7 +7 @@ +- friend pyth::governance; ++ /* friend pyth::governance; */ +@@ -9 +9 @@ +- struct StalePriceThreshold { ++ public struct StalePriceThreshold { +@@ -13 +13 @@ +- public(friend) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { ++ public(package) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { +@@ -19 +19 @@ +- let cursor = cursor::new(bytes); ++ let mut cursor = cursor::new(bytes); +@@ -50 +50 @@ +- let (scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -52 +52 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +--- ./sources/governance/set_update_fee.move ++++ ./sources/governance/set_update_fee.move +@@ -9 +9 @@ +- friend pyth::governance; ++ /* friend pyth::governance; */ +@@ -13 +13 @@ +- struct UpdateFee { ++ public struct UpdateFee { +@@ -18 +18 @@ +- public(friend) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { ++ public(package) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { +@@ -26 +26 @@ +- let cursor = cursor::new(bytes); ++ let mut cursor = cursor::new(bytes); +@@ -63 +63 @@ +- let (scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -65 +65 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +--- ./sources/hot_potato_vector.move ++++ ./sources/hot_potato_vector.move +@@ -6,3 +6,3 @@ +- friend pyth::pyth; +- #[test_only] +- friend pyth::pyth_tests; ++ /* friend pyth::pyth; */ ++ /* #[test_only] */ ++ /* friend pyth::pyth_tests; */ +@@ -11 +11 @@ +- struct HotPotatoVector { ++ public struct HotPotatoVector { +@@ -21 +21 @@ +- public(friend) fun new(vec: vector): HotPotatoVector { ++ public(package) fun new(vec: vector): HotPotatoVector { +@@ -35 +35 @@ +- public(friend) fun borrow(potato: &HotPotatoVector, i: u64): &T { ++ public(package) fun borrow(potato: &HotPotatoVector, i: u64): &T { +@@ -39 +39 @@ +- public(friend) fun pop_back(hot_potato_vector: HotPotatoVector): (T, HotPotatoVector) { ++ public(package) fun pop_back(mut hot_potato_vector: HotPotatoVector): (T, HotPotatoVector) { +@@ -45 +45 @@ +- struct A has copy, drop { ++ public struct A has copy, drop { +@@ -51 +51 @@ +- let vec_of_a = vector::empty(); ++ let mut vec_of_a = vector::empty(); +@@ -57 +57 @@ +- let (b, hot_potato) = pop_back(hot_potato); ++ let (mut b, mut hot_potato) = pop_back(hot_potato); +--- ./sources/i64.move ++++ ./sources/i64.move +@@ -14 +14 @@ +- struct I64 has copy, drop, store { ++ public struct I64 has copy, drop, store { +@@ -19,2 +19,2 @@ +- public fun new(magnitude: u64, negative: bool): I64 { +- let max_magnitude = MAX_POSITIVE_MAGNITUDE; ++ public fun new(magnitude: u64, mut negative: bool): I64 { ++ let mut max_magnitude = MAX_POSITIVE_MAGNITUDE; +--- ./sources/merkle_tree.move ++++ ./sources/merkle_tree.move +@@ -22,2 +22,2 @@ +- let hash_prefix = vector::empty(); +- let i = 0; ++ let mut hash_prefix = vector::empty(); ++ let mut i = 0; +@@ -37,2 +37,2 @@ +- let v = vector[MERKLE_LEAF_PREFIX]; +- let i = 0; ++ let mut v = vector[MERKLE_LEAF_PREFIX]; ++ let mut i = 0; +@@ -47,2 +47,2 @@ +- childA: Bytes20, +- childB: Bytes20 ++ mut childA: Bytes20, ++ mut childB: Bytes20 +@@ -58,2 +58,2 @@ +- let v = vector[MERKLE_NODE_PREFIX]; +- let i = 0; ++ let mut v = vector[MERKLE_NODE_PREFIX]; ++ let mut i = 0; +@@ -64 +64 @@ +- let i = 0; ++ let mut i = 0; +@@ -78 +78 @@ +- let i = 0; ++ let mut i = 0; +@@ -106,2 +106,2 @@ +- let current_digest: Bytes20 = leaf_hash(&leaf_data); +- let proofSize: u8 = deserialize::deserialize_u8(encoded_proof); ++ let mut current_digest: Bytes20 = leaf_hash(&leaf_data); ++ let mut proofSize: u8 = deserialize::deserialize_u8(encoded_proof); +@@ -142 +142 @@ +- let tree = vector::empty(); ++ let mut tree = vector::empty(); +@@ -149 +149 @@ +- let i: u64 = 0; ++ let mut i: u64 = 0; +@@ -156 +156 @@ +- let j: u64 = 0; ++ let mut j: u64 = 0; +@@ -163 +163 @@ +- let k: u8 = depth; ++ let mut k: u8 = depth; +@@ -167 +167 @@ +- let i: u64 = 0; ++ let mut i: u64 = 0; +@@ -180,2 +180,2 @@ +- let proofs = vector::empty(); +- let i: u64 = 0; ++ let mut proofs = vector::empty(); ++ let mut i: u64 = 0; +@@ -183 +183 @@ +- let cur_proof = vector::empty(); ++ let mut cur_proof = vector::empty(); +@@ -185 +185 @@ +- let idx = (1 << depth) + i; ++ let mut idx = (1 << depth) + i; +@@ -202,3 +202,3 @@ +- let x = bytes20::new(x"0000000000000000000000000000000000001000"); +- let y = bytes20::new(x"0000000000000000000000000000000000000001"); +- let res = greater_than(&x, &y); ++ let mut x = bytes20::new(x"0000000000000000000000000000000000001000"); ++ let mut y = bytes20::new(x"0000000000000000000000000000000000000001"); ++ let mut res = greater_than(&x, &y); +@@ -241 +241 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +@@ -246 +246 @@ +- let proofs_cursor = cursor::new(proofs); ++ let mut proofs_cursor = cursor::new(proofs); +@@ -256 +256 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +@@ -264 +264 @@ +- let proofs_cursor = cursor::new(proofs); ++ let mut proofs_cursor = cursor::new(proofs); +@@ -276 +276 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +@@ -288 +288 @@ +- let proofs_cursor = cursor::new(proofs); ++ let mut proofs_cursor = cursor::new(proofs); +@@ -304 +304 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +@@ -309 +309 @@ +- let proofs_cursor = cursor::new(proofs); ++ let mut proofs_cursor = cursor::new(proofs); +@@ -321 +321 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +@@ -329 +329 @@ +- let proofs_cursor = cursor::new(proofs); ++ let mut proofs_cursor = cursor::new(proofs); +@@ -345 +345 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +@@ -357 +357 @@ +- let proofs_cursor = cursor::new(proofs); ++ let mut proofs_cursor = cursor::new(proofs); +@@ -376 +376 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +@@ -387 +387 @@ +- let messages = vector::empty>(); ++ let mut messages = vector::empty>(); +--- ./sources/migrate.move ++++ ./sources/migrate.move +@@ -20 +20 @@ +- struct MigrateComplete has drop, copy { ++ public struct MigrateComplete has drop, copy { +--- ./sources/price.move ++++ ./sources/price.move +@@ -12 +12 @@ +- struct Price has copy, drop, store { ++ public struct Price has copy, drop, store { +--- ./sources/price_feed.move ++++ ./sources/price_feed.move +@@ -6 +6 @@ +- struct PriceFeed has copy, drop, store { ++ public struct PriceFeed has copy, drop, store { +--- ./sources/price_identifier.move ++++ ./sources/price_identifier.move +@@ -7 +7 @@ +- struct PriceIdentifier has copy, drop, store { ++ public struct PriceIdentifier has copy, drop, store { +--- ./sources/price_info.move ++++ ./sources/price_info.move +@@ -18,2 +18,2 @@ +- friend pyth::pyth; +- friend pyth::state; ++ /* friend pyth::pyth; */ ++ /* friend pyth::state; */ +@@ -23 +23 @@ +- struct PriceInfoObject has key, store { ++ public struct PriceInfoObject has key, store { +@@ -29 +29 @@ +- struct PriceInfo has copy, drop, store { ++ public struct PriceInfo has copy, drop, store { +@@ -37 +37 @@ +- public(friend) fun new_price_info_registry(parent_id: &mut UID, ctx: &mut TxContext) { ++ public(package) fun new_price_info_registry(parent_id: &mut UID, ctx: &mut TxContext) { +@@ -49 +49 @@ +- public(friend) fun add(parent_id: &mut UID, price_identifier: PriceIdentifier, id: ID) { ++ public(package) fun add(parent_id: &mut UID, price_identifier: PriceIdentifier, id: ID) { +@@ -118 +118 @@ +- public(friend) fun new_price_info_object( ++ public(package) fun new_price_info_object( +@@ -145,2 +145,2 @@ +- let scenario = test_scenario::begin(@pyth); +- let uid = object::new(ctx(&mut scenario)); ++ let mut scenario = test_scenario::begin(@pyth); ++ let mut uid = object::new(ctx(&mut scenario)); +@@ -213 +213 @@ +- public(friend) fun update_price_info_object( ++ public(package) fun update_price_info_object( +--- ./sources/price_status.move ++++ ./sources/price_status.move +@@ -11 +11 @@ +- struct PriceStatus has copy, drop, store { ++ public struct PriceStatus has copy, drop, store { +--- ./sources/pyth.move ++++ ./sources/pyth.move +@@ -34,2 +34,2 @@ +- #[test_only] +- friend pyth::pyth_tests; ++ /* #[test_only] */ ++ /* friend pyth::pyth_tests; */ +@@ -77,2 +77,2 @@ +- let sources = vector::empty(); +- let i = 0; ++ let mut sources = vector::empty(); ++ let mut i = 0; +@@ -113 +113 @@ +- let accumulator_message_cursor = cursor::new(accumulator_message); ++ let mut accumulator_message_cursor = cursor::new(accumulator_message); +@@ -130 +130 @@ +- verified_vaas: vector, ++ mut verified_vaas: vector, +@@ -165 +165 @@ +- fun create_and_share_price_feeds_using_verified_price_infos(latest_only: &LatestOnly, pyth_state: &mut PythState, price_infos: vector, ctx: &mut TxContext){ ++ fun create_and_share_price_feeds_using_verified_price_infos(latest_only: &LatestOnly, pyth_state: &mut PythState, mut price_infos: vector, ctx: &mut TxContext){ +@@ -213 +213 @@ +- let accumulator_message_cursor = cursor::new(accumulator_message); ++ let mut accumulator_message_cursor = cursor::new(accumulator_message); +@@ -225 +225 @@ +- verified_vaas: vector, ++ mut verified_vaas: vector, +@@ -230 +230 @@ +- let price_updates = vector::empty(); ++ let mut price_updates = vector::empty(); +@@ -243 +243 @@ +- let price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(cur_vaa), clock)); ++ let mut price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(cur_vaa), clock)); +@@ -281,2 +281,2 @@ +- let i = 0; +- let found = false; ++ let mut i = 0; ++ let mut found = false; +@@ -306 +306 @@ +- public(friend) fun update_cache( ++ public(package) fun update_cache( +@@ -460,3 +460,3 @@ +- let verified_vaas_reversed = vector::empty(); +- let test_vaas = test_vaas_; +- let i = 0; ++ let mut verified_vaas_reversed = vector::empty(); ++ let mut test_vaas = test_vaas_; ++ let mut i = 0; +@@ -469 +469 @@ +- let verified_vaas = vector::empty(); ++ let mut verified_vaas = vector::empty(); +@@ -483 +483 @@ +- let cursor = cursor::new(accumulator_message); ++ let mut cursor = cursor::new(accumulator_message); +@@ -516 +516 @@ +- let scenario = test_scenario::begin(DEPLOYER); ++ let mut scenario = test_scenario::begin(DEPLOYER); +@@ -630 +630 @@ +- let i = 0; ++ let mut i = 0; +@@ -653 +653 @@ +- let (scenario, test_coins, _clock) = setup_test(500 /* stale_price_threshold */, 23 /* governance emitter chain */, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, 0); ++ let (mut scenario, test_coins, _clock) = setup_test(500 /* stale_price_threshold */, 23 /* governance emitter chain */, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, 0); +@@ -684 +684 @@ +- let (scenario, test_coins, clock) = setup_test(500 /* stale_price_threshold */, 23 /* governance emitter chain */, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], 50, 0); ++ let (mut scenario, test_coins, clock) = setup_test(500 /* stale_price_threshold */, 23 /* governance emitter chain */, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], 50, 0); +@@ -688 +688 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -707 +707 @@ +- #[expected_failure(abort_code = pyth::pyth::E_INVALID_DATA_SOURCE)] ++ #[expected_failure(abort_code = ::pyth::pyth::E_INVALID_DATA_SOURCE)] +@@ -718 +718 @@ +- let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources, BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, 50, 0); ++ let (mut scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources, BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, 50, 0); +@@ -721 +721 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -753 +753 @@ +- let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, mut test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -756 +756 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -758 +758 @@ +- let verified_vaas = get_verified_test_vaas(&worm_state, &clock); ++ let mut verified_vaas = get_verified_test_vaas(&worm_state, &clock); +@@ -777 +777 @@ +- let price_info_object_1 = take_shared(&scenario); ++ let mut price_info_object_1 = take_shared(&scenario); +@@ -794 +794 @@ +- let vec = create_price_infos_hot_potato( ++ let mut vec = create_price_infos_hot_potato( +@@ -847 +847 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -851 +851 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -853 +853 @@ +- let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_SINGLE_FEED, &clock); ++ let mut verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_SINGLE_FEED, &clock); +@@ -873 +873 @@ +- let price_info_object_1 = take_shared(&scenario); ++ let mut price_info_object_1 = take_shared(&scenario); +@@ -877 +877 @@ +- let auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( ++ let mut auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( +@@ -909 +909 @@ +- #[expected_failure(abort_code = pyth::accumulator::E_INVALID_PROOF)] ++ #[expected_failure(abort_code = ::pyth::accumulator::E_INVALID_PROOF)] +@@ -912 +912 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -916 +916 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -943 +943 @@ +- #[expected_failure(abort_code = pyth::accumulator::E_INVALID_PROOF)] ++ #[expected_failure(abort_code = ::pyth::accumulator::E_INVALID_PROOF)] +@@ -946 +946 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -950 +950 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -976 +976 @@ +- #[expected_failure(abort_code = pyth::accumulator::E_INVALID_ACCUMULATOR_PAYLOAD)] ++ #[expected_failure(abort_code = ::pyth::accumulator::E_INVALID_ACCUMULATOR_PAYLOAD)] +@@ -979 +979 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -983 +983 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -1009 +1009 @@ +- #[expected_failure(abort_code = pyth::accumulator::E_INVALID_WORMHOLE_MESSAGE)] ++ #[expected_failure(abort_code = ::pyth::accumulator::E_INVALID_WORMHOLE_MESSAGE)] +@@ -1012 +1012 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -1016 +1016 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -1047 +1047 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -1051 +1051 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -1115 +1115 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, mut coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -1119 +1119 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -1121 +1121 @@ +- let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); ++ let mut verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); +@@ -1143 +1143 @@ +- let auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( ++ let mut auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( +@@ -1150 +1150 @@ +- let idx = 0; ++ let mut idx = 0; +@@ -1155 +1155 @@ +- let price_info_object = take_shared(&scenario); ++ let mut price_info_object = take_shared(&scenario); +@@ -1179 +1179 @@ +- #[expected_failure(abort_code = pyth::pyth::E_INSUFFICIENT_FEE)] ++ #[expected_failure(abort_code = ::pyth::pyth::E_INSUFFICIENT_FEE)] +@@ -1185 +1185 @@ +- let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], DEFAULT_BASE_UPDATE_FEE, coins_to_mint); ++ let (mut scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], DEFAULT_BASE_UPDATE_FEE, coins_to_mint); +@@ -1188 +1188 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -1190 +1190 @@ +- let verified_vaas = get_verified_test_vaas(&worm_state, &clock); ++ let mut verified_vaas = get_verified_test_vaas(&worm_state, &clock); +@@ -1209 +1209 @@ +- let price_info_object_1 = take_shared(&scenario); ++ let mut price_info_object_1 = take_shared(&scenario); +@@ -1226 +1226 @@ +- let vec = create_price_infos_hot_potato( ++ let mut vec = create_price_infos_hot_potato( +@@ -1258 +1258 @@ +- let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -1261 +1261 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -1275,4 +1275,4 @@ +- let price_info_object_1 = take_shared(&scenario); +- let price_info_object_2 = take_shared(&scenario); +- let price_info_object_3 = take_shared(&scenario); +- let price_info_object_4 = take_shared(&scenario); ++ let mut price_info_object_1 = take_shared(&scenario); ++ let mut price_info_object_2 = take_shared(&scenario); ++ let mut price_info_object_3 = take_shared(&scenario); ++ let mut price_info_object_4 = take_shared(&scenario); +@@ -1282 +1282 @@ +- let price_info_object_vec = vector[ ++ let mut price_info_object_vec = vector[ +@@ -1314 +1314 @@ +- let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); ++ let (mut scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); +@@ -1317 +1317 @@ +- let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); ++ let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); +@@ -1329 +1329 @@ +- let price_info_object_1 = take_shared(&scenario); ++ let mut price_info_object_1 = take_shared(&scenario); +@@ -1353 +1353 @@ +- let latest_only = pyth::state::create_latest_only_for_test(); ++ let latest_only = ::pyth::state::create_latest_only_for_test(); +@@ -1381 +1381 @@ +- let latest_only = pyth::state::create_latest_only_for_test(); ++ let latest_only = ::pyth::state::create_latest_only_for_test(); +@@ -1409 +1409 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, vector[], ACCUMULATOR_TESTS_INITIAL_GUARDIANS, 50, 0); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, vector[], ACCUMULATOR_TESTS_INITIAL_GUARDIANS, 50, 0); +@@ -1415 +1415 @@ +- let cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); ++ let mut cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); +@@ -1421 +1421 @@ +- let i = 0; ++ let mut i = 0; +@@ -1440 +1440 @@ +- let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, vector[], ACCUMULATOR_TESTS_INITIAL_GUARDIANS, 50, 0); ++ let (mut scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, vector[], ACCUMULATOR_TESTS_INITIAL_GUARDIANS, 50, 0); +@@ -1448 +1448 @@ +- let test_accumulator_3_msgs_modified = TEST_ACCUMULATOR_3_MSGS; ++ let mut test_accumulator_3_msgs_modified = TEST_ACCUMULATOR_3_MSGS; +@@ -1451 +1451 @@ +- let cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); ++ let mut cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); +@@ -1457 +1457 @@ +- let i = 0; ++ let mut i = 0; +@@ -1481 +1481 @@ +- let i = 0; ++ let mut i = 0; +@@ -1485 +1485 @@ +- let expected: vector = vector[]; ++ let mut expected: vector = vector[]; +--- ./sources/pyth_accumulator.move ++++ ./sources/pyth_accumulator.move +@@ -25,3 +25,3 @@ +- friend pyth::pyth; +- #[test_only] +- friend pyth::pyth_tests; ++ /* friend pyth::pyth; */ ++ /* #[test_only] */ ++ /* friend pyth::pyth_tests; */ +@@ -32 +32 @@ +- public(friend) fun parse_and_verify_accumulator_message(cursor: &mut Cursor, vaa_payload: vector, clock: &Clock): vector { ++ public(package) fun parse_and_verify_accumulator_message(cursor: &mut Cursor, vaa_payload: vector, clock: &Clock): vector { +@@ -64 +64 @@ +- let msg_payload_cursor = cursor::new(message); ++ let mut msg_payload_cursor = cursor::new(message); +@@ -105,2 +105,2 @@ +- let update_size = deserialize::deserialize_u8(cursor); +- let price_info_updates: vector = vector[]; ++ let mut update_size = deserialize::deserialize_u8(cursor); ++ let mut price_info_updates: vector = vector[]; +@@ -110 +110 @@ +- let message_cur = cursor::new(message); ++ let mut message_cur = cursor::new(message); +--- ./sources/set.move ++++ ./sources/set.move +@@ -8 +8 @@ +- struct Unit has store, copy, drop {} ++ public struct Unit has store, copy, drop {} +@@ -12 +12 @@ +- struct Set has store { ++ public struct Set has store { +--- ./sources/setup.move ++++ ./sources/setup.move +@@ -10,3 +10,3 @@ +- friend pyth::pyth; +- #[test_only] +- friend pyth::pyth_tests; ++ /* friend pyth::pyth; */ ++ /* #[test_only] */ ++ /* friend pyth::pyth_tests; */ +@@ -17 +17 @@ +- struct DeployerCap has key, store { ++ public struct DeployerCap has key, store { +@@ -45 +45 @@ +- public(friend) fun init_and_share_state( ++ public(package) fun init_and_share_state( +--- ./sources/state.move ++++ ./sources/state.move +@@ -17,13 +17,13 @@ +- friend pyth::pyth; +- #[test_only] +- friend pyth::pyth_tests; +- 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; +- friend pyth::migrate; +- friend pyth::contract_upgrade; +- friend pyth::set_fee_recipient; +- friend pyth::setup; ++ /* friend pyth::pyth; */ ++ /* #[test_only] */ ++ /* friend pyth::pyth_tests; */ ++ /* 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; */ ++ /* friend pyth::migrate; */ ++ /* friend pyth::contract_upgrade; */ ++ /* friend pyth::set_fee_recipient; */ ++ /* friend pyth::setup; */ +@@ -36 +36 @@ +- struct LatestOnly has drop {} ++ public struct LatestOnly has drop {} +@@ -43 +43 @@ +- struct State has key, store { ++ public struct State has key, store { +@@ -56 +56 @@ +- public(friend) fun new( ++ public(package) fun new( +@@ -58 +58 @@ +- sources: vector, ++ mut sources: vector, +@@ -64 +64 @@ +- let uid = object::new(ctx); ++ let mut uid = object::new(ctx); +@@ -187 +187 @@ +- public(friend) fun assert_latest_only(self: &State): LatestOnly { ++ public(package) fun assert_latest_only(self: &State): LatestOnly { +@@ -196 +196 @@ +- public(friend) fun set_fee_recipient( ++ public(package) fun set_fee_recipient( +@@ -207 +207 @@ +- public(friend) fun borrow_mut_consumed_vaas( ++ public(package) fun borrow_mut_consumed_vaas( +@@ -221 +221 @@ +- public(friend) fun borrow_mut_consumed_vaas_unchecked( ++ public(package) fun borrow_mut_consumed_vaas_unchecked( +@@ -227 +227 @@ +- public(friend) fun current_package(_: &LatestOnly, self: &State): ID { ++ public(package) fun current_package(_: &LatestOnly, self: &State): ID { +@@ -231 +231 @@ +- public(friend) fun set_data_sources(_: &LatestOnly, s: &mut State, new_sources: vector) { ++ public(package) fun set_data_sources(_: &LatestOnly, s: &mut State, mut new_sources: vector) { +@@ -240 +240 @@ +- public(friend) fun register_price_info_object(_: &LatestOnly, s: &mut State, price_identifier: PriceIdentifier, id: ID) { ++ public(package) fun register_price_info_object(_: &LatestOnly, s: &mut State, price_identifier: PriceIdentifier, id: ID) { +@@ -244 +244 @@ +- public(friend) fun set_governance_data_source(_: &LatestOnly, s: &mut State, source: DataSource) { ++ public(package) fun set_governance_data_source(_: &LatestOnly, s: &mut State, source: DataSource) { +@@ -248 +248 @@ +- public(friend) fun set_last_executed_governance_sequence(_: &LatestOnly, s: &mut State, sequence: u64) { ++ public(package) fun set_last_executed_governance_sequence(_: &LatestOnly, s: &mut State, sequence: u64) { +@@ -255 +255 @@ +- public(friend) fun set_last_executed_governance_sequence_unchecked(s: &mut State, sequence: u64) { ++ public(package) fun set_last_executed_governance_sequence_unchecked(s: &mut State, sequence: u64) { +@@ -259 +259 @@ +- public(friend) fun set_base_update_fee(_: &LatestOnly, s: &mut State, fee: u64) { ++ public(package) fun set_base_update_fee(_: &LatestOnly, s: &mut State, fee: u64) { +@@ -263 +263 @@ +- public(friend) fun set_stale_price_threshold_secs(_: &LatestOnly, s: &mut State, threshold_secs: u64) { ++ public(package) fun set_stale_price_threshold_secs(_: &LatestOnly, s: &mut State, threshold_secs: u64) { +@@ -287 +287 @@ +- public(friend) fun authorize_upgrade( ++ public(package) fun authorize_upgrade( +@@ -301 +301 @@ +- public(friend) fun commit_upgrade( ++ public(package) fun commit_upgrade( +@@ -311 +311 @@ +- public(friend) fun migrate_version(self: &mut State) { ++ public(package) fun migrate_version(self: &mut State) { +@@ -322 +322 @@ +- public(friend) fun assert_authorized_digest( ++ public(package) fun assert_authorized_digest( +@@ -342 +342 @@ +- public(friend) fun migrate__v__0_1_1(self: &mut State) { ++ public(package) fun migrate__v__0_1_1(self: &mut State) { +@@ -402 +402 @@ +- struct CurrentDigest has store, drop, copy {} ++ public struct CurrentDigest has store, drop, copy {} +--- ./sources/version_control.move ++++ ./sources/version_control.move +@@ -21 +21 @@ +- public(friend) fun current_version(): V__0_1_2 { ++ public(package) fun current_version(): V__0_1_2 { +@@ -25 +25 @@ +- public(friend) fun previous_version(): V__0_1_1 { ++ public(package) fun previous_version(): V__0_1_1 { +@@ -41 +41 @@ +- struct V__0_1_2 has store, drop, copy {} ++ public struct V__0_1_2 has store, drop, copy {} +@@ -51 +51 @@ +- struct V__0_1_1 has store, drop, copy {} ++ public struct V__0_1_1 has store, drop, copy {} +@@ -54 +54 @@ +- struct V__DUMMY has store, drop, copy {} ++ public struct V__DUMMY has store, drop, copy {} +@@ -62 +62 @@ +- friend pyth::state; ++ /* friend pyth::state; */ +@@ -70 +70 @@ +- struct V__MIGRATED has store, drop, copy {} ++ public struct V__MIGRATED has store, drop, copy {} diff --git a/target_chains/sui/contracts/sources/batch_price_attestation.move b/target_chains/sui/contracts/sources/batch_price_attestation.move index 4d44703c09..a82c9ccb4e 100644 --- a/target_chains/sui/contracts/sources/batch_price_attestation.move +++ b/target_chains/sui/contracts/sources/batch_price_attestation.move @@ -1,247 +1,255 @@ -module pyth::batch_price_attestation { - use std::vector::{Self}; - use sui::clock::{Self, Clock}; - - use pyth::price_feed::{Self}; - use pyth::price_info::{Self, PriceInfo}; - use pyth::price_identifier::{Self}; - use pyth::price_status; - use pyth::deserialize::{Self}; - - use wormhole::cursor::{Self, Cursor}; - use wormhole::bytes::{Self}; - - #[test_only] - use pyth::price; - #[test_only] - use pyth::i64; - - const MAGIC: u64 = 0x50325748; // "P2WH" (Pyth2Wormhole) raw ASCII bytes - const E_INVALID_ATTESTATION_MAGIC_VALUE: u64 = 0; - const E_INVALID_BATCH_ATTESTATION_HEADER_SIZE: u64 = 1; - - /// @notice This struct is based on the legacy wormhole attester implementation in pythnet_sdk - struct BatchPriceAttestation { - header: Header, - attestation_size: u64, - attestation_count: u64, - price_infos: vector, - } +module pyth::batch_price_attestation; + +use pyth::deserialize; +use pyth::price_feed; +use pyth::price_identifier; +use pyth::price_info::{Self, PriceInfo}; +use pyth::price_status; +use sui::clock::{Self, Clock}; +use wormhole::bytes; +use wormhole::cursor::{Self, Cursor}; + +#[test_only] +use pyth::{price, i64}; + +const MAGIC: u64 = 0x50325748; // "P2WH" (Pyth2Wormhole) raw ASCII bytes +const EInvalidAttestationMagicValue: u64 = 0; +const EInvalidBatchAttestationHeaderSize: u64 = 1; + +/// @notice This struct is based on the legacy wormhole attester implementation in pythnet_sdk +public struct BatchPriceAttestation { + header: Header, + attestation_size: u64, + attestation_count: u64, + price_infos: vector, +} - struct Header { - magic: u64, - version_major: u64, - version_minor: u64, - header_size: u64, - payload_id: u8, - } +public struct Header { + magic: u64, + version_major: u64, + version_minor: u64, + header_size: u64, + payload_id: u8, +} - fun deserialize_header(cur: &mut Cursor): Header { - let magic = (deserialize::deserialize_u32(cur) as u64); - assert!(magic == MAGIC, E_INVALID_ATTESTATION_MAGIC_VALUE); - let version_major = deserialize::deserialize_u16(cur); - let version_minor = deserialize::deserialize_u16(cur); - let header_size = deserialize::deserialize_u16(cur); - let payload_id = deserialize::deserialize_u8(cur); - - assert!(header_size >= 1, E_INVALID_BATCH_ATTESTATION_HEADER_SIZE); - let unknown_header_bytes = header_size - 1; - let _unknown = bytes::take_bytes(cur, (unknown_header_bytes as u64)); - - Header { - magic, - header_size: (header_size as u64), - version_minor: (version_minor as u64), - version_major: (version_major as u64), - payload_id, - } +fun deserialize_header(cur: &mut Cursor): Header { + let magic = (deserialize::deserialize_u32(cur) as u64); + assert!(magic == MAGIC, EInvalidAttestationMagicValue); + let version_major = deserialize::deserialize_u16(cur); + let version_minor = deserialize::deserialize_u16(cur); + let header_size = deserialize::deserialize_u16(cur); + let payload_id = deserialize::deserialize_u8(cur); + + assert!(header_size >= 1, EInvalidBatchAttestationHeaderSize); + let unknown_header_bytes = header_size - 1; + let _unknown = bytes::take_bytes(cur, (unknown_header_bytes as u64)); + + Header { + magic, + header_size: (header_size as u64), + version_minor: (version_minor as u64), + version_major: (version_major as u64), + payload_id, } +} - public fun destroy(batch: BatchPriceAttestation): vector { - let BatchPriceAttestation { - header: Header { - magic: _, - version_major: _, - version_minor: _, - header_size: _, - payload_id: _, - }, - attestation_size: _, - attestation_count: _, - price_infos, - } = batch; - price_infos - } +public fun destroy(batch: BatchPriceAttestation): vector { + let BatchPriceAttestation { + header: Header { + magic: _, + version_major: _, + version_minor: _, + header_size: _, + payload_id: _, + }, + attestation_size: _, + attestation_count: _, + price_infos, + } = batch; + price_infos +} - public fun get_attestation_count(batch: &BatchPriceAttestation): u64 { - batch.attestation_count - } +public fun get_attestation_count(batch: &BatchPriceAttestation): u64 { + batch.attestation_count +} - public fun get_price_info(batch: &BatchPriceAttestation, index: u64): &PriceInfo { - vector::borrow(&batch.price_infos, index) - } +public fun get_price_info(batch: &BatchPriceAttestation, index: u64): &PriceInfo { + vector::borrow(&batch.price_infos, index) +} - public fun deserialize(bytes: vector, clock: &Clock): BatchPriceAttestation { - let cur = cursor::new(bytes); - let header = deserialize_header(&mut cur); - - let attestation_count = deserialize::deserialize_u16(&mut cur); - let attestation_size = deserialize::deserialize_u16(&mut cur); - let price_infos = vector::empty(); - - let i = 0; - while (i < attestation_count) { - let price_info = deserialize_price_info(&mut cur, clock); - vector::push_back(&mut price_infos, price_info); - - // Consume any excess bytes - let parsed_bytes = 32+32+8+8+4+8+8+1+4+4+8+8+8+8+8; - let _excess = bytes::take_bytes(&mut cur, (attestation_size - parsed_bytes as u64)); - - i = i + 1; - }; - cursor::destroy_empty(cur); - - BatchPriceAttestation { - header, - attestation_count: (attestation_count as u64), - attestation_size: (attestation_size as u64), - price_infos, - } - } +public fun deserialize(bytes: vector, clock: &Clock): BatchPriceAttestation { + let mut cur = cursor::new(bytes); + let header = deserialize_header(&mut cur); - fun deserialize_price_info(cur: &mut Cursor, clock: &Clock): PriceInfo { - - // Skip obsolete field - let _product_identifier = deserialize::deserialize_vector(cur, 32); - let price_identifier = price_identifier::from_byte_vec(deserialize::deserialize_vector(cur, 32)); - let price = deserialize::deserialize_i64(cur); - let conf = deserialize::deserialize_u64(cur); - let expo = deserialize::deserialize_i32(cur); - let ema_price = deserialize::deserialize_i64(cur); - let ema_conf = deserialize::deserialize_u64(cur); - let status = price_status::from_u64((deserialize::deserialize_u8(cur) as u64)); - - // Skip obsolete fields - let _num_publishers = deserialize::deserialize_u32(cur); - let _max_num_publishers = deserialize::deserialize_u32(cur); - - let attestation_time = deserialize::deserialize_u64(cur); - let publish_time = deserialize::deserialize_u64(cur); // - let prev_publish_time = deserialize::deserialize_u64(cur); - let prev_price = deserialize::deserialize_i64(cur); - let prev_conf = deserialize::deserialize_u64(cur); - - // Handle the case where the status is not trading. This logic will soon be moved into - // the attester. - - // If status is trading, use the current price. - // If not, use the last known trading price. - let current_price = pyth::price::new(price, conf, expo, publish_time); - if (status != price_status::new_trading()) { - current_price = pyth::price::new(prev_price, prev_conf, expo, prev_publish_time); - }; - - // If status is trading, use the timestamp of the aggregate as the timestamp for the - // EMA price. If not, the EMA will have last been updated when the aggregate last had - // trading status, so use prev_publish_time (the time when the aggregate last had trading status). - let ema_timestamp = publish_time; - if (status != price_status::new_trading()) { - ema_timestamp = prev_publish_time; - }; - - price_info::new_price_info( - attestation_time, - clock::timestamp_ms(clock) / 1000, // Divide by 1000 to get timestamp in seconds - price_feed::new( - price_identifier, - current_price, - pyth::price::new(ema_price, ema_conf, expo, ema_timestamp), - ) - ) - } + let attestation_count = deserialize::deserialize_u16(&mut cur); + let attestation_size = deserialize::deserialize_u16(&mut cur); + let mut price_infos = vector::empty(); - #[test] - #[expected_failure] - fun test_deserialize_batch_price_attestation_invalid_magic() { - use sui::test_scenario::{Self, ctx}; - let test = test_scenario::begin(@0x1234); - let test_clock = clock::create_for_testing(ctx(&mut test)); - // A batch price attestation with a magic number of 0x50325749 - let bytes = x"5032574900030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001"; - let _ = destroy(deserialize(bytes, &test_clock)); - clock::destroy_for_testing(test_clock); - test_scenario::end(test); + let mut i = 0; + while (i < attestation_count) { + let price_info = deserialize_price_info(&mut cur, clock); + vector::push_back(&mut price_infos, price_info); + + // Consume any excess bytes + let parsed_bytes = 32+32+8+8+4+8+8+1+4+4+8+8+8+8+8; + let _excess = bytes::take_bytes(&mut cur, (attestation_size - parsed_bytes as u64)); + + i = i + 1; + }; + cursor::destroy_empty(cur); + + BatchPriceAttestation { + header, + attestation_count: (attestation_count as u64), + attestation_size: (attestation_size as u64), + price_infos, } +} + +fun deserialize_price_info(cur: &mut Cursor, clock: &Clock): PriceInfo { + // Skip obsolete field + let _product_identifier = deserialize::deserialize_vector(cur, 32); + let price_identifier = price_identifier::from_byte_vec( + deserialize::deserialize_vector(cur, 32), + ); + let price = deserialize::deserialize_i64(cur); + let conf = deserialize::deserialize_u64(cur); + let expo = deserialize::deserialize_i32(cur); + let ema_price = deserialize::deserialize_i64(cur); + let ema_conf = deserialize::deserialize_u64(cur); + let status = price_status::from_u64((deserialize::deserialize_u8(cur) as u64)); + + // Skip obsolete fields + let _num_publishers = deserialize::deserialize_u32(cur); + let _max_num_publishers = deserialize::deserialize_u32(cur); + + let attestation_time = deserialize::deserialize_u64(cur); + let publish_time = deserialize::deserialize_u64(cur); // + let prev_publish_time = deserialize::deserialize_u64(cur); + let prev_price = deserialize::deserialize_i64(cur); + let prev_conf = deserialize::deserialize_u64(cur); + + // Handle the case where the status is not trading. This logic will soon be moved into + // the attester. + + // If status is trading, use the current price. + // If not, use the last known trading price. + let mut current_price = pyth::price::new(price, conf, expo, publish_time); + if (status != price_status::new_trading()) { + current_price = pyth::price::new(prev_price, prev_conf, expo, prev_publish_time); + }; + + // If status is trading, use the timestamp of the aggregate as the timestamp for the + // EMA price. If not, the EMA will have last been updated when the aggregate last had + // trading status, so use prev_publish_time (the time when the aggregate last had trading status). + let mut ema_timestamp = publish_time; + if (status != price_status::new_trading()) { + ema_timestamp = prev_publish_time; + }; + + price_info::new_price_info( + attestation_time, + clock::timestamp_ms(clock) / 1000, // Divide by 1000 to get timestamp in seconds + price_feed::new( + price_identifier, + current_price, + pyth::price::new(ema_price, ema_conf, expo, ema_timestamp), + ), + ) +} - #[test] - fun test_deserialize_batch_price_attestation() { - use sui::test_scenario::{Self, ctx}; - // Set the arrival time - let test = test_scenario::begin(@0x1234); - let test_clock = clock::create_for_testing(ctx(&mut test)); - test_scenario::next_tx(&mut test, @0x1234); - let arrival_time_in_seconds = clock::timestamp_ms(&test_clock) / 1000; - - // let arrival_time = tx_context::epoch(ctx(&mut test)); - - // A raw batch price attestation - // The first attestation has a status of UNKNOWN - let bytes = x"5032574800030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001"; - - let expected = BatchPriceAttestation { - header: Header { - magic: 0x50325748, - version_major: 3, - version_minor: 0, - payload_id: 2, - header_size: 1, - }, - attestation_count: 4, - attestation_size: 149, - price_infos: vector[ - price_info::new_price_info( - 1663680747, - arrival_time_in_seconds, - price_feed::new( - price_identifier::from_byte_vec(x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1"), - price::new(i64::new(1557, false), 7, i64::new(5, true), 1663680740), - price::new(i64::new(1500, false), 3, i64::new(5, true), 1663680740), - ) ), - price_info::new_price_info( - 1663680747, - arrival_time_in_seconds, - price_feed::new( - price_identifier::from_byte_vec(x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe"), - price::new(i64::new(1050, false), 3, i64::new(5, true), 1663680745), - price::new(i64::new(1483, false), 3, i64::new(5, true), 1663680745), - ) ), - price_info::new_price_info( - 1663680747, - arrival_time_in_seconds, - price_feed::new( - price_identifier::from_byte_vec(x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d"), - price::new(i64::new(1010, false), 2, i64::new(5, true), 1663680745), - price::new(i64::new(1511, false), 3, i64::new(5, true), 1663680745), - ) ), - price_info::new_price_info( - 1663680747, - arrival_time_in_seconds, - price_feed::new( - price_identifier::from_byte_vec(x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8"), - price::new(i64::new(1739, false), 1, i64::new(5, true), 1663680745), - price::new(i64::new(1508, false), 3, i64::new(5, true), 1663680745), - ) +#[test, expected_failure] +fun test_deserialize_batch_price_attestation_invalid_magic() { + use sui::test_scenario::{Self, ctx}; + let mut test = test_scenario::begin(@0x1234); + let test_clock = clock::create_for_testing(ctx(&mut test)); + // A batch price attestation with a magic number of 0x50325749 + let bytes = + x"5032574900030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001"; + let _ = destroy(deserialize(bytes, &test_clock)); + clock::destroy_for_testing(test_clock); + test_scenario::end(test); +} + +#[test] +fun test_deserialize_batch_price_attestation() { + use sui::test_scenario::{Self, ctx}; + // Set the arrival time + let mut test = test_scenario::begin(@0x1234); + let test_clock = clock::create_for_testing(ctx(&mut test)); + test_scenario::next_tx(&mut test, @0x1234); + let arrival_time_in_seconds = clock::timestamp_ms(&test_clock) / 1000; + + // let arrival_time = tx_context::epoch(ctx(&mut test)); + + // A raw batch price attestation + // The first attestation has a status of UNKNOWN + let bytes = + x"5032574800030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001"; + + let expected = BatchPriceAttestation { + header: Header { + magic: 0x50325748, + version_major: 3, + version_minor: 0, + payload_id: 2, + header_size: 1, + }, + attestation_count: 4, + attestation_size: 149, + price_infos: vector[ + price_info::new_price_info( + 1663680747, + arrival_time_in_seconds, + price_feed::new( + price_identifier::from_byte_vec( + x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1", + ), + price::new(i64::new(1557, false), 7, i64::new(5, true), 1663680740), + price::new(i64::new(1500, false), 3, i64::new(5, true), 1663680740), + ), + ), + price_info::new_price_info( + 1663680747, + arrival_time_in_seconds, + price_feed::new( + price_identifier::from_byte_vec( + x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe", + ), + price::new(i64::new(1050, false), 3, i64::new(5, true), 1663680745), + price::new(i64::new(1483, false), 3, i64::new(5, true), 1663680745), ), - ], - }; + ), + price_info::new_price_info( + 1663680747, + arrival_time_in_seconds, + price_feed::new( + price_identifier::from_byte_vec( + x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d", + ), + price::new(i64::new(1010, false), 2, i64::new(5, true), 1663680745), + price::new(i64::new(1511, false), 3, i64::new(5, true), 1663680745), + ), + ), + price_info::new_price_info( + 1663680747, + arrival_time_in_seconds, + price_feed::new( + price_identifier::from_byte_vec( + x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8", + ), + price::new(i64::new(1739, false), 1, i64::new(5, true), 1663680745), + price::new(i64::new(1508, false), 3, i64::new(5, true), 1663680745), + ), + ), + ], + }; - let deserialized = deserialize(bytes, &test_clock); + let deserialized = deserialize(bytes, &test_clock); - assert!(&expected == &deserialized, 1); - destroy(expected); - destroy(deserialized); - clock::destroy_for_testing(test_clock); - test_scenario::end(test); - } + assert!(&expected == &deserialized, 1); + destroy(expected); + destroy(deserialized); + clock::destroy_for_testing(test_clock); + test_scenario::end(test); } diff --git a/target_chains/sui/contracts/sources/data_source.move b/target_chains/sui/contracts/sources/data_source.move index d4814cd554..1f26ccc0c8 100644 --- a/target_chains/sui/contracts/sources/data_source.move +++ b/target_chains/sui/contracts/sources/data_source.move @@ -1,85 +1,72 @@ -module pyth::data_source { - use sui::dynamic_field::{Self}; - use sui::object::{UID}; - use sui::tx_context::{TxContext}; +module pyth::data_source; - use pyth::set::{Self}; +use pyth::set; +use sui::dynamic_field; +use wormhole::external_address::ExternalAddress; - use wormhole::external_address::ExternalAddress; +const KEY: vector = b"data_sources"; - friend pyth::state; - friend pyth::set_data_sources; - friend pyth::pyth; - friend pyth::set_governance_data_source; - friend pyth::governance; - #[test_only] - friend pyth::pyth_tests; - #[test_only] - friend pyth::set_data_sources_tests; +const EDataSourceRegistryAlreadyExists: u64 = 0; +const EDataSourceAlreadyRegistered: u64 = 1; - const KEY: vector = b"data_sources"; - const E_DATA_SOURCE_REGISTRY_ALREADY_EXISTS: u64 = 0; - const E_DATA_SOURCE_ALREADY_REGISTERED: u64 = 1; - - struct DataSource has copy, drop, store { - emitter_chain: u64, - emitter_address: ExternalAddress, - } +public struct DataSource has copy, drop, store { + emitter_chain: u64, + emitter_address: ExternalAddress, +} - public(friend) fun new_data_source_registry(parent_id: &mut UID, ctx: &mut TxContext) { - assert!( - !dynamic_field::exists_(parent_id, KEY), - E_DATA_SOURCE_REGISTRY_ALREADY_EXISTS // TODO - add custom error type - ); - dynamic_field::add( - parent_id, - KEY, - set::new(ctx) - ) - } +public(package) fun new_data_source_registry(parent_id: &mut UID, ctx: &mut TxContext) { + assert!( + !dynamic_field::exists_(parent_id, KEY), + EDataSourceRegistryAlreadyExists, // TODO - add custom error type + ); + dynamic_field::add( + parent_id, + KEY, + set::new(ctx), + ) +} - public(friend) fun add(parent_id: &mut UID, data_source: DataSource) { - assert!( - !contains(parent_id, data_source), - E_DATA_SOURCE_ALREADY_REGISTERED - ); - set::add( - dynamic_field::borrow_mut(parent_id, KEY), - data_source - ) - } +public(package) fun add(parent_id: &mut UID, data_source: DataSource) { + assert!(!contains(parent_id, data_source), EDataSourceAlreadyRegistered); + set::add( + dynamic_field::borrow_mut(parent_id, KEY), + data_source, + ) +} - public(friend) fun empty(parent_id: &mut UID){ - set::empty( - dynamic_field::borrow_mut(parent_id, KEY) - ) - } +public(package) fun empty(parent_id: &mut UID) { + set::empty( + dynamic_field::borrow_mut(parent_id, KEY), + ) +} - public fun contains(parent_id: &UID, data_source: DataSource): bool { - let ref = dynamic_field::borrow(parent_id, KEY); - set::contains(ref, data_source) - } +public fun contains(parent_id: &UID, data_source: DataSource): bool { + let ref = dynamic_field::borrow(parent_id, KEY); + set::contains(ref, data_source) +} - public(friend) fun new(emitter_chain: u64, emitter_address: ExternalAddress): DataSource { - DataSource { - emitter_chain, - emitter_address, - } +public(package) fun new(emitter_chain: u64, emitter_address: ExternalAddress): DataSource { + DataSource { + emitter_chain, + emitter_address, } +} - public fun emitter_chain(data_source: &DataSource): u64{ - data_source.emitter_chain - } +public fun emitter_chain(data_source: &DataSource): u64 { + data_source.emitter_chain +} - public fun emitter_address(data_source: &DataSource): ExternalAddress{ - data_source.emitter_address - } +public fun emitter_address(data_source: &DataSource): ExternalAddress { + data_source.emitter_address +} - #[test_only] - public fun new_data_source_for_test(emitter_chain: u64, emitter_address: ExternalAddress): DataSource { - DataSource { - emitter_chain, - emitter_address, - } +#[test_only] +public fun new_data_source_for_test( + emitter_chain: u64, + emitter_address: ExternalAddress, +): DataSource { + DataSource { + emitter_chain, + emitter_address, } } diff --git a/target_chains/sui/contracts/sources/deserialize.move b/target_chains/sui/contracts/sources/deserialize.move index 310107d6ea..8dda6d9529 100644 --- a/target_chains/sui/contracts/sources/deserialize.move +++ b/target_chains/sui/contracts/sources/deserialize.move @@ -1,143 +1,141 @@ - module pyth::deserialize { - use wormhole::bytes::{Self}; - use wormhole::cursor::{Cursor}; - use pyth::i64::{Self, I64}; - #[test_only] - use wormhole::cursor::{take_rest}; - - #[test_only] - use wormhole::cursor::{Self}; - - public fun deserialize_vector(cur: &mut Cursor, n: u64): vector { - bytes::take_bytes(cur, n) - } +module pyth::deserialize; - public fun deserialize_u8(cur: &mut Cursor): u8 { - bytes::take_u8(cur) - } +use pyth::i64::{Self, I64}; +use wormhole::bytes; +use wormhole::cursor::Cursor; - public fun deserialize_u16(cur: &mut Cursor): u16 { - bytes::take_u16_be(cur) - } +#[test_only] +use wormhole::cursor::{Self, take_rest}; - public fun deserialize_u32(cur: &mut Cursor): u32 { - bytes::take_u32_be(cur) - } +public fun deserialize_vector(cur: &mut Cursor, n: u64): vector { + bytes::take_bytes(cur, n) +} - public fun deserialize_i32(cur: &mut Cursor): I64 { - let deserialized = deserialize_u32(cur); - // If negative, pad the value - let negative = (deserialized >> 31) == 1; - if (negative) { - let padded = (0xFFFFFFFF << 32) + (deserialized as u64); - i64::from_u64((padded as u64)) - } else { - i64::from_u64((deserialized as u64)) - } - } +public fun deserialize_u8(cur: &mut Cursor): u8 { + bytes::take_u8(cur) +} - public fun deserialize_u64(cur: &mut Cursor): u64 { - bytes::take_u64_be(cur) - } +public fun deserialize_u16(cur: &mut Cursor): u16 { + bytes::take_u16_be(cur) +} + +public fun deserialize_u32(cur: &mut Cursor): u32 { + bytes::take_u32_be(cur) +} - public fun deserialize_i64(cur: &mut Cursor): I64 { - i64::from_u64(deserialize_u64(cur)) +public fun deserialize_i32(cur: &mut Cursor): I64 { + let deserialized = deserialize_u32(cur); + // If negative, pad the value + let negative = (deserialized >> 31) == 1; + if (negative) { + let padded = (0xFFFFFFFF << 32) + (deserialized as u64); + i64::from_u64((padded as u64)) + } else { + i64::from_u64((deserialized as u64)) } +} - #[test] - fun test_deserialize_u8() { - let input = x"48258963"; - let cursor = cursor::new(input); +public fun deserialize_u64(cur: &mut Cursor): u64 { + bytes::take_u64_be(cur) +} - let result = deserialize_u8(&mut cursor); - assert!(result == 0x48, 1); +public fun deserialize_i64(cur: &mut Cursor): I64 { + i64::from_u64(deserialize_u64(cur)) +} - let rest = take_rest(cursor); - assert!(rest == x"258963", 1); - } +#[test] +fun test_deserialize_u8() { + let input = x"48258963"; + let mut cursor = cursor::new(input); - #[test] - fun test_deserialize_u16() { - let input = x"48258963"; - let cursor = cursor::new(input); + let result = deserialize_u8(&mut cursor); + assert!(result == 0x48, 1); - let result = deserialize_u16(&mut cursor); - assert!(result == 0x4825, 1); + let rest = take_rest(cursor); + assert!(rest == x"258963", 1); +} - let rest = take_rest(cursor); - assert!(rest == x"8963", 1); - } +#[test] +fun test_deserialize_u16() { + let input = x"48258963"; + let mut cursor = cursor::new(input); - #[test] - fun test_deserialize_u32() { - let input = x"4825896349741695"; - let cursor = cursor::new(input); + let result = deserialize_u16(&mut cursor); + assert!(result == 0x4825, 1); - let result = deserialize_u32(&mut cursor); - assert!(result == 0x48258963, 1); + let rest = take_rest(cursor); + assert!(rest == x"8963", 1); +} - let rest = take_rest(cursor); - assert!(rest == x"49741695", 1); - } +#[test] +fun test_deserialize_u32() { + let input = x"4825896349741695"; + let mut cursor = cursor::new(input); - #[test] - fun test_deserialize_i32_positive() { - let input = x"4825896349741695"; - let cursor = cursor::new(input); + let result = deserialize_u32(&mut cursor); + assert!(result == 0x48258963, 1); - let result = deserialize_i32(&mut cursor); - assert!(result == i64::from_u64(0x48258963), 1); + let rest = take_rest(cursor); + assert!(rest == x"49741695", 1); +} - let rest = take_rest(cursor); - assert!(rest == x"49741695", 1); - } +#[test] +fun test_deserialize_i32_positive() { + let input = x"4825896349741695"; + let mut cursor = cursor::new(input); - #[test] - fun test_deserialize_i32_negative() { - let input = x"FFFFFDC349741695"; + let result = deserialize_i32(&mut cursor); + assert!(result == i64::from_u64(0x48258963), 1); - let cursor = cursor::new(input); + let rest = take_rest(cursor); + assert!(rest == x"49741695", 1); +} - let result = deserialize_i32(&mut cursor); - assert!(result == i64::from_u64(0xFFFFFFFFFFFFFDC3), 1); +#[test] +fun test_deserialize_i32_negative() { + let input = x"FFFFFDC349741695"; - let rest = take_rest(cursor); - assert!(rest == x"49741695", 1); - } + let mut cursor = cursor::new(input); - #[test] - fun test_deserialize_u64() { - let input = x"48258963497416957497253486"; - let cursor = cursor::new(input); + let result = deserialize_i32(&mut cursor); + assert!(result == i64::from_u64(0xFFFFFFFFFFFFFDC3), 1); - let result = deserialize_u64(&mut cursor); - assert!(result == 0x4825896349741695, 1); + let rest = take_rest(cursor); + assert!(rest == x"49741695", 1); +} - let rest = take_rest(cursor); - assert!(rest == x"7497253486", 1); - } +#[test] +fun test_deserialize_u64() { + let input = x"48258963497416957497253486"; + let mut cursor = cursor::new(input); - #[test] - fun test_deserialize_i64_positive() { - let input = x"48258963497416957497253486"; - let cursor = cursor::new(input); + let result = deserialize_u64(&mut cursor); + assert!(result == 0x4825896349741695, 1); - let result = deserialize_i64(&mut cursor); - assert!(result == i64::from_u64(0x4825896349741695), 1); + let rest = take_rest(cursor); + assert!(rest == x"7497253486", 1); +} - let rest = take_rest(cursor); - assert!(rest == x"7497253486", 1); - } +#[test] +fun test_deserialize_i64_positive() { + let input = x"48258963497416957497253486"; + let mut cursor = cursor::new(input); - #[test] - fun test_deserialize_i64_negative() { - let input = x"FFFFFFFFFFFFFDC37497253486"; - let cursor = cursor::new(input); + let result = deserialize_i64(&mut cursor); + assert!(result == i64::from_u64(0x4825896349741695), 1); - let result = deserialize_i64(&mut cursor); - assert!(result == i64::from_u64(0xFFFFFFFFFFFFFDC3), 1); + let rest = take_rest(cursor); + assert!(rest == x"7497253486", 1); +} - let rest = take_rest(cursor); - assert!(rest == x"7497253486", 1); - } +#[test] +fun test_deserialize_i64_negative() { + let input = x"FFFFFFFFFFFFFDC37497253486"; + let mut cursor = cursor::new(input); + + let result = deserialize_i64(&mut cursor); + assert!(result == i64::from_u64(0xFFFFFFFFFFFFFDC3), 1); + + let rest = take_rest(cursor); + assert!(rest == x"7497253486", 1); } diff --git a/target_chains/sui/contracts/sources/event.move b/target_chains/sui/contracts/sources/event.move index a40c5d218b..78b47e136a 100644 --- a/target_chains/sui/contracts/sources/event.move +++ b/target_chains/sui/contracts/sources/event.move @@ -1,33 +1,25 @@ -module pyth::event { - use sui::event::{Self}; - use pyth::price_feed::{PriceFeed}; +module pyth::event; - friend pyth::pyth; - friend pyth::state; +use pyth::price_feed::PriceFeed; +use sui::event; - struct PythInitializationEvent has copy, drop {} +public struct PythInitializationEvent has copy, drop {} - /// Signifies that a price feed has been updated - struct PriceFeedUpdateEvent has copy, store, drop { - /// Value of the price feed - price_feed: PriceFeed, - /// Timestamp of the update - timestamp: u64, - } - - public(friend) fun emit_price_feed_update(price_feed: PriceFeed, timestamp: u64 /* in seconds */) { - event::emit( - PriceFeedUpdateEvent { - price_feed, - timestamp, - } - ); - } +/// Signifies that a price feed has been updated +public struct PriceFeedUpdateEvent has copy, drop, store { + /// Value of the price feed + price_feed: PriceFeed, + /// Timestamp of the update + timestamp: u64, +} - public(friend) fun emit_pyth_initialization_event() { - event::emit( - PythInitializationEvent {} - ); - } +public(package) fun emit_price_feed_update(price_feed: PriceFeed, timestamp: u64 /* in seconds */) { + event::emit(PriceFeedUpdateEvent { + price_feed, + timestamp, + }); +} +public(package) fun emit_pyth_initialization_event() { + event::emit(PythInitializationEvent {}); } diff --git a/target_chains/sui/contracts/sources/governance/contract_upgrade.move b/target_chains/sui/contracts/sources/governance/contract_upgrade.move index cc5aae0820..33df8081b9 100644 --- a/target_chains/sui/contracts/sources/governance/contract_upgrade.move +++ b/target_chains/sui/contracts/sources/governance/contract_upgrade.move @@ -7,139 +7,127 @@ /// 2. Authorize upgrade. /// 3. Upgrade. /// 4. Commit upgrade. -module pyth::contract_upgrade { - use sui::event::{Self}; - use sui::object::{ID}; - use sui::package::{UpgradeReceipt, UpgradeTicket}; - use wormhole::bytes32::{Self, Bytes32}; - use wormhole::cursor::{Self}; - - use pyth::state::{Self, State}; - use pyth::governance_instruction::{Self}; - use pyth::governance_action::{Self}; - use pyth::governance::{Self, WormholeVAAVerificationReceipt}; - - friend pyth::migrate; - - /// Digest is all zeros. - const E_DIGEST_ZERO_BYTES: u64 = 0; - const E_GOVERNANCE_ACTION_MUST_BE_CONTRACT_UPGRADE: u64 = 1; - const E_GOVERNANCE_CONTRACT_UPGRADE_CHAIN_ID_ZERO: u64 = 2; - const E_CANNOT_EXECUTE_GOVERNANCE_ACTION_WITH_OBSOLETE_SEQUENCE_NUMBER: u64 = 3; - - // Event reflecting package upgrade. - struct ContractUpgraded has drop, copy { - old_contract: ID, - new_contract: ID - } - - struct UpgradeContract { - digest: Bytes32 - } - - /// Redeem governance VAA to issue an `UpgradeTicket` for the upgrade given - /// a contract upgrade VAA. This governance message is only relevant for Sui - /// because a contract upgrade is only relevant to one particular network - /// (in this case Sui), whose build digest is encoded in this message. - public fun authorize_upgrade( - pyth_state: &mut State, - receipt: WormholeVAAVerificationReceipt, - ): UpgradeTicket { - - // Get the sequence number of the governance VAA that was used to - // generate the receipt. - let sequence = governance::take_sequence(&receipt); - - // Require that new sequence number is greater than last executed sequence number. - assert!(sequence > state::get_last_executed_governance_sequence(pyth_state), - E_CANNOT_EXECUTE_GOVERNANCE_ACTION_WITH_OBSOLETE_SEQUENCE_NUMBER); - - // Update latest executed sequence number to current one. - state::set_last_executed_governance_sequence_unchecked(pyth_state, sequence); - - let digest = take_upgrade_digest(receipt); - // Proceed with processing new implementation version. - handle_upgrade_contract(pyth_state, digest) - } - - - public(friend) fun take_upgrade_digest(receipt: WormholeVAAVerificationReceipt): Bytes32 { - let payload = governance::take_payload(&receipt); - - let instruction = governance_instruction::from_byte_vec(payload); - - // Get the governance action. - let action = governance_instruction::get_action(&instruction); - - assert!(action == governance_action::new_contract_upgrade(), - E_GOVERNANCE_ACTION_MUST_BE_CONTRACT_UPGRADE); - - assert!(governance_instruction::get_target_chain_id(&instruction) != 0, - E_GOVERNANCE_CONTRACT_UPGRADE_CHAIN_ID_ZERO); - - governance::destroy(receipt); - // upgrade_payload contains a 32-byte digest - let upgrade_payload = governance_instruction::destroy(instruction); - - take_digest(upgrade_payload) - } - - /// Finalize the upgrade that ran to produce the given `receipt`. This - /// method invokes `state::commit_upgrade` which interacts with - /// `sui::package`. - public fun commit_upgrade( - self: &mut State, - receipt: UpgradeReceipt, - ) { - let (old_contract, new_contract) = state::commit_upgrade(self, receipt); - - // Emit an event reflecting package ID change. - event::emit(ContractUpgraded { old_contract, new_contract }); - } - - /// Privileged method only to be used by this module and `migrate` module. - /// - /// During migration, we make sure that the digest equals what we expect by - /// passing in the same VAA used to upgrade the package. - public(friend) fun take_digest(governance_payload: vector): Bytes32 { - // Deserialize the payload as the build digest. - let UpgradeContract { digest } = deserialize(governance_payload); - - digest - } - - fun handle_upgrade_contract( - pyth_state: &mut State, - digest: Bytes32 - ): UpgradeTicket { - state::authorize_upgrade(pyth_state, digest) - } - - fun deserialize(payload: vector): UpgradeContract { - let cur = cursor::new(payload); - let digest = bytes32::take_bytes(&mut cur); - assert!(bytes32::is_nonzero(&digest), E_DIGEST_ZERO_BYTES); - - // there might be additional appended to payload in the future, - // which is why we don't cursor::destroy_empty(&mut cur) - cursor::take_rest(cur); - UpgradeContract { digest } - } - - #[test_only] - /// Specific governance payload ID (action) to complete upgrading the - /// contract. - /// TODO: is it okay for the contract upgrade action for Pyth to be 0? Or should it be 1? - const CONTRACT_UPGRADE: u8 = 0; - - - #[test_only] - public fun action(): u8 { - CONTRACT_UPGRADE - } +module pyth::contract_upgrade; + +use pyth::governance::{Self, WormholeVAAVerificationReceipt}; +use pyth::governance_action; +use pyth::governance_instruction; +use pyth::state::{Self, State}; +use sui::event; +use sui::package::{UpgradeReceipt, UpgradeTicket}; +use wormhole::bytes32::{Self, Bytes32}; +use wormhole::cursor; + +/// Digest is all zeros. +const EDigestZeroBytes: u64 = 0; +const EGovernanceActionMustBeContractUpgrade: u64 = 1; +const EGovernanceContractUpgradeChainIdZero: u64 = 2; +const ECannotExecuteGovernanceActionWithObsoleteSequenceNumber: u64 = 3; + +// Event reflecting package upgrade. +public struct ContractUpgraded has copy, drop { + old_contract: ID, + new_contract: ID, } +public struct UpgradeContract { + digest: Bytes32, +} + +/// Redeem governance VAA to issue an `UpgradeTicket` for the upgrade given +/// a contract upgrade VAA. This governance message is only relevant for Sui +/// because a contract upgrade is only relevant to one particular network +/// (in this case Sui), whose build digest is encoded in this message. +public fun authorize_upgrade( + pyth_state: &mut State, + receipt: WormholeVAAVerificationReceipt, +): UpgradeTicket { + // Get the sequence number of the governance VAA that was used to + // generate the receipt. + let sequence = governance::take_sequence(&receipt); + + // Require that new sequence number is greater than last executed sequence number. + assert!( + sequence > state::get_last_executed_governance_sequence(pyth_state), + ECannotExecuteGovernanceActionWithObsoleteSequenceNumber, + ); + + // Update latest executed sequence number to current one. + state::set_last_executed_governance_sequence_unchecked(pyth_state, sequence); + + let digest = take_upgrade_digest(receipt); + // Proceed with processing new implementation version. + handle_upgrade_contract(pyth_state, digest) +} + +public(package) fun take_upgrade_digest(receipt: WormholeVAAVerificationReceipt): Bytes32 { + let payload = governance::take_payload(&receipt); + + let instruction = governance_instruction::from_byte_vec(payload); + + // Get the governance action. + let action = governance_instruction::get_action(&instruction); + + assert!( + action == governance_action::new_contract_upgrade(), + EGovernanceActionMustBeContractUpgrade, + ); + + assert!( + governance_instruction::get_target_chain_id(&instruction) != 0, + EGovernanceContractUpgradeChainIdZero, + ); + + governance::destroy(receipt); + // upgrade_payload contains a 32-byte digest + let upgrade_payload = governance_instruction::destroy(instruction); + + take_digest(upgrade_payload) +} + +/// Finalize the upgrade that ran to produce the given `receipt`. This +/// method invokes `state::commit_upgrade` which interacts with +/// `sui::package`. +public fun commit_upgrade(self: &mut State, receipt: UpgradeReceipt) { + let (old_contract, new_contract) = state::commit_upgrade(self, receipt); + + // Emit an event reflecting package ID change. + event::emit(ContractUpgraded { old_contract, new_contract }); +} + +/// Privileged method only to be used by this module and `migrate` module. +/// +/// During migration, we make sure that the digest equals what we expect by +/// passing in the same VAA used to upgrade the package. +public(package) fun take_digest(governance_payload: vector): Bytes32 { + // Deserialize the payload as the build digest. + let UpgradeContract { digest } = deserialize(governance_payload); + + digest +} + +fun handle_upgrade_contract(pyth_state: &mut State, digest: Bytes32): UpgradeTicket { + state::authorize_upgrade(pyth_state, digest) +} + +fun deserialize(payload: vector): UpgradeContract { + let mut cur = cursor::new(payload); + let digest = bytes32::take_bytes(&mut cur); + assert!(bytes32::is_nonzero(&digest), EDigestZeroBytes); + + // there might be additional appended to payload in the future, + // which is why we don't cursor::destroy_empty(&mut cur) + cursor::take_rest(cur); + UpgradeContract { digest } +} + +#[test_only] +/// Specific governance payload ID (action) to complete upgrading the +/// contract. +/// TODO: is it okay for the contract upgrade action for Pyth to be 0? Or should it be 1? +const CONTRACT_UPGRADE: u8 = 0; + #[test_only] -module pyth::upgrade_contract_tests { - // TODO +public fun action(): u8 { + CONTRACT_UPGRADE } diff --git a/target_chains/sui/contracts/sources/governance/governance.move b/target_chains/sui/contracts/sources/governance/governance.move index c09b031a6b..a2f668d4cd 100644 --- a/target_chains/sui/contracts/sources/governance/governance.move +++ b/target_chains/sui/contracts/sources/governance/governance.move @@ -1,116 +1,137 @@ -module pyth::governance { - use pyth::governance_instruction; - use pyth::governance_action; - use pyth::set_governance_data_source; - use pyth::set_data_sources; - use pyth::set_stale_price_threshold; - use pyth::set_fee_recipient; - use pyth::state::{Self, State}; - use pyth::set_update_fee; - - use wormhole::vaa::{Self, VAA}; - use wormhole::bytes32::Bytes32; - - const E_INVALID_GOVERNANCE_ACTION: u64 = 0; - const E_MUST_USE_CONTRACT_UPGRADE_MODULE_TO_DO_UPGRADES: u64 = 1; - const E_CANNOT_EXECUTE_GOVERNANCE_ACTION_WITH_OBSOLETE_SEQUENCE_NUMBER: u64 = 2; - const E_INVALID_GOVERNANCE_DATA_SOURCE: u64 = 4; - - // this struct does not have the store or key ability so it must be - // used in the same txn chain in which it is created - struct WormholeVAAVerificationReceipt{ - payload: vector, - digest: Bytes32, - sequence: u64, // used for replay protection - } +module pyth::governance; + +use pyth::governance_action; +use pyth::governance_instruction; +use pyth::set_data_sources; +use pyth::set_fee_recipient; +use pyth::set_governance_data_source; +use pyth::set_stale_price_threshold; +use pyth::set_update_fee; +use pyth::state::{Self, State}; +use wormhole::bytes32::Bytes32; +use wormhole::vaa::{Self, VAA}; + +const EInvalidGovernanceAction: u64 = 0; +const EMustUseContractUpgradeModuleToDoUpgrades: u64 = 1; +const ECannotExecuteGovernanceActionWithObsoleteSequenceNumber: u64 = 2; +const EInvalidGovernanceDataSource: u64 = 4; + +// this struct does not have the store or key ability so it must be +// used in the same txn chain in which it is created +public struct WormholeVAAVerificationReceipt { + payload: vector, + digest: Bytes32, + sequence: u64, // used for replay protection +} - public fun take_payload(receipt: &WormholeVAAVerificationReceipt): vector { - receipt.payload - } +public fun take_payload(receipt: &WormholeVAAVerificationReceipt): vector { + receipt.payload +} - public fun take_digest(receipt: &WormholeVAAVerificationReceipt): Bytes32 { - receipt.digest - } +public fun take_digest(receipt: &WormholeVAAVerificationReceipt): Bytes32 { + receipt.digest +} - public fun take_sequence(receipt: &WormholeVAAVerificationReceipt): u64 { - receipt.sequence - } +public fun take_sequence(receipt: &WormholeVAAVerificationReceipt): u64 { + receipt.sequence +} - public fun destroy(receipt: WormholeVAAVerificationReceipt) { - let WormholeVAAVerificationReceipt{payload: _, digest: _, sequence: _} = receipt; - } +public fun destroy(receipt: WormholeVAAVerificationReceipt) { + let WormholeVAAVerificationReceipt { .. } = receipt; +} - // We define a custom verify_vaa function instead of using wormhole::governance_message::verify_vaa - // because that function makes extra assumptions about the VAA payload headers. Pyth uses a - // different header format compared to Wormhole, so - public fun verify_vaa( - pyth_state: &State, - verified_vaa: VAA, - ): WormholeVAAVerificationReceipt { - state::assert_latest_only(pyth_state); - - let vaa_data_source = pyth::data_source::new((vaa::emitter_chain(&verified_vaa) as u64), vaa::emitter_address(&verified_vaa)); - - // The emitter chain and address must correspond to the Pyth governance emitter chain and contract. - assert!( - pyth::state::is_valid_governance_data_source(pyth_state, vaa_data_source), - E_INVALID_GOVERNANCE_DATA_SOURCE - ); +// We define a custom verify_vaa function instead of using wormhole::governance_message::verify_vaa +// because that function makes extra assumptions about the VAA payload headers. Pyth uses a +// different header format compared to Wormhole, so +public fun verify_vaa(pyth_state: &State, verified_vaa: VAA): WormholeVAAVerificationReceipt { + state::assert_latest_only(pyth_state); - let digest = vaa::digest(&verified_vaa); + let vaa_data_source = pyth::data_source::new( + (vaa::emitter_chain(&verified_vaa) as u64), + vaa::emitter_address(&verified_vaa), + ); - let sequence = vaa::sequence(&verified_vaa); + // The emitter chain and address must correspond to the Pyth governance emitter chain and contract. + assert!( + pyth::state::is_valid_governance_data_source(pyth_state, vaa_data_source), + EInvalidGovernanceDataSource, + ); - let payload = vaa::take_payload(verified_vaa); + let digest = vaa::digest(&verified_vaa); - WormholeVAAVerificationReceipt { payload, digest, sequence } - } + let sequence = vaa::sequence(&verified_vaa); + + let payload = vaa::take_payload(verified_vaa); + + WormholeVAAVerificationReceipt { payload, digest, sequence } +} - /// Execute a governance instruction other than contract upgrade, which is - /// handled separately in the contract_upgrade.move module. - public fun execute_governance_instruction( - pyth_state : &mut State, - receipt: WormholeVAAVerificationReceipt, - ) { - // This capability ensures that the current build version is used. - let latest_only = state::assert_latest_only(pyth_state); - - // Get the sequence number of the governance VAA that was used to - // generate the receipt. - let sequence = receipt.sequence; - - // Require that new sequence number is greater than last executed sequence number. - assert!(sequence > state::get_last_executed_governance_sequence(pyth_state), - E_CANNOT_EXECUTE_GOVERNANCE_ACTION_WITH_OBSOLETE_SEQUENCE_NUMBER); - - // Update latest executed sequence number to current one. - state::set_last_executed_governance_sequence(&latest_only, pyth_state, sequence); - - let payload = receipt.payload; - - destroy(receipt); - - let instruction = governance_instruction::from_byte_vec(payload); - - // Get the governance action. - let action = governance_instruction::get_action(&instruction); - - // Dispatch the instruction to the appropriate handler. - if (action == governance_action::new_contract_upgrade()) { - abort(E_MUST_USE_CONTRACT_UPGRADE_MODULE_TO_DO_UPGRADES) - } else if (action == governance_action::new_set_governance_data_source()) { - set_governance_data_source::execute(&latest_only, pyth_state, governance_instruction::destroy(instruction)); - } else if (action == governance_action::new_set_data_sources()) { - set_data_sources::execute(&latest_only, pyth_state, governance_instruction::destroy(instruction)); - } else if (action == governance_action::new_set_update_fee()) { - set_update_fee::execute(&latest_only, pyth_state, governance_instruction::destroy(instruction)); - } else if (action == governance_action::new_set_stale_price_threshold()) { - set_stale_price_threshold::execute(&latest_only, pyth_state, governance_instruction::destroy(instruction)); - } else if (action == governance_action::new_set_fee_recipient()) { - set_fee_recipient::execute(&latest_only, pyth_state, governance_instruction::destroy(instruction)); - } else { - governance_instruction::destroy(instruction); - assert!(false, E_INVALID_GOVERNANCE_ACTION); - } +/// Execute a governance instruction other than contract upgrade, which is +/// handled separately in the contract_upgrade.move module. +public fun execute_governance_instruction( + pyth_state: &mut State, + receipt: WormholeVAAVerificationReceipt, +) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(pyth_state); + + // Get the sequence number of the governance VAA that was used to + // generate the receipt. + let sequence = receipt.sequence; + + // Require that new sequence number is greater than last executed sequence number. + assert!( + sequence > state::get_last_executed_governance_sequence(pyth_state), + ECannotExecuteGovernanceActionWithObsoleteSequenceNumber, + ); + + // Update latest executed sequence number to current one. + state::set_last_executed_governance_sequence(&latest_only, pyth_state, sequence); + + let payload = receipt.payload; + + destroy(receipt); + + let instruction = governance_instruction::from_byte_vec(payload); + + // Get the governance action. + let action = governance_instruction::get_action(&instruction); + + // Dispatch the instruction to the appropriate handler. + if (action == governance_action::new_contract_upgrade()) { + abort EMustUseContractUpgradeModuleToDoUpgrades + } else if (action == governance_action::new_set_governance_data_source()) { + set_governance_data_source::execute( + &latest_only, + pyth_state, + governance_instruction::destroy(instruction), + ); + } else if (action == governance_action::new_set_data_sources()) { + set_data_sources::execute( + &latest_only, + pyth_state, + governance_instruction::destroy(instruction), + ); + } else if (action == governance_action::new_set_update_fee()) { + set_update_fee::execute( + &latest_only, + pyth_state, + governance_instruction::destroy(instruction), + ); + } else if (action == governance_action::new_set_stale_price_threshold()) { + set_stale_price_threshold::execute( + &latest_only, + pyth_state, + governance_instruction::destroy(instruction), + ); + } else if (action == governance_action::new_set_fee_recipient()) { + set_fee_recipient::execute( + &latest_only, + pyth_state, + governance_instruction::destroy(instruction), + ); + } else { + governance_instruction::destroy(instruction); + abort EInvalidGovernanceAction } } diff --git a/target_chains/sui/contracts/sources/governance/governance_action.move b/target_chains/sui/contracts/sources/governance/governance_action.move index 6bfce10a50..a64266019b 100644 --- a/target_chains/sui/contracts/sources/governance/governance_action.move +++ b/target_chains/sui/contracts/sources/governance/governance_action.move @@ -1,48 +1,47 @@ -module pyth::governance_action { +module pyth::governance_action; - 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; - const SET_FEE_RECIPIENT: u8 = 5; +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; +const SET_FEE_RECIPIENT: u8 = 5; - const E_INVALID_GOVERNANCE_ACTION: u64 = 6; +const EInvalidGovernanceAction: u64 = 6; - struct GovernanceAction has copy, drop { - value: u8, - } +public struct GovernanceAction has copy, drop { + value: u8, +} - public fun from_u8(value: u8): GovernanceAction { - assert!(CONTRACT_UPGRADE <= value && value <= SET_FEE_RECIPIENT, E_INVALID_GOVERNANCE_ACTION); - GovernanceAction { value } - } +public fun from_u8(value: u8): GovernanceAction { + assert!(CONTRACT_UPGRADE <= value && value <= SET_FEE_RECIPIENT, EInvalidGovernanceAction); + GovernanceAction { value } +} - public fun get_value(a: GovernanceAction): u8{ - a.value - } +public fun get_value(a: GovernanceAction): u8 { + a.value +} - public fun new_contract_upgrade(): GovernanceAction { - GovernanceAction { value: CONTRACT_UPGRADE } - } +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_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_data_sources(): GovernanceAction { + GovernanceAction { value: SET_DATA_SOURCES } +} - public fun new_set_update_fee(): GovernanceAction { - GovernanceAction { value: SET_UPDATE_FEE } - } +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 } - } +public fun new_set_stale_price_threshold(): GovernanceAction { + GovernanceAction { value: SET_STALE_PRICE_THRESHOLD } +} - public fun new_set_fee_recipient(): GovernanceAction { - GovernanceAction { value: SET_FEE_RECIPIENT } - } +public fun new_set_fee_recipient(): GovernanceAction { + GovernanceAction { value: SET_FEE_RECIPIENT } } diff --git a/target_chains/sui/contracts/sources/governance/governance_instruction.move b/target_chains/sui/contracts/sources/governance/governance_instruction.move index ee008308e7..9216657838 100644 --- a/target_chains/sui/contracts/sources/governance/governance_instruction.move +++ b/target_chains/sui/contracts/sources/governance/governance_instruction.move @@ -1,91 +1,86 @@ -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; - - const E_INVALID_GOVERNANCE_MODULE: u64 = 0; - const E_INVALID_GOVERNANCE_MAGIC_VALUE: u64 = 1; - const E_TARGET_CHAIN_MISMATCH: u64 = 2; - - struct GovernanceInstruction { - module_: u8, - action: GovernanceAction, - target_chain_id: u64, - payload: vector, - } - - fun validate(instruction: &GovernanceInstruction) { - assert!(instruction.module_ == MODULE, E_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, E_TARGET_CHAIN_MISMATCH); - } - - public fun from_byte_vec(bytes: vector): GovernanceInstruction { - let cursor = cursor::new(bytes); - let magic = deserialize::deserialize_vector(&mut cursor, 4); - assert!(magic == MAGIC, E_INVALID_GOVERNANCE_MAGIC_VALUE); - // "module" is a reserved keyword, so we use "module_" instead. - 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 validates that module and target chain are correct - 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 - } = 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)); - } +module pyth::governance_instruction; + +use pyth::deserialize; +use pyth::governance_action::{Self, GovernanceAction}; +use wormhole::cursor; + +const MAGIC: vector = x"5054474d"; // "PTGM": Pyth Governance Message +const MODULE: u8 = 1; + +const EInvalidGovernanceModule: u64 = 0; +const EInvalidGovernanceMagicValue: u64 = 1; +const ETargetChainMismatch: u64 = 2; + +public struct GovernanceInstruction { + module_: u8, + action: GovernanceAction, + target_chain_id: u64, + payload: vector, +} + +fun validate(instruction: &GovernanceInstruction) { + assert!(instruction.module_ == MODULE, EInvalidGovernanceModule); + let target_chain_id = instruction.target_chain_id; + assert!( + target_chain_id == (wormhole::state::chain_id() as u64) || target_chain_id == 0, + ETargetChainMismatch, + ); +} + +public fun from_byte_vec(bytes: vector): GovernanceInstruction { + let mut cursor = cursor::new(bytes); + let magic = deserialize::deserialize_vector(&mut cursor, 4); + assert!(magic == MAGIC, EInvalidGovernanceMagicValue); + // "module" is a reserved keyword, so we use "module_" instead. + 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 validates that module and target chain are correct + 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 { 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 index 47148c30b5..957b04702d 100644 --- a/target_chains/sui/contracts/sources/governance/set_data_sources.move +++ b/target_chains/sui/contracts/sources/governance/set_data_sources.move @@ -1,100 +1,42 @@ -module pyth::set_data_sources { - use std::vector; +module pyth::set_data_sources; - use wormhole::cursor; - use wormhole::external_address::{Self}; - use wormhole::bytes32::{Self}; +use pyth::data_source::{Self, DataSource}; +use pyth::deserialize; +use pyth::state::{Self, State, LatestOnly}; +use wormhole::bytes32; +use wormhole::cursor; +use wormhole::external_address; - use pyth::deserialize; - use pyth::data_source::{Self, DataSource}; - use pyth::state::{Self, State, LatestOnly}; - - friend pyth::governance; - - struct DataSources { - sources: vector, - } - - public(friend) fun execute( - latest_only: &LatestOnly, - state: &mut State, - payload: vector - ) { - let DataSources { sources } = from_byte_vec(payload); - state::set_data_sources(latest_only, state, sources); - } - - fun from_byte_vec(bytes: vector): DataSources { - 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::new(bytes32::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); - - DataSources { - sources - } - } +public struct DataSources { + sources: vector, } -#[test_only] -module pyth::set_data_sources_tests { - use sui::test_scenario::{Self}; - use sui::coin::Self; - - use wormhole::external_address::{Self}; - use wormhole::bytes32::{Self}; - - use pyth::pyth_tests::{Self, setup_test, take_wormhole_and_pyth_states}; - use pyth::state::Self; - use pyth::data_source::Self; - - const SET_DATA_SOURCES_VAA: vector = x"01000000000100b29ee59868b9066b04d8d59e1c7cc66f0678eaf4c58b8c87e4405d6de615f64b04da4025719aeed349e03900f37829454d62cc7fc7bca80328c31fe40be7b21b010000000000000000000163278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c3850000000000000001015054474d0102001503001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b60001f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0"; - // VAA Info: - // module name: 0x1 - // action: 2 - // chain: 21 - // data sources (chain, addr) pairs: [(1, 0xf346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0), (26, 0xa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6), (26, 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71)] - - const DEPLOYER: address = @0x1234; - const DEFAULT_BASE_UPDATE_FEE: u64 = 0; - const DEFAULT_COIN_TO_MINT: u64 = 0; - - #[test] - fun set_data_sources(){ - let (scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - test_scenario::next_tx(&mut scenario, DEPLOYER); - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaa = wormhole::vaa::parse_and_verify(&worm_state, SET_DATA_SOURCES_VAA, &clock); - - let receipt = pyth::governance::verify_vaa(&pyth_state, verified_vaa); +public(package) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { + let DataSources { sources } = from_byte_vec(payload); + state::set_data_sources(latest_only, state, sources); +} - test_scenario::next_tx(&mut scenario, DEPLOYER); +fun from_byte_vec(bytes: vector): DataSources { + let mut cursor = cursor::new(bytes); + let data_sources_count = deserialize::deserialize_u8(&mut cursor); - pyth::governance::execute_governance_instruction(&mut pyth_state, receipt); + let mut sources = vector::empty(); - test_scenario::next_tx(&mut scenario, DEPLOYER); + let mut i = 0; + while (i < data_sources_count) { + let emitter_chain_id = deserialize::deserialize_u16(&mut cursor); + let emitter_address = external_address::new( + bytes32::from_bytes(deserialize::deserialize_vector(&mut cursor, 32)), + ); + vector::push_back( + &mut sources, + data_source::new((emitter_chain_id as u64), emitter_address), + ); - // assert data sources are set correctly + i = i + 1; + }; - assert!(state::is_valid_data_source(&pyth_state, data_source::new(1, external_address::new(bytes32::from_bytes(x"f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0")))), 0); - assert!(state::is_valid_data_source(&pyth_state, data_source::new(26, external_address::new(bytes32::from_bytes(x"a27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6")))), 0); - assert!(state::is_valid_data_source(&pyth_state, data_source::new(26, external_address::new(bytes32::from_bytes(x"e101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71")))), 0); + cursor::destroy_empty(cursor); - // clean up - coin::burn_for_testing(test_coins); - pyth_tests::cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } + DataSources { sources } } diff --git a/target_chains/sui/contracts/sources/governance/set_fee_recipient.move b/target_chains/sui/contracts/sources/governance/set_fee_recipient.move index 602b5e1324..8da003d5aa 100644 --- a/target_chains/sui/contracts/sources/governance/set_fee_recipient.move +++ b/target_chains/sui/contracts/sources/governance/set_fee_recipient.move @@ -1,32 +1,29 @@ /// The previous version of the contract sent the fees to a recipient address but this state is not used anymore /// This module is kept for backward compatibility -module pyth::set_fee_recipient { - use wormhole::cursor; - use wormhole::external_address::{Self}; +module pyth::set_fee_recipient; - use pyth::state::{Self, State, LatestOnly}; +use pyth::state::{Self, State, LatestOnly}; +use wormhole::cursor; +use wormhole::external_address; - friend pyth::governance; - - struct PythFeeRecipient { - recipient: address - } +public struct PythFeeRecipient { + recipient: address, +} - public(friend) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { - let PythFeeRecipient { recipient } = from_byte_vec(payload); - state::set_fee_recipient(latest_only, state, recipient); - } +public(package) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { + let PythFeeRecipient { recipient } = from_byte_vec(payload); + state::set_fee_recipient(latest_only, state, recipient); +} - fun from_byte_vec(payload: vector): PythFeeRecipient { - let cur = cursor::new(payload); +fun from_byte_vec(payload: vector): PythFeeRecipient { + let mut cur = cursor::new(payload); - // Recipient must be non-zero address. - let recipient = external_address::take_nonzero(&mut cur); + // Recipient must be non-zero address. + let recipient = external_address::take_nonzero(&mut cur); - cursor::destroy_empty(cur); + cursor::destroy_empty(cur); - PythFeeRecipient { - recipient: external_address::to_address(recipient) - } + PythFeeRecipient { + recipient: external_address::to_address(recipient), } } 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 index 682a1a7182..6a3bc55a50 100644 --- a/target_chains/sui/contracts/sources/governance/set_governance_data_source.move +++ b/target_chains/sui/contracts/sources/governance/set_governance_data_source.move @@ -1,36 +1,43 @@ -module pyth::set_governance_data_source { - use pyth::deserialize; - use pyth::data_source; - use pyth::state::{Self, State, LatestOnly}; +module pyth::set_governance_data_source; - use wormhole::cursor; - use wormhole::external_address::{Self, ExternalAddress}; - use wormhole::bytes32::{Self}; +use pyth::data_source; +use pyth::deserialize; +use pyth::state::{Self, State, LatestOnly}; +use wormhole::bytes32; +use wormhole::cursor; +use wormhole::external_address::{Self, ExternalAddress}; - friend pyth::governance; - - struct GovernanceDataSource { - emitter_chain_id: u64, - emitter_address: ExternalAddress, - initial_sequence: u64, - } +public struct GovernanceDataSource { + emitter_chain_id: u64, + emitter_address: ExternalAddress, + initial_sequence: u64, +} - public(friend) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { - let GovernanceDataSource { emitter_chain_id, emitter_address, initial_sequence: initial_sequence } = from_byte_vec(payload); - state::set_governance_data_source(latest_only, pyth_state, data_source::new(emitter_chain_id, emitter_address)); - state::set_last_executed_governance_sequence(latest_only, pyth_state, initial_sequence); - } +public(package) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { + let GovernanceDataSource { + emitter_chain_id, + emitter_address, + initial_sequence: initial_sequence, + } = from_byte_vec(payload); + state::set_governance_data_source( + latest_only, + pyth_state, + data_source::new(emitter_chain_id, emitter_address), + ); + state::set_last_executed_governance_sequence(latest_only, pyth_state, initial_sequence); +} - fun from_byte_vec(bytes: vector): GovernanceDataSource { - let cursor = cursor::new(bytes); - let emitter_chain_id = deserialize::deserialize_u16(&mut cursor); - let emitter_address = external_address::new(bytes32::from_bytes(deserialize::deserialize_vector(&mut cursor, 32))); - let initial_sequence = deserialize::deserialize_u64(&mut cursor); - cursor::destroy_empty(cursor); - GovernanceDataSource { - emitter_chain_id: (emitter_chain_id as u64), - emitter_address, - initial_sequence - } +fun from_byte_vec(bytes: vector): GovernanceDataSource { + let mut cursor = cursor::new(bytes); + let emitter_chain_id = deserialize::deserialize_u16(&mut cursor); + let emitter_address = external_address::new( + bytes32::from_bytes(deserialize::deserialize_vector(&mut cursor, 32)), + ); + let initial_sequence = deserialize::deserialize_u64(&mut cursor); + cursor::destroy_empty(cursor); + GovernanceDataSource { + 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 index c4a158dfd7..208f775c53 100644 --- a/target_chains/sui/contracts/sources/governance/set_stale_price_threshold.move +++ b/target_chains/sui/contracts/sources/governance/set_stale_price_threshold.move @@ -1,72 +1,23 @@ -module pyth::set_stale_price_threshold { - use wormhole::cursor; +module pyth::set_stale_price_threshold; - use pyth::deserialize; - use pyth::state::{Self, State, LatestOnly}; +use pyth::deserialize; +use pyth::state::{Self, State, LatestOnly}; +use wormhole::cursor; - friend pyth::governance; - - struct StalePriceThreshold { - threshold: u64, - } - - public(friend) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { - let StalePriceThreshold { threshold } = from_byte_vec(payload); - state::set_stale_price_threshold_secs(latest_only, state, threshold); - } - - fun from_byte_vec(bytes: vector): StalePriceThreshold { - let cursor = cursor::new(bytes); - let threshold = deserialize::deserialize_u64(&mut cursor); - cursor::destroy_empty(cursor); - StalePriceThreshold { - threshold - } - } +public struct StalePriceThreshold { + threshold: u64, } -#[test_only] -module pyth::set_stale_price_threshold_test { - use sui::test_scenario::{Self}; - use sui::coin::Self; - - use pyth::pyth_tests::{Self, setup_test, take_wormhole_and_pyth_states}; - use pyth::state::Self; - - const SET_STALE_PRICE_THRESHOLD_VAA: vector = x"010000000001000393eabdb4983e91e0fcfe7e6b2fc5c8fca2847fde52fd2f51a9b26b12298da13af09c271ce7723af8e0b1f52afa02b56f0b64764739b1b05e2f2c5cec80567c000000000000000000000163278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c3850000000000000001015054474d0104001500000000000f4020"; - // VAA Info: - // module name: 0x1 - // action: 4 - // chain: 21 - // stale price threshold: 999456 - - const DEPLOYER: address = @0x1234; - const DEFAULT_BASE_UPDATE_FEE: u64 = 0; - const DEFAULT_COIN_TO_MINT: u64 = 0; - - #[test] - fun set_stale_price_threshold(){ - - let (scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - test_scenario::next_tx(&mut scenario, DEPLOYER); - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaa = wormhole::vaa::parse_and_verify(&worm_state, SET_STALE_PRICE_THRESHOLD_VAA, &clock); - - let receipt = pyth::governance::verify_vaa(&pyth_state, verified_vaa); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::governance::execute_governance_instruction(&mut pyth_state, receipt); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - // assert stale price threshold is set correctly - assert!(state::get_stale_price_threshold_secs(&pyth_state)==999456, 0); +public(package) fun execute(latest_only: &LatestOnly, state: &mut State, payload: vector) { + let StalePriceThreshold { threshold } = from_byte_vec(payload); + state::set_stale_price_threshold_secs(latest_only, state, threshold); +} - // clean up - coin::burn_for_testing(test_coins); - pyth_tests::cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); +fun from_byte_vec(bytes: vector): StalePriceThreshold { + let mut cursor = cursor::new(bytes); + let threshold = deserialize::deserialize_u64(&mut cursor); + cursor::destroy_empty(cursor); + StalePriceThreshold { + 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 index a0c2c11a10..cc94734f36 100644 --- a/target_chains/sui/contracts/sources/governance/set_update_fee.move +++ b/target_chains/sui/contracts/sources/governance/set_update_fee.move @@ -1,85 +1,35 @@ -module pyth::set_update_fee { - use sui::math::{Self}; +module pyth::set_update_fee; - use wormhole::cursor; +use pyth::deserialize; +use pyth::state::{Self, State, LatestOnly}; +use sui::math; +use wormhole::cursor; - use pyth::deserialize; - use pyth::state::{Self, State, LatestOnly}; +const EExponentDoesNotFitInU8: u64 = 0; - friend pyth::governance; - - const E_EXPONENT_DOES_NOT_FIT_IN_U8: u64 = 0; - - struct UpdateFee { - mantissa: u64, - exponent: u64, - } - - public(friend) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { - let UpdateFee { mantissa, exponent } = from_byte_vec(payload); - assert!(exponent <= 255, E_EXPONENT_DOES_NOT_FIT_IN_U8); - let fee = apply_exponent(mantissa, (exponent as u8)); - state::set_base_update_fee(latest_only, pyth_state, fee); - } +public struct UpdateFee { + mantissa: u64, + exponent: u64, +} - fun from_byte_vec(bytes: vector): UpdateFee { - let cursor = cursor::new(bytes); - let mantissa = deserialize::deserialize_u64(&mut cursor); - let exponent = deserialize::deserialize_u64(&mut cursor); - cursor::destroy_empty(cursor); - UpdateFee { - mantissa, - exponent, - } - } +public(package) fun execute(latest_only: &LatestOnly, pyth_state: &mut State, payload: vector) { + let UpdateFee { mantissa, exponent } = from_byte_vec(payload); + assert!(exponent <= 255, EExponentDoesNotFitInU8); + let fee = apply_exponent(mantissa, (exponent as u8)); + state::set_base_update_fee(latest_only, pyth_state, fee); +} - fun apply_exponent(mantissa: u64, exponent: u8): u64 { - mantissa * math::pow(10, exponent) +fun from_byte_vec(bytes: vector): UpdateFee { + let mut cursor = cursor::new(bytes); + let mantissa = deserialize::deserialize_u64(&mut cursor); + let exponent = deserialize::deserialize_u64(&mut cursor); + cursor::destroy_empty(cursor); + UpdateFee { + mantissa, + exponent, } } -#[test_only] -module pyth::set_update_fee_tests { - use sui::test_scenario::{Self}; - use sui::coin::Self; - - use pyth::pyth_tests::{Self, setup_test, take_wormhole_and_pyth_states}; - use pyth::state::Self; - - const SET_FEE_VAA: vector = x"01000000000100189d01616814b185b5a26bde6123d48e0d44dd490bbb3bde5d12076247b2180068a8261165777076ae532b7b0739aaee6411c8ba0695d20d4fa548227ce15d8d010000000000000000000163278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c3850000000000000001015054474d0103001500000000000000050000000000000005"; - // VAA Info: - // module name: 0x1 - // action: 3 - // chain: 21 - // new fee: 5, new exponent: 5 - - const DEPLOYER: address = @0x1234; - const DEFAULT_BASE_UPDATE_FEE: u64 = 0; - const DEFAULT_COIN_TO_MINT: u64 = 0; - - #[test] - fun test_set_update_fee(){ - - let (scenario, test_coins, clock) = setup_test(500, 1, x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", pyth_tests::data_sources_for_test_vaa(), vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - test_scenario::next_tx(&mut scenario, DEPLOYER); - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaa = wormhole::vaa::parse_and_verify(&worm_state, SET_FEE_VAA, &clock); - - let receipt = pyth::governance::verify_vaa(&pyth_state, verified_vaa); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::governance::execute_governance_instruction(&mut pyth_state, receipt); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - // assert fee is set correctly - assert!(state::get_base_update_fee(&pyth_state)==500000, 0); - - // clean up - coin::burn_for_testing(test_coins); - pyth_tests::cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } +fun apply_exponent(mantissa: u64, exponent: u8): u64 { + mantissa * math::pow(10, exponent) } diff --git a/target_chains/sui/contracts/sources/hot_potato_vector.move b/target_chains/sui/contracts/sources/hot_potato_vector.move index a733a6ce51..bafa55c3cd 100644 --- a/target_chains/sui/contracts/sources/hot_potato_vector.move +++ b/target_chains/sui/contracts/sources/hot_potato_vector.move @@ -1,66 +1,62 @@ /// This class represents a vector of objects wrapped /// inside of a hot potato struct. -module pyth::hot_potato_vector { - use std::vector; +module pyth::hot_potato_vector; - friend pyth::pyth; - #[test_only] - friend pyth::pyth_tests; - - // A hot potato containing a vector of elements - struct HotPotatoVector { - contents: vector - } - - // A public destroy function. - public fun destroy(hot_potato_vector: HotPotatoVector) { - let HotPotatoVector { contents: _ } = hot_potato_vector; - } - - // Only certain on-chain functions are allowed to create a new hot potato vector. - public(friend) fun new(vec: vector): HotPotatoVector { - HotPotatoVector { - contents: vec - } - } +// A hot potato containing a vector of elements +public struct HotPotatoVector { + contents: vector, +} - public fun length(potato: &HotPotatoVector): u64 { - vector::length(&potato.contents) - } +// A public destroy function. +public fun destroy(hot_potato_vector: HotPotatoVector) { + let HotPotatoVector { contents: _ } = hot_potato_vector; +} - public fun is_empty(potato: &HotPotatoVector): bool { - vector::is_empty(&potato.contents) +// Only certain on-chain functions are allowed to create a new hot potato vector. +public(package) fun new(vec: vector): HotPotatoVector { + HotPotatoVector { + contents: vec, } +} - public(friend) fun borrow(potato: &HotPotatoVector, i: u64): &T { - vector::borrow(&potato.contents, i) - } +public fun length(potato: &HotPotatoVector): u64 { + vector::length(&potato.contents) +} - public(friend) fun pop_back(hot_potato_vector: HotPotatoVector): (T, HotPotatoVector) { - let elem = vector::pop_back(&mut hot_potato_vector.contents); - return (elem, hot_potato_vector) - } +public fun is_empty(potato: &HotPotatoVector): bool { + vector::is_empty(&potato.contents) +} - #[test_only] - struct A has copy, drop { - a: u64 - } +public(package) fun borrow(potato: &HotPotatoVector, i: u64): &T { + vector::borrow(&potato.contents, i) +} - #[test] - fun test_hot_potato_vector() { - let vec_of_a = vector::empty(); - vector::push_back(&mut vec_of_a, A { a: 5 }); - vector::push_back(&mut vec_of_a, A { a: 11 }); - vector::push_back(&mut vec_of_a, A { a: 23 }); +public(package) fun pop_back( + mut hot_potato_vector: HotPotatoVector, +): (T, HotPotatoVector) { + let elem = vector::pop_back(&mut hot_potato_vector.contents); + return (elem, hot_potato_vector) +} - let hot_potato = new(vec_of_a); - let (b, hot_potato) = pop_back(hot_potato); - assert!(b.a == 23, 0); - (b, hot_potato) = pop_back(hot_potato); - assert!(b.a == 11, 0); - let (b, hot_potato) = pop_back(hot_potato); - assert!(b.a == 5, 0); +#[test_only] +public struct A has copy, drop { + a: u64, +} - destroy(hot_potato); - } +#[test] +fun test_hot_potato_vector() { + let mut vec_of_a = vector::empty(); + vector::push_back(&mut vec_of_a, A { a: 5 }); + vector::push_back(&mut vec_of_a, A { a: 11 }); + vector::push_back(&mut vec_of_a, A { a: 23 }); + + let hot_potato = new(vec_of_a); + let (mut b, mut hot_potato) = pop_back(hot_potato); + assert!(b.a == 23, 0); + (b, hot_potato) = pop_back(hot_potato); + assert!(b.a == 11, 0); + let (b, hot_potato) = pop_back(hot_potato); + assert!(b.a == 5, 0); + + destroy(hot_potato); } diff --git a/target_chains/sui/contracts/sources/i64.move b/target_chains/sui/contracts/sources/i64.move index 889e7ba752..ce25a6aa13 100644 --- a/target_chains/sui/contracts/sources/i64.move +++ b/target_chains/sui/contracts/sources/i64.move @@ -1,140 +1,141 @@ -module pyth::i64 { - //use pyth::error; - - const MAX_POSITIVE_MAGNITUDE: u64 = (1 << 63) - 1; - const MAX_NEGATIVE_MAGNITUDE: u64 = (1 << 63); - - /// As Move does not support negative numbers natively, we use our own internal - /// representation. - /// - /// To consume these values, first call `get_is_negative()` to determine if the I64 - /// represents a negative or positive value. Then call `get_magnitude_if_positive()` or - /// `get_magnitude_if_negative()` to get the magnitude of the number in unsigned u64 format. - /// This API forces consumers to handle positive and negative numbers safely. - struct I64 has copy, drop, store { - negative: bool, - magnitude: u64, - } - - public fun new(magnitude: u64, negative: bool): I64 { - let max_magnitude = MAX_POSITIVE_MAGNITUDE; - if (negative) { - max_magnitude = MAX_NEGATIVE_MAGNITUDE; - }; - assert!(magnitude <= max_magnitude, 0); //error::magnitude_too_large() +module pyth::i64; + +//use pyth::error; + +const MAX_POSITIVE_MAGNITUDE: u64 = (1 << 63) - 1; +const MAX_NEGATIVE_MAGNITUDE: u64 = (1 << 63); + +/// As Move does not support negative numbers natively, we use our own internal +/// representation. +/// +/// To consume these values, first call `get_is_negative()` to determine if the I64 +/// represents a negative or positive value. Then call `get_magnitude_if_positive()` or +/// `get_magnitude_if_negative()` to get the magnitude of the number in unsigned u64 format. +/// This API forces consumers to handle positive and negative numbers safely. +public struct I64 has copy, drop, store { + negative: bool, + magnitude: u64, +} +public fun new(magnitude: u64, mut negative: bool): I64 { + let mut max_magnitude = MAX_POSITIVE_MAGNITUDE; + if (negative) { + max_magnitude = MAX_NEGATIVE_MAGNITUDE; + }; + assert!(magnitude <= max_magnitude, 0); //error::magnitude_too_large() - // Ensure we have a single zero representation: (0, false). - // (0, true) is invalid. - if (magnitude == 0) { - negative = false; - }; + // Ensure we have a single zero representation: (0, false). + // (0, true) is invalid. + if (magnitude == 0) { + negative = false; + }; - I64 { - magnitude, - negative, - } + I64 { + magnitude, + negative, } +} - public fun get_is_negative(i: &I64): bool { - i.negative - } +public use fun get_is_negative as I64.is_negative; - public fun get_magnitude_if_positive(in: &I64): u64 { - assert!(!in.negative, 0); // error::negative_value() - in.magnitude - } +public fun get_is_negative(i: &I64): bool { + i.negative +} - public fun get_magnitude_if_negative(in: &I64): u64 { - assert!(in.negative, 0); //error::positive_value() - in.magnitude - } +public use fun get_magnitude_if_positive as I64.magnitude_if_positive; - public fun from_u64(from: u64): I64 { - // Use the MSB to determine whether the number is negative or not. - let negative = (from >> 63) == 1; - let magnitude = parse_magnitude(from, negative); +public fun get_magnitude_if_positive(in: &I64): u64 { + assert!(!in.negative, 0); // error::negative_value() + in.magnitude +} - new(magnitude, negative) - } +public use fun get_magnitude_if_negative as I64.magnitude_if_negative; - fun parse_magnitude(from: u64, negative: bool): u64 { - // If positive, then return the input verbatamin - if (!negative) { - return from - }; +public fun get_magnitude_if_negative(in: &I64): u64 { + assert!(in.negative, 0); //error::positive_value() + in.magnitude +} - // Otherwise convert from two's complement by inverting and adding 1 - let inverted = from ^ 0xFFFFFFFFFFFFFFFF; - inverted + 1 - } +public fun from_u64(from: u64): I64 { + // Use the MSB to determine whether the number is negative or not. + let negative = (from >> 63) == 1; + let magnitude = parse_magnitude(from, negative); - #[test] - fun test_max_positive_magnitude() { - new(0x7FFFFFFFFFFFFFFF, false); - assert!(&new(1<<63 - 1, false) == &from_u64(1<<63 - 1), 1); - } + new(magnitude, negative) +} - #[test] - #[expected_failure] - fun test_magnitude_too_large_positive() { - new(0x8000000000000000, false); - } +fun parse_magnitude(from: u64, negative: bool): u64 { + // If positive, then return the input verbatamin + if (!negative) { + return from + }; - #[test] - fun test_max_negative_magnitude() { - new(0x8000000000000000, true); - assert!(&new(1<<63, true) == &from_u64(1<<63), 1); - } + // Otherwise convert from two's complement by inverting and adding 1 + let inverted = from ^ 0xFFFFFFFFFFFFFFFF; + inverted + 1 +} - #[test] - #[expected_failure] - fun test_magnitude_too_large_negative() { - new(0x8000000000000001, true); - } +#[test] +fun test_max_positive_magnitude() { + new(0x7FFFFFFFFFFFFFFF, false); + assert!(&new(1<<63 - 1, false) == &from_u64(1<<63 - 1), 1); +} - #[test] - fun test_from_u64_positive() { - assert!(from_u64(0x64673) == new(0x64673, false), 1); - } +#[test, expected_failure] +fun test_magnitude_too_large_positive() { + new(0x8000000000000000, false); +} - #[test] - fun test_from_u64_negative() { - assert!(from_u64(0xFFFFFFFFFFFEDC73) == new(0x1238D, true), 1); - } +#[test] +fun test_max_negative_magnitude() { + new(0x8000000000000000, true); + assert!(&new(1<<63, true) == &from_u64(1<<63), 1); +} - #[test] - fun test_get_is_negative() { - assert!(get_is_negative(&new(234, true)) == true, 1); - assert!(get_is_negative(&new(767, false)) == false, 1); - } +#[test, expected_failure] +fun test_magnitude_too_large_negative() { + new(0x8000000000000001, true); +} - #[test] - fun test_get_magnitude_if_positive_positive() { - assert!(get_magnitude_if_positive(&new(7686, false)) == 7686, 1); - } +#[test] +fun test_from_u64_positive() { + assert!(from_u64(0x64673) == new(0x64673, false), 1); +} - #[test] - #[expected_failure] - fun test_get_magnitude_if_positive_negative() { - assert!(get_magnitude_if_positive(&new(7686, true)) == 7686, 1); - } +#[test] +fun test_from_u64_negative() { + assert!(from_u64(0xFFFFFFFFFFFEDC73) == new(0x1238D, true), 1); +} - #[test] - fun test_get_magnitude_if_negative_negative() { - assert!(get_magnitude_if_negative(&new(7686, true)) == 7686, 1); - } +#[test] +fun test_get_is_negative() { + assert!(get_is_negative(&new(234, true)) == true, 1); + assert!(get_is_negative(&new(767, false)) == false, 1); +} - #[test] - #[expected_failure] - fun test_get_magnitude_if_negative_positive() { - assert!(get_magnitude_if_negative(&new(7686, false)) == 7686, 1); - } +#[test] +fun test_get_magnitude_if_positive_positive() { + assert!(get_magnitude_if_positive(&new(7686, false)) == 7686, 1); +} - #[test] - fun test_single_zero_representation() { - assert!(&new(0, true) == &new(0, false), 1); - assert!(&new(0, true) == &from_u64(0), 1); - assert!(&new(0, false) == &from_u64(0), 1); - } +#[test, expected_failure] +fun test_get_magnitude_if_positive_negative() { + assert!(get_magnitude_if_positive(&new(7686, true)) == 7686, 1); +} + +#[test] +fun test_get_magnitude_if_negative_negative() { + assert!(get_magnitude_if_negative(&new(7686, true)) == 7686, 1); +} + +#[test, expected_failure] +fun test_get_magnitude_if_negative_positive() { + assert!(get_magnitude_if_negative(&new(7686, false)) == 7686, 1); +} + +#[test] +fun test_single_zero_representation() { + assert!(&new(0, true) == &new(0, false), 1); + assert!(&new(0, true) == &from_u64(0), 1); + assert!(&new(0, false) == &from_u64(0), 1); } diff --git a/target_chains/sui/contracts/sources/merkle_tree.move b/target_chains/sui/contracts/sources/merkle_tree.move index 54545c6651..5177bc4340 100644 --- a/target_chains/sui/contracts/sources/merkle_tree.move +++ b/target_chains/sui/contracts/sources/merkle_tree.move @@ -1,397 +1,391 @@ // Implementation of a Merkle tree in Move. Supports constructing a new tree // with a given depth, as well as proving that a leaf node belongs to the tree. -module pyth::merkle_tree { - use std::vector::{Self}; - use sui::hash::{keccak256}; - use wormhole::bytes20::{Self, Bytes20, data}; - use wormhole::cursor::Cursor; - use pyth::deserialize::{Self}; - - #[test_only] - use wormhole::cursor::{Self}; - - const MERKLE_LEAF_PREFIX: u8 = 0; - const MERKLE_NODE_PREFIX: u8 = 1; - const MERKLE_EMPTY_LEAF_PREFIX: u8 = 2; - - const E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES: u64 = 1212121; - - // take keccak256 of input data, then return 20 leftmost bytes of result - fun hash(bytes: &vector): Bytes20 { - let hashed_bytes = keccak256(bytes); - let hash_prefix = vector::empty(); - let i = 0; - while (i < 20) { - vector::push_back(&mut hash_prefix, *vector::borrow(&hashed_bytes, i)); - i = i + 1; - }; - bytes20::new(hash_prefix) - } - - fun empty_leaf_hash(): Bytes20 { - let v = vector[MERKLE_EMPTY_LEAF_PREFIX]; - hash(&v) - } - - fun leaf_hash(data: &vector): Bytes20 { - let v = vector[MERKLE_LEAF_PREFIX]; - let i = 0; - while (i < vector::length(data)) { - vector::push_back(&mut v, *vector::borrow(data, i)); - i = i + 1; - }; - hash(&v) - } - - fun node_hash( - childA: Bytes20, - childB: Bytes20 - ): Bytes20 { - if (greater_than(&childA, &childB)) { - (childA, childB) = (childB, childA); - }; - // append data_B to data_A - let data_A = bytes20::data(&childA); - let data_B = bytes20::data(&childB); - - // create a vector containing MERKLE_NODE_PREFIX + data_A + data_B - let v = vector[MERKLE_NODE_PREFIX]; - let i = 0; - while (i < 20) { - vector::push_back(&mut v, *vector::borrow(&data_A, i)); - i = i + 1; - }; - let i = 0; - while (i < 20) { - vector::push_back(&mut v, *vector::borrow(&data_B, i)); - i = i + 1; - }; - hash(&v) - } - - // greater_than returns whether a is strictly greater than b - // note that data(&a) and data(&b) are both vectors of length 20 - fun greater_than(a: &Bytes20, b: &Bytes20): bool { - // aa and bb both have length 20 - let a_vector = data(a); - let b_vector = data(b); - let i = 0; - while (i < 20) { - let a_value = *vector::borrow(&a_vector, i); - let b_value = *vector::borrow(&b_vector, i); - if (a_value > b_value) { - return true - } else if (b_value > a_value) { - return false - }; - i = i + 1; +module pyth::merkle_tree; + +use pyth::deserialize; +use sui::hash::keccak256; +use wormhole::bytes20::{Self, Bytes20, data}; +use wormhole::cursor::Cursor; + +#[test_only] +use wormhole::cursor; + +const MERKLE_LEAF_PREFIX: u8 = 0; +const MERKLE_NODE_PREFIX: u8 = 1; +const MERKLE_EMPTY_LEAF_PREFIX: u8 = 2; + +const E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES: u64 = 1212121; + +// take keccak256 of input data, then return 20 leftmost bytes of result +fun hash(bytes: &vector): Bytes20 { + let hashed_bytes = keccak256(bytes); + let mut hash_prefix = vector::empty(); + let mut i = 0; + while (i < 20) { + vector::push_back(&mut hash_prefix, *vector::borrow(&hashed_bytes, i)); + i = i + 1; + }; + bytes20::new(hash_prefix) +} + +fun empty_leaf_hash(): Bytes20 { + let v = vector[MERKLE_EMPTY_LEAF_PREFIX]; + hash(&v) +} + +fun leaf_hash(data: &vector): Bytes20 { + let mut v = vector[MERKLE_LEAF_PREFIX]; + let mut i = 0; + while (i < vector::length(data)) { + vector::push_back(&mut v, *vector::borrow(data, i)); + i = i + 1; + }; + hash(&v) +} + +fun node_hash(mut childA: Bytes20, mut childB: Bytes20): Bytes20 { + if (greater_than(&childA, &childB)) { + (childA, childB) = (childB, childA); + }; + // append data_B to data_A + let data_A = bytes20::data(&childA); + let data_B = bytes20::data(&childB); + + // create a vector containing MERKLE_NODE_PREFIX + data_A + data_B + let mut v = vector[MERKLE_NODE_PREFIX]; + let mut i = 0; + while (i < 20) { + vector::push_back(&mut v, *vector::borrow(&data_A, i)); + i = i + 1; + }; + let mut i = 0; + while (i < 20) { + vector::push_back(&mut v, *vector::borrow(&data_B, i)); + i = i + 1; + }; + hash(&v) +} + +// greater_than returns whether a is strictly greater than b +// note that data(&a) and data(&b) are both vectors of length 20 +fun greater_than(a: &Bytes20, b: &Bytes20): bool { + // aa and bb both have length 20 + let a_vector = data(a); + let b_vector = data(b); + let mut i = 0; + while (i < 20) { + let a_value = *vector::borrow(&a_vector, i); + let b_value = *vector::borrow(&b_vector, i); + if (a_value > b_value) { + return true + } else if (b_value > a_value) { + return false }; - false - } - - // The Sui Move stdlb insert function shifts v[i] and subsequent elements to the right. - // We don't want this behavior, so we define our own set_element function that instead replaces the ith element. - // Reference: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/move-stdlib/sources/vector.move - fun set_element(a: &mut vector, value: T, index: u64){ - vector::push_back(a, value); // push value to end - vector::swap_remove(a, index); // swap value to correct position and pop last value - } - - // is_proof_valid returns whether a merkle proof is valid - public fun is_proof_valid( - encoded_proof: &mut Cursor, - root: Bytes20, - leaf_data: vector, - ): bool { - let current_digest: Bytes20 = leaf_hash(&leaf_data); - let proofSize: u8 = deserialize::deserialize_u8(encoded_proof); - while (proofSize > 0){ - let sibling_digest: Bytes20 = bytes20::new( - deserialize::deserialize_vector(encoded_proof, 20) - ); + i = i + 1; + }; + false +} - current_digest = node_hash( +// The Sui Move stdlb insert function shifts v[i] and subsequent elements to the right. +// We don't want this behavior, so we define our own set_element function that instead replaces the ith element. +// Reference: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/move-stdlib/sources/vector.move +fun set_element(a: &mut vector, value: T, index: u64) { + vector::push_back(a, value); // push value to end + vector::swap_remove(a, index); // swap value to correct position and pop last value +} + +// is_proof_valid returns whether a merkle proof is valid +public fun is_proof_valid( + encoded_proof: &mut Cursor, + root: Bytes20, + leaf_data: vector, +): bool { + let mut current_digest: Bytes20 = leaf_hash(&leaf_data); + let mut proofSize: u8 = deserialize::deserialize_u8(encoded_proof); + while (proofSize > 0) { + let sibling_digest: Bytes20 = bytes20::new( + deserialize::deserialize_vector(encoded_proof, 20), + ); + + current_digest = + node_hash( current_digest, - sibling_digest + sibling_digest, ); - proofSize = proofSize - 1; - }; - bytes20::data(¤t_digest) == bytes20::data(&root) - } - - // construct_proofs constructs a merkle tree and returns the root of the tree as - // a Bytes20 as well as the vector of encoded proofs - public fun construct_proofs( - messages: &vector>, - depth: u8 - ) : (Bytes20, vector) { - - if ( 1 << depth < vector::length(messages)) { - abort E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES - }; + proofSize = proofSize - 1; + }; + bytes20::data(¤t_digest) == bytes20::data(&root) +} - // empty tree - // The tree is structured as follows: - // 1 - // 2 3 - // 4 5 6 7 - // ... - // In this structure the parent of node x is x//2 and the children - // of node x are x*2 and x*2 + 1. Also, the sibling of the node x - // is x^1. The root is at index 1 and index 0 is not used. - let tree = vector::empty(); - - // empty leaf hash - let cachedEmptyLeafHash: Bytes20 = empty_leaf_hash(); - - // Instantiate tree to be a full binary tree with the appropriate depth. - // Add an entry at the end for swapping - let i: u64 = 0; - while (i < (1 << (depth+1)) + 1){ - vector::push_back(&mut tree, cachedEmptyLeafHash); +// construct_proofs constructs a merkle tree and returns the root of the tree as +// a Bytes20 as well as the vector of encoded proofs +public fun construct_proofs(messages: &vector>, depth: u8): (Bytes20, vector) { + if (1 << depth < vector::length(messages)) { + abort E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES + }; + + // empty tree + // The tree is structured as follows: + // 1 + // 2 3 + // 4 5 6 7 + // ... + // In this structure the parent of node x is x//2 and the children + // of node x are x*2 and x*2 + 1. Also, the sibling of the node x + // is x^1. The root is at index 1 and index 0 is not used. + let mut tree = vector::empty(); + + // empty leaf hash + let cachedEmptyLeafHash: Bytes20 = empty_leaf_hash(); + + // Instantiate tree to be a full binary tree with the appropriate depth. + // Add an entry at the end for swapping + let mut i: u64 = 0; + while (i < (1 << (depth+1)) + 1) { + vector::push_back(&mut tree, cachedEmptyLeafHash); + i = i + 1; + }; + + // Fill in bottom row with leaf hashes + let mut j: u64 = 0; + while (j < vector::length(messages)) { + set_element(&mut tree, leaf_hash(vector::borrow(messages, j)), (1 << depth) + j); + j = j + 1; + }; + + // Filling the node hashes from bottom to top + let mut k: u8 = depth; + while (k>0) { + let level: u8 = k-1; + let levelNumNodes = 1 << level; + let mut i: u64 = 0; + while (i < levelNumNodes) { + let id = (1 << level) + i; + let node_hash = node_hash( + *vector::borrow(&tree, id * 2), + *vector::borrow(&tree, id * 2 + 1), + ); + set_element(&mut tree, node_hash, id); i = i + 1; }; - - // Fill in bottom row with leaf hashes - let j: u64 = 0; - while (j < vector::length(messages)){ - set_element(&mut tree, leaf_hash(vector::borrow(messages, j)), (1 << depth) + j); - j = j + 1; + k = k - 1; + }; + + let root = *vector::borrow(&tree, 1); + + // construct proofs and create encoded proofs vector + let mut proofs = vector::empty(); + let mut i: u64 = 0; + while (i < vector::length(messages)) { + let mut cur_proof = vector::empty(); + vector::push_back(&mut cur_proof, depth); + let mut idx = (1 << depth) + i; + while (idx > 1) { + vector::append(&mut cur_proof, bytes20::data(vector::borrow(&tree, idx ^ 1))); + + // Jump to parent + idx = idx / 2; }; + vector::append(&mut proofs, cur_proof); + i = i + 1; + }; - // Filling the node hashes from bottom to top - let k: u8 = depth; - while (k>0){ - let level: u8 = k-1; - let levelNumNodes = 1 << level; - let i: u64 = 0; - while (i < levelNumNodes ){ - let id = (1 << level) + i; - let node_hash = node_hash(*vector::borrow(&tree, id * 2), *vector::borrow(&tree, id * 2 + 1)); - set_element(&mut tree, node_hash, id); - i = i + 1; - }; - k = k - 1; - }; + (root, proofs) +} - let root = *vector::borrow(&tree, 1); - - // construct proofs and create encoded proofs vector - let proofs = vector::empty(); - let i: u64 = 0; - while (i < vector::length(messages)){ - let cur_proof = vector::empty(); - vector::push_back(&mut cur_proof, depth); - let idx = (1 << depth) + i; - while (idx > 1) { - vector::append(&mut cur_proof, bytes20::data(vector::borrow(&tree, idx ^ 1))); - - // Jump to parent - idx = idx / 2; - }; - vector::append(&mut proofs, cur_proof); - i = i + 1; - }; +#[test] +fun testGreaterThan() { + // test 1 + let mut x = bytes20::new(x"0000000000000000000000000000000000001000"); + let mut y = bytes20::new(x"0000000000000000000000000000000000000001"); + let mut res = greater_than(&x, &y); + assert!(res==true, 0); + res = greater_than(&y, &x); + assert!(res==false, 0); + + // test 2 + x = bytes20::new(x"1100000000000000000000000000000000001000"); + y = bytes20::new(x"1100000000000000000000000000000000000001"); + res = greater_than(&x, &y); + assert!(res==true, 0); + + // equality case + x = bytes20::new(x"1100000000000000000000000000000000001001"); + y = bytes20::new(x"1100000000000000000000000000000000001001"); + res = greater_than(&x, &y); + assert!(res==false, 0); +} + +#[test] +fun test_hash_leaf() { + let data: vector = + x"00640000000000000000000000000000000000000000000000000000000000000000000000000000640000000000000064000000640000000000000064000000000000006400000000000000640000000000000064"; + let hash = leaf_hash(&data); + let expected = bytes20::new(x"afc6a8ac466430f35895055f8a4c951785dad5ce"); + assert!(hash == expected, 1); +} + +#[test] +fun test_hash_node() { + let h1 = bytes20::new(x"05c51b04b820c0f704e3fdd2e4fc1e70aff26dff"); + let h2 = bytes20::new(x"1e108841c8d21c7a5c4860c8c3499c918ea9e0ac"); + let hash = node_hash(h1, h2); + let expected = bytes20::new(x"2d0e4fde68184c7ce8af426a0865bd41ef84dfa4"); + assert!(hash == expected, 1); +} + +#[test] +fun testMerkleTreeDepth1() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"1234"); + + let (root, proofs) = construct_proofs(&messages, 1); + + let mut proofs_cursor = cursor::new(proofs); + let valid = is_proof_valid(&mut proofs_cursor, root, x"1234"); + assert!(valid==true, 0); + + // destroy cursor + cursor::take_rest(proofs_cursor); +} + +#[test] +fun testMerkleTreeDepth2() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"1234"); + vector::push_back(&mut messages, x"4321"); + vector::push_back(&mut messages, x"11"); + vector::push_back(&mut messages, x"22"); + + let (root, proofs) = construct_proofs(&messages, 2); + + let mut proofs_cursor = cursor::new(proofs); + assert!(is_proof_valid(&mut proofs_cursor, root, x"1234")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"11")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); + + // destroy cursor + cursor::take_rest(proofs_cursor); +} + +#[test] +fun test_merkle_tree_depth_3() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"00"); + vector::push_back(&mut messages, x"4321"); + vector::push_back(&mut messages, x"444444"); + vector::push_back(&mut messages, x"22222222"); + vector::push_back(&mut messages, x"22"); + vector::push_back(&mut messages, x"11"); + vector::push_back(&mut messages, x"100000"); + vector::push_back(&mut messages, x"eeeeee"); + + let (root, proofs) = construct_proofs(&messages, 3); + + let mut proofs_cursor = cursor::new(proofs); + assert!(is_proof_valid(&mut proofs_cursor, root, x"00")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"444444")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"22222222")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"11")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"100000")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"eeeeee")==true, 0); + + // destroy cursor + cursor::take_rest(proofs_cursor); +} + +#[test] +fun test_merkle_tree_depth_1_invalid_proofs() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"1234"); + + let (root, proofs) = construct_proofs(&messages, 1); + + let mut proofs_cursor = cursor::new(proofs); + + // use wrong leaf data (it is not included in the tree) + let valid = is_proof_valid(&mut proofs_cursor, root, x"432222"); + assert!(valid==false, 0); + + // destroy cursor + cursor::take_rest(proofs_cursor); +} + +#[test] +fun test_merkle_tree_depth_2_invalid_proofs() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"1234"); + vector::push_back(&mut messages, x"4321"); + vector::push_back(&mut messages, x"11"); + vector::push_back(&mut messages, x"22"); + + let (root, proofs) = construct_proofs(&messages, 2); + + let mut proofs_cursor = cursor::new(proofs); + // proof fails because we used the proof of x"1234" to try to prove that x"4321" is in the tree + assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==false, 0); + // proof succeeds + assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==true, 0); + // proof fails because we used the proof of x"11" to try to prove that x"22" is in the tree + assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==false, 0); + // proof succeeds + assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); + + // destroy cursor + cursor::take_rest(proofs_cursor); +} + +#[test] +fun test_merkle_tree_depth_3_invalid_proofs() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"00"); + vector::push_back(&mut messages, x"4321"); + vector::push_back(&mut messages, x"444444"); + vector::push_back(&mut messages, x"22222222"); + vector::push_back(&mut messages, x"22"); + vector::push_back(&mut messages, x"11"); + vector::push_back(&mut messages, x"100000"); + vector::push_back(&mut messages, x"eeeeee"); + + let (root, proofs) = construct_proofs(&messages, 3); + + let mut proofs_cursor = cursor::new(proofs); + + // test various proof failure cases (because of mismatch between proof and leaf data) + assert!(is_proof_valid(&mut proofs_cursor, root, x"00")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==false, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"22222222")==false, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"22222222")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"eeeeee")==false, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==false, 0); + assert!(is_proof_valid(&mut proofs_cursor, root, x"eeeeee")==true, 0); + + // destroy cursor + cursor::take_rest(proofs_cursor); +} + +#[test, expected_failure(abort_code = pyth::merkle_tree::E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES)] +fun test_merkle_tree_depth_exceeded_1() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"00"); + vector::push_back(&mut messages, x"4321"); + vector::push_back(&mut messages, x"444444"); + + construct_proofs(&messages, 1); //depth 1 +} - (root, proofs) - } - - #[test] - fun testGreaterThan(){ - // test 1 - let x = bytes20::new(x"0000000000000000000000000000000000001000"); - let y = bytes20::new(x"0000000000000000000000000000000000000001"); - let res = greater_than(&x, &y); - assert!(res==true, 0); - res = greater_than(&y, &x); - assert!(res==false, 0); - - // test 2 - x = bytes20::new(x"1100000000000000000000000000000000001000"); - y = bytes20::new(x"1100000000000000000000000000000000000001"); - res = greater_than(&x, &y); - assert!(res==true, 0); - - // equality case - x = bytes20::new(x"1100000000000000000000000000000000001001"); - y = bytes20::new(x"1100000000000000000000000000000000001001"); - res = greater_than(&x, &y); - assert!(res==false, 0); - } - - #[test] - fun test_hash_leaf() { - let data: vector = x"00640000000000000000000000000000000000000000000000000000000000000000000000000000640000000000000064000000640000000000000064000000000000006400000000000000640000000000000064"; - let hash = leaf_hash(&data); - let expected = bytes20::new(x"afc6a8ac466430f35895055f8a4c951785dad5ce"); - assert!(hash == expected, 1); - } - - #[test] - fun test_hash_node() { - let h1 = bytes20::new(x"05c51b04b820c0f704e3fdd2e4fc1e70aff26dff"); - let h2 = bytes20::new(x"1e108841c8d21c7a5c4860c8c3499c918ea9e0ac"); - let hash = node_hash(h1, h2); - let expected = bytes20::new(x"2d0e4fde68184c7ce8af426a0865bd41ef84dfa4"); - assert!(hash == expected, 1); - } - - #[test] - fun testMerkleTreeDepth1(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"1234"); - - let (root, proofs) = construct_proofs(&messages, 1); - - let proofs_cursor = cursor::new(proofs); - let valid = is_proof_valid(&mut proofs_cursor, root, x"1234"); - assert!(valid==true, 0); - - // destroy cursor - cursor::take_rest(proofs_cursor); - } - - #[test] - fun testMerkleTreeDepth2(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"1234"); - vector::push_back(&mut messages, x"4321"); - vector::push_back(&mut messages, x"11"); - vector::push_back(&mut messages, x"22"); - - let (root, proofs) = construct_proofs(&messages, 2); - - let proofs_cursor = cursor::new(proofs); - assert!(is_proof_valid(&mut proofs_cursor, root, x"1234")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"11")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); - - // destroy cursor - cursor::take_rest(proofs_cursor); - } - - #[test] - fun test_merkle_tree_depth_3(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"00"); - vector::push_back(&mut messages, x"4321"); - vector::push_back(&mut messages, x"444444"); - vector::push_back(&mut messages, x"22222222"); - vector::push_back(&mut messages, x"22"); - vector::push_back(&mut messages, x"11"); - vector::push_back(&mut messages, x"100000"); - vector::push_back(&mut messages, x"eeeeee"); - - let (root, proofs) = construct_proofs(&messages, 3); - - let proofs_cursor = cursor::new(proofs); - assert!(is_proof_valid(&mut proofs_cursor, root, x"00")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"444444")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"22222222")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"11")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"100000")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"eeeeee")==true, 0); - - // destroy cursor - cursor::take_rest(proofs_cursor); - } - - #[test] - fun test_merkle_tree_depth_1_invalid_proofs(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"1234"); - - let (root, proofs) = construct_proofs(&messages, 1); - - let proofs_cursor = cursor::new(proofs); - - // use wrong leaf data (it is not included in the tree) - let valid = is_proof_valid(&mut proofs_cursor, root, x"432222"); - assert!(valid==false, 0); - - // destroy cursor - cursor::take_rest(proofs_cursor); - } - - #[test] - fun test_merkle_tree_depth_2_invalid_proofs(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"1234"); - vector::push_back(&mut messages, x"4321"); - vector::push_back(&mut messages, x"11"); - vector::push_back(&mut messages, x"22"); - - let (root, proofs) = construct_proofs(&messages, 2); - - let proofs_cursor = cursor::new(proofs); - // proof fails because we used the proof of x"1234" to try to prove that x"4321" is in the tree - assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==false, 0); - // proof succeeds - assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==true, 0); - // proof fails because we used the proof of x"11" to try to prove that x"22" is in the tree - assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==false, 0); - // proof succeeds - assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); - - // destroy cursor - cursor::take_rest(proofs_cursor); - } - - #[test] - fun test_merkle_tree_depth_3_invalid_proofs(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"00"); - vector::push_back(&mut messages, x"4321"); - vector::push_back(&mut messages, x"444444"); - vector::push_back(&mut messages, x"22222222"); - vector::push_back(&mut messages, x"22"); - vector::push_back(&mut messages, x"11"); - vector::push_back(&mut messages, x"100000"); - vector::push_back(&mut messages, x"eeeeee"); - - let (root, proofs) = construct_proofs(&messages, 3); - - let proofs_cursor = cursor::new(proofs); - - // test various proof failure cases (because of mismatch between proof and leaf data) - assert!(is_proof_valid(&mut proofs_cursor, root, x"00")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==false, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"22222222")==false, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"22222222")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"22")==true, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"eeeeee")==false, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"4321")==false, 0); - assert!(is_proof_valid(&mut proofs_cursor, root, x"eeeeee")==true, 0); - - // destroy cursor - cursor::take_rest(proofs_cursor); - } - - #[test] - #[expected_failure(abort_code = pyth::merkle_tree::E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES)] - fun test_merkle_tree_depth_exceeded_1(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"00"); - vector::push_back(&mut messages, x"4321"); - vector::push_back(&mut messages, x"444444"); - - construct_proofs(&messages, 1); //depth 1 - } - - #[test] - #[expected_failure(abort_code = pyth::merkle_tree::E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES)] - fun test_merkle_tree_depth_exceeded_2(){ - let messages = vector::empty>(); - vector::push_back(&mut messages, x"00"); - vector::push_back(&mut messages, x"4321"); - vector::push_back(&mut messages, x"444444"); - vector::push_back(&mut messages, x"22222222"); - vector::push_back(&mut messages, x"22"); - - construct_proofs(&messages, 2); // depth 2 - } +#[test, expected_failure(abort_code = pyth::merkle_tree::E_DEPTH_NOT_LARGE_ENOUGH_FOR_MESSAGES)] +fun test_merkle_tree_depth_exceeded_2() { + let mut messages = vector::empty>(); + vector::push_back(&mut messages, x"00"); + vector::push_back(&mut messages, x"4321"); + vector::push_back(&mut messages, x"444444"); + vector::push_back(&mut messages, x"22222222"); + vector::push_back(&mut messages, x"22"); + construct_proofs(&messages, 2); // depth 2 } diff --git a/target_chains/sui/contracts/sources/migrate.move b/target_chains/sui/contracts/sources/migrate.move index 394691e6cd..145226a8ee 100644 --- a/target_chains/sui/contracts/sources/migrate.move +++ b/target_chains/sui/contracts/sources/migrate.move @@ -1,6 +1,5 @@ // SPDX-License-Identifier: Apache 2 - /// Note: this module is adapted from Wormhole's migrade.move module. /// /// This module implements a public method intended to be called after an @@ -10,68 +9,59 @@ /// Included in migration is the ability to ensure that breaking changes for /// any of Pyth's methods by enforcing the current build version as /// their required minimum version. -module pyth::migrate { - use sui::object::{ID}; - - use pyth::state::{Self, State}; - use pyth::contract_upgrade::{Self}; - use pyth::governance::{WormholeVAAVerificationReceipt}; +module pyth::migrate; - struct MigrateComplete has drop, copy { - package: ID - } +use pyth::contract_upgrade; +use pyth::governance::WormholeVAAVerificationReceipt; +use pyth::state::{Self, State}; - public fun migrate( - pyth_state: &mut State, - receipt: WormholeVAAVerificationReceipt, - ) { +public struct MigrateComplete has copy, drop { + package: ID, +} - // Perform standard migrate. - handle_migrate(pyth_state, receipt); +public fun migrate(pyth_state: &mut State, receipt: WormholeVAAVerificationReceipt) { + // Perform standard migrate. + handle_migrate(pyth_state, receipt); - //////////////////////////////////////////////////////////////////////// - // - // NOTE: Put any one-off migration logic here. - // - // Most upgrades likely won't need to do anything, in which case the - // rest of this function's body may be empty. Make sure to delete it - // after the migration has gone through successfully. - // - // WARNING: The migration does *not* proceed atomically with the - // upgrade (as they are done in separate transactions). - // If the nature of this migration absolutely requires the migration to - // happen before certain other functionality is available, then guard - // that functionality with the `assert!` from above. - // - //////////////////////////////////////////////////////////////////////// + //////////////////////////////////////////////////////////////////////// + // + // NOTE: Put any one-off migration logic here. + // + // Most upgrades likely won't need to do anything, in which case the + // rest of this function's body may be empty. Make sure to delete it + // after the migration has gone through successfully. + // + // WARNING: The migration does *not* proceed atomically with the + // upgrade (as they are done in separate transactions). + // If the nature of this migration absolutely requires the migration to + // happen before certain other functionality is available, then guard + // that functionality with the `assert!` from above. + // + //////////////////////////////////////////////////////////////////////// - //////////////////////////////////////////////////////////////////////// - } + //////////////////////////////////////////////////////////////////////// +} - fun handle_migrate( - pyth_state: &mut State, - receipt: WormholeVAAVerificationReceipt, - ) { - // See `version_control` module for hard-coded configuration. - state::migrate_version(pyth_state); +fun handle_migrate(pyth_state: &mut State, receipt: WormholeVAAVerificationReceipt) { + // See `version_control` module for hard-coded configuration. + state::migrate_version(pyth_state); - // This capability ensures that the current build version is used. - let latest_only = state::assert_latest_only(pyth_state); + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(pyth_state); - let digest = contract_upgrade::take_upgrade_digest(receipt); - state::assert_authorized_digest( - &latest_only, - pyth_state, - digest - ); + let digest = contract_upgrade::take_upgrade_digest(receipt); + state::assert_authorized_digest( + &latest_only, + pyth_state, + digest, + ); - // Finally emit an event reflecting a successful migrate. - let package = state::current_package(&latest_only, pyth_state); - sui::event::emit(MigrateComplete { package }); - } + // Finally emit an event reflecting a successful migrate. + let package = state::current_package(&latest_only, pyth_state); + sui::event::emit(MigrateComplete { package }); +} - #[test_only] - public fun set_up_migrate(pyth_state: &mut State) { - state::reverse_migrate__v__0_1_0(pyth_state); - } +#[test_only] +public fun set_up_migrate(pyth_state: &mut State) { + state::reverse_migrate__v__0_1_0(pyth_state); } diff --git a/target_chains/sui/contracts/sources/price.move b/target_chains/sui/contracts/sources/price.move index ea92602b9e..cc0d414c2d 100644 --- a/target_chains/sui/contracts/sources/price.move +++ b/target_chains/sui/contracts/sources/price.move @@ -1,46 +1,46 @@ -module pyth::price { - use pyth::i64::I64; +module pyth::price; - /// A price with a degree of uncertainty, represented as a price +- a confidence interval. - /// - /// The confidence interval roughly corresponds to the standard error of a normal distribution. - /// Both the price and confidence are stored in a fixed-point numeric representation, - /// `x * (10^expo)`, where `expo` is the exponent. - // - /// Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how - /// to how this price safely. - struct Price has copy, drop, store { - price: I64, - /// Confidence interval around the price - conf: u64, - /// The exponent - expo: I64, - /// Unix timestamp of when this price was computed - timestamp: u64, - } +use pyth::i64::I64; - public fun new(price: I64, conf: u64, expo: I64, timestamp: u64): Price { - Price { - price, - conf, - expo, - timestamp, - } - } +/// A price with a degree of uncertainty, represented as a price +- a confidence interval. +/// +/// The confidence interval roughly corresponds to the standard error of a normal distribution. +/// Both the price and confidence are stored in a fixed-point numeric representation, +/// `x * (10^expo)`, where `expo` is the exponent. +/// +/// Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how +/// to how this price safely. +public struct Price has copy, drop, store { + price: I64, + /// Confidence interval around the price + conf: u64, + /// The exponent + expo: I64, + /// Unix timestamp of when this price was computed + timestamp: u64, +} - public fun get_price(price: &Price): I64 { - price.price +public fun new(price: I64, conf: u64, expo: I64, timestamp: u64): Price { + Price { + price, + conf, + expo, + timestamp, } +} - public fun get_conf(price: &Price): u64 { - price.conf - } +public fun get_price(price: &Price): I64 { + price.price +} - public fun get_timestamp(price: &Price): u64 { - price.timestamp - } +public fun get_conf(price: &Price): u64 { + price.conf +} - public fun get_expo(price: &Price): I64 { - price.expo - } +public fun get_timestamp(price: &Price): u64 { + price.timestamp +} + +public fun get_expo(price: &Price): I64 { + price.expo } diff --git a/target_chains/sui/contracts/sources/price_feed.move b/target_chains/sui/contracts/sources/price_feed.move index 11a236f833..e659ffabbf 100644 --- a/target_chains/sui/contracts/sources/price_feed.move +++ b/target_chains/sui/contracts/sources/price_feed.move @@ -1,47 +1,48 @@ -module pyth::price_feed { - use pyth::price_identifier::PriceIdentifier; - use pyth::price::Price; - - /// PriceFeed represents a current aggregate price for a particular product. - struct PriceFeed has copy, drop, store { - /// The price identifier - price_identifier: PriceIdentifier, - /// The current aggregate price - price: Price, - /// The current exponentially moving average aggregate price - ema_price: Price, - } +module pyth::price_feed; - public fun new( - price_identifier: PriceIdentifier, - price: Price, - ema_price: Price): PriceFeed { - PriceFeed { - price_identifier, - price, - ema_price, - } - } +use pyth::price::Price; +use pyth::price_identifier::PriceIdentifier; - public fun from( - price_feed: &PriceFeed - ): PriceFeed { - PriceFeed { - price_identifier: price_feed.price_identifier, - price: price_feed.price, - ema_price: price_feed.ema_price, - } - } +/// PriceFeed represents a current aggregate price for a particular product. +public struct PriceFeed has copy, drop, store { + /// The price identifier + price_identifier: PriceIdentifier, + /// The current aggregate price + price: Price, + /// The current exponentially moving average aggregate price + ema_price: Price, +} - public fun get_price_identifier(price_feed: &PriceFeed): PriceIdentifier { - price_feed.price_identifier +public fun new(price_identifier: PriceIdentifier, price: Price, ema_price: Price): PriceFeed { + PriceFeed { + price_identifier, + price, + ema_price, } +} - public fun get_price(price_feed: &PriceFeed): Price { - price_feed.price +public fun from(price_feed: &PriceFeed): PriceFeed { + PriceFeed { + price_identifier: price_feed.price_identifier, + price: price_feed.price, + ema_price: price_feed.ema_price, } +} - public fun get_ema_price(price_feed: &PriceFeed): Price { - price_feed.ema_price - } +public use fun get_price_identifier as PriceFeed.price_identifier; + +public fun get_price_identifier(price_feed: &PriceFeed): PriceIdentifier { + price_feed.price_identifier +} + +public use fun get_price as PriceFeed.price; + +public fun get_price(price_feed: &PriceFeed): Price { + price_feed.price +} + +public use fun get_ema_price as PriceFeed.ema_price; + +public fun get_ema_price(price_feed: &PriceFeed): Price { + price_feed.ema_price } diff --git a/target_chains/sui/contracts/sources/price_identifier.move b/target_chains/sui/contracts/sources/price_identifier.move index dc05d22f29..7abd8cc1ee 100644 --- a/target_chains/sui/contracts/sources/price_identifier.move +++ b/target_chains/sui/contracts/sources/price_identifier.move @@ -1,21 +1,17 @@ -module pyth::price_identifier { - use std::vector; +module pyth::price_identifier; - const IDENTIFIER_BYTES_LENGTH: u64 = 32; - const E_INCORRECT_IDENTIFIER_LENGTH: u64 = 0; +const IDENTIFIER_BYTES_LENGTH: u64 = 32; +const EIncorrectIdentifierLength: u64 = 0; - struct PriceIdentifier has copy, drop, store { - bytes: vector, - } +public struct PriceIdentifier has copy, drop, store { + bytes: vector, +} - public fun from_byte_vec(bytes: vector): PriceIdentifier { - assert!(vector::length(&bytes) == IDENTIFIER_BYTES_LENGTH, E_INCORRECT_IDENTIFIER_LENGTH); - PriceIdentifier { - bytes - } - } +public fun from_byte_vec(bytes: vector): PriceIdentifier { + assert!(vector::length(&bytes) == IDENTIFIER_BYTES_LENGTH, EIncorrectIdentifierLength); + PriceIdentifier { bytes, } +} - public fun get_bytes(price_identifier: &PriceIdentifier): vector { - price_identifier.bytes - } +public fun get_bytes(price_identifier: &PriceIdentifier): vector { + price_identifier.bytes } diff --git a/target_chains/sui/contracts/sources/price_info.move b/target_chains/sui/contracts/sources/price_info.move index 8f5cea1346..4eb78572e8 100644 --- a/target_chains/sui/contracts/sources/price_info.move +++ b/target_chains/sui/contracts/sources/price_info.move @@ -1,223 +1,220 @@ -module pyth::price_info { - use sui::object::{Self, UID, ID}; - use sui::tx_context::{TxContext}; - use sui::dynamic_object_field::{Self}; - use sui::table::{Self}; - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - - use pyth::price_feed::{Self, PriceFeed}; - use pyth::price_identifier::{PriceIdentifier}; - - const KEY: vector = b"price_info"; - const FEE_STORAGE_KEY: vector = b"fee_storage"; - const E_PRICE_INFO_REGISTRY_ALREADY_EXISTS: u64 = 0; - const E_PRICE_IDENTIFIER_ALREADY_REGISTERED: u64 = 1; - const E_PRICE_IDENTIFIER_NOT_REGISTERED: u64 = 2; - - friend pyth::pyth; - friend pyth::state; - - /// Sui object version of PriceInfo. - /// Has a key ability, is unique for each price identifier, and lives in global store. - struct PriceInfoObject has key, store { - id: UID, - price_info: PriceInfo - } +module pyth::price_info; + +use pyth::price_feed::{Self, PriceFeed}; +use pyth::price_identifier::PriceIdentifier; +use sui::coin::{Self, Coin}; +use sui::dynamic_object_field; +use sui::sui::SUI; +use sui::table; + +const KEY: vector = b"price_info"; +const FEE_STORAGE_KEY: vector = b"fee_storage"; +const EPriceInfoRegistryAlreadyExists: u64 = 0; +const EPriceIdentifierAlreadyRegistered: u64 = 1; +const EPriceIdentifierNotRegistered: u64 = 2; + +/// Sui object version of PriceInfo. +/// Has a key ability, is unique for each price identifier, and lives in global store. +public struct PriceInfoObject has key, store { + id: UID, + price_info: PriceInfo, +} - /// Copyable and droppable. - struct PriceInfo has copy, drop, store { - attestation_time: u64, - arrival_time: u64, - price_feed: PriceFeed, - } +/// Copyable and droppable. +public struct PriceInfo has copy, drop, store { + attestation_time: u64, + arrival_time: u64, + price_feed: PriceFeed, +} - /// Creates a table which maps a PriceIdentifier to the - /// UID (in bytes) of the corresponding Sui PriceInfoObject. - public(friend) fun new_price_info_registry(parent_id: &mut UID, ctx: &mut TxContext) { - assert!( - !dynamic_object_field::exists_(parent_id, KEY), - E_PRICE_INFO_REGISTRY_ALREADY_EXISTS - ); - dynamic_object_field::add( - parent_id, - KEY, - table::new(ctx) - ) - } +/// Creates a table which maps a PriceIdentifier to the +/// UID (in bytes) of the corresponding Sui PriceInfoObject. +public(package) fun new_price_info_registry(parent_id: &mut UID, ctx: &mut TxContext) { + assert!(!dynamic_object_field::exists_(parent_id, KEY), EPriceInfoRegistryAlreadyExists); + dynamic_object_field::add( + parent_id, + KEY, + table::new(ctx), + ) +} - public(friend) fun add(parent_id: &mut UID, price_identifier: PriceIdentifier, id: ID) { - assert!( - !contains(parent_id, price_identifier), - E_PRICE_IDENTIFIER_ALREADY_REGISTERED - ); - table::add( - dynamic_object_field::borrow_mut(parent_id, KEY), - price_identifier, - id - ) - } +public(package) fun add(parent_id: &mut UID, price_identifier: PriceIdentifier, id: ID) { + assert!(!contains(parent_id, price_identifier), EPriceIdentifierAlreadyRegistered); + table::add( + dynamic_object_field::borrow_mut(parent_id, KEY), + price_identifier, + id, + ) +} +/// Returns ID of price info object corresponding to price_identifier as a byte vector. +public fun get_id_bytes(parent_id: &UID, price_identifier: PriceIdentifier): vector { + assert!(contains(parent_id, price_identifier), EPriceIdentifierNotRegistered); + object::id_to_bytes( + table::borrow( + dynamic_object_field::borrow(parent_id, KEY), + price_identifier, + ), + ) +} - /// Returns ID of price info object corresponding to price_identifier as a byte vector. - public fun get_id_bytes(parent_id: &UID, price_identifier: PriceIdentifier): vector { - assert!( - contains(parent_id, price_identifier), - E_PRICE_IDENTIFIER_NOT_REGISTERED - ); +/// Returns ID of price info object corresponding to price_identifier as an ID. +public fun get_id(parent_id: &UID, price_identifier: PriceIdentifier): ID { + assert!(contains(parent_id, price_identifier), EPriceIdentifierNotRegistered); + object::id_from_bytes( object::id_to_bytes( table::borrow( dynamic_object_field::borrow(parent_id, KEY), - price_identifier - ) - ) - } - - /// Returns ID of price info object corresponding to price_identifier as an ID. - public fun get_id(parent_id: &UID, price_identifier: PriceIdentifier): ID { - assert!( - contains(parent_id, price_identifier), - E_PRICE_IDENTIFIER_NOT_REGISTERED - ); - object::id_from_bytes( - object::id_to_bytes( - table::borrow( - dynamic_object_field::borrow(parent_id, KEY), - price_identifier - ) - ) - ) - } + price_identifier, + ), + ), + ) +} - public fun contains(parent_id: &UID, price_identifier: PriceIdentifier): bool { - let ref = dynamic_object_field::borrow(parent_id, KEY); - table::contains(ref, price_identifier) - } +public fun contains(parent_id: &UID, price_identifier: PriceIdentifier): bool { + let ref = dynamic_object_field::borrow(parent_id, KEY); + table::contains(ref, price_identifier) +} - public fun get_balance(price_info_object: &PriceInfoObject): u64 { - if (!dynamic_object_field::exists_with_type, Coin>(&price_info_object.id, FEE_STORAGE_KEY)) { - return 0 - }; - let fee = dynamic_object_field::borrow, Coin>(&price_info_object.id, FEE_STORAGE_KEY); - coin::value(fee) - } +public fun get_balance(price_info_object: &PriceInfoObject): u64 { + if ( + !dynamic_object_field::exists_with_type, Coin>( + &price_info_object.id, + FEE_STORAGE_KEY, + ) + ) { + return 0 + }; + let fee = dynamic_object_field::borrow, Coin>( + &price_info_object.id, + FEE_STORAGE_KEY, + ); + coin::value(fee) +} - public fun deposit_fee_coins(price_info_object: &mut PriceInfoObject, fee_coins: Coin) { - if (!dynamic_object_field::exists_with_type, Coin>(&price_info_object.id, FEE_STORAGE_KEY)) { - dynamic_object_field::add(&mut price_info_object.id, FEE_STORAGE_KEY, fee_coins); - } - else { - let current_fee = dynamic_object_field::borrow_mut, Coin>( - &mut price_info_object.id, - FEE_STORAGE_KEY - ); - coin::join(current_fee, fee_coins); - }; - } +public fun deposit_fee_coins(price_info_object: &mut PriceInfoObject, fee_coins: Coin) { + if ( + !dynamic_object_field::exists_with_type, Coin>( + &price_info_object.id, + FEE_STORAGE_KEY, + ) + ) { + dynamic_object_field::add(&mut price_info_object.id, FEE_STORAGE_KEY, fee_coins); + } else { + let current_fee = dynamic_object_field::borrow_mut, Coin>( + &mut price_info_object.id, + FEE_STORAGE_KEY, + ); + coin::join(current_fee, fee_coins); + }; +} - public(friend) fun new_price_info_object( - price_info: PriceInfo, - ctx: &mut TxContext - ): PriceInfoObject { - PriceInfoObject { - id: object::new(ctx), - price_info - } +public(package) fun new_price_info_object( + price_info: PriceInfo, + ctx: &mut TxContext, +): PriceInfoObject { + PriceInfoObject { + id: object::new(ctx), + price_info, } +} - public fun new_price_info( - attestation_time: u64, - arrival_time: u64, - price_feed: PriceFeed, - ): PriceInfo { - PriceInfo { - attestation_time, - arrival_time, - price_feed, - } +public fun new_price_info( + attestation_time: u64, + arrival_time: u64, + price_feed: PriceFeed, +): PriceInfo { + PriceInfo { + attestation_time, + arrival_time, + price_feed, } +} - #[test] - public fun test_get_price_info_object_id_from_price_identifier(){ - use sui::object::{Self}; - use sui::test_scenario::{Self, ctx}; - use pyth::price_identifier::{Self}; - let scenario = test_scenario::begin(@pyth); - let uid = object::new(ctx(&mut scenario)); +#[test] +public fun test_get_price_info_object_id_from_price_identifier() { + use sui::test_scenario::{Self, ctx}; + use pyth::price_identifier; + let mut scenario = test_scenario::begin(@pyth); + let mut uid = object::new(ctx(&mut scenario)); - // Create a new price info object registry. - new_price_info_registry(&mut uid, ctx(&mut scenario)); + // Create a new price info object registry. + new_price_info_registry(&mut uid, ctx(&mut scenario)); - // Register a price info object in the registry. - let price_identifier = price_identifier::from_byte_vec(x"ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"); + // Register a price info object in the registry. + let price_identifier = price_identifier::from_byte_vec( + x"ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace", + ); - // Create a new ID. - let id = object::id_from_bytes(x"19f253b07e88634bfd5a3a749f60bfdb83c9748910646803f06b60b76319e7ba"); + // Create a new ID. + let id = object::id_from_bytes( + x"19f253b07e88634bfd5a3a749f60bfdb83c9748910646803f06b60b76319e7ba", + ); - add(&mut uid, price_identifier, id); + add(&mut uid, price_identifier, id); - let result = get_id_bytes(&uid, price_identifier); + let result = get_id_bytes(&uid, price_identifier); - // Assert that ID matches original. - assert!(result==x"19f253b07e88634bfd5a3a749f60bfdb83c9748910646803f06b60b76319e7ba", 0); + // Assert that ID matches original. + assert!(result==x"19f253b07e88634bfd5a3a749f60bfdb83c9748910646803f06b60b76319e7ba", 0); - // Clean up. - object::delete(uid); - test_scenario::end(scenario); - } + // Clean up. + object::delete(uid); + test_scenario::end(scenario); +} - #[test_only] - public fun destroy(price_info: PriceInfoObject) { - let PriceInfoObject { - id, - price_info: _, - } = price_info; - object::delete(id); - } +#[test_only] +public fun destroy(price_info: PriceInfoObject) { + let PriceInfoObject { + id, + price_info: _, + } = price_info; + object::delete(id); +} - #[test_only] - public fun new_price_info_object_for_test( - price_info: PriceInfo, - ctx: &mut TxContext - ): PriceInfoObject { - PriceInfoObject { - id: object::new(ctx), - price_info - } +#[test_only] +public fun new_price_info_object_for_test( + price_info: PriceInfo, + ctx: &mut TxContext, +): PriceInfoObject { + PriceInfoObject { + id: object::new(ctx), + price_info, } +} - public fun uid_to_inner(price_info: &PriceInfoObject): ID { - object::uid_to_inner(&price_info.id) - } +public fun uid_to_inner(price_info: &PriceInfoObject): ID { + object::uid_to_inner(&price_info.id) +} - public fun get_price_info_from_price_info_object(price_info: &PriceInfoObject): PriceInfo { - price_info.price_info - } +public fun get_price_info_from_price_info_object(price_info: &PriceInfoObject): PriceInfo { + price_info.price_info +} - public fun get_price_identifier(price_info: &PriceInfo): PriceIdentifier { - price_feed::get_price_identifier(&price_info.price_feed) - } +public fun get_price_identifier(price_info: &PriceInfo): PriceIdentifier { + price_feed::get_price_identifier(&price_info.price_feed) +} - public fun get_price_feed(price_info: &PriceInfo): &PriceFeed { - &price_info.price_feed - } +public fun get_price_feed(price_info: &PriceInfo): &PriceFeed { + &price_info.price_feed +} - public fun get_attestation_time(price_info: &PriceInfo): u64 { - price_info.attestation_time - } +public fun get_attestation_time(price_info: &PriceInfo): u64 { + price_info.attestation_time +} - public fun get_arrival_time(price_info: &PriceInfo): u64 { - price_info.arrival_time - } +public fun get_arrival_time(price_info: &PriceInfo): u64 { + price_info.arrival_time +} - public(friend) fun update_price_info_object( - price_info_object: &mut PriceInfoObject, - price_info: &PriceInfo - ) { - price_info_object.price_info = new_price_info( +public(package) fun update_price_info_object( + price_info_object: &mut PriceInfoObject, + price_info: &PriceInfo, +) { + price_info_object.price_info = + new_price_info( price_info.attestation_time, price_info.arrival_time, - price_info.price_feed + price_info.price_feed, ); - } } diff --git a/target_chains/sui/contracts/sources/price_status.move b/target_chains/sui/contracts/sources/price_status.move index 031649ff7d..aab90cda07 100644 --- a/target_chains/sui/contracts/sources/price_status.move +++ b/target_chains/sui/contracts/sources/price_status.move @@ -1,53 +1,53 @@ -module pyth::price_status { - //use pyth::error; - - /// The price feed is not currently updating for an unknown reason. - const UNKNOWN: u64 = 0; - /// The price feed is updating as expected. - const TRADING: u64 = 1; - - /// PriceStatus represents the availability status of a price feed. - /// Prices should only be used if they have a status of trading. - struct PriceStatus has copy, drop, store { - status: u64, - } +module pyth::price_status; - public fun from_u64(status: u64): PriceStatus { - assert!(status <= TRADING, 0); - PriceStatus { - status - } - } +//use pyth::error; - public fun get_status(price_status: &PriceStatus): u64 { - price_status.status - } +/// The price feed is not currently updating for an unknown reason. +const UNKNOWN: u64 = 0; +/// The price feed is updating as expected. +const TRADING: u64 = 1; - public fun new_unknown(): PriceStatus { - PriceStatus { - status: UNKNOWN, - } - } +/// PriceStatus represents the availability status of a price feed. +/// Prices should only be used if they have a status of trading. +public struct PriceStatus has copy, drop, store { + status: u64, +} - public fun new_trading(): PriceStatus { - PriceStatus { - status: TRADING, - } +public fun from_u64(status: u64): PriceStatus { + assert!(status <= TRADING, 0); + PriceStatus { + status, } +} - #[test] - fun test_unknown_status() { - assert!(PriceStatus{ status: UNKNOWN } == from_u64(0), 1); - } +public fun get_status(price_status: &PriceStatus): u64 { + price_status.status +} - #[test] - fun test_trading_status() { - assert!(PriceStatus{ status: TRADING } == from_u64(1), 1); +public fun new_unknown(): PriceStatus { + PriceStatus { + status: UNKNOWN, } +} - #[test] - #[expected_failure] - fun test_invalid_price_status() { - from_u64(3); +public fun new_trading(): PriceStatus { + PriceStatus { + status: TRADING, } } + +#[test] +fun test_unknown_status() { + assert!(PriceStatus{ status: UNKNOWN } == from_u64(0), 1); +} + +#[test] +fun test_trading_status() { + assert!(PriceStatus{ status: TRADING } == from_u64(1), 1); +} + +#[test] +#[expected_failure] +fun test_invalid_price_status() { + from_u64(3); +} diff --git a/target_chains/sui/contracts/sources/pyth.move b/target_chains/sui/contracts/sources/pyth.move index 7d7ce52d4f..d35d3ca2c5 100644 --- a/target_chains/sui/contracts/sources/pyth.move +++ b/target_chains/sui/contracts/sources/pyth.move @@ -1,102 +1,152 @@ -module pyth::pyth { - use std::vector; - use sui::tx_context::{TxContext}; - use sui::coin::{Self, Coin}; - use sui::sui::{SUI}; - use sui::transfer::{Self}; - use sui::clock::{Self, Clock}; - use sui::package::{UpgradeCap}; - - use pyth::event::{Self as pyth_event}; - use pyth::data_source::{Self, DataSource}; - use pyth::state::{Self as state, State as PythState, LatestOnly}; - use pyth::price_info::{Self, PriceInfo, PriceInfoObject}; - use pyth::batch_price_attestation::{Self}; - use pyth::price_feed::{Self}; - use pyth::price::{Self, Price}; - use pyth::price_identifier::{PriceIdentifier}; - use pyth::setup::{Self, DeployerCap}; - use pyth::hot_potato_vector::{Self, HotPotatoVector}; - use pyth::accumulator::{Self}; - - use wormhole::external_address::{Self}; - use wormhole::vaa::{Self, VAA}; - use wormhole::bytes32::{Self}; - use wormhole::cursor::{Self}; - - const E_DATA_SOURCE_EMITTER_ADDRESS_AND_CHAIN_IDS_DIFFERENT_LENGTHS: u64 = 0; - const E_INVALID_DATA_SOURCE: u64 = 1; - const E_INSUFFICIENT_FEE: u64 = 2; - const E_STALE_PRICE_UPDATE: u64 = 3; - const E_UPDATE_AND_PRICE_INFO_OBJECT_MISMATCH: u64 = 4; - const E_PRICE_UPDATE_NOT_FOUND_FOR_PRICE_INFO_OBJECT: u64 = 5; - - #[test_only] - friend pyth::pyth_tests; +module pyth::pyth; + +use pyth::accumulator; +use pyth::batch_price_attestation; +use pyth::data_source::{Self, DataSource}; +use pyth::event as pyth_event; +use pyth::hot_potato_vector::{Self, HotPotatoVector}; +use pyth::price::{Self, Price}; +use pyth::price_feed; +use pyth::price_identifier::PriceIdentifier; +use pyth::price_info::{Self, PriceInfo, PriceInfoObject}; +use pyth::setup::{Self, DeployerCap}; +use pyth::state::{Self as state, State as PythState, LatestOnly}; +use sui::clock::{Self, Clock}; +use sui::coin::{Self, Coin}; +use sui::package::UpgradeCap; +use sui::sui::SUI; +use wormhole::bytes32; +use wormhole::cursor; +use wormhole::external_address; +use wormhole::vaa::{Self, VAA}; + +const EDataSourceEmitterAddressAndChainIdsDifferentLengths: u64 = 0; +const EInvalidDataSource: u64 = 1; +const EInsufficientFee: u64 = 2; +const EStalePriceUpdate: u64 = 3; +const EUpdateAndPriceInfoObjectMismatch: u64 = 4; +const EPriceUpdateNotFoundForPriceInfoObject: u64 = 5; + +/* #[test_only] */ +/* friend pyth::pyth_tests; */ + +/// Init state and emit event corresponding to Pyth initialization. +public entry fun init_pyth( + deployer: DeployerCap, + upgrade_cap: UpgradeCap, + stale_price_threshold: u64, + governance_emitter_chain_id: u64, + governance_emitter_address: vector, + data_sources_emitter_chain_ids: vector, + data_sources_emitter_addresses: vector>, + update_fee: u64, + ctx: &mut TxContext, +) { + setup::init_and_share_state( + deployer, + upgrade_cap, + stale_price_threshold, + update_fee, + data_source::new( + governance_emitter_chain_id, + external_address::new((bytes32::from_bytes(governance_emitter_address))), + ), + parse_data_sources( + data_sources_emitter_chain_ids, + data_sources_emitter_addresses, + ), + ctx, + ); + + // Emit Pyth initialization event. + pyth_event::emit_pyth_initialization_event(); +} - /// Init state and emit event corresponding to Pyth initialization. - public entry fun init_pyth( - deployer: DeployerCap, - upgrade_cap: UpgradeCap, - stale_price_threshold: u64, - governance_emitter_chain_id: u64, - governance_emitter_address: vector, - data_sources_emitter_chain_ids: vector, - data_sources_emitter_addresses: vector>, - update_fee: u64, - ctx: &mut TxContext - ) { - setup::init_and_share_state( - deployer, - upgrade_cap, - stale_price_threshold, - update_fee, +fun parse_data_sources( + emitter_chain_ids: vector, + emitter_addresses: vector>, +): vector { + assert!( + vector::length(&emitter_chain_ids) == vector::length(&emitter_addresses), + EDataSourceEmitterAddressAndChainIdsDifferentLengths, + ); + + let mut sources = vector::empty(); + let mut i = 0; + while (i < vector::length(&emitter_chain_ids)) { + vector::push_back( + &mut sources, data_source::new( - governance_emitter_chain_id, - external_address::new((bytes32::from_bytes(governance_emitter_address))) - ), - parse_data_sources( - data_sources_emitter_chain_ids, - data_sources_emitter_addresses, + *vector::borrow(&emitter_chain_ids, i), + external_address::new( + bytes32::from_bytes(*vector::borrow(&emitter_addresses, i)), + ), ), - ctx ); - // Emit Pyth initialization event. - pyth_event::emit_pyth_initialization_event(); - } - - fun parse_data_sources( - emitter_chain_ids: vector, - emitter_addresses: vector> - ): vector { - - assert!(vector::length(&emitter_chain_ids) == vector::length(&emitter_addresses), - E_DATA_SOURCE_EMITTER_ADDRESS_AND_CHAIN_IDS_DIFFERENT_LENGTHS); - - let sources = vector::empty(); - let i = 0; - while (i < vector::length(&emitter_chain_ids)) { - vector::push_back(&mut sources, data_source::new( - *vector::borrow(&emitter_chain_ids, i), - external_address::new(bytes32::from_bytes(*vector::borrow(&emitter_addresses, i))) - )); + i = i + 1; + }; + sources +} - i = i + 1; - }; - sources - } +/// Create and share new price feed objects if they don't already exist using accumulator message. +public fun create_price_feeds_using_accumulator( + pyth_state: &mut PythState, + accumulator_message: vector, + vaa: VAA, // the verified version of the vaa bytes encoded within the accumulator_message + clock: &Clock, + ctx: &mut TxContext, +) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(pyth_state); + + // Check that the VAA is from a valid data source (emitter) + assert!( + state::is_valid_data_source( + pyth_state, + data_source::new( + (vaa::emitter_chain(&vaa) as u64), + vaa::emitter_address(&vaa), + ), + ), + EInvalidDataSource, + ); + + // decode the price info updates from the VAA payload (first check if it is an accumulator or batch price update) + let mut accumulator_message_cursor = cursor::new(accumulator_message); + let price_infos = accumulator::parse_and_verify_accumulator_message( + &mut accumulator_message_cursor, + vaa::take_payload(vaa), + clock, + ); + + // Create and share new price info objects, if not already exists. + create_and_share_price_feeds_using_verified_price_infos( + &latest_only, + pyth_state, + price_infos, + ctx, + ); + + // destroy rest of cursor + cursor::take_rest(accumulator_message_cursor); +} - /// Create and share new price feed objects if they don't already exist using accumulator message. - public fun create_price_feeds_using_accumulator( - pyth_state: &mut PythState, - accumulator_message: vector, - vaa: VAA, // the verified version of the vaa bytes encoded within the accumulator_message - clock: &Clock, - ctx: &mut TxContext - ){ - // This capability ensures that the current build version is used. - let latest_only = state::assert_latest_only(pyth_state); +/// Create and share new price feed objects if they don't already exist using batch price attestation. +/// The name of the function is kept as is to remain backward compatible +public fun create_price_feeds( + pyth_state: &mut PythState, + // These vaas have been verified and consumed, so we don't have to worry about + // doing replay protection for them. + mut verified_vaas: vector, + clock: &Clock, + ctx: &mut TxContext, +) { + // This capability ensures that the current build version is used. + let latest_only = state::assert_latest_only(pyth_state); + + while (!vector::is_empty(&verified_vaas)) { + let vaa = vector::pop_back(&mut verified_vaas); // Check that the VAA is from a valid data source (emitter) assert!( @@ -104,1446 +154,309 @@ module pyth::pyth { pyth_state, data_source::new( (vaa::emitter_chain(&vaa) as u64), - vaa::emitter_address(&vaa)) + vaa::emitter_address(&vaa), ), - E_INVALID_DATA_SOURCE + ), + EInvalidDataSource, ); - // decode the price info updates from the VAA payload (first check if it is an accumulator or batch price update) - let accumulator_message_cursor = cursor::new(accumulator_message); - let price_infos = accumulator::parse_and_verify_accumulator_message(&mut accumulator_message_cursor, vaa::take_payload(vaa), clock); + // Deserialize the batch price attestation + let price_infos = batch_price_attestation::destroy( + batch_price_attestation::deserialize(vaa::take_payload(vaa), clock), + ); // Create and share new price info objects, if not already exists. - create_and_share_price_feeds_using_verified_price_infos(&latest_only, pyth_state, price_infos, ctx); - - // destroy rest of cursor - cursor::take_rest(accumulator_message_cursor); - } - - - /// Create and share new price feed objects if they don't already exist using batch price attestation. - /// The name of the function is kept as is to remain backward compatible - public fun create_price_feeds( - pyth_state: &mut PythState, - // These vaas have been verified and consumed, so we don't have to worry about - // doing replay protection for them. - verified_vaas: vector, - clock: &Clock, - ctx: &mut TxContext - ){ - // This capability ensures that the current build version is used. - let latest_only = state::assert_latest_only(pyth_state); - - while (!vector::is_empty(&verified_vaas)) { - let vaa = vector::pop_back(&mut verified_vaas); - - // Check that the VAA is from a valid data source (emitter) - assert!( - state::is_valid_data_source( - pyth_state, - data_source::new( - (vaa::emitter_chain(&vaa) as u64), - vaa::emitter_address(&vaa)) - ), - E_INVALID_DATA_SOURCE - ); - - // Deserialize the batch price attestation - let price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(vaa), clock)); - - // Create and share new price info objects, if not already exists. - create_and_share_price_feeds_using_verified_price_infos(&latest_only, pyth_state, price_infos, ctx); - }; - vector::destroy_empty(verified_vaas); - } - - #[allow(lint(share_owned))] - // create_and_share_price_feeds_using_verified_price_infos is a private function used by - // 1) create_price_feeds - // 2) create_price_feeds_using_accumulator - // to create new price feeds for symbols. - fun create_and_share_price_feeds_using_verified_price_infos(latest_only: &LatestOnly, pyth_state: &mut PythState, price_infos: vector, ctx: &mut TxContext){ - while (!vector::is_empty(&price_infos)){ - let cur_price_info = vector::pop_back(&mut price_infos); - - // Only create new Sui PriceInfoObject if not already - // registered with the Pyth State object. - if (!state::price_feed_object_exists( - pyth_state, - price_feed::get_price_identifier( - price_info::get_price_feed(&cur_price_info) - ) - ) - ){ - // Create and share newly created Sui PriceInfoObject containing a price feed, - // and then register a copy of its ID with State. - let new_price_info_object = price_info::new_price_info_object(cur_price_info, ctx); - let price_identifier = price_info::get_price_identifier(&cur_price_info); - let id = price_info::uid_to_inner(&new_price_info_object); - - state::register_price_info_object(latest_only, pyth_state, price_identifier, id); - - transfer::public_share_object(new_price_info_object); - } - } - } - - - // verified_vaa is the verified version of the VAA encoded within the accumulator_message - public fun create_authenticated_price_infos_using_accumulator( - pyth_state: &PythState, - accumulator_message: vector, - verified_vaa: VAA, - clock: &Clock, - ): HotPotatoVector { - state::assert_latest_only(pyth_state); - - // verify that the VAA originates from a valid data source - assert!( - state::is_valid_data_source( - pyth_state, - data_source::new( - (vaa::emitter_chain(&verified_vaa) as u64), - vaa::emitter_address(&verified_vaa)) - ), - E_INVALID_DATA_SOURCE + create_and_share_price_feeds_using_verified_price_infos( + &latest_only, + pyth_state, + price_infos, + ctx, ); + }; + vector::destroy_empty(verified_vaas); +} - // decode the price info updates from the VAA payload (first check if it is an accumulator or batch price update) - let accumulator_message_cursor = cursor::new(accumulator_message); - let price_infos = accumulator::parse_and_verify_accumulator_message(&mut accumulator_message_cursor, vaa::take_payload(verified_vaa), clock); - - // check that accumulator message has been fully consumed - cursor::destroy_empty(accumulator_message_cursor); - hot_potato_vector::new(price_infos) - } - - /// Creates authenticated price infos using batch price attestation - /// Name is kept as is to remain backward compatible - public fun create_price_infos_hot_potato( - pyth_state: &PythState, - verified_vaas: vector, - clock: &Clock - ): HotPotatoVector { - state::assert_latest_only(pyth_state); - - let price_updates = vector::empty(); - while (vector::length(&verified_vaas) != 0){ - let cur_vaa = vector::pop_back(&mut verified_vaas); - - assert!( - state::is_valid_data_source( - pyth_state, - data_source::new( - (vaa::emitter_chain(&cur_vaa) as u64), - vaa::emitter_address(&cur_vaa)) +#[allow(lint(share_owned))] +// create_and_share_price_feeds_using_verified_price_infos is a private function used by +// 1) create_price_feeds +// 2) create_price_feeds_using_accumulator +// to create new price feeds for symbols. +fun create_and_share_price_feeds_using_verified_price_infos( + latest_only: &LatestOnly, + pyth_state: &mut PythState, + mut price_infos: vector, + ctx: &mut TxContext, +) { + while (!vector::is_empty(&price_infos)) { + let cur_price_info = vector::pop_back(&mut price_infos); + + // Only create new Sui PriceInfoObject if not already + // registered with the Pyth State object. + if ( + !state::price_feed_object_exists( + pyth_state, + price_feed::get_price_identifier( + price_info::get_price_feed(&cur_price_info), ), - E_INVALID_DATA_SOURCE - ); - let price_infos = batch_price_attestation::destroy(batch_price_attestation::deserialize(vaa::take_payload(cur_vaa), clock)); - while (vector::length(&price_infos) !=0 ){ - let cur_price_info = vector::pop_back(&mut price_infos); - vector::push_back(&mut price_updates, cur_price_info); - } - }; - vector::destroy_empty(verified_vaas); - return hot_potato_vector::new(price_updates) - } - - /// Update a singular Pyth PriceInfoObject (containing a price feed) with the - /// price data in the authenticated price infos vector (a vector of PriceInfo objects). - /// - /// For more information on the end-to-end process for updating a price feed, please see the README. - /// - /// The given fee must contain a sufficient number of coins to pay the update fee for the given vaas. - /// The update fee amount can be queried by calling get_update_fee(&vaas). - /// - /// Please read more information about the update fee here: https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand#fees - public fun update_single_price_feed( - pyth_state: &PythState, - price_updates: HotPotatoVector, - price_info_object: &mut PriceInfoObject, - fee: Coin, - clock: &Clock - ): HotPotatoVector { - let latest_only = state::assert_latest_only(pyth_state); - - // On Sui, users get to choose which price feeds to update. They specify a single price feed to - // update at a time. We therefore charge the base fee for each such individual update. - // This is a departure from Eth, where users don't get to necessarily choose. - assert!(state::get_base_update_fee(pyth_state) <= coin::value(&fee), E_INSUFFICIENT_FEE); - - // store fee coins within price info object - price_info::deposit_fee_coins(price_info_object, fee); - - // Find price update corresponding to PriceInfoObject within the array of price_updates - // and use it to update PriceInfoObject. - let i = 0; - let found = false; - while (i < hot_potato_vector::length(&price_updates)){ - let cur_price_info = hot_potato_vector::borrow(&price_updates, i); - if (has_same_price_identifier(cur_price_info, price_info_object)){ - found = true; - update_cache(latest_only, cur_price_info, price_info_object, clock); - break - }; - i = i + 1; - }; - if (found==false){ - abort E_PRICE_UPDATE_NOT_FOUND_FOR_PRICE_INFO_OBJECT - }; - price_updates - } - - fun has_same_price_identifier(price_info: &PriceInfo, price_info_object: &PriceInfoObject) : bool { - let price_info_from_object = price_info::get_price_info_from_price_info_object(price_info_object); - let price_identifier_from_object = price_info::get_price_identifier(&price_info_from_object); - let price_identifier_from_price_info = price_info::get_price_identifier(price_info); - price_identifier_from_object == price_identifier_from_price_info - } - - /// Update PriceInfoObject with updated data from a PriceInfo - public(friend) fun update_cache( - _: LatestOnly, - update: &PriceInfo, - price_info_object: &mut PriceInfoObject, - clock: &Clock, - ){ - let has_same_price_identifier = has_same_price_identifier(update, price_info_object); - assert!(has_same_price_identifier, E_UPDATE_AND_PRICE_INFO_OBJECT_MISMATCH); - - // Update the price info object with the new updated price info. - if (is_fresh_update(update, price_info_object)){ - pyth_event::emit_price_feed_update(price_feed::from(price_info::get_price_feed(update)), clock::timestamp_ms(clock)/1000); - price_info::update_price_info_object( - price_info_object, - update - ); - } - } - - /// Determine if the given price update is "fresh": we have nothing newer already cached for that - /// price feed within a PriceInfoObject. - fun is_fresh_update(update: &PriceInfo, price_info_object: &PriceInfoObject): bool { - // Get the timestamp of the update's current price - let price_feed = price_info::get_price_feed(update); - let update_timestamp = price::get_timestamp(&price_feed::get_price(price_feed)); - - // Get the timestamp of the cached data for the price identifier - let cached_price_info = price_info::get_price_info_from_price_info_object(price_info_object); - let cached_price_feed = price_info::get_price_feed(&cached_price_info); - let cached_timestamp = price::get_timestamp(&price_feed::get_price(cached_price_feed)); - - update_timestamp > cached_timestamp - } - - // ----------------------------------------------------------------------------- - // Query the cached prices - // - // It is strongly recommended to update the cached prices using the functions above, - // before using the functions below to query the cached data. - - /// Determine if a price feed for the given price_identifier exists - public fun price_feed_exists(state: &PythState, price_identifier: PriceIdentifier): bool { - state::price_feed_object_exists(state, price_identifier) - } - - /// Get the latest available price cached for the given price identifier, if that price is - /// no older than the stale price threshold. - /// - /// Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for - /// how to how this price safely. - /// - /// Important: Pyth uses an on-demand update model, where consumers need to update the - /// cached prices before using them. Please read more about this at https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand. - /// get_price() is likely to abort unless you call update_price_feeds() to update the cached price - /// beforehand, as the cached prices may be older than the stale price threshold. - /// - /// The price_info_object is a Sui object with the key ability that uniquely - /// contains a price feed for a given price_identifier. - /// - public fun get_price(state: &PythState, price_info_object: &PriceInfoObject, clock: &Clock): Price { - get_price_no_older_than(price_info_object, clock, state::get_stale_price_threshold_secs(state)) - } - - /// Get the latest available price cached for the given price identifier, if that price is - /// no older than the given age. - public fun get_price_no_older_than(price_info_object: &PriceInfoObject, clock: &Clock, max_age_secs: u64): Price { - let price = get_price_unsafe(price_info_object); - check_price_is_fresh(&price, clock, max_age_secs); - price - } + ) + ) { + // Create and share newly created Sui PriceInfoObject containing a price feed, + // and then register a copy of its ID with State. + let new_price_info_object = price_info::new_price_info_object(cur_price_info, ctx); + let price_identifier = price_info::get_price_identifier(&cur_price_info); + let id = price_info::uid_to_inner(&new_price_info_object); - /// Get the latest available price cached for the given price identifier. - /// - /// WARNING: the returned price can be from arbitrarily far in the past. - /// This function makes no guarantees that the returned price is recent or - /// useful for any particular application. Users of this function should check - /// the returned timestamp to ensure that the returned price is sufficiently - /// recent for their application. The checked get_price_no_older_than() - /// function should be used in preference to this. - public fun get_price_unsafe(price_info_object: &PriceInfoObject): Price { - // TODO: extract Price from this guy... - let price_info = price_info::get_price_info_from_price_info_object(price_info_object); - price_feed::get_price( - price_info::get_price_feed(&price_info) - ) - } + state::register_price_info_object(latest_only, pyth_state, price_identifier, id); - fun abs_diff(x: u64, y: u64): u64 { - if (x > y) { - return x - y - } else { - return y - x + transfer::public_share_object(new_price_info_object); } } - - /// Get the stale price threshold: the amount of time after which a cached price - /// is considered stale and no longer returned by get_price()/get_ema_price(). - public fun get_stale_price_threshold_secs(state: &PythState): u64 { - state::get_stale_price_threshold_secs(state) - } - - fun check_price_is_fresh(price: &Price, clock: &Clock, max_age_secs: u64) { - let age = abs_diff(clock::timestamp_ms(clock)/1000, price::get_timestamp(price)); - assert!(age < max_age_secs, E_STALE_PRICE_UPDATE); - } - - /// Please read more information about the update fee here: https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand#fees - public fun get_total_update_fee(pyth_state: &PythState, n: u64): u64 { - state::get_base_update_fee(pyth_state) * n - } } -#[test_only] -module pyth::pyth_tests{ - use std::vector::{Self}; - - use sui::sui::SUI; - use sui::coin::{Self, Coin}; - use sui::test_scenario::{Self, Scenario, ctx, take_shared, return_shared}; - use sui::package::Self; - use sui::object::{Self, ID}; - use sui::clock::{Self, Clock}; - - use pyth::state::{State as PythState}; - use pyth::setup::{Self}; - use pyth::price_info::{Self, PriceInfo, PriceInfoObject};//, PriceInfo, PriceInfoObject}; - use pyth::data_source::{Self, DataSource}; - use pyth::pyth::{Self, create_price_infos_hot_potato, update_single_price_feed}; - use pyth::hot_potato_vector::{Self}; - use pyth::price_identifier::{Self}; - use pyth::price_feed::{Self}; - use pyth::accumulator::{Self}; - use pyth::deserialize::{Self}; - - use wormhole::setup::{Self as wormhole_setup, DeployerCap}; - use wormhole::external_address::{Self}; - use wormhole::bytes32::{Self}; - use wormhole::state::{State as WormState}; - use wormhole::vaa::{Self, VAA}; - use wormhole::cursor::{Self}; - - const DEPLOYER: address = @0x1234; - const ACCUMULATOR_TESTS_EMITTER_ADDRESS: vector = x"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b"; - const ACCUMULATOR_TESTS_INITIAL_GUARDIANS: vector> = vector[x"7E5F4552091A69125d5DfCb7b8C2659029395Bdf"]; - const DEFAULT_BASE_UPDATE_FEE: u64 = 50; - const DEFAULT_COIN_TO_MINT: u64 = 5000; - const BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS: vector> = vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"]; - - fun ACCUMULATOR_TESTS_DATA_SOURCE(): vector { - vector[data_source::new(1, external_address::new(bytes32::from_bytes(ACCUMULATOR_TESTS_EMITTER_ADDRESS)))] - } - - fun get_verified_test_vaas(worm_state: &WormState, clock: &Clock): vector { - let test_vaas_: vector> = vector[x"0100000000010036eb563b80a24f4253bee6150eb8924e4bdf6e4fa1dfc759a6664d2e865b4b134651a7b021b7f1ce3bd078070b688b6f2e37ce2de0d9b48e6a78684561e49d5201527e4f9b00000001001171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000001005032574800030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001"]; - let verified_vaas_reversed = vector::empty(); - let test_vaas = test_vaas_; - let i = 0; - while (i < vector::length(&test_vaas_)) { - let cur_test_vaa = vector::pop_back(&mut test_vaas); - let verified_vaa = vaa::parse_and_verify(worm_state, cur_test_vaa, clock); - vector::push_back(&mut verified_vaas_reversed, verified_vaa); - i=i+1; - }; - let verified_vaas = vector::empty(); - while (vector::length(&verified_vaas_reversed)!=0){ - let cur = vector::pop_back(&mut verified_vaas_reversed); - vector::push_back(&mut verified_vaas, cur); - }; - vector::destroy_empty(verified_vaas_reversed); - verified_vaas - } - - // get_verified_vaa_from_accumulator_message parses the accumulator message up until the vaa, then - // parses the vaa, yielding a verified wormhole::vaa::VAA object - fun get_verified_vaa_from_accumulator_message(worm_state: &WormState, accumulator_message: vector, clock: &Clock): VAA { - let _PYTHNET_ACCUMULATOR_UPDATE_MAGIC: u64 = 1347305813; - - let cursor = cursor::new(accumulator_message); - let header: u32 = deserialize::deserialize_u32(&mut cursor); - assert!((header as u64) == _PYTHNET_ACCUMULATOR_UPDATE_MAGIC, 0); - let _major = deserialize::deserialize_u8(&mut cursor); - let _minor = deserialize::deserialize_u8(&mut cursor); - - let trailing_size = deserialize::deserialize_u8(&mut cursor); - deserialize::deserialize_vector(&mut cursor, (trailing_size as u64)); - - let proof_type = deserialize::deserialize_u8(&mut cursor); - assert!(proof_type == 0, 0); - - let vaa_size = deserialize::deserialize_u16(&mut cursor); - let vaa = deserialize::deserialize_vector(&mut cursor, (vaa_size as u64)); - cursor::take_rest(cursor); - vaa::parse_and_verify(worm_state, vaa, clock) - } - - #[test_only] - /// Init Wormhole core bridge state. - /// Init Pyth state. - /// Set initial Sui clock time. - /// Mint some SUI fee coins. - public fun setup_test( - stale_price_threshold: u64, - governance_emitter_chain_id: u64, - governance_emitter_address: vector, - data_sources: vector, - initial_guardians: vector>, - base_update_fee: u64, - to_mint: u64 - ): (Scenario, Coin, Clock) { - - let scenario = test_scenario::begin(DEPLOYER); - - // Initialize Wormhole core bridge. - wormhole_setup::init_test_only(ctx(&mut scenario)); - test_scenario::next_tx(&mut scenario, DEPLOYER); - // Take the `DeployerCap` from the sender of the transaction. - let deployer_cap = - test_scenario::take_from_address( - &scenario, - DEPLOYER - ); - - // This will be created and sent to the transaction sender automatically - // when the contract is published. This exists in place of grabbing - // it from the sender. - let upgrade_cap = - package::test_publish( - object::id_from_address(@wormhole), - test_scenario::ctx(&mut scenario) - ); - - let governance_chain = 1234; - let governance_contract = - x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; - let guardian_set_seconds_to_live = 5678; - let message_fee = 350; - let guardian_set_index = 0; - wormhole_setup::complete( - deployer_cap, - upgrade_cap, - governance_chain, - governance_contract, - guardian_set_index, - initial_guardians, - guardian_set_seconds_to_live, - message_fee, - test_scenario::ctx(&mut scenario) - ); - - // Initialize Pyth state. - let pyth_upgrade_cap= - package::test_publish( - object::id_from_address(@pyth), - test_scenario::ctx(&mut scenario) - ); - - setup::init_test_only(ctx(&mut scenario)); - test_scenario::next_tx(&mut scenario, DEPLOYER); - let pyth_deployer_cap = test_scenario::take_from_address( - &scenario, - DEPLOYER - ); - - setup::init_and_share_state( - pyth_deployer_cap, - pyth_upgrade_cap, - stale_price_threshold, - base_update_fee, - data_source::new(governance_emitter_chain_id, external_address::new(bytes32::from_bytes(governance_emitter_address))), - data_sources, - ctx(&mut scenario) - ); - - let coins = coin::mint_for_testing(to_mint, ctx(&mut scenario)); - let clock = clock::create_for_testing(ctx(&mut scenario)); - (scenario, coins, clock) - } - - fun get_mock_price_infos(): vector { - use pyth::i64::Self; - use pyth::price::{Self}; - vector[ - price_info::new_price_info( - 1663680747, - 1663074349, - price_feed::new( - price_identifier::from_byte_vec(x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1"), - price::new(i64::new(1557, false), 7, i64::new(5, true), 1663680740), - price::new(i64::new(1500, false), 3, i64::new(5, true), 1663680740), - ), - ), - price_info::new_price_info( - 1663680747, - 1663074349, - price_feed::new( - price_identifier::from_byte_vec(x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe"), - price::new(i64::new(1050, false), 3, i64::new(5, true), 1663680745), - price::new(i64::new(1483, false), 3, i64::new(5, true), 1663680745), - ), - ), - price_info::new_price_info( - 1663680747, - 1663074349, - price_feed::new( - price_identifier::from_byte_vec(x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d"), - price::new(i64::new(1010, false), 2, i64::new(5, true), 1663680745), - price::new(i64::new(1511, false), 3, i64::new(5, true), 1663680745), - ), - ), - price_info::new_price_info( - 1663680747, - 1663074349, - price_feed::new( - price_identifier::from_byte_vec(x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8"), - price::new(i64::new(1739, false), 1, i64::new(5, true), 1663680745), - price::new(i64::new(1508, false), 3, i64::new(5, true), 1663680745), - ), - ), - ] - } - - /// Compare the expected price feed with the actual Pyth price feeds. - fun check_price_feeds_cached(expected: &vector, actual: &vector) { - // Check that we can retrieve the correct current price and ema price for each price feed - let i = 0; - while (i < vector::length(expected)) { - let price_feed = price_info::get_price_feed(vector::borrow(expected, i)); - let price = price_feed::get_price(price_feed); - let ema_price = price_feed::get_ema_price(price_feed); - let price_identifier = price_info::get_price_identifier(vector::borrow(expected, i)); - - let actual_price_info = price_info::get_price_info_from_price_info_object(vector::borrow(actual, i)); - let actual_price_feed = price_info::get_price_feed(&actual_price_info); - let actual_price = price_feed::get_price(actual_price_feed); - let actual_ema_price = price_feed::get_ema_price(actual_price_feed); - let actual_price_identifier = price_info::get_price_identifier(&actual_price_info); - - assert!(price == actual_price, 0); - assert!(ema_price == actual_ema_price, 0); - assert!(price_identifier::get_bytes(&price_identifier) == price_identifier::get_bytes(&actual_price_identifier), 0); - - i = i + 1; - }; - } - - #[test] - fun test_get_update_fee() { - let (scenario, test_coins, _clock) = setup_test(500 /* stale_price_threshold */, 23 /* governance emitter chain */, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, 0); - test_scenario::next_tx(&mut scenario, DEPLOYER, ); - let pyth_state = take_shared(&scenario); - // Pass in a single VAA - - let single_vaa = vector[ - x"fb1543888001083cf2e6ef3afdcf827e89b11efd87c563638df6e1995ada9f93", - ]; - - assert!(pyth::get_total_update_fee(&pyth_state, vector::length>(&single_vaa)) == DEFAULT_BASE_UPDATE_FEE, 1); - - let multiple_vaas = vector[ - x"4ee17a1a4524118de513fddcf82b77454e51be5d6fc9e29fc72dd6c204c0e4fa", - x"c72fdf81cfc939d4286c93fbaaae2eec7bae28a5926fa68646b43a279846ccc1", - x"d9a8123a793529c31200339820a3210059ecace6c044f81ecad62936e47ca049", - x"84e4f21b3e65cef47fda25d15b4eddda1edf720a1d062ccbf441d6396465fbe6", - x"9e73f9041476a93701a0b9c7501422cc2aa55d16100bec628cf53e0281b6f72f" - ]; - - // Pass in multiple VAAs - assert!(pyth::get_total_update_fee(&pyth_state, vector::length>(&multiple_vaas)) == 5*DEFAULT_BASE_UPDATE_FEE, 1); - - return_shared(pyth_state); - coin::burn_for_testing(test_coins); - clock::destroy_for_testing(_clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = wormhole::vaa::E_WRONG_VERSION)] - fun test_create_price_feeds_corrupt_vaa() { - let (scenario, test_coins, clock) = setup_test(500 /* stale_price_threshold */, 23 /* governance emitter chain */, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", vector[], vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], 50, 0); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - // Pass in a corrupt VAA, which should fail deserializing - let corrupt_vaa = x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"; - let verified_vaas = vector[vaa::parse_and_verify(&worm_state, corrupt_vaa, &clock)]; - // Create Pyth price feed - pyth::create_price_feeds( - &mut pyth_state, - verified_vaas, - &clock, - ctx(&mut scenario) - ); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - coin::burn_for_testing(test_coins); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = pyth::pyth::E_INVALID_DATA_SOURCE)] - fun test_create_price_feeds_invalid_data_source() { - // Initialize the contract with some valid data sources, excluding our test VAA's source - let data_sources = vector[ +// verified_vaa is the verified version of the VAA encoded within the accumulator_message +public fun create_authenticated_price_infos_using_accumulator( + pyth_state: &PythState, + accumulator_message: vector, + verified_vaa: VAA, + clock: &Clock, +): HotPotatoVector { + state::assert_latest_only(pyth_state); + + // verify that the VAA originates from a valid data source + assert!( + state::is_valid_data_source( + pyth_state, data_source::new( - 4, external_address::new(bytes32::new(x"0000000000000000000000000000000000000000000000000000000000007742")) + (vaa::emitter_chain(&verified_vaa) as u64), + vaa::emitter_address(&verified_vaa), ), - data_source::new( - 5, external_address::new(bytes32::new(x"0000000000000000000000000000000000000000000000000000000000007637")) - ) - ]; - let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources, BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, 50, 0); - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaas = get_verified_test_vaas(&worm_state, &clock); - - pyth::create_price_feeds( - &mut pyth_state, - verified_vaas, - &clock, - ctx(&mut scenario) - ); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - coin::burn_for_testing(test_coins); - test_scenario::end(scenario); - } - - public fun data_sources_for_test_vaa(): vector { - // Set some valid data sources, including our test VAA's source - vector[ - data_source::new( - 1, external_address::new(bytes32::from_bytes(x"0000000000000000000000000000000000000000000000000000000000000004"))), - data_source::new( - 5, external_address::new(bytes32::new(x"0000000000000000000000000000000000000000000000000000000000007637"))), - data_source::new( - 17, external_address::new(bytes32::new(ACCUMULATOR_TESTS_EMITTER_ADDRESS))) - ] - } - - #[test] - // test_create_and_update_price_feeds_with_batch_attestation_success tests the creation and updating of price - // feeds, as well as depositing fee coins into price info objects - fun test_create_and_update_price_feeds_with_batch_attestation_success() { - let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaas = get_verified_test_vaas(&worm_state, &clock); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds( - &mut pyth_state, - verified_vaas, - &clock, - ctx(&mut scenario) - ); - - // Affirm that 4 objects, which correspond to the 4 new price info objects - // containing the price feeds were created and shared. - let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); - let shared_ids = test_scenario::shared(&effects); - let created_ids = test_scenario::created(&effects); - assert!(vector::length(&shared_ids)==4, 0); - assert!(vector::length(&created_ids)==4, 0); - - let price_info_object_1 = take_shared(&scenario); - let price_info_object_2 = take_shared(&scenario); - let price_info_object_3 = take_shared(&scenario); - let price_info_object_4 = take_shared(&scenario); - - // Create vector of price info objects (Sui objects with key ability and living in global store), - // which contain the price feeds we want to update. Note that these can be passed into - // update_price_feeds in any order! - //let price_info_object_vec = vector[price_info_object_1, price_info_object_2, price_info_object_3, price_info_object_4]; - verified_vaas = get_verified_test_vaas(&worm_state, &clock); - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let vaa_1 = vector::pop_back(&mut verified_vaas); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - // Create authenticated price infos - let vec = create_price_infos_hot_potato( - &pyth_state, - vector[vaa_1], - &clock - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let fee_coins = coin::split(&mut test_coins, DEFAULT_BASE_UPDATE_FEE, ctx(&mut scenario)); - vec = update_single_price_feed( - &pyth_state, - vec, - &mut price_info_object_1, - fee_coins, - &clock - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - // check price feed updated - assert!(price_feeds_equal(hot_potato_vector::borrow(&vec, 3), &price_info::get_price_info_from_price_info_object(&price_info_object_1)), 0); - - // check fee coins are deposited in the price info object - assert!(price_info::get_balance(&price_info_object_1)==DEFAULT_BASE_UPDATE_FEE, 0); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - hot_potato_vector::destroy(vec); - - vector::destroy_empty(verified_vaas); - return_shared(price_info_object_1); - return_shared(price_info_object_2); - return_shared(price_info_object_3); - return_shared(price_info_object_4); - - coin::burn_for_testing(test_coins); - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - - // TEST_ACCUMULATOR_SINGLE_FEED details: - // Price Identifier: 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 - // Price: 6887568746747646632 - // Conf: 13092246197863718329 - // Exponent: 1559537863 - // EMA Price: 4772242609775910581 - // EMA Conf: 358129956189946877 - // EMA Expo: 1559537863 - // Published Time: 1687276661 - const TEST_ACCUMULATOR_SINGLE_FEED: vector = x"504e41550100000000a0010000000001005d461ac1dfffa8451edda17e4b28a46c8ae912422b2dc0cb7732828c497778ea27147fb95b4d250651931845e7f3e22c46326716bcf82be2874a9c9ab94b6e42000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000da936d73429246d131873a0bab90ad7b416510be01005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65f958f4883f9d2a8b5b1008d1fa01db95cf4a8c7000000006491cc757be59f3f377c0d3f423a695e81ad1eb504f8554c3620c3fd02f2ee15ea639b73fa3db9b34a245bdfa015c260c5a8a1180177cf30b2c0bebbb1adfe8f7985d051d2"; - - #[test] - fun test_create_and_update_single_price_feed_with_accumulator_success() { - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_SINGLE_FEED, &clock); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_SINGLE_FEED, - verified_vaa, - &clock, - ctx(&mut scenario) - ); - - // Affirm that 1 object, which correspond to the 1 new price info object - // containing the price feeds were created and shared. - let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); - let shared_ids = test_scenario::shared(&effects); - let created_ids = test_scenario::created(&effects); - assert!(vector::length(&shared_ids)==1, 0); - assert!(vector::length(&created_ids)==1, 0); - - let price_info_object_1 = take_shared(&scenario); - - // Create authenticated price infos - verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_SINGLE_FEED, &clock); - let auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( - &pyth_state, - TEST_ACCUMULATOR_SINGLE_FEED, - verified_vaa, - &clock - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - auth_price_infos = update_single_price_feed( - &pyth_state, - auth_price_infos, - &mut price_info_object_1, - coins, - &clock - ); - - // assert that price info obejct is as expected - let expected = accumulator_test_1_to_price_info(); - assert!(price_feeds_equal(&expected, &price_info::get_price_info_from_price_info_object(&price_info_object_1)), 0); - - // clean up test scenario - - test_scenario::next_tx(&mut scenario, DEPLOYER); - hot_potato_vector::destroy(auth_price_infos); - - return_shared(price_info_object_1); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = pyth::accumulator::E_INVALID_PROOF)] - fun test_create_and_update_single_price_feed_with_accumulator_failure() { - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - // the verified vaa here contains the wrong merkle root - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_SINGLE_FEED, - verified_vaa, - &clock, - ctx(&mut scenario) - ); - - // clean up test scenario - test_scenario::next_tx(&mut scenario, DEPLOYER); - coin::burn_for_testing(coins); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - #[test_only] - const TEST_ACCUMULATOR_INVALID_PROOF_1: vector = x"504e41550100000000a001000000000100110db9cd8325ccfab0dae92eeb9ea70a1faba5c5e96dc21ff46a8ddc560afc9a60df096b8ff21172804692bbdc958153e838437d8b474cbf45f0dc2a8acae831000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000a8bea2b5f12f3177ff9b3929d77c3476ab2d32c602005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6fa75cd3aa3bb5ace5e2516446f71f85be36bd19bb0703f3154bb3db07be59f3f377c0d3f44661d9a8736c68884c8169e8b636ee3043202397384073120dce9e5d0efe24b44b4a0d62da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d950055006e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af5f958f4883f9d2a8b5b1008d1fa01db95cf4a8c7423a695e81ad1eb504f8554c3620c3fd40b40f7d581ac802e2de5cb82a9ae672043202397384073120dce9e5d0efe24b44b4a0d62da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; - - #[test] - #[expected_failure(abort_code = pyth::accumulator::E_INVALID_PROOF)] - fun test_accumulator_invalid_proof() { - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_INVALID_PROOF_1, &clock); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_INVALID_PROOF_1, - verified_vaa, - &clock, - ctx(&mut scenario) - ); - - // clean up test scenario - test_scenario::next_tx(&mut scenario, DEPLOYER); - coin::burn_for_testing(coins); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - #[test_only] - const TEST_ACCUMULATOR_INVALID_MAJOR_VERSION: vector = x"504e41553c00000000a001000000000100496b7fbd18dca2f0e690712fd8ca522ff79ca7d9d6d22e9f5d753fba4bd16fff440a811bad710071c79859290bcb1700de49dd8400db90b048437b521200123e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000005f5db4488a7cae9f9a6c1938340c0fbf4beb9090200550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e0454d2655c6c34e7e50580fd8c94511322968bbc6da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005500944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb049e6e88181a1e1e8b6d3c6bbb95135a73041f3b56a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; - - #[test] - #[expected_failure(abort_code = pyth::accumulator::E_INVALID_ACCUMULATOR_PAYLOAD)] - fun test_accumulator_invalid_major_version() { - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_INVALID_MAJOR_VERSION, &clock); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_INVALID_MAJOR_VERSION, - verified_vaa, - &clock, - ctx(&mut scenario) - ); - - // clean up test scenario - test_scenario::next_tx(&mut scenario, DEPLOYER); - coin::burn_for_testing(coins); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - #[test_only] - const TEST_ACCUMULATOR_INVALID_WH_MSG: vector = x"504e41550100000000a001000000000100e87f98238c5357730936cfdfde3a37249e5219409a4f41b301924b8eb10815a43ea2f96e4fe1bc8cd398250f39448d3b8ca57c96f9cf7a2be292517280683caa010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b00000000000000000041555755000000000000000000000000000fb6f9f2b3b6cc1c9ef6708985fef226d92a3c0801005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6fa75cd3aa3bb5ace5e2516446f71f85be36bd19b000000006491cc747be59f3f377c0d3f44661d9a8736c68884c8169e8b636ee301f2ee15ea639b73fa3db9b34a245bdfa015c260c5"; - - #[test] - #[expected_failure(abort_code = pyth::accumulator::E_INVALID_WORMHOLE_MESSAGE)] - fun test_accumulator_invalid_wormhole_message() { - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_INVALID_WH_MSG, &clock); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_INVALID_WH_MSG, - verified_vaa, - &clock, - ctx(&mut scenario) - ); - - // clean up test scenario - test_scenario::next_tx(&mut scenario, DEPLOYER); - coin::burn_for_testing(coins); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - - #[test_only] - const TEST_ACCUMULATOR_INCREASED_MINOR_VERSION: vector = x"504e4155010a000000a001000000000100496b7fbd18dca2f0e690712fd8ca522ff79ca7d9d6d22e9f5d753fba4bd16fff440a811bad710071c79859290bcb1700de49dd8400db90b048437b521200123e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000005f5db4488a7cae9f9a6c1938340c0fbf4beb9090200550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e0454d2655c6c34e7e50580fd8c94511322968bbc6da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005500944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb049e6e88181a1e1e8b6d3c6bbb95135a73041f3b56a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; - #[test_only] - const TEST_ACCUMULATOR_EXTRA_PAYLOAD: vector = x"504e41550100000000a001000000000100b2d11f181d81b4ff10beca30091754b464dc48bc1f7432d114f64a7a8f660e7964f2a0c6121bae6c1977514d46ee7a29d9395b20a45f2086071715c1dc19ab74000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000013f83cfdf63a5a1b3189182fa0a52e6de53ba7d002005d0031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e000000000000000004a576f4a87f443f7d961a682f508c4f7b06ee1595a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005d00944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb0000000000000000045be67ba87a8dfbea404827ccbf07790299b6c023a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; - - #[test] - fun test_accumulator_forward_compatibility() { - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_EXTRA_PAYLOAD, - get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_EXTRA_PAYLOAD, &clock), - &clock, - ctx(&mut scenario) - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_INCREASED_MINOR_VERSION, - get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_INCREASED_MINOR_VERSION, &clock), - &clock, - ctx(&mut scenario) - ); - - // clean up test scenario - test_scenario::next_tx(&mut scenario, DEPLOYER); - coin::burn_for_testing(coins); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - - // TEST_ACCUMULATOR_3_MSGS details: - // Price Identifier: 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 - // Price: 100 - // Conf: 50 - // Exponent: 9 - // EMA Price: 99 - // EMA Conf: 52 - // EMA Expo: 9 - // Published Time: 1687276660 - - // Price Identifier: 0x6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af - // Price: 101 - // Conf: 51 - // Exponent: 10 - // EMA Price: 100 - // EMA Conf: 53 - // EMA Expo: 10 - // Published Time: 1687276661 - - // Price Identifier: 0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68 - // Price: 102 - // Conf: 52 - // Exponent: 11 - // EMA Price: 101 - // EMA Conf: 54 - // EMA Expo: 11 - // Published Time: 1687276662 - const TEST_ACCUMULATOR_3_MSGS: vector = x"504e41550100000000a001000000000100d39b55fa311213959f91866d52624f3a9c07350d8956f6d42cfbb037883f31575c494a2f09fea84e4884dc9c244123fd124bc7825cd64d7c11e33ba5cfbdea7e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000029da4c066b6e03b16a71e77811570dd9e19f258103005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60000000000000064000000000000003200000009000000006491cc747be59f3f377c0d3f000000000000006300000000000000340436992facb15658a7e9f08c4df4848ca80750f61fadcd96993de66b1fe7aef94e29e3bbef8b12db2305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d950055006e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af000000000000006500000000000000330000000a000000006491cc7504f8554c3620c3fd0000000000000064000000000000003504171ed10ac4f1eacf3a4951e1da6b119f07c45da5adcd96993de66b1fe7aef94e29e3bbef8b12db2305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d9500550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68000000000000006600000000000000340000000b000000006491cc76e87d69c7b51242890000000000000065000000000000003604f2ee15ea639b73fa3db9b34a245bdfa015c260c5fe83e4772e0e346613de00e5348158a01bcb27b305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; - - #[test] - fun test_create_and_update_multiple_price_feeds_with_accumulator_success() { - use sui::coin::Self; - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, ACCUMULATOR_TESTS_DATA_SOURCE(), ACCUMULATOR_TESTS_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + ), + EInvalidDataSource, + ); + + // decode the price info updates from the VAA payload (first check if it is an accumulator or batch price update) + let mut accumulator_message_cursor = cursor::new(accumulator_message); + let price_infos = accumulator::parse_and_verify_accumulator_message( + &mut accumulator_message_cursor, + vaa::take_payload(verified_vaa), + clock, + ); + + // check that accumulator message has been fully consumed + cursor::destroy_empty(accumulator_message_cursor); + hot_potato_vector::new(price_infos) +} - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); +/// Creates authenticated price infos using batch price attestation +/// Name is kept as is to remain backward compatible +public fun create_price_infos_hot_potato( + pyth_state: &PythState, + mut verified_vaas: vector, + clock: &Clock, +): HotPotatoVector { + state::assert_latest_only(pyth_state); - test_scenario::next_tx(&mut scenario, DEPLOYER); + let mut price_updates = vector::empty(); + while (vector::length(&verified_vaas) != 0) { + let cur_vaa = vector::pop_back(&mut verified_vaas); - pyth::create_price_feeds_using_accumulator( - &mut pyth_state, - TEST_ACCUMULATOR_3_MSGS, - verified_vaa, - &clock, - ctx(&mut scenario) + assert!( + state::is_valid_data_source( + pyth_state, + data_source::new( + (vaa::emitter_chain(&cur_vaa) as u64), + vaa::emitter_address(&cur_vaa), + ), + ), + EInvalidDataSource, ); - - // Affirm that 3 objects, which correspond to the 3 new price info objects - // containing the price feeds were created and shared. - let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); - let shared_ids = test_scenario::shared(&effects); - let created_ids = test_scenario::created(&effects); - assert!(vector::length(&shared_ids)==3, 0); - assert!(vector::length(&created_ids)==3, 0); - - // Create authenticated price infos - verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); - let auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( - &pyth_state, - TEST_ACCUMULATOR_3_MSGS, - verified_vaa, - &clock + let mut price_infos = batch_price_attestation::destroy( + batch_price_attestation::deserialize(vaa::take_payload(cur_vaa), clock), ); + while (vector::length(&price_infos) !=0) { + let cur_price_info = vector::pop_back(&mut price_infos); + vector::push_back(&mut price_updates, cur_price_info); + } + }; + vector::destroy_empty(verified_vaas); + return hot_potato_vector::new(price_updates) +} - let idx = 0; - let expected_price_infos = accumulator_test_3_to_price_info(0 /*offset argument*/); - - while (idx < 3){ - let coin_split = coin::split(&mut coins, 1000, ctx(&mut scenario)); - let price_info_object = take_shared(&scenario); - auth_price_infos = update_single_price_feed( - &pyth_state, - auth_price_infos, - &mut price_info_object, - coin_split, - &clock - ); - let price_info = price_info::get_price_info_from_price_info_object(&price_info_object); - assert!(price_feeds_equal(&price_info, vector::borrow(&expected_price_infos, idx)), 0); - return_shared(price_info_object); - idx = idx + 1; +/// Update a singular Pyth PriceInfoObject (containing a price feed) with the +/// price data in the authenticated price infos vector (a vector of PriceInfo objects). +/// +/// For more information on the end-to-end process for updating a price feed, please see the README. +/// +/// The given fee must contain a sufficient number of coins to pay the update fee for the given vaas. +/// The update fee amount can be queried by calling get_update_fee(&vaas). +/// +/// Please read more information about the update fee here: https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand#fees +public fun update_single_price_feed( + pyth_state: &PythState, + price_updates: HotPotatoVector, + price_info_object: &mut PriceInfoObject, + fee: Coin, + clock: &Clock, +): HotPotatoVector { + let latest_only = state::assert_latest_only(pyth_state); + + // On Sui, users get to choose which price feeds to update. They specify a single price feed to + // update at a time. We therefore charge the base fee for each such individual update. + // This is a departure from Eth, where users don't get to necessarily choose. + assert!(state::get_base_update_fee(pyth_state) <= coin::value(&fee), EInsufficientFee); + + // store fee coins within price info object + price_info::deposit_fee_coins(price_info_object, fee); + + // Find price update corresponding to PriceInfoObject within the array of price_updates + // and use it to update PriceInfoObject. + let mut i = 0; + let mut found = false; + while (i < hot_potato_vector::length(&price_updates)) { + let cur_price_info = hot_potato_vector::borrow(&price_updates, i); + if (has_same_price_identifier(cur_price_info, price_info_object)) { + found = true; + update_cache(latest_only, cur_price_info, price_info_object, clock); + break }; - coin::burn_for_testing(coins); - - // clean up test scenario - test_scenario::next_tx(&mut scenario, DEPLOYER); - hot_potato_vector::destroy(auth_price_infos); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - #[test] - #[expected_failure(abort_code = pyth::pyth::E_INSUFFICIENT_FEE)] - fun test_create_and_update_price_feeds_insufficient_fee() { - - // this is not enough fee and will cause a failure - let coins_to_mint = 1; - - let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], DEFAULT_BASE_UPDATE_FEE, coins_to_mint); - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaas = get_verified_test_vaas(&worm_state, &clock); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - pyth::create_price_feeds( - &mut pyth_state, - verified_vaas, - &clock, - ctx(&mut scenario) - ); - - // Affirm that 4 objects, which correspond to the 4 new price info objects - // containing the price feeds were created and shared. - let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); - let shared_ids = test_scenario::shared(&effects); - let created_ids = test_scenario::created(&effects); - assert!(vector::length(&shared_ids)==4, 0); - assert!(vector::length(&created_ids)==4, 0); - - let price_info_object_1 = take_shared(&scenario); - let price_info_object_2 = take_shared(&scenario); - let price_info_object_3 = take_shared(&scenario); - let price_info_object_4 = take_shared(&scenario); - - // Create vector of price info objects (Sui objects with key ability and living in global store), - // which contain the price feeds we want to update. Note that these can be passed into - // update_price_feeds in any order! - //let price_info_object_vec = vector[price_info_object_1, price_info_object_2, price_info_object_3, price_info_object_4]; - verified_vaas = get_verified_test_vaas(&worm_state, &clock); - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let vaa_1 = vector::pop_back(&mut verified_vaas); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - // Create authenticated price infos - let vec = create_price_infos_hot_potato( - &pyth_state, - vector[vaa_1], - &clock - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - vec = update_single_price_feed( - &pyth_state, - vec, - &mut price_info_object_1, - test_coins, - &clock - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - hot_potato_vector::destroy(vec); - - vector::destroy_empty(verified_vaas); - - return_shared(price_info_object_1); - return_shared(price_info_object_2); - return_shared(price_info_object_3); - return_shared(price_info_object_4); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - - #[test] - fun test_update_cache(){ - let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - - let verified_vaas = get_verified_test_vaas(&worm_state, &clock); - - // Update cache is called by create_price_feeds. - pyth::create_price_feeds( - &mut pyth_state, - verified_vaas, - &clock, - ctx(&mut scenario) - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let price_info_object_1 = take_shared(&scenario); - let price_info_object_2 = take_shared(&scenario); - let price_info_object_3 = take_shared(&scenario); - let price_info_object_4 = take_shared(&scenario); - - // These updates are price infos that correspond to the ones in TEST_VAAS. - let updates = get_mock_price_infos(); - let price_info_object_vec = vector[ - price_info_object_1, - price_info_object_2, - price_info_object_3, - price_info_object_4 - ]; - - // Check that TEST_VAAS was indeed used to instantiate the price feeds correctly, - // by confirming that the info in updates is contained in price_info_object_vec. - check_price_feeds_cached(&updates, &price_info_object_vec); - - price_info_object_4 = vector::pop_back(&mut price_info_object_vec); - price_info_object_3 = vector::pop_back(&mut price_info_object_vec); - price_info_object_2 = vector::pop_back(&mut price_info_object_vec); - price_info_object_1 = vector::pop_back(&mut price_info_object_vec); - vector::destroy_empty(price_info_object_vec); - - return_shared(price_info_object_1); - return_shared(price_info_object_2); - return_shared(price_info_object_3); - return_shared(price_info_object_4); - coin::burn_for_testing(test_coins); - - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - #[test] - fun test_update_cache_old_update() { - use pyth::i64::Self; - use pyth::price::Self; - - let (scenario, test_coins, clock) = setup_test(500, 23, x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", data_sources_for_test_vaa(), BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, DEFAULT_BASE_UPDATE_FEE, DEFAULT_COIN_TO_MINT); - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let (pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); - let verified_vaas = get_verified_test_vaas(&worm_state, &clock); - - pyth::create_price_feeds( - &mut pyth_state, - verified_vaas, - &clock, - ctx(&mut scenario) - ); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - let price_info_object_1 = take_shared(&scenario); - let price_info_object_2 = take_shared(&scenario); - let price_info_object_3 = take_shared(&scenario); - let price_info_object_4 = take_shared(&scenario); + i = i + 1; + }; + assert!(found, EPriceUpdateNotFoundForPriceInfoObject); + price_updates +} - // Hardcode the price identifier, price, and ema_price for price_info_object_1, because - // it's easier than unwrapping price_info_object_1 and getting the quantities via getters. - let timestamp = 1663680740; - let price_identifier = price_identifier::from_byte_vec(x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1"); - let price = price::new(i64::new(1557, false), 7, i64::new(5, true), timestamp); - let ema_price = price::new(i64::new(1500, false), 3, i64::new(5, true), timestamp); +fun has_same_price_identifier(price_info: &PriceInfo, price_info_object: &PriceInfoObject): bool { + let price_info_from_object = price_info::get_price_info_from_price_info_object( + price_info_object, + ); + let price_identifier_from_object = price_info::get_price_identifier( + &price_info_from_object, + ); + let price_identifier_from_price_info = price_info::get_price_identifier(price_info); + price_identifier_from_object == price_identifier_from_price_info +} - // Attempt to update the price with an update older than the current cached one. - let old_price = price::new(i64::new(1243, true), 9802, i64::new(6, false), timestamp - 200); - let old_ema_price = price::new(i64::new(8976, true), 234, i64::new(897, false), timestamp - 200); - let old_update = price_info::new_price_info( - 1257278600, - 1690226180, - price_feed::new( - price_identifier, - old_price, - old_ema_price, - ) +/// Update PriceInfoObject with updated data from a PriceInfo +public(package) fun update_cache( + _: LatestOnly, + update: &PriceInfo, + price_info_object: &mut PriceInfoObject, + clock: &Clock, +) { + let has_same_price_identifier = has_same_price_identifier(update, price_info_object); + assert!(has_same_price_identifier, EUpdateAndPriceInfoObjectMismatch); + + // Update the price info object with the new updated price info. + if (is_fresh_update(update, price_info_object)) { + pyth_event::emit_price_feed_update( + price_feed::from(price_info::get_price_feed(update)), + clock::timestamp_ms(clock)/1000, ); - let latest_only = pyth::state::create_latest_only_for_test(); - pyth::update_cache(latest_only, &old_update, &mut price_info_object_1, &clock); - - let current_price_info = price_info::get_price_info_from_price_info_object(&price_info_object_1); - let current_price_feed = price_info::get_price_feed(¤t_price_info); - let current_price = price_feed::get_price(current_price_feed); - let current_ema_price = price_feed::get_ema_price(current_price_feed); - - // Confirm that no price update occurred when we tried to update cache with an - // outdated update: old_update. - assert!(current_price == price, 1); - assert!(current_ema_price == ema_price, 1); - - test_scenario::next_tx(&mut scenario, DEPLOYER); - - // Update the cache with a fresh update. - let fresh_price = price::new(i64::new(5243, true), 2, i64::new(3, false), timestamp + 200); - let fresh_ema_price = price::new(i64::new(8976, true), 21, i64::new(32, false), timestamp + 200); - let fresh_update = price_info::new_price_info( - 1257278600, - 1690226180, - price_feed::new( - price_identifier, - fresh_price, - fresh_ema_price, - ) + price_info::update_price_info_object( + price_info_object, + update, ); - - let latest_only = pyth::state::create_latest_only_for_test(); - pyth::update_cache(latest_only, &fresh_update, &mut price_info_object_1, &clock); - - // Confirm that the Pyth cached price got updated to fresh_price. - let current_price_info = price_info::get_price_info_from_price_info_object(&price_info_object_1); - let current_price_feed = price_info::get_price_feed(¤t_price_info); - let current_price = price_feed::get_price(current_price_feed); - let current_ema_price = price_feed::get_ema_price(current_price_feed); - - assert!(current_price==fresh_price, 0); - assert!(current_ema_price==fresh_ema_price, 0); - - return_shared(price_info_object_1); - return_shared(price_info_object_2); - return_shared(price_info_object_3); - return_shared(price_info_object_4); - - coin::burn_for_testing(test_coins); - cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); - test_scenario::end(scenario); - } - - // pyth accumulator tests (included in this file instead of pyth_accumulator.move to avoid dependency cycle - as we need pyth_tests::setup_test) - #[test] - fun test_parse_and_verify_accumulator_updates(){ - use sui::test_scenario::{Self, take_shared, return_shared}; - use sui::transfer::{Self}; - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, vector[], ACCUMULATOR_TESTS_INITIAL_GUARDIANS, 50, 0); - let worm_state = take_shared(&scenario); - test_scenario::next_tx(&mut scenario, @0x123); - - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); - - let cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); - - let price_info_updates = accumulator::parse_and_verify_accumulator_message(&mut cur, vaa::take_payload(verified_vaa), &clock); - - let expected_price_infos = accumulator_test_3_to_price_info(0); - let num_updates = vector::length(&price_info_updates); - let i = 0; - while (i < num_updates){ - assert!(price_feeds_equal(vector::borrow(&price_info_updates, i), vector::borrow(&expected_price_infos, i)), 0); - i = i + 1; - }; - - // clean-up - cursor::take_rest(cur); - transfer::public_transfer(coins, @0x1234); - clock::destroy_for_testing(clock); - return_shared(worm_state); - test_scenario::end(scenario); } +} - #[test] - fun test_parse_and_verify_accumulator_updates_with_extra_bytes_at_end_of_message(){ - use sui::test_scenario::{Self, take_shared, return_shared}; - use sui::transfer::{Self}; - - let (scenario, coins, clock) = setup_test(500, 23, ACCUMULATOR_TESTS_EMITTER_ADDRESS, vector[], ACCUMULATOR_TESTS_INITIAL_GUARDIANS, 50, 0); - let worm_state = take_shared(&scenario); - test_scenario::next_tx(&mut scenario, @0x123); - - let verified_vaa = get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); - - // append some extra garbage bytes at the end of the accumulator message, and make sure - // that parse_and_verify_accumulator_message does not error out - let test_accumulator_3_msgs_modified = TEST_ACCUMULATOR_3_MSGS; - vector::append(&mut test_accumulator_3_msgs_modified, x"1234123412341234"); - - let cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); +/// Determine if the given price update is "fresh": we have nothing newer already cached for that +/// price feed within a PriceInfoObject. +fun is_fresh_update(update: &PriceInfo, price_info_object: &PriceInfoObject): bool { + // Get the timestamp of the update's current price + let price_feed = price_info::get_price_feed(update); + let update_timestamp = price::get_timestamp(&price_feed::get_price(price_feed)); + + // Get the timestamp of the cached data for the price identifier + let cached_price_info = price_info::get_price_info_from_price_info_object( + price_info_object, + ); + let cached_price_feed = price_info::get_price_feed(&cached_price_info); + let cached_timestamp = price::get_timestamp(&price_feed::get_price(cached_price_feed)); + + update_timestamp > cached_timestamp +} - let price_info_updates = accumulator::parse_and_verify_accumulator_message(&mut cur, vaa::take_payload(verified_vaa), &clock); +// ----------------------------------------------------------------------------- +// Query the cached prices +// +// It is strongly recommended to update the cached prices using the functions above, +// before using the functions below to query the cached data. - let expected_price_infos = accumulator_test_3_to_price_info(0); - let num_updates = vector::length(&price_info_updates); - let i = 0; - while (i < num_updates){ - assert!(price_feeds_equal(vector::borrow(&price_info_updates, i), vector::borrow(&expected_price_infos, i)), 0); - i = i + 1; - }; +/// Determine if a price feed for the given price_identifier exists +public fun price_feed_exists(state: &PythState, price_identifier: PriceIdentifier): bool { + state::price_feed_object_exists(state, price_identifier) +} - // clean-up - cursor::take_rest(cur); - transfer::public_transfer(coins, @0x1234); - clock::destroy_for_testing(clock); - return_shared(worm_state); - test_scenario::end(scenario); - } +/// Get the latest available price cached for the given price identifier, if that price is +/// no older than the stale price threshold. +/// +/// Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for +/// how to how this price safely. +/// +/// Important: Pyth uses an on-demand update model, where consumers need to update the +/// cached prices before using them. Please read more about this at https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand. +/// get_price() is likely to abort unless you call update_price_feeds() to update the cached price +/// beforehand, as the cached prices may be older than the stale price threshold. +/// +/// The price_info_object is a Sui object with the key ability that uniquely +/// contains a price feed for a given price_identifier. +/// +public fun get_price(state: &PythState, price_info_object: &PriceInfoObject, clock: &Clock): Price { + get_price_no_older_than( + price_info_object, + clock, + state::get_stale_price_threshold_secs(state), + ) +} - fun price_feeds_equal(p1: &PriceInfo, p2: &PriceInfo): bool{ - price_info::get_price_feed(p1)== price_info::get_price_feed(p2) - } +/// Get the latest available price cached for the given price identifier, if that price is +/// no older than the given age. +public fun get_price_no_older_than( + price_info_object: &PriceInfoObject, + clock: &Clock, + max_age_secs: u64, +): Price { + let price = get_price_unsafe(price_info_object); + check_price_is_fresh(&price, clock, max_age_secs); + price +} - // helper functions for setting up tests +/// Get the latest available price cached for the given price identifier. +/// +/// WARNING: the returned price can be from arbitrarily far in the past. +/// This function makes no guarantees that the returned price is recent or +/// useful for any particular application. Users of this function should check +/// the returned timestamp to ensure that the returned price is sufficiently +/// recent for their application. The checked get_price_no_older_than() +/// function should be used in preference to this. +public fun get_price_unsafe(price_info_object: &PriceInfoObject): Price { + // TODO: extract Price from this guy... + let price_info = price_info::get_price_info_from_price_info_object(price_info_object); + price_feed::get_price( + price_info::get_price_feed(&price_info), + ) +} - // accumulator_test_3_to_price_info gets the data encoded within TEST_ACCUMULATOR_3_MSGS - fun accumulator_test_3_to_price_info(offset: u64): vector { - use pyth::i64::{Self}; - use pyth::price::{Self}; - let i = 0; - let feed_ids = vector[x"b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", - x"6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af", - x"31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68"]; - let expected: vector = vector[]; - while (i < 3) { - vector::push_back(&mut expected, price_info::new_price_info( - 1663680747, - 1663074349, - price_feed::new( - price_identifier::from_byte_vec( - *vector::borrow(&feed_ids, i) - ), - price::new( - i64::new(100 + i + offset, false), - 50 + i + offset, - i64::new(9 + i + offset, false), - 1687276660 + i + offset - ), - price::new( - i64::new(99 + i + offset, false), - 52 + i + offset, - i64::new(9 + i + offset, false), - 1687276660 + i + offset - ), - ), - )); - i = i + 1; - }; - return expected +fun abs_diff(x: u64, y: u64): u64 { + if (x > y) { + return x - y + } else { + return y - x } +} - // accumulator_test_1_to_price_info gets the data encoded within TEST_ACCUMULATOR_SINGLE_FEED - fun accumulator_test_1_to_price_info(): PriceInfo { - use pyth::i64::{Self}; - use pyth::price::{Self}; - price_info::new_price_info( - 1663680747, - 1663074349, - price_feed::new( - price_identifier::from_byte_vec( - x"b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6" - ), - price::new( - i64::new(6887568746747646632, false), - 13092246197863718329, - i64::new(1559537863, false), - 1687276661 - ), - price::new( - i64::new(4772242609775910581, false), - 358129956189946877, - i64::new(1559537863, false), - 1687276661 - ), - ), - ) - } +/// Get the stale price threshold: the amount of time after which a cached price +/// is considered stale and no longer returned by get_price()/get_ema_price(). +public fun get_stale_price_threshold_secs(state: &PythState): u64 { + state::get_stale_price_threshold_secs(state) +} - public fun cleanup_worm_state_pyth_state_and_clock(worm_state: WormState, pyth_state: PythState, clock: Clock){ - return_shared(worm_state); - return_shared(pyth_state); - clock::destroy_for_testing(clock); - } +fun check_price_is_fresh(price: &Price, clock: &Clock, max_age_secs: u64) { + let age = abs_diff(clock::timestamp_ms(clock)/1000, price::get_timestamp(price)); + assert!(age < max_age_secs, EStalePriceUpdate); +} - public fun take_wormhole_and_pyth_states(scenario: &Scenario): (PythState, WormState){ - (take_shared(scenario), take_shared(scenario)) - } +/// Please read more information about the update fee here: https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand#fees +public fun get_total_update_fee(pyth_state: &PythState, n: u64): u64 { + state::get_base_update_fee(pyth_state) * n } diff --git a/target_chains/sui/contracts/sources/pyth_accumulator.move b/target_chains/sui/contracts/sources/pyth_accumulator.move index 46000b3609..c8cd94a0ba 100644 --- a/target_chains/sui/contracts/sources/pyth_accumulator.move +++ b/target_chains/sui/contracts/sources/pyth_accumulator.move @@ -1,123 +1,131 @@ -module pyth::accumulator { - use std::vector::{Self}; - use sui::clock::{Clock, Self}; - use wormhole::bytes20::{Self, Bytes20}; - use wormhole::cursor::{Self, Cursor}; - use pyth::deserialize::{Self}; - use pyth::price_identifier::{Self}; - use pyth::price_info::{Self, PriceInfo}; - use pyth::price_feed::{Self}; - use pyth::merkle_tree::{Self}; - - const PRICE_FEED_MESSAGE_TYPE: u8 = 0; - const E_INVALID_UPDATE_DATA: u64 = 245; - const E_INVALID_PROOF: u64 = 345; - const E_INVALID_WORMHOLE_MESSAGE: u64 = 454; - const E_INVALID_ACCUMULATOR_PAYLOAD: u64 = 554; - const E_INVALID_ACCUMULATOR_HEADER: u64 = 657; - - const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC: u32 = 1096111958; - const PYTHNET_ACCUMULATOR_UPDATE_MAGIC: u64 = 1347305813; - - const MINIMUM_SUPPORTED_MINOR_VERSION: u8 = 0; - const MAJOR_VERSION: u8 = 1; - - friend pyth::pyth; - #[test_only] - friend pyth::pyth_tests; - - // parse_and_verify_accumulator_message verifies that the price updates encoded in the - // accumulator message (accessed via cursor) belong to the merkle tree defined by the merkle root encoded in - // vaa_payload. - public(friend) fun parse_and_verify_accumulator_message(cursor: &mut Cursor, vaa_payload: vector, clock: &Clock): vector { - let header = deserialize::deserialize_u32(cursor); - - if ((header as u64) != PYTHNET_ACCUMULATOR_UPDATE_MAGIC) { - abort E_INVALID_ACCUMULATOR_HEADER - }; - - let major = deserialize::deserialize_u8(cursor); - assert!(major == MAJOR_VERSION, E_INVALID_ACCUMULATOR_PAYLOAD); - - let minor = deserialize::deserialize_u8(cursor); - assert!(minor >= MINIMUM_SUPPORTED_MINOR_VERSION, E_INVALID_ACCUMULATOR_PAYLOAD); - - let trailing_size = deserialize::deserialize_u8(cursor); - deserialize::deserialize_vector(cursor, (trailing_size as u64)); - - let proof_type = deserialize::deserialize_u8(cursor); - assert!(proof_type == 0, E_INVALID_ACCUMULATOR_PAYLOAD); - - // Ignore the vaa in the accumulator message because presumably it has already been verified - // and passed to this function as input. - let vaa_size = deserialize::deserialize_u16(cursor); - deserialize::deserialize_vector(cursor, (vaa_size as u64)); - - let merkle_root_hash = parse_accumulator_merkle_root_from_vaa_payload(vaa_payload); - parse_and_verify_accumulator_updates(cursor, merkle_root_hash, clock) - } - - // parse_accumulator_merkle_root_from_vaa_payload takes in some VAA bytes, verifies that the vaa - // corresponds to a merkle update, and finally returns the merkle root. - // Note: this function is adapted from the Aptos Pyth accumulator - fun parse_accumulator_merkle_root_from_vaa_payload(message: vector): Bytes20 { - let msg_payload_cursor = cursor::new(message); - let payload_type = deserialize::deserialize_u32(&mut msg_payload_cursor); - assert!(payload_type == ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC, E_INVALID_WORMHOLE_MESSAGE); - let wh_message_payload_type = deserialize::deserialize_u8(&mut msg_payload_cursor); - assert!(wh_message_payload_type == 0, E_INVALID_WORMHOLE_MESSAGE); // Merkle variant - let _merkle_root_slot = deserialize::deserialize_u64(&mut msg_payload_cursor); - let _merkle_root_ring_size = deserialize::deserialize_u32(&mut msg_payload_cursor); - let merkle_root_hash = deserialize::deserialize_vector(&mut msg_payload_cursor, 20); - cursor::take_rest(msg_payload_cursor); - bytes20::new(merkle_root_hash) - } - - // Note: this parsing function is adapted from the Aptos Pyth parse_price_feed_message function - fun parse_price_feed_message(message_cur: &mut Cursor, clock: &Clock): PriceInfo { - let message_type = deserialize::deserialize_u8(message_cur); - - assert!(message_type == PRICE_FEED_MESSAGE_TYPE, E_INVALID_UPDATE_DATA); - let price_identifier = price_identifier::from_byte_vec(deserialize::deserialize_vector(message_cur, 32)); - let price = deserialize::deserialize_i64(message_cur); - let conf = deserialize::deserialize_u64(message_cur); - let expo = deserialize::deserialize_i32(message_cur); - let publish_time = deserialize::deserialize_u64(message_cur); - let _prev_publish_time = deserialize::deserialize_i64(message_cur); - let ema_price = deserialize::deserialize_i64(message_cur); - let ema_conf = deserialize::deserialize_u64(message_cur); - let price_info = price_info::new_price_info( - clock::timestamp_ms(clock) / 1000, // not used anywhere kept for backward compatibility - clock::timestamp_ms(clock) / 1000, - price_feed::new( - price_identifier, - pyth::price::new(price, conf, expo, publish_time), - pyth::price::new(ema_price, ema_conf, expo, publish_time), - ) - ); - price_info - } - - // parse_and_verify_accumulator_updates takes as input a merkle root and cursor over the encoded update message (containing encoded - // leafs and merkle proofs), iterates over each leaf/proof pair and verifies it is part of the tree, and finally outputs the set of - // decoded and authenticated PriceInfos. - fun parse_and_verify_accumulator_updates(cursor: &mut Cursor, merkle_root: Bytes20, clock: &Clock): vector { - let update_size = deserialize::deserialize_u8(cursor); - let price_info_updates: vector = vector[]; - while (update_size > 0) { - let message_size = deserialize::deserialize_u16(cursor); - let message = deserialize::deserialize_vector(cursor, (message_size as u64)); //should be safe to go from u16 to u16 - let message_cur = cursor::new(message); - let price_info = parse_price_feed_message(&mut message_cur, clock); - cursor::take_rest(message_cur); - - vector::push_back(&mut price_info_updates, price_info); - - // isProofValid pops the next merkle proof from the front of cursor and checks if it proves that message is part of the - // merkle tree defined by merkle_root - assert!(merkle_tree::is_proof_valid(cursor, merkle_root, message), E_INVALID_PROOF); - update_size = update_size - 1; - }; - price_info_updates - } +module pyth::accumulator; + +use pyth::deserialize; +use pyth::merkle_tree; +use pyth::price_feed; +use pyth::price_identifier; +use pyth::price_info::{Self, PriceInfo}; +use sui::clock::{Self, Clock}; +use wormhole::bytes20::{Self, Bytes20}; +use wormhole::cursor::{Self, Cursor}; + +const PRICE_FEED_MESSAGE_TYPE: u8 = 0; +const EEinvalidUpdateData: u64 = 245; +const EInvalidProof: u64 = 345; +const EInvalidWormholeMessage: u64 = 454; +const EInvalidAccumulatorPayload: u64 = 554; +const EInvalidAccumulatorHeader: u64 = 657; + +const ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC: u32 = 1096111958; +const PYTHNET_ACCUMULATOR_UPDATE_MAGIC: u64 = 1347305813; + +const MINIMUM_SUPPORTED_MINOR_VERSION: u8 = 0; +const MAJOR_VERSION: u8 = 1; + +// parse_and_verify_accumulator_message verifies that the price updates encoded in the +// accumulator message (accessed via cursor) belong to the merkle tree defined by the merkle root encoded in +// vaa_payload. +public(package) fun parse_and_verify_accumulator_message( + cursor: &mut Cursor, + vaa_payload: vector, + clock: &Clock, +): vector { + let header = deserialize::deserialize_u32(cursor); + + if ((header as u64) != PYTHNET_ACCUMULATOR_UPDATE_MAGIC) { + abort EInvalidAccumulatorHeader + }; + + let major = deserialize::deserialize_u8(cursor); + assert!(major == MAJOR_VERSION, EInvalidAccumulatorPayload); + + let minor = deserialize::deserialize_u8(cursor); + assert!(minor >= MINIMUM_SUPPORTED_MINOR_VERSION, EInvalidAccumulatorPayload); + + let trailing_size = deserialize::deserialize_u8(cursor); + deserialize::deserialize_vector(cursor, (trailing_size as u64)); + + let proof_type = deserialize::deserialize_u8(cursor); + assert!(proof_type == 0, EInvalidAccumulatorPayload); + + // Ignore the vaa in the accumulator message because presumably it has already been verified + // and passed to this function as input. + let vaa_size = deserialize::deserialize_u16(cursor); + deserialize::deserialize_vector(cursor, (vaa_size as u64)); + + let merkle_root_hash = parse_accumulator_merkle_root_from_vaa_payload(vaa_payload); + parse_and_verify_accumulator_updates(cursor, merkle_root_hash, clock) +} + +// parse_accumulator_merkle_root_from_vaa_payload takes in some VAA bytes, verifies that the vaa +// corresponds to a merkle update, and finally returns the merkle root. +// Note: this function is adapted from the Aptos Pyth accumulator +fun parse_accumulator_merkle_root_from_vaa_payload(message: vector): Bytes20 { + let mut msg_payload_cursor = cursor::new(message); + let payload_type = deserialize::deserialize_u32(&mut msg_payload_cursor); + assert!( + payload_type == ACCUMULATOR_UPDATE_WORMHOLE_VERIFICATION_MAGIC, + EInvalidWormholeMessage, + ); + let wh_message_payload_type = deserialize::deserialize_u8(&mut msg_payload_cursor); + assert!(wh_message_payload_type == 0, EInvalidWormholeMessage); // Merkle variant + let _merkle_root_slot = deserialize::deserialize_u64(&mut msg_payload_cursor); + let _merkle_root_ring_size = deserialize::deserialize_u32(&mut msg_payload_cursor); + let merkle_root_hash = deserialize::deserialize_vector(&mut msg_payload_cursor, 20); + cursor::take_rest(msg_payload_cursor); + bytes20::new(merkle_root_hash) +} + +// Note: this parsing function is adapted from the Aptos Pyth parse_price_feed_message function +fun parse_price_feed_message(message_cur: &mut Cursor, clock: &Clock): PriceInfo { + let message_type = deserialize::deserialize_u8(message_cur); + + assert!(message_type == PRICE_FEED_MESSAGE_TYPE, EEinvalidUpdateData); + let price_identifier = price_identifier::from_byte_vec( + deserialize::deserialize_vector(message_cur, 32), + ); + let price = deserialize::deserialize_i64(message_cur); + let conf = deserialize::deserialize_u64(message_cur); + let expo = deserialize::deserialize_i32(message_cur); + let publish_time = deserialize::deserialize_u64(message_cur); + let _prev_publish_time = deserialize::deserialize_i64(message_cur); + let ema_price = deserialize::deserialize_i64(message_cur); + let ema_conf = deserialize::deserialize_u64(message_cur); + let price_info = price_info::new_price_info( + clock::timestamp_ms(clock) / 1000, // not used anywhere kept for backward compatibility + clock::timestamp_ms(clock) / 1000, + price_feed::new( + price_identifier, + pyth::price::new(price, conf, expo, publish_time), + pyth::price::new(ema_price, ema_conf, expo, publish_time), + ), + ); + price_info +} + +// parse_and_verify_accumulator_updates takes as input a merkle root and cursor over the encoded update message (containing encoded +// leafs and merkle proofs), iterates over each leaf/proof pair and verifies it is part of the tree, and finally outputs the set of +// decoded and authenticated PriceInfos. +fun parse_and_verify_accumulator_updates( + cursor: &mut Cursor, + merkle_root: Bytes20, + clock: &Clock, +): vector { + let mut update_size = deserialize::deserialize_u8(cursor); + let mut price_info_updates: vector = vector[]; + while (update_size > 0) { + let message_size = deserialize::deserialize_u16(cursor); + let message = deserialize::deserialize_vector(cursor, (message_size as u64)); //should be safe to go from u16 to u16 + let mut message_cur = cursor::new(message); + let price_info = parse_price_feed_message(&mut message_cur, clock); + cursor::take_rest(message_cur); + + vector::push_back(&mut price_info_updates, price_info); + + // isProofValid pops the next merkle proof from the front of cursor and checks if it proves that message is part of the + // merkle tree defined by merkle_root + assert!(merkle_tree::is_proof_valid(cursor, merkle_root, message), EInvalidProof); + update_size = update_size - 1; + }; + price_info_updates } diff --git a/target_chains/sui/contracts/sources/set.move b/target_chains/sui/contracts/sources/set.move index b0f53549c8..5c62e034e0 100644 --- a/target_chains/sui/contracts/sources/set.move +++ b/target_chains/sui/contracts/sources/set.move @@ -1,46 +1,44 @@ /// A set data structure. -module pyth::set { - use sui::table::{Self, Table}; - use sui::tx_context::{TxContext}; - use std::vector; - - /// Empty struct. Used as the value type in mappings to encode a set - struct Unit has store, copy, drop {} - - /// A set containing elements of type `A` with support for membership - /// checking. - struct Set has store { - keys: vector, - elems: Table - } +module pyth::set; - /// Create a new Set. - public fun new(ctx: &mut TxContext): Set { - Set { - keys: vector::empty(), - elems: table::new(ctx), - } - } +use sui::table::{Self, Table}; - /// Add a new element to the set. - /// Aborts if the element already exists - public fun add(set: &mut Set, key: A) { - table::add(&mut set.elems, key, Unit {}); - vector::push_back(&mut set.keys, key); - } +/// Empty struct. Used as the value type in mappings to encode a set +public struct Unit has copy, drop, store {} - /// Returns true iff `set` contains an entry for `key`. - public fun contains(set: &Set, key: A): bool { - table::contains(&set.elems, key) - } +/// A set containing elements of type `A` with support for membership +/// checking. +public struct Set has store { + keys: vector, + elems: Table, +} - /// Removes all elements from the set - public fun empty(set: &mut Set) { - while (!vector::is_empty(&set.keys)) { - table::remove(&mut set.elems, vector::pop_back(&mut set.keys)); - } +/// Create a new Set. +public fun new(ctx: &mut TxContext): Set { + Set { + keys: vector::empty(), + elems: table::new(ctx), } +} - // TODO: destroy_empty, but this is tricky because std::table doesn't - // have this functionality. +/// Add a new element to the set. +/// Aborts if the element already exists +public fun add(set: &mut Set, key: A) { + table::add(&mut set.elems, key, Unit {}); + vector::push_back(&mut set.keys, key); } + +/// Returns true iff `set` contains an entry for `key`. +public fun contains(set: &Set, key: A): bool { + table::contains(&set.elems, key) +} + +/// Removes all elements from the set +public fun empty(set: &mut Set) { + while (!vector::is_empty(&set.keys)) { + table::remove(&mut set.elems, vector::pop_back(&mut set.keys)); + } +} + +// TODO: destroy_empty, but this is tricky because std::table doesn't +// have this functionality. diff --git a/target_chains/sui/contracts/sources/setup.move b/target_chains/sui/contracts/sources/setup.move index b92609bed8..495b038aa6 100644 --- a/target_chains/sui/contracts/sources/setup.move +++ b/target_chains/sui/contracts/sources/setup.move @@ -1,75 +1,68 @@ -module pyth::setup { - use sui::object::{Self, UID}; - use sui::package::{Self, UpgradeCap}; - use sui::transfer::{Self}; - use sui::tx_context::{Self, TxContext}; +module pyth::setup; - use pyth::state::{Self}; - use pyth::data_source::{DataSource}; +use pyth::data_source::DataSource; +use pyth::state; +use sui::package::{Self, UpgradeCap}; - friend pyth::pyth; - #[test_only] - friend pyth::pyth_tests; - - /// Capability created at `init`, which will be destroyed once - /// `init_and_share_state` is called. This ensures only the deployer can - /// create the shared `State`. - struct DeployerCap has key, store { - id: UID - } +/// Capability created at `init`, which will be destroyed once +/// `init_and_share_state` is called. This ensures only the deployer can +/// create the shared `State`. +public struct DeployerCap has key, store { + id: UID, +} - fun init(ctx: &mut TxContext) { - transfer::public_transfer( - DeployerCap { - id: object::new(ctx) - }, - tx_context::sender(ctx) - ); - } +fun init(ctx: &mut TxContext) { + transfer::public_transfer( + DeployerCap { + id: object::new(ctx), + }, + tx_context::sender(ctx), + ); +} - #[test_only] - public fun init_test_only(ctx: &mut TxContext) { - init(ctx); +#[test_only] +public fun init_test_only(ctx: &mut TxContext) { + init(ctx); - // This will be created and sent to the transaction sender - // automatically when the contract is published. - transfer::public_transfer( - sui::package::test_publish(object::id_from_address(@pyth), ctx), - tx_context::sender(ctx) - ); - } + // This will be created and sent to the transaction sender + // automatically when the contract is published. + transfer::public_transfer( + sui::package::test_publish(object::id_from_address(@pyth), ctx), + tx_context::sender(ctx), + ); +} - #[allow(lint(share_owned))] - /// Only the owner of the `DeployerCap` can call this method. This - /// method destroys the capability and shares the `State` object. - public(friend) fun init_and_share_state( - deployer: DeployerCap, - upgrade_cap: UpgradeCap, - stale_price_threshold: u64, - base_update_fee: u64, - governance_data_source: DataSource, - sources: vector, - ctx: &mut TxContext - ) { - wormhole::package_utils::assert_package_upgrade_cap( - &upgrade_cap, - package::compatible_policy(), - 1 - ); +#[allow(lint(share_owned))] +/// Only the owner of the `DeployerCap` can call this method. This +/// method destroys the capability and shares the `State` object. +public(package) fun init_and_share_state( + deployer: DeployerCap, + upgrade_cap: UpgradeCap, + stale_price_threshold: u64, + base_update_fee: u64, + governance_data_source: DataSource, + sources: vector, + ctx: &mut TxContext, +) { + wormhole::package_utils::assert_package_upgrade_cap( + &upgrade_cap, + package::compatible_policy(), + 1, + ); - // Destroy deployer cap. - let DeployerCap { id } = deployer; - object::delete(id); + // Destroy deployer cap. + let DeployerCap { id } = deployer; + object::delete(id); - // Share new state. - transfer::public_share_object( - state::new( - upgrade_cap, - sources, - governance_data_source, - stale_price_threshold, - base_update_fee, - ctx - )); - } + // Share new state. + transfer::public_share_object( + state::new( + upgrade_cap, + sources, + governance_data_source, + stale_price_threshold, + base_update_fee, + ctx, + ), + ); } diff --git a/target_chains/sui/contracts/sources/state.move b/target_chains/sui/contracts/sources/state.move index 9381e6457f..878ca1d5e3 100644 --- a/target_chains/sui/contracts/sources/state.move +++ b/target_chains/sui/contracts/sources/state.move @@ -1,404 +1,387 @@ -module pyth::state { - use std::vector; - use sui::object::{Self, UID, ID}; - use sui::tx_context::{Self, TxContext}; - use sui::package::{UpgradeCap, UpgradeTicket, UpgradeReceipt}; - - use pyth::data_source::{Self, DataSource}; - use pyth::price_info::{Self}; - use pyth::price_identifier::{Self, PriceIdentifier}; - use pyth::version_control::{Self}; - - use wormhole::consumed_vaas::{Self, ConsumedVAAs}; - use wormhole::bytes32::{Self, Bytes32}; - use wormhole::package_utils::{Self}; - use wormhole::external_address::{ExternalAddress}; - - friend pyth::pyth; - #[test_only] - friend pyth::pyth_tests; - 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; - friend pyth::migrate; - friend pyth::contract_upgrade; - friend pyth::set_fee_recipient; - friend pyth::setup; - - /// Build digest does not agree with current implementation. - const E_INVALID_BUILD_DIGEST: u64 = 0; - - /// Capability reflecting that the current build version is used to invoke - /// state methods. - struct LatestOnly has drop {} - - #[test_only] - public fun create_latest_only_for_test():LatestOnly { - LatestOnly{} - } +module pyth::state; + +use pyth::data_source::{Self, DataSource}; +use pyth::price_identifier::{Self, PriceIdentifier}; +use pyth::price_info; +use pyth::version_control; +use sui::package::{UpgradeCap, UpgradeTicket, UpgradeReceipt}; +use wormhole::bytes32::{Self, Bytes32}; +use wormhole::consumed_vaas::{Self, ConsumedVAAs}; +use wormhole::external_address::ExternalAddress; +use wormhole::package_utils; + +/// Build digest does not agree with current implementation. +const EInvalidBuildDigest: u64 = 0; + +/// Capability reflecting that the current build version is used to invoke +/// state methods. +public struct LatestOnly has drop {} + +#[test_only] +public fun create_latest_only_for_test(): LatestOnly { + LatestOnly {} +} - struct State has key, store { - id: UID, - governance_data_source: DataSource, - stale_price_threshold: u64, - base_update_fee: u64, - fee_recipient_address: address, - last_executed_governance_sequence: u64, - consumed_vaas: ConsumedVAAs, - - // Upgrade capability. - upgrade_cap: UpgradeCap - } +public struct State has key, store { + id: UID, + governance_data_source: DataSource, + stale_price_threshold: u64, + base_update_fee: u64, + fee_recipient_address: address, + last_executed_governance_sequence: u64, + consumed_vaas: ConsumedVAAs, + // Upgrade capability. + upgrade_cap: UpgradeCap, +} - public(friend) fun new( - upgrade_cap: UpgradeCap, - sources: vector, - governance_data_source: DataSource, - stale_price_threshold: u64, - base_update_fee: u64, - ctx: &mut TxContext - ): State { - let uid = object::new(ctx); - - // Create a set that contains all registered data sources and - // attach it to uid as a dynamic field to minimize the - // size of State. - data_source::new_data_source_registry(&mut uid, ctx); - - // Create a table that tracks the object IDs of price feeds and - // attach it to the uid as a dynamic object field to minimize the - // size of State. - price_info::new_price_info_registry(&mut uid, ctx); - - while (!vector::is_empty(&sources)) { - data_source::add(&mut uid, vector::pop_back(&mut sources)); - }; - - let consumed_vaas = consumed_vaas::new(ctx); - - // Initialize package info. This will be used for emitting information - // of successful migrations. - package_utils::init_package_info( - &mut uid, - version_control::current_version(), - &upgrade_cap - ); - - State { - id: uid, - upgrade_cap, - governance_data_source, - stale_price_threshold, - fee_recipient_address: tx_context::sender(ctx), - base_update_fee, - consumed_vaas, - last_executed_governance_sequence: 0 - } +public(package) fun new( + upgrade_cap: UpgradeCap, + mut sources: vector, + governance_data_source: DataSource, + stale_price_threshold: u64, + base_update_fee: u64, + ctx: &mut TxContext, +): State { + let mut uid = object::new(ctx); + + // Create a set that contains all registered data sources and + // attach it to uid as a dynamic field to minimize the + // size of State. + data_source::new_data_source_registry(&mut uid, ctx); + + // Create a table that tracks the object IDs of price feeds and + // attach it to the uid as a dynamic object field to minimize the + // size of State. + price_info::new_price_info_registry(&mut uid, ctx); + + while (!vector::is_empty(&sources)) { + data_source::add(&mut uid, vector::pop_back(&mut sources)); + }; + + let consumed_vaas = consumed_vaas::new(ctx); + + // Initialize package info. This will be used for emitting information + // of successful migrations. + package_utils::init_package_info( + &mut uid, + version_control::current_version(), + &upgrade_cap, + ); + + State { + id: uid, + upgrade_cap, + governance_data_source, + stale_price_threshold, + fee_recipient_address: tx_context::sender(ctx), + base_update_fee, + consumed_vaas, + last_executed_governance_sequence: 0, } +} - //////////////////////////////////////////////////////////////////////////// - // - // Simple Getters - // - // These methods do not require `LatestOnly` for access. Anyone is free to - // access these values. - // - //////////////////////////////////////////////////////////////////////////// - - public fun get_stale_price_threshold_secs(s: &State): u64 { - s.stale_price_threshold - } +//////////////////////////////////////////////////////////////////////////// +// +// Simple Getters +// +// These methods do not require `LatestOnly` for access. Anyone is free to +// access these values. +// +//////////////////////////////////////////////////////////////////////////// + +public fun get_stale_price_threshold_secs(s: &State): u64 { + s.stale_price_threshold +} - public fun get_base_update_fee(s: &State): u64 { - s.base_update_fee - } +public fun get_base_update_fee(s: &State): u64 { + s.base_update_fee +} - public fun get_fee_recipient(s: &State): address { - s.fee_recipient_address - } +public fun get_fee_recipient(s: &State): address { + s.fee_recipient_address +} - public fun is_valid_data_source(s: &State, data_source: DataSource): bool { - data_source::contains(&s.id, data_source) - } +public fun is_valid_data_source(s: &State, data_source: DataSource): bool { + data_source::contains(&s.id, data_source) +} - public fun is_valid_governance_data_source(s: &State, source: DataSource): bool { - s.governance_data_source == source - } +public fun is_valid_governance_data_source(s: &State, source: DataSource): bool { + s.governance_data_source == source +} - public fun price_feed_object_exists(s: &State, p: PriceIdentifier): bool { - price_info::contains(&s.id, p) - } +public fun price_feed_object_exists(s: &State, p: PriceIdentifier): bool { + price_info::contains(&s.id, p) +} - /// Retrieve governance chain ID, which is governance's emitter chain ID. - public fun governance_data_source(self: &State): DataSource { - self.governance_data_source - } +/// Retrieve governance chain ID, which is governance's emitter chain ID. +public fun governance_data_source(self: &State): DataSource { + self.governance_data_source +} - public fun get_last_executed_governance_sequence(self: &State): u64{ - return self.last_executed_governance_sequence - } +public fun get_last_executed_governance_sequence(self: &State): u64 { + return self.last_executed_governance_sequence +} - /// Retrieve governance module name. - public fun governance_module(): Bytes32 { - bytes32::new( - x"0000000000000000000000000000000000000000000000000000000000000001" - ) - } +/// Retrieve governance module name. +public fun governance_module(): Bytes32 { + bytes32::new( + x"0000000000000000000000000000000000000000000000000000000000000001", + ) +} - /// Retrieve governance chain ID, which is governance's emitter chain ID. - public fun governance_chain(self: &State): u16 { - let governance_data_source = governance_data_source(self); - (data_source::emitter_chain(&governance_data_source) as u16) - } +/// Retrieve governance chain ID, which is governance's emitter chain ID. +public fun governance_chain(self: &State): u16 { + let governance_data_source = governance_data_source(self); + (data_source::emitter_chain(&governance_data_source) as u16) +} - /// Retrieve governance emitter address. - public fun governance_contract(self: &State): ExternalAddress { - let governance_data_source = governance_data_source(self); - data_source::emitter_address(&governance_data_source) - } +/// Retrieve governance emitter address. +public fun governance_contract(self: &State): ExternalAddress { + let governance_data_source = governance_data_source(self); + data_source::emitter_address(&governance_data_source) +} - public fun get_price_info_object_id(self: &State, price_identifier_bytes: vector): ID { - let price_identifier = price_identifier::from_byte_vec(price_identifier_bytes); - price_info::get_id(&self.id, price_identifier) - } +public fun get_price_info_object_id(self: &State, price_identifier_bytes: vector): ID { + let price_identifier = price_identifier::from_byte_vec(price_identifier_bytes); + price_info::get_id(&self.id, price_identifier) +} - //////////////////////////////////////////////////////////////////////////// - // - // Privileged `State` Access - // - // This section of methods require a `LatestOnly`, which can only be created - // within the Wormhole package. This capability allows special access to - // the `State` object. - // - // NOTE: A lot of these methods are still marked as `(friend)` as a safety - // precaution. When a package is upgraded, friend modifiers can be - // removed. - // - //////////////////////////////////////////////////////////////////////////// - - /// Obtain a capability to interact with `State` methods. This method checks - /// that we are running the current build. - /// - /// NOTE: This method allows caching the current version check so we avoid - /// multiple checks to dynamic fields. - public(friend) fun assert_latest_only(self: &State): LatestOnly { - package_utils::assert_version( - &self.id, - version_control::current_version() - ); - - LatestOnly {} - } +//////////////////////////////////////////////////////////////////////////// +// +// Privileged `State` Access +// +// This section of methods require a `LatestOnly`, which can only be created +// within the Wormhole package. This capability allows special access to +// the `State` object. +// +// NOTE: A lot of these methods are still marked as `(friend)` as a safety +// precaution. When a package is upgraded, friend modifiers can be +// removed. +// +//////////////////////////////////////////////////////////////////////////// + +/// Obtain a capability to interact with `State` methods. This method checks +/// that we are running the current build. +/// +/// NOTE: This method allows caching the current version check so we avoid +/// multiple checks to dynamic fields. +public(package) fun assert_latest_only(self: &State): LatestOnly { + package_utils::assert_version( + &self.id, + version_control::current_version(), + ); + + LatestOnly {} +} - public(friend) fun set_fee_recipient( - _: &LatestOnly, - self: &mut State, - addr: address - ) { - self.fee_recipient_address = addr; - } +public(package) fun set_fee_recipient(_: &LatestOnly, self: &mut State, addr: address) { + self.fee_recipient_address = addr; +} - /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA - /// from being replayed. For Wormhole, the only VAAs that it cares about - /// being replayed are its governance actions. - public(friend) fun borrow_mut_consumed_vaas( - _: &LatestOnly, - self: &mut State - ): &mut ConsumedVAAs { - borrow_mut_consumed_vaas_unchecked(self) - } +/// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA +/// from being replayed. For Wormhole, the only VAAs that it cares about +/// being replayed are its governance actions. +public(package) fun borrow_mut_consumed_vaas(_: &LatestOnly, self: &mut State): &mut ConsumedVAAs { + borrow_mut_consumed_vaas_unchecked(self) +} - /// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA - /// from being replayed. For Wormhole, the only VAAs that it cares about - /// being replayed are its governance actions. - /// - /// NOTE: This method does not require `LatestOnly`. Only methods in the - /// `upgrade_contract` module requires this to be unprotected to prevent - /// a corrupted upgraded contract from bricking upgradability. - public(friend) fun borrow_mut_consumed_vaas_unchecked( - self: &mut State - ): &mut ConsumedVAAs { - &mut self.consumed_vaas - } +/// Store `VAA` hash as a way to claim a VAA. This method prevents a VAA +/// from being replayed. For Wormhole, the only VAAs that it cares about +/// being replayed are its governance actions. +/// +/// NOTE: This method does not require `LatestOnly`. Only methods in the +/// `upgrade_contract` module requires this to be unprotected to prevent +/// a corrupted upgraded contract from bricking upgradability. +public(package) fun borrow_mut_consumed_vaas_unchecked(self: &mut State): &mut ConsumedVAAs { + &mut self.consumed_vaas +} - public(friend) fun current_package(_: &LatestOnly, self: &State): ID { - package_utils::current_package(&self.id) - } +public(package) fun current_package(_: &LatestOnly, self: &State): ID { + package_utils::current_package(&self.id) +} - public(friend) fun set_data_sources(_: &LatestOnly, s: &mut State, new_sources: vector) { - // Empty the existing table of data sources registered in state. - data_source::empty(&mut s.id); - // Add the new data sources to the dynamic field registry. - while (!vector::is_empty(&new_sources)) { - data_source::add(&mut s.id, vector::pop_back(&mut new_sources)); - }; - } +public(package) fun set_data_sources( + _: &LatestOnly, + s: &mut State, + mut new_sources: vector, +) { + // Empty the existing table of data sources registered in state. + data_source::empty(&mut s.id); + // Add the new data sources to the dynamic field registry. + while (!vector::is_empty(&new_sources)) { + data_source::add(&mut s.id, vector::pop_back(&mut new_sources)); + }; +} - public(friend) fun register_price_info_object(_: &LatestOnly, s: &mut State, price_identifier: PriceIdentifier, id: ID) { - price_info::add(&mut s.id, price_identifier, id); - } +public(package) fun register_price_info_object( + _: &LatestOnly, + s: &mut State, + price_identifier: PriceIdentifier, + id: ID, +) { + price_info::add(&mut s.id, price_identifier, id); +} - public(friend) fun set_governance_data_source(_: &LatestOnly, s: &mut State, source: DataSource) { - s.governance_data_source = source; - } +public(package) fun set_governance_data_source(_: &LatestOnly, s: &mut State, source: DataSource) { + s.governance_data_source = source; +} - public(friend) fun set_last_executed_governance_sequence(_: &LatestOnly, s: &mut State, sequence: u64) { - s.last_executed_governance_sequence = sequence; - } +public(package) fun set_last_executed_governance_sequence( + _: &LatestOnly, + s: &mut State, + sequence: u64, +) { + s.last_executed_governance_sequence = sequence; +} - // We have an unchecked version of set_last_executed_governance_sequence, because in the governance contract - // upgrade code path, no LatestOnly is created (for example, see authorize_upgrade and commit_upgrade in - // governance/contract_upgrade.move) - public(friend) fun set_last_executed_governance_sequence_unchecked(s: &mut State, sequence: u64) { - s.last_executed_governance_sequence = sequence; - } +// We have an unchecked version of set_last_executed_governance_sequence, because in the governance contract +// upgrade code path, no LatestOnly is created (for example, see authorize_upgrade and commit_upgrade in +// governance/contract_upgrade.move) +public(package) fun set_last_executed_governance_sequence_unchecked(s: &mut State, sequence: u64) { + s.last_executed_governance_sequence = sequence; +} - public(friend) fun set_base_update_fee(_: &LatestOnly, s: &mut State, fee: u64) { - s.base_update_fee = fee; - } +public(package) fun set_base_update_fee(_: &LatestOnly, s: &mut State, fee: u64) { + s.base_update_fee = fee; +} - public(friend) fun set_stale_price_threshold_secs(_: &LatestOnly, s: &mut State, threshold_secs: u64) { - s.stale_price_threshold = threshold_secs; - } +public(package) fun set_stale_price_threshold_secs( + _: &LatestOnly, + s: &mut State, + threshold_secs: u64, +) { + s.stale_price_threshold = threshold_secs; +} - //////////////////////////////////////////////////////////////////////////// - // - // Upgradability - // - // A special space that controls upgrade logic. These methods are invoked - // via the `upgrade_contract` module. - // - // Also in this section is managing contract migrations, which uses the - // `migrate` module to officially roll state access to the latest build. - // Only those methods that require `LatestOnly` will be affected by an - // upgrade. - // - //////////////////////////////////////////////////////////////////////////// - - /// Issue an `UpgradeTicket` for the upgrade. - /// - /// NOTE: The Sui VM performs a check that this method is executed from the - /// latest published package. If someone were to try to execute this using - /// a stale build, the transaction will revert with `PackageUpgradeError`, - /// specifically `PackageIDDoesNotMatch`. - public(friend) fun authorize_upgrade( - self: &mut State, - package_digest: Bytes32 - ): UpgradeTicket { - let cap = &mut self.upgrade_cap; - package_utils::authorize_upgrade(&mut self.id, cap, package_digest) - } +//////////////////////////////////////////////////////////////////////////// +// +// Upgradability +// +// A special space that controls upgrade logic. These methods are invoked +// via the `upgrade_contract` module. +// +// Also in this section is managing contract migrations, which uses the +// `migrate` module to officially roll state access to the latest build. +// Only those methods that require `LatestOnly` will be affected by an +// upgrade. +// +//////////////////////////////////////////////////////////////////////////// + +/// Issue an `UpgradeTicket` for the upgrade. +/// +/// NOTE: The Sui VM performs a check that this method is executed from the +/// latest published package. If someone were to try to execute this using +/// a stale build, the transaction will revert with `PackageUpgradeError`, +/// specifically `PackageIDDoesNotMatch`. +public(package) fun authorize_upgrade(self: &mut State, package_digest: Bytes32): UpgradeTicket { + let cap = &mut self.upgrade_cap; + package_utils::authorize_upgrade(&mut self.id, cap, package_digest) +} - /// Finalize the upgrade that ran to produce the given `receipt`. - /// - /// NOTE: The Sui VM performs a check that this method is executed from the - /// latest published package. If someone were to try to execute this using - /// a stale build, the transaction will revert with `PackageUpgradeError`, - /// specifically `PackageIDDoesNotMatch`. - public(friend) fun commit_upgrade( - self: &mut State, - receipt: UpgradeReceipt - ): (ID, ID) { - let cap = &mut self.upgrade_cap; - package_utils::commit_upgrade(&mut self.id, cap, receipt) - } +/// Finalize the upgrade that ran to produce the given `receipt`. +/// +/// NOTE: The Sui VM performs a check that this method is executed from the +/// latest published package. If someone were to try to execute this using +/// a stale build, the transaction will revert with `PackageUpgradeError`, +/// specifically `PackageIDDoesNotMatch`. +public(package) fun commit_upgrade(self: &mut State, receipt: UpgradeReceipt): (ID, ID) { + let cap = &mut self.upgrade_cap; + package_utils::commit_upgrade(&mut self.id, cap, receipt) +} - /// Method executed by the `migrate` module to roll access from one package - /// to another. This method will be called from the upgraded package. - public(friend) fun migrate_version(self: &mut State) { - package_utils::migrate_version( - &mut self.id, - version_control::previous_version(), - version_control::current_version() - ); - } +/// Method executed by the `migrate` module to roll access from one package +/// to another. This method will be called from the upgraded package. +public(package) fun migrate_version(self: &mut State) { + package_utils::migrate_version( + &mut self.id, + version_control::previous_version(), + version_control::current_version(), + ); +} - /// As a part of the migration, we verify that the upgrade contract VAA's - /// encoded package digest used in `migrate` equals the one used to conduct - /// the upgrade. - public(friend) fun assert_authorized_digest( - _: &LatestOnly, - self: &State, - digest: Bytes32 - ) { - let authorized = package_utils::authorized_digest(&self.id); - assert!(digest == authorized, E_INVALID_BUILD_DIGEST); - } +/// As a part of the migration, we verify that the upgrade contract VAA's +/// encoded package digest used in `migrate` equals the one used to conduct +/// the upgrade. +public(package) fun assert_authorized_digest(_: &LatestOnly, self: &State, digest: Bytes32) { + let authorized = package_utils::authorized_digest(&self.id); + assert!(digest == authorized, EInvalidBuildDigest); +} - //////////////////////////////////////////////////////////////////////////// - // - // Special State Interaction via Migrate - // - // A VERY special space that manipulates `State` via calling `migrate`. - // - // PLEASE KEEP ANY METHODS HERE AS FRIENDS. We want the ability to remove - // these for future builds. - // - //////////////////////////////////////////////////////////////////////////// - - public(friend) fun migrate__v__0_1_1(self: &mut State) { - // We need to add dynamic fields via the new package utils method. These - // fields do not exist in the previous build (0.1.0). - // See `state::new` above. - - // Initialize package info. This will be used for emitting information - // of successful migrations. - let upgrade_cap = &self.upgrade_cap; - package_utils::init_package_info( - &mut self.id, - version_control::current_version(), - upgrade_cap, - ); - } +//////////////////////////////////////////////////////////////////////////// +// +// Special State Interaction via Migrate +// +// A VERY special space that manipulates `State` via calling `migrate`. +// +// PLEASE KEEP ANY METHODS HERE AS FRIENDS. We want the ability to remove +// these for future builds. +// +//////////////////////////////////////////////////////////////////////////// + +public(package) fun migrate__v__0_1_1(self: &mut State) { + // We need to add dynamic fields via the new package utils method. These + // fields do not exist in the previous build (0.1.0). + // See `state::new` above. + + // Initialize package info. This will be used for emitting information + // of successful migrations. + let upgrade_cap = &self.upgrade_cap; + package_utils::init_package_info( + &mut self.id, + version_control::current_version(), + upgrade_cap, + ); +} - #[test_only] - /// Bloody hack. - public fun reverse_migrate__v__0_1_0(self: &mut State) { - package_utils::remove_package_info(&mut self.id); +#[test_only] +#[allow(deprecated_usage)] +/// Bloody hack. +public fun reverse_migrate__v__0_1_0(self: &mut State) { + package_utils::remove_package_info(&mut self.id); - // Add back in old dynamic field(s)... + // Add back in old dynamic field(s)... - // Add dummy hash since this is the first time the package is published. - sui::dynamic_field::add(&mut self.id, CurrentDigest {}, bytes32::from_bytes(b"new build")); - } + // Add dummy hash since this is the first time the package is published. + sui::dynamic_field::add(&mut self.id, CurrentDigest {}, bytes32::from_bytes(b"new build")); +} - #[test_only] - public fun register_price_info_object_for_test(self: &mut State, price_identifier: PriceIdentifier, id: ID) { - price_info::add(&mut self.id, price_identifier, id); - } +#[test_only] +public fun register_price_info_object_for_test( + self: &mut State, + price_identifier: PriceIdentifier, + id: ID, +) { + price_info::add(&mut self.id, price_identifier, id); +} - #[test_only] - public fun new_state_for_test( - upgrade_cap: UpgradeCap, - governance_data_source: DataSource, - stale_price_threshold: u64, - base_update_fee: u64, - ctx: &mut TxContext - ): State { - State { - id: object::new(ctx), - upgrade_cap, - governance_data_source, - stale_price_threshold, - base_update_fee, - fee_recipient_address: tx_context::sender(ctx), - last_executed_governance_sequence: 0, - consumed_vaas: consumed_vaas::new(ctx), - } +#[test_only] +public fun new_state_for_test( + upgrade_cap: UpgradeCap, + governance_data_source: DataSource, + stale_price_threshold: u64, + base_update_fee: u64, + ctx: &mut TxContext, +): State { + State { + id: object::new(ctx), + upgrade_cap, + governance_data_source, + stale_price_threshold, + base_update_fee, + fee_recipient_address: tx_context::sender(ctx), + last_executed_governance_sequence: 0, + consumed_vaas: consumed_vaas::new(ctx), } - - //////////////////////////////////////////////////////////////////////////// - // - // Deprecated - // - // Dumping grounds for old structs and methods. These things should not - // be used in future builds. - // - //////////////////////////////////////////////////////////////////////////// - - struct CurrentDigest has store, drop, copy {} - } + +//////////////////////////////////////////////////////////////////////////// +// +// Deprecated +// +// Dumping grounds for old structs and methods. These things should not +// be used in future builds. +// +//////////////////////////////////////////////////////////////////////////// + +#[deprecated(note = b"Current Digest is deprecated")] +public struct CurrentDigest has copy, drop, store {} diff --git a/target_chains/sui/contracts/sources/version_control.move b/target_chains/sui/contracts/sources/version_control.move index ab5b3446d8..66ed37f7b6 100644 --- a/target_chains/sui/contracts/sources/version_control.move +++ b/target_chains/sui/contracts/sources/version_control.move @@ -7,75 +7,73 @@ /// is not this build's, then paths through the `state` module will abort. /// /// See `pyth::state` and `wormhole::package_utils` for more info. -module pyth::version_control { - //////////////////////////////////////////////////////////////////////////// - // - // Hard-coded Version Control - // - // Before upgrading, please set the types for `current_version` and - // `previous_version` to match the correct types (current being the latest - // version reflecting this build). - // - //////////////////////////////////////////////////////////////////////////// +module pyth::version_control; - public(friend) fun current_version(): V__0_1_2 { - V__0_1_2 {} - } +//////////////////////////////////////////////////////////////////////////// +// +// Hard-coded Version Control +// +// Before upgrading, please set the types for `current_version` and +// `previous_version` to match the correct types (current being the latest +// version reflecting this build). +// +//////////////////////////////////////////////////////////////////////////// - public(friend) fun previous_version(): V__0_1_1 { - V__0_1_1 {} - } +public(package) fun current_version(): V__0_1_2 { + V__0_1_2 {} +} - //////////////////////////////////////////////////////////////////////////// - // - // Change Log - // - // Please write release notes as doc strings for each version struct. These - // notes will be our attempt at tracking upgrades. Wish us luck. - // - //////////////////////////////////////////////////////////////////////////// +public(package) fun previous_version(): V__0_1_1 { + V__0_1_1 {} +} - /// RELEASE NOTES - /// - /// - Gas optimizations on merkle tree verifications - struct V__0_1_2 has store, drop, copy {} +//////////////////////////////////////////////////////////////////////////// +// +// Change Log +// +// Please write release notes as doc strings for each version struct. These +// notes will be our attempt at tracking upgrades. Wish us luck. +// +//////////////////////////////////////////////////////////////////////////// - /// RELEASE NOTES - /// - /// - Refactor state to use package management via - /// `wormhole::package_utils`. - /// - Add `MigrateComplete` event in `migrate`. - /// - /// Also added `migrate__v__0_1_1` in `wormhole::state`, which is - /// meant to perform a one-time `State` modification via `migrate`. - struct V__0_1_1 has store, drop, copy {} +/// RELEASE NOTES +/// +/// - Gas optimizations on merkle tree verifications +public struct V__0_1_2 has copy, drop, store {} - // Dummy. - struct V__DUMMY has store, drop, copy {} +/// RELEASE NOTES +/// +/// - Refactor state to use package management via +/// `wormhole::package_utils`. +/// - Add `MigrateComplete` event in `migrate`. +/// +/// Also added `migrate__v__0_1_1` in `wormhole::state`, which is +/// meant to perform a one-time `State` modification via `migrate`. +public struct V__0_1_1 has copy, drop, store {} - //////////////////////////////////////////////////////////////////////////// - // - // Implementation and Test-Only Methods - // - //////////////////////////////////////////////////////////////////////////// +// Dummy. +public struct V__DUMMY has copy, drop, store {} - friend pyth::state; +//////////////////////////////////////////////////////////////////////////// +// +// Implementation and Test-Only Methods +// +//////////////////////////////////////////////////////////////////////////// - #[test_only] - public fun dummy(): V__DUMMY { - V__DUMMY {} - } +#[test_only] +public fun dummy(): V__DUMMY { + V__DUMMY {} +} - #[test_only] - struct V__MIGRATED has store, drop, copy {} +#[test_only] +public struct V__MIGRATED has copy, drop, store {} - #[test_only] - public fun next_version(): V__MIGRATED { - V__MIGRATED {} - } +#[test_only] +public fun next_version(): V__MIGRATED { + V__MIGRATED {} +} - #[test_only] - public fun previous_version_test_only(): V__0_1_1 { - previous_version() - } +#[test_only] +public fun previous_version_test_only(): V__0_1_1 { + previous_version() } diff --git a/target_chains/sui/contracts/tests/pyth_tests.move b/target_chains/sui/contracts/tests/pyth_tests.move new file mode 100644 index 0000000000..eed008c1de --- /dev/null +++ b/target_chains/sui/contracts/tests/pyth_tests.move @@ -0,0 +1,1403 @@ +#[test_only] +module pyth::pyth_tests; + +use pyth::accumulator; +use pyth::data_source::{Self, DataSource}; +use pyth::deserialize; +use pyth::hot_potato_vector; +use pyth::price_feed; +use pyth::price_identifier; +use pyth::price_info::{Self, PriceInfo, PriceInfoObject}; +use pyth::pyth::{Self, create_price_infos_hot_potato, update_single_price_feed}; +use pyth::setup; +use pyth::state::State as PythState; +use sui::clock::{Self, Clock}; +use sui::coin::{Self, Coin}; +use sui::package; +use sui::sui::SUI; +use sui::test_scenario::{Self, Scenario, ctx, take_shared, return_shared}; +use wormhole::bytes32; +use wormhole::cursor; +use wormhole::external_address; +use wormhole::setup::{Self as wormhole_setup, DeployerCap}; +use wormhole::state::State as WormState; +use wormhole::vaa::{Self, VAA}; + +const DEPLOYER: address = @0x1234; +const ACCUMULATOR_TESTS_EMITTER_ADDRESS: vector = + x"71f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b"; +const ACCUMULATOR_TESTS_INITIAL_GUARDIANS: vector> = vector[ + x"7E5F4552091A69125d5DfCb7b8C2659029395Bdf", +]; +const DEFAULT_BASE_UPDATE_FEE: u64 = 50; +const DEFAULT_COIN_TO_MINT: u64 = 5000; +const BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS: vector> = vector[ + x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe", +]; + +fun ACCUMULATOR_TESTS_DATA_SOURCE(): vector { + vector[ + data_source::new( + 1, + external_address::new(bytes32::from_bytes(ACCUMULATOR_TESTS_EMITTER_ADDRESS)), + ), + ] +} + +fun get_verified_test_vaas(worm_state: &WormState, clock: &Clock): vector { + let test_vaas_: vector> = vector[ + x"0100000000010036eb563b80a24f4253bee6150eb8924e4bdf6e4fa1dfc759a6664d2e865b4b134651a7b021b7f1ce3bd078070b688b6f2e37ce2de0d9b48e6a78684561e49d5201527e4f9b00000001001171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000001005032574800030000000102000400951436e0be37536be96f0896366089506a59763d036728332d3e3038047851aea7c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1000000000000049a0000000000000008fffffffb00000000000005dc0000000000000003000000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000006150000000000000007215258d81468614f6b7e194c5d145609394f67b041e93e6695dcc616faadd0603b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe000000000000041a0000000000000003fffffffb00000000000005cb0000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e4000000000000048600000000000000078ac9cf3ab299af710d735163726fdae0db8465280502eb9f801f74b3c1bd190333832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d00000000000003f20000000000000002fffffffb00000000000005e70000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e40000000000000685000000000000000861db714e9ff987b6fedf00d01f9fea6db7c30632d6fc83b7bc9459d7192bc44a21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db800000000000006cb0000000000000001fffffffb00000000000005e40000000000000003010000000100000001000000006329c0eb000000006329c0e9000000006329c0e400000000000007970000000000000001", + ]; + let mut verified_vaas_reversed = vector::empty(); + let mut test_vaas = test_vaas_; + let mut i = 0; + while (i < vector::length(&test_vaas_)) { + let cur_test_vaa = vector::pop_back(&mut test_vaas); + let verified_vaa = vaa::parse_and_verify(worm_state, cur_test_vaa, clock); + vector::push_back(&mut verified_vaas_reversed, verified_vaa); + i = i+1; + }; + let mut verified_vaas = vector::empty(); + while (vector::length(&verified_vaas_reversed)!=0) { + let cur = vector::pop_back(&mut verified_vaas_reversed); + vector::push_back(&mut verified_vaas, cur); + }; + vector::destroy_empty(verified_vaas_reversed); + verified_vaas +} + +// get_verified_vaa_from_accumulator_message parses the accumulator message up until the vaa, then +// parses the vaa, yielding a verified wormhole::vaa::VAA object +fun get_verified_vaa_from_accumulator_message( + worm_state: &WormState, + accumulator_message: vector, + clock: &Clock, +): VAA { + let _PYTHNET_ACCUMULATOR_UPDATE_MAGIC: u64 = 1347305813; + + let mut cursor = cursor::new(accumulator_message); + let header: u32 = deserialize::deserialize_u32(&mut cursor); + assert!((header as u64) == _PYTHNET_ACCUMULATOR_UPDATE_MAGIC, 0); + let _major = deserialize::deserialize_u8(&mut cursor); + let _minor = deserialize::deserialize_u8(&mut cursor); + + let trailing_size = deserialize::deserialize_u8(&mut cursor); + deserialize::deserialize_vector(&mut cursor, (trailing_size as u64)); + + let proof_type = deserialize::deserialize_u8(&mut cursor); + assert!(proof_type == 0, 0); + + let vaa_size = deserialize::deserialize_u16(&mut cursor); + let vaa = deserialize::deserialize_vector(&mut cursor, (vaa_size as u64)); + cursor::take_rest(cursor); + vaa::parse_and_verify(worm_state, vaa, clock) +} + +#[test_only] +/// Init Wormhole core bridge state. +/// Init Pyth state. +/// Set initial Sui clock time. +/// Mint some SUI fee coins. +public fun setup_test( + stale_price_threshold: u64, + governance_emitter_chain_id: u64, + governance_emitter_address: vector, + data_sources: vector, + initial_guardians: vector>, + base_update_fee: u64, + to_mint: u64, +): (Scenario, Coin, Clock) { + let mut scenario = test_scenario::begin(DEPLOYER); + + // Initialize Wormhole core bridge. + wormhole_setup::init_test_only(ctx(&mut scenario)); + test_scenario::next_tx(&mut scenario, DEPLOYER); + // Take the `DeployerCap` from the sender of the transaction. + let deployer_cap = test_scenario::take_from_address( + &scenario, + DEPLOYER, + ); + + // This will be created and sent to the transaction sender automatically + // when the contract is published. This exists in place of grabbing + // it from the sender. + let upgrade_cap = package::test_publish( + object::id_from_address(@wormhole), + test_scenario::ctx(&mut scenario), + ); + + let governance_chain = 1234; + let governance_contract = x"deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; + let guardian_set_seconds_to_live = 5678; + let message_fee = 350; + let guardian_set_index = 0; + wormhole_setup::complete( + deployer_cap, + upgrade_cap, + governance_chain, + governance_contract, + guardian_set_index, + initial_guardians, + guardian_set_seconds_to_live, + message_fee, + test_scenario::ctx(&mut scenario), + ); + + // Initialize Pyth state. + let pyth_upgrade_cap = package::test_publish( + object::id_from_address(@pyth), + test_scenario::ctx(&mut scenario), + ); + + setup::init_test_only(ctx(&mut scenario)); + test_scenario::next_tx(&mut scenario, DEPLOYER); + let pyth_deployer_cap = test_scenario::take_from_address( + &scenario, + DEPLOYER, + ); + + setup::init_and_share_state( + pyth_deployer_cap, + pyth_upgrade_cap, + stale_price_threshold, + base_update_fee, + data_source::new( + governance_emitter_chain_id, + external_address::new(bytes32::from_bytes(governance_emitter_address)), + ), + data_sources, + ctx(&mut scenario), + ); + + let coins = coin::mint_for_testing(to_mint, ctx(&mut scenario)); + let clock = clock::create_for_testing(ctx(&mut scenario)); + (scenario, coins, clock) +} + +fun get_mock_price_infos(): vector { + use pyth::i64::Self; + use pyth::price; + vector[ + price_info::new_price_info( + 1663680747, + 1663074349, + price_feed::new( + price_identifier::from_byte_vec( + x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1", + ), + price::new(i64::new(1557, false), 7, i64::new(5, true), 1663680740), + price::new(i64::new(1500, false), 3, i64::new(5, true), 1663680740), + ), + ), + price_info::new_price_info( + 1663680747, + 1663074349, + price_feed::new( + price_identifier::from_byte_vec( + x"3b9551a68d01d954d6387aff4df1529027ffb2fee413082e509feb29cc4904fe", + ), + price::new(i64::new(1050, false), 3, i64::new(5, true), 1663680745), + price::new(i64::new(1483, false), 3, i64::new(5, true), 1663680745), + ), + ), + price_info::new_price_info( + 1663680747, + 1663074349, + price_feed::new( + price_identifier::from_byte_vec( + x"33832fad6e36eb05a8972fe5f219b27b5b2bb2230a79ce79beb4c5c5e7ecc76d", + ), + price::new(i64::new(1010, false), 2, i64::new(5, true), 1663680745), + price::new(i64::new(1511, false), 3, i64::new(5, true), 1663680745), + ), + ), + price_info::new_price_info( + 1663680747, + 1663074349, + price_feed::new( + price_identifier::from_byte_vec( + x"21a28b4c6619968bd8c20e95b0aaed7df2187fd310275347e0376a2cd7427db8", + ), + price::new(i64::new(1739, false), 1, i64::new(5, true), 1663680745), + price::new(i64::new(1508, false), 3, i64::new(5, true), 1663680745), + ), + ), + ] +} + +/// Compare the expected price feed with the actual Pyth price feeds. +fun check_price_feeds_cached(expected: &vector, actual: &vector) { + // Check that we can retrieve the correct current price and ema price for each price feed + let mut i = 0; + while (i < vector::length(expected)) { + let price_feed = price_info::get_price_feed(vector::borrow(expected, i)); + let price = price_feed::get_price(price_feed); + let ema_price = price_feed::get_ema_price(price_feed); + let price_identifier = price_info::get_price_identifier(vector::borrow(expected, i)); + + let actual_price_info = price_info::get_price_info_from_price_info_object( + vector::borrow(actual, i), + ); + let actual_price_feed = price_info::get_price_feed(&actual_price_info); + let actual_price = price_feed::get_price(actual_price_feed); + let actual_ema_price = price_feed::get_ema_price(actual_price_feed); + let actual_price_identifier = price_info::get_price_identifier(&actual_price_info); + + assert!(price == actual_price, 0); + assert!(ema_price == actual_ema_price, 0); + assert!( + price_identifier::get_bytes(&price_identifier) == price_identifier::get_bytes(&actual_price_identifier), + 0, + ); + + i = i + 1; + }; +} + +#[test] +fun test_get_update_fee() { + let (mut scenario, test_coins, _clock) = setup_test( + 500, + /* stale_price_threshold */ + 23, + /* governance emitter chain */ + x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", + vector[], + BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + 0, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + let pyth_state = take_shared(&scenario); + // Pass in a single VAA + + let single_vaa = vector[x"fb1543888001083cf2e6ef3afdcf827e89b11efd87c563638df6e1995ada9f93"]; + + assert!( + pyth::get_total_update_fee(&pyth_state, vector::length>(&single_vaa)) == DEFAULT_BASE_UPDATE_FEE, + 1, + ); + + let multiple_vaas = vector[ + x"4ee17a1a4524118de513fddcf82b77454e51be5d6fc9e29fc72dd6c204c0e4fa", + x"c72fdf81cfc939d4286c93fbaaae2eec7bae28a5926fa68646b43a279846ccc1", + x"d9a8123a793529c31200339820a3210059ecace6c044f81ecad62936e47ca049", + x"84e4f21b3e65cef47fda25d15b4eddda1edf720a1d062ccbf441d6396465fbe6", + x"9e73f9041476a93701a0b9c7501422cc2aa55d16100bec628cf53e0281b6f72f", + ]; + + // Pass in multiple VAAs + assert!( + pyth::get_total_update_fee(&pyth_state, vector::length>(&multiple_vaas)) == 5*DEFAULT_BASE_UPDATE_FEE, + 1, + ); + + return_shared(pyth_state); + coin::burn_for_testing(test_coins); + clock::destroy_for_testing(_clock); + test_scenario::end(scenario); +} + +#[test, expected_failure(abort_code = wormhole::vaa::E_WRONG_VERSION)] +fun test_create_price_feeds_corrupt_vaa() { + let (mut scenario, test_coins, clock) = setup_test( + 500, + /* stale_price_threshold */ + 23, + /* governance emitter chain */ + x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", + vector[], + vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], + 50, + 0, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + // Pass in a corrupt VAA, which should fail deserializing + let corrupt_vaa = x"90F8bf6A479f320ead074411a4B0e7944Ea8c9C1"; + let verified_vaas = vector[vaa::parse_and_verify(&worm_state, corrupt_vaa, &clock)]; + // Create Pyth price feed + pyth::create_price_feeds( + &mut pyth_state, + verified_vaas, + &clock, + ctx(&mut scenario), + ); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + coin::burn_for_testing(test_coins); + test_scenario::end(scenario); +} + +#[test, expected_failure(abort_code = ::pyth::pyth::EInvalidDataSource)] +fun test_create_price_feeds_invalid_data_source() { + // Initialize the contract with some valid data sources, excluding our test VAA's source + let data_sources = vector[ + data_source::new( + 4, + external_address::new( + bytes32::new( + x"0000000000000000000000000000000000000000000000000000000000007742", + ), + ), + ), + data_source::new( + 5, + external_address::new( + bytes32::new( + x"0000000000000000000000000000000000000000000000000000000000007637", + ), + ), + ), + ]; + let (mut scenario, test_coins, clock) = setup_test( + 500, + 23, + x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", + data_sources, + BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, + 50, + 0, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaas = get_verified_test_vaas(&worm_state, &clock); + + pyth::create_price_feeds( + &mut pyth_state, + verified_vaas, + &clock, + ctx(&mut scenario), + ); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + coin::burn_for_testing(test_coins); + test_scenario::end(scenario); +} + +public fun data_sources_for_test_vaa(): vector { + // Set some valid data sources, including our test VAA's source + vector[ + data_source::new( + 1, + external_address::new( + bytes32::from_bytes( + x"0000000000000000000000000000000000000000000000000000000000000004", + ), + ), + ), + data_source::new( + 5, + external_address::new( + bytes32::new( + x"0000000000000000000000000000000000000000000000000000000000007637", + ), + ), + ), + data_source::new( + 17, + external_address::new(bytes32::new(ACCUMULATOR_TESTS_EMITTER_ADDRESS)), + ), + ] +} + +#[test] +// test_create_and_update_price_feeds_with_batch_attestation_success tests the creation and updating of price +// feeds, as well as depositing fee coins into price info objects +fun test_create_and_update_price_feeds_with_batch_attestation_success() { + let (mut scenario, mut test_coins, clock) = setup_test( + 500, + 23, + x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", + data_sources_for_test_vaa(), + vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let mut verified_vaas = get_verified_test_vaas(&worm_state, &clock); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds( + &mut pyth_state, + verified_vaas, + &clock, + ctx(&mut scenario), + ); + + // Affirm that 4 objects, which correspond to the 4 new price info objects + // containing the price feeds were created and shared. + let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); + let shared_ids = test_scenario::shared(&effects); + let created_ids = test_scenario::created(&effects); + assert!(vector::length(&shared_ids)==4, 0); + assert!(vector::length(&created_ids)==4, 0); + + let mut price_info_object_1 = take_shared(&scenario); + let price_info_object_2 = take_shared(&scenario); + let price_info_object_3 = take_shared(&scenario); + let price_info_object_4 = take_shared(&scenario); + + // Create vector of price info objects (Sui objects with key ability and living in global store), + // which contain the price feeds we want to update. Note that these can be passed into + // update_price_feeds in any order! + //let price_info_object_vec = vector[price_info_object_1, price_info_object_2, price_info_object_3, price_info_object_4]; + verified_vaas = get_verified_test_vaas(&worm_state, &clock); + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let vaa_1 = vector::pop_back(&mut verified_vaas); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + // Create authenticated price infos + let mut vec = create_price_infos_hot_potato( + &pyth_state, + vector[vaa_1], + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let fee_coins = coin::split(&mut test_coins, DEFAULT_BASE_UPDATE_FEE, ctx(&mut scenario)); + vec = + update_single_price_feed( + &pyth_state, + vec, + &mut price_info_object_1, + fee_coins, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + // check price feed updated + assert!( + price_feeds_equal( + hot_potato_vector::borrow(&vec, 3), + &price_info::get_price_info_from_price_info_object(&price_info_object_1), + ), + 0, + ); + + // check fee coins are deposited in the price info object + assert!(price_info::get_balance(&price_info_object_1)==DEFAULT_BASE_UPDATE_FEE, 0); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + hot_potato_vector::destroy(vec); + + vector::destroy_empty(verified_vaas); + return_shared(price_info_object_1); + return_shared(price_info_object_2); + return_shared(price_info_object_3); + return_shared(price_info_object_4); + + coin::burn_for_testing(test_coins); + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +// TEST_ACCUMULATOR_SINGLE_FEED details: +// Price Identifier: 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 +// Price: 6887568746747646632 +// Conf: 13092246197863718329 +// Exponent: 1559537863 +// EMA Price: 4772242609775910581 +// EMA Conf: 358129956189946877 +// EMA Expo: 1559537863 +// Published Time: 1687276661 +const TEST_ACCUMULATOR_SINGLE_FEED: vector = + x"504e41550100000000a0010000000001005d461ac1dfffa8451edda17e4b28a46c8ae912422b2dc0cb7732828c497778ea27147fb95b4d250651931845e7f3e22c46326716bcf82be2874a9c9ab94b6e42000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000da936d73429246d131873a0bab90ad7b416510be01005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf65f958f4883f9d2a8b5b1008d1fa01db95cf4a8c7000000006491cc757be59f3f377c0d3f423a695e81ad1eb504f8554c3620c3fd02f2ee15ea639b73fa3db9b34a245bdfa015c260c5a8a1180177cf30b2c0bebbb1adfe8f7985d051d2"; + +#[test] +fun test_create_and_update_single_price_feed_with_accumulator_success() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + ACCUMULATOR_TESTS_DATA_SOURCE(), + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let mut verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_SINGLE_FEED, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_SINGLE_FEED, + verified_vaa, + &clock, + ctx(&mut scenario), + ); + + // Affirm that 1 object, which correspond to the 1 new price info object + // containing the price feeds were created and shared. + let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); + let shared_ids = test_scenario::shared(&effects); + let created_ids = test_scenario::created(&effects); + assert!(vector::length(&shared_ids)==1, 0); + assert!(vector::length(&created_ids)==1, 0); + + let mut price_info_object_1 = take_shared(&scenario); + + // Create authenticated price infos + verified_vaa = + get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_SINGLE_FEED, + &clock, + ); + let mut auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( + &pyth_state, + TEST_ACCUMULATOR_SINGLE_FEED, + verified_vaa, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + auth_price_infos = + update_single_price_feed( + &pyth_state, + auth_price_infos, + &mut price_info_object_1, + coins, + &clock, + ); + + // assert that price info obejct is as expected + let expected = accumulator_test_1_to_price_info(); + assert!( + price_feeds_equal( + &expected, + &price_info::get_price_info_from_price_info_object(&price_info_object_1), + ), + 0, + ); + + // clean up test scenario + + test_scenario::next_tx(&mut scenario, DEPLOYER); + hot_potato_vector::destroy(auth_price_infos); + + return_shared(price_info_object_1); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test, expected_failure(abort_code = ::pyth::accumulator::EInvalidProof)] +fun test_create_and_update_single_price_feed_with_accumulator_failure() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + ACCUMULATOR_TESTS_DATA_SOURCE(), + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + // the verified vaa here contains the wrong merkle root + let verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_3_MSGS, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_SINGLE_FEED, + verified_vaa, + &clock, + ctx(&mut scenario), + ); + + // clean up test scenario + test_scenario::next_tx(&mut scenario, DEPLOYER); + coin::burn_for_testing(coins); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test_only] +const TEST_ACCUMULATOR_INVALID_PROOF_1: vector = + x"504e41550100000000a001000000000100110db9cd8325ccfab0dae92eeb9ea70a1faba5c5e96dc21ff46a8ddc560afc9a60df096b8ff21172804692bbdc958153e838437d8b474cbf45f0dc2a8acae831000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b0000000000000000004155575600000000000000000000000000a8bea2b5f12f3177ff9b3929d77c3476ab2d32c602005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6fa75cd3aa3bb5ace5e2516446f71f85be36bd19bb0703f3154bb3db07be59f3f377c0d3f44661d9a8736c68884c8169e8b636ee3043202397384073120dce9e5d0efe24b44b4a0d62da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d950055006e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af5f958f4883f9d2a8b5b1008d1fa01db95cf4a8c7423a695e81ad1eb504f8554c3620c3fd40b40f7d581ac802e2de5cb82a9ae672043202397384073120dce9e5d0efe24b44b4a0d62da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; + +#[test, expected_failure(abort_code = ::pyth::accumulator::EInvalidProof)] +fun test_accumulator_invalid_proof() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + ACCUMULATOR_TESTS_DATA_SOURCE(), + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_INVALID_PROOF_1, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_INVALID_PROOF_1, + verified_vaa, + &clock, + ctx(&mut scenario), + ); + + // clean up test scenario + test_scenario::next_tx(&mut scenario, DEPLOYER); + coin::burn_for_testing(coins); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test_only] +const TEST_ACCUMULATOR_INVALID_MAJOR_VERSION: vector = + x"504e41553c00000000a001000000000100496b7fbd18dca2f0e690712fd8ca522ff79ca7d9d6d22e9f5d753fba4bd16fff440a811bad710071c79859290bcb1700de49dd8400db90b048437b521200123e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000005f5db4488a7cae9f9a6c1938340c0fbf4beb9090200550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e0454d2655c6c34e7e50580fd8c94511322968bbc6da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005500944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb049e6e88181a1e1e8b6d3c6bbb95135a73041f3b56a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; + +#[test, expected_failure(abort_code = ::pyth::accumulator::EInvalidAccumulatorPayload)] +fun test_accumulator_invalid_major_version() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + ACCUMULATOR_TESTS_DATA_SOURCE(), + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_INVALID_MAJOR_VERSION, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_INVALID_MAJOR_VERSION, + verified_vaa, + &clock, + ctx(&mut scenario), + ); + + // clean up test scenario + test_scenario::next_tx(&mut scenario, DEPLOYER); + coin::burn_for_testing(coins); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test_only] +const TEST_ACCUMULATOR_INVALID_WH_MSG: vector = + x"504e41550100000000a001000000000100e87f98238c5357730936cfdfde3a37249e5219409a4f41b301924b8eb10815a43ea2f96e4fe1bc8cd398250f39448d3b8ca57c96f9cf7a2be292517280683caa010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b00000000000000000041555755000000000000000000000000000fb6f9f2b3b6cc1c9ef6708985fef226d92a3c0801005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6fa75cd3aa3bb5ace5e2516446f71f85be36bd19b000000006491cc747be59f3f377c0d3f44661d9a8736c68884c8169e8b636ee301f2ee15ea639b73fa3db9b34a245bdfa015c260c5"; + +#[test, expected_failure(abort_code = ::pyth::accumulator::EInvalidWormholeMessage)] +fun test_accumulator_invalid_wormhole_message() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + ACCUMULATOR_TESTS_DATA_SOURCE(), + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_INVALID_WH_MSG, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_INVALID_WH_MSG, + verified_vaa, + &clock, + ctx(&mut scenario), + ); + + // clean up test scenario + test_scenario::next_tx(&mut scenario, DEPLOYER); + coin::burn_for_testing(coins); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test_only] +const TEST_ACCUMULATOR_INCREASED_MINOR_VERSION: vector = + x"504e4155010a000000a001000000000100496b7fbd18dca2f0e690712fd8ca522ff79ca7d9d6d22e9f5d753fba4bd16fff440a811bad710071c79859290bcb1700de49dd8400db90b048437b521200123e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000005f5db4488a7cae9f9a6c1938340c0fbf4beb9090200550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e0454d2655c6c34e7e50580fd8c94511322968bbc6da8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005500944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb049e6e88181a1e1e8b6d3c6bbb95135a73041f3b56a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; +#[test_only] +const TEST_ACCUMULATOR_EXTRA_PAYLOAD: vector = + x"504e41550100000000a001000000000100b2d11f181d81b4ff10beca30091754b464dc48bc1f7432d114f64a7a8f660e7964f2a0c6121bae6c1977514d46ee7a29d9395b20a45f2086071715c1dc19ab74000000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000013f83cfdf63a5a1b3189182fa0a52e6de53ba7d002005d0031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c6879bc5a3617ec3444d93c06501cf6a0909c38d4ec81d96026b71ec475e87d69c7b5124289adbf24212bed8c15db354391d2378d2e000000000000000004a576f4a87f443f7d961a682f508c4f7b06ee1595a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95005d00944998273e477b495144fb8794c914197f3ccb46be2900f4698fd0ef743c9695a573a6ff665ff63edb5f9a85ad579dc14500a2112c09680fc146134f9a539ca82cb6e3501c801278fd08d80732a24118292866bb0000000000000000045be67ba87a8dfbea404827ccbf07790299b6c023a8a1180177cf30b2c0bebbb1adfe8f7985d051d205a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; + +#[test] +fun test_accumulator_forward_compatibility() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + ACCUMULATOR_TESTS_DATA_SOURCE(), + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_EXTRA_PAYLOAD, + get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_EXTRA_PAYLOAD, + &clock, + ), + &clock, + ctx(&mut scenario), + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_INCREASED_MINOR_VERSION, + get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_INCREASED_MINOR_VERSION, + &clock, + ), + &clock, + ctx(&mut scenario), + ); + + // clean up test scenario + test_scenario::next_tx(&mut scenario, DEPLOYER); + coin::burn_for_testing(coins); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +// TEST_ACCUMULATOR_3_MSGS details: +// Price Identifier: 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6 +// Price: 100 +// Conf: 50 +// Exponent: 9 +// EMA Price: 99 +// EMA Conf: 52 +// EMA Expo: 9 +// Published Time: 1687276660 + +// Price Identifier: 0x6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af +// Price: 101 +// Conf: 51 +// Exponent: 10 +// EMA Price: 100 +// EMA Conf: 53 +// EMA Expo: 10 +// Published Time: 1687276661 + +// Price Identifier: 0x31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68 +// Price: 102 +// Conf: 52 +// Exponent: 11 +// EMA Price: 101 +// EMA Conf: 54 +// EMA Expo: 11 +// Published Time: 1687276662 +const TEST_ACCUMULATOR_3_MSGS: vector = + x"504e41550100000000a001000000000100d39b55fa311213959f91866d52624f3a9c07350d8956f6d42cfbb037883f31575c494a2f09fea84e4884dc9c244123fd124bc7825cd64d7c11e33ba5cfbdea7e010000000000000000000171f8dcb863d176e2c420ad6610cf687359612b6fb392e0642b0ca6b1f186aa3b000000000000000000415557560000000000000000000000000029da4c066b6e03b16a71e77811570dd9e19f258103005500b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf60000000000000064000000000000003200000009000000006491cc747be59f3f377c0d3f000000000000006300000000000000340436992facb15658a7e9f08c4df4848ca80750f61fadcd96993de66b1fe7aef94e29e3bbef8b12db2305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d950055006e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af000000000000006500000000000000330000000a000000006491cc7504f8554c3620c3fd0000000000000064000000000000003504171ed10ac4f1eacf3a4951e1da6b119f07c45da5adcd96993de66b1fe7aef94e29e3bbef8b12db2305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d9500550031ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68000000000000006600000000000000340000000b000000006491cc76e87d69c7b51242890000000000000065000000000000003604f2ee15ea639b73fa3db9b34a245bdfa015c260c5fe83e4772e0e346613de00e5348158a01bcb27b305a01e2504d9f0c06e7e7cb0cf24116098ca202ac5f6ade2e8f5a12ec006b16d46be1f0228b94d95"; + +#[test] +fun test_create_and_update_multiple_price_feeds_with_accumulator_success() { + let (mut scenario, mut coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + ACCUMULATOR_TESTS_DATA_SOURCE(), + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let mut verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_3_MSGS, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds_using_accumulator( + &mut pyth_state, + TEST_ACCUMULATOR_3_MSGS, + verified_vaa, + &clock, + ctx(&mut scenario), + ); + + // Affirm that 3 objects, which correspond to the 3 new price info objects + // containing the price feeds were created and shared. + let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); + let shared_ids = test_scenario::shared(&effects); + let created_ids = test_scenario::created(&effects); + assert!(vector::length(&shared_ids)==3, 0); + assert!(vector::length(&created_ids)==3, 0); + + // Create authenticated price infos + verified_vaa = + get_verified_vaa_from_accumulator_message(&worm_state, TEST_ACCUMULATOR_3_MSGS, &clock); + let mut auth_price_infos = pyth::create_authenticated_price_infos_using_accumulator( + &pyth_state, + TEST_ACCUMULATOR_3_MSGS, + verified_vaa, + &clock, + ); + + let mut idx = 0; + let expected_price_infos = accumulator_test_3_to_price_info(0 /*offset argument*/); + + while (idx < 3) { + let coin_split = coin::split(&mut coins, 1000, ctx(&mut scenario)); + let mut price_info_object = take_shared(&scenario); + auth_price_infos = + update_single_price_feed( + &pyth_state, + auth_price_infos, + &mut price_info_object, + coin_split, + &clock, + ); + let price_info = price_info::get_price_info_from_price_info_object(&price_info_object); + assert!(price_feeds_equal(&price_info, vector::borrow(&expected_price_infos, idx)), 0); + return_shared(price_info_object); + idx = idx + 1; + }; + coin::burn_for_testing(coins); + + // clean up test scenario + test_scenario::next_tx(&mut scenario, DEPLOYER); + hot_potato_vector::destroy(auth_price_infos); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test, expected_failure(abort_code = ::pyth::pyth::EInsufficientFee)] +fun test_create_and_update_price_feeds_insufficient_fee() { + // this is not enough fee and will cause a failure + let coins_to_mint = 1; + + let (mut scenario, test_coins, clock) = setup_test( + 500, + 23, + x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", + data_sources_for_test_vaa(), + vector[x"beFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe"], + DEFAULT_BASE_UPDATE_FEE, + coins_to_mint, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let mut verified_vaas = get_verified_test_vaas(&worm_state, &clock); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::create_price_feeds( + &mut pyth_state, + verified_vaas, + &clock, + ctx(&mut scenario), + ); + + // Affirm that 4 objects, which correspond to the 4 new price info objects + // containing the price feeds were created and shared. + let effects = test_scenario::next_tx(&mut scenario, DEPLOYER); + let shared_ids = test_scenario::shared(&effects); + let created_ids = test_scenario::created(&effects); + assert!(vector::length(&shared_ids)==4, 0); + assert!(vector::length(&created_ids)==4, 0); + + let mut price_info_object_1 = take_shared(&scenario); + let price_info_object_2 = take_shared(&scenario); + let price_info_object_3 = take_shared(&scenario); + let price_info_object_4 = take_shared(&scenario); + + // Create vector of price info objects (Sui objects with key ability and living in global store), + // which contain the price feeds we want to update. Note that these can be passed into + // update_price_feeds in any order! + //let price_info_object_vec = vector[price_info_object_1, price_info_object_2, price_info_object_3, price_info_object_4]; + verified_vaas = get_verified_test_vaas(&worm_state, &clock); + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let vaa_1 = vector::pop_back(&mut verified_vaas); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + // Create authenticated price infos + let mut vec = create_price_infos_hot_potato( + &pyth_state, + vector[vaa_1], + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + vec = + update_single_price_feed( + &pyth_state, + vec, + &mut price_info_object_1, + test_coins, + &clock, + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + hot_potato_vector::destroy(vec); + + vector::destroy_empty(verified_vaas); + + return_shared(price_info_object_1); + return_shared(price_info_object_2); + return_shared(price_info_object_3); + return_shared(price_info_object_4); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test] +fun test_update_cache() { + let (mut scenario, test_coins, clock) = setup_test( + 500, + 23, + x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", + data_sources_for_test_vaa(), + BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaas = get_verified_test_vaas(&worm_state, &clock); + + // Update cache is called by create_price_feeds. + pyth::create_price_feeds( + &mut pyth_state, + verified_vaas, + &clock, + ctx(&mut scenario), + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let mut price_info_object_1 = take_shared(&scenario); + let mut price_info_object_2 = take_shared(&scenario); + let mut price_info_object_3 = take_shared(&scenario); + let mut price_info_object_4 = take_shared(&scenario); + + // These updates are price infos that correspond to the ones in TEST_VAAS. + let updates = get_mock_price_infos(); + let mut price_info_object_vec = vector[ + price_info_object_1, + price_info_object_2, + price_info_object_3, + price_info_object_4, + ]; + + // Check that TEST_VAAS was indeed used to instantiate the price feeds correctly, + // by confirming that the info in updates is contained in price_info_object_vec. + check_price_feeds_cached(&updates, &price_info_object_vec); + + price_info_object_4 = vector::pop_back(&mut price_info_object_vec); + price_info_object_3 = vector::pop_back(&mut price_info_object_vec); + price_info_object_2 = vector::pop_back(&mut price_info_object_vec); + price_info_object_1 = vector::pop_back(&mut price_info_object_vec); + vector::destroy_empty(price_info_object_vec); + + return_shared(price_info_object_1); + return_shared(price_info_object_2); + return_shared(price_info_object_3); + return_shared(price_info_object_4); + coin::burn_for_testing(test_coins); + + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +#[test] +fun test_update_cache_old_update() { + use pyth::i64::Self; + use pyth::price::Self; + + let (mut scenario, test_coins, clock) = setup_test( + 500, + 23, + x"5d1f252d5de865279b00c84bce362774c2804294ed53299bc4a0389a5defef92", + data_sources_for_test_vaa(), + BATCH_ATTESTATION_TEST_INITIAL_GUARDIANS, + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + let verified_vaas = get_verified_test_vaas(&worm_state, &clock); + + pyth::create_price_feeds( + &mut pyth_state, + verified_vaas, + &clock, + ctx(&mut scenario), + ); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + let mut price_info_object_1 = take_shared(&scenario); + let price_info_object_2 = take_shared(&scenario); + let price_info_object_3 = take_shared(&scenario); + let price_info_object_4 = take_shared(&scenario); + + // Hardcode the price identifier, price, and ema_price for price_info_object_1, because + // it's easier than unwrapping price_info_object_1 and getting the quantities via getters. + let timestamp = 1663680740; + let price_identifier = price_identifier::from_byte_vec( + x"c6c75c89f14810ec1c54c03ab8f1864a4c4032791f05747f560faec380a695d1", + ); + let price = price::new(i64::new(1557, false), 7, i64::new(5, true), timestamp); + let ema_price = price::new(i64::new(1500, false), 3, i64::new(5, true), timestamp); + + // Attempt to update the price with an update older than the current cached one. + let old_price = price::new(i64::new(1243, true), 9802, i64::new(6, false), timestamp - 200); + let old_ema_price = price::new( + i64::new(8976, true), + 234, + i64::new(897, false), + timestamp - 200, + ); + let old_update = price_info::new_price_info( + 1257278600, + 1690226180, + price_feed::new( + price_identifier, + old_price, + old_ema_price, + ), + ); + let latest_only = ::pyth::state::create_latest_only_for_test(); + pyth::update_cache(latest_only, &old_update, &mut price_info_object_1, &clock); + + let current_price_info = price_info::get_price_info_from_price_info_object( + &price_info_object_1, + ); + let current_price_feed = price_info::get_price_feed(¤t_price_info); + let current_price = price_feed::get_price(current_price_feed); + let current_ema_price = price_feed::get_ema_price(current_price_feed); + + // Confirm that no price update occurred when we tried to update cache with an + // outdated update: old_update. + assert!(current_price == price, 1); + assert!(current_ema_price == ema_price, 1); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + // Update the cache with a fresh update. + let fresh_price = price::new(i64::new(5243, true), 2, i64::new(3, false), timestamp + 200); + let fresh_ema_price = price::new( + i64::new(8976, true), + 21, + i64::new(32, false), + timestamp + 200, + ); + let fresh_update = price_info::new_price_info( + 1257278600, + 1690226180, + price_feed::new( + price_identifier, + fresh_price, + fresh_ema_price, + ), + ); + + let latest_only = ::pyth::state::create_latest_only_for_test(); + pyth::update_cache(latest_only, &fresh_update, &mut price_info_object_1, &clock); + + // Confirm that the Pyth cached price got updated to fresh_price. + let current_price_info = price_info::get_price_info_from_price_info_object( + &price_info_object_1, + ); + let current_price_feed = price_info::get_price_feed(¤t_price_info); + let current_price = price_feed::get_price(current_price_feed); + let current_ema_price = price_feed::get_ema_price(current_price_feed); + + assert!(current_price==fresh_price, 0); + assert!(current_ema_price==fresh_ema_price, 0); + + return_shared(price_info_object_1); + return_shared(price_info_object_2); + return_shared(price_info_object_3); + return_shared(price_info_object_4); + + coin::burn_for_testing(test_coins); + cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} + +// pyth accumulator tests (included in this file instead of pyth_accumulator.move to avoid dependency cycle - as we need pyth_tests::setup_test) +#[test] +fun test_parse_and_verify_accumulator_updates() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + vector[], + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + 50, + 0, + ); + let worm_state = take_shared(&scenario); + test_scenario::next_tx(&mut scenario, @0x123); + + let verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_3_MSGS, + &clock, + ); + + let mut cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); + + let price_info_updates = accumulator::parse_and_verify_accumulator_message( + &mut cur, + vaa::take_payload(verified_vaa), + &clock, + ); + + let expected_price_infos = accumulator_test_3_to_price_info(0); + let num_updates = vector::length(&price_info_updates); + let mut i = 0; + while (i < num_updates) { + assert!( + price_feeds_equal( + vector::borrow(&price_info_updates, i), + vector::borrow(&expected_price_infos, i), + ), + 0, + ); + i = i + 1; + }; + + // clean-up + cursor::take_rest(cur); + transfer::public_transfer(coins, @0x1234); + clock::destroy_for_testing(clock); + return_shared(worm_state); + test_scenario::end(scenario); +} + +#[test] +fun test_parse_and_verify_accumulator_updates_with_extra_bytes_at_end_of_message() { + let (mut scenario, coins, clock) = setup_test( + 500, + 23, + ACCUMULATOR_TESTS_EMITTER_ADDRESS, + vector[], + ACCUMULATOR_TESTS_INITIAL_GUARDIANS, + 50, + 0, + ); + let worm_state = take_shared(&scenario); + test_scenario::next_tx(&mut scenario, @0x123); + + let verified_vaa = get_verified_vaa_from_accumulator_message( + &worm_state, + TEST_ACCUMULATOR_3_MSGS, + &clock, + ); + + // append some extra garbage bytes at the end of the accumulator message, and make sure + // that parse_and_verify_accumulator_message does not error out + let mut test_accumulator_3_msgs_modified = TEST_ACCUMULATOR_3_MSGS; + vector::append(&mut test_accumulator_3_msgs_modified, x"1234123412341234"); + + let mut cur = cursor::new(TEST_ACCUMULATOR_3_MSGS); + + let price_info_updates = accumulator::parse_and_verify_accumulator_message( + &mut cur, + vaa::take_payload(verified_vaa), + &clock, + ); + + let expected_price_infos = accumulator_test_3_to_price_info(0); + let num_updates = vector::length(&price_info_updates); + let mut i = 0; + while (i < num_updates) { + assert!( + price_feeds_equal( + vector::borrow(&price_info_updates, i), + vector::borrow(&expected_price_infos, i), + ), + 0, + ); + i = i + 1; + }; + + // clean-up + cursor::take_rest(cur); + transfer::public_transfer(coins, @0x1234); + clock::destroy_for_testing(clock); + return_shared(worm_state); + test_scenario::end(scenario); +} + +fun price_feeds_equal(p1: &PriceInfo, p2: &PriceInfo): bool { + price_info::get_price_feed(p1)== price_info::get_price_feed(p2) +} + +// helper functions for setting up tests + +// accumulator_test_3_to_price_info gets the data encoded within TEST_ACCUMULATOR_3_MSGS +fun accumulator_test_3_to_price_info(offset: u64): vector { + use pyth::i64; + use pyth::price; + let mut i = 0; + let feed_ids = vector[ + x"b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + x"6e1540171b6c0c960b71a7020d9f60077f6af931a8bbf590da0223dacf75c7af", + x"31ecc21a745e3968a04e9570e4425bc18fa8019c68028196b546d1669c200c68", + ]; + let mut expected: vector = vector[]; + while (i < 3) { + vector::push_back( + &mut expected, + price_info::new_price_info( + 1663680747, + 1663074349, + price_feed::new( + price_identifier::from_byte_vec( + *vector::borrow(&feed_ids, i), + ), + price::new( + i64::new(100 + i + offset, false), + 50 + i + offset, + i64::new(9 + i + offset, false), + 1687276660 + i + offset, + ), + price::new( + i64::new(99 + i + offset, false), + 52 + i + offset, + i64::new(9 + i + offset, false), + 1687276660 + i + offset, + ), + ), + ), + ); + i = i + 1; + }; + return expected +} + +// accumulator_test_1_to_price_info gets the data encoded within TEST_ACCUMULATOR_SINGLE_FEED +fun accumulator_test_1_to_price_info(): PriceInfo { + use pyth::i64; + use pyth::price; + price_info::new_price_info( + 1663680747, + 1663074349, + price_feed::new( + price_identifier::from_byte_vec( + x"b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + ), + price::new( + i64::new(6887568746747646632, false), + 13092246197863718329, + i64::new(1559537863, false), + 1687276661, + ), + price::new( + i64::new(4772242609775910581, false), + 358129956189946877, + i64::new(1559537863, false), + 1687276661, + ), + ), + ) +} + +public fun cleanup_worm_state_pyth_state_and_clock( + worm_state: WormState, + pyth_state: PythState, + clock: Clock, +) { + return_shared(worm_state); + return_shared(pyth_state); + clock::destroy_for_testing(clock); +} + +public fun take_wormhole_and_pyth_states(scenario: &Scenario): (PythState, WormState) { + (take_shared(scenario), take_shared(scenario)) +} diff --git a/target_chains/sui/contracts/tests/set_data_sources_tests.move b/target_chains/sui/contracts/tests/set_data_sources_tests.move new file mode 100644 index 0000000000..f036735bbe --- /dev/null +++ b/target_chains/sui/contracts/tests/set_data_sources_tests.move @@ -0,0 +1,100 @@ +module pyth::set_data_sources_tests; + +use pyth::data_source; +use pyth::pyth_tests::{Self, setup_test, take_wormhole_and_pyth_states}; +use pyth::state; +use sui::coin; +use sui::test_scenario; +use wormhole::bytes32; +use wormhole::external_address; + +const SET_DATA_SOURCES_VAA: vector = + x"01000000000100b29ee59868b9066b04d8d59e1c7cc66f0678eaf4c58b8c87e4405d6de615f64b04da4025719aeed349e03900f37829454d62cc7fc7bca80328c31fe40be7b21b010000000000000000000163278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c3850000000000000001015054474d0102001503001ae101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71001aa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b60001f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0"; +// VAA Info: +// module name: 0x1 +// action: 2 +// chain: 21 +// data sources (chain, addr) pairs: [(1, 0xf346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0), (26, 0xa27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6), (26, 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71)] + +const DEPLOYER: address = @0x1234; +const DEFAULT_BASE_UPDATE_FEE: u64 = 0; +const DEFAULT_COIN_TO_MINT: u64 = 0; + +#[test] +fun set_data_sources() { + let (mut scenario, test_coins, clock) = setup_test( + 500, + 1, + x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", + pyth_tests::data_sources_for_test_vaa(), + vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaa = wormhole::vaa::parse_and_verify( + &worm_state, + SET_DATA_SOURCES_VAA, + &clock, + ); + + let receipt = pyth::governance::verify_vaa(&pyth_state, verified_vaa); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::governance::execute_governance_instruction(&mut pyth_state, receipt); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + // assert data sources are set correctly + + assert!( + state::is_valid_data_source( + &pyth_state, + data_source::new( + 1, + external_address::new( + bytes32::from_bytes( + x"f346195ac02f37d60d4db8ffa6ef74cb1be3550047543a4a9ee9acf4d78697b0", + ), + ), + ), + ), + 0, + ); + assert!( + state::is_valid_data_source( + &pyth_state, + data_source::new( + 26, + external_address::new( + bytes32::from_bytes( + x"a27839d641b07743c0cb5f68c51f8cd31d2c0762bec00dc6fcd25433ef1ab5b6", + ), + ), + ), + ), + 0, + ); + assert!( + state::is_valid_data_source( + &pyth_state, + data_source::new( + 26, + external_address::new( + bytes32::from_bytes( + x"e101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71", + ), + ), + ), + ), + 0, + ); + + // clean up + coin::burn_for_testing(test_coins); + pyth_tests::cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} diff --git a/target_chains/sui/contracts/tests/set_stale_price_threshold_tests.move b/target_chains/sui/contracts/tests/set_stale_price_threshold_tests.move new file mode 100644 index 0000000000..fc08808ba9 --- /dev/null +++ b/target_chains/sui/contracts/tests/set_stale_price_threshold_tests.move @@ -0,0 +1,55 @@ +module pyth::set_stale_price_threshold_tests; + +use pyth::pyth_tests::{Self, setup_test, take_wormhole_and_pyth_states}; +use pyth::state; +use sui::coin; +use sui::test_scenario; + +const SET_STALE_PRICE_THRESHOLD_VAA: vector = + x"010000000001000393eabdb4983e91e0fcfe7e6b2fc5c8fca2847fde52fd2f51a9b26b12298da13af09c271ce7723af8e0b1f52afa02b56f0b64764739b1b05e2f2c5cec80567c000000000000000000000163278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c3850000000000000001015054474d0104001500000000000f4020"; +// VAA Info: +// module name: 0x1 +// action: 4 +// chain: 21 +// stale price threshold: 999456 + +const DEPLOYER: address = @0x1234; +const DEFAULT_BASE_UPDATE_FEE: u64 = 0; +const DEFAULT_COIN_TO_MINT: u64 = 0; + +#[test] +fun set_stale_price_threshold() { + let (mut scenario, test_coins, clock) = setup_test( + 500, + 1, + x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", + pyth_tests::data_sources_for_test_vaa(), + vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaa = wormhole::vaa::parse_and_verify( + &worm_state, + SET_STALE_PRICE_THRESHOLD_VAA, + &clock, + ); + + let receipt = pyth::governance::verify_vaa(&pyth_state, verified_vaa); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::governance::execute_governance_instruction(&mut pyth_state, receipt); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + // assert stale price threshold is set correctly + assert!(state::get_stale_price_threshold_secs(&pyth_state)==999456, 0); + + // clean up + coin::burn_for_testing(test_coins); + pyth_tests::cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +} diff --git a/target_chains/sui/contracts/tests/set_update_fee_tests.move b/target_chains/sui/contracts/tests/set_update_fee_tests.move new file mode 100644 index 0000000000..c2f92152ab --- /dev/null +++ b/target_chains/sui/contracts/tests/set_update_fee_tests.move @@ -0,0 +1,52 @@ +#[test_only] +module pyth::set_update_fee_tests; + +use pyth::pyth_tests::{Self, setup_test, take_wormhole_and_pyth_states}; +use pyth::state; +use sui::coin; +use sui::test_scenario; + +const SET_FEE_VAA: vector = + x"01000000000100189d01616814b185b5a26bde6123d48e0d44dd490bbb3bde5d12076247b2180068a8261165777076ae532b7b0739aaee6411c8ba0695d20d4fa548227ce15d8d010000000000000000000163278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c3850000000000000001015054474d0103001500000000000000050000000000000005"; +// VAA Info: +// module name: 0x1 +// action: 3 +// chain: 21 +// new fee: 5, new exponent: 5 + +const DEPLOYER: address = @0x1234; +const DEFAULT_BASE_UPDATE_FEE: u64 = 0; +const DEFAULT_COIN_TO_MINT: u64 = 0; + +#[test] +fun test_set_update_fee() { + let (mut scenario, test_coins, clock) = setup_test( + 500, + 1, + x"63278d271099bfd491951b3e648f08b1c71631e4a53674ad43e8f9f98068c385", + pyth_tests::data_sources_for_test_vaa(), + vector[x"13947bd48b18e53fdaeee77f3473391ac727c638"], + DEFAULT_BASE_UPDATE_FEE, + DEFAULT_COIN_TO_MINT, + ); + test_scenario::next_tx(&mut scenario, DEPLOYER); + let (mut pyth_state, worm_state) = take_wormhole_and_pyth_states(&scenario); + + let verified_vaa = wormhole::vaa::parse_and_verify(&worm_state, SET_FEE_VAA, &clock); + + let receipt = pyth::governance::verify_vaa(&pyth_state, verified_vaa); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + pyth::governance::execute_governance_instruction(&mut pyth_state, receipt); + + test_scenario::next_tx(&mut scenario, DEPLOYER); + + // assert fee is set correctly + assert!(state::get_base_update_fee(&pyth_state)==500000, 0); + + // clean up + coin::burn_for_testing(test_coins); + pyth_tests::cleanup_worm_state_pyth_state_and_clock(worm_state, pyth_state, clock); + test_scenario::end(scenario); +}