Browse Source

feat(target_chains/starknet): handle SetDataSources governance instruction (#1582)

* feat(target_chains/starknet): handle SetDataSources governance instruction

* doc(target_chains/starknet): add docs and comments about test input data
Pavel Strakhov 1 year ago
parent
commit
4bc32a728b

+ 5 - 5
governance/xc_admin/packages/xc_admin_common/src/__tests__/GovernancePayload.test.ts

@@ -203,9 +203,9 @@ test("GovernancePayload ser/de", (done) => {
         "6bb14509a612f01fbbc4cffeebd4bbfb492a86df717ebe92eb6df432a3f00a25",
     },
     {
-      emitterChain: 26,
+      emitterChain: 3,
       emitterAddress:
-        "f8cd23c2ab91237730770bbea08d61005cdda0984348f3f6eecb559638c0bba0",
+        "000000000000000000000000000000000000000000000000000000000000012d",
     },
   ]);
   const setDataSourcesBuffer = setDataSources.encode();
@@ -215,9 +215,9 @@ test("GovernancePayload ser/de", (done) => {
       Buffer.from([
         80, 84, 71, 77, 1, 2, 234, 147, 2, 0, 1, 107, 177, 69, 9, 166, 18, 240,
         31, 187, 196, 207, 254, 235, 212, 187, 251, 73, 42, 134, 223, 113, 126,
-        190, 146, 235, 109, 244, 50, 163, 240, 10, 37, 0, 26, 248, 205, 35, 194,
-        171, 145, 35, 119, 48, 119, 11, 190, 160, 141, 97, 0, 92, 221, 160, 152,
-        67, 72, 243, 246, 238, 203, 85, 150, 56, 192, 187, 160,
+        190, 146, 235, 109, 244, 50, 163, 240, 10, 37, 0, 3, 0, 0, 0, 0, 0, 0,
+        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        1, 45,
       ])
     )
   ).toBeTruthy();

+ 14 - 0
target_chains/starknet/contracts/README.md

@@ -13,6 +13,20 @@ The `.tool-versions` file in this directory specifies the tool versions used by
 
 Run `snforge test` in this directory to run the contract unit tests and integration tests.
 
+Some tests contain input data that was generated with `test_vaas` tool.
+To run it, use the following commands from the repository root (requires Rust installation):
+
+```
+cd target_chains/starknet/tools/test_vaas
+cargo run --bin re_sign_price_updates
+cargo run --bin generate_wormhole_vaas
+cargo run --bin wormhole_mainnet_upgrades
+```
+
+## Formatting
+
+Run `scarb fmt` to automatically format the source code.
+
 ## Local deployment
 
 1. Install Starkli (a cli tool for Starknet) by following [the installation instructions](https://github.com/xJonathanLEI/starkli).

+ 4 - 1
target_chains/starknet/contracts/src/pyth.cairo

@@ -201,7 +201,10 @@ mod pyth {
                 GovernancePayload::SetFee(data) => {
                     let value = apply_decimal_expo(data.value, data.expo);
                     self.single_update_fee.write(value);
-                }
+                },
+                GovernancePayload::SetDataSources(data) => {
+                    self.write_data_sources(data.sources);
+                },
             }
             self.last_executed_governance_sequence.write(vm.sequence);
         }

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

@@ -1,7 +1,9 @@
+use core::array::ArrayTrait;
 use pyth::reader::{Reader, ReaderImpl};
 use pyth::byte_array::ByteArray;
 use pyth::pyth::errors::GovernanceActionError;
 use core::panic_with_felt252;
+use super::DataSource;
 
 const MAGIC: u32 = 0x5054474d;
 const MODULE_TARGET: u8 = 1;
