1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
pub mod account;
pub mod explorer;
pub mod pubkey;
pub mod thread;

use std::fmt::{Debug, Display, Formatter};

use anchor_lang::{prelude::Pubkey, prelude::*, AnchorDeserialize};
use base64::{engine::general_purpose::STANDARD, Engine};

/// Crate build information
#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
pub struct CrateInfo {
    /// The link to the crate spec
    pub spec: String,
    /// Arbitrary blob that can be set by developers
    pub blob: String,
}

impl Display for CrateInfo {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "spec:{} blob:{}", self.spec, self.blob)
    }
}

/// Parse struct from sol_set_return_data in program's logs
pub trait ProgramLogsDeserializable {
    fn try_from_program_logs(
        program_logs: Vec<String>,
        program_id: &Pubkey,
    ) -> std::result::Result<Self, ErrorCode>
    where
        Self: Sized;
}

impl<T> ProgramLogsDeserializable for T
where
    T: AnchorDeserialize,
{
    fn try_from_program_logs(
        program_logs: Vec<String>,
        program_id: &Pubkey,
    ) -> std::result::Result<T, ErrorCode> {
        // A Program's return data appears in the log in this format:
        // "Program return: <program-id> <program-generated-data-in-base64>"
        // https://github.com/solana-labs/solana/blob/b8837c04ec3976c9c16d028fbee86f87823fb97f/program-runtime/src/stable_log.rs#L68
        let preimage = format!("Program return: {} ", program_id);

        // Extract the return data after Program return: <program-id>
        let get_return_data_base64 = program_logs
            .iter()
            .find(|&s| s.starts_with(&preimage))
            .ok_or(ErrorCode::AccountDidNotDeserialize)?
            .strip_prefix(&preimage)
            .ok_or(ErrorCode::AccountDidNotDeserialize)?;

        let decoded = STANDARD
            .decode(get_return_data_base64)
            .map_err(|_err| ErrorCode::AccountDidNotDeserialize)?;

        T::try_from_slice(&decoded).map_err(|_err| ErrorCode::AccountDidNotDeserialize)
    }
}