Skip to content

Commit

Permalink
nour/program: add post multiple pyth oracle updates in one ix (#1133)
Browse files Browse the repository at this point in the history
* rework multi post update architecture

* pass anchor tests and finish drift client

* multi test doesnt pass but everything else does

* lifetime error blocking

* push for chris

* update cargo.toml

* fix lifetime

* update cargo.toml

* added idl and test all pass

* derive pda correctly

* adding some debug logging

* tests all pass

* remove unnecssary mut

* init price feed account with feed_id

* change require to validate, add changelog entry

---------

Co-authored-by: Chris Heaney <[email protected]>
  • Loading branch information
NourAlharithi and crispheaney authored Jul 16, 2024
1 parent 02863a1 commit 439a1d4
Show file tree
Hide file tree
Showing 12 changed files with 859 additions and 28 deletions.
19 changes: 10 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: add oracle id for wen ([#1129](https://github.com/drift-labs/protocol-v2/pull/1129))
- program: track fuel ([#1048](https://github.com/drift-labs/protocol-v2/pull/1048))
- program: add post multi pyth oracle updates atomic ([#1133](https://github.com/drift-labs/protocol-v2/pull/1133))
- program: track fuel for if staking ([#1127](https://github.com/drift-labs/protocol-v2/pull/1127))
- program: validate fee structure ([#1075](https://github.com/drift-labs/protocol-v2/pull/1075))
- program: check 5 min oracle twap divergence in trigger order ([#1116](https://github.com/drift-labs/protocol-v2/pull/1116))
Expand Down Expand Up @@ -123,13 +123,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixes

- program: set default ContractTier to HighlySpeculative ([#1013](https://github.com/drift-labs/protocol-v2/pull/1013))
- program: avoid dust borrows not being transferred
- program: avoid dust borrows not being transferred

### Breaking

## [2.77.0] - 2024-04-13

### Features

- program: lax funding rate update oracle validity criteria ([#1009](https://github.com/drift-labs/protocol-v2/pull/1009))

### Fixes
Expand Down Expand Up @@ -157,10 +158,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Features

- program: add fee adjustment to spot market ([#987](https://github.com/drift-labs/protocol-v2/pull/987))
- program: allow multiple makers to be passed into for spot fills ([#946](https://github.com/drift-labs/protocol-v2/pull/946))
- ts-sdk: add fn to get admin ix ([#980](https://github.com/drift-labs/protocol-v2/pull/980))
- program: add invariant check boolean for attempt settle revenue to insurance ([#937](https://github.com/drift-labs/protocol-v2/pull/937))
- program: improve best bid/ask estimate in mark twap update ([#975](https://github.com/drift-labs/protocol-v2/pull/975))
- program: allow multiple makers to be passed into for spot fills ([#946](https://github.com/drift-labs/protocol-v2/pull/946))
- ts-sdk: add fn to get admin ix ([#980](https://github.com/drift-labs/protocol-v2/pull/980))
- program: add invariant check boolean for attempt settle revenue to insurance ([#937](https://github.com/drift-labs/protocol-v2/pull/937))
- program: improve best bid/ask estimate in mark twap update ([#975](https://github.com/drift-labs/protocol-v2/pull/975))
- program: add optional margin calculations for drift-rs ([#978](https://github.com/drift-labs/protocol-v2/pull/978))

### Fixes
Expand All @@ -171,12 +172,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- program: add 'highly speculative' contract tier enum 4 ([#968](https://github.com/drift-labs/protocol-v2/pull/968))
- program: expand initialize market parameters ([#969](https://github.com/drift-labs/protocol-v2/pull/969))
- program: add 'highly speculative' contract tier enum 4 ([#968](https://github.com/drift-labs/protocol-v2/pull/968))
- program: expand initialize market parameters ([#969](https://github.com/drift-labs/protocol-v2/pull/969))

### Fixes

- program: fix checking isolated tier in add_perp_lp_shares ([#965](https://github.com/drift-labs/protocol-v2/pull/965))
- program: fix checking isolated tier in add_perp_lp_shares ([#965](https://github.com/drift-labs/protocol-v2/pull/965))

### Breaking

Expand Down
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions programs/drift/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ anchor-lang = "0.29.0"
solana-program = "1.16"
anchor-spl = "0.29.0"
pyth-client = "0.2.2"
pythnet-sdk = { git = "https://github.com/drift-labs/pyth-crosschain", rev = "04cf2c"}
pyth-solana-receiver-sdk = { git = "https://github.com/drift-labs/pyth-crosschain", rev = "04cf2c"}
pythnet-sdk = { git = "https://github.com/drift-labs/pyth-crosschain", rev = "3e8a24ecd0bcf22b787313e2020f4186bb22c729"}
pyth-solana-receiver-sdk = { git = "https://github.com/drift-labs/pyth-crosschain", rev = "3e8a24ecd0bcf22b787313e2020f4186bb22c729"}
bytemuck = { version = "1.4.0" }
borsh = "0.10.3"
num-traits = "0.2"
Expand Down
6 changes: 6 additions & 0 deletions programs/drift/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,12 @@ pub enum ErrorCode {
OracleWrongWriteAuthority,
#[msg("Oracle vaa owner must be wormhole program")]
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")]
OracleMismatchedVaaAndPriceUpdates,
#[msg("Remaining account passed is not a valid pda")]
OracleBadRemainingAccountPublicKey,
}

#[macro_export]
Expand Down
2 changes: 1 addition & 1 deletion programs/drift/src/instructions/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3826,7 +3826,7 @@ pub fn handle_initialize_pyth_pull_oracle(
let signer_seeds = &[&seeds[..]];
let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);

pyth_solana_receiver_sdk::cpi::init_price_update(cpi_context)?;
pyth_solana_receiver_sdk::cpi::init_price_update(cpi_context, feed_id)?;

Ok(())
}
Expand Down
87 changes: 84 additions & 3 deletions programs/drift/src/instructions/pyth_pull_oracle.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use crate::error::ErrorCode;
use crate::ids::{drift_oracle_receiver_program, wormhole_program};
use crate::validate;
use anchor_lang::prelude::*;
use pyth_solana_receiver_sdk::{
cpi::accounts::{PostUpdate, PostUpdateAtomic},
price_update::PriceUpdateV2,
program::PythSolanaReceiver,
PostUpdateAtomicParams, PostUpdateParams,
PostMultiUpdatesAtomicParams, PostUpdateAtomicParams, PostUpdateParams,
};
use pythnet_sdk::{
messages::Message,
Expand Down Expand Up @@ -51,7 +52,7 @@ pub fn handle_update_pyth_pull_oracle(
let price_feed_account =
PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;

require!(
validate!(
price_feed_account.price_message.feed_id == feed_id,
ErrorCode::OraclePriceFeedMessageMismatch
);
Expand Down Expand Up @@ -96,7 +97,7 @@ pub fn handle_post_pyth_pull_oracle_update_atomic(
let price_feed_account =
PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;

require!(
validate!(
price_feed_account.price_message.feed_id == feed_id,
ErrorCode::OraclePriceFeedMessageMismatch
);
Expand All @@ -105,6 +106,75 @@ pub fn handle_post_pyth_pull_oracle_update_atomic(
Ok(())
}

pub fn handle_post_multi_pyth_pull_oracle_updates_atomic<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, PostPythPullMultiOracleUpdatesAtomic<'info>>,
params: Vec<u8>,
) -> Result<()> {
let remaining_accounts = ctx.remaining_accounts;
validate!(
remaining_accounts.len() <= 2,
ErrorCode::OracleTooManyPriceAccountUpdates
);
let update_param = PostMultiUpdatesAtomicParams::deserialize(&mut &params[..]).unwrap();
let vaa = update_param.vaa;
let merkle_price_updates = update_param.merkle_price_updates;

validate!(
remaining_accounts.len() == merkle_price_updates.len(),
ErrorCode::OracleMismatchedVaaAndPriceUpdates
);

for (account, merkle_price_update) in remaining_accounts.iter().zip(merkle_price_updates.iter())
{
let cpi_program = ctx.accounts.pyth_solana_receiver.to_account_info().clone();
let cpi_accounts = PostUpdateAtomic {
payer: ctx.accounts.keeper.to_account_info().clone(),
guardian_set: ctx.accounts.guardian_set.to_account_info().clone(),
price_update_account: account.clone(),
write_authority: account.clone(),
};

let price_feed_account_data = account.try_borrow_data()?;
let price_feed_account = PriceUpdateV2::try_deserialize(&mut &price_feed_account_data[..])?;
let feed_id = price_feed_account.price_message.feed_id;

// Verify the pda
let (pda, bump) = Pubkey::find_program_address(
&[PTYH_PRICE_FEED_SEED_PREFIX, feed_id.as_ref()],
&crate::ID,
);
require_keys_eq!(
*account.key,
pda,
ErrorCode::OracleBadRemainingAccountPublicKey
);

let seeds = &[PTYH_PRICE_FEED_SEED_PREFIX, feed_id.as_ref(), &[bump]];

let signer_seeds = &[&seeds[..]];
let cpi_context = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);

// Get the timestamp of the price currently stored in the price feed account.
let current_timestamp = get_timestamp_from_price_feed_account(account)?;
let next_timestamp = get_timestamp_from_price_update_message(&merkle_price_update.message)?;

drop(price_feed_account_data);
drop(price_feed_account);

if next_timestamp > current_timestamp {
pyth_solana_receiver_sdk::cpi::post_update_atomic(
cpi_context,
PostUpdateAtomicParams {
merkle_price_update: merkle_price_update.clone(),
vaa: vaa.clone(),
},
)?;
}
}

Ok(())
}

pub fn get_timestamp_from_price_feed_account(price_feed_account: &AccountInfo) -> Result<i64> {
if price_feed_account.data_is_empty() {
Ok(0)
Expand Down Expand Up @@ -158,3 +228,14 @@ pub struct PostPythPullOracleUpdateAtomic<'info> {
#[account(mut, owner = drift_oracle_receiver_program::id(), seeds = [PTYH_PRICE_FEED_SEED_PREFIX, &feed_id], bump)]
pub price_feed: AccountInfo<'info>,
}

#[derive(Accounts)]
pub struct PostPythPullMultiOracleUpdatesAtomic<'info> {
#[account(mut)]
pub keeper: Signer<'info>,
pub pyth_solana_receiver: Program<'info, PythSolanaReceiver>,
/// CHECK: We can't use AccountVariant::<GuardianSet> here because its owner is hardcoded as the "official" Wormhole program
#[account(
owner = wormhole_program::id() @ ErrorCode::OracleWrongGuardianSetOwner)]
pub guardian_set: AccountInfo<'info>,
}
7 changes: 7 additions & 0 deletions programs/drift/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,13 @@ pub mod drift {
handle_post_pyth_pull_oracle_update_atomic(ctx, feed_id, params)
}

pub fn post_multi_pyth_pull_oracle_updates_atomic<'c: 'info, 'info>(
ctx: Context<'_, '_, 'c, 'info, PostPythPullMultiOracleUpdatesAtomic<'info>>,
params: Vec<u8>,
) -> Result<()> {
handle_post_multi_pyth_pull_oracle_updates_atomic(ctx, params)
}

// Admin Instructions

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
Expand Down
71 changes: 61 additions & 10 deletions sdk/src/driftClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ import { isOracleValid, trimVaaSignatures } from './math/oracles';
import { TxHandler } from './tx/txHandler';
import {
wormholeCoreBridgeIdl,
pythSolanaReceiverIdl,
DEFAULT_RECEIVER_PROGRAM_ID,
} from '@pythnetwork/pyth-solana-receiver';
import { parseAccumulatorUpdateData } from '@pythnetwork/price-service-sdk';
Expand All @@ -145,6 +144,7 @@ import { WormholeCoreBridgeSolana } from '@pythnetwork/pyth-solana-receiver/lib/
import { PythSolanaReceiver } from '@pythnetwork/pyth-solana-receiver/lib/idl/pyth_solana_receiver';
import { getFeedIdUint8Array, trimFeedId } from './util/pythPullOracleUtils';
import { isVersionedTransaction } from './tx/utils';
import pythSolanaReceiverIdl from './idl/pyth_solana_receiver.json';

type RemainingAccountParams = {
userAccounts: UserAccount[];
Expand Down Expand Up @@ -6913,7 +6913,7 @@ export class DriftClient {
public getReceiverProgram(): Program<PythSolanaReceiver> {
if (this.receiverProgram === undefined) {
this.receiverProgram = new Program(
pythSolanaReceiverIdl,
pythSolanaReceiverIdl as PythSolanaReceiver,
DEFAULT_RECEIVER_PROGRAM_ID,
this.provider
);
Expand All @@ -6935,12 +6935,25 @@ export class DriftClient {
return txSig;
}

public async postMultiPythPullOracleUpdatesAtomic(
vaaString: string,
feedIds: string[]
): Promise<TransactionSignature> {
const postIxs = await this.getPostPythPullOracleUpdateAtomicIxs(
vaaString,
feedIds
);
const tx = await this.buildTransaction(postIxs);
const { txSig } = await this.sendTransaction(tx, [], this.opts);

return txSig;
}

public async getPostPythPullOracleUpdateAtomicIxs(
vaaString: string,
feedId: string,
feedIds: string | string[],
numSignatures = 2
): Promise<TransactionInstruction[]> {
feedId = trimFeedId(feedId);
const accumulatorUpdateData = parseAccumulatorUpdateData(
Buffer.from(vaaString, 'base64')
);
Expand All @@ -6955,23 +6968,61 @@ export class DriftClient {
);

const postIxs: TransactionInstruction[] = [];
for (const update of accumulatorUpdateData.updates) {
if (accumulatorUpdateData.updates.length > 1) {
const encodedParams = this.getReceiverProgram().coder.types.encode(
'PostMultiUpdatesAtomicParams',
{
vaa: trimmedVaa,
merklePriceUpdates: accumulatorUpdateData.updates,
}
);
const feedIdsToUse: string[] =
typeof feedIds === 'string' ? [feedIds] : feedIds;
const pubkeys = feedIdsToUse.map((feedId) => {
return getPythPullOraclePublicKey(
this.program.programId,
getFeedIdUint8Array(feedId)
);
});

const remainingAccounts: Array<AccountMeta> = pubkeys.map((pubkey) => {
return {
pubkey,
isSigner: false,
isWritable: true,
};
});
postIxs.push(
this.program.instruction.postMultiPythPullOracleUpdatesAtomic(
encodedParams,
{
accounts: {
keeper: this.wallet.publicKey,
pythSolanaReceiver: DRIFT_ORACLE_RECEIVER_ID,
guardianSet,
},
remainingAccounts,
}
)
);
} else {
let feedIdToUse = typeof feedIds === 'string' ? feedIds : feedIds[0];
feedIdToUse = trimFeedId(feedIdToUse);
postIxs.push(
await this.getSinglePostPythPullOracleAtomicIx(
{
vaa: trimmedVaa,
merklePriceUpdate: update,
merklePriceUpdate: accumulatorUpdateData.updates[0],
},
feedId,
feedIdToUse,
guardianSet
)
);
}

return postIxs;
}

public async getSinglePostPythPullOracleAtomicIx(
private async getSinglePostPythPullOracleAtomicIx(
params: {
vaa: Buffer;
merklePriceUpdate: {
Expand Down Expand Up @@ -7083,7 +7134,7 @@ export class DriftClient {
);
}

public async getBuildEncodedVaaIxs(
private async getBuildEncodedVaaIxs(
vaa: Buffer,
guardianSet: PublicKey
): Promise<[TransactionInstruction[], Keypair]> {
Expand Down
Loading

0 comments on commit 439a1d4

Please sign in to comment.