-
Notifications
You must be signed in to change notification settings - Fork 106
feat: Single-word Array abstraction
#2203
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
mmagician
wants to merge
14
commits into
next
Choose a base branch
from
mmagician-array-via-map
base: next
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
6f03672
feat: working array
mmagician e136d3c
chore: add changelog entry for Array component (#2204)
Copilot c1c788d
fix: set_map_item no longer returns old root
mmagician 05b5234
chore: change Try to TryFrom for AccountComponent
mmagician aa50de3
chore: correct the docs
mmagician d04e396
chore: masm doc corrections
mmagician 17d96bf
chore: use BeWord instead of Word
mmagician 2d34da0
chore: adjust masm comment about max len
mmagician cff5982
Update crates/miden-standards/asm/account_components/array.masm
mmagician 6bd6069
chore: turn array into utility
mmagician 3ff3c49
chore: lint, simplify test & comments
mmagician 0c37ba3
chore: remove unnecessary comments
mmagician ab2ca4d
chore: remove duplicated changelog entry
mmagician 8a1fa90
Apply suggestions from code review
mmagician File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
crates/miden-standards/asm/standards/data_structures/array.masm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # The MASM code for the Array abstraction. | ||
| # | ||
| # It provides an abstraction layer over a storage map, treating it as an array, | ||
| # with "set" and "get" for storing and retrieving words by (slot_id, index). | ||
| # The array can store up to 2^64 - 2^32 + 1 elements (indices 0 to 2^64 - 2^32). | ||
| # | ||
| # Using this Array utility requires that the underlying storage map is already created and | ||
| # initialized as part of an account component, under the given slot ID. | ||
|
|
||
| use miden::protocol::active_account | ||
| use miden::protocol::native_account | ||
|
|
||
| type BeWord = struct @bigendian { a: felt, b: felt, c: felt, d: felt } | ||
|
|
||
| # PROCEDURES | ||
| # ================================================================================================= | ||
|
|
||
| #! Sets a word in the array at the specified index. | ||
| #! | ||
| #! Inputs: [slot_id_prefix, slot_id_suffix, index, VALUE, pad(9)] | ||
| #! Outputs: [OLD_VALUE, pad(12)] | ||
| #! | ||
| #! Where: | ||
| #! - slot_id_{prefix, suffix} are the prefix and suffix felts of the slot identifier. | ||
| #! - index is the index at which to store the value (0 to 2^64 - 2^32). | ||
| #! - VALUE is the word to store at the specified index. | ||
| #! | ||
| #! Invocation: call | ||
| pub proc set(slot_id_prefix: felt, slot_id_suffix: felt, index: felt, value: BeWord) -> BeWord | ||
| # Build KEY = [index, 0, 0, 0] | ||
| push.0.0.0 movup.5 | ||
| # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, VALUE, pad(9)] | ||
|
|
||
| # truncate the stack | ||
| movup.10 drop | ||
| movup.10 drop | ||
| movup.10 drop | ||
| # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, VALUE, pad(6)] | ||
|
|
||
| movup.5 movup.5 | ||
| # => [slot_id_prefix, slot_id_suffix, KEY, VALUE, pad(6)] | ||
|
|
||
| exec.native_account::set_map_item | ||
| # => [OLD_VALUE, pad(12)] | ||
| end | ||
|
|
||
| #! Gets a word from the array at the specified index. | ||
| #! | ||
| #! Inputs: [slot_id_prefix, slot_id_suffix, index, pad(13)] | ||
| #! Outputs: [VALUE, pad(12)] | ||
| #! | ||
| #! Where: | ||
| #! - slot_id_{prefix, suffix} are the prefix and suffix felts of the slot identifier. | ||
| #! - index is the index of the element to retrieve (0 to 2^64 - 2^32). | ||
| #! - VALUE is the word stored at the specified index (zero if not set). | ||
| #! | ||
| #! Invocation: call | ||
| pub proc get(slot_id_prefix: felt, slot_id_suffix: felt, index: felt) -> BeWord | ||
| # Build KEY = [index, 0, 0, 0] | ||
| push.0.0.0 movup.5 | ||
| # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, pad(13)] | ||
|
|
||
| # truncate the stack | ||
| movup.6 drop | ||
| movup.6 drop | ||
| movup.6 drop | ||
| # => [index, 0, 0, 0, slot_id_prefix, slot_id_suffix, pad(10)] | ||
|
|
||
| movup.5 movup.5 | ||
| # => [slot_id_prefix, slot_id_suffix, KEY, pad(10)] | ||
|
|
||
| exec.active_account::get_map_item | ||
| # => [VALUE, pad(15)] | ||
|
|
||
| # truncate the stack | ||
| repeat.3 movup.4 drop end | ||
| # => [VALUE, pad(12)] | ||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| //! Tests for the Array utility `get` and `set` procedures. | ||
|
|
||
| use miden_protocol::account::{ | ||
| AccountBuilder, | ||
| AccountComponent, | ||
| StorageMap, | ||
| StorageSlot, | ||
| StorageSlotName, | ||
| }; | ||
| use miden_protocol::{Felt, FieldElement, Word}; | ||
| use miden_standards::code_builder::CodeBuilder; | ||
| use rand::{Rng, SeedableRng}; | ||
| use rand_chacha::ChaCha20Rng; | ||
|
|
||
| use crate::{Auth, TransactionContextBuilder}; | ||
|
|
||
| /// The slot name used for testing the array component. | ||
| const TEST_ARRAY_SLOT: &str = "test::array::data"; | ||
|
|
||
| /// Verify that, given an account component with a storage map to hold the array data, | ||
| /// we can use the array utility to: | ||
| /// 1. Retrieve the initial value via `get` | ||
| /// 2. Update the value via `set` | ||
| /// 3. Retrieve the updated value via `get` | ||
| #[tokio::test] | ||
| async fn test_array_get_and_set() -> anyhow::Result<()> { | ||
| let slot_name = StorageSlotName::new(TEST_ARRAY_SLOT).expect("slot name should be valid"); | ||
|
|
||
| let wrapper_component_code = format!( | ||
| r#" | ||
| use miden::core::word | ||
| use miden::standards::data_structures::array | ||
|
|
||
| const ARRAY_SLOT_NAME = word("{slot_name}") | ||
|
|
||
| #! Wrapper for array::get that uses exec internally. | ||
| #! Inputs: [index, pad(15)] | ||
| #! Outputs: [VALUE, pad(12)] | ||
| pub proc test_get | ||
| push.ARRAY_SLOT_NAME[0..2] | ||
| exec.array::get | ||
| end | ||
|
|
||
| #! Wrapper for array::set that uses exec internally. | ||
| #! Inputs: [index, VALUE, pad(11)] | ||
| #! Outputs: [OLD_VALUE, pad(12)] | ||
| pub proc test_set | ||
| push.ARRAY_SLOT_NAME[0..2] | ||
| exec.array::set | ||
| end | ||
| "#, | ||
| ); | ||
|
|
||
| // Build the wrapper component by linking against the array library | ||
| let wrapper_library = CodeBuilder::default() | ||
| .compile_component_code("wrapper::component", wrapper_component_code)?; | ||
|
|
||
| // Create the wrapper account component with a storage map to hold the array data | ||
| let initial_value = Word::from([42u32, 42, 42, 42]); | ||
| let wrapper_component = AccountComponent::new( | ||
| wrapper_library.clone(), | ||
| vec![StorageSlot::with_map( | ||
| slot_name.clone(), | ||
| StorageMap::with_entries([( | ||
| Word::from([Felt::ZERO, Felt::ZERO, Felt::ZERO, Felt::ZERO]), | ||
| initial_value, | ||
| )])?, | ||
| )], | ||
| )? | ||
| .with_supports_all_types(); | ||
|
|
||
| // Build an account with the wrapper component that uses the array utility | ||
| let account = AccountBuilder::new(ChaCha20Rng::from_os_rng().random()) | ||
| .with_auth_component(Auth::IncrNonce) | ||
| .with_component(wrapper_component) | ||
| .build_existing()?; | ||
|
|
||
| // Verify the storage slot exists | ||
| assert!( | ||
| account.storage().get(&slot_name).is_some(), | ||
| "Array data slot should exist in account storage" | ||
| ); | ||
|
|
||
| // Transaction script that: | ||
| // 1. Gets the initial value at index 0 (should be [42, 42, 42, 42]) | ||
| // 2. Sets index 0 to [43, 43, 43, 43] | ||
| // 3. Gets the updated value at index 0 (should be [43, 43, 43, 43]) | ||
| let tx_script_code = r#" | ||
| use wrapper::component->wrapper | ||
|
|
||
| begin | ||
| # Step 1: Get value at index 0 (should return [42, 42, 42, 42]) | ||
| push.0 | ||
| # => [index, pad(16)] | ||
mmagician marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| call.wrapper::test_get | ||
| # => [VALUE, pad(13)] | ||
mmagician marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Verify value is [42, 42, 42, 42] | ||
| push.42.42.42.42 | ||
| assert_eqw.err="get(0) should return [42, 42, 42, 42] initially" | ||
| # => [pad(16)] (auto-padding) | ||
|
|
||
| # Step 2: Set value at index 0 to [43, 43, 43, 43] | ||
| push.43.43.43.43 | ||
| push.0 | ||
| # => [index, VALUE, pad(16)] | ||
mmagician marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| call.wrapper::test_set | ||
| # => [OLD_VALUE, pad(17)] | ||
mmagician marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| dropw | ||
|
|
||
| # Step 3: Get value at index 0 (should return [43, 43, 43, 43]) | ||
| push.0 | ||
| # => [index, pad(17)] | ||
mmagician marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| call.wrapper::test_get | ||
| # => [VALUE, pad(14)] | ||
mmagician marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| # Verify value is [43, 43, 43, 43] | ||
| push.43.43.43.43 | ||
| assert_eqw.err="get(0) should return [43, 43, 43, 43] after set" | ||
| # => [pad(16)] (auto-padding) | ||
| end | ||
| "#; | ||
|
|
||
| // Compile the transaction script with the wrapper library linked | ||
| let tx_script = CodeBuilder::default() | ||
| .with_dynamically_linked_library(&wrapper_library)? | ||
| .compile_tx_script(tx_script_code)?; | ||
|
|
||
| // Create transaction context and execute | ||
| let tx_context = TransactionContextBuilder::new(account).tx_script(tx_script).build()?; | ||
|
|
||
| tx_context.execute().await?; | ||
|
|
||
| Ok(()) | ||
| } | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.