use std::{collections::HashMap, ops::Deref};
use arrayref::array_ref;
use solana_idl::{IdlType, IdlTypeDefinition, IdlTypeDefinitionTy};
use crate::idl;
#[derive(Debug)]
pub enum Matcher {
COption(usize, usize),
Bool(usize),
}
impl TryFrom<(&IdlType, &HashMap<String, &IdlTypeDefinitionTy>, usize)>
for Matcher
{
type Error = ();
fn try_from(
(ty, type_map, offset): (
&IdlType,
&HashMap<String, &IdlTypeDefinitionTy>,
usize,
),
) -> Result<Self, Self::Error> {
match ty {
IdlType::COption(inner) => {
let inner_size =
idl::idl_type_bytes(inner, Some(type_map)).unwrap_or(0);
Ok(Matcher::COption(offset, inner_size))
}
IdlType::Bool => Ok(Matcher::Bool(offset)),
_ => Err(()),
}
}
}
impl Matcher {
fn matches(&self, buf: &[u8]) -> bool {
use Matcher::*;
match self {
COption(offset, _) => {
let src = array_ref![buf, *offset, 4];
matches!(src, [1, 0, 0, 0]) || matches!(src, [0, 0, 0, 0])
}
Bool(offset) => {
let src = array_ref![buf, *offset, 1];
matches!(src, [0] | [1])
}
}
}
}
#[derive(Debug)]
pub struct MatchDiscriminators(Vec<MatchDiscriminator>);
impl From<(&[IdlTypeDefinition], &HashMap<String, &IdlTypeDefinitionTy>)>
for MatchDiscriminators
{
fn from(
(accounts, type_map): (
&[IdlTypeDefinition],
&HashMap<String, &IdlTypeDefinitionTy>,
),
) -> Self {
let mut discs = accounts
.iter()
.flat_map(|acc| MatchDiscriminator::new(acc.clone(), type_map))
.collect::<Vec<_>>();
discs.sort_by_key(|f| f.min_total_size);
Self(discs)
}
}
impl Deref for MatchDiscriminators {
type Target = Vec<MatchDiscriminator>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl MatchDiscriminators {
pub fn find_match(&self, buf: &[u8]) -> Option<IdlTypeDefinition> {
self.find_matching_disc(buf)
.map(|disc| disc.account.clone())
}
pub fn find_match_name(&self, buf: &[u8]) -> Option<&str> {
self.find_matching_disc(buf).map(|disc| disc.account_name())
}
fn find_matching_disc(&self, buf: &[u8]) -> Option<&MatchDiscriminator> {
let mut candidates = Vec::new();
for disc in self.iter() {
if disc.matches_account(buf) {
if disc.min_total_size == buf.len() {
return Some(disc);
} else {
candidates.push(disc);
}
}
}
let mut best_candidate = None::<&MatchDiscriminator>;
for candidate in candidates {
if let Some(disc) = best_candidate {
if candidate.matchers.len() > disc.matchers.len() {
best_candidate = Some(candidate);
}
} else {
best_candidate = Some(candidate);
}
}
best_candidate
}
}
#[derive(Debug)]
pub struct MatchDiscriminator {
pub account: IdlTypeDefinition,
min_total_size: usize,
matchers: Vec<Matcher>,
}
impl MatchDiscriminator {
pub fn new(
account: IdlTypeDefinition,
type_map: &HashMap<String, &IdlTypeDefinitionTy>,
) -> Option<Self> {
let account_sizes = base_account_sizes(&account, type_map);
match account_sizes {
Some((field_sizes, field_offsets)) => {
let min_total_size = field_sizes.iter().sum();
let matchers =
account_matchers(&account, type_map, &field_offsets);
if matchers.is_empty() {
None
} else {
Some(Self {
account,
min_total_size,
matchers,
})
}
}
_ => None,
}
}
pub fn account_name(&self) -> &str {
&self.account.name
}
fn matches_account(&self, buf: &[u8]) -> bool {
if buf.len() < self.min_total_size {
return false;
}
self.matchers.iter().all(|matcher| matcher.matches(buf))
}
}
fn account_matchers(
account: &IdlTypeDefinition,
type_map: &HashMap<String, &IdlTypeDefinitionTy>,
offsets: &[usize],
) -> Vec<Matcher> {
match &account.ty {
IdlTypeDefinitionTy::Struct { fields } => {
let mut matchers = Vec::new();
for (field, offset) in fields.iter().zip(offsets) {
if let Ok(matcher) =
Matcher::try_from((&field.ty, type_map, *offset))
{
matchers.push(matcher)
}
}
matchers
}
_ => Vec::new(),
}
}
fn base_account_sizes(
account: &IdlTypeDefinition,
type_map: &HashMap<String, &IdlTypeDefinitionTy>,
) -> Option<(Vec<usize>, Vec<usize>)> {
let mut offsets = Vec::new();
let mut sizes = Vec::new();
let mut offset = 0;
match &account.ty {
IdlTypeDefinitionTy::Struct { fields } => {
for field in fields {
if let Some(size) =
idl::idl_type_bytes(&field.ty, Some(type_map))
{
offsets.push(offset);
sizes.push(size);
offset += size;
}
}
Some((sizes, offsets))
}
_ => None, }
}