Преглед изворни кода

feat(target_chains/starknet): wormhole governance VAA verification (#1547)

* feat(target_chains/starknet): wormhole governance VAA verification

* refactor(target_chains/starknet): rename VM to VerifiedVM
Pavel Strakhov пре 1 година
родитељ
комит
55cbe62997

+ 3 - 0
target_chains/starknet/contracts/deploy/local_deploy

@@ -33,6 +33,9 @@ wormhole_address=$(starkli deploy "${wormhole_hash}" \
     "${owner}" \
     1 `# num_guardians` \
     0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5 \
+    1 `# chain_id` \
+    1 `# governance_chain_id` \
+    4 0 `# governance_contract` \
 )
 
 ${sleep}

+ 79 - 11
target_chains/starknet/contracts/src/wormhole.cairo

@@ -5,7 +5,8 @@ use pyth::util::UnwrapWithFelt252;
 #[starknet::interface]
 pub trait IWormhole<T> {
     fn submit_new_guardian_set(ref self: T, set_index: u32, guardians: Array<felt252>);
-    fn parse_and_verify_vm(self: @T, encoded_vm: ByteArray) -> VM;
+    fn parse_and_verify_vm(self: @T, encoded_vm: ByteArray) -> VerifiedVM;
+    fn submit_new_guardian_set2(ref self: T, encoded_vm: ByteArray);
 }
 
 #[derive(Drop, Debug, Clone, Serde)]
@@ -15,7 +16,7 @@ pub struct GuardianSignature {
 }
 
 #[derive(Drop, Debug, Clone, Serde)]
-pub struct VM {
+pub struct VerifiedVM {
     pub version: u8,
     pub guardian_set_index: u32,
     pub signatures: Array<GuardianSignature>,
@@ -26,6 +27,35 @@ pub struct VM {
     pub sequence: u64,
     pub consistency_level: u8,
     pub payload: ByteArray,
+    pub hash: u256,
+}
+
+#[derive(Copy, Drop, Debug, Serde, PartialEq)]
+pub enum GovernanceError {
+    NotCurrentGuardianSet,
+    WrongChain,
+    WrongContract,
+    ActionAlreadyConsumed,
+}
+
+pub impl GovernanceErrorUnwrapWithFelt252<T> of UnwrapWithFelt252<T, GovernanceError> {
+    fn unwrap_with_felt252(self: Result<T, GovernanceError>) -> T {
+        match self {
+            Result::Ok(v) => v,
+            Result::Err(err) => core::panic_with_felt252(err.into()),
+        }
+    }
+}
+
+impl GovernanceErrorIntoFelt252 of Into<GovernanceError, felt252> {
+    fn into(self: GovernanceError) -> felt252 {
+        match self {
+            GovernanceError::NotCurrentGuardianSet => 'not signed by current guard.set',
+            GovernanceError::WrongChain => 'wrong governance chain',
+            GovernanceError::WrongContract => 'wrong governance contract',
+            GovernanceError::ActionAlreadyConsumed => 'gov. action already consumed',
+        }
+    }
 }
 
 #[derive(Copy, Drop, Debug, Serde, PartialEq)]
@@ -110,7 +140,8 @@ mod wormhole {
     use core::box::BoxTrait;
     use core::array::ArrayTrait;
     use super::{
-        VM, IWormhole, GuardianSignature, quorum, ParseAndVerifyVmError, SubmitNewGuardianSetError
+        VerifiedVM, IWormhole, GuardianSignature, quorum, ParseAndVerifyVmError,
+        SubmitNewGuardianSetError, GovernanceError
     };
     use pyth::reader::{Reader, ReaderImpl};
     use pyth::byte_array::ByteArray;
@@ -135,7 +166,11 @@ mod wormhole {
     #[storage]
     struct Storage {
         owner: ContractAddress,
+        chain_id: u16,
+        governance_chain_id: u16,
+        governance_contract: u256,
         current_guardian_set_index: u32,
+        consumed_governance_actions: LegacyMap<u256, bool>,
         guardian_sets: LegacyMap<u32, GuardianSet>,
         // (guardian_set_index, guardian_index) => guardian_address
         guardian_keys: LegacyMap<(u32, u8), u256>,
@@ -143,9 +178,17 @@ mod wormhole {
 
     #[constructor]
     fn constructor(
-        ref self: ContractState, owner: ContractAddress, initial_guardians: Array<felt252>
+        ref self: ContractState,
+        owner: ContractAddress,
+        initial_guardians: Array<felt252>,
+        chain_id: u16,
+        governance_chain_id: u16,
+        governance_contract: u256,
     ) {
         self.owner.write(owner);
+        self.chain_id.write(chain_id);
+        self.governance_chain_id.write(governance_chain_id);
+        self.governance_contract.write(governance_contract);
         let set_index = 0;
         store_guardian_set(ref self, set_index, @initial_guardians);
     }
@@ -205,8 +248,8 @@ mod wormhole {
             );
         }
 
-        fn parse_and_verify_vm(self: @ContractState, encoded_vm: ByteArray) -> VM {
-            let (vm, body_hash) = parse_vm(encoded_vm);
+        fn parse_and_verify_vm(self: @ContractState, encoded_vm: ByteArray) -> VerifiedVM {
+            let vm = parse_vm(encoded_vm);
             let guardian_set = self.guardian_sets.read(vm.guardian_set_index);
             if guardian_set.num_guardians == 0 {
                 panic_with_felt252(ParseAndVerifyVmError::InvalidGuardianSetIndex.into());
@@ -245,10 +288,17 @@ mod wormhole {
                     .guardian_keys
                     .read((vm.guardian_set_index, signature.guardian_index));
 
-                verify_signature(body_hash, signature.signature, guardian_key);
+                verify_signature(vm.hash, signature.signature, guardian_key);
             };
             vm
         }
+
+        fn submit_new_guardian_set2(ref self: ContractState, encoded_vm: ByteArray) {
+            let vm = self.parse_and_verify_vm(encoded_vm);
+            self.verify_governance_vm(@vm);
+            self.consumed_governance_actions.write(vm.hash, true);
+            panic_with_felt252('todo: parse vm')
+        }
     }
 
     fn parse_signature(ref reader: Reader) -> GuardianSignature {
@@ -260,7 +310,7 @@ mod wormhole {
         GuardianSignature { guardian_index, signature: Signature { r, s, y_parity } }
     }
 
-    fn parse_vm(encoded_vm: ByteArray) -> (VM, u256) {
+    fn parse_vm(encoded_vm: ByteArray) -> VerifiedVM {
         let mut reader = ReaderImpl::new(encoded_vm);
         let version = reader.read_u8();
         if version != 1 {
@@ -294,7 +344,7 @@ mod wormhole {
         let payload_len = reader.len();
         let payload = reader.read_byte_array(payload_len);
 
-        let vm = VM {
+        VerifiedVM {
             version,
             guardian_set_index,
             signatures,
@@ -305,8 +355,8 @@ mod wormhole {
             sequence,
             consistency_level,
             payload,
-        };
-        (vm, body_hash2)
+            hash: body_hash2,
+        }
     }
 
     fn verify_signature(body_hash: u256, signature: Signature, guardian_key: u256,) {
@@ -327,4 +377,22 @@ mod wormhole {
         hasher.push_u256(y);
         hasher.finalize() % ONE_SHIFT_160
     }
+
+    #[generate_trait]
+    impl PrivateImpl of PrivateImplTrait {
+        fn verify_governance_vm(self: @ContractState, vm: @VerifiedVM) {
+            if self.current_guardian_set_index.read() != *vm.guardian_set_index {
+                panic_with_felt252(GovernanceError::NotCurrentGuardianSet.into());
+            }
+            if self.governance_chain_id.read() != *vm.emitter_chain_id {
+                panic_with_felt252(GovernanceError::WrongChain.into());
+            }
+            if self.governance_contract.read() != *vm.emitter_address {
+                panic_with_felt252(GovernanceError::WrongContract.into());
+            }
+            if self.consumed_governance_actions.read(*vm.hash) {
+                panic_with_felt252(GovernanceError::ActionAlreadyConsumed.into());
+            }
+        }
+    }
 }

+ 67 - 7
target_chains/starknet/contracts/tests/wormhole.cairo

@@ -49,7 +49,9 @@ fn test_parse_and_verify_vm_rejects_corrupted_vm(pos: usize, random1: usize, ran
 #[should_panic(expected: ('access denied',))]
 fn test_submit_guardian_set_rejects_wrong_owner() {
     let owner = 'owner'.try_into().unwrap();
-    let dispatcher = deploy(owner, guardian_set1());
+    let dispatcher = deploy(
+        owner, guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT
+    );
     start_prank(CheatTarget::One(dispatcher.contract_address), 'baddy'.try_into().unwrap());
     dispatcher.submit_new_guardian_set(1, guardian_set1());
 }
@@ -58,7 +60,9 @@ fn test_submit_guardian_set_rejects_wrong_owner() {
 #[should_panic(expected: ('invalid guardian set sequence',))]
 fn test_submit_guardian_set_rejects_wrong_index() {
     let owner = 'owner'.try_into().unwrap();
-    let dispatcher = deploy(owner, guardian_set1());
+    let dispatcher = deploy(
+        owner, guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT
+    );
 
     start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
     dispatcher.submit_new_guardian_set(1, guardian_set1());
@@ -69,22 +73,42 @@ fn test_submit_guardian_set_rejects_wrong_index() {
 #[should_panic(expected: ('no guardians specified',))]
 fn test_deploy_rejects_empty() {
     let owner = 'owner'.try_into().unwrap();
-    deploy(owner, array![]);
+    deploy(owner, array![], CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);
 }
 
 #[test]
 #[should_panic(expected: ('no guardians specified',))]
 fn test_submit_guardian_set_rejects_empty() {
     let owner = 'owner'.try_into().unwrap();
-    let dispatcher = deploy(owner, guardian_set1());
+    let dispatcher = deploy(
+        owner, guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT
+    );
 
     start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
     dispatcher.submit_new_guardian_set(1, array![]);
 }
 
-fn deploy(owner: ContractAddress, guardians: Array<felt252>) -> IWormholeDispatcher {
+#[test]
+#[should_panic(expected: ('todo: parse vm',))]
+fn test_guardian_set_upgrade() {
+    let owner = 'owner'.try_into().unwrap();
+    let dispatcher = deploy(
+        owner, guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT
+    );
+
+    dispatcher.submit_new_guardian_set2(governance_upgrade_vm1());
+}
+
+fn deploy(
+    owner: ContractAddress,
+    guardians: Array<felt252>,
+    chain_id: u16,
+    governance_chain_id: u16,
+    governance_contract: u256,
+) -> IWormholeDispatcher {
     let mut args = array![];
-    (owner, guardians).serialize(ref args);
+    (owner, guardians, chain_id, governance_chain_id).serialize(ref args);
+    (governance_contract,).serialize(ref args);
     let contract = declare("wormhole");
     let contract_address = match contract.deploy(@args) {
         Result::Ok(v) => { v },
@@ -97,7 +121,9 @@ fn deploy(owner: ContractAddress, guardians: Array<felt252>) -> IWormholeDispatc
 }
 
 pub fn deploy_and_init(owner: ContractAddress) -> IWormholeDispatcher {
-    let dispatcher = deploy(owner, guardian_set1());
+    let dispatcher = deploy(
+        owner, guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT
+    );
 
     start_prank(CheatTarget::One(dispatcher.contract_address), owner.try_into().unwrap());
     dispatcher.submit_new_guardian_set(1, guardian_set1());
@@ -170,6 +196,10 @@ fn corrupted_byte(value: u8, random: usize) -> u8 {
     (v % 256).try_into().unwrap()
 }
 
+fn guardian_set0() -> Array<felt252> {
+    array![0x58CC3AE5C097b213cE3c81979e1B9f9570746AA5]
+}
+
 // Below are actual guardian keys from
 // https://github.com/wormhole-foundation/wormhole-networks/tree/master/mainnetv2/guardianset
 fn guardian_set1() -> Array<felt252> {
@@ -279,3 +309,33 @@ fn good_vm1() -> ByteArray {
     ];
     ByteArrayImpl::new(array_felt252_to_bytes31(bytes), 22)
 }
+
+const CHAIN_ID: u16 = 1;
+const GOVERNANCE_CHAIN_ID: u16 = 1;
+const GOVERNANCE_CONTRACT: u256 = 4;
+
+// Below are actual guardian set upgrade VAAS from
+// https://github.com/pyth-network/pyth-crosschain/blob/main/contract_manager/src/contracts/wormhole.ts#L32-L37
+fn governance_upgrade_vm1() -> ByteArray {
+    let bytes = array![
+        1766847064779994277746302277072294871108550301449637470263976489521154979,
+        374953657095152923031303770743522269007103499920836805761143506434463979495,
+        373725794026553362537846905304981854320892126869150736450761801254169477120,
+        4835703278458516786446336,
+        1131377253,
+        3533694129556775410652111826415980944262631656421498398215501759245151417,
+        145493015216602589471695207668173527044214450021182755196032581352392984224,
+        267497573836069714380350521200881787609530659298168186016481773490244091266,
+        443348533394886521835330696538264729103669807313401311199245411889706258110,
+        200303433165499832354845293203843028338419687800279845786613090211434473108,
+        37282816539161742972709083946551920068062204748477644719930149699874385462,
+        111200938271608595261384934914291476246753101189480743698823215749338358345,
+        5785682963869019134199015821749288033381872318410562688180948003975909269,
+        372447340016996751453958019806457886428852701283870538393820846119845147788,
+        33251468085387571623103303511315696691298281336333243761063342581452341650,
+        323161992096034641767541451811925056802673576212351940217752194462561980347,
+        55852237138651071644815135002358067220635692701051811455610533875912981641,
+        190413173566657072516608762222993749133,
+    ];
+    ByteArrayImpl::new(array_felt252_to_bytes31(bytes), 16)
+}