Explorar o código

feat(target_chains/starknet): handle SetFee governance instruction (#1580)

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

* test(target_chains/starknet): test SetFee governance instruction

* fix(target_chains/starknet): allow chain_id == 0 in pyth governance

* test(target_chains/starknet): use chain_id == 0 in wrong_index_upgrade vaa
Pavel Strakhov hai 1 ano
pai
achega
53be2ab48a

+ 23 - 2
target_chains/starknet/contracts/src/pyth.cairo

@@ -1,6 +1,7 @@
 mod errors;
 mod interface;
 mod price_update;
+mod governance;
 
 pub use pyth::{Event, PriceFeedUpdateEvent};
 pub use errors::{GetPriceUnsafeError, GovernanceActionError, UpdatePriceFeedsError};
@@ -20,6 +21,8 @@ mod pyth {
     use super::{
         DataSource, UpdatePriceFeedsError, GovernanceActionError, Price, GetPriceUnsafeError
     };
+    use super::governance;
+    use super::governance::GovernancePayload;
     use openzeppelin::token::erc20::interface::{IERC20CamelDispatcherTrait, IERC20CamelDispatcher};
 
     #[event]
@@ -190,9 +193,17 @@ mod pyth {
             let wormhole = IWormholeDispatcher { contract_address: self.wormhole_address.read() };
             let vm = wormhole.parse_and_verify_vm(data);
             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() {
+                panic_with_felt252(GovernanceActionError::InvalidGovernanceTarget.into());
+            }
+            match data.payload {
+                GovernancePayload::SetFee(data) => {
+                    let value = apply_decimal_expo(data.value, data.expo);
+                    self.single_update_fee.write(value);
+                }
+            }
             self.last_executed_governance_sequence.write(vm.sequence);
-            panic_with_felt252('todo');
         }
     }
 
@@ -258,4 +269,14 @@ mod pyth {
             }
         }
     }
+
+    fn apply_decimal_expo(value: u64, expo: u64) -> u256 {
+        let mut output: u256 = value.into();
+        let mut i = 0;
+        while i < expo {
+            output *= 10;
+            i += 1;
+        };
+        output
+    }
 }

+ 93 - 0
target_chains/starknet/contracts/src/pyth/governance.cairo

@@ -0,0 +1,93 @@
+use pyth::reader::{Reader, ReaderImpl};
+use pyth::byte_array::ByteArray;
+use pyth::pyth::errors::GovernanceActionError;
+use core::panic_with_felt252;
+
+const MAGIC: u32 = 0x5054474d;
+const MODULE_TARGET: u8 = 1;
+
+#[derive(Drop, Debug)]
+pub enum GovernanceAction {
+    UpgradeContract,
+    AuthorizeGovernanceDataSourceTransfer,
+    SetDataSources,
+    SetFee,
+    SetValidPeriod,
+    RequestGovernanceDataSourceTransfer,
+    SetWormholeAddress,
+}
+
+impl U8TryIntoGovernanceAction of TryInto<u8, GovernanceAction> {
+    fn try_into(self: u8) -> Option<GovernanceAction> {
+        let v = match self {
+            0 => GovernanceAction::UpgradeContract,
+            1 => GovernanceAction::AuthorizeGovernanceDataSourceTransfer,
+            2 => GovernanceAction::SetDataSources,
+            3 => GovernanceAction::SetFee,
+            4 => GovernanceAction::SetValidPeriod,
+            5 => GovernanceAction::RequestGovernanceDataSourceTransfer,
+            6 => GovernanceAction::SetWormholeAddress,
+            _ => { return Option::None; }
+        };
+        Option::Some(v)
+    }
+}
+
+#[derive(Drop, Debug)]
+pub struct GovernanceInstruction {
+    pub target_chain_id: u16,
+    pub payload: GovernancePayload,
+}
+
+#[derive(Drop, Debug)]
+pub enum GovernancePayload {
+    SetFee: SetFee,
+// TODO: others
+}
+
+#[derive(Drop, Debug)]
+pub struct SetFee {
+    pub value: u64,
+    pub expo: u64,
+}
+
+pub fn parse_instruction(payload: ByteArray) -> GovernanceInstruction {
+    let mut reader = ReaderImpl::new(payload);
+    let magic = reader.read_u32();
+    if magic != MAGIC {
+        panic_with_felt252(GovernanceActionError::InvalidGovernanceMessage.into());
+    }
+    let module = reader.read_u8();
+    if module != MODULE_TARGET {
+        panic_with_felt252(GovernanceActionError::InvalidGovernanceTarget.into());
+    }
+    let action: GovernanceAction = reader
+        .read_u8()
+        .try_into()
+        .expect(GovernanceActionError::InvalidGovernanceMessage.into());
+
+    let target_chain_id = reader.read_u16();
+
+    let payload = match action {
+        GovernanceAction::UpgradeContract => { panic_with_felt252('unimplemented') },
+        GovernanceAction::AuthorizeGovernanceDataSourceTransfer => {
+            panic_with_felt252('unimplemented')
+        },
+        GovernanceAction::SetDataSources => { panic_with_felt252('unimplemented') },
+        GovernanceAction::SetFee => {
+            let value = reader.read_u64();
+            let expo = reader.read_u64();
+            GovernancePayload::SetFee(SetFee { value, expo })
+        },
+        GovernanceAction::SetValidPeriod => { panic_with_felt252('unimplemented') },
+        GovernanceAction::RequestGovernanceDataSourceTransfer => {
+            panic_with_felt252('unimplemented')
+        },
+        GovernanceAction::SetWormholeAddress => { panic_with_felt252('unimplemented') },
+    };
+
+    if reader.len() != 0 {
+        panic_with_felt252(GovernanceActionError::InvalidGovernanceMessage.into());
+    }
+    GovernanceInstruction { target_chain_id, payload, }
+}