@@ -42,6 +44,7 @@ pub struct GovernanceInstruction {
 #[derive(Drop, Debug)]
 pub enum GovernancePayload {
     SetFee: SetFee,
+    SetDataSources: SetDataSources,
 // TODO: others
 }
 
@@ -51,6 +54,11 @@ pub struct SetFee {
     pub expo: u64,
 }
 
+#[derive(Drop, Debug)]
+pub struct SetDataSources {
+    pub sources: Array<DataSource>,
+}
+
 pub fn parse_instruction(payload: ByteArray) -> GovernanceInstruction {
     let mut reader = ReaderImpl::new(payload);
     let magic = reader.read_u32();
@@ -73,7 +81,18 @@ pub fn parse_instruction(payload: ByteArray) -> GovernanceInstruction {
         GovernanceAction::AuthorizeGovernanceDataSourceTransfer => {
             panic_with_felt252('unimplemented')
         },
-        GovernanceAction::SetDataSources => { panic_with_felt252('unimplemented') },
+        GovernanceAction::SetDataSources => {
+            let num_sources = reader.read_u8();
+            let mut i = 0;
+            let mut sources = array![];
+            while i < num_sources {
+                let emitter_chain_id = reader.read_u16();
+                let emitter_address = reader.read_u256();
+                sources.append(DataSource { emitter_chain_id, emitter_address });
+                i += 1;
+            };
+            GovernancePayload::SetDataSources(SetDataSources { sources })
+        },
         GovernanceAction::SetFee => {
             let value = reader.read_u64();
             let expo = reader.read_u64();

+ 163 - 51
target_chains/starknet/contracts/tests/pyth.cairo

@@ -92,31 +92,7 @@ fn test_governance_set_fee_works() {
 
     let mut balance = fee_contract.balanceOf(user);
     start_prank(CheatTarget::One(pyth.contract_address), user);
-    pyth
-        .update_price_feeds(
-            ByteArrayImpl::new(
-                array_try_into(
-                    array![
-                        141887862745809943100421399774809552050876420277163116849842965275903806689,
-                        210740906737592158039211995620336526131859667363627655742687286503264782608,
-                        437230063624699337579360546580839669896712252828825008570863758867641146081,
-                        3498691308882995183871222184377409432186747119716981166996399082193594993,
-                        1390200166945919815453709407753165121175395927094647129599868236,
-                        222819573728193325268644030206737371345667885599602384508424089704440116301,
-                        341318259000017461738706238280879290398059773267212529438772847337449455616,
-                        1275126645346645395843037504005879519843596923369759718556759844520336145,
-                        363528783578153760894082184744116718493621815898909809604883433584616420886,
-                        301537311768214106147206781423041990995720118715322906821301413003463484347,
-                        83150006264761451992768264969047148434524798781124754530141755679159432208,
-                        96387772316726941183358990094337324283641753573556594738287498821253761827,
-                        395908154570808692326126405856049827157095768069251211022053821585519235652,
-                        87135893730137265929093180553063146337041045646221968026289709394440932141,
-                        245333243912241114598596888050489286502591033459250287888834,
-                    ]
-                ),
-                25
-            )
-        );
+    pyth.update_price_feeds(price_update1_test());
     stop_prank(CheatTarget::One(pyth.contract_address));
     let new_balance = fee_contract.balanceOf(user);
     assert!(balance - new_balance == 1000);
@@ -129,31 +105,7 @@ fn test_governance_set_fee_works() {
     pyth.execute_governance_instruction(governance_set_fee());
 
     start_prank(CheatTarget::One(pyth.contract_address), user);
-    pyth
-        .update_price_feeds(
-            ByteArrayImpl::new(
-                array_try_into(
-                    array![
-                        141887862745809943100421399774809552050874823427618844548942380383465221086,
-                        106893583704677921907497845070624642590618427233243792006390965895909696183,
-                        126617671723931969110123875642449115250793288301361049879364132884271078113,
-                        3498691308882995183871222184377409432186747119716981166996399082193594993,
-                        1390200461185063661704370212555794334034815850290352693418762308,
-                        419598057710749587537080281518289024699150505326900462079484531390510117965,
-                        341318259000017461738706238280879290398059773267212529438780607147892801536,
-                        1437437604754599821041091415535991441313586347841485651963630208563420739,
-                        305222830440467078008666830004555943609735125691441831219591213494068931362,
-                        358396406696718360717615797531477055540194104082154743994717297650279402646,
-                        429270385827211102844129651648706540139690432947840438198166022904666187018,
-                        343946166212648899477337159288779715507980257611242783073384876024451565860,
-                        67853010773876862913176476530730880916439012004585961528150130218675908823,
-                        370855179649505412564259994413632062925303311800103998016489412083011059699,
-                        1182295126766215829784496273374889928477877265080355104888778,
-                    ]
-                ),
-                25
-            )
-        );
+    pyth.update_price_feeds(price_update2_test());
     stop_prank(CheatTarget::One(pyth.contract_address));
     let new_balance = fee_contract.balanceOf(user);
     assert!(balance - new_balance == 4200);
@@ -177,6 +129,69 @@ fn test_rejects_corrupted_governance_instruction(pos: usize, random1: usize, ran
     pyth.execute_governance_instruction(input);
 }
 
+#[test]
+fn test_governance_set_data_sources_works() {
+    let owner = 'owner'.try_into().unwrap();
+    let user = 'user'.try_into().unwrap();
+    let wormhole = super::wormhole::deploy_with_test_guardian();
+    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);
+
+    pyth.execute_governance_instruction(governance_set_data_sources());
+
+    start_prank(CheatTarget::One(pyth.contract_address), user);
+    pyth.update_price_feeds(price_update2_test_alt_emitter());
+    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 update data source',))]
+fn test_rejects_update_after_data_source_changed() {
+    let owner = 'owner'.try_into().unwrap();
+    let user = 'user'.try_into().unwrap();
+    let wormhole = super::wormhole::deploy_with_test_guardian();
+    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);
+
+    pyth.execute_governance_instruction(governance_set_data_sources());
+
+    start_prank(CheatTarget::One(pyth.contract_address), user);
+    pyth.update_price_feeds(price_update2_test());
+    stop_prank(CheatTarget::One(pyth.contract_address));
+    let last_price = pyth
+        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
+        .unwrap_with_felt252();
+    assert!(last_price.price == 6281522520745);
+}
+
 fn deploy_default(
     owner: ContractAddress, wormhole_address: ContractAddress, fee_contract_address: ContractAddress
 ) -> IPythDispatcher {
@@ -197,7 +212,6 @@ fn deploy_default(
     )
 }
 
-
 fn deploy(
     owner: ContractAddress,
     wormhole_address: ContractAddress,
@@ -287,6 +301,7 @@ fn good_update1() -> ByteArray {
     ByteArrayImpl::new(array_try_into(bytes), 11)
 }
 
+// Generated with `../../tools/test_vaas/src/bin/generate_wormhole_vaas.rs`
 fn governance_set_fee() -> ByteArray {
     ByteArrayImpl::new(
         array_try_into(
@@ -301,3 +316,100 @@ fn governance_set_fee() -> ByteArray {
         23
     )
 }
+
+// Generated with `../../tools/test_vaas/src/bin/generate_wormhole_vaas.rs`
+fn governance_set_data_sources() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                1766847064779993795984967344618836356750759980724568847727566676204733945,
+                319252252405206634291073190903653114488682078063415369176250618646860635118,
+                427774687951454487776318063357824898404188691225649546174530713404617785344,
+                49565958604199796163020368,
+                148907253454411774545738931219166892876160512393929267898119961543514185585,
+                223938022913800988696085410923418445187967252047785407181969631814277398528,
+                301,
+            ]
+        ),
+        14
+    )
+}
+
+// Generated with `../../tools/test_vaas/src/bin/re_sign_price_updates.rs`
+fn price_update1_test() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                141887862745809943100421399774809552050876420277163116849842965275903806689,
+                210740906737592158039211995620336526131859667363627655742687286503264782608,
+                437230063624699337579360546580839669896712252828825008570863758867641146081,
+                3498691308882995183871222184377409432186747119716981166996399082193594993,
+                1390200166945919815453709407753165121175395927094647129599868236,
+                222819573728193325268644030206737371345667885599602384508424089704440116301,
+                341318259000017461738706238280879290398059773267212529438772847337449455616,
+                1275126645346645395843037504005879519843596923369759718556759844520336145,
+                363528783578153760894082184744116718493621815898909809604883433584616420886,
+                301537311768214106147206781423041990995720118715322906821301413003463484347,
+                83150006264761451992768264969047148434524798781124754530141755679159432208,
+                96387772316726941183358990094337324283641753573556594738287498821253761827,
+                395908154570808692326126405856049827157095768069251211022053821585519235652,
+                87135893730137265929093180553063146337041045646221968026289709394440932141,
+                245333243912241114598596888050489286502591033459250287888834,
+            ]
+        ),
+        25
+    )
+}
+
+// Generated with `../../tools/test_vaas/src/bin/re_sign_price_updates.rs`
+fn price_update2_test() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                141887862745809943100421399774809552050874823427618844548942380383465221086,
+                106893583704677921907497845070624642590618427233243792006390965895909696183,
+                126617671723931969110123875642449115250793288301361049879364132884271078113,
+                3498691308882995183871222184377409432186747119716981166996399082193594993,
+                1390200461185063661704370212555794334034815850290352693418762308,
+                419598057710749587537080281518289024699150505326900462079484531390510117965,
+                341318259000017461738706238280879290398059773267212529438780607147892801536,
+                1437437604754599821041091415535991441313586347841485651963630208563420739,
+                305222830440467078008666830004555943609735125691441831219591213494068931362,
+                358396406696718360717615797531477055540194104082154743994717297650279402646,
+                429270385827211102844129651648706540139690432947840438198166022904666187018,
+                343946166212648899477337159288779715507980257611242783073384876024451565860,
+                67853010773876862913176476530730880916439012004585961528150130218675908823,
+                370855179649505412564259994413632062925303311800103998016489412083011059699,
+                1182295126766215829784496273374889928477877265080355104888778,
+            ]
+        ),
+        25
+    )
+}
+
+// Generated with `../../tools/test_vaas/src/bin/re_sign_price_updates.rs`
+// (same as `price_update2_test()` but with a different emitter)
+fn price_update2_test_alt_emitter() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                141887862745809943100421399774809552050876183715022494587482285730295850458,
+                359963320496358929787450247990998878269668655936959553372924597144593948268,
+                168294065609209340478050191639515428002729901421915929480902120205187023616,
+                301,
+                1390200461185063661704370212555794334034815850290352693418762308,
+                419598057710749587537080281518289024699150505326900462079484531390510117965,
+                341318259000017461738706238280879290398059773267212529438780607147892801536,
+                1437437604754599821041091415535991441313586347841485651963630208563420739,
+                305222830440467078008666830004555943609735125691441831219591213494068931362,
+                358396406696718360717615797531477055540194104082154743994717297650279402646,
+                429270385827211102844129651648706540139690432947840438198166022904666187018,
+                343946166212648899477337159288779715507980257611242783073384876024451565860,
+                67853010773876862913176476530730880916439012004585961528150130218675908823,
+                370855179649505412564259994413632062925303311800103998016489412083011059699,
+                1182295126766215829784496273374889928477877265080355104888778,
+            ]
+        ),
+        25
+    )
+}

