Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5,900 changes: 5,225 additions & 675 deletions lazer/Cargo.lock

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion lazer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ resolver = "2"
members = [
"sdk/rust/protocol",
"contracts/solana/programs/pyth-lazer-solana-contract",
"sdk/solana",
]

# TODO: only for solana programs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "pyth-lazer-solana-contract"
version = "0.1.0"
edition = "2021"
description = "Pyth Lazer Solana contract."
description = "Pyth Lazer Solana contract and SDK."
license = "Apache-2.0"
repository = "https://github.com/pyth-network/pyth-crosschain"

Expand All @@ -19,4 +19,15 @@ no-log-ix-name = []
idl-build = ["anchor-lang/idl-build"]

[dependencies]
pyth-lazer-protocol = { version = "0.1.0", path = "../../../../sdk/rust/protocol" }

anchor-lang = "0.30.1"
bytemuck = "1.20.0"
byteorder = "1.5.0"
thiserror = "2.0.3"

[dev-dependencies]
hex = "0.4.3"
solana-program-test = "1.18.26"
solana-sdk = "1.18.26"
tokio = { version = "1.40.0", features = ["full"] }
190 changes: 169 additions & 21 deletions lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
mod signature;

use {
anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES},
crate::signature::VerifiedMessage,
anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES, system_program},
std::io::Cursor,
std::mem::size_of,
};

declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");

