1
+ mod signature;
2
+
1
3
use {
2
- anchor_lang:: { prelude:: * , solana_program:: pubkey:: PUBKEY_BYTES } ,
3
- std:: mem:: size_of,
4
+ crate :: signature:: VerifiedMessage ,
5
+ anchor_lang:: {
6
+ prelude:: * , solana_program:: pubkey:: PUBKEY_BYTES , system_program, Discriminator ,
7
+ } ,
8
+ std:: { io:: Cursor , mem:: size_of} ,
4
9
} ;
5
10
6
- declare_id ! ( "pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt" ) ;
7
-
8
- pub mod storage {
9
- use anchor_lang :: prelude :: { pubkey , Pubkey } ;
11
+ pub use {
12
+ crate :: signature :: { ed25519_program_args , Ed25519SignatureOffsets } ,
13
+ pyth_lazer_protocol as protocol ,
14
+ } ;
10
15
11
- pub const ID : Pubkey = pubkey ! ( "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL " ) ;
16
+ declare_id ! ( "pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt " ) ;
12
17
13
- #[ test]
14
- fn test_storage_id ( ) {
15
- use { crate :: STORAGE_SEED , anchor_lang:: prelude:: Pubkey } ;
18
+ pub const STORAGE_ID : Pubkey = pubkey ! ( "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL" ) ;
16
19
17
- assert_eq ! (
18
- Pubkey :: find_program_address( & [ STORAGE_SEED ] , & super :: ID ) . 0 ,
19
- ID
20
- ) ;
21
- }
20
+ #[ test]
21
+ fn test_ids ( ) {
22
+ assert_eq ! (
23
+ Pubkey :: find_program_address( & [ STORAGE_SEED ] , & ID ) . 0 ,
24
+ STORAGE_ID
25
+ ) ;
22
26
}
23
27
28
+ pub const ANCHOR_DISCRIMINATOR_BYTES : usize = 8 ;
24
29
pub const MAX_NUM_TRUSTED_SIGNERS : usize = 2 ;
30
+ pub const SPACE_FOR_TRUSTED_SIGNERS : usize = 5 ;
31
+ pub const EXTRA_SPACE : usize = 100 ;
25
32
26
33
#[ derive( Debug , Clone , Copy , PartialEq , Eq , Default , AnchorSerialize , AnchorDeserialize ) ]
27
34
pub struct TrustedSignerInfo {
@@ -33,17 +40,41 @@ impl TrustedSignerInfo {
33
40
const SERIALIZED_LEN : usize = PUBKEY_BYTES + size_of :: < i64 > ( ) ;
34
41
}
35
42
43
+ /// TODO: remove this legacy storage type
44
+ #[ derive( AnchorDeserialize ) ]
45
+ pub struct StorageV010 {
46
+ pub top_authority : Pubkey ,
47
+ pub num_trusted_signers : u8 ,
48
+ pub trusted_signers : [ TrustedSignerInfo ; MAX_NUM_TRUSTED_SIGNERS ] ,
49
+ }
50
+
51
+ impl StorageV010 {
52
+ pub const SERIALIZED_LEN : usize = PUBKEY_BYTES
53
+ + size_of :: < u8 > ( )
54
+ + TrustedSignerInfo :: SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS ;
55
+
56
+ pub fn initialized_trusted_signers ( & self ) -> & [ TrustedSignerInfo ] {
57
+ & self . trusted_signers [ 0 ..usize:: from ( self . num_trusted_signers ) ]
58
+ }
59
+ }
60
+
36
61
#[ account]
37
62
pub struct Storage {
38
63
pub top_authority : Pubkey ,
64
+ pub treasury : Pubkey ,
65
+ pub single_update_fee_in_lamports : u64 ,
39
66
pub num_trusted_signers : u8 ,
40
- pub trusted_signers : [ TrustedSignerInfo ; MAX_NUM_TRUSTED_SIGNERS ] ,
67
+ pub trusted_signers : [ TrustedSignerInfo ; SPACE_FOR_TRUSTED_SIGNERS ] ,
68
+ pub _extra_space : [ u8 ; EXTRA_SPACE ] ,
41
69
}
42
70
43
71
impl Storage {
44
72
const SERIALIZED_LEN : usize = PUBKEY_BYTES
73
+ + PUBKEY_BYTES
74
+ + size_of :: < u64 > ( )
45
75
+ size_of :: < u8 > ( )
46
- + TrustedSignerInfo :: SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS ;
76
+ + TrustedSignerInfo :: SERIALIZED_LEN * SPACE_FOR_TRUSTED_SIGNERS
77
+ + EXTRA_SPACE ;
47
78
48
79
pub fn initialized_trusted_signers ( & self ) -> & [ TrustedSignerInfo ] {
49
80
& self . trusted_signers [ 0 ..usize:: from ( self . num_trusted_signers ) ]
@@ -56,8 +87,48 @@ pub const STORAGE_SEED: &[u8] = b"storage";
56
87
pub mod pyth_lazer_solana_contract {
57
88
use super :: * ;
58
89
59
- pub fn initialize ( ctx : Context < Initialize > , top_authority : Pubkey ) -> Result < ( ) > {
90
+ pub fn initialize (
91
+ ctx : Context < Initialize > ,
92
+ top_authority : Pubkey ,
93
+ treasury : Pubkey ,
94
+ ) -> Result < ( ) > {
60
95
ctx. accounts . storage . top_authority = top_authority;
96
+ ctx. accounts . storage . treasury = treasury;
97
+ ctx. accounts . storage . single_update_fee_in_lamports = 1 ;
98
+ Ok ( ( ) )
99
+ }
100
+
101
+ pub fn migrate_from_0_1_0 ( ctx : Context < MigrateFrom010 > , treasury : Pubkey ) -> Result < ( ) > {
102
+ let old_data = ctx. accounts . storage . data . borrow ( ) ;
103
+ if old_data[ 0 ..ANCHOR_DISCRIMINATOR_BYTES ] != Storage :: DISCRIMINATOR {
104
+ return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
105
+ }
106
+ let old_storage = StorageV010 :: deserialize ( & mut & old_data[ ANCHOR_DISCRIMINATOR_BYTES ..] ) ?;
107
+ if old_storage. top_authority != ctx. accounts . top_authority . key ( ) {
108
+ return Err ( ProgramError :: MissingRequiredSignature . into ( ) ) ;
109
+ }
110
+ drop ( old_data) ;
111
+
112
+ let space = ANCHOR_DISCRIMINATOR_BYTES + Storage :: SERIALIZED_LEN ;
113
+ ctx. accounts . storage . realloc ( space, false ) ?;
114
+ let min_lamports = Rent :: get ( ) ?. minimum_balance ( space) ;
115
+ if ctx. accounts . storage . lamports ( ) < min_lamports {
116
+ return Err ( ProgramError :: AccountNotRentExempt . into ( ) ) ;
117
+ }
118
+
119
+ let mut new_storage = Storage {
120
+ top_authority : old_storage. top_authority ,
121
+ treasury,
122
+ single_update_fee_in_lamports : 1 ,
123
+ num_trusted_signers : old_storage. num_trusted_signers ,
124
+ trusted_signers : Default :: default ( ) ,
125
+ _extra_space : [ 0 ; EXTRA_SPACE ] ,
126
+ } ;
127
+ new_storage. trusted_signers [ ..old_storage. trusted_signers . len ( ) ]
128
+ . copy_from_slice ( & old_storage. trusted_signers ) ;
129
+ new_storage. try_serialize ( & mut Cursor :: new (
130
+ & mut * * ctx. accounts . storage . data . borrow_mut ( ) ,
131
+ ) ) ?;
61
132
Ok ( ( ) )
62
133
}
63
134
@@ -66,6 +137,9 @@ pub mod pyth_lazer_solana_contract {
66
137
if num_trusted_signers > ctx. accounts . storage . trusted_signers . len ( ) {
67
138
return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
68
139
}
140
+ if num_trusted_signers > MAX_NUM_TRUSTED_SIGNERS {
141
+ return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
142
+ }
69
143
let mut trusted_signers =
70
144
ctx. accounts . storage . trusted_signers [ ..num_trusted_signers] . to_vec ( ) ;
71
145
if expires_at == 0 {
@@ -92,6 +166,9 @@ pub mod pyth_lazer_solana_contract {
92
166
if trusted_signers. len ( ) > ctx. accounts . storage . trusted_signers . len ( ) {
93
167
return Err ( ProgramError :: AccountDataTooSmall . into ( ) ) ;
94
168
}
169
+ if trusted_signers. len ( ) > MAX_NUM_TRUSTED_SIGNERS {
170
+ return Err ( ProgramError :: InvalidInstructionData . into ( ) ) ;
171
+ }
95
172
96
173
ctx. accounts . storage . trusted_signers = Default :: default ( ) ;
97
174
ctx. accounts . storage . trusted_signers [ ..trusted_signers. len ( ) ]
@@ -102,6 +179,47 @@ pub mod pyth_lazer_solana_contract {
102
179
. expect ( "num signers overflow" ) ;
103
180
Ok ( ( ) )
104
181
}
182
+
183
+ /// Verifies a ed25519 signature on Solana by checking that the transaction contains
184
+ /// a correct call to the built-in `ed25519_program`.
185
+ ///
186
+ /// - `message_data` is the signed message that is being verified.
187
+ /// - `ed25519_instruction_index` is the index of the `ed25519_program` instruction
188
+ /// within the transaction. This instruction must precede the current instruction.
189
+ /// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
190
+ /// - `message_offset` is the offset of the signed message within the
191
+ /// input data for the current instruction.
192
+ pub fn verify_message (
193
+ ctx : Context < VerifyMessage > ,
194
+ message_data : Vec < u8 > ,
195
+ ed25519_instruction_index : u16 ,
196
+ signature_index : u8 ,
197
+ message_offset : u16 ,
198
+ ) -> Result < VerifiedMessage > {
199
+ system_program:: transfer (
200
+ CpiContext :: new (
201
+ ctx. accounts . system_program . to_account_info ( ) ,
202
+ system_program:: Transfer {
203
+ from : ctx. accounts . payer . to_account_info ( ) ,
204
+ to : ctx. accounts . treasury . to_account_info ( ) ,
205
+ } ,
206
+ ) ,
207
+ ctx. accounts . storage . single_update_fee_in_lamports ,
208
+ ) ?;
209
+
210
+ signature:: verify_message (
211
+ & ctx. accounts . storage ,
212
+ & ctx. accounts . instructions_sysvar ,
213
+ & message_data,
214
+ ed25519_instruction_index,
215
+ signature_index,
216
+ message_offset,
217
+ )
218
+ . map_err ( |err| {
219
+ msg ! ( "signature verification error: {:?}" , err) ;
220
+ err. into ( )
221
+ } )
222
+ }
105
223
}
106
224
107
225
#[ derive( Accounts ) ]
@@ -111,14 +229,27 @@ pub struct Initialize<'info> {
111
229
#[ account(
112
230
init,
113
231
payer = payer,
114
- space = 8 + Storage :: SERIALIZED_LEN ,
232
+ space = ANCHOR_DISCRIMINATOR_BYTES + Storage :: SERIALIZED_LEN ,
115
233
seeds = [ STORAGE_SEED ] ,
116
234
bump,
117
235
) ]
118
236
pub storage : Account < ' info , Storage > ,
119
237
pub system_program : Program < ' info , System > ,
120
238
}
121
239
240
+ #[ derive( Accounts ) ]
241
+ pub struct MigrateFrom010 < ' info > {
242
+ pub top_authority : Signer < ' info > ,
243
+ #[ account(
244
+ mut ,
245
+ seeds = [ STORAGE_SEED ] ,
246
+ bump,
247
+ ) ]
248
+ /// CHECK: top_authority in storage must match top_authority account.
249
+ pub storage : AccountInfo < ' info > ,
250
+ pub system_program : Program < ' info , System > ,
251
+ }
252
+
122
253
#[ derive( Accounts ) ]
123
254
pub struct Update < ' info > {
124
255
pub top_authority : Signer < ' info > ,
@@ -130,3 +261,22 @@ pub struct Update<'info> {
130
261
) ]
131
262
pub storage : Account < ' info , Storage > ,
132
263
}
264
+
265
+ #[ derive( Accounts ) ]
266
+ pub struct VerifyMessage < ' info > {
267
+ #[ account( mut ) ]
268
+ pub payer : Signer < ' info > ,
269
+ #[ account(
270
+ seeds = [ STORAGE_SEED ] ,
271
+ bump,
272
+ has_one = treasury
273
+ ) ]
274
+ pub storage : Account < ' info , Storage > ,
275
+ /// CHECK: this account doesn't need additional constraints.
276
+ pub treasury : AccountInfo < ' info > ,
277
+ pub system_program : Program < ' info , System > ,
278
+ /// CHECK: account ID is checked in Solana SDK during calls
279
+ /// (e.g. in `sysvar::instructions::load_instruction_at_checked`).
280
+ /// This account is not usable with anchor's `Program` account type because it's not executable.
281
+ pub instructions_sysvar : AccountInfo < ' info > ,
282
+ }
0 commit comments