use borsh::BorshDeserialize;
use flate2::read::ZlibDecoder;
use solana_idl::Idl;
use solana_program::pubkey::Pubkey;
use std::io::Read;
use crate::{
errors::{ChainsawError, ChainsawResult},
try_idl_address, IdlProvider,
};
use solana_client::rpc_client::RpcClient;
use solana_sdk::commitment_config::CommitmentConfig;
use super::types::*;
pub struct IdlClient {
provider: IdlProvider,
client: RpcClient,
}
impl IdlClient {
pub fn new(
provider: IdlProvider,
cluster: Cluster,
commitment: CommitmentConfig,
) -> Self {
let client =
RpcClient::new_with_commitment(cluster.endpoint(), commitment);
Self { provider, client }
}
pub fn fetch_idl(
&self,
program_id: Pubkey,
) -> ChainsawResult<FetchIdlResult> {
let (acc, idl_address) = self.fetch_idl_account(&program_id)?;
let (idl, json) = decode_idl_data(&acc.data)?;
Ok(FetchIdlResult {
idl,
account: acc,
program_id,
idl_address,
json,
})
}
pub fn fetch_idl_json(
&self,
program_id: Pubkey,
) -> ChainsawResult<FetchIdlJsonResult> {
let (acc, idl_address) = self.fetch_idl_account(&program_id)?;
let json = unzip(&acc.data)?;
Ok(FetchIdlJsonResult {
account: acc,
program_id,
idl_address,
json,
})
}
pub fn fetch_idl_account(
&self,
address: &Pubkey,
) -> ChainsawResult<(IdlContainerAccount, Pubkey)> {
let (data, idl_address) = self.fetch_idl_container_data(address)?;
let acc = BorshDeserialize::deserialize(&mut data.as_slice()).map_err(
|err| {
ChainsawError::IdlContainerAccountDeserializationError(
address.to_string(),
idl_address.to_string(),
err.to_string(),
)
},
)?;
Ok((acc, idl_address))
}
pub fn fetch_idl_container_data(
&self,
address: &Pubkey,
) -> ChainsawResult<(Vec<u8>, Pubkey)> {
let idl_address = self.idl_address(address)?;
let account_data = self.client.get_account_data(&idl_address);
let data = match self.provider {
IdlProvider::Anchor => {
let data = account_data?;
if data.len() < 8 {
return Err(
ChainsawError::AnchorIdlDataNeedsToBeAtLeast8Bytes,
);
}
data[8..].to_vec()
}
IdlProvider::Shank => account_data?,
};
Ok((data, idl_address))
}
pub fn idl_address(&self, program_id: &Pubkey) -> ChainsawResult<Pubkey> {
try_idl_address(&self.provider, program_id)
}
pub fn for_anchor_on_mainnet() -> Self {
Self::new(
IdlProvider::Anchor,
Cluster::MainnetBeta,
CommitmentConfig::processed(),
)
}
pub fn for_anchor_on_devnet() -> Self {
Self::new(
IdlProvider::Anchor,
Cluster::Devnet,
CommitmentConfig::processed(),
)
}
pub fn for_anchor_on_rpc(url: String) -> Self {
Self::new(
IdlProvider::Anchor,
Cluster::Custom(url),
CommitmentConfig::processed(),
)
}
}
fn decode_idl_data(data: &[u8]) -> ChainsawResult<(Idl, String)> {
let json = unzip(data)?;
let idl: Idl = serde_json::from_str(&json)?;
Ok((idl, json))
}
fn unzip(bytes: &[u8]) -> ChainsawResult<String> {
let mut zlib = ZlibDecoder::new(bytes);
let mut write = String::new();
zlib.read_to_string(&mut write).map_err(|err| {
ChainsawError::IdlContainerShouldContainZlibData(err.to_string())
})?;
Ok(write)
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
use solana_program::pubkey::Pubkey;
#[test]
fn test_idl_address() {
let consumer = IdlClient::for_anchor_on_devnet();
let program_id =
Pubkey::from_str("1USDCmv8QmvZ9JaL7bmevGsNHn7ez8TNahJzCN551sb")
.unwrap();
let key = consumer.idl_address(&program_id).unwrap();
assert_eq!(
key,
Pubkey::from_str("Ahs3Spb5rZpBkJPNjRpj285332ZZN4GCfLrvSWZ1z7rE")
.unwrap()
);
}
#[cfg(feature = "test_idl_fetch")]
#[test]
fn test_idl_fetch() {
let consumer = IdlClient::for_anchor_on_mainnet();
let program_id =
Pubkey::from_str("1USDCmv8QmvZ9JaL7bmevGsNHn7ez8TNahJzCN551sb")
.unwrap();
let FetchIdlResult { idl, .. } =
consumer.fetch_idl(program_id).unwrap();
let errors = idl.errors.as_ref().expect("should have errors");
assert_eq!(errors.len(), 10);
assert_eq!(idl.instructions.len(), 27);
assert_eq!(idl.accounts.len(), 6);
assert_eq!(idl.types.len(), 4);
}
}