Skip to content

Commit

Permalink
feat: Add NoteExecutionHint; rename previously existing enum
Browse files Browse the repository at this point in the history
  • Loading branch information
igamigo committed Aug 2, 2024
1 parent 493e193 commit b8f4d2d
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 52 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- Added `CHANGELOG.md` warning message on CI (#799).
- Account deltas can now be merged (#797).
- Changed `AccountCode` procedures from merkle tree to sequential hash + added storage_offset support (#763).
- Added `NoteExecutionHint` to `NoteMetadata` (#812).
- [BREAKING] Refactored and simplified `NoteOrigin` and `NoteInclusionProof` structs (#810, #814).

## 0.4.0 (2024-07-03)
Expand Down
10 changes: 5 additions & 5 deletions miden-lib/src/notes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use miden_objects::{
assets::Asset,
crypto::rand::FeltRng,
notes::{
Note, NoteAssets, NoteDetails, NoteExecutionHint, NoteInputs, NoteMetadata, NoteRecipient,
Note, NoteAssets, NoteDetails, NoteExecutionMode, NoteInputs, NoteMetadata, NoteRecipient,
NoteTag, NoteType,
},
Felt, NoteError, Word,
Expand Down Expand Up @@ -40,7 +40,7 @@ pub fn create_p2id_note<R: FeltRng>(
let note_script = build_note_script(bytes)?;

let inputs = NoteInputs::new(vec![target.into()])?;
let tag = NoteTag::from_account_id(target, NoteExecutionHint::Local)?;
let tag = NoteTag::from_account_id(target, NoteExecutionMode::Local)?;
let serial_num = rng.draw_word();

let metadata = NoteMetadata::new(sender, note_type, tag, aux)?;
Expand Down Expand Up @@ -74,7 +74,7 @@ pub fn create_p2idr_note<R: FeltRng>(
let note_script = build_note_script(bytes)?;

let inputs = NoteInputs::new(vec![target.into(), recall_height.into()])?;
let tag = NoteTag::from_account_id(target, NoteExecutionHint::Local)?;
let tag = NoteTag::from_account_id(target, NoteExecutionMode::Local)?;
let serial_num = rng.draw_word();

let vault = NoteAssets::new(assets)?;
Expand Down Expand Up @@ -108,7 +108,7 @@ pub fn create_swap_note<R: FeltRng>(

let payback_recipient_word: Word = payback_recipient.digest().into();
let requested_asset_word: Word = requested_asset.into();
let payback_tag = NoteTag::from_account_id(sender, NoteExecutionHint::Local)?;
let payback_tag = NoteTag::from_account_id(sender, NoteExecutionMode::Local)?;

let inputs = NoteInputs::new(vec![
payback_recipient_word[0],
Expand Down Expand Up @@ -169,7 +169,7 @@ fn build_swap_tag(

let payload = ((offered_asset_tag as u16) << 8) | (requested_asset_tag as u16);

let execution = NoteExecutionHint::Local;
let execution = NoteExecutionMode::Local;
match note_type {
NoteType::Public => NoteTag::for_public_use_case(SWAP_USE_CASE_ID, payload, execution),
_ => NoteTag::for_local_use_case(SWAP_USE_CASE_ID, payload),
Expand Down
10 changes: 5 additions & 5 deletions miden-tx/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use miden_objects::{
assembly::{Assembler, ModuleAst, ProgramAst},
assets::{Asset, FungibleAsset},
notes::{
Note, NoteAssets, NoteExecutionHint, NoteHeader, NoteId, NoteInputs, NoteMetadata,
Note, NoteAssets, NoteExecutionMode, NoteHeader, NoteId, NoteInputs, NoteMetadata,
NoteRecipient, NoteScript, NoteTag, NoteType,
},
testing::{
Expand Down Expand Up @@ -139,7 +139,7 @@ fn executed_transaction_account_delta() {

let tag1 = NoteTag::from_account_id(
ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN.try_into().unwrap(),
NoteExecutionHint::Local,
NoteExecutionMode::Local,
)
.unwrap();
let tag2 = NoteTag::for_local_use_case(0, 0).unwrap();
Expand Down Expand Up @@ -471,11 +471,11 @@ fn executed_transaction_output_notes() {

let tag1 = NoteTag::from_account_id(
ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN.try_into().unwrap(),
NoteExecutionHint::Local,
NoteExecutionMode::Local,
)
.unwrap();
let tag2 = NoteTag::for_public_use_case(0, 0, NoteExecutionHint::Local).unwrap();
let tag3 = NoteTag::for_public_use_case(0, 0, NoteExecutionHint::Local).unwrap();
let tag2 = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local).unwrap();
let tag3 = NoteTag::for_public_use_case(0, 0, NoteExecutionMode::Local).unwrap();
let aux1 = Felt::new(27);
let aux2 = Felt::new(28);
let aux3 = Felt::new(29);
Expand Down
4 changes: 2 additions & 2 deletions miden-tx/tests/integration/scripts/swap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use miden_objects::{
assembly::ProgramAst,
assets::{Asset, AssetVault, FungibleAsset, NonFungibleAsset, NonFungibleAssetDetails},
crypto::rand::RpoRandomCoin,
notes::{NoteAssets, NoteExecutionHint, NoteHeader, NoteId, NoteMetadata, NoteTag, NoteType},
notes::{NoteAssets, NoteExecutionMode, NoteHeader, NoteId, NoteMetadata, NoteTag, NoteType},
testing::account_code::DEFAULT_AUTH_SCRIPT,
transaction::TransactionArgs,
Felt, ZERO,
Expand Down Expand Up @@ -102,7 +102,7 @@ fn prove_swap_script() {

// Check if the output `Note` is what we expect
let recipient = payback_note.recipient().clone();
let tag = NoteTag::from_account_id(sender_account_id, NoteExecutionHint::Local).unwrap();
let tag = NoteTag::from_account_id(sender_account_id, NoteExecutionMode::Local).unwrap();
let note_metadata = NoteMetadata::new(target_account_id, NoteType::Private, tag, ZERO).unwrap();
let assets = NoteAssets::new(vec![requested_asset]).unwrap();
let note_id = NoteId::new(recipient.digest(), assets.commitment());
Expand Down
2 changes: 2 additions & 0 deletions objects/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ pub enum NoteError {
InvalidAssetData(AssetError),
InvalidNoteSender(AccountError),
InvalidNoteTagUseCase(u16),
InvalidNoteExecutionHintTag(u8),
InvalidNoteExecutionHintPayload(u8, u32),
InvalidNoteType(NoteType),
InvalidNoteTypeValue(u64),
InvalidLocationIndex(String),
Expand Down
20 changes: 17 additions & 3 deletions objects/src/notes/metadata.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use alloc::string::ToString;

use super::{
AccountId, ByteReader, ByteWriter, Deserializable, DeserializationError, Felt, NoteError,
NoteTag, NoteType, Serializable, Word,
note_execution_hint::NoteExecutionHint, AccountId, ByteReader, ByteWriter, Deserializable,
DeserializationError, Felt, NoteError, NoteTag, NoteType, Serializable, Word,
};

// NOTE METADATA
Expand Down Expand Up @@ -30,6 +30,9 @@ pub struct NoteMetadata {

/// An arbitrary user-defined value.
aux: Felt,

/// Expresses when a note is ready to be consumed.
execution_hint: NoteExecutionHint,
}

impl NoteMetadata {
Expand All @@ -44,7 +47,13 @@ impl NoteMetadata {
aux: Felt,
) -> Result<Self, NoteError> {
let tag = tag.validate(note_type)?;
Ok(Self { sender, note_type, tag, aux })
Ok(Self {
sender,
note_type,
tag,
aux,
execution_hint: NoteExecutionHint::None,
})
}

/// Returns the account which created the note.
Expand All @@ -62,6 +71,11 @@ impl NoteMetadata {
self.tag
}

/// Returns the execution hint associated with the note.
pub fn execution_hint(&self) -> NoteExecutionHint {
self.execution_hint
}

/// Returns the note's aux field.
pub fn aux(&self) -> Felt {
self.aux
Expand Down
5 changes: 4 additions & 1 deletion objects/src/notes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@ pub use inputs::NoteInputs;
mod metadata;
pub use metadata::NoteMetadata;

mod note_execution_hint;
pub use note_execution_hint::NoteExecutionHint;

mod note_id;
pub use note_id::NoteId;

mod note_tag;
pub use note_tag::{NoteExecutionHint, NoteTag};
pub use note_tag::{NoteExecutionMode, NoteTag};

mod note_type;
pub use note_type::NoteType;
Expand Down
189 changes: 189 additions & 0 deletions objects/src/notes/note_execution_hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// NOTE EXECUTION HINT
// ================================================================================================

use crate::NoteError;

/// Describes the conditions under which a note is ready to be consumed.
/// These conditions are meant to be encoded in the note script.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum NoteExecutionHint {
/// Unspecified note execution hint. Implies we don't know under which conditions the note is
/// consumable.
None,
/// The note's script can be executed at any time.
Always,
/// The note's script can be executed after the specified block height.
AfterBlock { block_num: u32 },
/// The note's script can be executed in the specified slot within the specified epoch.
///
/// The slot is defined as follows:
/// - First we define the length of the epoch in powers of 2. For example, epoch_len = 10 is an epoch
/// of 1024 blocks.
/// - Then we define the length of a slot within the epoch also using powers of 2. For example,
/// slot_len = 7 is a slot of 128 blocks.
/// - Lastly, the offset specifies the index of the slot within the epoch - i.e., 0 is the first slot,
/// 1 is the second slot etc.
///
/// For example: { epoch_len: 10, slot_len: 7, slot_offset: 1 } means that the note can
/// be executed in any second 128 block slot of a 1024 block epoch. These would be blocks 128..255,
/// 1152..1279, 2176..2303 etc.
OnBlockSlot {
epoch_len: u8,
slot_len: u8,
slot_offset: u8,
},
}

impl NoteExecutionHint {
/// Creates a [NoteExecutionHint::None] variant
pub fn none() -> Self {
NoteExecutionHint::None
}

/// Creates a [NoteExecutionHint::Always] variant
pub fn always() -> Self {
NoteExecutionHint::Always
}

/// Creates a [NoteExecutionHint::AfterBlock] variant based on the given `block_num`
pub fn after_block(block_num: u32) -> Self {
NoteExecutionHint::AfterBlock { block_num }
}

/// Creates a [NoteExecutionHint::OnBlockSlot] for the given parameters
pub fn on_block_slot(epoch_len: u8, slot_len: u8, slot_offset: u8) -> Self {
NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset }
}

pub fn from_parts(tag: u8, payload: u32) -> Result<NoteExecutionHint, NoteError> {
match tag {
0 => {
if payload != 0 {
return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
}
Ok(NoteExecutionHint::None)
},
1 => {
if payload != 0 {
return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
}
Ok(NoteExecutionHint::Always)
},
2 => Ok(NoteExecutionHint::AfterBlock { block_num: payload }),
3 => {
let remainder = (payload >> 24 & 0xFF) as u8;
if remainder != 0 {
return Err(NoteError::InvalidNoteExecutionHintPayload(tag, payload));
}

let epoch_len = ((payload >> 16) & 0xFF) as u8;
let slot_len = ((payload >> 8) & 0xFF) as u8;
let slot_offset = (payload & 0xFF) as u8;
let hint = NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset };

Ok(hint)
},
_ => Err(NoteError::InvalidNoteExecutionHintTag(tag)),
}
}

/// Returns whether the note execution conditions validate for the given `block_num`
///
/// # Returns
/// - `None` if we don't know whether the note can be consumed.
/// - `Some(true)` if the note is consumable for the given `block_num`
/// - `Some(false)` if the note is not consumable for the given `block_num`
pub fn can_be_consumed(&self, block_num: u32) -> Option<bool> {
match self {
NoteExecutionHint::None => None,
NoteExecutionHint::Always => Some(true),
NoteExecutionHint::AfterBlock { block_num: hint_block_num } => {
Some(block_num >= *hint_block_num)
},
NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } => {
let epoch_len_blocks: u32 = 1 << epoch_len;
let slot_len_blocks: u32 = 1 << slot_len;

let block_epoch_index = block_num / epoch_len_blocks;

let slot_start_block =
block_epoch_index * epoch_len_blocks + (*slot_offset as u32) * slot_len_blocks;
let slot_end_block = slot_start_block + slot_len_blocks;

let can_be_consumed = block_num >= slot_start_block && block_num < slot_end_block;
Some(can_be_consumed)
},
}
}

pub fn into_parts(&self) -> (u8, u32) {
match self {
NoteExecutionHint::None => (0, 0),
NoteExecutionHint::Always => (1, 0),
NoteExecutionHint::AfterBlock { block_num } => (2, *block_num),
NoteExecutionHint::OnBlockSlot { epoch_len, slot_len, slot_offset } => {
let payload: u32 =
((*epoch_len as u32) << 16) | ((*slot_len as u32) << 8) | (*slot_offset as u32);
(3, payload)
},
}
}
}

#[cfg(test)]
mod tests {
use super::*;

fn assert_hint_serde(note_execution_hint: NoteExecutionHint) {
let (tag, payload) = note_execution_hint.into_parts();
let deserialized = NoteExecutionHint::from_parts(tag, payload).unwrap();
assert_eq!(deserialized, note_execution_hint);
}

#[test]
fn test_serialization_round_trip() {
assert_hint_serde(NoteExecutionHint::None);
assert_hint_serde(NoteExecutionHint::Always);
assert_hint_serde(NoteExecutionHint::AfterBlock { block_num: 15 });
assert_hint_serde(NoteExecutionHint::OnBlockSlot {
epoch_len: 9,
slot_len: 12,
slot_offset: 18,
});
}

#[test]
fn test_can_be_consumed() {
let none = NoteExecutionHint::none();
assert!(none.can_be_consumed(100).is_none());

let always = NoteExecutionHint::always();
assert!(always.can_be_consumed(100).unwrap());

let after_block = NoteExecutionHint::after_block(12345);
assert!(!after_block.can_be_consumed(12344).unwrap());
assert!(after_block.can_be_consumed(12345).unwrap());

let on_block_slot = NoteExecutionHint::on_block_slot(10, 7, 1);
assert!(!on_block_slot.can_be_consumed(127).unwrap()); // Block 127 is not in the slot 128..255
assert!(on_block_slot.can_be_consumed(128).unwrap()); // Block 128 is in the slot 128..255
assert!(on_block_slot.can_be_consumed(255).unwrap()); // Block 255 is in the slot 128..255
assert!(!on_block_slot.can_be_consumed(256).unwrap()); // Block 256 is not in the slot 128..255
assert!(on_block_slot.can_be_consumed(1152).unwrap()); // Block 1152 is in the slot 1152..1279
assert!(on_block_slot.can_be_consumed(1279).unwrap()); // Block 1279 is in the slot 1152..1279
assert!(on_block_slot.can_be_consumed(2176).unwrap()); // Block 2176 is in the slot 2176..2303
assert!(!on_block_slot.can_be_consumed(2175).unwrap()); // Block 1279 is in the slot 2176..2303
}

#[test]
fn test_parts_validity() {
NoteExecutionHint::from_parts(0, 1).unwrap_err();
NoteExecutionHint::from_parts(1, 12).unwrap_err();
// 4th byte should be blank for tag 3 (OnBlockSlot)
NoteExecutionHint::from_parts(3, 1 << 24).unwrap_err();
NoteExecutionHint::from_parts(3, 0).unwrap();

NoteExecutionHint::from_parts(10, 1).unwrap_err();
}
}
Loading

0 comments on commit b8f4d2d

Please sign in to comment.