1
1
use {
2
2
crate :: {
3
- attestation_state:: {
4
- AttestationState ,
5
- AttestationStateMapPDA ,
6
- } ,
3
+ attestation_state:: AttestationStatePDA ,
7
4
config:: P2WConfigAccount ,
8
5
message:: {
9
6
P2WMessage ,
39
36
solitaire:: {
40
37
trace,
41
38
AccountState ,
42
- CreationLamports ,
43
39
ExecutionContext ,
44
40
FromAccounts ,
45
41
Info ,
57
53
/// Important: must be manually maintained until native Solitaire
58
54
/// variable len vector support.
59
55
///
60
- /// The number must reflect how many pyth product /price pairs are
56
+ /// The number must reflect how many pyth state /price pairs are
61
57
/// expected in the Attest struct below. The constant itself is only
62
58
/// used in the on-chain config in order for attesters to learn the
63
59
/// correct value dynamically.
@@ -66,42 +62,41 @@ pub const P2W_MAX_BATCH_SIZE: u16 = 5;
66
62
#[ derive( FromAccounts ) ]
67
63
pub struct Attest < ' b > {
68
64
// Payer also used for wormhole
69
- pub payer : Mut < Signer < Info < ' b > > > ,
70
- pub system_program : Info < ' b > ,
71
- pub config : P2WConfigAccount < ' b , { AccountState :: Initialized } > ,
72
- pub attestation_state : Mut < AttestationStateMapPDA < ' b > > ,
65
+ pub payer : Mut < Signer < Info < ' b > > > ,
66
+ pub system_program : Info < ' b > ,
67
+ pub config : P2WConfigAccount < ' b , { AccountState :: Initialized } > ,
73
68
74
- // Hardcoded product /price pairs, bypassing Solitaire's variable-length limitations
69
+ // Hardcoded state /price pairs, bypassing Solitaire's variable-length limitations
75
70
// Any change to the number of accounts must include an appropriate change to P2W_MAX_BATCH_SIZE
76
- pub pyth_product : Info < ' b > ,
77
- pub pyth_price : Info < ' b > ,
71
+ pub pyth_state : Mut < AttestationStatePDA < ' b > > ,
72
+ pub pyth_price : Info < ' b > ,
78
73
79
- pub pyth_product2 : Option < Info < ' b > > ,
80
- pub pyth_price2 : Option < Info < ' b > > ,
74
+ pub pyth_state2 : Option < Mut < AttestationStatePDA < ' b > > > ,
75
+ pub pyth_price2 : Option < Info < ' b > > ,
81
76
82
- pub pyth_product3 : Option < Info < ' b > > ,
83
- pub pyth_price3 : Option < Info < ' b > > ,
77
+ pub pyth_state3 : Option < Mut < AttestationStatePDA < ' b > > > ,
78
+ pub pyth_price3 : Option < Info < ' b > > ,
84
79
85
- pub pyth_product4 : Option < Info < ' b > > ,
86
- pub pyth_price4 : Option < Info < ' b > > ,
80
+ pub pyth_state4 : Option < Mut < AttestationStatePDA < ' b > > > ,
81
+ pub pyth_price4 : Option < Info < ' b > > ,
87
82
88
- pub pyth_product5 : Option < Info < ' b > > ,
89
- pub pyth_price5 : Option < Info < ' b > > ,
83
+ pub pyth_state5 : Option < Mut < AttestationStatePDA < ' b > > > ,
84
+ pub pyth_price5 : Option < Info < ' b > > ,
90
85
91
- // Did you read the comment near `pyth_product `?
92
- // pub pyth_product6 : Option<Info<'b >>,
86
+ // Did you read the comment near `pyth_state `?
87
+ // pub pyth_state6 : Option<Mut<AttestationStatePDA<'b> >>,
93
88
// pub pyth_price6: Option<Info<'b>>,
94
89
95
- // pub pyth_product7 : Option<Info<'b >>,
90
+ // pub pyth_state7 : Option<Mut<AttestationStatePDA<'b> >>,
96
91
// pub pyth_price7: Option<Info<'b>>,
97
92
98
- // pub pyth_product8 : Option<Info<'b >>,
93
+ // pub pyth_state8 : Option<Mut<AttestationStatePDA<'b> >>,
99
94
// pub pyth_price8: Option<Info<'b>>,
100
95
101
- // pub pyth_product9 : Option<Info<'b >>,
96
+ // pub pyth_state9 : Option<Mut<AttestationStatePDA<'b> >>,
102
97
// pub pyth_price9: Option<Info<'b>>,
103
98
104
- // pub pyth_product10 : Option<Info<'b >>,
99
+ // pub pyth_state10 : Option<Mut<AttestationStatePDA<'b> >>,
105
100
// pub pyth_price10: Option<Info<'b>>,
106
101
pub clock : Sysvar < ' b , Clock > ,
107
102
@@ -161,57 +156,55 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
161
156
162
157
163
158
// Make the specified prices iterable
164
- let price_pair_opts = [
165
- Some ( & accs. pyth_product ) ,
166
- Some ( & accs. pyth_price ) ,
167
- accs. pyth_product2 . as_ref ( ) ,
168
- accs. pyth_price2 . as_ref ( ) ,
169
- accs. pyth_product3 . as_ref ( ) ,
170
- accs. pyth_price3 . as_ref ( ) ,
171
- accs. pyth_product4 . as_ref ( ) ,
172
- accs. pyth_price4 . as_ref ( ) ,
173
- accs. pyth_product5 . as_ref ( ) ,
174
- accs. pyth_price5 . as_ref ( ) ,
175
- // Did you read the comment near `pyth_product`?
176
- // accs.pyth_product6.as_ref(),
177
- // accs.pyth_price6.as_ref(),
178
- // accs.pyth_product7.as_ref(),
179
- // accs.pyth_price7.as_ref(),
180
- // accs.pyth_product8.as_ref(),
181
- // accs.pyth_price8.as_ref(),
182
- // accs.pyth_product9.as_ref(),
183
- // accs.pyth_price9.as_ref(),
184
- // accs.pyth_product10.as_ref(),
185
- // accs.pyth_price10.as_ref(),
159
+ let mut price_pair_opts = [
160
+ ( Some ( & mut accs. pyth_state ) , Some ( & accs. pyth_price ) ) ,
161
+ ( accs. pyth_state2 . as_mut ( ) , accs. pyth_price2 . as_ref ( ) ) ,
162
+ ( accs. pyth_state3 . as_mut ( ) , accs. pyth_price3 . as_ref ( ) ) ,
163
+ ( accs. pyth_state4 . as_mut ( ) , accs. pyth_price4 . as_ref ( ) ) ,
164
+ ( accs. pyth_state5 . as_mut ( ) , accs. pyth_price5 . as_ref ( ) ) ,
165
+ // Did you read the comment near `pyth_state`?
166
+ // (accs.pyth_state6.as_mut(), accs.pyth_price6.as_ref()),
167
+ // (accs.pyth_state7.as_mut(), accs.pyth_price7.as_ref()),
168
+ // (accs.pyth_state8.as_mut(), accs.pyth_price8.as_ref()),
169
+ // (accs.pyth_state9.as_mut(), accs.pyth_price9.as_ref()),
170
+ // (accs.pyth_state10.as_mut(), accs.pyth_price10.as_ref()),
186
171
] ;
187
172
188
- let price_pairs: Vec < _ > = price_pair_opts. iter ( ) . filter_map ( |acc| * acc) . collect ( ) ;
173
+ let price_pairs: Vec < ( _ , _ ) > = price_pair_opts
174
+ . iter_mut ( )
175
+ . filter_map ( |pair| match pair {
176
+ // Only use this pair if both accounts are Some
177
+ ( Some ( state) , Some ( price) ) => Some ( ( state, price) ) ,
178
+ _other => None ,
179
+ } )
180
+ . collect ( ) ;
189
181
190
- if price_pairs. len ( ) % 2 != 0 {
191
- trace ! ( & format!(
192
- "Uneven product/price count detected: {}" ,
193
- price_pairs. len( )
194
- ) ) ;
195
- return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
196
- }
197
182
198
- trace ! ( "{} Pyth symbols received" , price_pairs. len( ) / 2 ) ;
183
+ trace ! ( "{} Pyth symbols received" , price_pairs. len( ) ) ;
199
184
200
- // Collect the validated symbols for batch serialization
201
- let mut attestations = Vec :: with_capacity ( price_pairs. len ( ) / 2 ) ;
185
+ // Collect the validated symbols here for batch serialization
186
+ let mut attestations = Vec :: with_capacity ( price_pairs. len ( ) ) ;
202
187
203
- for pair in price_pairs. as_slice ( ) . chunks_exact ( 2 ) {
204
- let product = pair[ 0 ] ;
205
- let price = pair[ 1 ] ;
188
+ for ( state, price) in price_pairs. into_iter ( ) {
189
+ // Pyth must own the price
190
+ if accs. config . pyth_owner != * price. owner {
191
+ trace ! ( & format!(
192
+ "Price {:?}: owner pubkey mismatch (expected pyth_owner {:?}, got unknown price owner {:?})" ,
193
+ price, accs. config. pyth_owner, price. owner
194
+ ) ) ;
195
+ return Err ( SolitaireError :: InvalidOwner ( * price. owner ) ) ;
196
+ }
206
197
207
- if accs. config . pyth_owner != * price. owner || accs. config . pyth_owner != * product. owner {
198
+ // State pubkey must reproduce from the price id
199
+ let state_addr_from_price = AttestationStatePDA :: key ( price. key , ctx. program_id ) ;
200
+ if state_addr_from_price != * state. 0 . 0 . info ( ) . key {
208
201
trace ! ( & format!(
209
- "Pair {:?} - {:?}: pyth_owner pubkey mismatch (expected {:?}, got product owner {:?} and price owner {:?}" ,
210
- product, price,
211
- accs. config. pyth_owner, product. owner, price. owner
212
- ) ) ;
213
- return Err ( SolitaireError :: InvalidOwner ( * accs. pyth_price . owner ) ) ;
202
+ "Price {:?}: pubkey does not produce the passed state account (expected {:?} from seeds, {:?} was passed)" ,
203
+ price. key, state_addr_from_price, state. 0.0 . info( ) . key
204
+ ) ) ;
205
+ return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
214
206
}
207
+
215
208
let attestation_time = accs. clock . unix_timestamp ;
216
209
217
210
let price_data_ref = price. try_borrow_data ( ) ?;
@@ -224,53 +217,63 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
224
217
ProgramError :: InvalidAccountData
225
218
} ) ?;
226
219
227
- // prev_publish_time is picked if the price is not trading
228
- let last_trading_publish_time = match price_struct. agg . status {
220
+ // Retrieve and rotate last_attested_tradind_publish_time
221
+
222
+ // Pick the value to store for the next attestation of this
223
+ // symbol. We use the prev_ value if the symbol is not
224
+ // currently being traded. The oracle marks the last known
225
+ // trading timestamp with it.
226
+ let new_last_attested_trading_publish_time = match price_struct. agg . status {
229
227
PriceStatus :: Trading => price_struct. timestamp ,
230
228
_ => price_struct. prev_timestamp ,
231
229
} ;
232
230
233
- // Take a mut reference to this price's metadata
234
- let state_entry: & mut AttestationState = accs
235
- . attestation_state
236
- . entries
237
- . entry ( * price. key )
238
- . or_insert ( AttestationState {
239
- // Use the same value if no state
240
- // exists for the symbol, the new value _becomes_ the
241
- // last attested trading publish time
242
- last_attested_trading_publish_time : last_trading_publish_time,
243
- } ) ;
231
+ // Retrieve the timestamp saved during the previous
232
+ // attestation. Use the new_* value if no existind state is
233
+ // present on-chain
234
+ let current_last_attested_trading_publish_time = if state. 0 . 0 . is_initialized ( ) {
235
+ // Use the existing on-chain value
236
+ state. 0 . 0 . 1 . last_attested_trading_publish_time
237
+ } else {
238
+ // Fall back to the new value if the state is not initialized
239
+ new_last_attested_trading_publish_time
240
+ } ;
244
241
242
+ // Build an attestatioin struct for this symbol using the just decided current value
245
243
let attestation = PriceAttestation :: from_pyth_price_struct (
246
244
Identifier :: new ( price. key . to_bytes ( ) ) ,
247
245
attestation_time,
248
- state_entry . last_attested_trading_publish_time , // Used as last_attested_publish_time
246
+ current_last_attested_trading_publish_time ,
249
247
price_struct,
250
248
) ;
251
249
252
-
253
- // update last_attested_publish_time with this price's
254
- // publish_time. Yes, it may be redundant for the entry() used
255
- // above in the rare first attestation edge case.
256
- state_entry. last_attested_trading_publish_time = last_trading_publish_time;
257
-
258
- // The following check is crucial against poorly ordered
259
- // account inputs, e.g. [Some(prod1), Some(price1),
260
- // Some(prod2), None, None, Some(price)], interpreted by
261
- // earlier logic as [(prod1, price1), (prod2, price3)].
262
- //
263
- // Failing to verify the product/price relationship could lead
264
- // to mismatched product/price metadata, which would result in
265
- // a false attestation.
266
- if attestation. product_id . to_bytes ( ) != product. key . to_bytes ( ) {
267
- trace ! ( & format!(
268
- "Price's product_id does not match the pased account (points at {:?} instead)" ,
269
- attestation. product_id
270
- ) ) ;
271
- return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
250
+ // Save the new value for the next attestation of this symbol
251
+ state. 0 . 0 . last_attested_trading_publish_time = new_last_attested_trading_publish_time;
252
+
253
+ // handling of last_attested_trading_publish_time ends here
254
+
255
+ if !state. 0 . 0 . is_initialized ( ) {
256
+ // Serialize the state to learn account size for creation
257
+ let state_serialized = state. 0 . 0 . 1 . try_to_vec ( ) ?;
258
+
259
+ let seeds = state. self_bumped_seeds ( price. key , ctx. program_id ) ;
260
+ solitaire:: create_account (
261
+ ctx,
262
+ state. 0 . 0 . info ( ) ,
263
+ accs. payer . key ,
264
+ solitaire:: CreationLamports :: Exempt ,
265
+ state_serialized. len ( ) ,
266
+ ctx. program_id ,
267
+ solitaire:: IsSigned :: SignedWithSeeds ( & [ seeds
268
+ . iter ( )
269
+ . map ( |s| s. as_slice ( ) )
270
+ . collect :: < Vec < _ > > ( )
271
+ . as_slice ( ) ] ) ,
272
+ ) ?;
273
+ trace ! ( "Attestation state init OK" ) ;
272
274
}
273
275
276
+
274
277
attestations. push ( attestation) ;
275
278
}
276
279
@@ -280,51 +283,6 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
280
283
281
284
trace ! ( "Attestations successfully created" ) ;
282
285
283
- // Serialize the state to calculate rent/account size adjustments
284
- let serialized = accs. attestation_state . 1 . try_to_vec ( ) ?;
285
-
286
- if accs. attestation_state . is_initialized ( ) {
287
- accs. attestation_state
288
- . info ( )
289
- . realloc ( serialized. len ( ) , false ) ?;
290
- trace ! ( "Attestation state resize OK" ) ;
291
-
292
- let target_rent = CreationLamports :: Exempt . amount ( serialized. len ( ) ) ;
293
- let current_rent = accs. attestation_state . info ( ) . lamports ( ) ;
294
-
295
- // Adjust rent, but only if there isn't enough
296
- if target_rent > current_rent {
297
- let transfer_amount = target_rent - current_rent;
298
-
299
- let transfer_ix = system_instruction:: transfer (
300
- accs. payer . info ( ) . key ,
301
- accs. attestation_state . info ( ) . key ,
302
- transfer_amount,
303
- ) ;
304
-
305
- invoke ( & transfer_ix, ctx. accounts ) ?;
306
- }
307
-
308
- trace ! ( "Attestation state rent transfer OK" ) ;
309
- } else {
310
- let seeds = accs
311
- . attestation_state
312
- . self_bumped_seeds ( None , ctx. program_id ) ;
313
- solitaire:: create_account (
314
- ctx,
315
- accs. attestation_state . info ( ) ,
316
- accs. payer . key ,
317
- solitaire:: CreationLamports :: Exempt ,
318
- serialized. len ( ) ,
319
- ctx. program_id ,
320
- solitaire:: IsSigned :: SignedWithSeeds ( & [ seeds
321
- . iter ( )
322
- . map ( |s| s. as_slice ( ) )
323
- . collect :: < Vec < _ > > ( )
324
- . as_slice ( ) ] ) ,
325
- ) ?;
326
- trace ! ( "Attestation state init OK" ) ;
327
- }
328
286
let bridge_config = BridgeData :: try_from_slice ( & accs. wh_bridge . try_borrow_mut_data ( ) ?) ?. config ;
329
287
330
288
// Pay wormhole fee
0 commit comments