pub mod storage {
use anchor_lang::prelude::{pubkey, Pubkey};
pub use {
crate::signature::{ed25519_program_args, Ed25519SignatureOffsets},
pyth_lazer_protocol as protocol,
};

pub const ID: Pubkey = pubkey!("3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL");
declare_id!("pytd2yyk641x7ak7mkaasSJVXh6YYZnC7wTmtgAyxPt");

#[test]
fn test_storage_id() {
use {crate::STORAGE_SEED, anchor_lang::prelude::Pubkey};
pub const STORAGE_ID: Pubkey = pubkey!("3rdJbqfnagQ4yx9HXJViD4zc4xpiSqmFsKpPuSCQVyQL");

assert_eq!(
Pubkey::find_program_address(&[STORAGE_SEED], &super::ID).0,
ID
);
}
#[test]
fn test_ids() {
assert_eq!(
Pubkey::find_program_address(&[STORAGE_SEED], &ID).0,
STORAGE_ID
);
}

pub const MAX_NUM_TRUSTED_SIGNERS: usize = 2;
Expand All @@ -34,13 +37,13 @@ impl TrustedSignerInfo {
}

#[account]
pub struct Storage {
pub struct StorageV1 {
pub top_authority: Pubkey,
pub num_trusted_signers: u8,
pub trusted_signers: [TrustedSignerInfo; MAX_NUM_TRUSTED_SIGNERS],
}

impl Storage {
impl StorageV1 {
const SERIALIZED_LEN: usize = PUBKEY_BYTES
+ size_of::<u8>()
+ TrustedSignerInfo::SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS;
Expand All @@ -49,15 +52,72 @@ impl Storage {
&self.trusted_signers[0..usize::from(self.num_trusted_signers)]
}
}
#[account]
pub struct StorageV2 {
pub top_authority: Pubkey,
pub treasury: Pubkey,
pub single_update_fee_in_lamports: u64,
pub num_trusted_signers: u8,
pub trusted_signers: [TrustedSignerInfo; MAX_NUM_TRUSTED_SIGNERS],
}

impl StorageV2 {
const SERIALIZED_LEN: usize = PUBKEY_BYTES
+ PUBKEY_BYTES
+ size_of::<u64>()
+ size_of::<u8>()
+ TrustedSignerInfo::SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS;

pub fn initialized_trusted_signers(&self) -> &[TrustedSignerInfo] {
&self.trusted_signers[0..usize::from(self.num_trusted_signers)]
}
}

pub const STORAGE_SEED: &[u8] = b"storage";

#[program]
pub mod pyth_lazer_solana_contract {
use super::*;

pub fn initialize(ctx: Context<Initialize>, top_authority: Pubkey) -> Result<()> {
pub fn initialize_v1(ctx: Context<InitializeV1>, top_authority: Pubkey) -> Result<()> {
ctx.accounts.storage.top_authority = top_authority;
Ok(())
}

pub fn initialize_v2(
ctx: Context<InitializeV2>,
top_authority: Pubkey,
treasury: Pubkey,
) -> Result<()> {
ctx.accounts.storage.top_authority = top_authority;
ctx.accounts.storage.treasury = treasury;
ctx.accounts.storage.single_update_fee_in_lamports = 1;
Ok(())
}

pub fn migrate_to_storage_v2(ctx: Context<MigrateToStorageV2>, treasury: Pubkey) -> Result<()> {
let old_storage = StorageV1::try_deserialize(&mut &**ctx.accounts.storage.data.borrow())?;
if old_storage.top_authority != ctx.accounts.top_authority.key() {
return Err(ProgramError::MissingRequiredSignature.into());
}

let space = 8 + StorageV2::SERIALIZED_LEN;
ctx.accounts.storage.realloc(space, false)?;
let min_lamports = Rent::get()?.minimum_balance(space);
if ctx.accounts.storage.lamports() < min_lamports {
return Err(ProgramError::AccountNotRentExempt.into());
}

let new_storage = StorageV2 {
top_authority: old_storage.top_authority,
treasury,
single_update_fee_in_lamports: 1,
num_trusted_signers: old_storage.num_trusted_signers,
trusted_signers: old_storage.trusted_signers,
};
new_storage.try_serialize(&mut Cursor::new(
&mut **ctx.accounts.storage.data.borrow_mut(),
))?;
Ok(())
}

Expand Down Expand Up @@ -102,20 +162,89 @@ pub mod pyth_lazer_solana_contract {
.expect("num signers overflow");
Ok(())
}

/// Verifies a ed25519 signature on Solana by checking that the transaction contains
/// a correct call to the built-in `ed25519_program`.
///
/// - `message_data` is the signed message that is being verified.
/// - `ed25519_instruction_index` is the index of the `ed25519_program` instruction
/// within the transaction. This instruction must precede the current instruction.
/// - `signature_index` is the index of the signature within the inputs to the `ed25519_program`.
/// - `message_offset` is the offset of the signed message within the
/// input data for the current instruction.
pub fn verify_message(
ctx: Context<VerifyMessage>,
message_data: Vec<u8>,
ed25519_instruction_index: u16,
signature_index: u8,
message_offset: u16,
) -> Result<VerifiedMessage> {
system_program::transfer(
CpiContext::new(
ctx.accounts.system_program.to_account_info(),
system_program::Transfer {
from: ctx.accounts.payer.to_account_info(),
to: ctx.accounts.treasury.to_account_info(),
},
),
ctx.accounts.storage.single_update_fee_in_lamports,
)?;

signature::verify_message(
&ctx.accounts.storage,
&ctx.accounts.instructions_sysvar,
&message_data,
ed25519_instruction_index,
signature_index,
message_offset,
)
.map_err(|err| {
msg!("signature verification error: {:?}", err);
err.into()
})
}
}

#[derive(Accounts)]
pub struct Initialize<'info> {
pub struct InitializeV1<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + Storage::SERIALIZED_LEN,
space = 8 + StorageV1::SERIALIZED_LEN,
seeds = [STORAGE_SEED],
bump,
)]
pub storage: Account<'info, Storage>,
pub storage: Account<'info, StorageV1>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct MigrateToStorageV2<'info> {
pub top_authority: Signer<'info>,
#[account(
mut,
seeds = [STORAGE_SEED],
bump,
)]
/// CHECK: top_authority in storage must match top_authority account.
pub storage: AccountInfo<'info>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct InitializeV2<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
init,
payer = payer,
space = 8 + StorageV2::SERIALIZED_LEN,
seeds = [STORAGE_SEED],
bump,
)]
pub storage: Account<'info, StorageV2>,
pub system_program: Program<'info, System>,
}

Expand All @@ -128,5 +257,24 @@ pub struct Update<'info> {
bump,
has_one = top_authority,
)]
pub storage: Account<'info, Storage>,
pub storage: Account<'info, StorageV2>,
}

#[derive(Accounts)]
pub struct VerifyMessage<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
seeds = [STORAGE_SEED],
bump,
has_one = treasury
)]
pub storage: Account<'info, StorageV2>,
/// CHECK: this account doesn't need additional constraints.
pub treasury: AccountInfo<'info>,
pub system_program: Program<'info, System>,
/// CHECK: account ID is checked in Solana SDK during calls
/// (e.g. in `sysvar::instructions::load_instruction_at_checked`).
/// This account is not usable with anchor's `Program` account type because it's not executable.
pub instructions_sysvar: AccountInfo<'info>,
}
Loading
Loading