11use std:: {
2- collections:: HashMap ,
2+ collections:: {
3+ HashMap ,
4+ HashSet ,
5+ } ,
6+ iter,
37 str:: FromStr ,
48} ;
59
@@ -13,7 +17,7 @@ use serde::{
1317use solana_program:: pubkey:: Pubkey ;
1418
1519/// Pyth2wormhole config specific to attestation requests
16- #[ derive( Debug , Deserialize , Serialize , PartialEq ) ]
20+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
1721pub struct AttestationConfig {
1822 #[ serde( default = "default_min_msg_reuse_interval_ms" ) ]
1923 pub min_msg_reuse_interval_ms : u64 ,
@@ -28,7 +32,61 @@ pub struct AttestationConfig {
2832 pub symbol_groups : Vec < SymbolGroup > ,
2933}
3034
31- #[ derive( Debug , Deserialize , Serialize , PartialEq ) ]
35+ impl AttestationConfig {
36+ /// Merges new symbols into the attestation config. Pre-existing
37+ /// new symbols are ignored. The new_group_name group can already
38+ /// exist - symbols will be appended to `symbols` field.
39+ pub fn add_symbols (
40+ & mut self ,
41+ mut new_symbols : HashMap < Pubkey , HashSet < Pubkey > > ,
42+ group_name : String , // Which group is extended by the new symbols
43+ ) {
44+ // Remove pre-existing symbols from the new symbols collection
45+ for existing_group in & self . symbol_groups {
46+ for existing_sym in & existing_group. symbols {
47+ // Check if new symbols mention this product
48+ if let Some ( mut prices) = new_symbols. get_mut ( & existing_sym. product_addr ) {
49+ // Prune the price if exists
50+ prices. remove ( & existing_sym. price_addr ) ;
51+ }
52+ }
53+ }
54+
55+ // Turn the pruned symbols into P2WSymbol structs
56+ let mut new_symbols_vec = new_symbols
57+ . drain ( ) // Makes us own the elements and lets us move them
58+ . map ( |( prod, prices) | iter:: zip ( iter:: repeat ( prod) , prices) ) // Convert to iterator over flat (prod, price) tuples
59+ . flatten ( ) // Flatten the tuple iterators
60+ . map ( |( prod, price) | P2WSymbol {
61+ name : None ,
62+ product_addr : prod,
63+ price_addr : price,
64+ } )
65+ . collect :: < Vec < P2WSymbol > > ( ) ;
66+
67+ // Find and extend OR create the group of specified name
68+ match self
69+ . symbol_groups
70+ . iter_mut ( )
71+ . find ( |g| g. group_name == group_name) // Advances the iterator and returns Some(item) on first hit
72+ {
73+ Some ( mut existing_group) => existing_group. symbols . append ( & mut new_symbols_vec) ,
74+ None if new_symbols_vec. len ( ) != 0 => {
75+ // Group does not exist, assume defaults
76+ let new_group = SymbolGroup {
77+ group_name,
78+ conditions : Default :: default ( ) ,
79+ symbols : new_symbols_vec,
80+ } ;
81+
82+ self . symbol_groups . push ( new_group) ;
83+ }
84+ None => { }
85+ }
86+ }
87+ }
88+
89+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
3290pub struct SymbolGroup {
3391 pub group_name : String ,
3492 /// Attestation conditions applied to all symbols in this group
@@ -56,7 +114,7 @@ pub const fn default_max_batch_jobs() -> usize {
56114/// of the active conditions is met. Option<> fields can be
57115/// de-activated with None. All conditions are inactive by default,
58116/// except for the non-Option ones.
59- #[ derive( Clone , Default , Debug , Deserialize , Serialize , PartialEq ) ]
117+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
60118pub struct AttestationConditions {
61119 /// Baseline, unconditional attestation interval. Attestation is triggered if the specified interval elapsed since last attestation.
62120 #[ serde( default = "default_min_interval_secs" ) ]
@@ -78,6 +136,17 @@ pub struct AttestationConditions {
78136 pub publish_time_min_delta_secs : Option < u64 > ,
79137}
80138
139+ impl Default for AttestationConditions {
140+ fn default ( ) -> Self {
141+ Self {
142+ min_interval_secs : default_min_interval_secs ( ) ,
143+ max_batch_jobs : default_max_batch_jobs ( ) ,
144+ price_changed_pct : None ,
145+ publish_time_min_delta_secs : None ,
146+ }
147+ }
148+ }
149+
81150/// Config entry for a Pyth product + price pair
82151#[ derive( Clone , Default , Debug , Deserialize , Serialize , PartialEq , Eq ) ]
83152pub struct P2WSymbol {
@@ -200,4 +269,45 @@ mod tests {
200269
201270 Ok ( ( ) )
202271 }
272+
273+ #[ test]
274+ fn test_add_symbols_works ( ) -> Result < ( ) , ErrBox > {
275+ let empty_config = AttestationConfig {
276+ min_msg_reuse_interval_ms : 1000 ,
277+ max_msg_accounts : 100 ,
278+ mapping_addr : None ,
279+ symbol_groups : vec ! [ ] ,
280+ } ;
281+
282+ let mock_new_symbols = ( 0 ..255 )
283+ . map ( |sym_idx| {
284+ let mut mock_prod_bytes = [ 0u8 ; 32 ] ;
285+ mock_prod_bytes[ 31 ] = sym_idx;
286+
287+ let mut mock_prices = HashSet :: new ( ) ;
288+ for px_idx in 1 ..=5 {
289+ let mut mock_price_bytes = [ 0u8 ; 32 ] ;
290+ mock_price_bytes[ 31 ] = sym_idx;
291+ mock_prices. insert ( Pubkey :: new_from_array ( mock_price_bytes) ) ;
292+ }
293+
294+ ( Pubkey :: new_from_array ( mock_prod_bytes) , mock_prices)
295+ } )
296+ . collect :: < HashMap < Pubkey , HashSet < Pubkey > > > ( ) ;
297+
298+ let mut config1 = empty_config. clone ( ) ;
299+
300+ config1. add_symbols ( mock_new_symbols. clone ( ) , "default" . to_owned ( ) ) ;
301+
302+ let mut config2 = config1. clone ( ) ;
303+
304+ // Should not be created because there's no new symbols to add
305+ // (we're adding identical mock_new_symbols again)
306+ config2. add_symbols ( mock_new_symbols. clone ( ) , "default2" . to_owned ( ) ) ;
307+
308+ assert_ne ! ( config1, empty_config) ; // Check that config grows from empty
309+ assert_eq ! ( config1, config2) ; // Check that no changes are made if all symbols are already in there
310+
311+ Ok ( ( ) )
312+ }
203313}
0 commit comments