miden-lib 0.12.4

Standard library of the Miden protocol
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
use.$kernel::account
use.$kernel::memory
use.$kernel::note
use.$kernel::asset
use.$kernel::constants
use.std::word

# CONSTANTS
# =================================================================================================

# Constants for different note types
const.PUBLIC_NOTE=1     # 0b01
const.PRIVATE_NOTE=2    # 0b10
const.ENCRYPTED_NOTE=3  # 0b11

# The note type must be PUBLIC, unless the high bits are `0b11`. (See the table below.)
const.LOCAL_ANY_PREFIX=3 # 0b11

# ERRORS
# =================================================================================================

const.ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT="number of output notes in the transaction exceeds the maximum limit of 1024"

const.ERR_NOTE_INVALID_TYPE="invalid note type"

const.ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS="requested output note index should be less than the total number of created output notes"

const.ERR_NOTE_INVALID_INDEX="failed to find note at the given index; index must be within [0, num_of_notes]"

const.ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED="adding a fungible asset to a note cannot exceed the max_amount of 9223372036854775807"

const.ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS="non-fungible asset that already exists in the note cannot be added again"

# The 2 highest bits in the u32 tag have the following meaning:
#
# | Prefix | Name                   | [`NoteExecutionMode`] | Target                   | Allowed [`NoteType`] |
# | :----: | :--------------------: | :-------------------: | :----------------------: | :------------------: |
# | `0b00` | `NetworkAccount`       | Network               | Network Account          | [`NoteType::Public`] |
# | `0b01` | `NetworkUseCase`       | Network               | Use case                 | [`NoteType::Public`] |
# | `0b10` | `LocalPublicAny`       | Local                 | Any                      | [`NoteType::Public`] |
# | `0b11` | `LocalAny`             | Local                 | Any                      | Any                  |
#
# Execution: Is a hint for the network, to check if the note can be consumed by a network controlled
#   account
# Target: Is a hint for the type of target. Use case means the note may be consumed by anyone,
#   specific means there is a specific target for the note (the target may be a public key, a user
#   that knows some secret, or a specific account ID)
#
# Only the note type from the above list is enforced. The other values are only hints intended as a
# best effort optimization strategy. A badly formatted note may 1. not be consumed because honest
# users won't see the note 2. generate slightly more load as extra validation is performed for the
# invalid tags. None of these scenarios have any significant impact.

const.ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX="invalid note type for the given note tag prefix"

const.ERR_NOTE_TAG_MUST_BE_U32="the note's tag must fit into a u32 so the 32 most significant bits must be zero"

# EVENTS
# =================================================================================================

# Event emitted before a new note is created.
const.NOTE_BEFORE_CREATED_EVENT=event("miden::note::before_created")
# Event emitted after a new note is created.
const.NOTE_AFTER_CREATED_EVENT=event("miden::note::after_created")

# Event emitted before an ASSET is added to a note
const.NOTE_BEFORE_ADD_ASSET_EVENT=event("miden::note::before_add_asset")
# Event emitted after an ASSET is added to a note
const.NOTE_AFTER_ADD_ASSET_EVENT=event("miden::note::after_add_asset")

# OUTPUT NOTE PROCEDURES
# =================================================================================================

