use crate::prelude::*;
use anyhow::anyhow;
use sha2::{Digest, Sha256};
use solana_client::nonblocking::rpc_client::RpcClient as NonblockingRpcClient;
use solana_sdk::client::SyncClient;
use solana_sdk::signer::keypair::{keypair_from_seed, read_keypair_file, Keypair};
use solana_sdk::signer::Signer;
use std::env;
use std::result::Result;
use std::str::FromStr;
use std::sync::Arc;
use tokio::sync::RwLock;
use solana_sdk::transaction::Transaction;
use solana_program::message::Message;
use crate::anchor_traits::*;
use solana_program::pubkey::Pubkey;
use anchor_client::anchor_lang::AccountDeserialize;
use anyhow::Error as AnyhowError;
use solana_sdk::message::v0::Message as V0Message;
use solana_sdk::transaction::VersionedTransaction;
use solana_sdk::compute_budget::ComputeBudgetInstruction;
use solana_program::hash::Hash;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::address_lookup_table::AddressLookupTableAccount;
use solana_program::message::VersionedMessage::V0;
pub async fn ix_to_tx_v0(
rpc_client: &RpcClient,
ixs: &[Instruction],
signers: &[&Keypair],
blockhash: Hash,
luts: &[AddressLookupTableAccount],
) -> Result<VersionedTransaction, OnDemandError> {
let payer = signers[0].pubkey();
let compute_unit_limit = estimate_compute_units(rpc_client, ixs, luts, blockhash, signers).await.unwrap_or(1_400_000);
let cus = std::cmp::min((compute_unit_limit as f64 * 1.4) as u32, 1_400_000);
let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(cus);
let priority_fee_ix = ComputeBudgetInstruction::set_compute_unit_price(10_000);
let mut final_ixs = vec![
compute_budget_ix,
priority_fee_ix,
];
final_ixs.extend_from_slice(ixs);
let message = V0Message::try_compile(&payer, &final_ixs, luts, blockhash)
.map_err(|_| OnDemandError::SolanaSignError)?;
let message = V0(message);
let tx = VersionedTransaction::try_new(message, signers)
.map_err(|_| OnDemandError::SolanaSignError)?;
Ok(tx)
}
async fn estimate_compute_units(rpc_client: &RpcClient, ixs: &[Instruction], luts: &[AddressLookupTableAccount], blockhash: Hash, signers: &[&Keypair]) -> Result<u32, AnyhowError> {
let payer = signers[0].pubkey();
let mut ixs = ixs.to_vec();
ixs.insert(0, ComputeBudgetInstruction::set_compute_unit_limit(1_400_000).into());
let message = V0Message::try_compile(&payer, &ixs, luts, blockhash)
.map_err(|_| OnDemandError::SolanaSignError)?;
let tx = VersionedTransaction::try_new(V0(message), signers)
.map_err(|_| OnDemandError::SolanaSignError)?;
let sim_result = rpc_client.simulate_transaction(&tx)
.await
.map_err(|_| anyhow!("Failed to simulate transaction"))?;
if let Some(units) = sim_result.value.units_consumed {
Ok(units as u32)
} else {
Err(anyhow!("Failed to estimate compute units"))
}
}
pub fn ix_to_tx(
ixs: &[Instruction],
signers: &[&Keypair],
blockhash: solana_program::hash::Hash,
) -> Result<Transaction, OnDemandError> {
let msg = Message::new(ixs, Some(&signers[0].pubkey()));
let mut tx = Transaction::new_unsigned(msg);
tx.try_sign(&signers.to_vec(), blockhash)
.map_err(|_e| OnDemandError::SolanaSignError)?;
Ok(tx)
}
pub async fn get_enclave_signer_pubkey(
enclave_signer: &Arc<RwLock<Keypair>>,
) -> Result<Arc<Pubkey>, OnDemandError> {
let enclave_signer = enclave_signer.clone();
let ro_enclave_signer = enclave_signer.read().await;
let pubkey = Arc::new(ro_enclave_signer.pubkey());
Ok(pubkey)
}
pub fn load_env_pubkey(key: &str) -> Result<Pubkey, OnDemandError> {
Pubkey::from_str(&env::var(key).unwrap_or_default())
.map_err(|_| OnDemandError::EnvVariableMissing)
}
pub fn parse_optional_pubkey(var: &str) -> Option<Pubkey> {
if var.is_empty() {
None
} else {
match Pubkey::from_str(var) {
Ok(pubkey) => {
if pubkey != Pubkey::default() {
Some(pubkey)
} else {
None
}
}
Err(_) => None,
}
}
}
pub fn keypair_from_base_seed(
base: &str,
secret_key: Vec<u8>,
more_bytes: Option<Vec<u8>>,
program_id: Option<Pubkey>,
) -> Result<Arc<Keypair>, OnDemandError> {
if secret_key.len() != 32 {
return Err(OnDemandError::InvalidSecretKey);
}
let mut seed = base.as_bytes().to_vec();
seed.extend_from_slice(&secret_key);
if let Some(bytes) = more_bytes.as_ref() {
seed.extend_from_slice(bytes);
}
if let Some(program_id) = program_id.as_ref() {
seed.extend_from_slice(&borsh::to_vec(&program_id).unwrap_or_default());
} else {
seed.extend_from_slice(
&borsh::to_vec(&crate::get_switchboard_on_demand_program_id())
.unwrap_or_default(),
);
}
match keypair_from_seed(&Sha256::digest(&seed)) {
Ok(keypair) => Ok(Arc::new(keypair)),
Err(e) => {
if let Some(err) = e.source() {
println!("Failed to derive keypair -- {}", err);
}
Err(OnDemandError::KeyDerivationFailed)
}
}
}
pub fn signer_to_pubkey(signer: Arc<Keypair>) -> std::result::Result<Pubkey, OnDemandError> {
Ok(signer.pubkey())
}
pub fn load_keypair_fs(fs_path: &str) -> Result<Arc<Keypair>, OnDemandError> {
match read_keypair_file(fs_path) {
Ok(keypair) => Ok(Arc::new(keypair)),
Err(e) => {
if let Some(err) = e.source() {
println!("Failed to read keypair file -- {}", err);
}
Err(OnDemandError::IoError)
}
}
}
pub fn fetch_zerocopy_account<T: bytemuck::Pod + Discriminator + Owner>(
client: &solana_client::rpc_client::RpcClient,
pubkey: Pubkey,
) -> Result<T, OnDemandError> {
let data = client
.get_account_data(&pubkey)
.map_err(|_| OnDemandError::AccountNotFound)?;
if data.len() < T::discriminator().len() {
return Err(OnDemandError::InvalidDiscriminator);
}
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != T::discriminator() {
return Err(OnDemandError::InvalidDiscriminator);
}
Ok(*bytemuck::try_from_bytes::<T>(&data[8..])
.map_err(|_| OnDemandError::AnchorParseError)?)
}
pub fn fetch_zerocopy_account_sync<C: SyncClient, T: bytemuck::Pod + Discriminator + Owner>(
client: &C,
pubkey: Pubkey,
) -> Result<T, OnDemandError> {
let data = client
.get_account_data(&pubkey)
.map_err(|_| OnDemandError::AccountNotFound)?
.ok_or(OnDemandError::AccountNotFound)?;
if data.len() < T::discriminator().len() {
return Err(OnDemandError::InvalidDiscriminator);
}
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != T::discriminator() {
return Err(OnDemandError::InvalidDiscriminator);
}
Ok(*bytemuck::try_from_bytes::<T>(&data[8..])
.map_err(|_| OnDemandError::AnchorParseError)?)
}
pub async fn fetch_zerocopy_account_async<T: bytemuck::Pod + Discriminator + Owner>(
client: &NonblockingRpcClient,
pubkey: Pubkey,
) -> Result<T, OnDemandError> {
let data = client
.get_account_data(&pubkey)
.await
.map_err(|_| OnDemandError::AccountNotFound)?;
if data.len() < T::discriminator().len() {
return Err(OnDemandError::InvalidDiscriminator);
}
let mut disc_bytes = [0u8; 8];
disc_bytes.copy_from_slice(&data[..8]);
if disc_bytes != T::discriminator() {
return Err(OnDemandError::InvalidDiscriminator);
}
Ok(*bytemuck::try_from_bytes::<T>(&data[8..])
.map_err(|_| OnDemandError::AnchorParseError)?)
}
pub fn fetch_borsh_account<T: Discriminator + Owner + AccountDeserialize>(
client: &solana_client::rpc_client::RpcClient,
pubkey: Pubkey,
) -> Result<T, OnDemandError> {
let account_data = client
.get_account_data(&pubkey)
.map_err(|_| OnDemandError::AccountNotFound)?;
T::try_deserialize(&mut account_data.as_slice())
.map_err(|_| OnDemandError::AnchorParseError)
}
pub async fn fetch_borsh_account_async<T: Discriminator + Owner + AccountDeserialize>(
client: &NonblockingRpcClient,
pubkey: Pubkey,
) -> Result<T, OnDemandError> {
let account_data = client
.get_account_data(&pubkey)
.await
.map_err(|_| OnDemandError::AccountNotFound)?;
T::try_deserialize(&mut account_data.as_slice())
.map_err(|_| OnDemandError::AnchorParseError)
}
pub fn fetch_borsh_account_sync<C: SyncClient, T: Discriminator + Owner + AccountDeserialize>(
client: &C,
pubkey: Pubkey,
) -> Result<T, OnDemandError> {
let data = client
.get_account_data(&pubkey)
.map_err(|_| OnDemandError::AccountNotFound)?
.ok_or(OnDemandError::AccountNotFound)?;
T::try_deserialize(&mut data.as_slice()).map_err(|_| OnDemandError::AnchorParseError)
}