miden_objects/note/
execution_hint.rs1use vm_core::Felt;
5
6use crate::{block::BlockNumber, NoteError};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum NoteExecutionHint {
25 None,
28 Always,
30 AfterBlock { block_num: AfterBlockNumber },
35 OnBlockSlot {
49 round_len: u8,
50 slot_len: u8,
51 slot_offset: u8,
52 },
53}
54
55impl NoteExecutionHint {
56 pub(crate) const NONE_TAG: u8 = 0;
60 pub(crate) const ALWAYS_TAG: u8 = 1;
61 pub(crate) const AFTER_BLOCK_TAG: u8 = 2;
62 pub(crate) const ON_BLOCK_SLOT_TAG: u8 = 3;
63
64 pub fn none() -> Self {
69 NoteExecutionHint::None
70 }
71
72 pub fn always() -> Self {
74 NoteExecutionHint::Always
75 }
76
77 pub fn after_block(block_num: BlockNumber) -> Result<Self, NoteError> {
83 AfterBlockNumber::new(block_num)
84 .map(|block_number| NoteExecutionHint::AfterBlock { block_num: block_number })
85 }
86
87 pub fn on_block_slot(round_len: u8, slot_len: u8, slot_offset: u8) -> Self {
90 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset }
91 }
92
93 pub fn from_parts(tag: u8, payload: u32) -> Result<NoteExecutionHint, NoteError> {
94 match tag {
95 Self::NONE_TAG => {
96 if payload != 0 {
97 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
98 }
99 Ok(NoteExecutionHint::None)
100 },
101 Self::ALWAYS_TAG => {
102 if payload != 0 {
103 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
104 }
105 Ok(NoteExecutionHint::Always)
106 },
107 Self::AFTER_BLOCK_TAG => NoteExecutionHint::after_block(payload.into()),
108 Self::ON_BLOCK_SLOT_TAG => {
109 let remainder = (payload >> 24 & 0xff) as u8;
110 if remainder != 0 {
111 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
112 }
113
114 let round_len = ((payload >> 16) & 0xff) as u8;
115 let slot_len = ((payload >> 8) & 0xff) as u8;
116 let slot_offset = (payload & 0xff) as u8;
117 let hint = NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset };
118
119 Ok(hint)
120 },
121 _ => Err(NoteError::NoteExecutionHintTagOutOfRange(tag)),
122 }
123 }
124
125 pub fn can_be_consumed(&self, block_num: BlockNumber) -> Option<bool> {
132 let block_num = block_num.as_u32();
133 match self {
134 NoteExecutionHint::None => None,
135 NoteExecutionHint::Always => Some(true),
136 NoteExecutionHint::AfterBlock { block_num: hint_block_num } => {
137 Some(block_num >= hint_block_num.as_u32())
138 },
139 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
140 let round_len_blocks: u32 = 1 << round_len;
141 let slot_len_blocks: u32 = 1 << slot_len;
142
143 let block_round_index = block_num / round_len_blocks;
144
145 let slot_start_block =
146 block_round_index * round_len_blocks + (*slot_offset as u32) * slot_len_blocks;
147 let slot_end_block = slot_start_block + slot_len_blocks;
148
149 let can_be_consumed = block_num >= slot_start_block && block_num < slot_end_block;
150 Some(can_be_consumed)
151 },
152 }
153 }
154
155 pub fn into_parts(&self) -> (u8, u32) {
165 match self {
166 NoteExecutionHint::None => (Self::NONE_TAG, 0),
167 NoteExecutionHint::Always => (Self::ALWAYS_TAG, 0),
168 NoteExecutionHint::AfterBlock { block_num } => {
169 (Self::AFTER_BLOCK_TAG, block_num.as_u32())
170 },
171 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
172 let payload: u32 =
173 ((*round_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32);
174 (Self::ON_BLOCK_SLOT_TAG, payload)
175 },
176 }
177 }
178}
179
180impl From<NoteExecutionHint> for Felt {
182 fn from(value: NoteExecutionHint) -> Self {
183 let int_representation: u64 = value.into();
184 Felt::new(int_representation)
185 }
186}
187
188impl TryFrom<u64> for NoteExecutionHint {
193 type Error = NoteError;
194 fn try_from(value: u64) -> Result<Self, Self::Error> {
195 let tag = (value & 0b111111) as u8;
196 let payload = ((value >> 6) & 0xffffffff) as u32;
197
198 Self::from_parts(tag, payload)
199 }
200}
201
202impl From<NoteExecutionHint> for u64 {
204 fn from(value: NoteExecutionHint) -> Self {
205 let (tag, payload) = value.into_parts();
206 (payload as u64) << 6 | (tag as u64)
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub struct AfterBlockNumber(BlockNumber);
218
219impl AfterBlockNumber {
220 pub fn new(block_number: BlockNumber) -> Result<Self, NoteError> {
227 if block_number.as_u32() == u32::MAX {
228 Err(NoteError::NoteExecutionHintAfterBlockCannotBeU32Max)
229 } else {
230 Ok(Self(block_number))
231 }
232 }
233
234 pub fn as_u32(&self) -> u32 {
236 self.0.as_u32()
237 }
238}
239
240impl From<AfterBlockNumber> for u32 {
241 fn from(block_number: AfterBlockNumber) -> Self {
242 block_number.0.as_u32()
243 }
244}
245
246impl TryFrom<u32> for AfterBlockNumber {
247 type Error = NoteError;
248
249 fn try_from(block_number: u32) -> Result<Self, Self::Error> {
250 Self::new(block_number.into())
251 }
252}
253
254#[cfg(test)]
258mod tests {
259 use assert_matches::assert_matches;
260
261 use super::*;
262
263 fn assert_hint_serde(note_execution_hint: NoteExecutionHint) {
264 let (tag, payload) = note_execution_hint.into_parts();
265 let deserialized = NoteExecutionHint::from_parts(tag, payload).unwrap();
266 assert_eq!(deserialized, note_execution_hint);
267 }
268
269 #[test]
270 fn test_serialization_round_trip() {
271 assert_hint_serde(NoteExecutionHint::None);
272 assert_hint_serde(NoteExecutionHint::Always);
273 assert_hint_serde(NoteExecutionHint::after_block(15.into()).unwrap());
274 assert_hint_serde(NoteExecutionHint::OnBlockSlot {
275 round_len: 9,
276 slot_len: 12,
277 slot_offset: 18,
278 });
279 }
280
281 #[test]
282 fn test_encode_round_trip() {
283 let hint = NoteExecutionHint::after_block(15.into()).unwrap();
284 let hint_int: u64 = hint.into();
285 let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap();
286 assert_eq!(hint, decoded_hint);
287
288 let hint = NoteExecutionHint::OnBlockSlot {
289 round_len: 22,
290 slot_len: 33,
291 slot_offset: 44,
292 };
293 let hint_int: u64 = hint.into();
294 let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap();
295 assert_eq!(hint, decoded_hint);
296
297 let always_int: u64 = NoteExecutionHint::always().into();
298 assert_eq!(always_int, 1u64);
299 }
300
301 #[test]
302 fn test_can_be_consumed() {
303 let none = NoteExecutionHint::none();
304 assert!(none.can_be_consumed(100.into()).is_none());
305
306 let always = NoteExecutionHint::always();
307 assert!(always.can_be_consumed(100.into()).unwrap());
308
309 let after_block = NoteExecutionHint::after_block(12345.into()).unwrap();
310 assert!(!after_block.can_be_consumed(12344.into()).unwrap());
311 assert!(after_block.can_be_consumed(12345.into()).unwrap());
312
313 let on_block_slot = NoteExecutionHint::on_block_slot(10, 7, 1);
314 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()); }
324
325 #[test]
326 fn test_parts_validity() {
327 NoteExecutionHint::from_parts(NoteExecutionHint::NONE_TAG, 1).unwrap_err();
328 NoteExecutionHint::from_parts(NoteExecutionHint::ALWAYS_TAG, 12).unwrap_err();
329 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 1 << 24).unwrap_err();
331 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 0).unwrap();
332
333 NoteExecutionHint::from_parts(10, 1).unwrap_err();
334 }
335
336 #[test]
337 fn test_after_block_fails_on_u32_max() {
338 assert_matches!(
339 NoteExecutionHint::after_block(u32::MAX.into()).unwrap_err(),
340 NoteError::NoteExecutionHintAfterBlockCannotBeU32Max
341 );
342 }
343}