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}
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::other(format!(
114 "note execution hint tag {tag} must be in range 0..={}",
115 Self::ON_BLOCK_SLOT_TAG
116 ))),
117 }
118 }
119
120 pub fn can_be_consumed(&self, block_num: BlockNumber) -> Option<bool> {
127 let block_num = block_num.as_u32();
128 match self {
129 NoteExecutionHint::None => None,
130 NoteExecutionHint::Always => Some(true),
131 NoteExecutionHint::AfterBlock { block_num: hint_block_num } => {
132 Some(block_num >= hint_block_num.as_u32())
133 },
134 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
135 let round_len_blocks: u32 = 1 << round_len;
136 let slot_len_blocks: u32 = 1 << slot_len;
137
138 let block_round_index = block_num / round_len_blocks;
139
140 let slot_start_block =
141 block_round_index * round_len_blocks + (*slot_offset as u32) * slot_len_blocks;
142 let slot_end_block = slot_start_block + slot_len_blocks;
143
144 let can_be_consumed = block_num >= slot_start_block && block_num < slot_end_block;
145 Some(can_be_consumed)
146 },
147 }
148 }
149
150 pub fn into_parts(&self) -> (u8, u32) {
152 match self {
153 NoteExecutionHint::None => (Self::NONE_TAG, 0),
154 NoteExecutionHint::Always => (Self::ALWAYS_TAG, 0),
155 NoteExecutionHint::AfterBlock { block_num } => {
156 (Self::AFTER_BLOCK_TAG, block_num.as_u32())
157 },
158 NoteExecutionHint::OnBlockSlot { round_len, slot_len, slot_offset } => {
159 let payload: u32 =
160 ((*round_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32);
161 (Self::ON_BLOCK_SLOT_TAG, payload)
162 },
163 }
164 }
165}
166
167impl From<NoteExecutionHint> for Felt {
169 fn from(value: NoteExecutionHint) -> Self {
170 let int_representation: u64 = value.into();
171 Felt::new(int_representation)
172 }
173}
174
175impl TryFrom<u64> for NoteExecutionHint {
180 type Error = NoteError;
181 fn try_from(value: u64) -> Result<Self, Self::Error> {
182 let tag = (value & 0b1111_1111) as u8;
183 let payload = (value >> 8) as u32;
185
186 Self::from_parts(tag, payload)
187 }
188}
189
190impl From<NoteExecutionHint> for u64 {
192 fn from(value: NoteExecutionHint) -> Self {
193 let (tag, payload) = value.into_parts();
194 ((payload as u64) << 8) | (tag as u64)
195 }
196}
197
198#[cfg(test)]
202mod tests {
203
204 use super::*;
205
206 fn assert_hint_serde(note_execution_hint: NoteExecutionHint) {
207 let (tag, payload) = note_execution_hint.into_parts();
208 let deserialized = NoteExecutionHint::from_parts(tag, payload).unwrap();
209 assert_eq!(deserialized, note_execution_hint);
210 }
211
212 #[test]
213 fn test_serialization_round_trip() {
214 assert_hint_serde(NoteExecutionHint::None);
215 assert_hint_serde(NoteExecutionHint::Always);
216 assert_hint_serde(NoteExecutionHint::after_block(15.into()));
217 assert_hint_serde(NoteExecutionHint::OnBlockSlot {
218 round_len: 9,
219 slot_len: 12,
220 slot_offset: 18,
221 });
222 }
223
224 #[test]
225 fn test_encode_round_trip() {
226 let hint = NoteExecutionHint::after_block(15.into());
227 let hint_int: u64 = hint.into();
228 let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap();
229 assert_eq!(hint, decoded_hint);
230
231 let hint = NoteExecutionHint::OnBlockSlot {
232 round_len: 22,
233 slot_len: 33,
234 slot_offset: 44,
235 };
236 let hint_int: u64 = hint.into();
237 let decoded_hint: NoteExecutionHint = hint_int.try_into().unwrap();
238 assert_eq!(hint, decoded_hint);
239
240 let always_int: u64 = NoteExecutionHint::always().into();
241 assert_eq!(always_int, 1u64);
242 }
243
244 #[test]
245 fn test_can_be_consumed() {
246 let none = NoteExecutionHint::none();
247 assert!(none.can_be_consumed(100.into()).is_none());
248
249 let always = NoteExecutionHint::always();
250 assert!(always.can_be_consumed(100.into()).unwrap());
251
252 let after_block = NoteExecutionHint::after_block(12345.into());
253 assert!(!after_block.can_be_consumed(12344.into()).unwrap());
254 assert!(after_block.can_be_consumed(12345.into()).unwrap());
255
256 let on_block_slot = NoteExecutionHint::on_block_slot(10, 7, 1);
257 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()); }
267
268 #[test]
269 fn test_parts_validity() {
270 NoteExecutionHint::from_parts(NoteExecutionHint::NONE_TAG, 1).unwrap_err();
271 NoteExecutionHint::from_parts(NoteExecutionHint::ALWAYS_TAG, 12).unwrap_err();
272 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 1 << 24).unwrap_err();
274 NoteExecutionHint::from_parts(NoteExecutionHint::ON_BLOCK_SLOT_TAG, 0).unwrap();
275
276 NoteExecutionHint::from_parts(10, 1).unwrap_err();
277 }
278}