+ 21 - 0
target_chains/starknet/tools/test_vaas/src/bin/generate_wormhole_vaas.rs

@@ -109,4 +109,25 @@ fn main() {
     println!("pyth_set_fee");
     print_as_array_and_last(&pyth_set_fee);
     println!();
+
+    let pyth_set_data_sources = 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, 2, 234, 147, 2, 0, 1, 107, 177, 69, 9, 166, 18, 240, 31, 187,
+                196, 207, 254, 235, 212, 187, 251, 73, 42, 134, 223, 113, 126, 190, 146, 235, 109,
+                244, 50, 163, 240, 10, 37, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 45,
+            ]),
+        },
+    ));
+    println!("pyth_set_data_sources");
+    print_as_array_and_last(&pyth_set_data_sources);
+    println!();
 }

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


+ 15 - 2
target_chains/starknet/tools/test_vaas/src/lib.rs

@@ -138,7 +138,16 @@ pub fn serialize_vaa(vaa: Vaa) -> Vec<u8> {
 
 pub struct EthAddress(pub Vec<u8>);
 
-pub fn re_sign_price_update(update: &[u8], guardian_set: &GuardianSet) -> Vec<u8> {
+pub struct DataSource {
+    pub emitter_chain_id: u16,
+    pub emitter_address: FixedBytes<32>,
+}
+
+pub fn re_sign_price_update(
+    update: &[u8],
+    guardian_set: &GuardianSet,
+    new_emitter: Option<DataSource>,
+) -> Vec<u8> {
     let mut reader = Cursor::new(update);
     reader.seek(SeekFrom::Current(6)).unwrap();
     let trailing_header_len = reader.read_u8().unwrap();
@@ -152,7 +161,11 @@ pub fn re_sign_price_update(update: &[u8], guardian_set: &GuardianSet) -> Vec<u8
     let pos_before_vaa: usize = reader.position().try_into().unwrap();
     let pos_after_vaa = pos_before_vaa + wh_proof_size;
 
-    let vaa = Vaa::read(&mut Cursor::new(&update[pos_before_vaa..pos_after_vaa])).unwrap();
+    let mut vaa = Vaa::read(&mut Cursor::new(&update[pos_before_vaa..pos_after_vaa])).unwrap();
+    if let Some(new_emitter) = new_emitter {
+        vaa.body.emitter_chain = new_emitter.emitter_chain_id;
+        vaa.body.emitter_address = new_emitter.emitter_address;
+    }
     let new_vaa = serialize_vaa(guardian_set.sign_vaa(&[0], vaa.body));
     let mut new_update = update[..pos_before_vaa_size].to_vec();
     new_update.extend_from_slice(&u16::try_from(new_vaa.len()).unwrap().to_be_bytes());

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