miden_core/mast/node/
dyn_node.rs1use alloc::{boxed::Box, vec::Vec};
2use core::fmt;
3
4#[cfg(feature = "serde")]
5use serde::{Deserialize, Serialize};
6
7use super::{MastForestContributor, MastNodeExt};
8use crate::{
9 Felt, Word,
10 mast::{MastForest, MastForestError, MastNodeId},
11 operations::opcodes,
12 prettier::{Document, PrettyPrint, const_text},
13 utils::LookupByIdx,
14};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
22#[cfg_attr(all(feature = "arbitrary", test), miden_test_serde_macros::serde_test)]
23pub struct DynNode {
24 is_dyncall: bool,
25 digest: Word,
26}
27
28impl DynNode {
30 pub const DYN_DOMAIN: Felt = Felt::new_unchecked(opcodes::DYN as u64);
32
33 pub const DYNCALL_DOMAIN: Felt = Felt::new_unchecked(opcodes::DYNCALL as u64);
35}
36
37impl DynNode {
39 pub const DYNCALL_DEFAULT_DIGEST: Word = Word::new([
41 Felt::new_unchecked(16830415514927835337),
42 Felt::new_unchecked(12164645914672292987),
43 Felt::new_unchecked(13192574193032437705),
44 Felt::new_unchecked(4604554596675732269),
45 ]);
46
47 pub const DYN_DEFAULT_DIGEST: Word = Word::new([
49 Felt::new_unchecked(16952228088962355159),
50 Felt::new_unchecked(5793482471479538911),
51 Felt::new_unchecked(14446299416172848527),
52 Felt::new_unchecked(13522295374716441620),
53 ]);
54}
55
56impl DynNode {
58 pub fn is_dyncall(&self) -> bool {
60 self.is_dyncall
61 }
62
63 pub fn domain(&self) -> Felt {
65 if self.is_dyncall() {
66 Self::DYNCALL_DOMAIN
67 } else {
68 Self::DYN_DOMAIN
69 }
70 }
71}
72
73impl DynNode {
77 pub(super) fn to_display<'a>(&'a self, _mast_forest: &'a MastForest) -> impl fmt::Display + 'a {
78 self.clone()
79 }
80
81 pub(super) fn to_pretty_print<'a>(
82 &'a self,
83 _mast_forest: &'a MastForest,
84 ) -> impl PrettyPrint + 'a {
85 self.clone()
86 }
87}
88
89impl PrettyPrint for DynNode {
90 fn render(&self) -> Document {
91 if self.is_dyncall() {
92 const_text("dyncall")
93 } else {
94 const_text("dyn")
95 }
96 }
97}
98
99impl fmt::Display for DynNode {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 self.pretty_print(f)
102 }
103}
104
105impl MastNodeExt for DynNode {
109 fn digest(&self) -> Word {
111 self.digest
112 }
113
114 fn to_display<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn fmt::Display + 'a> {
115 Box::new(DynNode::to_display(self, mast_forest))
116 }
117
118 fn to_pretty_print<'a>(&'a self, mast_forest: &'a MastForest) -> Box<dyn PrettyPrint + 'a> {
119 Box::new(DynNode::to_pretty_print(self, mast_forest))
120 }
121
122 fn has_children(&self) -> bool {
123 false
124 }
125
126 fn append_children_to(&self, _target: &mut Vec<MastNodeId>) {
127 }
129
130 fn for_each_child<F>(&self, _f: F)
131 where
132 F: FnMut(MastNodeId),
133 {
134 }
136
137 fn domain(&self) -> Felt {
138 self.domain()
139 }
140
141 type Builder = DynNodeBuilder;
142
143 fn to_builder(self, _forest: &MastForest) -> Self::Builder {
144 let builder = if self.is_dyncall {
145 DynNodeBuilder::new_dyncall()
146 } else {
147 DynNodeBuilder::new_dyn()
148 };
149 builder.with_digest(self.digest)
150 }
151}
152
153#[cfg(all(feature = "arbitrary", test))]
157impl proptest::prelude::Arbitrary for DynNode {
158 type Parameters = ();
159
160 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
161 use proptest::prelude::*;
162
163 any::<bool>()
165 .prop_map(|is_dyncall| {
166 if is_dyncall {
167 DynNodeBuilder::new_dyncall().build()
168 } else {
169 DynNodeBuilder::new_dyn().build()
170 }
171 })
172 .no_shrink() .boxed()
174 }
175
176 type Strategy = proptest::prelude::BoxedStrategy<Self>;
177}
178
179#[derive(Debug)]
182pub struct DynNodeBuilder {
183 is_dyncall: bool,
184 digest: Option<Word>,
185}
186
187impl DynNodeBuilder {
188 pub fn new_dyn() -> Self {
190 Self { is_dyncall: false, digest: None }
191 }
192
193 pub fn new_dyncall() -> Self {
195 Self { is_dyncall: true, digest: None }
196 }
197
198 pub fn build(self) -> DynNode {
200 let digest = if let Some(forced_digest) = self.digest {
202 forced_digest
203 } else if self.is_dyncall {
204 DynNode::DYNCALL_DEFAULT_DIGEST
205 } else {
206 DynNode::DYN_DEFAULT_DIGEST
207 };
208
209 DynNode { is_dyncall: self.is_dyncall, digest }
210 }
211}
212
213impl MastForestContributor for DynNodeBuilder {
214 fn add_to_forest(self, forest: &mut MastForest) -> Result<MastNodeId, MastForestError> {
215 let digest = if let Some(forced_digest) = self.digest {
217 forced_digest
218 } else if self.is_dyncall {
219 DynNode::DYNCALL_DEFAULT_DIGEST
220 } else {
221 DynNode::DYN_DEFAULT_DIGEST
222 };
223
224 let node_id = forest
227 .nodes
228 .push(DynNode { is_dyncall: self.is_dyncall, digest }.into())
229 .map_err(|_| MastForestError::TooManyNodes)?;
230
231 Ok(node_id)
232 }
233
234 fn fingerprint_for_node(
235 &self,
236 _forest: &MastForest,
237 _hash_by_node_id: &impl LookupByIdx<MastNodeId, Word>,
238 ) -> Result<Word, MastForestError> {
239 Ok(if let Some(forced_digest) = self.digest {
240 forced_digest
241 } else if self.is_dyncall {
242 DynNode::DYNCALL_DEFAULT_DIGEST
243 } else {
244 DynNode::DYN_DEFAULT_DIGEST
245 })
246 }
247
248 fn remap_children(self, _remapping: &impl LookupByIdx<MastNodeId, MastNodeId>) -> Self {
249 self
251 }
252
253 fn with_digest(mut self, digest: Word) -> Self {
254 self.digest = Some(digest);
255 self
256 }
257}
258
259impl DynNodeBuilder {
260 pub(in crate::mast) fn add_to_forest_relaxed(
270 self,
271 forest: &mut MastForest,
272 ) -> Result<MastNodeId, MastForestError> {
273 let digest = if let Some(forced_digest) = self.digest {
275 forced_digest
276 } else if self.is_dyncall {
277 DynNode::DYNCALL_DEFAULT_DIGEST
278 } else {
279 DynNode::DYN_DEFAULT_DIGEST
280 };
281
282 let node_id = forest
285 .nodes
286 .push(DynNode { is_dyncall: self.is_dyncall, digest }.into())
287 .map_err(|_| MastForestError::TooManyNodes)?;
288
289 Ok(node_id)
290 }
291}
292
293#[cfg(any(test, feature = "arbitrary"))]
294impl proptest::prelude::Arbitrary for DynNodeBuilder {
295 type Parameters = ();
296 type Strategy = proptest::strategy::BoxedStrategy<Self>;
297
298 fn arbitrary_with(_params: Self::Parameters) -> Self::Strategy {
299 use proptest::prelude::*;
300
301 any::<bool>()
302 .prop_map(|is_dyncall| {
303 if is_dyncall {
304 Self::new_dyncall()
305 } else {
306 Self::new_dyn()
307 }
308 })
309 .boxed()
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 use miden_crypto::hash::poseidon2::Poseidon2;
316
317 use super::*;
318
319 #[test]
322 pub fn test_dyn_node_digest() {
323 let mut forest = MastForest::new();
324 let dyn_node_id = DynNodeBuilder::new_dyn().add_to_forest(&mut forest).unwrap();
325 let dyn_node = forest.get_node_by_id(dyn_node_id).unwrap().unwrap_dyn();
326 assert_eq!(
327 dyn_node.digest(),
328 Poseidon2::merge_in_domain(&[Word::default(), Word::default()], DynNode::DYN_DOMAIN)
329 );
330
331 let dyncall_node_id = DynNodeBuilder::new_dyncall().add_to_forest(&mut forest).unwrap();
332 let dyncall_node = forest.get_node_by_id(dyncall_node_id).unwrap().unwrap_dyn();
333 assert_eq!(
334 dyncall_node.digest(),
335 Poseidon2::merge_in_domain(
336 &[Word::default(), Word::default()],
337 DynNode::DYNCALL_DOMAIN
338 )
339 );
340 }
341}