#! Creates a new note and returns the index of the note.
#!
#! Inputs:  [tag, aux, note_type, execution_hint, RECIPIENT]
#! Outputs: [note_idx]
#!
#! Where:
#! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them.
#! - aux is the arbitrary user-defined value.
#! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain
#!   or off-chain).
#! - execution_hint is the hint which specifies when a note is ready to be consumed.
#! - RECIPIENT defines spend conditions for the note.
#! - note_idx is the index of the created note.
#!
#! Panics if:
#! - the note_type is not valid.
#! - the note_tag is not an u32.
#! - the note_tag starts with anything but 0b11 and note_type is not public.
#! - the number of output notes exceeds the maximum limit of 1024.
export.create
    emit.NOTE_BEFORE_CREATED_EVENT

    exec.build_metadata
    # => [NOTE_METADATA, RECIPIENT]

    # get the index for the next note to be created and increment counter
    exec.increment_num_output_notes dup movdn.9
    # => [note_idx, NOTE_METADATA, RECIPIENT, note_idx]

    # get a pointer to the memory address at which the note will be stored
    exec.memory::get_output_note_ptr
    # => [note_ptr, NOTE_METADATA, RECIPIENT, note_idx]

    movdn.4
    # => [NOTE_METADATA, note_ptr, RECIPIENT, note_idx]

    # emit event to signal that a new note is created
    emit.NOTE_AFTER_CREATED_EVENT

    # set the metadata for the output note
    dup.4
    # => [note_ptr, NOTE_METADATA, note_ptr, RECIPIENT, note_idx]
    exec.memory::set_output_note_metadata dropw
    # => [note_ptr, RECIPIENT, note_idx]

    # set the RECIPIENT for the output note
    exec.memory::set_output_note_recipient dropw
    # => [note_idx]
end

#! Returns the information about assets in the output note with the specified index.
#!
#! The provided output note index is expected to be less than the total number of output notes.
#!
#! Inputs:  [note_index]
#! Outputs: [ASSETS_COMMITMENT, num_assets]
#!
#! Where:
#! - note_index is the index of the output note whose assets info should be returned.
#! - num_assets is the number of assets in the specified note.
#! - ASSETS_COMMITMENT is a sequential hash of the assets in the specified note.
export.get_assets_info
    # get the note data pointer based on the index of the requested note
    exec.memory::get_output_note_ptr
    # => [note_data_ptr]

    # get the number of assets in the note
    dup exec.memory::get_output_note_num_assets swap
    # => [note_data_ptr, num_assets]

    # get the assets commitment
    dup exec.note::compute_output_note_assets_commitment
    # => [ASSETS_COMMITMENT, note_data_ptr, num_assets]

    # next we should store the assets in the advice map using the computed assets commitment to be
    # able to get the assets later (in the `miden::output_note::get_assets` procedure)

    # get the start and the end pointers of the asset data
    #
    # notice that if the number of assets is odd, the asset data end pointer will be shifted one
    # word further to make the assets number even (the same way it is done in the
    # `note::compute_output_note_assets_commitment` procedure)
    movup.4 exec.memory::get_output_note_asset_data_ptr
    # => [assets_data_ptr, ASSETS_COMMITMENT, num_assets]

    dup dup.6 dup is_odd add
    # => [padded_num_assets, assets_data_ptr, assets_data_ptr, ASSETS_COMMITMENT, num_assets]

    mul.4 add
    # => [assets_end_ptr, assets_start_ptr, ASSETS_COMMITMENT, num_assets]

    movdn.5 movdn.4
    # => [ASSETS_COMMITMENT, assets_start_ptr, assets_end_ptr, num_assets]

    # store the assets data to the advice map using ASSETS_COMMITMENT as a key
    adv.insert_mem
    # => [ASSETS_COMMITMENT, assets_start_ptr, assets_end_ptr, num_assets]

    # remove asset pointers from the stack
    movup.4 drop movup.4 drop
    # => [ASSETS_COMMITMENT, num_assets]
end