+ 5 - 0
target_chains/starknet/contracts/src/wormhole.cairo

@@ -8,6 +8,7 @@ mod governance;
 #[starknet::interface]
 pub trait IWormhole<T> {
     fn parse_and_verify_vm(self: @T, encoded_vm: ByteArray) -> VerifiedVM;
+    fn chain_id(self: @T) -> u16;
 
     // We don't need to implement other governance actions for now.
     // Instead of upgrading the Wormhole contract, we can switch to another Wormhole address
@@ -262,6 +263,10 @@ mod wormhole {
             vm
         }
 
+        fn chain_id(self: @ContractState) -> u16 {
+            self.chain_id.read()
+        }
+
         fn submit_new_guardian_set(ref self: ContractState, encoded_vm: ByteArray) {
             let vm = self.parse_and_verify_vm(encoded_vm);
             self.verify_governance_vm(@vm);

+ 100 - 10
target_chains/starknet/contracts/tests/pyth.cairo

@@ -9,6 +9,7 @@ use pyth::byte_array::{ByteArray, ByteArrayImpl};
 use pyth::util::{array_try_into, UnwrapWithFelt252};
 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') {
@@ -78,28 +79,102 @@ fn update_price_feeds_works() {
 }
 
 #[test]
-#[should_panic(expected: ('todo',))]
-fn test_governance_instruction() {
+fn test_governance_set_fee_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('fee_minter'.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));
+
+    let mut balance = fee_contract.balanceOf(user);
+    start_prank(CheatTarget::One(pyth.contract_address), user);
     pyth
-        .execute_governance_instruction(
+        .update_price_feeds(
             ByteArrayImpl::new(
                 array_try_into(
                     array![
-                        1766847064779996587365624568667043246291590523274145657502751050464410443,
-                        182147492092754791035110789954683447734750444021289139488583154515468770101,
-                        260016405939643347307460729490551925646487038190069904485177441375406260224,
-                        49565958604199796163020368,
-                        8072278384728444780182694421117884443886221966887092232,
+                        141887862745809943100421399774809552050876420277163116849842965275903806689,
+                        210740906737592158039211995620336526131859667363627655742687286503264782608,
+                        437230063624699337579360546580839669896712252828825008570863758867641146081,
+                        3498691308882995183871222184377409432186747119716981166996399082193594993,
+                        1390200166945919815453709407753165121175395927094647129599868236,
+                        222819573728193325268644030206737371345667885599602384508424089704440116301,
+                        341318259000017461738706238280879290398059773267212529438772847337449455616,
+                        1275126645346645395843037504005879519843596923369759718556759844520336145,
+                        363528783578153760894082184744116718493621815898909809604883433584616420886,
+                        301537311768214106147206781423041990995720118715322906821301413003463484347,
+                        83150006264761451992768264969047148434524798781124754530141755679159432208,
+                        96387772316726941183358990094337324283641753573556594738287498821253761827,
+                        395908154570808692326126405856049827157095768069251211022053821585519235652,
+                        87135893730137265929093180553063146337041045646221968026289709394440932141,
+                        245333243912241114598596888050489286502591033459250287888834,
                     ]
                 ),
-                23
+                25
             )
         );
+    stop_prank(CheatTarget::One(pyth.contract_address));
+    let new_balance = fee_contract.balanceOf(user);
+    assert!(balance - new_balance == 1000);
+    balance = new_balance;
+    let last_price = pyth
+        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
+        .unwrap_with_felt252();
+    assert!(last_price.price == 6281060000000);
+
+    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
+            )
+        );
+    stop_prank(CheatTarget::One(pyth.contract_address));
+    let new_balance = fee_contract.balanceOf(user);
+    assert!(balance - new_balance == 4200);
+    let last_price = pyth
+        .get_price_unsafe(0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43)
+        .unwrap_with_felt252();
+    assert!(last_price.price == 6281522520745);
+}
+
+#[test]
+#[fuzzer(runs: 100, seed: 0)]
+#[should_panic]
+fn test_rejects_corrupted_governance_instruction(pos: usize, random1: usize, random2: usize) {
+    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);
+
+    let input = corrupted_vm(governance_set_fee(), pos, random1, random2);
+    pyth.execute_governance_instruction(input);
 }
 
 fn deploy_default(
@@ -211,3 +286,18 @@ fn good_update1() -> ByteArray {
     ];
     ByteArrayImpl::new(array_try_into(bytes), 11)
 }
