@@ -11,7 +11,7 @@ pub use pyth::{
11
11
};
12
12
pub use errors :: {
13
13
GetPriceUnsafeError , GovernanceActionError , UpdatePriceFeedsError , GetPriceNoOlderThanError ,
14
- UpdatePriceFeedsIfNecessaryError , ParsePriceFeedsError ,
14
+ UpdatePriceFeedsIfNecessaryError , ParsePriceFeedsError , GetSingleUpdateFeeError ,
15
15
};
16
16
pub use interface :: {
17
17
IPyth , IPythDispatcher , IPythDispatcherTrait , DataSource , Price , PriceFeedPublishTime , PriceFeed
@@ -36,7 +36,7 @@ mod pyth {
36
36
use super :: {
37
37
DataSource , UpdatePriceFeedsError , GovernanceActionError , Price , GetPriceUnsafeError ,
38
38
IPythDispatcher , IPythDispatcherTrait , PriceFeedPublishTime , GetPriceNoOlderThanError ,
39
- UpdatePriceFeedsIfNecessaryError , PriceFeed , ParsePriceFeedsError ,
39
+ UpdatePriceFeedsIfNecessaryError , PriceFeed , ParsePriceFeedsError , GetSingleUpdateFeeError ,
40
40
};
41
41
use super :: governance;
42
42
use super :: governance :: GovernancePayload ;
@@ -97,8 +97,10 @@ mod pyth {
97
97
#[storage]
98
98
struct Storage {
99
99
wormhole_address : ContractAddress ,
100
- fee_token_address : ContractAddress ,
101
- single_update_fee : u256 ,
100
+ fee_token_address1 : ContractAddress ,
101
+ fee_token_address2 : ContractAddress ,
102
+ single_update_fee1 : u256 ,
103
+ single_update_fee2 : u256 ,
102
104
data_sources : LegacyMap <usize , DataSource >,
103
105
num_data_sources : usize ,
104
106
// For fast validation.
@@ -115,29 +117,36 @@ mod pyth {
115
117
///
116
118
/// `wormhole_address` is the address of the deployed Wormhole contract implemented in the `wormhole` module.
117
119
///
118
- /// `fee_token_address ` is the address of the ERC20 token used to pay fees to Pyth
120
+ /// `fee_token_address1 ` is the address of the ERC20 token used to pay fees to Pyth
119
121
/// for price updates. There is no native token on Starknet so an ERC20 contract has to be used.
120
122
/// On Katana, an ETH fee contract is pre-deployed. On Starknet testnet, ETH and STRK fee tokens are
121
123
/// available. Any other ERC20-compatible token can also be used.
122
124
/// In a Starknet Forge testing environment, a fee contract must be deployed manually.
123
125
///
124
- /// `single_update_fee` is the number of tokens of `fee_token_address` charged for a single price update.
126
+ /// `single_update_fee1` is the number of tokens of `fee_token_address1` charged for a single price update.
127
+ ///
128
+ /// `fee_token_address2` and `single_update_fee2` specify the secondary fee contract and fee rate
129
+ /// that can be used instead of the main fee token.
125
130
///
126
131
/// `data_sources` is the list of Wormhole data sources accepted by this contract.
127
132
#[constructor]
128
133
fn constructor (
129
134
ref self : ContractState ,
130
135
wormhole_address : ContractAddress ,
131
- fee_token_address : ContractAddress ,
132
- single_update_fee : u256 ,
136
+ fee_token_address1 : ContractAddress ,
137
+ single_update_fee1 : u256 ,
138
+ fee_token_address2 : ContractAddress ,
139
+ single_update_fee2 : u256 ,
133
140
data_sources : Array <DataSource >,
134
141
governance_emitter_chain_id : u16 ,
135
142
governance_emitter_address : u256 ,
136
143
governance_initial_sequence : u64 ,
137
144
) {
138
145
self . wormhole_address. write (wormhole_address );
139
- self . fee_token_address. write (fee_token_address );
140
- self . single_update_fee. write (single_update_fee );
146
+ self . fee_token_address1. write (fee_token_address1 );
147
+ self . single_update_fee1. write (single_update_fee1 );
148
+ self . fee_token_address2. write (fee_token_address2 );
149
+ self . single_update_fee2. write (single_update_fee2 );
141
150
self . write_data_sources (@ data_sources );
142
151
self
143
152
. governance_data_source
@@ -253,13 +262,21 @@ mod pyth {
253
262
self . update_price_feeds_internal (data , array! [], 0 , 0 , false );
254
263
}
255
264
256
- fn get_update_fee (self : @ ContractState , data : ByteArray ) -> u256 {
265
+ fn get_update_fee (self : @ ContractState , data : ByteArray , token : ContractAddress ) -> u256 {
266
+ let single_update_fee = if token == self . fee_token_address1. read () {
267
+ self . single_update_fee1. read ()
268
+ } else if token == self . fee_token_address2. read () {
269
+ self . single_update_fee2. read ()
270
+ } else {
271
+ panic_with_felt252 (GetSingleUpdateFeeError :: UnsupportedToken . into ())
272
+ };
273
+
257
274
let mut reader = ReaderImpl :: new (data );
258
275
read_and_verify_header (ref reader );
259
276
let wormhole_proof_size = reader . read_u16 ();
260
277
reader . skip (wormhole_proof_size . into ());
261
278
let num_updates = reader . read_u8 ();
262
- self . get_total_fee ( num_updates )
279
+ single_update_fee * num_updates . into ( )
263
280
}
264
281
265
282
fn update_price_feeds_if_necessary (
@@ -314,12 +331,18 @@ mod pyth {
314
331
self . wormhole_address. read ()
315
332
}
316
333
317
- fn fee_token_address (self : @ ContractState ) -> ContractAddress {
318
- self . fee_token_address . read ()
334
+ fn fee_token_addresses (self : @ ContractState ) -> Array < ContractAddress > {
335
+ array! [ self . fee_token_address1 . read (), self . fee_token_address2 . read ()]
319
336
}
320
337
321
- fn get_single_update_fee (self : @ ContractState ) -> u256 {
322
- self . single_update_fee. read ()
338
+ fn get_single_update_fee (self : @ ContractState , token : ContractAddress ) -> u256 {
339
+ if token == self . fee_token_address1. read () {
340
+ self . single_update_fee1. read ()
341
+ } else if token == self . fee_token_address2. read () {
342
+ self . single_update_fee2. read ()
343
+ } else {
344
+ panic_with_felt252 (GetSingleUpdateFeeError :: UnsupportedToken . into ())
345
+ }
323
346
}
324
347
325
348
fn valid_data_sources (self : @ ContractState ) -> Array <DataSource > {
@@ -370,8 +393,8 @@ mod pyth {
370
393
match instruction . payload {
371
394
GovernancePayload :: SetFee (payload ) => {
372
395
let new_fee = apply_decimal_expo (payload . value, payload . expo);
373
- let old_fee = self . single_update_fee . read ();
374
- self . single_update_fee . write (new_fee );
396
+ let old_fee = self . single_update_fee1 . read ();
397
+ self . single_update_fee1 . write (new_fee );
375
398
let event = FeeSet { old_fee , new_fee };
376
399
self . emit (event );
377
400
},
@@ -468,10 +491,6 @@ mod pyth {
468
491
}
469
492
}
470
493
471
- fn get_total_fee (self : @ ContractState , num_updates : u8 ) -> u256 {
472
- self . single_update_fee. read () * num_updates . into ()
473
- }
474
-
475
494
fn verify_governance_vm (ref self : ContractState , vm : @ VerifiedVM ) {
476
495
let governance_data_source = self . governance_data_source. read ();
477
496
if governance_data_source . emitter_chain_id != * vm . emitter_chain_id {
@@ -611,18 +630,27 @@ mod pyth {
611
630
let root_digest = parse_wormhole_proof (vm . payload);
612
631
613
632
let num_updates = reader . read_u8 ();
614
- let total_fee = self . get_total_fee (num_updates );
615
- let fee_contract = IERC20CamelDispatcher {
616
- contract_address : self . fee_token_address. read ()
617
- };
618
633
let execution_info = get_execution_info (). unbox ();
619
634
let caller = execution_info . caller_address;
620
635
let contract = execution_info . contract_address;
621
- if fee_contract . allowance (caller , contract ) < total_fee {
622
- panic_with_felt252 (UpdatePriceFeedsError :: InsufficientFeeAllowance . into ());
623
- }
624
- if ! fee_contract . transferFrom (caller , contract , total_fee ) {
625
- panic_with_felt252 (UpdatePriceFeedsError :: InsufficientFeeAllowance . into ());
636
+ let fee1_transfered = transfer_fee (
637
+ num_updates ,
638
+ caller ,
639
+ contract ,
640
+ self . fee_token_address1. read (),
641
+ self . single_update_fee1. read (),
642
+ );
643
+ if ! fee1_transfered {
644
+ let fee2_transfered = transfer_fee (
645
+ num_updates ,
646
+ caller ,
647
+ contract ,
648
+ self . fee_token_address2. read (),
649
+ self . single_update_fee2. read (),
650
+ );
651
+ if ! fee2_transfered {
652
+ panic_with_felt252 (UpdatePriceFeedsError :: InsufficientFeeAllowance . into ());
653
+ }
626
654
}
627
655
628
656
let mut i = 0 ;
@@ -708,4 +736,25 @@ mod pyth {
708
736
Option :: Some (i )
709
737
}
710
738
}
739
+
740
+ fn transfer_fee (
741
+ num_updates : u8 ,
742
+ caller : ContractAddress ,
743
+ contract : ContractAddress ,
744
+ fee_token : ContractAddress ,
745
+ single_update_fee1 : u256 ,
746
+ ) -> bool {
747
+ let total_fee = single_update_fee1 * num_updates . into ();
748
+ let fee_contract = IERC20CamelDispatcher { contract_address : fee_token };
749
+ if fee_contract . allowance (caller , contract ) < total_fee {
750
+ return false ;
751
+ }
752
+ if fee_contract . balanceOf (caller ) < total_fee {
753
+ return false ;
754
+ }
755
+ if ! fee_contract . transferFrom (caller , contract , total_fee ) {
756
+ return false ;
757
+ }
758
+ true
759
+ }
711
760
}
0 commit comments