@@ -87,10 +87,11 @@ impl TwapUpdate {
87
87
/// # Warning
88
88
/// This function does not check :
89
89
/// - How recent the price is
90
+ /// - If the TWAP's window size is expected
90
91
/// - Whether the price update has been verified
91
92
///
92
93
/// It is therefore unsafe to use this function without any extra checks,
93
- /// as it allows for the possibility of using unverified or outdated price updates.
94
+ /// as it allows for the possibility of using unverified, outdated, or arbitrary window length twap updates.
94
95
pub fn get_twap_unchecked (
95
96
& self ,
96
97
feed_id : & FeedId ,
@@ -101,32 +102,38 @@ impl TwapUpdate {
101
102
) ;
102
103
Ok ( self . twap )
103
104
}
104
-
105
- /// Get a `TwapPrice` from a `TwapUpdate` account for a given `FeedId` no older than `maximum_age`.
105
+ /// Get a `TwapPrice` from a `TwapUpdate` account for a given `FeedId` no older than `maximum_age` with a specific window size.
106
106
///
107
107
/// # Example
108
108
/// ```
109
109
/// use pyth_solana_receiver_sdk::price_update::{get_feed_id_from_hex, TwapUpdate};
110
110
/// use anchor_lang::prelude::*;
111
111
///
112
- /// const MAXIMUM_AGE : u64 = 30;
112
+ /// const MAXIMUM_AGE: u64 = 30;
113
+ /// const WINDOW_SECONDS: u64 = 300; // 5-minute TWAP
113
114
/// const FEED_ID: &str = "0xef0d8b6fda2ceba41da15d4095d1da392a0d2f8ed0c6c7bc0f4cfac8c280b56d"; // SOL/USD
114
115
///
115
116
/// #[derive(Accounts)]
116
117
/// pub struct ReadTwapAccount<'info> {
117
118
/// pub twap_update: Account<'info, TwapUpdate>,
118
119
/// }
119
120
///
120
- /// pub fn read_twap_account(ctx : Context<ReadTwapAccount>) -> Result<()> {
121
+ /// pub fn read_twap_account(ctx: Context<ReadTwapAccount>) -> Result<()> {
121
122
/// let twap_update = &ctx.accounts.twap_update;
122
- /// let twap = twap_update.get_twap_no_older_than(&Clock::get()?, MAXIMUM_AGE, &get_feed_id_from_hex(FEED_ID)?)?;
123
+ /// let twap = twap_update.get_twap_no_older_than(
124
+ /// &Clock::get()?,
125
+ /// MAXIMUM_AGE,
126
+ /// WINDOW_SECONDS,
127
+ /// &get_feed_id_from_hex(FEED_ID)?
128
+ /// )?;
123
129
/// Ok(())
124
130
/// }
125
131
/// ```
126
132
pub fn get_twap_no_older_than (
127
133
& self ,
128
134
clock : & Clock ,
129
135
maximum_age : u64 ,
136
+ window_seconds : u64 ,
130
137
feed_id : & FeedId ,
131
138
) -> std:: result:: Result < TwapPrice , GetPriceError > {
132
139
// Ensure the update isn't outdated
@@ -138,6 +145,14 @@ impl TwapUpdate {
138
145
>= clock. unix_timestamp,
139
146
GetPriceError :: PriceTooOld
140
147
) ;
148
+
149
+ // Ensure the twap window size is as expected
150
+ let actual_window = twap_price. end_time . saturating_sub ( twap_price. start_time ) ;
151
+ check ! (
152
+ actual_window == i64 :: try_from( window_seconds) . unwrap( ) ,
153
+ GetPriceError :: InvalidWindowSize
154
+ ) ;
155
+
141
156
Ok ( twap_price)
142
157
}
143
158
}
@@ -543,13 +558,12 @@ pub mod tests {
543
558
Err ( GetPriceError :: MismatchedFeedId )
544
559
) ;
545
560
}
546
-
547
561
#[ test]
548
562
fn test_get_twap_no_older_than ( ) {
549
563
let expected_twap = TwapPrice {
550
564
feed_id : [ 0 ; 32 ] ,
551
565
start_time : 800 ,
552
- end_time : 900 ,
566
+ end_time : 900 , // Window size is 100 seconds (900 - 800)
553
567
price : 1 ,
554
568
conf : 2 ,
555
569
exponent : -3 ,
@@ -571,15 +585,27 @@ pub mod tests {
571
585
// Test unchecked access
572
586
assert_eq ! ( update. get_twap_unchecked( & feed_id) , Ok ( expected_twap) ) ;
573
587
574
- // Test with age check
588
+ // Test with correct window size (100 seconds)
575
589
assert_eq ! (
576
- update. get_twap_no_older_than( & mock_clock, 100 , & feed_id) ,
590
+ update. get_twap_no_older_than( & mock_clock, 100 , 100 , & feed_id) ,
577
591
Ok ( expected_twap)
578
592
) ;
579
593
594
+ // Test with incorrect window size
595
+ assert_eq ! (
596
+ update. get_twap_no_older_than( & mock_clock, 100 , 101 , & feed_id) ,
597
+ Err ( GetPriceError :: InvalidWindowSize )
598
+ ) ;
599
+
600
+ // Test with incorrect window size
601
+ assert_eq ! (
602
+ update. get_twap_no_older_than( & mock_clock, 100 , 99 , & feed_id) ,
603
+ Err ( GetPriceError :: InvalidWindowSize )
604
+ ) ;
605
+
580
606
// Test with reduced maximum age
581
607
assert_eq ! (
582
- update. get_twap_no_older_than( & mock_clock, 10 , & feed_id) ,
608
+ update. get_twap_no_older_than( & mock_clock, 10 , 100 , & feed_id) ,
583
609
Err ( GetPriceError :: PriceTooOld )
584
610
) ;
585
611
@@ -589,7 +615,7 @@ pub mod tests {
589
615
Err ( GetPriceError :: MismatchedFeedId )
590
616
) ;
591
617
assert_eq ! (
592
- update. get_twap_no_older_than( & mock_clock, 100 , & mismatched_feed_id) ,
618
+ update. get_twap_no_older_than( & mock_clock, 100 , 100 , & mismatched_feed_id) ,
593
619
Err ( GetPriceError :: MismatchedFeedId )
594
620
) ;
595
621
}
0 commit comments