+
+fn governance_set_fee() -> ByteArray {
+    ByteArrayImpl::new(
+        array_try_into(
+            array![
+                1766847064779993955862540543984267022910717161432209540262366788014689913,
+                322968519187498395396360816568387642032723484530650782503164941848016432477,
+                407975527128964115747680681091773270935844489133353741223501742992990928896,
+                49565958604199796163020368,
+                8072278384728444780182694421117884443886221966887092226,
+            ]
+        ),
+        23
+    )
+}

+ 17 - 14
target_chains/starknet/contracts/tests/wormhole.cairo

@@ -9,6 +9,7 @@ use core::panic_with_felt252;
 #[test]
 fn test_parse_and_verify_vm_works() {
     let dispatcher = deploy_with_mainnet_guardians();
+    assert!(dispatcher.chain_id() == CHAIN_ID);
 
     let vm = dispatcher.parse_and_verify_vm(good_vm1());
     assert!(vm.version == 1);
@@ -54,12 +55,12 @@ fn test_submit_guardian_set_rejects_invalid_emitter() {
             ByteArrayImpl::new(
                 array_try_into(
                     array![
-                        1766847064779993780836504669527478016540696065743262550388106844126893447,
-                        69151420679589009790918040295649622139220704596068812939049771907133879935,
-                        104495383778385781169925874245014135708035627262536525919103632893328490496,
+                        1766847064779994286935846877901139612104608837038764127758248269325228906,
+                        418887508807350452965708560347030276949211978189626151402664101079839557558,
+                        197343809067707990824443354127489340211392396345777701010771497603219587072,
                         6044629098073145873860096,
                         1131377253,
-                        210626190275159008588167432483938910866205453014421218013934467097,
+                        307122819832911374634462256129025725147663742791077927773782095897,
                     ]
                 ),
                 28
@@ -86,12 +87,12 @@ fn test_submit_guardian_set_rejects_wrong_index_in_payload() {
             ByteArrayImpl::new(
                 array_try_into(
                     array![
-                        1766847064779992086486657352557640859156186269544082638392748527776826036,
-                        194277059768327503957595402526511955581212349039595919400671200491321209820,
-                        434496814532575177274556800095069791478767471500230428914335519265903345664,
+                        1766847064779996028795173168119917022867471281757617479529431178557452389,
+                        193283302630366040376283958080462867566389017166937293916301423178932756308,
+                        242372832420793675848312873608890898918531405734576271399238077831505248256,
                         4835703278458516699153920,
                         1131377253,
-                        210626190275159756877005745906233031152839803751327281851396470809,
+                        210624583337115497886730203944140689990237281548333499058561169433,
                     ]
                 ),
                 28
@@ -115,12 +116,12 @@ fn test_submit_guardian_set_rejects_empty() {
             ByteArrayImpl::new(
                 array_try_into(
                     array![
-                        1766847064779991325832211120621568385196129637393266719212748612567366167,
-                        101913621687013458268034923649142123047718029322567283658602535951581008800,
-                        334673952516423004763530105323893104621683276518933872430791864882642812928,
+                        1766847064779992178996580835909277664613661950847933439396875833330622292,
+                        364268459763994160238907315760635322263304542127720162879870384990884673431,
+                        419730305090247124548471445441531015782496798516552461724622762829422788608,
                         4835703278458516699153920,
                         1131377253,
-                        144116287587483904,
+                        210141960835432704,
                     ]
                 ),
                 8
@@ -189,7 +190,9 @@ pub fn deploy_with_test_guardian() -> IWormholeDispatcher {
     )
 }
 
-fn corrupted_vm(mut real_data: ByteArray, pos: usize, random1: usize, random2: usize) -> ByteArray {
+pub fn corrupted_vm(
+    mut real_data: ByteArray, pos: usize, random1: usize, random2: usize
+) -> ByteArray {
     let mut new_data = array![];
 
     // Make sure we select a position not on the last item because
@@ -292,7 +295,7 @@ fn good_vm1() -> ByteArray {
     ByteArrayImpl::new(array_try_into(bytes), 22)
 }
 
-const CHAIN_ID: u16 = 1;
+const CHAIN_ID: u16 = 60051;
 const GOVERNANCE_CHAIN_ID: u16 = 1;
 const GOVERNANCE_CONTRACT: u256 = 4;
 

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

@@ -25,7 +25,7 @@ fn main() {
                 consistency_level: 6,
                 payload: PayloadKind::Binary(
                     GuardianSetUpgrade {
-                        chain_id: 1,
+                        chain_id: 60051,
                         set_index: 1,
                         guardians: Vec::new(),
                     }
@@ -50,7 +50,7 @@ fn main() {
                 consistency_level: 6,
                 payload: PayloadKind::Binary(
                     GuardianSetUpgrade {
-                        chain_id: 1,
+                        chain_id: 60051,
                         set_index: 1,
                         guardians: vec![EthAddress(
                             hex::decode("686b9ea8e3237110eaaba1f1b7467559a3273819").unwrap(),
@@ -77,7 +77,7 @@ fn main() {
                 consistency_level: 6,
                 payload: PayloadKind::Binary(
                     GuardianSetUpgrade {
-                        chain_id: 1,
+                        chain_id: 0,
                         set_index: 3,
                         guardians: vec![EthAddress(
                             hex::decode("686b9ea8e3237110eaaba1f1b7467559a3273819").unwrap(),
@@ -91,4 +91,22 @@ fn main() {
     println!("wrong_index_upgrade");
     print_as_array_and_last(&wrong_index_upgrade);
     println!();
+
+    let pyth_set_fee = 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, 3, 234, 147, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 2,
+            ]),
+        },
+    ));
+    println!("pyth_set_fee");
+    print_as_array_and_last(&pyth_set_fee);
+    println!();
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 13 - 0
target_chains/starknet/tools/test_vaas/src/bin/re_sign_price_updates.rs


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

@@ -1,8 +1,10 @@
+use std::io::{Cursor, Seek, SeekFrom};
+
 use alloy_primitives::FixedBytes;
-use byteorder::{WriteBytesExt, BE};
+use byteorder::{ReadBytesExt, WriteBytesExt, BE};
 use libsecp256k1::{sign, Message, PublicKey, SecretKey};
 use primitive_types::U256;
-use wormhole_vaas::{keccak256, GuardianSetSig, Vaa, VaaBody, VaaHeader, Writeable};
+use wormhole_vaas::{keccak256, GuardianSetSig, Readable, Vaa, VaaBody, VaaHeader, Writeable};
 
 /// A data format compatible with `pyth::byte_array::ByteArray`.
 struct CairoByteArrayData {
@@ -135,3 +137,26 @@ 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> {
+    let mut reader = Cursor::new(update);
+    reader.seek(SeekFrom::Current(6)).unwrap();
+    let trailing_header_len = reader.read_u8().unwrap();
+    reader
+        .seek(SeekFrom::Current(trailing_header_len.into()))
+        .unwrap();
+    reader.seek(SeekFrom::Current(1)).unwrap();
+
+    let pos_before_vaa_size: usize = reader.position().try_into().unwrap();
+    let wh_proof_size: usize = reader.read_u16::<BE>().unwrap().into();
+    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 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());
+    new_update.extend_from_slice(&new_vaa);
+    new_update.extend_from_slice(&update[pos_after_vaa..]);
+    new_update
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio