Skip to content

Commit a5a95ea

Browse files
authored
Add Codama IDL type (#85)
* Add Codama IDL type * Refactor
1 parent 67a4679 commit a5a95ea

File tree

4 files changed

+92
-72
lines changed

4 files changed

+92
-72
lines changed

include-idl-cli/src/main.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::path::PathBuf;
22

33
// use goblin::error::Result;
4-
use include_idl::parse::{parse_idl_from_program_binary, IdlType};
4+
use include_idl::parse::parse_idl_from_program_binary;
55

66
use clap::{Error, Parser, Subcommand};
77

@@ -17,24 +17,21 @@ enum Commands {
1717
Parse {
1818
/// Read IDL from a solana program binary
1919
path: PathBuf,
20-
idl_type: IdlType,
2120
},
2221
}
2322

24-
// This example uses ArgEnum, so this might not be necessary.
25-
2623
pub fn main() -> Result<(), Error> {
2724
let cli = Cli::parse();
2825

2926
match &cli.command {
30-
Some(Commands::Parse { path, idl_type }) => {
27+
Some(Commands::Parse { path }) => {
3128
let buffer = std::fs::read(path).expect("Could not read file.");
32-
if let Ok(idl) = parse_idl_from_program_binary(&buffer, idl_type.clone()) {
33-
println!(" Program IDL");
29+
if let Ok((idl_type, idl_data)) = parse_idl_from_program_binary(&buffer) {
30+
println!("Program IDL ({})", idl_type);
3431
println!("============================");
35-
println!("{}", idl);
32+
println!("{}", idl_data);
3633
} else {
37-
println!("Could not find {:?} IDL in program binary", idl_type);
34+
println!("Could not find IDL in program binary");
3835
}
3936
}
4037
None => {}

include-idl/src/lib.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
#[cfg(feature = "parse")]
21
pub mod parse;
32

43
#[cfg(feature = "shrink")]
@@ -9,26 +8,21 @@ pub use shrink::compress_idl;
98

109
#[macro_export]
1110
macro_rules! include_idl {
12-
($s:expr) => {
11+
($type:path, $file:expr) => {
1312
#[cfg_attr(
1413
any(target_arch = "sbf", target_arch = "bpf"),
15-
link_section = ".solana.idl"
14+
link_section = ".idl.type"
1615
)]
1716
#[allow(dead_code)]
1817
#[no_mangle]
19-
pub static IDL_BYTES: &[u8] = include_bytes!($s);
20-
};
21-
}
18+
pub static IDL_TYPE: &[u8] = $type.as_str().as_bytes();
2219

23-
#[macro_export]
24-
macro_rules! include_kinobi_idl {
25-
($s:expr) => {
2620
#[cfg_attr(
2721
any(target_arch = "sbf", target_arch = "bpf"),
28-
link_section = ".kinobi.idl"
22+
link_section = ".idl.data"
2923
)]
3024
#[allow(dead_code)]
3125
#[no_mangle]
32-
pub static KINOBI_IDL_BYTES: &[u8] = include_bytes!($s);
26+
pub static IDL_BYTES: &[u8] = include_bytes!($file);
3327
};
3428
}

include-idl/src/parse.rs

Lines changed: 79 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,110 @@
11
#[cfg(feature = "parse")]
22
use {
3-
flate2::bufread::ZlibDecoder, goblin::elf::Elf, serde_json::Value, std::fmt, std::io::Read,
4-
std::str::FromStr,
3+
flate2::bufread::ZlibDecoder,
4+
goblin::elf::Elf,
5+
serde_json::Value,
6+
std::io::Read,
7+
std::str::{from_utf8, FromStr},
58
};
69

10+
/// Name of the section containing the IDL type value.
11+
pub const IDL_TYPE_SECTION: &str = ".idl.type";
12+
13+
/// Name of the section containing the IDL data.
14+
pub const IDL_DATA_SECTION: &str = ".idl.data";
15+
16+
const ANCHOR_IDL_TYPE: &str = "anchor";
17+
const CODAMA_IDL_TYPE: &str = "codama";
18+
19+
/// Defines the IDL type.
720
#[derive(Clone, Debug)]
821
pub enum IdlType {
922
Anchor,
10-
Kinobi,
23+
Codama,
24+
}
25+
26+
impl IdlType {
27+
pub const fn as_str(&self) -> &'static str {
28+
match self {
29+
IdlType::Anchor => ANCHOR_IDL_TYPE,
30+
IdlType::Codama => CODAMA_IDL_TYPE,
31+
}
32+
}
1133
}
1234

13-
impl fmt::Display for IdlType {
14-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35+
impl std::fmt::Display for IdlType {
36+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1537
match self {
16-
IdlType::Anchor => write!(f, "Anchor"),
17-
IdlType::Kinobi => write!(f, "Kinobi"),
38+
IdlType::Anchor => write!(f, "{ANCHOR_IDL_TYPE}"),
39+
IdlType::Codama => write!(f, "{CODAMA_IDL_TYPE}"),
1840
}
1941
}
2042
}
2143

22-
impl FromStr for IdlType {
44+
impl std::str::FromStr for IdlType {
2345
type Err = &'static str;
2446

2547
fn from_str(s: &str) -> Result<Self, &'static str> {
2648
match s.to_lowercase().as_str() {
27-
"anchor" => Ok(IdlType::Anchor),
28-
"kinobi" => Ok(IdlType::Kinobi),
49+
ANCHOR_IDL_TYPE => Ok(IdlType::Anchor),
50+
CODAMA_IDL_TYPE => Ok(IdlType::Codama),
2951
_ => Err("Invalid IDL type"),
3052
}
3153
}
3254
}
3355

34-
fn get_section_name(idl_type: IdlType) -> String {
35-
match idl_type {
36-
IdlType::Anchor => ".solana.idl".to_string(),
37-
IdlType::Kinobi => ".kinobi.idl".to_string(),
38-
}
39-
}
40-
41-
pub fn parse_idl_from_program_binary(
42-
buffer: &[u8],
43-
idl_type: IdlType,
44-
) -> goblin::error::Result<Value> {
56+
#[cfg(feature = "parse")]
57+
pub fn parse_idl_from_program_binary(buffer: &[u8]) -> goblin::error::Result<(IdlType, Value)> {
4558
let elf = Elf::parse(buffer)?;
4659

47-
let section_name = get_section_name(idl_type);
60+
let mut idl_type: Option<IdlType> = None;
61+
let mut idl_json: Option<Value> = None;
4862

49-
// Iterate over section headers and print information
63+
// Iterate over section headers and retrieve the IDL data.
5064
for sh in &elf.section_headers {
5165
let name = elf.shdr_strtab.get_at(sh.sh_name).unwrap_or("<invalid>");
52-
if name == section_name {
53-
// Get offset of .solana.idl section data
54-
let offset = sh.sh_offset as usize;
55-
56-
// Get offset & size of the compressed IDL bytes
57-
let _data_loc = &buffer[offset + 4..offset + 8];
58-
let data_loc = u32::from_le_bytes(_data_loc.try_into().unwrap());
59-
let _data_size = &buffer[offset + 8..offset + 16];
60-
let data_size = u64::from_le_bytes(_data_size.try_into().unwrap());
61-
62-
let compressed_data =
63-
&buffer[data_loc as usize..(data_loc as u64 + data_size) as usize];
64-
let mut d = ZlibDecoder::new(compressed_data);
65-
let mut decompressed_data = Vec::new();
66-
d.read_to_end(&mut decompressed_data).unwrap();
67-
68-
let json: Value = serde_json::from_slice(&decompressed_data).unwrap();
69-
return Ok(json);
66+
67+
match name {
68+
IDL_DATA_SECTION => {
69+
let (location, size) = get_section_data_offset(buffer, sh.sh_offset as usize);
70+
71+
let slice = &buffer[location..location + size];
72+
let mut compressed_data = ZlibDecoder::new(slice);
73+
let mut data = Vec::new();
74+
compressed_data.read_to_end(&mut data).unwrap();
75+
76+
idl_json = Some(serde_json::from_slice(&data).unwrap());
77+
}
78+
IDL_TYPE_SECTION => {
79+
let (location, size) = get_section_data_offset(buffer, sh.sh_offset as usize);
80+
let slice = &buffer[location..location + size];
81+
82+
idl_type = Some(IdlType::from_str(from_utf8(slice).unwrap()).unwrap());
83+
}
84+
// Ignore other sections.
85+
_ => (),
7086
}
7187
}
72-
Err(goblin::error::Error::Malformed(
73-
"Could not find .solana.idl section".to_string(),
74-
))
88+
89+
if idl_type.is_some() && idl_json.is_some() {
90+
#[allow(clippy::unnecessary_unwrap)]
91+
Ok((idl_type.unwrap(), idl_json.unwrap()))
92+
} else {
93+
// Returns an error if we could not find the IDL information.
94+
Err(goblin::error::Error::Malformed(
95+
"Could not find .idl.* sections".to_string(),
96+
))
97+
}
98+
}
99+
100+
#[cfg(feature = "parse")]
101+
#[inline(always)]
102+
fn get_section_data_offset(buffer: &[u8], offset: usize) -> (usize, usize) {
103+
let slice = &buffer[offset + 4..offset + 8];
104+
let location = u32::from_le_bytes(slice.try_into().unwrap());
105+
106+
let slice = &buffer[offset + 8..offset + 16];
107+
let size = u64::from_le_bytes(slice.try_into().unwrap());
108+
109+
(location as usize, size as usize)
75110
}

programs/asset/program/src/lib.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,12 @@ pub mod instruction;
1212
pub mod processor;
1313
pub mod utils;
1414

15+
use include_idl::{include_idl, parse::IdlType};
1516
pub use solana_program;
16-
17-
#[cfg(not(feature = "no-entrypoint"))]
18-
use include_idl::include_kinobi_idl;
19-
20-
#[cfg(not(feature = "no-entrypoint"))]
2117
use solana_security_txt::security_txt;
2218

23-
#[cfg(not(feature = "no-entrypoint"))]
24-
include_kinobi_idl!(concat!(env!("OUT_DIR"), "/kinobi.idl.zip"));
19+
include_idl!(IdlType::Codama, concat!(env!("OUT_DIR"), "/kinobi.idl.zip"));
2520

26-
#[cfg(not(feature = "no-entrypoint"))]
2721
security_txt! {
2822
// Required fields
2923
name: "Nifty Asset",

0 commit comments

Comments
 (0)