Browse Source

feat(target_chains/starknet): support SetWormholeAddress instruction (#1585)

Pavel Strakhov 1 year ago
parent
commit
51dc213678

+ 53 - 9
target_chains/starknet/contracts/src/pyth.cairo

@@ -3,7 +3,7 @@ mod interface;
 mod price_update;
 mod governance;
 
-pub use pyth::{Event, PriceFeedUpdateEvent};
+pub use pyth::{Event, PriceFeedUpdateEvent, WormholeAddressSet};
 pub use errors::{GetPriceUnsafeError, GovernanceActionError, UpdatePriceFeedsError};
 pub use interface::{IPyth, IPythDispatcher, IPythDispatcherTrait, DataSource, Price};
 
@@ -29,6 +29,7 @@ mod pyth {
     #[derive(Drop, PartialEq, starknet::Event)]
     pub enum Event {
         PriceFeedUpdate: PriceFeedUpdateEvent,
+        WormholeAddressSet: WormholeAddressSet,
     }
 
     #[derive(Drop, PartialEq, starknet::Event)]
@@ -40,6 +41,12 @@ mod pyth {
         pub conf: u64,
     }
 
+    #[derive(Drop, PartialEq, starknet::Event)]
+    pub struct WormholeAddressSet {
+        pub old_address: ContractAddress,
+        pub new_address: ContractAddress,
+    }
+
     #[storage]
     struct Storage {
         wormhole_address: ContractAddress,
@@ -191,20 +198,32 @@ mod pyth {
 
         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);
+            let vm = wormhole.parse_and_verify_vm(data.clone());
             self.verify_governance_vm(@vm);
-            let data = governance::parse_instruction(vm.payload);
-            if data.target_chain_id != 0 && data.target_chain_id != wormhole.chain_id() {
+            let instruction = governance::parse_instruction(vm.payload);
+            if instruction.target_chain_id != 0
+                && instruction.target_chain_id != wormhole.chain_id() {
                 panic_with_felt252(GovernanceActionError::InvalidGovernanceTarget.into());
             }
-            match data.payload {
-                GovernancePayload::SetFee(data) => {
-                    let value = apply_decimal_expo(data.value, data.expo);
+            match instruction.payload {
+                GovernancePayload::SetFee(payload) => {
+                    let value = apply_decimal_expo(payload.value, payload.expo);
                     self.single_update_fee.write(value);
                 },
-                GovernancePayload::SetDataSources(data) => {
-                    self.write_data_sources(data.sources);
+                GovernancePayload::SetDataSources(payload) => {
+                    self.write_data_sources(payload.sources);
                 },
+                GovernancePayload::SetWormholeAddress(payload) => {
+                    if instruction.target_chain_id == 0 {
+                        panic_with_felt252(GovernanceActionError::InvalidGovernanceTarget.into());
+                    }
+                    self.check_new_wormhole(payload.address, data);
+                    self.wormhole_address.write(payload.address);
+                    let event = WormholeAddressSet {
+                        old_address: wormhole.contract_address, new_address: payload.address,
+                    };
+                    self.emit(event);
+                }
             }
             self.last_executed_governance_sequence.write(vm.sequence);
         }
@@ -271,6 +290,31 @@ mod pyth {
                 panic_with_felt252(GovernanceActionError::OldGovernanceMessage.into());
             }
         }
+
+        fn check_new_wormhole(
+            self: @ContractState, wormhole_address: ContractAddress, vm: ByteArray
+        ) {
+            let wormhole = IWormholeDispatcher { contract_address: wormhole_address };
+            let vm = wormhole.parse_and_verify_vm(vm);
+            self.verify_governance_vm(@vm);
+            // Purposefully, we don't check whether the chainId is the same as the current chainId because
+            // we might want to change the chain id of the wormhole contract.
+            let data = governance::parse_instruction(vm.payload);
+            match data.payload {
+                GovernancePayload::SetWormholeAddress(payload) => {
+                    // The following check is not necessary for security, but is a sanity check that the new wormhole
+                    // contract parses the payload correctly.
+                    if payload.address != wormhole_address {
+                        panic_with_felt252(
+                            GovernanceActionError::InvalidWormholeAddressToSet.into()
+                        );
+                    }
+                },
+                _ => {
+                    panic_with_felt252(GovernanceActionError::InvalidWormholeAddressToSet.into());
+                }
+            }
+        }
     }
 
     fn apply_decimal_expo(value: u64, expo: u64) -> u256 {

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

@@ -19,6 +19,7 @@ pub enum GovernanceActionError {
     OldGovernanceMessage,
     InvalidGovernanceTarget,
     InvalidGovernanceMessage,
+    InvalidWormholeAddressToSet,
 }
 
 impl GovernanceActionErrorIntoFelt252 of Into<GovernanceActionError, felt252> {
@@ -30,6 +31,7 @@ impl GovernanceActionErrorIntoFelt252 of Into<GovernanceActionError, felt252> {
             GovernanceActionError::OldGovernanceMessage => 'old governance message',
             GovernanceActionError::InvalidGovernanceTarget => 'invalid governance target',
             GovernanceActionError::InvalidGovernanceMessage => 'invalid governance message',
+            GovernanceActionError::InvalidWormholeAddressToSet => 'invalid new wormhole address',
         }
     }
 }

+ 17 - 1
target_chains/starknet/contracts/src/pyth/governance.cairo

@@ -3,6 +3,7 @@ use pyth::reader::{Reader, ReaderImpl};
 use pyth::byte_array::ByteArray;
 use pyth::pyth::errors::GovernanceActionError;
 use core::panic_with_felt252;
+use core::starknet::ContractAddress;
 use super::DataSource;
 
 const MAGIC: u32 = 0x5054474d;
@@ -45,6 +46,7 @@ pub struct GovernanceInstruction {
 pub enum GovernancePayload {
     SetFee: SetFee,
     SetDataSources: SetDataSources,
+    SetWormholeAddress: SetWormholeAddress,
 // TODO: others
 }
 
@@ -59,6 +61,11 @@ pub struct SetDataSources {
     pub sources: Array<DataSource>,
 }
 
+#[derive(Drop, Debug)]
+pub struct SetWormholeAddress {
+    pub address: ContractAddress,
+}
+
 pub fn parse_instruction(payload: ByteArray) -> GovernanceInstruction {
     let mut reader = ReaderImpl::new(payload);
     let magic = reader.read_u32();
@@ -102,7 +109,16 @@ pub fn parse_instruction(payload: ByteArray) -> GovernanceInstruction {
         GovernanceAction::RequestGovernanceDataSourceTransfer => {
             panic_with_felt252('unimplemented')
         },
-        GovernanceAction::SetWormholeAddress => { panic_with_felt252('unimplemented') },
+        GovernanceAction::SetWormholeAddress => {
+            let address: felt252 = reader
+                .read_u256()
+                .try_into()
+                .expect(GovernanceActionError::InvalidGovernanceMessage.into());
+            let address = address
+                .try_into()
+                .expect(GovernanceActionError::InvalidGovernanceMessage.into());
+            GovernancePayload::SetWormholeAddress(SetWormholeAddress { address })
+        },
     };
 
     if reader.len() != 0 {

+ 190 - 2
target_chains/starknet/contracts/tests/pyth.cairo

@@ -3,16 +3,19 @@ use snforge_std::{
     EventFetcher, event_name_hash, Event
 };
 use pyth::pyth::{
-    IPythDispatcher, IPythDispatcherTrait, DataSource, Event as PythEvent, PriceFeedUpdateEvent
+    IPythDispatcher, IPythDispatcherTrait, DataSource, Event as PythEvent, PriceFeedUpdateEvent,
+    WormholeAddressSet,
 };
 use pyth::byte_array::{ByteArray, ByteArrayImpl};
 use pyth::util::{array_try_into, UnwrapWithFelt252};
+use pyth::wormhole::IWormholeDispatcherTrait;
 use core::starknet::ContractAddress;
 use openzeppelin::token::erc20::interface::{IERC20CamelDispatcher, IERC20CamelDispatcherTrait};
 use super::wormhole::corrupted_vm;
 
 fn decode_event(event: @Event) -> PythEvent {
-    if *event.keys.at(0) == event_name_hash('PriceFeedUpdate') {
+    let key0 = *event.keys.at(0);
+    if key0 == event_name_hash('PriceFeedUpdate') {
         assert!(event.keys.len() == 3);
         assert!(event.data.len() == 3);
         let event = PriceFeedUpdateEvent {
@@ -25,6 +28,14 @@ fn decode_event(event: @Event) -> PythEvent {
             conf: (*event.data.at(2)).try_into().unwrap(),
         };
         PythEvent::PriceFeedUpdate(event)
+    } else if key0 == event_name_hash('WormholeAddressSet') {
+        assert!(event.keys.len() == 1);
+        assert!(event.data.len() == 2);
+        let event = WormholeAddressSet {
+            old_address: (*event.data.at(0)).try_into().unwrap(),
+            new_address: (*event.data.at(1)).try_into().unwrap(),
+        };
+        PythEvent::WormholeAddressSet(event)
     } else {
         panic!("unrecognized event")
     }
@@ -192,6 +203,140 @@ fn test_rejects_update_after_data_source_changed() {
     assert!(last_price.price == 6281522520745);
 }
 
+#[test]
+fn test_governance_set_wormhole_works() {
+    let wormhole_class = declare("wormhole");
+    // Arbitrary
+    let wormhole_address = 0x42.try_into().unwrap();
+    let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
+        @wormhole_class, wormhole_address
+    );
+
+    let owner = 'owner'.try_into().unwrap();
+    let user = 'user'.try_into().unwrap();
+    let fee_contract = deploy_fee_contract(user);
+    let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);
+
+    start_prank(CheatTarget::One(fee_contract.contract_address), user);
+    fee_contract.approve(pyth.contract_address, 10000);
+    stop_prank(CheatTarget::One(fee_contract.contract_address));
+
+    start_prank(CheatTarget::One(pyth.contract_address), user);
+    pyth.update_price_feeds(price_update1_test());
+    stop_prank(CheatTarget::One(pyth.contract_address));
+    let last_price = pyth
+        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
+        .unwrap_with_felt252();
+    assert!(last_price.price == 6281060000000);
+
+    // Address used in the governance instruction
+    let wormhole2_address = 0x05033f06d5c47bcce7960ea703b04a0bf64bf33f6f2eb5613496da747522d9c2
+        .try_into()
+        .unwrap();
+    let wormhole2 = super::wormhole::deploy_declared_with_test_guardian_at(
+        @wormhole_class, wormhole2_address
+    );
+    wormhole2.submit_new_guardian_set(super::wormhole::upgrade_test_guardian_1_to_2());
+
+    let mut spy = spy_events(SpyOn::One(pyth.contract_address));
+
+    pyth.execute_governance_instruction(governance_set_wormhole());
+
+    spy.fetch_events();
+    assert!(spy.events.len() == 1);
+    let (from, event) = spy.events.at(0);
+    assert!(from == @pyth.contract_address);
+    let event = decode_event(event);
+    let expected = WormholeAddressSet {
+        old_address: wormhole_address, new_address: wormhole2_address,
+    };
+    assert!(event == PythEvent::WormholeAddressSet(expected));
+
+    start_prank(CheatTarget::One(pyth.contract_address), user);
+    pyth.update_price_feeds(price_update2_test2_guardian());
+    stop_prank(CheatTarget::One(pyth.contract_address));
+    let last_price = pyth
+        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
+        .unwrap_with_felt252();
+    assert!(last_price.price == 6281522520745);
+}
+
+#[test]
+#[should_panic(expected: ('invalid guardian set index',))]
+fn test_rejects_price_update_without_setting_wormhole() {
+    let wormhole = super::wormhole::deploy_with_test_guardian();
+    let owner = 'owner'.try_into().unwrap();
+    let user = 'user'.try_into().unwrap();
+    let fee_contract = deploy_fee_contract(user);
+    let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);
+
+    start_prank(CheatTarget::One(fee_contract.contract_address), user);
+    fee_contract.approve(pyth.contract_address, 10000);
+    stop_prank(CheatTarget::One(fee_contract.contract_address));
+
+    start_prank(CheatTarget::One(pyth.contract_address), user);
+    pyth.update_price_feeds(price_update1_test());
+    stop_prank(CheatTarget::One(pyth.contract_address));
+    let last_price = pyth
+        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
+        .unwrap_with_felt252();
+    assert!(last_price.price == 6281060000000);
+
+    start_prank(CheatTarget::One(pyth.contract_address), user);
+    pyth.update_price_feeds(price_update2_test2_guardian());
+}
+
+// This test doesn't pass because of an snforge bug.
+// See https://github.com/foundry-rs/starknet-foundry/issues/2096
+// TODO: update snforge and unignore when the next release is available
+#[test]
+#[should_panic]
+#[ignore]
+fn test_rejects_set_wormhole_without_deploying() {
+    let wormhole_class = declare("wormhole");
+    // Arbitrary
+    let wormhole_address = 0x42.try_into().unwrap();
+    let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
+        @wormhole_class, wormhole_address
+    );
+
+    let owner = 'owner'.try_into().unwrap();
+    let user = 'user'.try_into().unwrap();
+    let fee_contract = deploy_fee_contract(user);
+    let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);
+    pyth.execute_governance_instruction(governance_set_wormhole());
+}
+
+#[test]
+#[should_panic(expected: ('Invalid signature',))]
+fn test_rejects_set_wormhole_with_incompatible_guardians() {
+    let wormhole_class = declare("wormhole");
+    // Arbitrary
+    let wormhole_address = 0x42.try_into().unwrap();
+    let wormhole = super::wormhole::deploy_declared_with_test_guardian_at(
+        @wormhole_class, wormhole_address
+    );
+
+    let owner = 'owner'.try_into().unwrap();
+    let user = 'user'.try_into().unwrap();
+    let fee_contract = deploy_fee_contract(user);
+    let pyth = deploy_default(owner, wormhole.contract_address, fee_contract.contract_address);
+
+    // Address used in the governance instruction
+    let wormhole2_address = 0x05033f06d5c47bcce7960ea703b04a0bf64bf33f6f2eb5613496da747522d9c2
+        .try_into()
+        .unwrap();
+    super::wormhole::deploy_declared_at(
+        @wormhole_class,
+        array_try_into(array![0x301]),
+        super::wormhole::CHAIN_ID,
+        super::wormhole::GOVERNANCE_CHAIN_ID,
+        super::wormhole::GOVERNANCE_CONTRACT,
+        Option::Some(wormhole2_address),
+    );
+    pyth.execute_governance_instruction(governance_set_wormhole());
+}
+
 fn deploy_default(
     owner: ContractAddress, wormhole_address: ContractAddress, fee_contract_address: ContractAddress
 ) -> IPythDispatcher {
@@ -335,6 +480,23 @@ fn governance_set_data_sources() -> ByteArray {
     )
 }
 
+// Generated with `../../tools/test_vaas/src/bin/generate_wormhole_vaas.rs`
+fn governance_set_wormhole() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                1766847064779993746734475762358060494055703996306832791834621971457521573,
+                304597972750688370688620483915336485865968448355388067310514768529150663948,
+                37753701654018547624593082738443625808734511977366199414609989499994767360,
+                49565958604199796163020368,
+                148907253456057279176930315687485033494639386197985334929728922792833758561,
+                3789456330195130818,
+            ]
+        ),
+        8
+    )
+}
+
 // Generated with `../../tools/test_vaas/src/bin/re_sign_price_updates.rs`
 fn price_update1_test() -> ByteArray {
     ByteArrayImpl::new(
@@ -413,3 +575,29 @@ fn price_update2_test_alt_emitter() -> ByteArray {
         25
     )
 }
+
+// Generated with `../../tools/test_vaas/src/bin/re_sign_price_updates.rs`
+fn price_update2_test2_guardian() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                141887862745809943100421399774809552391157901121219476151849805356757998433,
+                22927445661989480418689320204846867835510434886542566099417398893061382455,
+                299474373929736638290349370983054029794228129896969116108467835428084390625,
+                3498691308882995183871222184377409432186747119716981166996399082193594993,
+                1390200461185063661704370212555794334034815850290352693418762308,
+                419598057710749587537080281518289024699150505326900462079484531390510117965,
+                341318259000017461738706238280879290398059773267212529438780607147892801536,
+                1437437604754599821041091415535991441313586347841485651963630208563420739,
+                305222830440467078008666830004555943609735125691441831219591213494068931362,
+                358396406696718360717615797531477055540194104082154743994717297650279402646,
+                429270385827211102844129651648706540139690432947840438198166022904666187018,
+                343946166212648899477337159288779715507980257611242783073384876024451565860,
+                67853010773876862913176476530730880916439012004585961528150130218675908823,
+                370855179649505412564259994413632062925303311800103998016489412083011059699,
+                1182295126766215829784496273374889928477877265080355104888778,
+            ]
+        ),
+        25
+    )
+}

+ 65 - 9
target_chains/starknet/contracts/tests/wormhole.cairo

@@ -1,4 +1,4 @@
-use snforge_std::{declare, ContractClassTrait, start_prank, stop_prank, CheatTarget};
+use snforge_std::{declare, ContractClass, ContractClassTrait, start_prank, stop_prank, CheatTarget};
 use pyth::wormhole::{IWormholeDispatcher, IWormholeDispatcherTrait, ParseAndVerifyVmError};
 use pyth::reader::ReaderImpl;
 use pyth::byte_array::{ByteArray, ByteArrayImpl};
@@ -151,16 +151,23 @@ fn test_submit_guardian_set_rejects_non_governance(pos: usize, random1: usize, r
     dispatcher.submit_new_guardian_set(good_vm1());
 }
 
-fn deploy(
+// Deploys a previously declared wormhole contract class at the specified address.
+// If address is not specified, the default address derivation is used.
+pub fn deploy_declared_at(
+    class: @ContractClass,
     guardians: Array<EthAddress>,
     chain_id: u16,
     governance_chain_id: u16,
     governance_contract: u256,
+    address: Option<ContractAddress>,
 ) -> IWormholeDispatcher {
     let mut args = array![];
     (guardians, chain_id, governance_chain_id, governance_contract).serialize(ref args);
-    let contract = declare("wormhole");
-    let contract_address = match contract.deploy(@args) {
+    let result = match address {
+        Option::Some(address) => class.deploy_at(@args, address),
+        Option::None => class.deploy(@args),
+    };
+    let contract_address = match result {
         Result::Ok(v) => { v },
         Result::Err(err) => {
             panic(err.panic_data);
@@ -170,6 +177,20 @@ fn deploy(
     IWormholeDispatcher { contract_address }
 }
 
+// Declares and deploys the contract.
+fn deploy(
+    guardians: Array<EthAddress>,
+    chain_id: u16,
+    governance_chain_id: u16,
+    governance_contract: u256,
+) -> IWormholeDispatcher {
+    let class = declare("wormhole");
+    deploy_declared_at(
+        @class, guardians, chain_id, governance_chain_id, governance_contract, Option::None
+    )
+}
+
+// Declares and deploys the contract and initializes it with mainnet guardian set upgrades.
 pub fn deploy_with_mainnet_guardians() -> IWormholeDispatcher {
     let dispatcher = deploy(guardian_set0(), CHAIN_ID, GOVERNANCE_CHAIN_ID, GOVERNANCE_CONTRACT);
 
@@ -181,12 +202,30 @@ pub fn deploy_with_mainnet_guardians() -> IWormholeDispatcher {
     dispatcher
 }
 
+const TEST_GUARDIAN_ADDRESS: felt252 = 0x686b9ea8e3237110eaaba1f1b7467559a3273819;
+
+// Declares and deploys the contract with the test guardian address that's used to sign VAAs generated in `test_vaas`.
 pub fn deploy_with_test_guardian() -> IWormholeDispatcher {
     deploy(
-        array_try_into(array![0x686b9ea8e3237110eaaba1f1b7467559a3273819]),
+        array_try_into(array![TEST_GUARDIAN_ADDRESS]),
+        CHAIN_ID,
+        GOVERNANCE_CHAIN_ID,
+        GOVERNANCE_CONTRACT,
+    )
+}
+
+// Deploys a previously declared wormhole contract class
+// with the test guardian address that's used to sign VAAs generated in `test_vaas`.
+pub fn deploy_declared_with_test_guardian_at(
+    class: @ContractClass, address: ContractAddress
+) -> IWormholeDispatcher {
+    deploy_declared_at(
+        class,
+        array_try_into(array![TEST_GUARDIAN_ADDRESS]),
         CHAIN_ID,
         GOVERNANCE_CHAIN_ID,
-        GOVERNANCE_CONTRACT
+        GOVERNANCE_CONTRACT,
+        Option::Some(address),
     )
 }
 
@@ -295,9 +334,26 @@ fn good_vm1() -> ByteArray {
     ByteArrayImpl::new(array_try_into(bytes), 22)
 }
 
-const CHAIN_ID: u16 = 60051;
-const GOVERNANCE_CHAIN_ID: u16 = 1;
-const GOVERNANCE_CONTRACT: u256 = 4;
+pub const CHAIN_ID: u16 = 60051;
+pub const GOVERNANCE_CHAIN_ID: u16 = 1;
+pub const GOVERNANCE_CONTRACT: u256 = 4;
+
+// Generated with `../../tools/test_vaas/src/bin/generate_wormhole_vaas.rs`
+pub fn upgrade_test_guardian_1_to_2() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                1766847064779995287375101177319600239435018729139341591012343354326614060,
+                152103705464783935682610402914146418697934830197930803919710856925871578605,
+                421150899970781847000318380764303006646986333759174982038890844802036793344,
+                4835703278458516699153920,
+                1131377253,
+                210624583337114749311237613435643962969294824395451022190048752713,
+            ]
+        ),
+        28
+    )
+}
 
 // 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

+ 51 - 7
target_chains/starknet/tools/test_vaas/src/bin/generate_wormhole_vaas.rs

@@ -6,7 +6,10 @@ use wormhole_vaas::{PayloadKind, VaaBody};
 
 fn main() {
     let secret1 = "047f10198517025e9bf2f6d09ebb650826b35397f01ca2a64a38348cae653f86";
-    // address 0x686b9ea8e3237110eaaba1f1b7467559a3273819
+    let address1 = EthAddress(hex::decode("686b9ea8e3237110eaaba1f1b7467559a3273819").unwrap());
+
+    // secret2 = "a95d32e5e2b9464b3f49a0f7ef2ede3ff17585836b253b96c832a86d2b5614cb"
+    let address2 = EthAddress(hex::decode("363598f080a817e633fc2d8f2b92e6e637f8b449").unwrap());
 
     let guardians = GuardianSet {
         set_index: 0,
@@ -52,9 +55,7 @@ fn main() {
                     GuardianSetUpgrade {
                         chain_id: 60051,
                         set_index: 1,
-                        guardians: vec![EthAddress(
-                            hex::decode("686b9ea8e3237110eaaba1f1b7467559a3273819").unwrap(),
-                        )],
+                        guardians: vec![address1.clone()],
                     }
                     .serialize(),
                 ),
@@ -79,9 +80,7 @@ fn main() {
                     GuardianSetUpgrade {
                         chain_id: 0,
                         set_index: 3,
-                        guardians: vec![EthAddress(
-                            hex::decode("686b9ea8e3237110eaaba1f1b7467559a3273819").unwrap(),
-                        )],
+                        guardians: vec![address1.clone()],
                     }
                     .serialize(),
                 ),
@@ -92,6 +91,31 @@ fn main() {
     print_as_array_and_last(&wrong_index_upgrade);
     println!();
 
+    let upgrade_to_test2 = serialize_vaa(
+        guardians.sign_vaa(
+            &[0],
+            VaaBody {
+                timestamp: 1,
+                nonce: 2,
+                emitter_chain: 1,
+                emitter_address: u256_to_be(4.into()).into(),
+                sequence: 5.try_into().unwrap(),
+                consistency_level: 6,
+                payload: PayloadKind::Binary(
+                    GuardianSetUpgrade {
+                        chain_id: 0,
+                        set_index: 1,
+                        guardians: vec![address2],
+                    }
+                    .serialize(),
+                ),
+            },
+        ),
+    );
+    println!("upgrade_to_test2");
+    print_as_array_and_last(&upgrade_to_test2);
+    println!();
+
     let pyth_set_fee = serialize_vaa(guardians.sign_vaa(
         &[0],
         VaaBody {
@@ -130,4 +154,24 @@ fn main() {
     println!("pyth_set_data_sources");
     print_as_array_and_last(&pyth_set_data_sources);
     println!();
+
+    let pyth_set_wormhole = serialize_vaa(guardians.sign_vaa(
+        &[0],
+        VaaBody {
+            timestamp: 1,
+            nonce: 2,
+            emitter_chain: 1,
+            emitter_address: u256_to_be(41.into()).into(),
+            sequence: 1.try_into().unwrap(),
+            consistency_level: 6,
+            payload: PayloadKind::Binary(vec![
+                80, 84, 71, 77, 1, 6, 234, 147, 5, 3, 63, 6, 213, 196, 123, 204, 231, 150, 14, 167,
+                3, 176, 74, 11, 246, 75, 243, 63, 111, 46, 181, 97, 52, 150, 218, 116, 117, 34,
+                217, 194,
+            ]),
+        },
+    ));
+    println!("pyth_set_wormhole");
+    print_as_array_and_last(&pyth_set_wormhole);
+    println!();
 }

File diff suppressed because it is too large
+ 9 - 1
target_chains/starknet/tools/test_vaas/src/bin/re_sign_price_updates.rs


+ 1 - 0
target_chains/starknet/tools/test_vaas/src/lib.rs

@@ -136,6 +136,7 @@ pub fn serialize_vaa(vaa: Vaa) -> Vec<u8> {
     vaa_bytes
 }
 
+#[derive(Debug, Clone)]
 pub struct EthAddress(pub Vec<u8>);
 
 pub struct DataSource {

Some files were not shown because too many files changed in this diff