Skip to main content

hyperstack_idl/
parse.rs

1//! IDL parsing utilities
2
3use 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}