miden_standards/note/
execution_hint.rs1use miden_protocol::Felt;
5use miden_protocol::block::BlockNumber;
6use miden_protocol::errors::NoteError;
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum NoteExecutionHint {
25 None,
28 Always,
30 AfterBlock { block_num: BlockNumber },
32 OnBlockSlot {
46 round_len: u8,
47 slot_len: u8,
48 slot_offset: u8,
49 },
50 Unknown(Felt),
52}
53
54impl NoteExecutionHint {
55 pub(crate) const NONE_TAG: u8 = 0;
59 pub(crate) const ALWAYS_TAG: u8 = 1;
60 pub(crate) const AFTER_BLOCK_TAG: u8 = 2;
61 pub(crate) const ON_BLOCK_SLOT_TAG: u8 = 3;
62
63 pub fn none() -> Self {
68 NoteExecutionHint::None
69 }
70
71 pub fn always() -> Self {
73 NoteExecutionHint::Always
74 }
75
76 pub fn after_block(block_num: BlockNumber) -> Self {
78 NoteExecutionHint::AfterBlock { block_num }
79 }
80
81 pub fn on_block_slot(round_len: u8, slot_len: u8, slot_offset: u8) -> Self {
84 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset }
85 }
86
87 pub fn from_parts(tag: u8, payload: u32) -> Result<NoteExecutionHint, NoteError> {
88 match tag {
89 Self::NONE_TAG => {
90 if payload != 0 {
91 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
92 }
93 Ok(NoteExecutionHint::None)
94 },
95 Self::ALWAYS_TAG => {
96 if payload != 0 {
97 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
98 }
99 Ok(NoteExecutionHint::Always)
100 },
101 Self::AFTER_BLOCK_TAG => Ok(NoteExecutionHint::after_block(BlockNumber::from(payload))),
102 Self::ON_BLOCK_SLOT_TAG => {
103 let remainder = ((payload >> 24) & 0xff) as u8;
104 if remainder != 0 {
105 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
106 }
107
108 let round_len = ((payload >> 16) & 0xff) as u8;
109 let slot_len = ((payload >> 8) & 0xff) as u8;
110 let slot_offset = (payload & 0xff) as u8;
111 let hint = NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset };
112
113 Ok(hint)
114 },
115 _ => Err(NoteError::other(format!(
116 "note execution hint tag {tag} must be in range 0..={}",
117 Self::ON_BLOCK_SLOT_TAG
118 ))),
119 }
120 }
121
122 pub fn can_be_consumed(&self, block_num: BlockNumber) -> Option<bool> {
129 let block_num = block_num.as_u32();
130 match self {
131 NoteExecutionHint::None | NoteExecutionHint::Unknown(_) => None,
132 NoteExecutionHint::Always => Some(true),
133 NoteExecutionHint::AfterBlock { block_num: hint_block_num } => {
134 Some(block_num >= hint_block_num.as_u32())
135 },
136 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
137 let round_len_blocks: u32 = 1 << round_len;
138 let slot_len_blocks: u32 = 1 << slot_len;
139
140 let block_round_index = block_num / round_len_blocks;
141
142 let slot_start_block =
143 block_round_index * round_len_blocks + (*slot_offset as u32) * slot_len_blocks;
144 let slot_end_block = slot_start_block + slot_len_blocks;
145
146 let can_be_consumed = block_num >= slot_start_block && block_num < slot_end_block;
147 Some(can_be_consumed)
148 },
149 }
150 }
151
152 pub fn into_parts(&self) -> Option<(u8, u32)> {
155 match self {
156 NoteExecutionHint::None => Some((Self::NONE_TAG, 0)),
157 NoteExecutionHint::Always => Some((Self::ALWAYS_TAG, 0)),
158 NoteExecutionHint::AfterBlock { block_num } => {
159 Some((Self::AFTER_BLOCK_TAG, block_num.as_u32()))
160 },
161 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
162 let payload: u32 =
163 ((*round_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32);
164 Some((Self::ON_BLOCK_SLOT_TAG, payload))
165 },
166 NoteExecutionHint::Unknown(_) => None,
167 }
168 }
169}
170
171impl From<NoteExecutionHint> for Felt {
173 fn from(value: NoteExecutionHint) -> Self {
174 match value {
175 NoteExecutionHint::Unknown(felt) => felt,
176 hint => {
177 let (tag, payload) =
178 hint.into_parts().expect("every hint but `Unknown` decomposes into parts");
179 Felt::new_unchecked(((payload as u64) << 8) | (tag as u64))
181 },
182 }
183 }
184}
185
186impl From<Felt> for NoteExecutionHint {
188 fn from(value: Felt) -> Self {
189 let encoded = value.as_canonical_u64();
190 let tag = (encoded & 0b1111_1111) as u8;
191
192 u32::try_from(encoded >> 8)
195 .ok()
196 .and_then(|payload| Self::from_parts(tag, payload).ok())
197 .unwrap_or(NoteExecutionHint::Unknown(value))
198 }
199}
200
201#[cfg(test)]
205mod tests {
206
207 use super::*;
208
209 fn assert_hint_serde(note_execution_hint: NoteExecutionHint) {
210 let (tag, payload) = note_execution_hint.into_parts().unwrap();
211 let deserialized = NoteExecutionHint::from_parts(tag, payload).unwrap();
212 assert_eq!(deserialized, note_execution_hint);
213 }
214
215 #[test]
216 fn test_serialization_round_trip() {
217 assert_hint_serde(NoteExecutionHint::None);
218 assert_hint_serde(NoteExecutionHint::Always);
219 assert_hint_serde(NoteExecutionHint::after_block(15.into()));
220 assert_hint_serde(NoteExecutionHint::OnBlockSlot {
221 round_len: 9,
222 slot_len: 12,
223 slot_offset: 18,
224 });
225 }
226
227 #[test]
228 fn test_encode_round_trip() {
229 for hint in [
230 NoteExecutionHint::None,
231 NoteExecutionHint::Always,
232 NoteExecutionHint::after_block(15.into()),
233 NoteExecutionHint::OnBlockSlot {
234 round_len: 22,
235 slot_len: 33,
236 slot_offset: 44,
237 },
238 ] {
239 let encoded = Felt::from(hint);
240 assert_eq!(NoteExecutionHint::from(encoded), hint);
241 }
242
243 assert_eq!(Felt::from(NoteExecutionHint::always()).as_canonical_u64(), 1);
244 }
245
246 #[test]
248 fn unknown_hint_round_trip() {
249 for encoded in [7u64, (1 << 8) | 1, (1 << 32) | 3, 1 << 40] {
253 let encoded = Felt::new(encoded).unwrap();
254 let hint = NoteExecutionHint::from(encoded);
255
256 assert_eq!(hint, NoteExecutionHint::Unknown(encoded));
257 assert_eq!(hint.into_parts(), None);
258 assert_eq!(hint.can_be_consumed(100.into()), None);
259 assert_eq!(Felt::from(hint), encoded);
260 }
261 }
262
263 #[test]
264 fn test_can_be_consumed() {
265 let none = NoteExecutionHint::none();
266 assert!(none.can_be_consumed(100.into()).is_none());
267
268 let always = NoteExecutionHint::always();
269 assert!(always.can_be_consumed(100.into()).unwrap());
270
271 let after_block = NoteExecutionHint::after_block(12345.into());
272 assert!(!after_block.can_be_consumed(12344.into()).unwrap());
273 assert!(after_block.can_be_consumed(12345.into()).unwrap());
274
275 let on_block_slot = NoteExecutionHint::on_block_slot(10, 7, 1);
276 assert!(!on_block_slot.can_be_consumed(127.into()).unwrap()); assert!(on_block_slot.can_be_consumed(128.into()).unwrap()); assert!(on_block_slot.can_be_consumed(255.into()).unwrap()); assert!(!on_block_slot.can_be_consumed(256.into()).unwrap()); assert!(on_block_slot.can_be_consumed(1152.into()).unwrap()); assert!(on_block_slot.can_be_consumed(1279.into()).unwrap()); assert!(on_block_slot.can_be_consumed(2176.into()).unwrap()); assert!(!on_block_slot.can_be_consumed(2175.into()).unwrap()); }
286
287 #[test]
288 fn test_parts_validity() {
289 NoteExecutionHint::from_parts(NoteExecutionHint::NONE_TAG, 1).unwrap_err();
290 NoteExecutionHint::from_parts(NoteExecutionHint::ALWAYS_TAG, 12).unwrap_err();
291 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 1 << 24).unwrap_err();
293 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 0).unwrap();
294
295 NoteExecutionHint::from_parts(10, 1).unwrap_err();
296 }
297}