Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
- Added `MockChain::add_pending_batch()` to allow submitting user batches directly ([#2565](https://github.com/0xMiden/protocol/pull/2565)).
- Added `create_fungible_key` for construction of fungible asset keys ([#2575](https://github.com/0xMiden/protocol/pull/2575)).
- Implemented the `on_before_asset_added_to_account` asset callback ([#2571](https://github.com/0xMiden/protocol/pull/2571)).
- Implemented the `on_before_asset_added_to_note` asset callback ([#2595](https://github.com/0xMiden/protocol/pull/2595)).
- Added `InputNoteCommitment::from_parts()` for construction of input note commitments from a nullifier and optional note header ([#2588](https://github.com/0xMiden/protocol/pull/2588)).
- Added `bool` schema type to the type registry and updated ACL auth component to use it for boolean config fields ([#2591](https://github.com/0xMiden/protocol/pull/2591)).
- Added `component_metadata()` to all account components to expose their metadata ([#2596](https://github.com/0xMiden/protocol/pull/2596)).
Expand Down
3 changes: 0 additions & 3 deletions crates/miden-protocol/asm/kernels/transaction/api.masm
Original file line number Diff line number Diff line change
Expand Up @@ -1126,9 +1126,6 @@ pub proc output_note_add_asset
exec.memory::assert_native_account
# => [ASSET_KEY, ASSET_VALUE, note_idx, pad(7)]

movup.8
# => [note_idx, ASSET_KEY, ASSET_VALUE, pad(7)]

exec.output_note::add_asset
# => [pad(16)]
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use $kernel::account_id
use $kernel::asset_vault
use $kernel::callbacks
use $kernel::callbacks::ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_PROC_ROOT_SLOT
use $kernel::callbacks::ON_BEFORE_ASSET_ADDED_TO_NOTE_PROC_ROOT_SLOT
use $kernel::constants::ACCOUNT_PROCEDURE_DATA_LENGTH
use $kernel::constants::EMPTY_SMT_ROOT
use $kernel::constants::STORAGE_SLOT_TYPE_MAP
Expand Down Expand Up @@ -2064,6 +2065,14 @@ pub proc has_callbacks
# check if the on_before_asset_added_to_account callback slot exists and is non-empty
push.ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_PROC_ROOT_SLOT[0..2]
exec.has_non_empty_slot
# => [has_account_callback]

# check if the on_before_asset_added_to_note callback slot exists and is non-empty
push.ON_BEFORE_ASSET_ADDED_TO_NOTE_PROC_ROOT_SLOT[0..2]
exec.has_non_empty_slot
# => [has_note_callback, has_account_callback]

or
# => [has_callbacks]
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,19 +428,19 @@ proc update_non_fungible_asset_delta
# convert was_added to a boolean
# was_added is 1 if the asset was added and 0 - 1 if it was removed
eq.1
# => [[was_added, 0, 0, 0], [asset_id_suffix, asset_id_prefix, faucet_id_suffix, faucet_id_prefix], ASSET_VALUE, ...]
# => [[was_added, 0, 0, 0], [asset_id_suffix, asset_id_prefix, faucet_id_suffix_and_metadata, faucet_id_prefix], ASSET_VALUE, ...]

# replace asset_id_prefix with was_added and drop the remaining word
swap.5 dropw
# => [[asset_id_suffix, was_added, faucet_id_suffix, faucet_id_prefix], ASSET_VALUE, ...]
# => [[asset_id_suffix, was_added, faucet_id_suffix_and_metadata, faucet_id_prefix], ASSET_VALUE, ...]

# replace asset_id_suffix with domain
drop push.DOMAIN_ASSET
# => [[domain, was_added, faucet_id_suffix, faucet_id_prefix], ASSET_VALUE, ...]
# => [[domain, was_added, faucet_id_suffix_and_metadata, faucet_id_prefix], ASSET_VALUE, ...]

# drop previous RATE elements
swapdw dropw dropw
# => [[domain, was_added, faucet_id_suffix, faucet_id_prefix], ASSET_VALUE, CAPACITY]
# => [[domain, was_added, faucet_id_suffix_and_metadata, faucet_id_prefix], ASSET_VALUE, CAPACITY]

exec.poseidon2::permute
# => [RATE0, RATE1, CAPACITY]
Expand Down
138 changes: 106 additions & 32 deletions crates/miden-protocol/asm/kernels/transaction/lib/callbacks.masm
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const CALLBACK_PROC_ROOT_LOC = 0
# is stored.
pub const ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_PROC_ROOT_SLOT = word("miden::protocol::faucet::callback::on_before_asset_added_to_account")

# The name of the storage slot where the procedure root for the on_before_asset_added_to_note callback
# is stored.
pub const ON_BEFORE_ASSET_ADDED_TO_NOTE_PROC_ROOT_SLOT = word("miden::protocol::faucet::callback::on_before_asset_added_to_note")

# PROCEDURES
# ==================================================================================================

Expand All @@ -37,7 +41,12 @@ pub proc on_before_asset_added_to_account
# => [callbacks_enabled, ASSET_KEY, ASSET_VALUE]

if.true
exec.on_before_asset_added_to_account_raw
# set custom_data = 0
push.0 movdn.8
# => [ASSET_KEY, ASSET_VALUE, custom_data = 0]

push.ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_PROC_ROOT_SLOT[0..2]
exec.invoke_callback
# => [PROCESSED_ASSET_VALUE]
else
# drop asset key
Expand All @@ -47,77 +56,142 @@ pub proc on_before_asset_added_to_account
# => [PROCESSED_ASSET_VALUE]
end

#! Executes the `on_before_asset_added_to_account` callback by starting a foreign context against
#! the faucet, reading the callback procedure root from the faucet's storage, and invoking it via
#! `dyncall`.
#! Invokes the `on_before_asset_added_to_note` callback on the faucet that issued the asset,
#! if the asset has callbacks enabled.
#!
#! The callback invocation is skipped in these cases:
#! - If the global callback flag in the asset key is `Disabled`.
#! - If the faucet does not have the callback storage slot.
#! - If the callback storage slot contains the empty word.
#!
#! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx]
#! Outputs: [PROCESSED_ASSET_VALUE]
#!
#! Where:
#! - ASSET_KEY is the vault key of the asset being added.
#! - ASSET_VALUE is the value of the asset being added.
#! - note_idx is the index of the output note the asset is being added to.
#! - PROCESSED_ASSET_VALUE is the asset value returned by the callback, or the original
#! ASSET_VALUE if callbacks are disabled.
pub proc on_before_asset_added_to_note
exec.asset::key_to_callbacks_enabled
# => [callbacks_enabled, ASSET_KEY, ASSET_VALUE, note_idx]

if.true
push.ON_BEFORE_ASSET_ADDED_TO_NOTE_PROC_ROOT_SLOT[0..2]
exec.invoke_callback
# => [PROCESSED_ASSET_VALUE]
else
# drop asset key and note index
dropw movup.4 drop
# => [ASSET_VALUE]
end
# => [PROCESSED_ASSET_VALUE]
end

#! Invokes a callback by starting a foreign context against the faucet, reading the callback
#! procedure root from the provided slot ID in the faucet's storage, and invoking it via `dyncall`.
#!
#! If the faucet does not have the callback storage slot, or if the slot contains the empty word,
#! the callback is skipped and the original ASSET_VALUE is returned.
#!
#! Inputs: [ASSET_KEY, ASSET_VALUE]
#! custom_data should be set to 0 for the account callback and to note_idx for the note callback.
#!
#! Inputs: [slot_id_suffix, slot_id_prefix, ASSET_KEY, ASSET_VALUE, custom_data]
#! Outputs: [PROCESSED_ASSET_VALUE]
#!
#! Where:
#! - slot_id* is the ID of the slot that contains the callback procedure root.
#! - ASSET_KEY is the vault key of the asset being added.
#! - ASSET_VALUE is the value of the asset being added.
#! - PROCESSED_ASSET_VALUE is the asset value returned by the callback, or the original
#! ASSET_VALUE if no callback is configured.
@locals(4)
proc on_before_asset_added_to_account_raw
proc invoke_callback
exec.start_foreign_callback_context
# => [ASSET_KEY, ASSET_VALUE]

# try to find the callback procedure root in the faucet's storage
push.ON_BEFORE_ASSET_ADDED_TO_ACCOUNT_PROC_ROOT_SLOT[0..2]
exec.account::find_item
# => [is_found, PROC_ROOT, ASSET_KEY, ASSET_VALUE]

movdn.4 exec.word::testz not
# => [is_non_empty_word, PROC_ROOT, is_found, ASSET_KEY, ASSET_VALUE]

# invoke the callback if is_found && is_non_empty_word
movup.5 and
# => [should_invoke, PROC_ROOT, ASSET_KEY, ASSET_VALUE]
# => [should_invoke, PROC_ROOT, ASSET_KEY, ASSET_VALUE, custom_data]

# only invoke the callback if the procedure root is not the empty word
if.true
# prepare for dyncall by storing procedure root in local memory
loc_storew_le.CALLBACK_PROC_ROOT_LOC dropw
# => [ASSET_KEY, ASSET_VALUE]
# => [ASSET_KEY, ASSET_VALUE, custom_data]

# pad the stack to 16 for the call (excluding the proc root ptr)
padw padw swapdw
locaddr.CALLBACK_PROC_ROOT_LOC
# => [callback_proc_root_ptr, ASSET_KEY, ASSET_VALUE, pad(8)]
# pad the stack to 16 for the call
repeat.7 push.0 movdn.9 end
# => [ASSET_KEY, ASSET_VALUE, custom_data, pad(7)]

# invoke the callback
locaddr.CALLBACK_PROC_ROOT_LOC
dyncall
# => [PROCESSED_ASSET_VALUE, pad(12)]

# truncate the stack after the call
swapdw dropw dropw swapw dropw
# => [PROCESSED_ASSET_VALUE]
else
# drop proc root and asset key
dropw dropw
# drop proc root, asset key and custom_data
dropw dropw movup.4 drop
# => [ASSET_VALUE]
end
# => [PROCESSED_ASSET_VALUE]

exec.tx::end_foreign_context
exec.end_foreign_callback_context
# => [PROCESSED_ASSET_VALUE]
end

#! Prepares the invocation of a faucet callback by starting a foreign context against the faucet
#! identified by the asset key's faucet ID.
#! identified by the asset key's faucet ID, looking up the callback procedure root from the
#! faucet's storage, and computing whether the callback should be invoked.
#!
#! Inputs: [ASSET_KEY, ASSET_VALUE]
#! Outputs: [ASSET_KEY, ASSET_VALUE]
#! The callback should be invoked if the storage slot exists and contains a non-empty procedure
#! root.
#!
#! Inputs: [slot_id_suffix, slot_id_prefix, ASSET_KEY, ASSET_VALUE]
#! Outputs: [should_invoke, PROC_ROOT, ASSET_KEY, ASSET_VALUE]
#!
#! Where:
#! - slot_id_suffix and slot_id_prefix identify the storage slot containing the callback procedure root.
#! - ASSET_KEY is the vault key of the asset being added.
#! - ASSET_VALUE is the value of the asset being added.
#! - should_invoke is 1 if the callback should be invoked, 0 otherwise.
#! - PROC_ROOT is the procedure root of the callback, or the empty word if not found.
proc start_foreign_callback_context
# move slot IDs past ASSET_KEY and ASSET_VALUE
movdn.9 movdn.9
# => [ASSET_KEY, ASSET_VALUE, slot_id_suffix, slot_id_prefix]

exec.asset::key_to_faucet_id
# => [faucet_id_suffix, faucet_id_prefix, ASSET_KEY, ASSET_VALUE]
# => [faucet_id_suffix, faucet_id_prefix, ASSET_KEY, ASSET_VALUE, slot_id_suffix, slot_id_prefix]

# start a foreign context against the faucet
exec.tx::start_foreign_context
# => [ASSET_KEY, ASSET_VALUE]
# => [ASSET_KEY, ASSET_VALUE, slot_id_suffix, slot_id_prefix]

# bring slot IDs back to top
movup.9 movup.9
# => [slot_id_suffix, slot_id_prefix, ASSET_KEY, ASSET_VALUE]

# try to find the callback procedure root in the faucet's storage
exec.account::find_item
# => [is_found, PROC_ROOT, ASSET_KEY, ASSET_VALUE]

movdn.4 exec.word::testz not
# => [is_non_empty_word, PROC_ROOT, is_found, ASSET_KEY, ASSET_VALUE]

# should_invoke = is_found && is_non_empty_word
movup.5 and
# => [should_invoke, PROC_ROOT, ASSET_KEY, ASSET_VALUE]
end

#! Ends a foreign callback context.
#!
#! This pops the top of the account stack, making the previous account the active account.
#!
#! This wrapper exists only for uniformity with start_foreign_callback_context.
#!
#! Inputs: []
#! Outputs: []
proc end_foreign_callback_context
exec.tx::end_foreign_context
end
43 changes: 27 additions & 16 deletions crates/miden-protocol/asm/kernels/transaction/lib/output_note.masm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use $kernel::account
use $kernel::asset
use $kernel::callbacks
use $kernel::fungible_asset
use $kernel::memory
use $kernel::note
Expand Down Expand Up @@ -178,13 +179,13 @@ end

#! Adds the asset to the note specified by the index.
#!
#! Inputs: [note_idx, ASSET_KEY, ASSET_VALUE]
#! Inputs: [ASSET_KEY, ASSET_VALUE, note_idx]
#! Outputs: []
#!
#! Where:
#! - note_idx is the index of the note to which the asset is added.
#! - ASSET_KEY is the vault key of the asset to add.
#! - ASSET_VALUE is the value of the asset to add.
#! - note_idx is the index of the note to which the asset is added.
#!
#! Panics if:
#! - the note index points to a non-existent output note.
Expand All @@ -194,30 +195,40 @@ end
#! - the total number of ASSETs exceeds the maximum of 256.
pub proc add_asset
# check if the note exists, it must be within [0, num_of_notes]
dup exec.memory::get_num_output_notes lte assert.err=ERR_NOTE_INVALID_INDEX
# => [note_idx, ASSET_KEY, ASSET_VALUE]

# get a pointer to the memory address of the note at which the asset will be stored
exec.memory::get_output_note_ptr
# => [note_ptr, ASSET_KEY, ASSET_VALUE]

# duplicate note ptr
dup movdn.9 movdn.9
# => [ASSET_KEY, ASSET_VALUE, note_ptr, note_ptr]
dup.8 exec.memory::get_num_output_notes lte assert.err=ERR_NOTE_INVALID_INDEX
# => [ASSET_KEY, ASSET_VALUE, note_idx]

# validate the asset
exec.asset::validate
# => [ASSET_KEY, ASSET_VALUE, note_ptr, note_ptr]
# => [ASSET_KEY, ASSET_VALUE, note_idx]

# emit event to signal that a new asset is going to be added to the note.
# emit event to signal that a new asset is going to be added to the note
emit.NOTE_BEFORE_ADD_ASSET_EVENT
# => [ASSET_KEY, ASSET_VALUE, note_ptr]
# => [ASSET_KEY, ASSET_VALUE, note_idx]

# prepare the stack for the callback
swapw dupw.1
# => [ASSET_KEY, ASSET_VALUE, ASSET_KEY, note_idx]

dup.12 movdn.8
# => [ASSET_KEY, ASSET_VALUE, note_idx, ASSET_KEY, note_idx]

# invoke the callback
exec.callbacks::on_before_asset_added_to_note
swapw
# => [ASSET_KEY, PROCESSED_ASSET_VALUE, note_idx]

movup.8 exec.memory::get_output_note_ptr dup
# => [note_ptr, note_ptr, ASSET_KEY, PROCESSED_ASSET_VALUE]

movdn.9 movdn.9
# => [ASSET_KEY, PROCESSED_ASSET_VALUE, note_ptr, note_ptr]

# add the asset to the note
exec.add_asset_raw
# => [note_ptr]

# emit event to signal that a new asset was added to the note.
# emit event to signal that a new asset was added to the note
emit.NOTE_AFTER_ADD_ASSET_EVENT
# => [note_ptr]

Expand Down
8 changes: 5 additions & 3 deletions crates/miden-protocol/asm/protocol/asset.masm
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,11 @@ end

#! Creates a non fungible asset for the specified non-fungible faucet.
#!
#! Inputs: [faucet_id_suffix, faucet_id_prefix, DATA_HASH]
#! Inputs: [enable_callbacks, faucet_id_suffix, faucet_id_prefix, DATA_HASH]
#! Outputs: [ASSET_KEY, ASSET_VALUE]
#!
#! Where:
#! - enable_callbacks is a flag (0 or 1) indicating whether asset callbacks are enabled.
#! - faucet_id_{suffix,prefix} are the suffix and prefix felts of the faucet to create the asset
#! for.
#! - DATA_HASH is the data hash of the non-fungible asset to create.
Expand All @@ -78,13 +79,14 @@ end
#!
#! Panics if:
#! - the provided faucet ID is not a non-fungible faucet.
#! - enable_callbacks is not 0 or 1.
#!
#! Invocation: exec
pub proc create_non_fungible_asset
# assert the faucet is a non-fungible faucet
dup.1 exec.account_id::is_non_fungible_faucet
dup.2 exec.account_id::is_non_fungible_faucet
assert.err=ERR_NON_FUNGIBLE_ASSET_PROVIDED_FAUCET_ID_IS_INVALID
# => [faucet_id_suffix, faucet_id_prefix, DATA_HASH]
# => [enable_callbacks, faucet_id_suffix, faucet_id_prefix, DATA_HASH]

# SAFETY: faucet ID was validated
exec.::miden::protocol::util::asset::create_non_fungible_asset_unchecked
Expand Down
8 changes: 6 additions & 2 deletions crates/miden-protocol/asm/protocol/faucet.masm
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,13 @@ end
#!
#! Invocation: exec
pub proc create_non_fungible_asset
# get the id of the faucet the transaction is being executed against
# fetch the id of the faucet the transaction is being executed against
exec.active_account::get_id
# => [faucet_id_suffix, faucet_id_prefix, DATA_HASH]
# => [id_suffix, id_prefix, DATA_HASH]

# check whether the faucet has callbacks defined
exec.has_callbacks
# => [has_callbacks, id_suffix, id_prefix, DATA_HASH]

# build the non-fungible asset
exec.asset::create_non_fungible_asset
Expand Down
Loading
Loading