#! Adds the ASSET to the note specified by the index.
#!
#! Inputs:  [note_idx, ASSET]
#! Outputs: []
#!
#! Where:
#! - note_idx is the index of the note to which the asset is added.
#! - ASSET can be a fungible or non-fungible asset.
#!
#! Panics if:
#! - the ASSET is malformed (e.g., invalid faucet ID).
#! - the max amount of fungible assets is exceeded.
#! - the non-fungible asset already exists in the note.
#! - the total number of ASSETs exceeds the maximum of 256.
export.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]

    # get a pointer to the memory address of the note at which the asset will be stored
    dup movdn.5 exec.memory::get_output_note_ptr
    # => [note_ptr, ASSET, note_idx]

    # get current num of assets
    dup exec.memory::get_output_note_num_assets movdn.5
    # => [note_ptr, ASSET, num_of_assets, note_idx]

    # validate the ASSET
    movdn.4 exec.asset::validate_asset
    # => [ASSET, note_ptr, num_of_assets, note_idx]

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

    # Check if ASSET to add is fungible
    exec.asset::is_fungible_asset
    # => [is_fungible_asset?, ASSET, note_ptr, num_of_assets, note_idx]

    if.true
        # ASSET to add is fungible
        exec.add_fungible_asset
        # => [note_ptr, note_idx]
    else
        # ASSET to add is non-fungible
        exec.add_non_fungible_asset
        # => [note_ptr, note_idx]
    end
    # => [note_ptr, note_idx]

    # update the assets commitment dirty flag to signal that the current assets commitment is not
    # valid anymore
    push.1 swap exec.memory::set_output_note_dirty_flag
    # => [note_idx]

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

    # drop the note index
    drop
    # => []
end

#! Assert that the provided note index is less than the total number of output notes.
#!
#! Inputs:  [note_index]
#! Outputs: [note_index]
export.assert_note_index_in_bounds
    # assert that the provided note index is less than the total number of notes
    dup exec.memory::get_num_output_notes
    # => [output_notes_num, note_index, note_index]

    u32assert2.err=ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS
    u32lt assert.err=ERR_OUTPUT_NOTE_INDEX_OUT_OF_BOUNDS
    # => [note_index]
end

# HELPER PROCEDURES
# =================================================================================================

#! Builds the stack into the NOTE_METADATA word, encoding the note type and execution hint into a
#! single element.
#! Note that this procedure is only exported so it can be tested. It should not be called from
#! non-test code.
#!
#! Inputs:  [tag, aux, note_type, execution_hint]
#! Outputs: [NOTE_METADATA]
#!
#! Where:
#! - tag is the note tag which can be used by the recipient(s) to identify notes intended for them.
#! - aux is the arbitrary user-defined value.
#! - note_type is the type of the note, which defines how the note is to be stored (e.g., on-chain
#!   or off-chain).
#! - execution_hint is the hint which specifies when a note is ready to be consumed.
#! - NOTE_METADATA is the metadata associated with a note.
export.build_metadata
    # Validate the note type.
    # --------------------------------------------------------------------------------------------

    # NOTE: encrypted notes are currently unsupported
    dup.2 eq.PRIVATE_NOTE dup.3 eq.PUBLIC_NOTE or assert.err=ERR_NOTE_INVALID_TYPE
    # => [tag, aux, note_type, execution_hint]

    # copy data to validate the tag
    dup.2 push.PUBLIC_NOTE dup.1 dup.3
    # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint]

    u32assert.err=ERR_NOTE_TAG_MUST_BE_U32
    # => [tag, note_type, public_note, note_type, tag, aux, note_type, execution_hint]

    # enforce the note type depending on the tag' bits
    u32shr.30 eq.LOCAL_ANY_PREFIX cdrop
    assert_eq.err=ERR_NOTE_INVALID_NOTE_TYPE_FOR_NOTE_TAG_PREFIX
    # => [tag, aux, note_type, execution_hint]

    # Split execution hint into its tag and payload parts as they are encoded in separate elements
    # of the metadata.
    # --------------------------------------------------------------------------------------------

    # the execution_hint is laid out like this: [26 zero bits | payload (32 bits) | tag (6 bits)]
    movup.3
    # => [execution_hint, tag, aux, note_type]
    dup u32split drop
    # => [execution_hint_lo, execution_hint, tag, aux, note_type]

    # mask out the lower 6 execution hint tag bits.
    u32and.0x3f
    # => [execution_hint_tag, execution_hint, tag, aux, note_type]

    # compute the payload by subtracting the tag value so the lower 6 bits are zero
    # note that this results in the following layout: [26 zero bits | payload (32 bits) | 6 zero bits]
    swap
    # => [execution_hint, execution_hint_tag, tag, aux, note_type]
    dup.1
    # => [execution_hint_tag, execution_hint, execution_hint_tag, tag, aux, note_type]
    sub
    # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type]

    # Merge execution hint payload and note tag.
    # --------------------------------------------------------------------------------------------

    # we need to move the payload to the upper 32 bits of the felt
    # we only need to shift by 26 bits because the payload is already shifted left by 6 bits
    # we shift the payload by multiplying with 2^26
    # this results in the lower 32 bits being zero which is where the note tag will be added
    mul.0x04000000
    # => [execution_hint_payload, execution_hint_tag, tag, aux, note_type]

    # add the tag to the payload to produce the merged value
    movup.2 add
    # => [note_tag_hint_payload, execution_hint_tag, aux, note_type]

    # Merge sender_id_suffix, note_type and execution_hint_tag.
    # --------------------------------------------------------------------------------------------

    exec.account::get_id
    # => [sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux, note_type]

    movup.5
    # => [note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux]
    # multiply by 2^6 to shift the two note_type bits left by 6 bits.
    mul.0x40
    # => [shifted_note_type, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, execution_hint_tag, aux]

    # merge execution_hint_tag into the note_type
    # this produces an 8-bit value with the layout: [note_type (2 bits) | execution_hint_tag (6 bits)]
    movup.4 add
    # => [merged_note_type_execution_hint_tag, sender_id_prefix, sender_id_suffix, note_tag_hint_payload, aux]

    # merge sender_id_suffix into this value
    movup.2 add
    # => [sender_id_suffix_type_and_hint_tag, sender_id_prefix, note_tag_hint_payload, aux]

    # Rearrange elements to produce the final note metadata layout.
    # --------------------------------------------------------------------------------------------

    swap movdn.3
    # => [sender_id_suffix_type_and_hint_tag, note_tag_hint_payload, aux, sender_id_prefix]
    swap
    # => [note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, aux, sender_id_prefix]
    movup.2
    # => [NOTE_METADATA = [aux, note_tag_hint_payload, sender_id_suffix_type_and_hint_tag, sender_id_prefix]]
