use base64::engine::general_purpose::STANDARD as BASE64;
use base64::Engine;
use serde_json::{Map, Value};
use sha2::{Digest, Sha256};
use crate::idl::{Idl, IdlDefinedFields, IdlEvent, IdlField, IdlType, IdlTypeDefTy};
const MAX_DEPTH: usize = 64;
#[derive(Debug, Clone, PartialEq)]
pub struct DecodedEvent {
pub name: String,
pub data: Value,
}
impl DecodedEvent {
pub fn to_json(&self) -> String {
serde_json::json!({ "name": self.name, "data": self.data }).to_string()
}
}
#[derive(Debug)]
pub enum DecodeError {
InvalidIdl(String),
InvalidData(String),
UnknownType(String),
UnsupportedType(String),
}
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
DecodeError::InvalidIdl(msg) => write!(f, "invalid IDL: {}", msg),
DecodeError::InvalidData(msg) => write!(f, "invalid event data: {}", msg),
DecodeError::UnknownType(name) => write!(f, "unknown type in IDL: {}", name),
DecodeError::UnsupportedType(name) => write!(f, "unsupported IDL type: {}", name),
}
}
}
impl std::error::Error for DecodeError {}
pub fn event_discriminator(name: &str) -> [u8; 8] {
let mut hasher = Sha256::new();
hasher.update(b"event:");
hasher.update(name.as_bytes());
let digest = hasher.finalize();
let mut out = [0u8; 8];
out.copy_from_slice(&digest[..8]);
out
}
pub fn decode_event(idl: &Idl, data_b64: &str) -> Result<Option<DecodedEvent>, DecodeError> {
let bytes = BASE64
.decode(data_b64.trim())
.map_err(|e| DecodeError::InvalidData(format!("base64: {}", e)))?;
if bytes.len() < 8 {
return Ok(None);
}
for event in &idl.events {
if bytes[..8] == event.discriminator_bytes() {
let mut reader = Reader::new(&bytes[8..]);
let data = decode_event_fields(idl, event, &mut reader)?;
return Ok(Some(DecodedEvent {
name: event.name.clone(),
data,
}));
}
}
Ok(None)
}
pub fn decode_events(idl: &Idl, data_logs: &[String]) -> Vec<DecodedEvent> {
data_logs
.iter()
.filter_map(|data| match decode_event(idl, data) {
Ok(found) => found,
Err(err) => {
log::debug!("skipping undecodable data log: {}", err);
None
}
})
.collect()
}
fn decode_event_fields(
idl: &Idl,
event: &IdlEvent,
reader: &mut Reader,
) -> Result<Value, DecodeError> {
match &event.fields {
Some(fields) => decode_named_fields(idl, fields, reader, 0),
None => decode_defined(idl, &event.name, reader, 0),
}
}
fn decode_defined(
idl: &Idl,
name: &str,
reader: &mut Reader,
depth: usize,
) -> Result<Value, DecodeError> {
if depth > MAX_DEPTH {
return Err(DecodeError::InvalidData(
"max nesting depth exceeded".into(),
));
}
let type_def = idl
.find_type(name)
.ok_or_else(|| DecodeError::UnknownType(name.to_string()))?;
match &type_def.ty {
IdlTypeDefTy::Struct { fields } => {
decode_defined_fields(idl, fields.as_ref(), reader, depth)
}
IdlTypeDefTy::Enum { variants } => {
let index = reader.read_u8()? as usize;
let variant = variants.get(index).ok_or_else(|| {
DecodeError::InvalidData(format!("enum {} has no variant {}", name, index))
})?;
match &variant.fields {
None => Ok(Value::String(variant.name.clone())),
Some(fields) => {
let inner = decode_defined_fields(idl, Some(fields), reader, depth)?;
let mut map = Map::new();
map.insert(variant.name.clone(), inner);
Ok(Value::Object(map))
}
}
}
IdlTypeDefTy::Alias { value } => decode_type(idl, value, reader, depth + 1),
}
}
fn decode_defined_fields(
idl: &Idl,
fields: Option<&IdlDefinedFields>,
reader: &mut Reader,
depth: usize,
) -> Result<Value, DecodeError> {
match fields {
None => Ok(Value::Object(Map::new())),
Some(IdlDefinedFields::Named(fields)) => decode_named_fields(idl, fields, reader, depth),
Some(IdlDefinedFields::Tuple(types)) => {
let mut items = Vec::with_capacity(types.len());
for ty in types {
items.push(decode_type(idl, ty, reader, depth + 1)?);
}
Ok(Value::Array(items))
}
}
}
fn decode_named_fields(
idl: &Idl,
fields: &[IdlField],
reader: &mut Reader,
depth: usize,
) -> Result<Value, DecodeError> {
let mut map = Map::new();
for field in fields {
let value = decode_type(idl, &field.ty, reader, depth + 1)?;
map.insert(field.name.clone(), value);
}
Ok(Value::Object(map))
}
fn decode_type(
idl: &Idl,
ty: &IdlType,
reader: &mut Reader,
depth: usize,
) -> Result<Value, DecodeError> {
if depth > MAX_DEPTH {
return Err(DecodeError::InvalidData(
"max nesting depth exceeded".into(),
));
}
match ty {
IdlType::Bool => match reader.read_u8()? {
0 => Ok(Value::Bool(false)),
1 => Ok(Value::Bool(true)),
other => Err(DecodeError::InvalidData(format!(
"invalid bool tag {}",
other
))),
},
IdlType::U8 => Ok(Value::from(reader.read_u8()?)),
IdlType::I8 => Ok(Value::from(reader.read_i8()?)),
IdlType::U16 => Ok(Value::from(reader.read_u16()?)),
IdlType::I16 => Ok(Value::from(reader.read_i16()?)),
IdlType::U32 => Ok(Value::from(reader.read_u32()?)),
IdlType::I32 => Ok(Value::from(reader.read_i32()?)),
IdlType::U64 => Ok(Value::from(reader.read_u64()?)),
IdlType::I64 => Ok(Value::from(reader.read_i64()?)),
IdlType::U128 => Ok(Value::String(reader.read_u128()?.to_string())),
IdlType::I128 => Ok(Value::String(reader.read_i128()?.to_string())),
IdlType::F32 => Ok(Value::from(reader.read_f32()? as f64)),
IdlType::F64 => Ok(Value::from(reader.read_f64()?)),
IdlType::Bytes => {
let len = reader.read_len()?;
let bytes = reader.take(len)?;
Ok(Value::String(BASE64.encode(bytes)))
}
IdlType::String => {
let len = reader.read_len()?;
let bytes = reader.take(len)?;
Ok(Value::String(String::from_utf8_lossy(bytes).into_owned()))
}
IdlType::Pubkey => {
let bytes = reader.take(32)?;
Ok(Value::String(bs58::encode(bytes).into_string()))
}
IdlType::Vec(elem) => {
let len = reader.read_len()?;
let mut items = Vec::new();
for _ in 0..len {
items.push(decode_type(idl, elem, reader, depth + 1)?);
}
Ok(Value::Array(items))
}
IdlType::Option(inner) => match reader.read_u8()? {
0 => Ok(Value::Null),
1 => decode_type(idl, inner, reader, depth + 1),
other => Err(DecodeError::InvalidData(format!(
"invalid option tag {}",
other
))),
},
IdlType::Array(elem, len) => {
let mut items = Vec::with_capacity((*len).min(4096));
for _ in 0..*len {
items.push(decode_type(idl, elem, reader, depth + 1)?);
}
Ok(Value::Array(items))
}
IdlType::Defined(name) => decode_defined(idl, name, reader, depth + 1),
IdlType::Unsupported(desc) => Err(DecodeError::UnsupportedType(desc.clone())),
}
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
}
macro_rules! read_le {
($name:ident, $ty:ty) => {
fn $name(&mut self) -> Result<$ty, DecodeError> {
let bytes = self.take(std::mem::size_of::<$ty>())?;
Ok(<$ty>::from_le_bytes(bytes.try_into().unwrap()))
}
};
}
impl<'a> Reader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
fn remaining(&self) -> usize {
self.data.len() - self.pos
}
fn take(&mut self, len: usize) -> Result<&'a [u8], DecodeError> {
if len > self.remaining() {
return Err(DecodeError::InvalidData(format!(
"payload truncated: wanted {} bytes at offset {}, {} remain",
len,
self.pos,
self.remaining()
)));
}
let slice = &self.data[self.pos..self.pos + len];
self.pos += len;
Ok(slice)
}
fn read_len(&mut self) -> Result<usize, DecodeError> {
let len = self.read_u32()? as usize;
if len > self.remaining() {
return Err(DecodeError::InvalidData(format!(
"length prefix {} exceeds remaining payload {}",
len,
self.remaining()
)));
}
Ok(len)
}
read_le!(read_u16, u16);
read_le!(read_u32, u32);
read_le!(read_u64, u64);
read_le!(read_u128, u128);
read_le!(read_i8, i8);
read_le!(read_i16, i16);
read_le!(read_i32, i32);
read_le!(read_i64, i64);
read_le!(read_i128, i128);
read_le!(read_f32, f32);
read_le!(read_f64, f64);
fn read_u8(&mut self) -> Result<u8, DecodeError> {
Ok(self.take(1)?[0])
}
}