Selaa lähdekoodia

feat(target_chains/starknet): verify pyth governance vm (#1569)

Pavel Strakhov 1 vuosi sitten
vanhempi
sitoutus
9854a5de59

+ 3 - 0
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}

+ 38 - 2
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<DataSource, bool>,
         latest_price_info: LegacyMap<u256, PriceInfo>,
+        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<DataSource>
+        data_sources: Array<DataSource>,
+        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());
+            }
+        }
     }
 }

+ 10 - 0
target_chains/starknet/contracts/src/pyth/errors.cairo

@@ -14,12 +14,22 @@ impl GetPriceUnsafeErrorIntoFelt252 of Into<GetPriceUnsafeError, felt252> {
 #[derive(Copy, Drop, Debug, Serde, PartialEq)]
 pub enum GovernanceActionError {
     AccessDenied,
+    Wormhole: pyth::wormhole::ParseAndVerifyVmError,
+    InvalidGovernanceDataSource,
+    OldGovernanceMessage,
+    InvalidGovernanceTarget,
+    InvalidGovernanceMessage,
 }
 
 impl GovernanceActionErrorIntoFelt252 of Into<GovernanceActionError, felt252> {
     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',
         }
     }
 }

+ 1 - 0
target_chains/starknet/contracts/src/pyth/interface.cairo

@@ -8,6 +8,7 @@ pub trait IPyth<T> {
     fn set_data_sources(ref self: T, sources: Array<DataSource>);
     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)]

+ 54 - 15
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<DataSource>
+    data_sources: Array<DataSource>,
+    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 },

+ 15 - 21
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![];