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!(idl.instructions[0].get_discriminator(), vec![0]);
181        assert_eq!(idl.instructions[1].get_discriminator(), vec![1]);
182    }
183
184    #[test]
185    fn test_legacy_event_computes_discriminator() {
186        let json = r#"{
187            "name": "test",
188            "instructions": [],
189            "accounts": [],
190            "types": [],
191            "events": [
192                { "name": "TransferEvent" }
193            ],
194            "errors": []
195        }"#;
196        let idl = parse_idl_content(json).unwrap();
197        let disc = idl.events[0].get_discriminator();
198
199        assert_eq!(disc.len(), 8);
200        let expected = anchor_discriminator("event:TransferEvent");
201        assert_eq!(disc, expected);
202    }
203
204    #[test]
205    fn test_is_mut_is_signer_aliases() {
206        let json = r#"{
207            "name": "test",
208            "instructions": [
209                {
210                    "name": "do_thing",
211                    "accounts": [
212                        { "name": "payer", "isMut": true, "isSigner": true },
213                        { "name": "dest", "writable": true, "signer": false }
214                    ],
215                    "args": []
216                }
217            ],
218            "accounts": [],
219            "types": [],
220            "errors": []
221        }"#;
222        let idl = parse_idl_content(json).unwrap();
223        let accounts = &idl.instructions[0].accounts;
224
225        assert!(accounts[0].is_mut);
226        assert!(accounts[0].is_signer);
227        assert!(accounts[1].is_mut);
228        assert!(!accounts[1].is_signer);
229    }
230
231    #[test]
232    fn test_constants() {
233        let json = r#"{
234            "address": "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo",
235            "metadata": {
236                "name": "lb_clmm",
237                "version": "0.11.0",
238                "spec": "0.1.0",
239                "description": "Created with Anchor"
240            },
241            "instructions": [],
242            "accounts": [],
243            "types": [],
244            "events": [],
245            "errors": [],
246            "constants": [
247                {
248                    "name": "BASIS_POINT_MAX",
249                    "type": "i32",
250                    "value": "10000"
251                },
252                {
253                    "name": "MAX_BIN_PER_ARRAY",
254                    "type": "u64",
255                    "value": "70"
256                }
257            ]
258        }"#;
259        let idl = parse_idl_content(json).expect("IDL with constants should parse");
260
261        assert_eq!(idl.constants.len(), 2);
262        assert_eq!(idl.constants[0].name, "BASIS_POINT_MAX");
263        assert_eq!(idl.constants[0].value, "10000");
264        assert_eq!(idl.constants[1].name, "MAX_BIN_PER_ARRAY");
265        assert_eq!(idl.constants[1].value, "70");
266    }
267}