miden_protocol/note/
execution_hint.rs1use crate::Felt;
5use crate::block::BlockNumber;
6use crate::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}
51
52impl NoteExecutionHint {
53 pub(crate) const NONE_TAG: u8 = 0;
57 pub(crate) const ALWAYS_TAG: u8 = 1;
58 pub(crate) const AFTER_BLOCK_TAG: u8 = 2;
59 pub(crate) const ON_BLOCK_SLOT_TAG: u8 = 3;
60
61 pub fn none() -> Self {
66 NoteExecutionHint::None
67 }
68
69 pub fn always() -> Self {
71 NoteExecutionHint::Always
72 }
73
74 pub fn after_block(block_num: BlockNumber) -> Self {
76 NoteExecutionHint::AfterBlock { block_num }
77 }
78
79 pub fn on_block_slot(round_len: u8, slot_len: u8, slot_offset: u8) -> Self {
82 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset }
83 }
84
85 pub fn from_parts(tag: u8, payload: u32) -> Result<NoteExecutionHint, NoteError> {
86 match tag {
87 Self::NONE_TAG => {
88 if payload != 0 {
89 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
90 }
91 Ok(NoteExecutionHint::None)
92 },
93 Self::ALWAYS_TAG => {
94 if payload != 0 {
95 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
96 }
97 Ok(NoteExecutionHint::Always)
98 },
99 Self::AFTER_BLOCK_TAG => Ok(NoteExecutionHint::after_block(BlockNumber::from(payload))),
100 Self::ON_BLOCK_SLOT_TAG => {
101 let remainder = ((payload >> 24) & 0xff) as u8;
102 if remainder != 0 {
103 return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
104 }
105
106 let round_len = ((payload >> 16) & 0xff) as u8;
107 let slot_len = ((payload >> 8) & 0xff) as u8;
108 let slot_offset = (payload & 0xff) as u8;
109 let hint = NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset };
110
111 Ok(hint)
112 },
113 _ => Err(NoteError::NoteExecutionHintTagOutOfRange(tag)),
114 }
115 }
116
117 pub fn can_be_consumed(&self, block_num: BlockNumber) -> Option<bool> {
124 let block_num = block_num.as_u32();
125 match self {
126 NoteExecutionHint::None => None,
127 NoteExecutionHint::Always => Some(true),
128 NoteExecutionHint::AfterBlock { block_num: hint_block_num } => {
129 Some(block_num >= hint_block_num.as_u32())
130 },
131 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
132 let round_len_blocks: u32 = 1 << round_len;
133 let slot_len_blocks: u32 = 1 << slot_len;
134
135 let block_round_index = block_num / round_len_blocks;
136
137 let slot_start_block =
138 block_round_index * round_len_blocks + (*slot_offset as u32) * slot_len_blocks;
139 let slot_end_block = slot_start_block + slot_len_blocks;
140
141 let can_be_consumed = block_num >= slot_start_block && block_num < slot_end_block;
142 Some(can_be_consumed)
143 },
144 }
145 }
146
147 pub fn into_parts(&self) -> (u8, u32) {
149 match self {
150 NoteExecutionHint::None => (Self::NONE_TAG, 0),
151 NoteExecutionHint::Always => (Self::ALWAYS_TAG, 0),
152 NoteExecutionHint::AfterBlock { block_num } => {
153 (Self::AFTER_BLOCK_TAG, block_num.as_u32())
154 },
155 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
156 let payload: u32 =
157 ((*round_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32);
158 (Self::ON_BLOCK_SLOT_TAG, payload)
159 },
160 }
161 }
162}
163
164impl From<NoteExecutionHint> for Felt {
166 fn from(value: NoteExecutionHint) -> Self {
167 let int_representation: u64 = value.into();
168 Felt::new(int_representation)
169 }
170}
171
172impl TryFrom<u64> for NoteExecutionHint {
177 type Error = NoteError;
178 fn try_from(value: u64) -> Result<Self, Self::Error> {
179 let tag = (value & 0b1111_1111) as u8;
180 let payload = (value >> 8) as u32;
182
183 Self::from_parts(tag, payload)
184 }
185}
186
187impl From<NoteExecutionHint> for u64 {
189 fn from(value: NoteExecutionHint) -> Self {
190 let (tag, payload) = value.into_parts();
191 ((payload as u64) << 8) | (tag as u64)
192 }
193}
194
195#[cfg(test)]
199mod tests {
200
201 use super::*;
202
203 fn assert_hint_serde(note_execution_hint: NoteExecutionHint) {
204 let (tag, payload) = note_execution_hint.into_parts();
205 let deserialized = NoteExecutionHint::from_parts(tag, payload).unwrap();
206 assert_eq!(deserialized, note_execution_hint);
207 }
208
209 #[test]
210 fn test_serialization_round_trip() {
211 assert_hint_serde(NoteExecutionHint::None);
212 assert_hint_serde(NoteExecutionHint::Always);
213 assert_hint_serde(NoteExecutionHint::after_block(15.into()));
214 assert_hint_serde(NoteExecutionHint::OnBlockSlot {
215 round_len: 9,
216 slot_len: 12,
217 slot_offset: 18,
218 });
219 }
220
221 #[test]
222 fn test_encode_round_trip() {
223 let hint = NoteExecutionHint::after_block(15.into());
224 let hint_int: u64 = hint.into();
225 let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap();
226 assert_eq!(hint, decoded_hint);
227
228 let hint = NoteExecutionHint::OnBlockSlot {
229 round_len: 22,
230 slot_len: 33,
231 slot_offset: 44,
232 };
233 let hint_int: u64 = hint.into();
234 let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap();
235 assert_eq!(hint, decoded_hint);
236
237 let always_int: u64 = NoteExecutionHint::always().into();
238 assert_eq!(always_int, 1u64);
239 }
240
241 #[test]
242 fn test_can_be_consumed() {
243 let none = NoteExecutionHint::none();
244 assert!(none.can_be_consumed(100.into()).is_none());
245
246 let always = NoteExecutionHint::always();
247 assert!(always.can_be_consumed(100.into()).unwrap());
248
249 let after_block = NoteExecutionHint::after_block(12345.into());
250 assert!(!after_block.can_be_consumed(12344.into()).unwrap());
251 assert!(after_block.can_be_consumed(12345.into()).unwrap());
252
253 let on_block_slot = NoteExecutionHint::on_block_slot(10, 7, 1);
254 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()); }
264
265 #[test]
266 fn test_parts_validity() {
267 NoteExecutionHint::from_parts(NoteExecutionHint::NONE_TAG, 1).unwrap_err();
268 NoteExecutionHint::from_parts(NoteExecutionHint::ALWAYS_TAG, 12).unwrap_err();
269 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 1 << 24).unwrap_err();
271 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 0).unwrap();
272
273 NoteExecutionHint::from_parts(10, 1).unwrap_err();
274 }
275}