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

refactor to multi update #1379

Merged
merged 3 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
NourAlharithi marked this conversation as resolved.
Show resolved Hide resolved
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>)]
NourAlharithi marked this conversation as resolved.
Show resolved Hide resolved
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
Loading