1use crate::types::IdlSpec;
4use std::fs;
5use std::path::Path;
6
7pub fn parse_idl_file<P: AsRef<Path>>(path: P) -> Result<IdlSpec, String> {
8 let content = fs::read_to_string(&path)
9 .map_err(|e| format!("Failed to read IDL file {:?}: {}", path.as_ref(), e))?;
10
11 parse_idl_content(&content)
12}
13
14pub fn parse_idl_content(content: &str) -> Result<IdlSpec, String> {
15 serde_json::from_str(content).map_err(|e| format!("Failed to parse IDL JSON: {}", e))
16}
17
18#[cfg(test)]
19mod tests {
20 use super::*;
21 use crate::discriminator::anchor_discriminator;
22 use sha2::{Digest, Sha256};
23
24 #[test]
25 fn test_anchor_discriminator_known_values() {
26 let disc = anchor_discriminator("global:initialize");
27 assert_eq!(disc.len(), 8);
28 assert_eq!(disc, &Sha256::digest(b"global:initialize")[..8]);
29 }
30
31 #[test]
32 fn test_anchor_account_discriminator() {
33 let disc = anchor_discriminator("account:LendingMarket");
34 assert_eq!(disc.len(), 8);
35 assert_eq!(disc, &Sha256::digest(b"account:LendingMarket")[..8]);
36 }
37
38 #[test]
39 fn test_legacy_idl_parses_without_discriminator() {
40 let json = r#"{
41 "address": "675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8",
42 "version": "0.3.0",
43 "name": "raydium_amm",
44 "instructions": [
45 {
46 "name": "initialize",
47 "accounts": [
48 { "name": "tokenProgram", "isMut": false, "isSigner": false }
49 ],
50 "args": [
51 { "name": "nonce", "type": "u8" }
52 ]
53 }
54 ],
55 "accounts": [
56 {
57 "name": "TargetOrders",
58 "type": {
59 "kind": "struct",
60 "fields": [
61 { "name": "owner", "type": { "array": ["u64", 4] } }
62 ]
63 }
64 }
65 ],
66 "types": [],
67 "errors": []
68 }"#;
69 let idl = parse_idl_content(json).expect("legacy IDL should parse");
70
71 assert_eq!(idl.instructions.len(), 1);
72 assert_eq!(idl.accounts.len(), 1);
73 assert!(idl.accounts[0].discriminator.is_empty());
74 assert!(idl.instructions[0].discriminator.is_empty());
75 assert!(idl.instructions[0].discriminant.is_none());
76 }
77
78 #[test]
79 fn test_legacy_instruction_computes_discriminator() {
80 let json = r#"{
81 "name": "raydium_amm",
82 "instructions": [
83 {
84 "name": "initialize",
85 "accounts": [],
86 "args": []
87 }
88 ],
89 "accounts": [],
90 "types": [],
91 "errors": []
92 }"#;
93 let idl = parse_idl_content(json).unwrap();
94 let disc = idl.instructions[0].get_discriminator();
95
96 assert_eq!(disc.len(), 8);
97 let expected = anchor_discriminator("global:initialize");
98 assert_eq!(disc, expected);
99 }
100
101 #[test]
102 fn test_legacy_account_computes_discriminator() {
103 let json = r#"{
104 "name": "test",
105 "instructions": [],
106 "accounts": [
107 {
108 "name": "LendingMarket",
109 "type": { "kind": "struct", "fields": [] }
110 }
111 ],
112 "types": [],
113 "errors": []
114 }"#;
115 let idl = parse_idl_content(json).unwrap();
116 let disc = idl.accounts[0].get_discriminator();
117
118 assert_eq!(disc.len(), 8);
119 let expected = anchor_discriminator("account:LendingMarket");
120 assert_eq!(disc, expected);
121 }
122
123 #[test]
124 fn test_explicit_discriminator_not_overridden() {
125 let json = r#"{
126 "name": "test",
127 "instructions": [
128 {
129 "name": "transfer",
130 "discriminator": [1, 2, 3, 4, 5, 6, 7, 8],
131 "accounts": [],
132 "args": []
133 }
134 ],
135 "accounts": [
136 {
137 "name": "TokenAccount",
138 "discriminator": [10, 20, 30, 40, 50, 60, 70, 80]
139 }
140 ],
141 "types": [],
142 "errors": []
143 }"#;
144 let idl = parse_idl_content(json).unwrap();
145
146 assert_eq!(
147 idl.instructions[0].get_discriminator(),
148 vec![1, 2, 3, 4, 5, 6, 7, 8]
149 );
150 assert_eq!(
151 idl.accounts[0].get_discriminator(),
152 vec![10, 20, 30, 40, 50, 60, 70, 80]
153 );
154 }
155
156 #[test]
157 fn test_steel_discriminant_still_works() {
158 let json = r#"{
159 "name": "test",
160 "instructions": [
161 {
162 "name": "CreateMetadataAccount",
163 "accounts": [],
164 "args": [],
165 "discriminant": { "type": "u8", "value": 0 }
166 },
167 {
168 "name": "UpdateMetadataAccount",
169 "accounts": [],
170 "args": [],
171 "discriminant": { "type": "u8", "value": 1 }
172 }
173 ],
174 "accounts": [],
175 "types": [],
176 "errors": []
177 }"#;
178 let idl = parse_idl_content(json).unwrap();
179
180 assert_eq!(
181 idl.instructions[0].get_discriminator(),
182 vec![0, 0, 0, 0, 0, 0, 0, 0]
183 );
184 assert_eq!(
185 idl.instructions[1].get_discriminator(),
186 vec![1, 0, 0, 0, 0, 0, 0, 0]
187 );
188 }
189
190 #[test]
191 fn test_legacy_event_computes_discriminator() {
192 let json = r#"{
193 "name": "test",
194 "instructions": [],
195 "accounts": [],
196 "types": [],
197 "events": [
198 { "name": "TransferEvent" }
199 ],
200 "errors": []
201 }"#;
202 let idl = parse_idl_content(json).unwrap();
203 let disc = idl.events[0].get_discriminator();
204
205 assert_eq!(disc.len(), 8);
206 let expected = anchor_discriminator("event:TransferEvent");
207 assert_eq!(disc, expected);
208 }
209
210 #[test]
211 fn test_is_mut_is_signer_aliases() {
212 let json = r#"{
213 "name": "test",
214 "instructions": [
215 {
216 "name": "do_thing",
217 "accounts": [
218 { "name": "payer", "isMut": true, "isSigner": true },
219 { "name": "dest", "writable": true, "signer": false }
220 ],
221 "args": []
222 }
223 ],
224 "accounts": [],
225 "types": [],
226 "errors": []
227 }"#;
228 let idl = parse_idl_content(json).unwrap();
229 let accounts = &idl.instructions[0].accounts;
230
231 assert!(accounts[0].is_mut);
232 assert!(accounts[0].is_signer);
233 assert!(accounts[1].is_mut);
234 assert!(!accounts[1].is_signer);
235 }
236
237 #[test]
238 fn test_constants() {
239 let json = r#"{
240 "address": "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
241 "metadata": {
242 "name": "lb_clmm",
243 "version": "0.11.0",
244 "spec": "0.1.0",
245 "description": "Created with Anchor"
246 },
247 "instructions": [],
248 "accounts": [],
249 "types": [],
250 "events": [],
251 "errors": [],
252 "constants": [
253 {
254 "name": "BASIS_POINT_MAX",
255 "type": "i32",
256 "value": "10000"
257 },
258 {
259 "name": "MAX_BIN_PER_ARRAY",
260 "type": "u64",
261 "value": "70"
262 }
263 ]
264 }"#;
265 let idl = parse_idl_content(json).expect("IDL with constants should parse");
266
267 assert_eq!(idl.constants.len(), 2);
268 assert_eq!(idl.constants[0].name, "BASIS_POINT_MAX");
269 assert_eq!(idl.constants[0].value, "10000");
270 assert_eq!(idl.constants[1].name, "MAX_BIN_PER_ARRAY");
271 assert_eq!(idl.constants[1].value, "70");
272 }
273}