diff --git a/target_chains/starknet/contracts/deploy/local_deploy b/target_chains/starknet/contracts/deploy/local_deploy index 8213134cc6..6bdeb8e292 100755 --- a/target_chains/starknet/contracts/deploy/local_deploy +++ b/target_chains/starknet/contracts/deploy/local_deploy @@ -72,6 +72,9 @@ pyth_address=$(starkli deploy "${pyth_hash}" \ 1 `# num_data_sources` \ 26 `# emitter_chain_id` \ 58051393581875729396504816158954007153 299086580781278228892874716333010393740 `# emitter_address` \ + 1 `# governance_emitter_chain_id` \ + 41 0 `# governance_emitter_address` \ + 0 `# governance_initial_sequence` \ ) ${sleep} diff --git a/target_chains/starknet/contracts/src/pyth.cairo b/target_chains/starknet/contracts/src/pyth.cairo index 4e04df8a93..1a46f33932 100644 --- a/target_chains/starknet/contracts/src/pyth.cairo +++ b/target_chains/starknet/contracts/src/pyth.cairo @@ -16,7 +16,7 @@ mod pyth { use pyth::byte_array::{ByteArray, ByteArrayImpl}; use core::panic_with_felt252; use core::starknet::{ContractAddress, get_caller_address, get_execution_info}; - use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait}; + use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait, VerifiedVM}; use super::{ DataSource, UpdatePriceFeedsError, GovernanceActionError, Price, GetPriceUnsafeError }; @@ -48,6 +48,8 @@ mod pyth { // For fast validation. is_valid_data_source: LegacyMap, latest_price_info: LegacyMap, + governance_data_source: DataSource, + last_executed_governance_sequence: u64, } /// Initializes the Pyth contract. @@ -73,13 +75,25 @@ mod pyth { wormhole_address: ContractAddress, fee_contract_address: ContractAddress, single_update_fee: u256, - data_sources: Array + data_sources: Array, + governance_emitter_chain_id: u16, + governance_emitter_address: u256, + governance_initial_sequence: u64, ) { self.owner.write(wormhole_address); self.wormhole_address.write(wormhole_address); self.fee_contract_address.write(fee_contract_address); self.single_update_fee.write(single_update_fee); self.write_data_sources(data_sources); + self + .governance_data_source + .write( + DataSource { + emitter_chain_id: governance_emitter_chain_id, + emitter_address: governance_emitter_address, + } + ); + self.last_executed_governance_sequence.write(governance_initial_sequence); } #[abi(embed_v0)] @@ -171,6 +185,15 @@ mod pyth { panic_with_felt252(UpdatePriceFeedsError::InvalidUpdateData.into()); } } + + fn execute_governance_instruction(ref self: ContractState, data: ByteArray) { + let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() }; + let vm = wormhole.parse_and_verify_vm(data); + self.verify_governance_vm(@vm); + //... + self.last_executed_governance_sequence.write(vm.sequence); + panic_with_felt252('todo'); + } } #[generate_trait] @@ -221,5 +244,18 @@ mod pyth { fn get_total_fee(ref self: ContractState, num_updates: u8) -> u256 { self.single_update_fee.read() * num_updates.into() } + + fn verify_governance_vm(self: @ContractState, vm: @VerifiedVM) { + let governance_data_source = self.governance_data_source.read(); + if governance_data_source.emitter_chain_id != *vm.emitter_chain_id { + panic_with_felt252(GovernanceActionError::InvalidGovernanceDataSource.into()); + } + if governance_data_source.emitter_address != *vm.emitter_address { + panic_with_felt252(GovernanceActionError::InvalidGovernanceDataSource.into()); + } + if *vm.sequence <= self.last_executed_governance_sequence.read() { + panic_with_felt252(GovernanceActionError::OldGovernanceMessage.into()); + } + } } } diff --git a/target_chains/starknet/contracts/src/pyth/errors.cairo b/target_chains/starknet/contracts/src/pyth/errors.cairo index 899220623a..8a74c44156 100644 --- a/target_chains/starknet/contracts/src/pyth/errors.cairo +++ b/target_chains/starknet/contracts/src/pyth/errors.cairo @@ -14,12 +14,22 @@ impl GetPriceUnsafeErrorIntoFelt252 of Into { #[derive(Copy, Drop, Debug, Serde, PartialEq)] pub enum GovernanceActionError { AccessDenied, + Wormhole: pyth::wormhole::ParseAndVerifyVmError, + InvalidGovernanceDataSource, + OldGovernanceMessage, + InvalidGovernanceTarget, + InvalidGovernanceMessage, } impl GovernanceActionErrorIntoFelt252 of Into { fn into(self: GovernanceActionError) -> felt252 { match self { GovernanceActionError::AccessDenied => 'access denied', + GovernanceActionError::Wormhole(err) => err.into(), + GovernanceActionError::InvalidGovernanceDataSource => 'invalid governance data source', + GovernanceActionError::OldGovernanceMessage => 'old governance message', + GovernanceActionError::InvalidGovernanceTarget => 'invalid governance target', + GovernanceActionError::InvalidGovernanceMessage => 'invalid governance message', } } } diff --git a/target_chains/starknet/contracts/src/pyth/interface.cairo b/target_chains/starknet/contracts/src/pyth/interface.cairo index 02eca0aceb..67e5991323 100644 --- a/target_chains/starknet/contracts/src/pyth/interface.cairo +++ b/target_chains/starknet/contracts/src/pyth/interface.cairo @@ -8,6 +8,7 @@ pub trait IPyth { fn set_data_sources(ref self: T, sources: Array); fn set_fee(ref self: T, single_update_fee: u256); fn update_price_feeds(ref self: T, data: ByteArray); + fn execute_governance_instruction(ref self: T, data: ByteArray); } #[derive(Drop, Debug, Clone, Copy, Hash, Default, Serde, starknet::Store)] diff --git a/target_chains/starknet/contracts/tests/pyth.cairo b/target_chains/starknet/contracts/tests/pyth.cairo index 9d39126b57..131eca2c6f 100644 --- a/target_chains/starknet/contracts/tests/pyth.cairo +++ b/target_chains/starknet/contracts/tests/pyth.cairo @@ -33,20 +33,9 @@ fn decode_event(event: @Event) -> PythEvent { fn update_price_feeds_works() { let owner = 'owner'.try_into().unwrap(); let user = 'user'.try_into().unwrap(); - let wormhole = super::wormhole::deploy_and_init(); + let wormhole = super::wormhole::deploy_with_mainnet_guardians(); let fee_contract = deploy_fee_contract(user); - let pyth = deploy( - owner, - wormhole.contract_address, - fee_contract.contract_address, - 1000, - array![ - DataSource { - emitter_chain_id: 26, - emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71, - } - ] - ); + let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address); start_prank(CheatTarget::One(fee_contract.contract_address), user.try_into().unwrap()); fee_contract.approve(pyth.contract_address, 10000); @@ -88,16 +77,66 @@ fn update_price_feeds_works() { assert!(last_ema_price.publish_time == 1712589206); } +#[test] +#[should_panic(expected: ('todo',))] +fn test_governance_instruction() { + let owner = 'owner'.try_into().unwrap(); + let wormhole = super::wormhole::deploy_with_test_guardian(); + let fee_contract = deploy_fee_contract('fee_minter'.try_into().unwrap()); + let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address); + + pyth + .execute_governance_instruction( + ByteArrayImpl::new( + array_try_into( + array![ + 1766847064779996587365624568667043246291590523274145657502751050464410443, + 182147492092754791035110789954683447734750444021289139488583154515468770101, + 260016405939643347307460729490551925646487038190069904485177441375406260224, + 49565958604199796163020368, + 8072278384728444780182694421117884443886221966887092232, + ] + ), + 23 + ) + ); +} + +fn deploy_default( + owner: ContractAddress, wormhole_address: ContractAddress, fee_contract_address: ContractAddress +) -> IPythDispatcher { + deploy( + owner, + wormhole_address, + fee_contract_address, + 1000, + array![ + DataSource { + emitter_chain_id: 26, + emitter_address: 0xe101faedac5851e32b9b23b5f9411a8c2bac4aae3ed4dd7b811dd1a72ea4aa71, + } + ], + 1, + 41, + 0, + ) +} + + fn deploy( owner: ContractAddress, wormhole_address: ContractAddress, fee_contract_address: ContractAddress, single_update_fee: u256, - data_sources: Array + data_sources: Array, + governance_emitter_chain_id: u16, + governance_emitter_address: u256, + governance_initial_sequence: u64, ) -> IPythDispatcher { let mut args = array![]; (owner, wormhole_address, fee_contract_address, single_update_fee).serialize(ref args); - data_sources.serialize(ref args); + (data_sources, governance_emitter_chain_id).serialize(ref args); + (governance_emitter_address, governance_initial_sequence).serialize(ref args); let contract = declare("pyth"); let contract_address = match contract.deploy(@args) { Result::Ok(v) => { v }, diff --git a/target_chains/starknet/contracts/tests/wormhole.cairo b/target_chains/starknet/contracts/tests/wormhole.cairo index dcef502b23..871c18446f 100644 --- a/target_chains/starknet/contracts/tests/wormhole.cairo +++ b/target_chains/starknet/contracts/tests/wormhole.cairo @@ -8,7 +8,7 @@ use core::panic_with_felt252; #[test] fn test_parse_and_verify_vm_works() { - let dispatcher = deploy_and_init(); + let dispatcher = deploy_with_mainnet_guardians(); let vm = dispatcher.parse_and_verify_vm(good_vm1()); assert!(vm.version == 1); @@ -37,7 +37,7 @@ fn test_parse_and_verify_vm_works() { #[fuzzer(runs: 100, seed: 0)] #[should_panic] fn test_parse_and_verify_vm_rejects_corrupted_vm(pos: usize, random1: usize, random2: usize) { - let dispatcher = deploy_and_init(); + let dispatcher = deploy_with_mainnet_guardians(); let input = corrupted_vm(good_vm1(), pos, random1, random2); let vm = dispatcher.parse_and_verify_vm(input); @@ -47,12 +47,7 @@ fn test_parse_and_verify_vm_rejects_corrupted_vm(pos: usize, random1: usize, ran #[test] #[should_panic(expected: ('wrong governance contract',))] fn test_submit_guardian_set_rejects_invalid_emitter() { - let dispatcher = deploy( - array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]), - CHAIN_ID, - GOVERNANCE_CHAIN_ID, - GOVERNANCE_CONTRACT - ); + let dispatcher = deploy_with_test_guardian(); dispatcher .submit_new_guardian_set( @@ -84,12 +79,7 @@ fn test_submit_guardian_set_rejects_wrong_index_in_signer() { #[test] #[should_panic(expected: ('invalid guardian set sequence',))] fn test_submit_guardian_set_rejects_wrong_index_in_payload() { - let dispatcher = deploy( - array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]), - CHAIN_ID, - GOVERNANCE_CHAIN_ID, - GOVERNANCE_CONTRACT - ); + let dispatcher = deploy_with_test_guardian(); dispatcher .submit_new_guardian_set( @@ -118,12 +108,7 @@ fn test_deploy_rejects_empty() { #[test] #[should_panic(expected: ('no guardians specified',))] fn test_submit_guardian_set_rejects_empty() { - let dispatcher = deploy( - array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]), - CHAIN_ID, - GOVERNANCE_CHAIN_ID, - GOVERNANCE_CONTRACT - ); + let dispatcher = deploy_with_test_guardian(); dispatcher .submit_new_guardian_set( @@ -184,7 +169,7 @@ fn deploy( IWormholeDispatcher { contract_address } } -pub fn deploy_and_init() -> IWormholeDispatcher { +pub fn deploy_with_mainnet_guardians() -> IWormholeDispatcher { let dispatcher = deploy(guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT); dispatcher.submit_new_guardian_set(governance_upgrade_vm1()); @@ -195,6 +180,15 @@ pub fn deploy_and_init() -> IWormholeDispatcher { dispatcher } +pub fn deploy_with_test_guardian() -> IWormholeDispatcher { + deploy( + array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]), + CHAIN_ID, + GOVERNANCE_CHAIN_ID, + GOVERNANCE_CONTRACT + ) +} + fn corrupted_vm(mut real_data: ByteArray, pos: usize, random1: usize, random2: usize) -> ByteArray { let mut new_data = array![];