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