end

#! Increments the number of output notes by one. Returns the index of the next note to be created.
#!
#! Inputs:  []
#! Outputs: [note_idx]
#!
#! Where:
#! - note_idx is the index of the next note to be created.
proc.increment_num_output_notes
    # get the current number of output notes
    exec.memory::get_num_output_notes
    # => [note_idx]

    # assert that there is space for a new note
    dup exec.constants::get_max_num_output_notes lt
    assert.err=ERR_TX_NUMBER_OF_OUTPUT_NOTES_EXCEEDS_LIMIT
    # => [note_idx]

    # increment the number of output notes
    dup add.1 exec.memory::set_num_output_notes
    # => [note_idx]
end

#! Adds a fungible asset to a note. If the note already holds an asset issued by the same faucet id
#! the two quantities are summed up and the new quantity is stored at the old position in the note.
#! In the other case, the asset is stored at the next available position.
#! Returns the pointer to the note the asset was stored at.
#!
#! Inputs:  [ASSET, note_ptr, num_of_assets, note_idx]
#! Outputs: [note_ptr]
#!
#! Where:
#! - ASSET is the fungible asset to be added to the note.
#! - note_ptr is the pointer to the note the asset will be added to.
#! - num_of_assets is the current number of assets.
#! - note_idx is the index of the note the asset will be added to.
#!
#! Panics if
#! - the summed amounts exceed the maximum amount of fungible assets.
proc.add_fungible_asset
    dup.4 exec.memory::get_output_note_asset_data_ptr
    # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]

    # compute the pointer at which we should stop iterating
    dup dup.7 mul.4 add
    # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]

    # reorganize and pad the stack, prepare for the loop
    movdn.5 movdn.5 padw dup.9
    # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

    # compute the loop latch
    dup dup.10 neq
    # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets,
    #     note_idx]

    while.true
        mem_loadw_be
        # => [STORED_ASSET, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

        dup.4 eq
        # => [are_equal, 0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr,
        #     num_of_assets, note_idx]

        if.true
            # add the asset quantity, we don't overflow here, bc both ASSETs are valid.
            movup.2 movup.6 add
            # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr,
            #     num_of_assets, note_idx]

            # check that we don't overflow bc we use lte
            dup exec.asset::get_fungible_asset_max_amount lte
            assert.err=ERR_NOTE_FUNGIBLE_MAX_AMOUNT_EXCEEDED
            # => [updated_amount, 0, 0, faucet_id, 0, 0, end_asset_ptr, asset_ptr, note_ptr,
            #     num_of_assets, note_idx]

            # prepare stack to store the "updated" ASSET'' with the new quantity
            movdn.5
            # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

            # decrease num_of_assets by 1 to offset incrementing it later
            movup.9 sub.1 movdn.9
            # => [0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr, num_of_assets - 1, note_idx]

            # end the loop we add 0's to the stack to have the correct number of elements
            push.0.0 dup.9 push.0
            # => [0, asset_ptr, 0, 0, 0, 0, ASSET'', end_asset_ptr, asset_ptr, note_ptr,
            #     num_of_assets - 1, note_idx]
        else
            # => [0, 0, stored_amount, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets,
            #     note_idx]

            # drop ASSETs and increment the asset pointer
            movup.2 drop push.0.0 movup.9 add.4 dup movdn.10
            # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr,
            #     num_of_assets, note_idx]

            # check if we reached the end of the loop
            dup dup.10 neq
        end
    end
    # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]
    # prepare stack for storing the ASSET
    movdn.4 dropw
    # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

    # Store the fungible asset, either the combined ASSET or the new ASSET
    mem_storew_be dropw drop drop
    # => [note_ptr, num_of_assets, note_idx]

    # increase the number of assets in the note
    swap add.1 dup.1 exec.memory::set_output_note_num_assets
    # => [note_ptr, note_idx]
