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" ) ;
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+ } ;
1015
11- pub const ID : Pubkey = pubkey ! ( "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL " ) ;
16+ declare_id ! ( "pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt " ) ;
1217
13- #[ test]
14- fn test_storage_id ( ) {
15- use { crate :: STORAGE_SEED , anchor_lang:: prelude:: Pubkey } ;
18+ pub const STORAGE_ID : Pubkey = pubkey ! ( "3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL" ) ;
1619
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+ ) ;
2226}
2327
28+ pub const ANCHOR_DISCRIMINATOR_BYTES : usize = 8 ;
2429pub const MAX_NUM_TRUSTED_SIGNERS : usize = 2 ;
30+ pub const SPACE_FOR_TRUSTED_SIGNERS : usize = 5 ;
31+ pub const EXTRA_SPACE : usize = 100 ;
2532
2633#[ derive( Debug , Clone , Copy , PartialEq , Eq , Default , AnchorSerialize , AnchorDeserialize ) ]
2734pub struct TrustedSignerInfo {
@@ -33,17 +40,41 @@ impl TrustedSignerInfo {
3340 const SERIALIZED_LEN : usize = PUBKEY_BYTES + size_of :: < i64 > ( ) ;
3441}
3542
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+
3661#[ account]
3762pub struct Storage {
3863 pub top_authority : Pubkey ,
64+ pub treasury : Pubkey ,
65+ pub single_update_fee_in_lamports : u64 ,
3966 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 ] ,
4169}
4270
4371impl Storage {
4472 const SERIALIZED_LEN : usize = PUBKEY_BYTES
73+ + PUBKEY_BYTES
74+ + size_of :: < u64 > ( )
4575 + size_of :: < u8 > ( )
46- + TrustedSignerInfo :: SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS ;
76+ + TrustedSignerInfo :: SERIALIZED_LEN * SPACE_FOR_TRUSTED_SIGNERS
77+ + EXTRA_SPACE ;
4778
4879 pub fn initialized_trusted_signers ( & self ) -> & [ TrustedSignerInfo ] {
4980 & self . trusted_signers [ 0 ..usize:: from ( self . num_trusted_signers ) ]
@@ -56,8 +87,48 @@ pub const STORAGE_SEED: &[u8] = b"storage";
5687pub mod pyth_lazer_solana_contract {
5788 use super :: * ;
5889
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 < ( ) > {
6095 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+ ) ) ?;
61132 Ok ( ( ) )
62133 }
63134
@@ -66,6 +137,9 @@ pub mod pyth_lazer_solana_contract {
66137 if num_trusted_signers > ctx. accounts . storage . trusted_signers . len ( ) {
67138 return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
68139 }
140+ if num_trusted_signers > MAX_NUM_TRUSTED_SIGNERS {
141+ return Err ( ProgramError :: InvalidAccountData . into ( ) ) ;
142+ }
69143 let mut trusted_signers =
70144 ctx. accounts . storage . trusted_signers [ ..num_trusted_signers] . to_vec ( ) ;
71145 if expires_at == 0 {
@@ -92,6 +166,9 @@ pub mod pyth_lazer_solana_contract {
92166 if trusted_signers. len ( ) > ctx. accounts . storage . trusted_signers . len ( ) {
93167 return Err ( ProgramError :: AccountDataTooSmall . into ( ) ) ;
94168 }
169+ if trusted_signers. len ( ) > MAX_NUM_TRUSTED_SIGNERS {
170+ return Err ( ProgramError :: InvalidInstructionData . into ( ) ) ;
171+ }
95172
96173 ctx. accounts . storage . trusted_signers = Default :: default ( ) ;
97174 ctx. accounts . storage . trusted_signers [ ..trusted_signers. len ( ) ]
@@ -102,6 +179,47 @@ pub mod pyth_lazer_solana_contract {
102179 . expect ( "num signers overflow" ) ;
103180 Ok ( ( ) )
104181 }
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+ }
105223}
106224
107225#[ derive( Accounts ) ]
@@ -111,14 +229,27 @@ pub struct Initialize<'info> {
111229 #[ account(
112230 init,
113231 payer = payer,
114- space = 8 + Storage :: SERIALIZED_LEN ,
232+ space = ANCHOR_DISCRIMINATOR_BYTES + Storage :: SERIALIZED_LEN ,
115233 seeds = [ STORAGE_SEED ] ,
116234 bump,
117235 ) ]
118236 pub storage : Account < ' info , Storage > ,
119237 pub system_program : Program < ' info , System > ,
120238}
121239
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+
122253#[ derive( Accounts ) ]
123254pub struct Update < ' info > {
124255 pub top_authority : Signer < ' info > ,
@@ -130,3 +261,22 @@ pub struct Update<'info> {
130261 ) ]
131262 pub storage : Account < ' info , Storage > ,
132263}
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