@@ -11,10 +11,10 @@ pub use pyth::{
11
11
};
12
12
pub use errors :: {
13
13
GetPriceUnsafeError , GovernanceActionError , UpdatePriceFeedsError , GetPriceNoOlderThanError ,
14
- UpdatePriceFeedsIfNecessaryError ,
14
+ UpdatePriceFeedsIfNecessaryError , ParsePriceFeedsError ,
15
15
};
16
16
pub use interface :: {
17
- IPyth , IPythDispatcher , IPythDispatcherTrait , DataSource , Price , PriceFeedPublishTime
17
+ IPyth , IPythDispatcher , IPythDispatcherTrait , DataSource , Price , PriceFeedPublishTime , PriceFeed
18
18
};
19
19
20
20
#[starknet:: contract]
@@ -36,12 +36,13 @@ mod pyth {
36
36
use super :: {
37
37
DataSource , UpdatePriceFeedsError , GovernanceActionError , Price , GetPriceUnsafeError ,
38
38
IPythDispatcher , IPythDispatcherTrait , PriceFeedPublishTime , GetPriceNoOlderThanError ,
39
- UpdatePriceFeedsIfNecessaryError ,
39
+ UpdatePriceFeedsIfNecessaryError , PriceFeed , ParsePriceFeedsError ,
40
40
};
41
41
use super :: governance;
42
42
use super :: governance :: GovernancePayload ;
43
43
use openzeppelin :: token :: erc20 :: interface :: {IERC20CamelDispatcherTrait , IERC20CamelDispatcher };
44
44
use pyth :: util :: ResultMapErrInto ;
45
+ use core :: nullable :: {NullableTrait , match_nullable, FromNullableResult };
45
46
46
47
#[event]
47
48
#[derive(Drop , PartialEq , starknet:: Event )]
@@ -204,48 +205,7 @@ mod pyth {
204
205
}
205
206
206
207
fn update_price_feeds (ref self : ContractState , data : ByteArray ) {
207
- let mut reader = ReaderImpl :: new (data );
208
- read_and_verify_header (ref reader );
209
- let wormhole_proof_size = reader . read_u16 ();
210
- let wormhole_proof = reader . read_byte_array (wormhole_proof_size . into ());
211
-
212
- let wormhole = IWormholeDispatcher { contract_address : self . wormhole_address. read () };
213
- let vm = wormhole . parse_and_verify_vm (wormhole_proof );
214
-
215
- let source = DataSource {
216
- emitter_chain_id : vm . emitter_chain_id, emitter_address : vm . emitter_address
217
- };
218
- if ! self . is_valid_data_source. read (source ) {
219
- panic_with_felt252 (UpdatePriceFeedsError :: InvalidUpdateDataSource . into ());
220
- }
221
-
222
- let root_digest = parse_wormhole_proof (vm . payload);
223
-
224
- let num_updates = reader . read_u8 ();
225
- let total_fee = self . get_total_fee (num_updates );
226
- let fee_contract = IERC20CamelDispatcher {
227
- contract_address : self . fee_contract_address. read ()
228
- };
229
- let execution_info = get_execution_info (). unbox ();
230
- let caller = execution_info . caller_address;
231
- let contract = execution_info . contract_address;
232
- if fee_contract . allowance (caller , contract ) < total_fee {
233
- panic_with_felt252 (UpdatePriceFeedsError :: InsufficientFeeAllowance . into ());
234
- }
235
- if ! fee_contract . transferFrom (caller , contract , total_fee ) {
236
- panic_with_felt252 (UpdatePriceFeedsError :: InsufficientFeeAllowance . into ());
237
- }
238
-
239
- let mut i = 0 ;
240
- while i < num_updates {
241
- let message = read_and_verify_message (ref reader , root_digest );
242
- self . update_latest_price_if_necessary (message );
243
- i += 1 ;
244
- };
245
-
246
- if reader . len () != 0 {
247
- panic_with_felt252 (UpdatePriceFeedsError :: InvalidUpdateData . into ());
248
- }
208
+ self . update_price_feeds_internal (data , array! [], 0 , 0 , false );
249
209
}
250
210
251
211
fn get_update_fee (self : @ ContractState , data : ByteArray ) -> u256 {
@@ -279,6 +239,32 @@ mod pyth {
279
239
}
280
240
}
281
241
242
+ fn parse_price_feed_updates (
243
+ ref self : ContractState ,
244
+ data : ByteArray ,
245
+ price_ids : Array <u256 >,
246
+ min_publish_time : u64 ,
247
+ max_publish_time : u64
248
+ ) -> Array <PriceFeed > {
249
+ self
250
+ . update_price_feeds_internal (
251
+ data , price_ids , min_publish_time , max_publish_time , false
252
+ )
253
+ }
254
+
255
+ fn parse_unique_price_feed_updates (
256
+ ref self : ContractState ,
257
+ data : ByteArray ,
258
+ price_ids : Array <u256 >,
259
+ publish_time : u64 ,
260
+ max_staleness : u64 ,
261
+ ) -> Array <PriceFeed > {
262
+ self
263
+ . update_price_feeds_internal (
264
+ data , price_ids , publish_time , publish_time + max_staleness , true
265
+ )
266
+ }
267
+
282
268
fn execute_governance_instruction (ref self : ContractState , data : ByteArray ) {
283
269
let wormhole = IWormholeDispatcher { contract_address : self . wormhole_address. read () };
284
270
let vm = wormhole . parse_and_verify_vm (data . clone ());
@@ -362,24 +348,24 @@ mod pyth {
362
348
old_data_sources
363
349
}
364
350
365
- fn update_latest_price_if_necessary (ref self : ContractState , message : PriceFeedMessage ) {
366
- let latest_publish_time = self . latest_price_info. read (message . price_id). publish_time;
367
- if message . publish_time > latest_publish_time {
351
+ fn update_latest_price_if_necessary (ref self : ContractState , message : @ PriceFeedMessage ) {
352
+ let latest_publish_time = self . latest_price_info. read (* message . price_id). publish_time;
353
+ if * message . publish_time > latest_publish_time {
368
354
let info = PriceInfo {
369
- price : message . price,
370
- conf : message . conf,
371
- expo : message . expo,
372
- publish_time : message . publish_time,
373
- ema_price : message . ema_price,
374
- ema_conf : message . ema_conf,
355
+ price : * message . price,
356
+ conf : * message . conf,
357
+ expo : * message . expo,
358
+ publish_time : * message . publish_time,
359
+ ema_price : * message . ema_price,
360
+ ema_conf : * message . ema_conf,
375
361
};
376
- self . latest_price_info. write (message . price_id, info );
362
+ self . latest_price_info. write (* message . price_id, info );
377
363
378
364
let event = PriceFeedUpdated {
379
- price_id : message . price_id,
380
- publish_time : message . publish_time,
381
- price : message . price,
382
- conf : message . conf,
365
+ price_id : * message . price_id,
366
+ publish_time : * message . publish_time,
367
+ price : * message . price,
368
+ conf : * message . conf,
383
369
};
384
370
self . emit (event );
385
371
}
@@ -490,6 +476,105 @@ mod pyth {
490
476
let event = ContractUpgraded { new_class_hash : new_implementation };
491
477
self . emit (event );
492
478
}
479
+
480
+ // Applies all price feed updates encoded in `data` and extracts requested information
481
+ // about the new updates. `price_ids` specifies price feeds of interest. The output will
482
+ // contain as many items as `price_ids`, with price feeds returned in the same order as
483
+ // specified in `price_ids`.
484
+ //
485
+ // If `unique == false`, for each price feed, the first encountered update
486
+ // in the specified time interval (both timestamps inclusive) will be returned.
487
+ // If `unique == true`, the globally unique first update will be returned, as verified by
488
+ // the `prev_publish_time` value of the update. Panics if a matching update was not found
489
+ // for any of the specified feeds.
490
+ fn update_price_feeds_internal (
491
+ ref self : ContractState ,
492
+ data : ByteArray ,
493
+ price_ids : Array <u256 >,
494
+ min_publish_time : u64 ,
495
+ max_publish_time : u64 ,
496
+ unique : bool ,
497
+ ) -> Array <PriceFeed > {
498
+ let mut output : Felt252Dict <Nullable <PriceFeed >> = Default :: default ();
499
+ let mut reader = ReaderImpl :: new (data );
500
+ read_and_verify_header (ref reader );
501
+ let wormhole_proof_size = reader . read_u16 ();
502
+ let wormhole_proof = reader . read_byte_array (wormhole_proof_size . into ());
503
+
504
+ let wormhole = IWormholeDispatcher { contract_address : self . wormhole_address. read () };
505
+ let vm = wormhole . parse_and_verify_vm (wormhole_proof );
506
+
507
+ let source = DataSource {
508
+ emitter_chain_id : vm . emitter_chain_id, emitter_address : vm . emitter_address
509
+ };
510
+ if ! self . is_valid_data_source. read (source ) {
511
+ panic_with_felt252 (UpdatePriceFeedsError :: InvalidUpdateDataSource . into ());
512
+ }
513
+
514
+ let root_digest = parse_wormhole_proof (vm . payload);
515
+
516
+ let num_updates = reader . read_u8 ();
517
+ let total_fee = self . get_total_fee (num_updates );
518
+ let fee_contract = IERC20CamelDispatcher {
519
+ contract_address : self . fee_contract_address. read ()
520
+ };
521
+ let execution_info = get_execution_info (). unbox ();
522
+ let caller = execution_info . caller_address;
523
+ let contract = execution_info . contract_address;
524
+ if fee_contract . allowance (caller , contract ) < total_fee {
525
+ panic_with_felt252 (UpdatePriceFeedsError :: InsufficientFeeAllowance . into ());
526
+ }
527
+ if ! fee_contract . transferFrom (caller , contract , total_fee ) {
528
+ panic_with_felt252 (UpdatePriceFeedsError :: InsufficientFeeAllowance . into ());
529
+ }
530
+
531
+ let mut i = 0 ;
532
+ let price_ids2 = @ price_ids ;
533
+ while i < num_updates {
534
+ let message = read_and_verify_message (ref reader , root_digest );
535
+ self . update_latest_price_if_necessary (@ message );
536
+
537
+ let output_index = find_index_of_price_id (price_ids2 , message . price_id);
538
+ match output_index {
539
+ Option :: Some (output_index ) => {
540
+ if output . get (output_index . into ()). is_null () {
541
+ let should_output = message . publish_time >= min_publish_time
542
+ && message . publish_time <= max_publish_time
543
+ && (! unique || min_publish_time > message . prev_publish_time);
544
+ if should_output {
545
+ output
546
+ . insert (
547
+ output_index . into (), NullableTrait :: new (message . into ())
548
+ );
549
+ }
550
+ }
551
+ },
552
+ Option :: None => {}
553
+ }
554
+
555
+ i += 1 ;
556
+ };
557
+
558
+ if reader . len () != 0 {
559
+ panic_with_felt252 (UpdatePriceFeedsError :: InvalidUpdateData . into ());
560
+ }
561
+
562
+ let mut output_array = array! [];
563
+ let mut i = 0 ;
564
+ while i < price_ids . len () {
565
+ let value = output . get (i . into ());
566
+ match match_nullable (value ) {
567
+ FromNullableResult :: Null => {
568
+ panic_with_felt252 (
569
+ ParsePriceFeedsError :: PriceFeedNotFoundWithinRange . into ()
570
+ )
571
+ },
572
+ FromNullableResult :: NotNull (value ) => { output_array . append (value . unbox ()); }
573
+ }
574
+ i += 1 ;
575
+ };
576
+ output_array
577
+ }
493
578
}
494
579
495
580
fn apply_decimal_expo (value : u64 , expo : u64 ) -> u256 {
@@ -511,4 +596,19 @@ mod pyth {
511
596
};
512
597
actual_age <= age
513
598
}
599
+
600
+ fn find_index_of_price_id (ids : @ Array <u256 >, value : u256 ) -> Option <usize > {
601
+ let mut i = 0 ;
602
+ while i < ids . len () {
603
+ if ids . at (i ) == @ value {
604
+ break ;
605
+ }
606
+ i += 1 ;
607
+ };
608
+ if i == ids . len () {
609
+ Option :: None
610
+ } else {
611
+ Option :: Some (i )
612
+ }
613
+ }
514
614
}
0 commit comments