chainparser/ixs/
instruction_mapper.rs

1use lazy_static::lazy_static;
2use log::*;
3use std::{collections::HashMap, str::FromStr};
4
5use solana_idl::{Idl, IdlInstruction};
6use solana_sdk::pubkey::Pubkey;
7
8use super::{discriminator::discriminator_from_ix, ParseableInstruction};
9
10#[rustfmt::skip]
11lazy_static! {
12    pub static ref BUILTIN_PROGRAMS: HashMap<Pubkey, &'static str> = [
13        ("System Program"                , "11111111111111111111111111111111")           ,
14        ("BPF Upgradeable Loader"        , "BPFLoaderUpgradeab1e11111111111111111111111"),
15        ("BPF Loader 2"                  , "BPFLoader2111111111111111111111111111111111"),
16        ("Config Program"                , "Config1111111111111111111111111111111111111"),
17        ("Feature Program"               , "Feature111111111111111111111111111111111111"),
18        ("Native Loader"                 , "NativeLoader1111111111111111111111111111111"),
19        ("Stake Program"                 , "Stake11111111111111111111111111111111111111"),
20        ("Sysvar"                        , "Sysvar1111111111111111111111111111111111111"),
21        ("Vote Program"                  , "Vote111111111111111111111111111111111111111"),
22        ("Stake Config"                  , "StakeConfig11111111111111111111111111111111"),
23        ("Sol Program"                   , "So11111111111111111111111111111111111111112"),
24        ("Clock Sysvar"                  , "SysvarC1ock11111111111111111111111111111111"),
25        ("Epoch Schedule Sysvar"         , "SysvarEpochSchedu1e111111111111111111111111"),
26        ("Fees Sysvar"                   , "SysvarFees111111111111111111111111111111111"),
27        ("Last Restart Slog Sysvar"      , "SysvarLastRestartS1ot1111111111111111111111"),
28        ("Recent Blockhashes Sysvar"     , "SysvarRecentB1ockHashes11111111111111111111"),
29        ("Rent Sysvar"                   , "SysvarRent111111111111111111111111111111111"),
30        ("Slot Hashes"                   , "SysvarS1otHashes111111111111111111111111111"),
31        ("Slot History"                  , "SysvarS1otHistory11111111111111111111111111"),
32        ("Stake History"                 , "SysvarStakeHistory1111111111111111111111111"),
33        ("MagicBlock System Program"     , "Magic11111111111111111111111111111111111111"),
34        ("MagicBlock Delegation Program" , "DELeGGvXpWV2fqJUhqcF5ZSYMS4JTLjteaAMARRSaeSh"),
35        ("Luzid Authority"               , "LUzidNSiPNjYNkxZcUm5hYHwnWPwsUfh2US1cpWwaBm"),
36    ]
37    .into_iter()
38    .map(|(name, key)| (Pubkey::from_str(key).unwrap(), name))
39    .collect();
40}
41
42pub fn map_instruction(
43    instruction: &impl ParseableInstruction,
44    idl: Option<&Idl>,
45) -> InstructionMapResult {
46    InstructionMapper::map_accounts(instruction, idl)
47}
48
49pub struct InstructionMapper {
50    idl_instruction: IdlInstruction,
51}
52
53pub struct InstructionMapResult {
54    pub accounts: HashMap<Pubkey, String>,
55    pub instruction_name: Option<String>,
56    pub program_name: Option<String>,
57}
58
59impl InstructionMapper {
60    /// First determines which IDL to use via the [program_id] of the instruction.
61    /// Then it finds the best matching IDL instruction for provided instruction and
62    /// creates an entry for each account pubkey providing its name.
63    pub fn map_accounts(
64        instruction: &impl ParseableInstruction,
65        idl: Option<&Idl>,
66    ) -> InstructionMapResult {
67        let mapper = idl
68            .as_ref()
69            .and_then(|idl| Self::determine_accounts_mapper(instruction, idl));
70        let program_name = idl.as_ref().map(|idl| idl.name.to_string());
71        let program_id = instruction.program_id();
72
73        let mut accounts = HashMap::new();
74        let mut instruction_name = None::<String>;
75        let ix_accounts = instruction.accounts();
76        for (idx, pubkey) in ix_accounts.into_iter().enumerate() {
77            if let Some(name) = BUILTIN_PROGRAMS.get(&pubkey) {
78                accounts.insert(pubkey, name.to_string());
79                continue;
80            }
81            if let Some(program_name) = program_name.as_ref() {
82                if &pubkey == program_id {
83                    accounts.insert(pubkey, program_name.to_string());
84                    continue;
85                }
86            }
87            if let Some(mapper) = &mapper {
88                let name = mapper
89                    .idl_instruction
90                    .accounts
91                    .get(idx)
92                    .map(|x| x.name().to_string());
93                if let Some(name) = name {
94                    accounts.insert(pubkey, name);
95                }
96                instruction_name
97                    .replace(mapper.idl_instruction.name.to_string());
98            }
99        }
100        let program_name = idl.map(|x| x.name.to_string()).or_else(|| {
101            BUILTIN_PROGRAMS.get(program_id).map(|x| x.to_string())
102        });
103
104        InstructionMapResult {
105            accounts,
106            instruction_name,
107            program_name,
108        }
109    }
110
111    fn determine_accounts_mapper(
112        instruction: &impl ParseableInstruction,
113        idl: &Idl,
114    ) -> Option<InstructionMapper> {
115        find_best_matching_idl_ix(&idl.instructions, instruction)
116            .map(|idl_instruction| InstructionMapper { idl_instruction })
117    }
118}
119
120fn find_best_matching_idl_ix(
121    ix_idls: &[IdlInstruction],
122    ix: &impl ParseableInstruction,
123) -> Option<IdlInstruction> {
124    let mut best_match = None;
125    let mut best_match_score = 0;
126    for idl_ix in ix_idls {
127        let disc = discriminator_from_ix(idl_ix);
128        trace!("Discriminator for '{}': {:?}", idl_ix.name, disc);
129        if disc.len() > ix.data().len() {
130            continue;
131        }
132        let mut score = 0;
133        for (a, b) in disc.iter().zip(ix.data()) {
134            if a != b {
135                break;
136            }
137            score += 1;
138        }
139        if score > best_match_score {
140            best_match = Some(idl_ix);
141            best_match_score = score;
142        }
143    }
144    best_match.cloned()
145}