end

#! Adds a non-fungible asset to a note at the next available position.
#! Returns the pointer to the note the asset was stored at.
#!
#! Inputs:  [ASSET, note_ptr, num_of_assets, note_idx]
#! Outputs: [note_ptr, note_idx]
#!
#! Where:
#! - ASSET is the non-fungible asset to be added to the note.
#! - note_ptr is the pointer to the note the asset will be added to.
#! - num_of_assets is the current number of assets.
#! - note_idx is the index of the note the asset will be added to.
#!
#! Panics if:
#! - the non-fungible asset already exists in the note.
proc.add_non_fungible_asset
    dup.4 exec.memory::get_output_note_asset_data_ptr
    # => [asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]

    # compute the pointer at which we should stop iterating
    dup dup.7 mul.4 add
    # => [end_asset_ptr, asset_ptr, ASSET, note_ptr, num_of_assets, note_idx]

    # reorganize and pad the stack, prepare for the loop
    movdn.5 movdn.5 padw dup.9
    # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

    # compute the loop latch
    dup dup.10 neq
    # => [latch, asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets,
    #     note_idx]

    while.true
        # load the asset and compare
        mem_loadw_be exec.word::test_eq
        assertz.err=ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS
        # => [ASSET', ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

        # drop ASSET' and increment the asset pointer
        dropw movup.5 add.4 dup movdn.6 padw movup.4
        # => [asset_ptr + 4, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr + 4, note_ptr,
        #     num_of_assets, note_idx]

        # check if we reached the end of the loop
        dup dup.10 neq
    end
    # => [asset_ptr, 0, 0, 0, 0, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

    # prepare stack for storing the ASSET
    movdn.4 dropw
    # => [asset_ptr, ASSET, end_asset_ptr, asset_ptr, note_ptr, num_of_assets, note_idx]

    # end of the loop reached, no error so we can store the non-fungible asset
    mem_storew_be dropw drop drop
    # => [note_ptr, num_of_assets, note_idx]

    # increase the number of assets in the note
    swap add.1 dup.1 exec.memory::set_output_note_num_assets
    # => [note_ptr, note_idx]
end