hyperstack_interpreter/
resolvers.rs

1use std::collections::HashMap;
2
3/// Context provided to primary key resolver functions
4pub struct ResolveContext<'a> {
5    #[allow(dead_code)]
6    pub(crate) state_id: u32,
7    pub(crate) slot: u64,
8    pub(crate) signature: String,
9    pub(crate) reverse_lookups:
10        &'a mut std::collections::HashMap<String, crate::vm::PdaReverseLookup>,
11}
12
13impl<'a> ResolveContext<'a> {
14    /// Create a new ResolveContext (primarily for use by generated code)
15    pub fn new(
16        state_id: u32,
17        slot: u64,
18        signature: String,
19        reverse_lookups: &'a mut std::collections::HashMap<String, crate::vm::PdaReverseLookup>,
20    ) -> Self {
21        Self {
22            state_id,
23            slot,
24            signature,
25            reverse_lookups,
26        }
27    }
28
29    /// Try to reverse lookup a PDA address to find the seed value
30    /// This is typically used to find the primary key from a PDA account address
31    pub fn pda_reverse_lookup(&mut self, pda_address: &str) -> Option<String> {
32        let lookup_name = "default_pda_lookup";
33        self.reverse_lookups
34            .get_mut(lookup_name)
35            .and_then(|t| t.lookup(pda_address))
36    }
37
38    pub fn slot(&self) -> u64 {
39        self.slot
40    }
41
42    pub fn signature(&self) -> &str {
43        &self.signature
44    }
45}
46
47/// Result of attempting to resolve a primary key
48pub enum KeyResolution {
49    /// Primary key successfully resolved
50    Found(String),
51
52    /// Queue this update until we see one of these instruction discriminators
53    /// The discriminators identify which instructions can populate the reverse lookup
54    QueueUntil(&'static [u8]),
55
56    /// Skip this update entirely (don't queue)
57    Skip,
58}
59
60/// Context provided to instruction hook functions
61pub struct InstructionContext<'a> {
62    pub(crate) accounts: HashMap<String, String>,
63    #[allow(dead_code)]
64    pub(crate) state_id: u32,
65    pub(crate) reverse_lookup_tx: &'a mut dyn ReverseLookupUpdater,
66    pub(crate) pending_updates: Vec<crate::vm::PendingAccountUpdate>,
67    pub(crate) registers: Option<&'a mut Vec<crate::vm::RegisterValue>>,
68    pub(crate) state_reg: Option<crate::vm::Register>,
69    #[allow(dead_code)]
70    pub(crate) compiled_paths: Option<&'a HashMap<String, crate::metrics_context::CompiledPath>>,
71    pub(crate) instruction_data: Option<&'a serde_json::Value>,
72    pub(crate) slot: Option<u64>,
73    pub(crate) signature: Option<String>,
74    pub(crate) timestamp: Option<i64>,
75    pub(crate) dirty_tracker: crate::vm::DirtyTracker,
76}
77
78pub trait ReverseLookupUpdater {
79    fn update(
80        &mut self,
81        pda_address: String,
82        seed_value: String,
83    ) -> Vec<crate::vm::PendingAccountUpdate>;
84    fn flush_pending(&mut self, pda_address: &str) -> Vec<crate::vm::PendingAccountUpdate>;
85}
86
87impl<'a> InstructionContext<'a> {
88    pub fn new(
89        accounts: HashMap<String, String>,
90        state_id: u32,
91        reverse_lookup_tx: &'a mut dyn ReverseLookupUpdater,
92    ) -> Self {
93        Self {
94            accounts,
95            state_id,
96            reverse_lookup_tx,
97            pending_updates: Vec::new(),
98            registers: None,
99            state_reg: None,
100            compiled_paths: None,
101            instruction_data: None,
102            slot: None,
103            signature: None,
104            timestamp: None,
105            dirty_tracker: crate::vm::DirtyTracker::new(),
106        }
107    }
108
109    #[allow(clippy::too_many_arguments)]
110    pub fn with_metrics(
111        accounts: HashMap<String, String>,
112        state_id: u32,
113        reverse_lookup_tx: &'a mut dyn ReverseLookupUpdater,
114        registers: &'a mut Vec<crate::vm::RegisterValue>,
115        state_reg: crate::vm::Register,
116        compiled_paths: &'a HashMap<String, crate::metrics_context::CompiledPath>,
117        instruction_data: &'a serde_json::Value,
118        slot: Option<u64>,
119        signature: Option<String>,
120        timestamp: i64,
121    ) -> Self {
122        Self {
123            accounts,
124            state_id,
125            reverse_lookup_tx,
126            pending_updates: Vec::new(),
127            registers: Some(registers),
128            state_reg: Some(state_reg),
129            compiled_paths: Some(compiled_paths),
130            instruction_data: Some(instruction_data),
131            slot,
132            signature,
133            timestamp: Some(timestamp),
134            dirty_tracker: crate::vm::DirtyTracker::new(),
135        }
136    }
137
138    /// Get an account address by its name from the instruction
139    pub fn account(&self, name: &str) -> Option<String> {
140        self.accounts.get(name).cloned()
141    }
142
143    /// Register a reverse lookup: PDA address -> seed value
144    /// This also flushes any pending account updates for this PDA
145    ///
146    /// The pending account updates are accumulated internally and can be retrieved
147    /// via `take_pending_updates()` after all hooks have executed.
148    pub fn register_pda_reverse_lookup(&mut self, pda_address: &str, seed_value: &str) {
149        let pending = self
150            .reverse_lookup_tx
151            .update(pda_address.to_string(), seed_value.to_string());
152        self.pending_updates.extend(pending);
153    }
154
155    /// Take all accumulated pending updates
156    ///
157    /// This should be called after all instruction hooks have executed to retrieve
158    /// any pending account updates that need to be reprocessed.
159    pub fn take_pending_updates(&mut self) -> Vec<crate::vm::PendingAccountUpdate> {
160        std::mem::take(&mut self.pending_updates)
161    }
162
163    pub fn dirty_tracker(&self) -> &crate::vm::DirtyTracker {
164        &self.dirty_tracker
165    }
166
167    pub fn dirty_tracker_mut(&mut self) -> &mut crate::vm::DirtyTracker {
168        &mut self.dirty_tracker
169    }
170
171    /// Get the current state register value (for generating mutations)
172    pub fn state_value(&self) -> Option<&serde_json::Value> {
173        if let (Some(registers), Some(state_reg)) = (self.registers.as_ref(), self.state_reg) {
174            Some(&registers[state_reg])
175        } else {
176            None
177        }
178    }
179
180    /// Get a field value from the entity state
181    /// This allows reading aggregated values or other entity fields
182    pub fn get<T: serde::de::DeserializeOwned>(&self, field_path: &str) -> Option<T> {
183        if let (Some(registers), Some(state_reg)) = (self.registers.as_ref(), self.state_reg) {
184            let state = &registers[state_reg];
185            self.get_nested_value(state, field_path)
186                .and_then(|v| serde_json::from_value(v.clone()).ok())
187        } else {
188            None
189        }
190    }
191
192    pub fn set<T: serde::Serialize>(&mut self, field_path: &str, value: T) {
193        if let (Some(registers), Some(state_reg)) = (self.registers.as_mut(), self.state_reg) {
194            let serialized = serde_json::to_value(value).ok();
195            if let Some(val) = serialized {
196                Self::set_nested_value_static(&mut registers[state_reg], field_path, val);
197                self.dirty_tracker.mark_replaced(field_path);
198                println!("      ✓ Set field '{}' and marked as dirty", field_path);
199            }
200        } else {
201            println!("      ⚠️  Cannot set field '{}': metrics not configured (registers={}, state_reg={:?})", 
202                field_path, self.registers.is_some(), self.state_reg);
203        }
204    }
205
206    pub fn increment(&mut self, field_path: &str, amount: i64) {
207        let current = self.get::<i64>(field_path).unwrap_or(0);
208        self.set(field_path, current + amount);
209    }
210
211    pub fn append<T: serde::Serialize>(&mut self, field_path: &str, value: T) {
212        if let (Some(registers), Some(state_reg)) = (self.registers.as_mut(), self.state_reg) {
213            let serialized = serde_json::to_value(&value).ok();
214            if let Some(val) = serialized {
215                Self::append_to_array_static(&mut registers[state_reg], field_path, val.clone());
216                self.dirty_tracker.mark_appended(field_path, val);
217                println!(
218                    "      ✓ Appended to '{}' and marked as appended",
219                    field_path
220                );
221            }
222        } else {
223            println!(
224                "      ⚠️  Cannot append to '{}': metrics not configured",
225                field_path
226            );
227        }
228    }
229
230    fn append_to_array_static(
231        value: &mut serde_json::Value,
232        path: &str,
233        new_value: serde_json::Value,
234    ) {
235        let segments: Vec<&str> = path.split('.').collect();
236        if segments.is_empty() {
237            return;
238        }
239
240        let mut current = value;
241        for segment in &segments[..segments.len() - 1] {
242            if !current.is_object() {
243                *current = serde_json::json!({});
244            }
245            let obj = current.as_object_mut().unwrap();
246            current = obj
247                .entry(segment.to_string())
248                .or_insert(serde_json::json!({}));
249        }
250
251        let last_segment = segments[segments.len() - 1];
252        if !current.is_object() {
253            *current = serde_json::json!({});
254        }
255        let obj = current.as_object_mut().unwrap();
256        let arr = obj
257            .entry(last_segment.to_string())
258            .or_insert_with(|| serde_json::json!([]));
259        if let Some(arr) = arr.as_array_mut() {
260            arr.push(new_value);
261        }
262    }
263
264    fn get_nested_value<'b>(
265        &self,
266        value: &'b serde_json::Value,
267        path: &str,
268    ) -> Option<&'b serde_json::Value> {
269        let mut current = value;
270        for segment in path.split('.') {
271            current = current.get(segment)?;
272        }
273        Some(current)
274    }
275
276    fn set_nested_value_static(
277        value: &mut serde_json::Value,
278        path: &str,
279        new_value: serde_json::Value,
280    ) {
281        let segments: Vec<&str> = path.split('.').collect();
282        if segments.is_empty() {
283            return;
284        }
285
286        let mut current = value;
287        for segment in &segments[..segments.len() - 1] {
288            if !current.is_object() {
289                *current = serde_json::json!({});
290            }
291            let obj = current.as_object_mut().unwrap();
292            current = obj
293                .entry(segment.to_string())
294                .or_insert(serde_json::json!({}));
295        }
296
297        if !current.is_object() {
298            *current = serde_json::json!({});
299        }
300        if let Some(obj) = current.as_object_mut() {
301            obj.insert(segments[segments.len() - 1].to_string(), new_value);
302        }
303    }
304
305    /// Access instruction data field
306    pub fn data<T: serde::de::DeserializeOwned>(&self, field: &str) -> Option<T> {
307        self.instruction_data
308            .and_then(|data| data.get(field))
309            .and_then(|v| serde_json::from_value(v.clone()).ok())
310    }
311
312    /// Get the current timestamp
313    pub fn timestamp(&self) -> i64 {
314        self.timestamp.unwrap_or(0)
315    }
316
317    /// Get the current slot
318    pub fn slot(&self) -> Option<u64> {
319        self.slot
320    }
321
322    /// Get the current signature
323    pub fn signature(&self) -> Option<&str> {
324        self.signature.as_deref()
325    }
326}