1+ mod signature;
2+
13use {
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} ,
49} ;
510
6- declare_id ! ( "pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt" ) ;
11+ pub use {
12+ crate :: signature:: { ed25519_program_args, Ed25519SignatureOffsets } ,
13+ pyth_lazer_protocol as protocol,
14+ } ;
715
8- pub mod storage {
9- use anchor_lang:: declare_id;
16+ use solana_program:: pubkey;
1017
11- declare_id ! ( "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL " ) ;
18+ declare_id ! ( "pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt " ) ;
1219
13- #[ test]
14- fn test_storage_id ( ) {
15- use { crate :: STORAGE_SEED , anchor_lang:: prelude:: Pubkey } ;
20+ pub const STORAGE_ID : Pubkey = pubkey ! ( "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL" ) ;
1621
17- assert_eq ! (
18- Pubkey :: find_program_address( & [ STORAGE_SEED ] , & super :: ID ) . 0 ,
19- ID
20- ) ;
21- }
22+ #[ test]
23+ fn test_ids ( ) {
24+ assert_eq ! (
25+ Pubkey :: find_program_address( & [ STORAGE_SEED ] , & ID ) . 0 ,
26+ STORAGE_ID
27+ ) ;
2228}
2329
30+ pub const ANCHOR_DISCRIMINATOR_BYTES : usize = 8 ;
2431pub const MAX_NUM_TRUSTED_SIGNERS : usize = 2 ;
32+ pub const SPACE_FOR_TRUSTED_SIGNERS : usize = 5 ;
33+ pub const EXTRA_SPACE : usize = 100 ;
2534
2635#[ derive( Debug , Clone , Copy , PartialEq , Eq , Default , AnchorSerialize , AnchorDeserialize ) ]
2736pub struct TrustedSignerInfo {
@@ -33,17 +42,41 @@ impl TrustedSignerInfo {
3342 const SERIALIZED_LEN : usize = PUBKEY_BYTES + size_of :: < i64 > ( ) ;
3443}
3544
45+ /// TODO: remove this legacy storage type
46+ #[ derive( AnchorDeserialize ) ]
47+ pub struct StorageV010 {
48+ pub top_authority : Pubkey ,
49+ pub num_trusted_signers : u8 ,
50+ pub trusted_signers : [ TrustedSignerInfo ; MAX_NUM_TRUSTED_SIGNERS ] ,
51+ }
52+
53+ impl StorageV010 {
54+ pub const SERIALIZED_LEN : usize = PUBKEY_BYTES
55+ + size_of :: < u8 > ( )
56+ + TrustedSignerInfo :: SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS ;
57+
58+ pub fn initialized_trusted_signers ( & self ) -> & [ TrustedSignerInfo ] {
59+ & self . trusted_signers [ 0 ..usize:: from ( self . num_trusted_signers ) ]
60+ }
61+ }
62+
3663#[ account]
3764pub struct Storage {
3865 pub top_authority : Pubkey ,
66+ pub treasury : Pubkey ,
67+ pub single_update_fee_in_lamports : u64 ,
3968 pub num_trusted_signers : u8 ,
40- pub trusted_signers : [ TrustedSignerInfo ; MAX_NUM_TRUSTED_SIGNERS ] ,
69+ pub trusted_signers : [ TrustedSignerInfo ; SPACE_FOR_TRUSTED_SIGNERS ] ,
70+ pub _extra_space : [ u8 ; EXTRA_SPACE ] ,
4171}
4272
4373impl Storage {
4474 const SERIALIZED_LEN : usize = PUBKEY_BYTES
75+ + PUBKEY_BYTES
76+ + size_of :: < u64 > ( )
4577 + size_of :: < u8 > ( )
46- + TrustedSignerInfo :: SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS ;
78+ + TrustedSignerInfo :: SERIALIZED_LEN * SPACE_FOR_TRUSTED_SIGNERS
79+ + EXTRA_SPACE ;
4780
4881 pub fn initialized_trusted_signers ( & self ) -> & [ TrustedSignerInfo ] {
4982 & self . trusted_signers [ 0 ..usize:: from ( self . num_trusted_signers ) ]
@@ -56,8 +89,48 @@ pub const STORAGE_SEED: &[u8] = b"storage";
5689pub mod pyth_lazer_solana_contract {
5790 use super :: * ;
5891
59- pub fn initialize ( ctx : Context < Initialize > , top_authority : Pubkey ) -> Result < ( ) > {
92+ pub fn initialize (
93+ ctx : Context < Initialize > ,
94+ top_authority : Pubkey ,
95+ treasury : Pubkey ,
96+ ) -> Result < ( ) > {
6097 ctx. accounts . storage . top_authority = top_authority;
98+ ctx. accounts . storage . treasury = treasury;
99+ ctx. accounts . storage . single_update_fee_in_lamports = 1 ;
100+ Ok ( ( ) )
101+ }
102+
103+ pub fn migrate_from_0_1_0 ( ctx : Context < MigrateFrom010 > , treasury : Pubkey ) -> Result < ( ) > {
104+ let old_data = ctx. accounts . storage . data . borrow ( ) ;
105+ if old_data[ 0 ..ANCHOR_DISCRIMINATOR_BYTES ] != Storage :: DISCRIMINATOR {
106+ return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
107+ }
108+ let old_storage = StorageV010 :: deserialize ( & mut & old_data[ ANCHOR_DISCRIMINATOR_BYTES ..] ) ?;
109+ if old_storage. top_authority != ctx. accounts . top_authority . key ( ) {
110+ return Err ( ProgramError :: MissingRequiredSignature . into ( ) ) ;
111+ }
112+ drop ( old_data) ;
113+
114+ let space = ANCHOR_DISCRIMINATOR_BYTES + Storage :: SERIALIZED_LEN ;
115+ ctx. accounts . storage . realloc ( space, false ) ?;
116+ let min_lamports = Rent :: get ( ) ?. minimum_balance ( space) ;
117+ if ctx. accounts . storage . lamports ( ) < min_lamports {
118+ return Err ( ProgramError :: AccountNotRentExempt . into ( ) ) ;
119+ }
120+
121+ let mut new_storage = Storage {
122+ top_authority : old_storage. top_authority ,
123+ treasury,
124+ single_update_fee_in_lamports : 1 ,
125+ num_trusted_signers : old_storage. num_trusted_signers ,
126+ trusted_signers : Default :: default ( ) ,
127+ _extra_space : [ 0 ; EXTRA_SPACE ] ,
128+ } ;
129+ new_storage. trusted_signers [ ..old_storage. trusted_signers . len ( ) ]
130+ . copy_from_slice ( & old_storage. trusted_signers ) ;
131+ new_storage. try_serialize ( & mut Cursor :: new (
132+ & mut * * ctx. accounts . storage . data . borrow_mut ( ) ,
133+ ) ) ?;
61134 Ok ( ( ) )
62135 }
63136
@@ -66,6 +139,9 @@ pub mod pyth_lazer_solana_contract {
66139 if num_trusted_signers > ctx. accounts . storage . trusted_signers . len ( ) {
67140 return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
68141 }
142+ if num_trusted_signers > MAX_NUM_TRUSTED_SIGNERS {
143+ return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
144+ }
69145 let mut trusted_signers =
70146 ctx. accounts . storage . trusted_signers [ ..num_trusted_signers] . to_vec ( ) ;
71147 if expires_at == 0 {
@@ -92,6 +168,9 @@ pub mod pyth_lazer_solana_contract {
92168 if trusted_signers. len ( ) > ctx. accounts . storage . trusted_signers . len ( ) {
93169 return Err ( ProgramError :: AccountDataTooSmall . into ( ) ) ;
94170 }
171+ if trusted_signers. len ( ) > MAX_NUM_TRUSTED_SIGNERS {
172+ return Err ( ProgramError :: InvalidInstructionData . into ( ) ) ;
173+ }
95174
96175 ctx. accounts . storage . trusted_signers = Default :: default ( ) ;
97176 ctx. accounts . storage . trusted_signers [ ..trusted_signers. len ( ) ]
@@ -102,6 +181,47 @@ pub mod pyth_lazer_solana_contract {
102181 . expect ( "num signers overflow" ) ;
103182 Ok ( ( ) )
104183 }
184+
185+ /// Verifies a ed25519 signature on Solana by checking that the transaction contains
186+ /// a correct call to the built-in `ed25519_program`.
187+ ///
188+ /// - `message_data` is the signed message that is being verified.
189+ /// - `ed25519_instruction_index` is the index of the `ed25519_program` instruction
190+ /// within the transaction. This instruction must precede the current instruction.
191+ /// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
192+ /// - `message_offset` is the offset of the signed message within the
193+ /// input data for the current instruction.
194+ pub fn verify_message (
195+ ctx : Context < VerifyMessage > ,
196+ message_data : Vec < u8 > ,
197+ ed25519_instruction_index : u16 ,
198+ signature_index : u8 ,
199+ message_offset : u16 ,
200+ ) -> Result < VerifiedMessage > {
201+ system_program:: transfer (
202+ CpiContext :: new (
203+ ctx. accounts . system_program . to_account_info ( ) ,
204+ system_program:: Transfer {
205+ from : ctx. accounts . payer . to_account_info ( ) ,
206+ to : ctx. accounts . treasury . to_account_info ( ) ,
207+ } ,
208+ ) ,
209+ ctx. accounts . storage . single_update_fee_in_lamports ,
210+ ) ?;
211+
212+ signature:: verify_message (
213+ & ctx. accounts . storage ,
214+ & ctx. accounts . instructions_sysvar ,
215+ & message_data,
216+ ed25519_instruction_index,
217+ signature_index,
218+ message_offset,
219+ )
220+ . map_err ( |err| {
221+ msg ! ( "signature verification error: {:?}" , err) ;
222+ err. into ( )
223+ } )
224+ }
105225}
106226
107227#[ derive( Accounts ) ]
@@ -111,14 +231,27 @@ pub struct Initialize<'info> {
111231 #[ account(
112232 init,
113233 payer = payer,
114- space = 8 + Storage :: SERIALIZED_LEN ,
234+ space = ANCHOR_DISCRIMINATOR_BYTES + Storage :: SERIALIZED_LEN ,
115235 seeds = [ STORAGE_SEED ] ,
116236 bump,
117237 ) ]
118238 pub storage : Account < ' info , Storage > ,
119239 pub system_program : Program < ' info , System > ,
120240}
121241
242+ #[ derive( Accounts ) ]
243+ pub struct MigrateFrom010 < ' info > {
244+ pub top_authority : Signer < ' info > ,
245+ #[ account(
246+ mut ,
247+ seeds = [ STORAGE_SEED ] ,
248+ bump,
249+ ) ]
250+ /// CHECK: top_authority in storage must match top_authority account.
251+ pub storage : AccountInfo < ' info > ,
252+ pub system_program : Program < ' info , System > ,
253+ }
254+
122255#[ derive( Accounts ) ]
123256pub struct Update < ' info > {
124257 pub top_authority : Signer < ' info > ,
@@ -130,3 +263,22 @@ pub struct Update<'info> {
130263 ) ]
131264 pub storage : Account < ' info , Storage > ,
132265}
266+
267+ #[ derive( Accounts ) ]
268+ pub struct VerifyMessage < ' info > {
269+ #[ account( mut ) ]
270+ pub payer : Signer < ' info > ,
271+ #[ account(
272+ seeds = [ STORAGE_SEED ] ,
273+ bump,
274+ has_one = treasury
275+ ) ]
276+ pub storage : Account < ' info , Storage > ,
277+ /// CHECK: this account doesn't need additional constraints.
278+ pub treasury : AccountInfo < ' info > ,
279+ pub system_program : Program < ' info , System > ,
280+ /// CHECK: account ID is checked in Solana SDK during calls
281+ /// (e.g. in `sysvar::instructions::load_instruction_at_checked`).
282+ /// This account is not usable with anchor's `Program` account type because it's not executable.
283+ pub instructions_sysvar : AccountInfo < ' info > ,
284+ }
0 commit comments