1
1
use std:: {
2
- collections:: HashMap ,
2
+ collections:: {
3
+ HashMap ,
4
+ HashSet ,
5
+ } ,
6
+ iter,
3
7
str:: FromStr ,
4
8
} ;
5
9
@@ -13,7 +17,7 @@ use serde::{
13
17
use solana_program:: pubkey:: Pubkey ;
14
18
15
19
/// Pyth2wormhole config specific to attestation requests
16
- #[ derive( Debug , Deserialize , Serialize , PartialEq ) ]
20
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
17
21
pub struct AttestationConfig {
18
22
#[ serde( default = "default_min_msg_reuse_interval_ms" ) ]
19
23
pub min_msg_reuse_interval_ms : u64 ,
@@ -28,7 +32,61 @@ pub struct AttestationConfig {
28
32
pub symbol_groups : Vec < SymbolGroup > ,
29
33
}
30
34
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 ) ]
32
90
pub struct SymbolGroup {
33
91
pub group_name : String ,
34
92
/// Attestation conditions applied to all symbols in this group
@@ -56,7 +114,7 @@ pub const fn default_max_batch_jobs() -> usize {
56
114
/// of the active conditions is met. Option<> fields can be
57
115
/// de-activated with None. All conditions are inactive by default,
58
116
/// except for the non-Option ones.
59
- #[ derive( Clone , Default , Debug , Deserialize , Serialize , PartialEq ) ]
117
+ #[ derive( Clone , Debug , Deserialize , Serialize , PartialEq ) ]
60
118
pub struct AttestationConditions {
61
119
/// Baseline, unconditional attestation interval. Attestation is triggered if the specified interval elapsed since last attestation.
62
120
#[ serde( default = "default_min_interval_secs" ) ]
@@ -78,6 +136,17 @@ pub struct AttestationConditions {
78
136
pub publish_time_min_delta_secs : Option < u64 > ,
79
137
}
80
138
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
+
81
150
/// Config entry for a Pyth product + price pair
82
151
#[ derive( Clone , Default , Debug , Deserialize , Serialize , PartialEq , Eq ) ]
83
152
pub struct P2WSymbol {
@@ -200,4 +269,45 @@ mod tests {
200
269
201
270
Ok ( ( ) )
202
271
}
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
+ }
203
313
}
0 commit comments