use core::fmt;
use solana_program::instruction::AccountMeta;
use solana_program::pubkey::Pubkey;
pub struct RemainingAccountsParams {
pub writable_asset_ids: Vec<pyra_tokens::AssetId>,
pub readable_asset_ids: Vec<pyra_tokens::AssetId>,
pub oracle_pubkeys: Vec<(pyra_tokens::AssetId, Pubkey)>,
}
#[derive(Debug, PartialEq, Eq)]
pub enum RemainingAccountsError {
MissingOracle(pyra_tokens::AssetId),
NoDriftMarket(pyra_tokens::AssetId),
}
impl fmt::Display for RemainingAccountsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingOracle(idx) => write!(f, "missing oracle for asset ID {}", idx.value()),
Self::NoDriftMarket(idx) => {
write!(f, "asset ID {} has no Drift market index", idx.value())
}
}
}
}
pub fn get_remaining_accounts(
params: &RemainingAccountsParams,
) -> Result<Vec<AccountMeta>, RemainingAccountsError> {
let mut accounts = Vec::new();
let mut seen_asset_ids: Vec<pyra_tokens::AssetId> = Vec::new();
for &idx in ¶ms.writable_asset_ids {
if !seen_asset_ids.contains(&idx) {
seen_asset_ids.push(idx);
}
}
for &idx in ¶ms.readable_asset_ids {
if !seen_asset_ids.contains(&idx) {
seen_asset_ids.push(idx);
}
}
for &idx in &seen_asset_ids {
let (_, oracle) = params
.oracle_pubkeys
.iter()
.find(|(mi, _)| *mi == idx)
.ok_or(RemainingAccountsError::MissingOracle(idx))?;
accounts.push(AccountMeta {
pubkey: *oracle,
is_signer: false,
is_writable: false,
});
}
for &idx in &seen_asset_ids {
let is_writable = params.writable_asset_ids.contains(&idx);
accounts.push(AccountMeta {
pubkey: pyra_accounts::get_drift_spot_market(idx)
.ok_or(RemainingAccountsError::NoDriftMarket(idx))?,
is_signer: false,
is_writable,
});
}
for &idx in &seen_asset_ids {
if !params.writable_asset_ids.contains(&idx) {
continue;
}
let token = idx.token();
if token.token_program == pyra_tokens::TOKEN_2022_PROGRAM_ID {
accounts.push(AccountMeta {
pubkey: token.mint,
is_signer: false,
is_writable: false,
});
}
}
Ok(accounts)
}
#[cfg(test)]
#[allow(
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::arithmetic_side_effects,
reason = "test code"
)]
mod tests {
use super::*;
use pyra_tokens::AssetId;
use solana_pubkey::pubkey;
#[test]
fn test_single_writable_market_with_quote() {
let sol_oracle = pubkey!("BAtFj4kQttZRVep3UZS2aZRDixkGYgWsbqTBVDbnSsPF");
let usdc_oracle = pubkey!("En8hkHLkRe9d9DraYmBTrus518BvmVH448YcvmrFM6Ce");
let result = get_remaining_accounts(&RemainingAccountsParams {
writable_asset_ids: vec![AssetId::new(1).unwrap()],
readable_asset_ids: vec![AssetId::new(0).unwrap()],
oracle_pubkeys: vec![(AssetId::new(1).unwrap(), sol_oracle), (AssetId::new(0).unwrap(), usdc_oracle)],
})
.expect("should succeed");
assert_eq!(result.len(), 4);
assert_eq!(result[0].pubkey, sol_oracle);
assert!(!result[0].is_writable);
assert_eq!(result[1].pubkey, usdc_oracle);
assert!(!result[1].is_writable);
let sol_spot_market = pyra_accounts::get_drift_spot_market(AssetId::new(1).unwrap()).unwrap();
let usdc_spot_market = pyra_accounts::get_drift_spot_market(AssetId::new(0).unwrap()).unwrap();
assert_eq!(result[2].pubkey, sol_spot_market);
assert!(result[2].is_writable);
assert_eq!(result[3].pubkey, usdc_spot_market);
assert!(!result[3].is_writable);
}
#[test]
fn test_token_2022_appends_mint() {
let pyusd_oracle = pubkey!("BAtFj4kQttZRVep3UZS2aZRDixkGYgWsbqTBVDbnSsPF");
let usdc_oracle = pubkey!("En8hkHLkRe9d9DraYmBTrus518BvmVH448YcvmrFM6Ce");
let result = get_remaining_accounts(&RemainingAccountsParams {
writable_asset_ids: vec![AssetId::new(6).unwrap()],
readable_asset_ids: vec![AssetId::new(0).unwrap()],
oracle_pubkeys: vec![(AssetId::new(6).unwrap(), pyusd_oracle), (AssetId::new(0).unwrap(), usdc_oracle)],
})
.expect("should succeed");
assert_eq!(result.len(), 5);
let pyusd = AssetId::new(6).unwrap().token();
assert_eq!(result[4].pubkey, pyusd.mint);
assert!(!result[4].is_writable);
assert!(!result[4].is_signer);
}
#[test]
fn test_deduplicates_overlapping_indexes() {
let usdc_oracle = pubkey!("En8hkHLkRe9d9DraYmBTrus518BvmVH448YcvmrFM6Ce");
let result = get_remaining_accounts(&RemainingAccountsParams {
writable_asset_ids: vec![AssetId::new(0).unwrap()],
readable_asset_ids: vec![AssetId::new(0).unwrap()],
oracle_pubkeys: vec![(AssetId::new(0).unwrap(), usdc_oracle)],
})
.expect("should succeed");
assert_eq!(result.len(), 2);
assert!(result[1].is_writable); }
#[test]
fn test_spl_token_market_no_mint_appended() {
let sol_oracle = pubkey!("BAtFj4kQttZRVep3UZS2aZRDixkGYgWsbqTBVDbnSsPF");
let result = get_remaining_accounts(&RemainingAccountsParams {
writable_asset_ids: vec![AssetId::new(1).unwrap()],
readable_asset_ids: vec![],
oracle_pubkeys: vec![(AssetId::new(1).unwrap(), sol_oracle)],
})
.expect("should succeed");
assert_eq!(result.len(), 2);
}
#[test]
fn test_empty_params() {
let result = get_remaining_accounts(&RemainingAccountsParams {
writable_asset_ids: vec![],
readable_asset_ids: vec![],
oracle_pubkeys: vec![],
})
.expect("should succeed");
assert!(result.is_empty());
}
#[test]
fn test_missing_oracle_returns_error() {
let result = get_remaining_accounts(&RemainingAccountsParams {
writable_asset_ids: vec![AssetId::new(1).unwrap()],
readable_asset_ids: vec![AssetId::new(0).unwrap()],
oracle_pubkeys: vec![], });
assert_eq!(result, Err(RemainingAccountsError::MissingOracle(AssetId::new(1).unwrap())));
}
}