Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(lazer): add fees to solana contract #2146

Merged
merged 6 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -1,8 +1,8 @@
[package]
name = "pyth-lazer-solana-contract"
version = "0.1.0"
version = "0.2.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"] }
188 changes: 169 additions & 19 deletions lazer/contracts/solana/programs/pyth-lazer-solana-contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
mod signature;

use {
anchor_lang::{prelude::*, solana_program::pubkey::PUBKEY_BYTES},
std::mem::size_of,
crate::signature::VerifiedMessage,
anchor_lang::{
prelude::*, solana_program::pubkey::PUBKEY_BYTES, system_program, Discriminator,
},
std::{io::Cursor, 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 ANCHOR_DISCRIMINATOR_BYTES: usize = 8;
pub const MAX_NUM_TRUSTED_SIGNERS: usize = 2;
pub const SPACE_FOR_TRUSTED_SIGNERS: usize = 5;
pub const EXTRA_SPACE: usize = 100;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, AnchorSerialize, AnchorDeserialize)]
pub struct TrustedSignerInfo {
Expand All @@ -33,17 +40,41 @@ impl TrustedSignerInfo {
const SERIALIZED_LEN: usize = PUBKEY_BYTES + size_of::<i64>();
}

/// TODO: remove this legacy storage type
#[derive(AnchorDeserialize)]
pub struct StorageV010 {
pub top_authority: Pubkey,
pub num_trusted_signers: u8,
pub trusted_signers: [TrustedSignerInfo; MAX_NUM_TRUSTED_SIGNERS],
}

impl StorageV010 {
pub const SERIALIZED_LEN: usize = PUBKEY_BYTES
+ 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)]
}
}

#[account]
pub struct Storage {
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],
pub trusted_signers: [TrustedSignerInfo; SPACE_FOR_TRUSTED_SIGNERS],
pub _extra_space: [u8; EXTRA_SPACE],
}

impl Storage {
const SERIALIZED_LEN: usize = PUBKEY_BYTES
Riateche marked this conversation as resolved.
Show resolved Hide resolved
+ PUBKEY_BYTES
+ size_of::<u64>()
+ size_of::<u8>()
+ TrustedSignerInfo::SERIALIZED_LEN * MAX_NUM_TRUSTED_SIGNERS;
+ TrustedSignerInfo::SERIALIZED_LEN * SPACE_FOR_TRUSTED_SIGNERS
+ EXTRA_SPACE;

pub fn initialized_trusted_signers(&self) -> &[TrustedSignerInfo] {
&self.trusted_signers[0..usize::from(self.num_trusted_signers)]
Expand All @@ -56,8 +87,48 @@ pub const STORAGE_SEED: &[u8] = b"storage";
pub mod pyth_lazer_solana_contract {
use super::*;

pub fn initialize(ctx: Context<Initialize>, top_authority: Pubkey) -> Result<()> {
pub fn initialize(
ctx: Context<Initialize>,
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_from_0_1_0(ctx: Context<MigrateFrom010>, treasury: Pubkey) -> Result<()> {
let old_data = ctx.accounts.storage.data.borrow();
if old_data[0..ANCHOR_DISCRIMINATOR_BYTES] != Storage::DISCRIMINATOR {
return Err(ProgramError::InvalidAccountData.into());
}
let old_storage = StorageV010::deserialize(&mut &old_data[ANCHOR_DISCRIMINATOR_BYTES..])?;
if old_storage.top_authority != ctx.accounts.top_authority.key() {
return Err(ProgramError::MissingRequiredSignature.into());
}
drop(old_data);

let space = ANCHOR_DISCRIMINATOR_BYTES + Storage::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 mut new_storage = Storage {
top_authority: old_storage.top_authority,
treasury,
single_update_fee_in_lamports: 1,
num_trusted_signers: old_storage.num_trusted_signers,
trusted_signers: Default::default(),
_extra_space: [0; EXTRA_SPACE],
};
new_storage.trusted_signers[..old_storage.trusted_signers.len()]
.copy_from_slice(&old_storage.trusted_signers);
new_storage.try_serialize(&mut Cursor::new(
&mut **ctx.accounts.storage.data.borrow_mut(),
))?;
Ok(())
}

Expand All @@ -66,6 +137,9 @@ pub mod pyth_lazer_solana_contract {
if num_trusted_signers > ctx.accounts.storage.trusted_signers.len() {
return Err(ProgramError::InvalidAccountData.into());
}
if num_trusted_signers > MAX_NUM_TRUSTED_SIGNERS {
return Err(ProgramError::InvalidAccountData.into());
}
let mut trusted_signers =
ctx.accounts.storage.trusted_signers[..num_trusted_signers].to_vec();
if expires_at == 0 {
Expand All @@ -92,6 +166,9 @@ pub mod pyth_lazer_solana_contract {
if trusted_signers.len() > ctx.accounts.storage.trusted_signers.len() {
return Err(ProgramError::AccountDataTooSmall.into());
}
if trusted_signers.len() > MAX_NUM_TRUSTED_SIGNERS {
return Err(ProgramError::InvalidInstructionData.into());
}

ctx.accounts.storage.trusted_signers = Default::default();
ctx.accounts.storage.trusted_signers[..trusted_signers.len()]
Expand All @@ -102,6 +179,47 @@ 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)]
Expand All @@ -111,14 +229,27 @@ pub struct Initialize<'info> {
#[account(
init,
payer = payer,
space = 8 + Storage::SERIALIZED_LEN,
space = ANCHOR_DISCRIMINATOR_BYTES + Storage::SERIALIZED_LEN,
seeds = [STORAGE_SEED],
bump,
)]
pub storage: Account<'info, Storage>,
pub system_program: Program<'info, System>,
}

#[derive(Accounts)]
pub struct MigrateFrom010<'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 Update<'info> {
pub top_authority: Signer<'info>,
Expand All @@ -130,3 +261,22 @@ pub struct Update<'info> {
)]
pub storage: Account<'info, Storage>,
}

#[derive(Accounts)]
pub struct VerifyMessage<'info> {
#[account(mut)]
pub payer: Signer<'info>,
#[account(
seeds = [STORAGE_SEED],
bump,
has_one = treasury
)]
pub storage: Account<'info, Storage>,
/// CHECK: this account doesn't need additional constraints.
pub treasury: AccountInfo<'info>,
Riateche marked this conversation as resolved.
Show resolved Hide resolved
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