diff --git a/CHANGELOG.md b/CHANGELOG.md index c9ff19a3d..96fc705b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - [BREAKING] Updated limits, introduced additional limits (#889). - Introduced `AccountDelta` maximum size limit of 32 KiB (#889). - [BREAKING] Moved `MAX_NUM_FOREIGN_ACCOUNTS` into `miden-objects` (#904). +- Implemented `storage_size`, updated storage bounds (#886). ## 0.5.1 (2024-08-28) - `miden-objects` crate only diff --git a/miden-lib/asm/kernels/transaction/api.masm b/miden-lib/asm/kernels/transaction/api.masm index 7fee6b195..dc3fe9ca6 100644 --- a/miden-lib/asm/kernels/transaction/api.masm +++ b/miden-lib/asm/kernels/transaction/api.masm @@ -18,7 +18,7 @@ use.kernel::tx # the memory. # # ================================================================================================= -# ERRORS +# ERRORS # ================================================================================================= # For faucets the slot FAUCET_STORAGE_DATA_SLOT is reserved and can not be used with set_account_item @@ -33,7 +33,7 @@ const.ERR_READING_MAP_VALUE_FROM_NON_MAP_SLOT=0x00020049 # Provided kernel procedure offset is out of bounds const.ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS=0x00020053 -# EVENTS +# EVENTS # ================================================================================================= # Event emitted before an asset is added to the account vault. @@ -51,19 +51,19 @@ const.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT=131075 #! Authenticates that the invocation of a kernel procedure originates from the account context. #! -#! Stack: [...] -#! Output: [storage_offset, ...] +#! Stack: [] +#! Output: [storage_offset, storage_size] #! #! Panics if: #! - the invocation of the kernel procedure does not originate from the account context. proc.authenticate_account_origin # get the hash of the caller padw caller - # => [CALLER, ...] + # => [CALLER] # assert that the caller is from the user context exec.account::authenticate_procedure - # => [storage_offset, ...] + # => [storage_offset, storage_size] end # KERNEL PROCEDURES @@ -98,7 +98,7 @@ end export.get_account_nonce # drop the procedure's hash dropw - + # get the account nonce exec.account::get_nonce # => [0, nonce] @@ -117,7 +117,7 @@ end export.get_initial_account_hash # drop the procedure's hash dropw - + # get the initial account hash exec.account::get_initial_hash # => [H, 0, 0, 0, 0] @@ -136,7 +136,7 @@ end export.get_current_account_hash # drop the procedure's hash dropw - + # get the current account hash exec.account::get_current_hash # => [ACCT_HASH, 0, 0, 0, 0] @@ -162,9 +162,9 @@ export.incr_account_nonce # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [value] # arrange stack @@ -193,7 +193,7 @@ export.get_account_item # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [storage_offset, index, 0, 0, 0] + # => [storage_offset, storage_size, index, 0, 0, 0] # apply offset to storage slot index exec.account::apply_storage_offset @@ -228,7 +228,7 @@ export.set_account_item # drop the procedure's hash dropw - + # if the transaction is being executed against a faucet account then assert # index != FAUCET_STORAGE_DATA_SLOT (reserved slot) dup exec.account::get_faucet_storage_data_slot eq @@ -238,7 +238,7 @@ export.set_account_item # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [storage_offset, index, V', 0, 0, 0] + # => [storage_offset, storage_size, index, V', 0, 0, 0] # apply offset to storage slot index exec.account::apply_storage_offset @@ -268,7 +268,7 @@ end export.get_account_map_item # drop the procedure's hash dropw - + # check if storage type is map dup exec.account::get_storage_slot_type # => [slot_type, index, KEY, ...] @@ -279,7 +279,7 @@ export.get_account_map_item # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [storage_offset, index, KEY, ...] + # => [storage_offset, storage_size, index, KEY, ...] # apply offset to storage slot index exec.account::apply_storage_offset @@ -322,10 +322,10 @@ export.set_account_map_item.1 # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context exec.authenticate_account_origin - # => [storage_offset, index, KEY, NEW_VALUE, ...] + # => [storage_offset, storage_size, index, KEY, NEW_VALUE, ...] # apply offset to storage slot index exec.account::apply_storage_offset @@ -348,8 +348,8 @@ export.set_account_map_item.1 # => [OLD_MAP_ROOT, OLD_MAP_VALUE, 0, ...] end -#! Sets the code of the account the transaction is being executed against. -#! This procedure can only be executed on regular accounts with updatable code. Otherwise, this +#! Sets the code of the account the transaction is being executed against. +#! This procedure can only be executed on regular accounts with updatable code. Otherwise, this #! procedure fails. #! #! Stack: [KERNEL_PROCEDURE_HASH, CODE_COMMITMENT] @@ -366,9 +366,9 @@ export.set_account_code # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [CODE_COMMITMENT] # arrange stack @@ -394,7 +394,7 @@ end export.account_vault_get_balance # drop the procedure's hash dropw - + # get the vault root exec.memory::get_acct_vault_root_ptr swap # => [faucet_id, acct_vault_root_ptr] @@ -418,7 +418,7 @@ end export.account_vault_has_non_fungible_asset # drop the procedure's hash dropw - + # arrange stack and get the vault root push.0 movdn.4 push.0 movdn.4 push.0 movdn.4 exec.memory::get_acct_vault_root_ptr movdn.4 # => [ASSET, 0, 0, 0] @@ -451,9 +451,9 @@ export.account_vault_add_asset # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [ASSET] push.19891 drop # TODO: remove line, see miden-vm/#1122 @@ -498,9 +498,9 @@ export.account_vault_remove_asset # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [ASSET] push.20071 drop # TODO: remove line, see miden-vm/#1122 @@ -515,13 +515,13 @@ export.account_vault_remove_asset exec.asset_vault::remove_asset # => [ASSET] - # emit event to signal that an asset is being removed from the account vault + # emit event to signal that an asset is being removed from the account vault push.20149 drop # TODO: remove line, see miden-vm/#1122 emit.ACCOUNT_VAULT_AFTER_REMOVE_ASSET_EVENT # => [ASSET] end -#! Returns the number of assets and the assets hash of the note currently being processed. +#! Returns the number of assets and the assets hash of the note currently being processed. #! #! Inputs: [KERNEL_PROCEDURE_HASH, 0, 0, 0, 0, 0] #! Outputs: [ASSETS_HASH, num_assets] @@ -535,7 +535,7 @@ end export.get_note_assets_info # drop the procedure's hash dropw - + # get the assets info exec.note::get_assets_info # => [ASSETS_HASH, num_assets, 0, 0, 0, 0, 0] @@ -555,7 +555,7 @@ end export.get_note_inputs_hash # drop the procedure's hash dropw - + exec.note::get_note_inputs_hash # => [NOTE_INPUTS_HASH, EMPTY_WORD] @@ -563,7 +563,7 @@ export.get_note_inputs_hash # => [NOTE_INPUTS_HASH] end -#! Returns the sender of the note currently being processed. +#! Returns the sender of the note currently being processed. #! #! Inputs: [KERNEL_PROCEDURE_HASH, 0] #! Outputs: [sender] @@ -576,7 +576,7 @@ end export.get_note_sender # drop the procedure's hash dropw - + exec.note::get_sender swap drop # => [sender] end @@ -591,12 +591,12 @@ end export.get_block_number # drop the procedure's hash dropw - + # get the block number exec.tx::get_block_number # => [num, 0] - # organize the stack for return + # organize the stack for return swap drop # => [num] end @@ -611,7 +611,7 @@ end export.get_block_hash # drop the procedure's hash dropw - + dropw exec.tx::get_block_hash # => [BLOCK_HASH] end @@ -632,7 +632,7 @@ end export.get_input_notes_commitment # drop the procedure's hash dropw - + exec.tx::get_input_notes_commitment # => [COM, 0, 0, 0, 0] @@ -651,7 +651,7 @@ end export.get_output_notes_hash # drop the procedure's hash dropw - + # get the output notes hash exec.tx::get_output_notes_hash # => [COM, 0, 0, 0, 0] @@ -683,9 +683,9 @@ export.create_note # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [tag, aux, note_type, execution_hint, RECIPIENT, pad(8)] exec.tx::create_note @@ -710,9 +710,9 @@ export.add_asset_to_note # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [note_idx, ASSET] # duplicate the asset word to be able to return it @@ -733,7 +733,7 @@ end export.get_account_vault_commitment # drop the procedure's hash dropw - + # fetch the account vault root exec.memory::get_acct_vault_root # => [COM, 0, 0, 0, 0] @@ -767,12 +767,12 @@ export.mint_asset # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [ASSET] - # mint the asset + # mint the asset exec.faucet::mint # => [ASSET] end @@ -794,7 +794,7 @@ end #! - For fungible faucets: #! - if the amount being burned is greater than the total input to the transaction. #! - For non-fungible faucets: -#! - if the non-fungible asset being burned does not exist or was not provided as input to the +#! - if the non-fungible asset being burned does not exist or was not provided as input to the #! transaction via a note or the accounts vault. export.burn_asset # check that this procedure was executed against the native account @@ -802,9 +802,9 @@ export.burn_asset # drop the procedure's hash dropw - + # authenticate that the procedure invocation originates from the account context - exec.authenticate_account_origin drop + exec.authenticate_account_origin drop drop # => [ASSET] # burn the asset @@ -826,7 +826,7 @@ end export.get_fungible_faucet_total_issuance # drop the procedure's hash dropw - + # assert that we are executing a transaction against a fungible faucet (access checks) exec.account::get_id exec.account::is_fungible_faucet assert.err=ERR_ACCT_MUST_BE_A_FAUCET # => [0] @@ -853,7 +853,7 @@ end export.get_note_serial_number # drop the procedure's hash dropw - + exec.note::get_serial_number # => [SERIAL_NUMBER] @@ -868,7 +868,7 @@ end #! Outputs: [pad(16)] #! #! Where: -#! - foreign_account_id is the ID of the foreign account whose procedure is going to be executed. +#! - foreign_account_id is the ID of the foreign account whose procedure is going to be executed. #! #! Panics if: #! - the current context is not a native context. @@ -894,7 +894,7 @@ export.end_foreign_context end #! Updates the transaction expiration time delta. -#! Once set, the delta can be decreased but not increased. +#! Once set, the delta can be decreased but not increased. #! #! The input block height delta is added to the reference block in order to output an upper limit #! up until which the transaction will be considered valid (not expired). @@ -936,7 +936,7 @@ end #! - the provided procedure offset exceeds the number of kernel procedures. export.exec_kernel_proc # check that the provided procedure offset is within expected bounds - dup exec.memory::get_num_kernel_procedures + dup exec.memory::get_num_kernel_procedures lt assert.err=ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS # => [procedure_pointer, , ] diff --git a/miden-lib/asm/kernels/transaction/lib/account.masm b/miden-lib/asm/kernels/transaction/lib/account.masm index a706aba93..c813c2fcd 100644 --- a/miden-lib/asm/kernels/transaction/lib/account.masm +++ b/miden-lib/asm/kernels/transaction/lib/account.masm @@ -40,6 +40,9 @@ const.ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS=0x0002004E # Storage offset is invalid for a faucet account (0 is prohibited being the faucet reserved data slot) const.ERR_INVALID_FAUCET_STORAGE_OFFSET=0x0002004F +# Storage offset is invalid for 0 storage size (should be 0) +const.ERR_INVALID_STORAGE_OFFSET_FOR_SIZE=0x00020056 + # CONSTANTS # ================================================================================================= @@ -307,34 +310,34 @@ export.set_code # => [] end -#! Applies offset to provided storage slot index for storage access +#! Applies storage offset to provided storage slot index for storage access #! #! Panics: -#! - slot index is out of bounds #! - computed index is out of bounds #! -#! Stack: [storage_offset, slot_index] +#! Stack: [storage_offset, storage_size, slot_index] #! Output: [offset_slot_index] export.apply_storage_offset - # verify that slot_index is in bounds - dup.1 exec.memory::get_num_storage_slots dup movdn.4 lt assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS - # => [storage_offset, slot_index, num_storage_slots] + # offset index + dup movup.3 add + # => [offset_slot_index, storage_offset, storage_size] - # verify that the computed offset_slot_index is in bounds - add dup movup.2 lt assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + # verify that slot_index is in bounds + movdn.2 add dup.1 gt assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS # => [offset_slot_index] end -#! Validates all account procedures storage offsets by checking that: -#! - All storage offsets are in bounds +#! Validates all account procedures storage metadata by checking that: +#! - All storage offsets and sizes are in bounds #! - All storage offsets adhere to account type specific rules +#! - All procedures not accessing storage have offset and size set to 0 #! #! Notes: #! - For faucets checks that no storage offset is 0 (preventing reserved storage slot access) #! #! Stack: [] #! Output: [] -export.validate_storage_offsets +export.validate_procedure_metadata # get number of account procedures and number of storage slots exec.memory::get_num_account_procedures exec.memory::get_num_storage_slots # => [num_storage_slots, num_account_procedures] @@ -351,29 +354,32 @@ export.validate_storage_offsets # account has between 1 and 256 procedures with associated offsets if.true while.true - # get storage offset from memory - dup exec.get_procedure_storage_offset - # => [storage_offset, index, num_storage_slots, num_account_procedures] + # get storage offset and size from memory + dup exec.get_procedure_metadata + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] # assert that storage offset is not 0 dup push.0 neq assert.err=ERR_INVALID_FAUCET_STORAGE_OFFSET - # => [storage_offset, index, num_storage_slots, num_account_procedures] + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] # TODO: This is a temporary check for faucets. We add 1 to all faucet offsets # to prevent access to the reserved faucet data slot. When the assembler - # will support storage offsets remove this check. + # will support storage offsets and sizes remove this check. # check if the storage offset and the number of storage slots are 1 - dup dup.3 eq.1 swap eq.1 and - # => [not_both_1, storage_offset, index, num_storage_slots, num_account_procedures] + dup dup.4 eq.1 swap eq.1 and + # => [both_1, storage_offset, storage_size, index, num_storage_slots, num_account_procedures] - if.false - # assert that storage offset is in bounds - dup.2 lt assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + if.true + # clean stack + drop drop # => [index, num_storage_slots, num_account_procedures] else - # TODO: Remove this drop with the check above - # skip bound check and drop storage_offset - drop + # assert that storage offset is in bounds + dup dup.3 lt assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # assert that storage limit is in bounds + add dup.2 lte assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS # => [index, num_storage_slots, num_account_procedures] end @@ -383,12 +389,24 @@ export.validate_storage_offsets end else while.true - # get storage offset from memory - dup exec.get_procedure_storage_offset - # => [storage_offset, index, num_storage_slots, num_account_procedures] + # get storage offset and size from memory + dup exec.get_procedure_metadata + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] # assert that storage offset is in bounds - dup.2 lt assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + dup dup.4 lt assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # TODO: Find a way to remove this `if` statement + # assert that if size is 0 then offset is 0 + dup.1 eq.0 + if.true + dup eq.0 assert.err=ERR_INVALID_STORAGE_OFFSET_FOR_SIZE + end + # => [storage_offset, storage_size, index, num_storage_slots, num_account_procedures] + + # assert that storage limit is in bounds + add dup.2 lte assert.err=ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS # => [index, num_storage_slots, num_account_procedures] # check if we should continue looping @@ -555,10 +573,11 @@ end #! Returns the procedure information #! #! Stack: [index, ...] -#! Output: [PROC_ROOT, storage_offset, ...] +#! Output: [PROC_ROOT, storage_offset, storage_size] #! #! - PROC_ROOT is the hash of the procedure. #! - storage_offset is the procedure storage offset. +#! - storage_size is the number of storage slots the procedure is allowed to access. #! #! Panics if #! - index is out of bounds @@ -568,20 +587,26 @@ export.get_procedure_info # => [index] # get procedure pointer - mul.2 exec.memory::get_acct_procedures_section_ptr add dup add.1 - # => [offset_ptr, proc_ptr] + mul.2 exec.memory::get_acct_procedures_section_ptr add dup add.1 swap + # => [metadata_ptr, proc_ptr] # load procedure information from memory - mem_load swap padw movup.4 mem_loadw - # => [PROC_ROOT, storage_offset] + padw movup.4 mem_loadw padw movup.8 mem_loadw + # => [METADATA, PROC_ROOT] + + # keep relevant data + drop drop swap movdn.5 movdn.5 + # => [PROC_ROOT, storage_offset, storage_size] end #! Verifies that the procedure root is part of the account code #! #! Stack: [PROC_ROOT] -#! Output: [storage_offset] +#! Output: [storage_offset, storage_size] #! #! - PROC_ROOT is the hash of the procedure to authenticate. +#! - storage_offset is the procedure storage offset. +#! - storage_size is the number of storage slots the procedure is allowed to access. #! #! Panics if #! - procedure root is not part of the account code. @@ -591,13 +616,13 @@ export.authenticate_procedure emit.ACCOUNT_PUSH_PROCEDURE_INDEX_EVENT adv_push.1 # => [index, PROC_ROOT] - # get procedure info (PROC_ROOT, storage_offset) from memory stored at index + # get procedure info (PROC_ROOT, storage_offset, storage_size) from memory stored at index exec.get_procedure_info - # => [PROC_ROOT, storage_offset, PROC_ROOT] + # => [MEM_PROC_ROOT, storage_offset, storage_size, PROC_ROOT] # verify that PROC_ROOT exists in memory at index - movup.4 movdn.8 assert_eqw.err=ERR_PROC_NOT_PART_OF_ACCOUNT_CODE - # => [storage_offset] + movup.4 movdn.9 movup.4 movdn.9 assert_eqw.err=ERR_PROC_NOT_PART_OF_ACCOUNT_CODE + # => [storage_offset, storage_size] end #! Validates that the account seed, provided via the advice map, satisfies the seed requirements. @@ -714,21 +739,22 @@ proc.set_item_raw # => [OLD_VALUE] end -#! Returns the procedure storage offset +#! Returns the procedure metadata #! #! Note: #! - We assume that index has been validated and is within bounds #! -#! Stack: [index, ...] -#! Output: [storage_offset, ...] +#! Stack: [index] +#! Output: [storage_offset, storage_size] #! #! - storage_offset is the procedure storage offset. -proc.get_procedure_storage_offset - # get procedure storage offset pointer +#! - storage_size is the number of storage slots the procedure is allowed to access. +proc.get_procedure_metadata + # get procedure storage metadata pointer mul.2 exec.memory::get_acct_procedures_section_ptr add add.1 # => [storage_offset_ptr] - # load procedure storage offset from memory - mem_load - # => [storage_offset] + # load procedure metadata from memory and keep relevant data + padw movup.4 mem_loadw drop drop swap + # => [storage_offset, storage_size] end diff --git a/miden-lib/asm/kernels/transaction/lib/memory.masm b/miden-lib/asm/kernels/transaction/lib/memory.masm index 4e51b9064..9e7533d7d 100644 --- a/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -107,7 +107,7 @@ const.CHAIN_MMR_PEAKS_PTR=301 # KERNEL DATA # ------------------------------------------------------------------------------------------------- -# The memory address at which the number of the procedures of the selected kernel is stored. +# The memory address at which the number of the procedures of the selected kernel is stored. const.NUM_KERNEL_PROCEDURES_PTR=400 # The memory address at which the hashes of kernel procedures begin. @@ -714,7 +714,7 @@ end #! - acct_nonce is the account nonce. export.set_acct_nonce exec.get_current_account_data_ptr push.ACCT_ID_AND_NONCE_OFFSET add - padw dup.4 + padw dup.4 mem_loadw # => [old_nonce, 0, 0, old_id, acct_id_and_nonce_ptr, new_nonce] @@ -1322,13 +1322,13 @@ export.get_num_kernel_procedures push.NUM_KERNEL_PROCEDURES_PTR mem_load end -#! Returns a pointer to the memory where hashes of the kernel procedures are stored. +#! Returns a pointer to the memory where hashes of the kernel procedures are stored. #! #! Stack: [] #! Output: [kernel_procedures_ptr] #! #! Where: -#! - kernel_procedures_ptr is the memory address where the hashes of the kernel procedures are stored. +#! - kernel_procedures_ptr is the memory address where the hashes of the kernel procedures are stored. export.get_kernel_procedures_ptr push.KERNEL_PROCEDURES_PTR end diff --git a/miden-lib/asm/kernels/transaction/lib/prologue.masm b/miden-lib/asm/kernels/transaction/lib/prologue.masm index d783faa09..1da032421 100644 --- a/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -111,10 +111,10 @@ end # ================================================================================================= -#! Saves the procedure hashes of the chosen kernel to memory. Verifies that kernel root and kernel -#! hash match the sequential hash of all kernels and sequential hash of kernel procedures -#! respectively. -#! +#! Saves the procedure hashes of the chosen kernel to memory. Verifies that kernel root and kernel +#! hash match the sequential hash of all kernels and sequential hash of kernel procedures +#! respectively. +#! #! Inputs: #! Operand stack: [] #! Advice stack: [kernel_version] @@ -125,15 +125,15 @@ end #! Outputs: #! Operand stack: [] #! Advice stack: [] -#! +#! #! Where: -#! - kernel_version, index of the desired kernel in the array of all kernels available for the +#! - kernel_version, index of the desired kernel in the array of all kernels available for the #! current transaction #! - KERNEL_ROOT, accumulative hash from all kernel hashes. #! - [KERNEL_HASHES], array of each kernel hash #! - [KERNEL_PROCEDURE_HASHES], array of procedure hashes of the current kernel proc.process_kernel_data - # move the kernel offset to the operand stack + # move the kernel offset to the operand stack adv_push.1 # OS => [kernel_version] # AS => [] @@ -143,12 +143,12 @@ proc.process_kernel_data # OS => [KERNEL_ROOT, kernel_version] # AS => [] - # push the kernel hashes from the advice map to the advice stack + # push the kernel hashes from the advice map to the advice stack adv.push_mapvaln # OS => [KERNEL_ROOT, kernel_version] # AS => [len_felts, [KERNEL_HASHES]] - # move the number of felt elements in the [KERNEL_HASHES] array to the stack and get the + # move the number of felt elements in the [KERNEL_HASHES] array to the stack and get the # number of Words from it adv_push.1 div.4 # OS => [len_words, KERNEL_ROOT, kernel_version] @@ -156,7 +156,7 @@ proc.process_kernel_data # get the pointer to the memory where kernel hashes will be stored # Note: for now we use the same address for kernel hash and for kernel procedures since there is - # only one kernel and its hash will be overwritten by the procedures anyway. + # only one kernel and its hash will be overwritten by the procedures anyway. exec.memory::get_kernel_procedures_ptr swap # OS => [len_words, kernel_mem_ptr, KERNEL_ROOT, kernel_version] # AS => [[KERNEL_HASHES]] @@ -172,7 +172,7 @@ proc.process_kernel_data # AS => [] # get the hash of the kernel which will be used in the current transaction - exec.memory::get_kernel_procedures_ptr add + exec.memory::get_kernel_procedures_ptr add # OS => [kernel_ptr] # AS => [] @@ -180,12 +180,12 @@ proc.process_kernel_data # OS => [KERNEL_HASH] # AS => [] - # push the procedure hashes of the chosen kernel from the advice map to the advice stack + # push the procedure hashes of the chosen kernel from the advice map to the advice stack adv.push_mapvaln # OS => [KERNEL_HASH] # AS => [len_felts, [PROC_HASHES]] - # move the number of felt elements in the [PROC_HASHES] array to the stack and get the + # move the number of felt elements in the [PROC_HASHES] array to the stack and get the # number of Words from it adv_push.1 div.4 # OS => [len_words, KERNEL_HASH] @@ -399,6 +399,11 @@ proc.validate_new_account # --------------------------------------------------------------------------------------------- exec.account::validate_seed # => [] + + # Assert the provided procedures offsets and sizes satisfy storage requirements + # --------------------------------------------------------------------------------------------- + exec.account::validate_procedure_metadata + # => [] end #! Validates that storage slots match storage commitment and saves storage slots into memory. @@ -539,7 +544,7 @@ end #! - ACCOUNT_STORAGE_COMMITMENT, account's storage commitment. #! - ACCOUNT_CODE_COMMITMENT, account's code commitment. proc.process_account_data - # Initialize the current account data pointer in the bookkeeping section with the native offset + # Initialize the current account data pointer in the bookkeeping section with the native offset # (2048) exec.memory::set_current_account_data_ptr_to_native_account @@ -621,9 +626,6 @@ proc.process_account_data assert.err=ERR_PROLOGUE_OLD_ACCT_NONCE_ZERO # => [] end - - # validate account procedure storage offsets - exec.account::validate_storage_offsets end # INPUT NOTES DATA @@ -1171,10 +1173,10 @@ end #! - Any of the input notes do note exist in the note db. #! #! Operand stack: [ -#! BLOCK_HASH, -#! account_id, -#! INITIAL_ACCOUNT_HASH, -#! INPUT_NOTES_COMMITMENT, +#! BLOCK_HASH, +#! account_id, +#! INITIAL_ACCOUNT_HASH, +#! INPUT_NOTES_COMMITMENT, #! ] #! Advice stack: [ #! PREVIOUS_BLOCK_HASH, @@ -1218,7 +1220,7 @@ end #! - version, the current protocol version. #! - timestamp, the current timestamp. #! - NOTE_ROOT, root of the tree with all notes created in the block. -#! - kernel_version, index of the desired kernel in the array of all kernels available for the +#! - kernel_version, index of the desired kernel in the array of all kernels available for the #! current transaction. #! - account_nonce, account's nonce. #! - ACCOUNT_VAULT_ROOT, account's vault root. @@ -1239,6 +1241,6 @@ export.prepare_transaction exec.process_input_notes_data exec.process_tx_script_root # => [] - + push.MAX_BLOCK_NUM exec.memory::set_expiration_block_num end diff --git a/miden-lib/src/transaction/procedures/kernel_v0.rs b/miden-lib/src/transaction/procedures/kernel_v0.rs index 76e21aad8..a743730a9 100644 --- a/miden-lib/src/transaction/procedures/kernel_v0.rs +++ b/miden-lib/src/transaction/procedures/kernel_v0.rs @@ -8,19 +8,19 @@ use miden_objects::{digest, Digest, Felt}; /// Hashes of all dynamically executed procedures from the kernel 0. pub const KERNEL0_PROCEDURES: [Digest; 32] = [ // account_vault_add_asset - digest!(0xb8815bfacbdcb4c2, 0x6c7e694cf4f6a517, 0xf6233da2865ca264, 0xe51463cd0df6e896), + digest!(0x77365035d901b352, 0x85d8042000096df, 0xa8531ec691f24d17, 0xc67a8fd2677bf558), // account_vault_get_balance digest!(0x92b81d20684fa47, 0x4920ee53425609b9, 0x2f8c32c56898141c, 0x9e4542839e34452f), // account_vault_has_non_fungible_asset digest!(0x1b1e6ec92fabca80, 0xbb3847ce15f98cac, 0x7152391739b5e0b3, 0x696aaf2c879c4fde), // account_vault_remove_asset - digest!(0xff01966b06c569b, 0x99fc26250c155461, 0xe0293966a4c4c7ae, 0xdec4ef96fca23f11), + digest!(0xdf93ea4374fe098f, 0x63df56e7578d9661, 0xc5d3b1958456cc5, 0xbfeec68c1c6b4ca9), // get_account_id digest!(0x386549d4435f79c1, 0x4a7add2e3b9f1b9e, 0x91c0af1138c14e77, 0xee8a5630e31bc74d), // get_account_item - digest!(0x29cfe0b5f97a3388, 0x3510774653258915, 0x6fe83ea3152b49ec, 0x7dc1830125de0b96), + digest!(0x83380522a33f8c7e, 0x1653bbd634d31107, 0x868fac07b1cb4005, 0x39bee294dac7fdc9), // get_account_map_item - digest!(0xfc989de557bf4cb8, 0x2e7443984efebb87, 0x698e04baf103ec41, 0x4c5c4cd14cfdd7a4), + digest!(0xdf739f276157cf90, 0x4c94a55654d426b, 0xff2528216462fa83, 0x45797577ddc9a224), // get_account_nonce digest!(0x64d14d80f9eff37a, 0x7587e273b2d8a416, 0x3c041064332c03d3, 0xc327341072f4f1e8), // get_account_vault_commitment @@ -30,23 +30,23 @@ pub const KERNEL0_PROCEDURES: [Digest; 32] = [ // get_initial_account_hash digest!(0xe239391d2c860c53, 0x7a9d09c3015d7417, 0x111e9be3640d3848, 0xf2d442cf1e685a89), // incr_account_nonce - digest!(0xb35351c9b87abeb5, 0x3f2607993a20eb41, 0xf50ef0e64bc386e, 0x265ad79a05151c58), + digest!(0x6d75402ead2fe81c, 0x6e66c9ec980ec9cd, 0xe82e007b0eda78f1, 0xea9de83af0fc2634), // set_account_code - digest!(0x6072f5e975697e09, 0x3384af10c011d5f4, 0x93d87a6c749002f2, 0x76b70654a4ac6025), + digest!(0x62110f0b57e49ee5, 0xd961174262cd614a, 0x3459572bcf110091, 0x319291c6c18ad0db), // set_account_item - digest!(0xd3402811a9171d13, 0xbaea0a2fe8b11ff6, 0xaeefcd9fc67b86af, 0xbaa253e9beb95c01), + digest!(0xc279aa203249464, 0x464f69a21be47e7a, 0xb9161aaee45f0ff5, 0xbca81ff227c9ca03), // set_account_map_item - digest!(0x3894ffa4dce29ab3, 0xe571cc3c85e40e6e, 0x709275d311d1dc86, 0xa2efbe0b3980e95c), + digest!(0x85c7e78d8e33f81, 0x2392bd80e65f27a7, 0x69d4d656a994dd2c, 0xcb9be97522be5cf4), // burn_asset - digest!(0x5d002cae26ebec39, 0x3f28bdfee3fc9000, 0xa143e738227e6be0, 0xddf6b123ae89e852), + digest!(0x3c71836eaa5fba1b, 0xee719bcada360cd1, 0xad55420b925fd10d, 0x4d32e15e121e5e3e), // get_fungible_faucet_total_issuance digest!(0xd9310aaf087d0dc4, 0xdc834fff6ea325d2, 0x2c9d90a33b9a6d8a, 0xa381c27e49c538a8), // mint_asset - digest!(0x3ba0deb8e089051c, 0x437139a5f81cb683, 0xa6951db7d21804b9, 0x41f71cfba44faa2b), + digest!(0x715eae96f4068cf1, 0x84ee32a7c64a85dd, 0x9b4d5a63fbd97064, 0xef0e81abf63aa2be), // add_asset_to_note - digest!(0xc016f1979f92c6a6, 0xd8b9abc6a769eca3, 0x766663e785a06a85, 0x7c7c16433193e65d), + digest!(0x9fbed6f52f2cc62d, 0xda9c2f699fac16fb, 0xeb6b8827beac6c95, 0xe27fc6900c673e2d), // create_note - digest!(0xfe30556c1b5cacc0, 0x625572ae47e0ed1c, 0x16fa16a25e468940, 0x4497a71681f81937), + digest!(0xa9e52dd343a6fa1d, 0xa54d666e10f34357, 0x7c53cc941096bd84, 0xe601314453890dfc), // get_input_notes_commitment digest!(0x1c078486abf976f5, 0xfce31a9f4b9687cd, 0xb1edb2edc115a619, 0xf1bb8c1bd9c7148b), // get_note_assets_info diff --git a/miden-tx/src/errors/tx_kernel_errors.rs b/miden-tx/src/errors/tx_kernel_errors.rs index 5e46aa7e9..87796d83f 100644 --- a/miden-tx/src/errors/tx_kernel_errors.rs +++ b/miden-tx/src/errors/tx_kernel_errors.rs @@ -87,8 +87,9 @@ pub const ERR_INVALID_NOTE_IDX: u32 = 131154; pub const ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS: u32 = 131155; pub const ERR_CURRENT_ACCOUNT_IS_NOT_NATIVE: u32 = 131156; pub const ERR_INVALID_TX_EXPIRATION_DELTA: u32 = 131157; +pub const ERR_INVALID_STORAGE_OFFSET_FOR_SIZE: u32 = 131158; -pub const KERNEL_ERRORS: [(u32, &str); 86] = [ +pub const KERNEL_ERRORS: [(u32, &str); 87] = [ (ERR_FAUCET_RESERVED_DATA_SLOT, "For faucets, storage slot 254 is reserved and can not be used with set_account_item procedure"), (ERR_ACCT_MUST_BE_A_FAUCET, "Procedure can only be called from faucet accounts"), (ERR_P2ID_WRONG_NUMBER_OF_INPUTS, "P2ID scripts expect exactly 1 note input"), @@ -173,6 +174,7 @@ pub const KERNEL_ERRORS: [(u32, &str); 86] = [ (ERR_STORAGE_SLOT_INDEX_OUT_OF_BOUNDS, "Provided storage slot index is out of bounds"), (ERR_INVALID_FAUCET_STORAGE_OFFSET, "Storage offset is invalid for a faucet account (0 is prohibited being the reserved faucet data slot)"), (ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS, "Provided kernel procedure offset is out of bounds"), + (ERR_INVALID_STORAGE_OFFSET_FOR_SIZE, "Storage offset is invalid for a procedure that does not access storage (should be 0)"), (ERR_CURRENT_ACCOUNT_IS_NOT_NATIVE, "Procedure can be called only for the native account"), - (ERR_INVALID_TX_EXPIRATION_DELTA, "Invalid transaction expiration block delta was set."), + (ERR_INVALID_TX_EXPIRATION_DELTA, "Invalid transaction expiration block delta was set.") ]; diff --git a/miden-tx/src/testing/tx_context/builder.rs b/miden-tx/src/testing/tx_context/builder.rs index 4ed5cf1f9..e38a48baa 100644 --- a/miden-tx/src/testing/tx_context/builder.rs +++ b/miden-tx/src/testing/tx_context/builder.rs @@ -52,7 +52,7 @@ pub struct TransactionContextBuilder { impl TransactionContextBuilder { pub fn new(account: Account) -> Self { Self { - assembler: TransactionKernel::testing_assembler(), + assembler: TransactionKernel::testing_assembler_with_mock_account(), account, account_seed: None, input_notes: Vec::new(), diff --git a/miden-tx/src/tests/kernel_tests/test_account.rs b/miden-tx/src/tests/kernel_tests/test_account.rs index b23b15d8f..433d2ac77 100644 --- a/miden-tx/src/tests/kernel_tests/test_account.rs +++ b/miden-tx/src/tests/kernel_tests/test_account.rs @@ -248,9 +248,16 @@ fn test_get_item() { #[test] fn test_get_map_item() { - let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); + let storage_slot = AccountStorage::mock_item_2().slot; + let (account, _) = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .add_storage_slot(storage_slot) + .code(AccountCode::mock_account_code(TransactionKernel::testing_assembler(), false)) + .nonce(ONE) + .build() + .unwrap(); + + let tx_context = TransactionContextBuilder::new(account).build(); - let storage_item = AccountStorage::mock_item_2(); for (key, value) in STORAGE_LEAVES_2 { let code = format!( " @@ -265,7 +272,7 @@ fn test_get_map_item() { call.::test::account::get_map_item end ", - item_index = storage_item.index, + item_index = 0, map_key = prepare_word(&key), ); let process = tx_context.execute_code(&code).unwrap(); @@ -378,7 +385,15 @@ fn test_set_map_item() { [Felt::new(9_u64), Felt::new(10_u64), Felt::new(11_u64), Felt::new(12_u64)], ); - let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); + let storage_slot = AccountStorage::mock_item_2().slot; + let (account, _) = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .add_storage_slot(storage_slot) + .code(AccountCode::mock_account_code(TransactionKernel::testing_assembler(), false)) + .nonce(ONE) + .build() + .unwrap(); + + let tx_context = TransactionContextBuilder::new(account).build(); let storage_item = AccountStorage::mock_item_2(); let code = format!( @@ -400,14 +415,14 @@ fn test_set_map_item() { call.account::get_item end ", - item_index = storage_item.index, + item_index = 0, new_key = prepare_word(&new_key), new_value = prepare_word(&new_value), ); let process = tx_context.execute_code(&code).unwrap(); - let mut new_storage_map = AccountStorage::mock_map_2(); + let mut new_storage_map = AccountStorage::mock_map(); new_storage_map.insert(new_key, new_value); assert_eq!( @@ -473,14 +488,14 @@ fn test_storage_offset() { // Setup account let code = AccountCode::compile(source_code, assembler.clone(), false).unwrap(); - // modify procedure offsets + // modify procedure storage offsets // TODO: We manually set the offsets here because we do not have the ability to set the // offsets through MASM for now. Remove this code when we enable this functionality. let procedures_with_offsets = vec![ - AccountProcedureInfo::new(*code.procedures()[0].mast_root(), 2), - AccountProcedureInfo::new(*code.procedures()[1].mast_root(), 2), - AccountProcedureInfo::new(*code.procedures()[2].mast_root(), 1), - AccountProcedureInfo::new(*code.procedures()[3].mast_root(), 1), + AccountProcedureInfo::new(*code.procedures()[0].mast_root(), 2, 1).unwrap(), + AccountProcedureInfo::new(*code.procedures()[1].mast_root(), 2, 1).unwrap(), + AccountProcedureInfo::new(*code.procedures()[2].mast_root(), 1, 1).unwrap(), + AccountProcedureInfo::new(*code.procedures()[3].mast_root(), 1, 1).unwrap(), ]; let code = AccountCode::from_parts(code.mast().clone(), procedures_with_offsets.clone()); diff --git a/miden-tx/src/tests/mod.rs b/miden-tx/src/tests/mod.rs index 98f829db1..fdd3d5239 100644 --- a/miden-tx/src/tests/mod.rs +++ b/miden-tx/src/tests/mod.rs @@ -8,14 +8,15 @@ use miden_objects::{ ACCOUNT_ID_NON_FUNGIBLE_FAUCET_ON_CHAIN, ACCOUNT_ID_REGULAR_ACCOUNT_IMMUTABLE_CODE_ON_CHAIN, }, - AccountCode, + AccountCode, AccountProcedureInfo, AccountStorage, }, - assets::{Asset, FungibleAsset, NonFungibleAsset}, + assets::{Asset, AssetVault, FungibleAsset, NonFungibleAsset}, notes::{ Note, NoteAssets, NoteExecutionHint, NoteExecutionMode, NoteHeader, NoteId, NoteInputs, NoteMetadata, NoteRecipient, NoteScript, NoteTag, NoteType, }, testing::{ + account::AccountBuilder, constants::{FUNGIBLE_ASSET_AMOUNT, NON_FUNGIBLE_ASSET_DATA}, notes::DEFAULT_NOTE_CODE, prepare_word, @@ -25,6 +26,8 @@ use miden_objects::{ Felt, Word, MIN_PROOF_SECURITY_LEVEL, }; use miden_prover::ProvingOptions; +use rand::SeedableRng; +use rand_chacha::ChaCha20Rng; use vm_processor::{ utils::{Deserializable, Serializable}, Digest, MemAdviceProvider, ONE, @@ -104,7 +107,33 @@ fn transaction_executor_witness() { #[test] fn executed_transaction_account_delta() { - let mut tx_context = TransactionContextBuilder::with_standard_account(ONE) + let account_code = + AccountCode::mock_account_code(TransactionKernel::testing_assembler(), false); + let account_assets = AssetVault::mock().assets().collect::>(); + + // modify procedure storage sizes + // TODO: We manually modify the sizes here to 3 because they are hardcoded as 1. + // Though we are accessing multiple storage slots up to 2 in this test. + // Remove this manual modification once we have the ability to set sizes using the assembler. + let procedures = account_code + .procedures() + .iter() + .map(|proc| AccountProcedureInfo::new(*proc.mast_root(), proc.storage_offset(), 3).unwrap()) + .collect(); + let account_code = AccountCode::from_parts(account_code.mast(), procedures); + let (account, _) = AccountBuilder::new(ChaCha20Rng::from_entropy()) + .add_storage_slots([ + AccountStorage::mock_item_0().slot, + AccountStorage::mock_item_1().slot, + AccountStorage::mock_item_2().slot, + ]) + .code(account_code) + .add_assets(account_assets) + .nonce(ONE) + .build() + .unwrap(); + + let mut tx_context = TransactionContextBuilder::new(account) .with_mock_notes_preserved_with_account_vault_delta() .build(); @@ -165,7 +194,7 @@ fn executed_transaction_account_delta() { send_asset_script.push_str(&format!( " ### note {i} - # prepare the stack for a new note creation + # prepare the stack for a new note creation push.0.1.2.3 # recipient push.{EXECUTION_HINT} # note_execution_hint push.{NOTETYPE} # note_type diff --git a/miden-tx/tests/integration/scripts/faucet.rs b/miden-tx/tests/integration/scripts/faucet.rs index bc13e3a9d..d831c804a 100644 --- a/miden-tx/tests/integration/scripts/faucet.rs +++ b/miden-tx/tests/integration/scripts/faucet.rs @@ -4,7 +4,7 @@ use miden_lib::transaction::{memory::FAUCET_STORAGE_DATA_SLOT, TransactionKernel use miden_objects::{ accounts::{ account_id::testing::ACCOUNT_ID_FUNGIBLE_FAUCET_OFF_CHAIN, Account, AccountCode, AccountId, - AccountStorage, StorageSlot, + AccountProcedureInfo, AccountStorage, StorageSlot, }, assets::{Asset, AssetVault, FungibleAsset}, notes::{NoteAssets, NoteExecutionHint, NoteId, NoteMetadata, NoteTag, NoteType}, @@ -32,6 +32,20 @@ fn prove_faucet_contract_mint_fungible_asset_succeeds() { let (faucet_pub_key, falcon_auth) = get_new_pk_and_authenticator(); let faucet_account = get_faucet_account_with_max_supply_and_total_issuance(faucet_pub_key, 200, None); + let procedures = faucet_account + .code() + .procedures() + .iter() + .map(|proc| AccountProcedureInfo::new(*proc.mast_root(), proc.storage_offset(), 2).unwrap()) + .collect(); + let account_code = AccountCode::from_parts(faucet_account.code().mast(), procedures); + let faucet_account = Account::from_parts( + faucet_account.id(), + faucet_account.vault().clone(), + faucet_account.storage().clone(), + account_code, + faucet_account.nonce(), + ); // CONSTRUCT AND EXECUTE TX (Success) // -------------------------------------------------------------------------------------------- diff --git a/objects/src/accounts/code/mod.rs b/objects/src/accounts/code/mod.rs index 72371ac3d..f275eccaa 100644 --- a/objects/src/accounts/code/mod.rs +++ b/objects/src/accounts/code/mod.rs @@ -19,17 +19,14 @@ use procedure::AccountProcedureInfo; /// Account's public interface consists of a set of account procedures, each procedure being a /// Miden VM program. Thus, MAST root of each procedure commits to the underlying program. /// -/// Each exported procedure is associated with a storage offset. This offset is applied to any -/// accesses made from within the procedure to the associated account's storage. For example, if -/// storage offset for a procedure is set ot 1, a call to the account::get_item(storage_slot=4) -/// made from this procedure would actually access storage slot with index 5. +/// Each exported procedure is associated with a storage offset and a storage size. /// /// We commit to the entire account interface by building a sequential hash of all procedure MAST /// roots and associated storage_offset's. Specifically, each procedure contributes exactly 8 field /// elements to the sequence of elements to be hashed. These elements are defined as follows: /// /// ```text -/// [PROCEDURE_MAST_ROOT, storage_offset, 0, 0, 0] +/// [PROCEDURE_MAST_ROOT, storage_offset, 0, 0, storage_size] /// ``` #[derive(Debug, Clone)] pub struct AccountCode { @@ -56,18 +53,24 @@ impl AccountCode { /// location 0. /// /// # Errors - /// - If the number of procedures exported from the provided library is smaller than 1 or - /// greater than 256. + /// - If the number of procedures exported from the provided library is 0. + /// - If the number of procedures exported from the provided library is greater than 256. + /// - If the creation of a new `AccountProcedureInfo` fails. pub fn new(library: Library, is_faucet: bool) -> Result { // extract procedure information from the library exports // TODO: currently, offsets for all regular account procedures are set to 0 - // and offsets for faucet accounts procedures are set to 1. Instead they should - // be read from the Library metadata. - let mut procedures: Vec = Vec::new(); + // and offsets for faucet accounts procedures are set to 1. Furthermore sizes + // are set to 1 for all accounts. Instead they should be read from the Library metadata. + let mut procedures = Vec::new(); let storage_offset = if is_faucet { 1 } else { 0 }; + let storage_size = 1; for module in library.module_infos() { for proc_mast_root in module.procedure_digests() { - procedures.push(AccountProcedureInfo::new(proc_mast_root, storage_offset)); + procedures.push(AccountProcedureInfo::new( + proc_mast_root, + storage_offset, + storage_size, + )?); } } @@ -192,7 +195,7 @@ impl AccountCode { /// /// This is done by first converting each procedure into 8 field elements as follows: /// ```text - /// [PROCEDURE_MAST_ROOT, storage_offset, 0, 0, 0] + /// [PROCEDURE_MAST_ROOT, storage_offset, 0, 0, storage_size] /// ``` /// And then concatenating the resulting elements into a single vector. pub fn as_elements(&self) -> Vec { diff --git a/objects/src/accounts/code/procedure.rs b/objects/src/accounts/code/procedure.rs index c6858db3e..ae4d4a4f5 100644 --- a/objects/src/accounts/code/procedure.rs +++ b/objects/src/accounts/code/procedure.rs @@ -1,3 +1,5 @@ +use alloc::string::ToString; + use vm_core::{ utils::{ByteReader, ByteWriter, Deserializable, Serializable}, FieldElement, @@ -5,41 +7,81 @@ use vm_core::{ use vm_processor::DeserializationError; use super::{Digest, Felt}; -use crate::AccountError; +use crate::{accounts::AccountStorage, AccountError}; // ACCOUNT PROCEDURE INFO // ================================================================================================ /// Information about a procedure exposed in a public account interface. /// -/// The info included the MAST root of the procedure and the storage offset applied to all account -/// storage-related accesses made by this procedure. For example, if storage offset is set ot 1, a -/// call to the account::get_item(storage_slot=4) made from this procedure would actually access +/// The info included the MAST root of the procedure, the storage offset applied to all account +/// storage-related accesses made by this procedure and the storage size allowed to be accessed +/// by this procedure. +/// +/// The offset is applied to any accesses made from within the procedure to the associated +/// account's storage. For example, if storage offset for a procedure is set ot 1, a call +/// to the account::get_item(storage_slot=4) made from this procedure would actually access /// storage slot with index 5. +/// +/// The size is used to limit how many storage slots a given procedure can access in the associated +/// account's storage. For example, if storage size for a procedure is set to 3, the procedure will +/// be bounded to access storage slots in the range [storage_offset, storage_offset + 3 - 1]. +/// Furthermore storage_size = 0 indicates that a procedure does not need to access storage. #[derive(Debug, PartialEq, Eq, Clone)] pub struct AccountProcedureInfo { mast_root: Digest, - storage_offset: u16, + storage_offset: u8, + storage_size: u8, } impl AccountProcedureInfo { /// The number of field elements needed to represent an [AccountProcedureInfo] in kernel memory. pub const NUM_ELEMENTS_PER_PROC: usize = 8; + // CONSTRUCTOR + // -------------------------------------------------------------------------------------------- + /// Returns a new instance of an [AccountProcedureInfo]. - pub fn new(mast_root: Digest, storage_offset: u16) -> Self { - Self { mast_root, storage_offset } + /// + /// # Errors + /// - If `storage_size` is 0 and `storage_offset` is not 0. + /// - If `storage_size + storage_offset` is greater than `MAX_NUM_STORAGE_SLOTS`. + pub fn new( + mast_root: Digest, + storage_offset: u8, + storage_size: u8, + ) -> Result { + if storage_size == 0 && storage_offset != 0 { + return Err(AccountError::PureProcedureWithStorageOffset); + } + + if (storage_offset + storage_size) as usize > AccountStorage::MAX_NUM_STORAGE_SLOTS { + return Err(AccountError::StorageOffsetOutOfBounds { + max: AccountStorage::MAX_NUM_STORAGE_SLOTS as u8, + actual: storage_offset + storage_size, + }); + } + + Ok(Self { mast_root, storage_offset, storage_size }) } - /// Returns a reference to the procedure's mast_root. + // PUBLIC ACCESSORS + // -------------------------------------------------------------------------------------------- + + /// Returns a reference to the procedure's mast root. pub fn mast_root(&self) -> &Digest { &self.mast_root } - /// Returns a reference to the procedure's storage_offset. - pub fn storage_offset(&self) -> u16 { + /// Returns the procedure's storage offset. + pub fn storage_offset(&self) -> u8 { self.storage_offset } + + /// Returns the procedure's storage size. + pub fn storage_size(&self) -> u8 { + self.storage_size + } } impl From for [Felt; 8] { @@ -50,7 +92,10 @@ impl From for [Felt; 8] { result[0..4].copy_from_slice(value.mast_root().as_elements()); // copy the storage offset into value[4] - result[4] = Felt::from(value.storage_offset()); + result[4] = Felt::from(value.storage_offset); + + // copy the storage size into value[7] + result[5] = Felt::from(value.storage_size); result } @@ -64,36 +109,45 @@ impl TryFrom<[Felt; 8]> for AccountProcedureInfo { let mast_root = Digest::from(<[Felt; 4]>::try_from(&value[0..4]).unwrap()); // get storage_offset form value[4] - let storage_offset: u16 = value[4] + let storage_offset: u8 = value[4] .try_into() .map_err(|_| AccountError::AccountCodeProcedureInvalidStorageOffset)?; - // Check if the last three elements are zero - if value[5..].iter().any(|&x| x != Felt::ZERO) { + // get storage_size form value[5] + let storage_size: u8 = value[5] + .try_into() + .map_err(|_| AccountError::AccountCodeProcedureInvalidStorageSize)?; + + // Check if the remaining values are 0 + if value[6] != Felt::ZERO || value[7] != Felt::ZERO { return Err(AccountError::AccountCodeProcedureInvalidPadding); } - Ok(Self { mast_root, storage_offset }) + Ok(Self { mast_root, storage_offset, storage_size }) } } impl Serializable for AccountProcedureInfo { fn write_into(&self, target: &mut W) { - target.write(self.mast_root()); - target.write_u16(self.storage_offset()); + target.write(self.mast_root); + target.write_u8(self.storage_offset); + target.write_u8(self.storage_size) } fn get_size_hint(&self) -> usize { - self.mast_root.get_size_hint() + self.storage_offset.get_size_hint() + self.mast_root.get_size_hint() + + self.storage_offset.get_size_hint() + + self.storage_size.get_size_hint() } } impl Deserializable for AccountProcedureInfo { fn read_from(source: &mut R) -> Result { let mast_root: Digest = source.read()?; - let storage_offset = source.read_u16()?; - - Ok(Self::new(mast_root, storage_offset)) + let storage_offset = source.read_u8()?; + let storage_size = source.read_u8()?; + Self::new(mast_root, storage_offset, storage_size) + .map_err(|err| DeserializationError::InvalidValue(err.to_string())) } } @@ -102,10 +156,27 @@ impl Deserializable for AccountProcedureInfo { #[cfg(test)] mod tests { + use miden_crypto::utils::{Deserializable, Serializable}; + use vm_core::Felt; use crate::accounts::{AccountCode, AccountProcedureInfo}; + #[test] + fn test_from_to_account_procedure() { + let account_code = AccountCode::mock(); + + let procedure = account_code.procedures()[0].clone(); + + // from procedure to [Felt; 8] + let felts: [Felt; 8] = procedure.clone().into(); + + // try_from [Felt; 8] to procedure + let final_procedure: AccountProcedureInfo = felts.try_into().unwrap(); + + assert_eq!(procedure, final_procedure); + } + #[test] fn test_serde_account_procedure() { let account_code = AccountCode::mock(); @@ -113,6 +184,6 @@ mod tests { let serialized = account_code.procedures()[0].to_bytes(); let deserialized = AccountProcedureInfo::read_from_bytes(&serialized).unwrap(); - assert_eq!(deserialized, account_code.procedures()[0]); + assert_eq!(account_code.procedures()[0], deserialized); } } diff --git a/objects/src/errors.rs b/objects/src/errors.rs index a3a5ee21a..2338c31a0 100644 --- a/objects/src/errors.rs +++ b/objects/src/errors.rs @@ -27,6 +27,7 @@ pub enum AccountError { AccountCodeNoProcedures, AccountCodeTooManyProcedures { max: usize, actual: usize }, AccountCodeProcedureInvalidStorageOffset, + AccountCodeProcedureInvalidStorageSize, AccountCodeProcedureInvalidPadding, AccountIdInvalidFieldElement(String), AccountIdTooFewOnes(u32, u32), @@ -44,6 +45,8 @@ pub enum AccountError { StorageSlotNotValue(u8), StorageIndexOutOfBounds { max: u8, actual: u8 }, StorageTooManySlots(u64), + StorageOffsetOutOfBounds { max: u8, actual: u8 }, + PureProcedureWithStorageOffset, } impl fmt::Display for AccountError { diff --git a/objects/src/testing/storage.rs b/objects/src/testing/storage.rs index 8a4365821..f61bc69e0 100644 --- a/objects/src/testing/storage.rs +++ b/objects/src/testing/storage.rs @@ -157,12 +157,12 @@ impl AccountStorage { pub fn mock_item_2() -> SlotWithIndex { SlotWithIndex { - slot: StorageSlot::Map(Self::mock_map_2()), + slot: StorageSlot::Map(Self::mock_map()), index: STORAGE_INDEX_2, } } - pub fn mock_map_2() -> StorageMap { + pub fn mock_map() -> StorageMap { StorageMap::with_entries(STORAGE_LEAVES_2).unwrap() } }