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