Skip to content

Commit 1f8d9d4

Browse files
committed
update lazer
1 parent 50ecf03 commit 1f8d9d4

File tree

9 files changed

+694
-23
lines changed

9 files changed

+694
-23
lines changed

lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
[package]
22
name = "pyth-lazer-solana-contract"
3-
version = "0.1.0"
4-
description = "Created with Anchor"
3+
version = "0.2.0"
54
edition = "2021"
5+
description = "Pyth Lazer Solana contract and SDK."
6+
license = "Apache-2.0"
7+
repository = "https://github.com/pyth-network/pyth-crosschain"
68

79
[lib]
810
crate-type = ["cdylib", "lib"]
@@ -17,4 +19,16 @@ no-log-ix-name = []
1719
idl-build = ["anchor-lang/idl-build"]
1820

1921
[dependencies]
22+
pyth-lazer-protocol = { version = "0.1.2", path = "../../../../sdk/rust/protocol" }
23+
2024
anchor-lang = "0.29.0"
25+
bytemuck = "1.4.0"
26+
byteorder = "1.4.3"
27+
thiserror = "1.0"
28+
solana-program = "1.16"
29+
30+
[dev-dependencies]
31+
hex = "0.4.3"
32+
solana-program-test = "1.18.26"
33+
solana-sdk = "1.18.26"
34+
tokio = { version = "1.40.0", features = ["full"] }

lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs

Lines changed: 170 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,36 @@
1+
mod signature;
2+
13
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},
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;
2431
pub 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)]
2736
pub 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]
3764
pub 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

4373
impl 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";
5689
pub 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)]
123256
pub 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

Comments
 (0)