Skip to content

Commit

Permalink
refactor to multi update
Browse files Browse the repository at this point in the history
  • Loading branch information
NourAlharithi committed Dec 12, 2024
1 parent a6b59dd commit c80a9c0
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 85 deletions.
4 changes: 2 additions & 2 deletions programs/drift/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,9 +559,9 @@ pub enum ErrorCode {
OracleWrongVaaOwner,
#[msg("Multi updates must have 2 or fewer accounts passed in remaining accounts")]
OracleTooManyPriceAccountUpdates,
#[msg("Don't have the same remaining accounts number and merkle price updates left")]
#[msg("Don't have the same remaining accounts number and pyth updates left")]
OracleMismatchedVaaAndPriceUpdates,
#[msg("Remaining account passed is not a valid pda")]
#[msg("Remaining account passed does not match oracle update derived pda")]
OracleBadRemainingAccountPublicKey,
#[msg("FailedOpenbookV2CPI")]
FailedOpenbookV2CPI,
Expand Down
125 changes: 82 additions & 43 deletions programs/drift/src/instructions/pyth_lazer_oracle.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
use crate::error::ErrorCode;
use crate::math::casting::Cast;
use crate::math::safe_math::SafeMath;
use crate::state::pyth_lazer_oracle::{
PythLazerOracle, PYTH_LAZER_ORACLE_SEED, PYTH_LAZER_STORAGE_ID,
};
use crate::validate;
use anchor_lang::prelude::*;
use pyth_lazer_sdk::protocol::payload::{PayloadData, PayloadPropertyValue};
use pyth_lazer_sdk::protocol::router::Price;
use solana_program::sysvar::instructions::load_current_index_checked;

pub fn handle_update_pyth_lazer_oracle(
ctx: Context<UpdatePythLazerOracle>,
feed_id: u32,
pub fn handle_update_pyth_lazer_oracle<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, UpdatePythLazerOracle>,
pyth_message: Vec<u8>,
) -> Result<()> {
// Verify the Pyth lazer message
let ix_idx = load_current_index_checked(&ctx.accounts.ix_sysvar.to_account_info())?;
validate!(
ix_idx > 0,
Expand All @@ -25,63 +28,101 @@ pub fn handle_update_pyth_lazer_oracle(
&pyth_message,
ix_idx - 1,
0,
1,
0,
);

if verified.is_err() {
msg!("{:?}", verified);
return Err(ErrorCode::UnverifiedPythLazerMessage.into());
}

// Load oracle accounts from remaining accounts
let remaining_accounts = ctx.remaining_accounts;
validate!(
remaining_accounts.len() <= 3,
ErrorCode::OracleTooManyPriceAccountUpdates
)?;

let data = PayloadData::deserialize_slice_le(verified.unwrap().payload)
.map_err(|_| ProgramError::InvalidInstructionData)?;
let next_timestamp = data.timestamp_us.0;

if data.feeds.is_empty() || data.feeds[0].properties.is_empty() {
msg!("Invalid Pyth lazer message. No feeds or properties found");
return Err(ErrorCode::InvalidPythLazerMessage.into());
}
validate!(
remaining_accounts.len() == data.feeds.len(),
ErrorCode::OracleMismatchedVaaAndPriceUpdates
)?;

if data.feeds[0].feed_id.0 != feed_id {
msg!(
"Feed ID mismatch. Expected {} but got {}",
feed_id,
data.feeds[0].feed_id.0
);
return Err(ErrorCode::InvalidPythLazerMessage.into());
}
for (account, payload_data) in remaining_accounts.iter().zip(data.feeds.iter()) {
let pyth_lazer_oracle_loader: AccountLoader<PythLazerOracle> =
AccountLoader::try_from(account)?;
let mut pyth_lazer_oracle = pyth_lazer_oracle_loader.load_mut()?;

let mut pyth_lazer_oracle = ctx.accounts.pyth_lazer_oracle.load_mut()?;
let current_timestamp = pyth_lazer_oracle.publish_time;
let next_timestamp = data.timestamp_us.0;
let feed_id = payload_data.feed_id.0;

if next_timestamp > current_timestamp {
let PayloadPropertyValue::Price(Some(price)) = data.feeds[0].properties[0] else {
return Err(ErrorCode::InvalidPythLazerMessage.into());
};
pyth_lazer_oracle.price = price.0.get();
pyth_lazer_oracle.posted_slot = Clock::get()?.slot;
pyth_lazer_oracle.publish_time = next_timestamp;
pyth_lazer_oracle.conf = 0;
pyth_lazer_oracle.exponent = -8;
msg!("Price updated to {}", price.0.get());

msg!(
"Posting new lazer update. current ts {} < next ts {}",
current_timestamp,
next_timestamp
);
} else {
msg!(
"Skipping new lazer update. current ts {} >= next ts {}",
current_timestamp,
next_timestamp
// Verify the pda
let pda = Pubkey::find_program_address(
&[PYTH_LAZER_ORACLE_SEED, &feed_id.to_le_bytes()],
&crate::ID,
)
.0;
require_keys_eq!(
*account.key,
pda,
ErrorCode::OracleBadRemainingAccountPublicKey
);

let current_timestamp = pyth_lazer_oracle.publish_time;

if next_timestamp > current_timestamp {
let PayloadPropertyValue::Price(Some(price)) = payload_data.properties[0] else {
return Err(ErrorCode::InvalidPythLazerMessage.into());
};

let mut best_bid_price: Option<Price> = None;
let mut best_ask_price: Option<Price> = None;

for property in &payload_data.properties {
match property {
PayloadPropertyValue::BestBidPrice(price) => best_bid_price = *price,
PayloadPropertyValue::BestAskPrice(price) => best_ask_price = *price,
_ => {}
}
}

// Default to 2% of the price for conf if bid > ask or one-sided market
let mut conf: i64 = price.0.get().safe_div(50)?;
if let (Some(bid), Some(ask)) = (best_bid_price, best_ask_price) {
if bid.0.get() < ask.0.get() {
conf = ask.0.get() - bid.0.get();
}
}

pyth_lazer_oracle.price = price.0.get();
pyth_lazer_oracle.posted_slot = Clock::get()?.slot;
pyth_lazer_oracle.publish_time = next_timestamp;
pyth_lazer_oracle.conf = conf.cast::<u64>()?;
pyth_lazer_oracle.exponent = -8;
msg!("Price updated to {}", price.0.get());

msg!(
"Posting new lazer update. current ts {} < next ts {}",
current_timestamp,
next_timestamp
);
} else {
msg!(
"Skipping new lazer update. current ts {} >= next ts {}",
current_timestamp,
next_timestamp
);
}
}

Ok(())
}

#[derive(Accounts)]
#[instruction(feed_id: u32, pyth_message: Vec<u8>)]
#[instruction(pyth_message: Vec<u8>)]
pub struct UpdatePythLazerOracle<'info> {
#[account(mut)]
pub keeper: Signer<'info>,
Expand All @@ -93,6 +134,4 @@ pub struct UpdatePythLazerOracle<'info> {
/// CHECK: checked by ed25519 verify
#[account(address = solana_program::sysvar::instructions::ID)]
pub ix_sysvar: AccountInfo<'info>,
#[account(mut, seeds = [PYTH_LAZER_ORACLE_SEED, &feed_id.to_le_bytes()], bump)]
pub pyth_lazer_oracle: AccountLoader<'info, PythLazerOracle>,
}
7 changes: 3 additions & 4 deletions programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1567,12 +1567,11 @@ pub mod drift {
handle_initialize_pyth_lazer_oracle(ctx, feed_id)
}

pub fn post_pyth_lazer_oracle_update(
ctx: Context<UpdatePythLazerOracle>,
feed_id: u32,
pub fn post_pyth_lazer_oracle_update<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, UpdatePythLazerOracle>,
pyth_message: Vec<u8>,
) -> Result<()> {
handle_update_pyth_lazer_oracle(ctx, feed_id, pyth_message)
handle_update_pyth_lazer_oracle(ctx, pyth_message)
}

pub fn initialize_high_leverage_mode_config(
Expand Down
28 changes: 14 additions & 14 deletions sdk/src/driftClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8606,11 +8606,11 @@ export class DriftClient {
}

public async postPythLazerOracleUpdate(
feedId: number,
feedIds: number[],
pythMessageHex: string
): Promise<string> {
const postIxs = this.getPostPythLazerOracleUpdateIxs(
feedId,
feedIds,
pythMessageHex,
undefined,
2
Expand All @@ -8621,37 +8621,37 @@ export class DriftClient {
}

public getPostPythLazerOracleUpdateIxs(
feedId: number,
feedIds: number[],
pythMessageHex: string,
precedingIxs: TransactionInstruction[] = [],
overrideIxCount?: number
): TransactionInstruction[] {
const pythMessageBytes = Buffer.from(pythMessageHex, 'hex');
const messageOffset = 1;

const updateData = new Uint8Array(1 + pythMessageBytes.length);
updateData[0] = feedId;
updateData.set(pythMessageBytes, 1);
const updateData = new Uint8Array(pythMessageBytes);

const verifyIx = createMinimalEd25519VerifyIx(
overrideIxCount || precedingIxs.length + 1,
messageOffset,
0,
updateData
);

const remainingAccountsMeta = feedIds.map((feedId) => {
return {
pubkey: getPythLazerOraclePublicKey(this.program.programId, feedId),
isSigner: false,
isWritable: true,
};
});

const ix = this.program.instruction.postPythLazerOracleUpdate(
feedId,
pythMessageBytes,
{
accounts: {
keeper: this.wallet.publicKey,
pythLazerStorage: PYTH_LAZER_STORAGE_ACCOUNT_KEY,
pythLazerOracle: getPythLazerOraclePublicKey(
this.program.programId,
feedId
),
ixSysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
},
remainingAccounts: remainingAccountsMeta,
}
);
return [verifyIx, ix];
Expand Down
15 changes: 3 additions & 12 deletions sdk/src/idl/drift.json
Original file line number Diff line number Diff line change
Expand Up @@ -6503,25 +6503,16 @@
},
{
"name": "pythLazerStorage",
"isMut": true,
"isMut": false,
"isSigner": false
},
{
"name": "ixSysvar",
"isMut": false,
"isSigner": false
},
{
"name": "pythLazerOracle",
"isMut": true,
"isSigner": false
}
],
"args": [
{
"name": "feedId",
"type": "u32"
},
{
"name": "pythMessage",
"type": "bytes"
Expand Down Expand Up @@ -14290,12 +14281,12 @@
{
"code": 6277,
"name": "OracleMismatchedVaaAndPriceUpdates",
"msg": "Don't have the same remaining accounts number and merkle price updates left"
"msg": "Don't have the same remaining accounts number and pyth updates left"
},
{
"code": 6278,
"name": "OracleBadRemainingAccountPublicKey",
"msg": "Remaining account passed is not a valid pda"
"msg": "Remaining account passed does not match oracle update derived pda"
},
{
"code": 6279,
Expand Down
6 changes: 3 additions & 3 deletions tests/placeAndMakeSwiftPerpBankrun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,12 +350,12 @@ describe('place and make swift order', () => {
// Switch the oracle over to using pyth lazer
await makerDriftClient.initializePythLazerOracle(6);
await makerDriftClient.postPythLazerOracleUpdate(
6,
[6],
PYTH_LAZER_HEX_STRING_SOL
);

await makerDriftClient.postPythLazerOracleUpdate(
6,
[6],
PYTH_LAZER_HEX_STRING_SOL
);
await makerDriftClient.updatePerpMarketOracle(
Expand Down Expand Up @@ -452,7 +452,7 @@ describe('place and make swift order', () => {

// Get pyth lazer instruction
const pythLazerCrankIxs = makerDriftClient.getPostPythLazerOracleUpdateIxs(
6,
[6],
PYTH_LAZER_HEX_STRING_SOL,
undefined,
1
Expand Down
44 changes: 42 additions & 2 deletions tests/pythLazer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ import {
mockUSDCMint,
} from './testHelpersLocalValidator';
import { Wallet, loadKeypair, EventSubscriber } from '../sdk/src';
import { PYTH_LAZER_HEX_STRING_BTC } from './pythLazerData';
import {
PYTH_LAZER_HEX_STRING_BTC,
PYTH_LAZER_HEX_STRING_MULTI,
PYTH_LAZER_HEX_STRING_SOL,
} from './pythLazerData';

describe('pyth lazer oracles', () => {
const provider = anchor.AnchorProvider.local(undefined, {
Expand Down Expand Up @@ -86,11 +90,13 @@ describe('pyth lazer oracles', () => {

it('init feed', async () => {
await driftClient.initializePythLazerOracle(1);
await driftClient.initializePythLazerOracle(2);
await driftClient.initializePythLazerOracle(6);
});

it('crank', async () => {
const ixs = await driftClient.getPostPythLazerOracleUpdateIxs(
1,
[1],
PYTH_LAZER_HEX_STRING_BTC
);

Expand All @@ -104,4 +110,38 @@ describe('pyth lazer oracles', () => {
console.log(simResult.value.logs);
assert(simResult.value.err === null);
});

it('crank multi', async () => {
const ixs = driftClient.getPostPythLazerOracleUpdateIxs(
[1, 2, 6],
PYTH_LAZER_HEX_STRING_MULTI
);

const message = new TransactionMessage({
instructions: ixs,
payerKey: driftClient.wallet.payer.publicKey,
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
}).compileToV0Message();
const tx = new VersionedTransaction(message);
const simResult = await provider.connection.simulateTransaction(tx);
console.log(simResult.value.logs);
assert(simResult.value.err === null);
});

it('fails on wrong message passed', async () => {
const ixs = driftClient.getPostPythLazerOracleUpdateIxs(
[1],
PYTH_LAZER_HEX_STRING_SOL
);

const message = new TransactionMessage({
instructions: ixs,
payerKey: driftClient.wallet.payer.publicKey,
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
}).compileToV0Message();
const tx = new VersionedTransaction(message);
const simResult = await provider.connection.simulateTransaction(tx);
console.log(simResult.value.logs);
assert(simResult.value.err !== null);
});
});
Loading

0 comments on commit c80a9c0

Please sign in to comment.