From 22f2f237b93ec19555444b1f94253564cd23b35a Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Thu, 11 May 2023 11:41:36 +0200 Subject: [PATCH] feat: memory (#34) * feat: MemoryTrait + store() * feat: memory store_n * feat: memory load / load_n * feat: memory expand/ensure_length * docs: memory docs * fix: cairo_project script kakarot path * refactor: u128-based memory rather instead of felt252 * tests: helper functions test * chore: address PR review 1 * refactor: optimize pow256_rev * chore: address PR review 2 - optimize mask, rename testfiles * fix: store_u256 bug, address pr * chore: clean remaining todos * chore: update CI --- .github/workflows/test.yml | 2 +- .gitignore | 6 +- Makefile | 15 +- README.md | 11 +- Scarb.toml | 3 - cairo_project.toml | 2 - scripts/generate_cairo_project.sh | 25 + src/lib.cairo | 3 - src/memory.cairo | 462 ++++++++++++++++++ src/stack.cairo | 12 +- src/tests.cairo | 2 - src/utils.cairo | 10 +- src/utils/constants.cairo | 1 + src/utils/helpers.cairo | 259 ++++++++++ tests/lib.cairo | 4 + .../test_kakarot.cairo | 0 tests/test_memory.cairo | 261 ++++++++++ .../test_stack.cairo | 0 tests/utils.cairo | 1 + tests/utils/test_helpers.cairo | 157 ++++++ 20 files changed, 1209 insertions(+), 27 deletions(-) delete mode 100644 cairo_project.toml create mode 100644 scripts/generate_cairo_project.sh delete mode 100644 src/tests.cairo create mode 100644 src/utils/constants.cairo create mode 100644 src/utils/helpers.cairo create mode 100644 tests/lib.cairo rename src/tests/kakarot_test.cairo => tests/test_kakarot.cairo (100%) create mode 100644 tests/test_memory.cairo rename src/tests/stack_test.cairo => tests/test_stack.cairo (100%) create mode 100644 tests/utils.cairo create mode 100644 tests/utils/test_helpers.cairo diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f10b0e6c..10d26b76d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ on: [push, pull_request] env: CAIRO_COMPILER_VERSION: 1.0.0-rc0 # Temporary use artifacts from custom CI build - ARTIFACT_RELEASE_ARCHIVE_LINK: https://api.github.com/repos/starkware-libs/cairo/actions/artifacts/679726320/zip + ARTIFACT_RELEASE_ARCHIVE_LINK: https://api.github.com/repos/starkware-libs/cairo/actions/artifacts/689746858/zip ARTIFACT_RELEASE_ARCHIVE_NAME: cairo.zip RELEASE_ARCHIVE_NAME: x86_64-unknown-linux-musl.tar.gz diff --git a/.gitignore b/.gitignore index b22484644..8b3e650d3 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,8 @@ target/ # Environment files .env -build \ No newline at end of file +build + +# Cairo project config file. +# Auto-generated by scripts/generate_cairo_project.sh +cairo_project.toml \ No newline at end of file diff --git a/Makefile b/Makefile index 5b445d403..5770ed2f9 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,16 @@ all: build run test # Targets +# There is no integration between Scarb and the default `cairo-test` runner. +# Therefore, we need to generate the cairo_project.toml file, required +# by the `cairo-test` runner, manually. This is done by the generate_cairo_project script. +cairo-project: + @echo "Generating cairo project..." + sh scripts/generate_cairo_project.sh +# Test the project + # Compile the project -build: FORCE +build: cairo-project FORCE $(MAKE) clean format @echo "Building..." cairo-compile . > $(BUILD_DIR)/$(PROJECT_NAME).sierra @@ -25,8 +33,9 @@ run: @echo "Running..." #cairo-run -p $(ENTRYPOINT) -# Test the project -test: + + +test: cairo-project @echo "Testing..." cairo-test $(TEST_ENTRYPOINT) diff --git a/README.md b/README.md index eda7615ba..d8bd97618 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,8 @@ It is a work in progress, and it is not ready for production. - [Cairo](https://github.com/starkware-libs/cairo) - [Rust](https://www.rust-lang.org/tools/install) +- [Scarb](https://docs.swmansion.com/scarb/download) +- [jq](https://stedolan.github.io/jq/download/) ### Installation @@ -66,7 +68,6 @@ It is a work in progress, and it is not ready for production. ## Usage - ### Build ```bash @@ -81,6 +82,11 @@ make run ### Test +We use the `cairo-test` runner to run the unit tests on Kakarot. +However, it is not directly compatible with Scarb. Hence, we use a script +to generate the `cairo_project.toml` file from Scarb's metadata. +Running this script requires `jq` to be installed. + ```bash make test ``` @@ -141,7 +147,6 @@ See [LICENSE](LICENSE) for more information. ## Acknowledgements - ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): @@ -176,4 +181,4 @@ This project follows the [all-contributors](https://github.com/all-contributors/

-

\ No newline at end of file +

diff --git a/Scarb.toml b/Scarb.toml index 377a4d28c..929dcec57 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -1,6 +1,3 @@ [package] name = "kakarot" version = "0.1.0" - -[dependencies] -quaireaux = { git = "https://github.com/keep-starknet-strange/quaireaux.git" } diff --git a/cairo_project.toml b/cairo_project.toml deleted file mode 100644 index df4a0bd9b..000000000 --- a/cairo_project.toml +++ /dev/null @@ -1,2 +0,0 @@ -[crate_roots] -kakarot = "src" diff --git a/scripts/generate_cairo_project.sh b/scripts/generate_cairo_project.sh new file mode 100644 index 000000000..fa137976b --- /dev/null +++ b/scripts/generate_cairo_project.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +# This script is used to generate the `cairo_project.toml` file +# from the Scarb project's metadata. +# It is required to run the `cairo-test` runner. + +# Run the scarb metadata command and store the JSON output in a variable +json_output=$(scarb metadata --format-version 1| sed -n '/^{/,$p') + +# Create a temporary file to store the JSON output +temp_file=$(mktemp) +echo "$json_output" > "$temp_file" + +# Initialize cairo_project.toml file +echo "[crate_roots]" > cairo_project.toml + +# Process the JSON output and create the cairo_project.toml file using jq +jq -r '.packages[] | select(.name != "core" and .name != "kakarot") | .name + " = \"" + .root + "/src\""' "$temp_file" >> cairo_project.toml + +# Add kakarot and tests to the cairo_project.toml +echo 'kakarot = "src"' >> cairo_project.toml +echo 'tests = "tests"' >> cairo_project.toml + +# Remove the temporary file +rm "$temp_file" \ No newline at end of file diff --git a/src/lib.cairo b/src/lib.cairo index 34870d6b6..a1aaad5eb 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -15,6 +15,3 @@ mod context; // Utils module mod utils; - -// Test modules -mod tests; diff --git a/src/memory.cairo b/src/memory.cairo index 8b1378917..dcbc20a24 100644 --- a/src/memory.cairo +++ b/src/memory.cairo @@ -1 +1,463 @@ +use traits::Index; +use array::SpanTrait; +use array::ArrayTrait; +use clone::Clone; +use dict::Felt252Dict; +use dict::Felt252DictTrait; +use integer::{ + u32_safe_divmod, u32_as_non_zero, u128_safe_divmod, u128_as_non_zero, u256_safe_divmod, + u256_as_non_zero +}; +use traits::{TryInto, Into}; +use kakarot::{utils, utils::helpers}; +use helpers::{SpanExtensionTrait}; +use option::OptionTrait; +use debug::PrintTrait; + + +#[derive(Destruct)] +struct Memory { + items: Felt252Dict, + bytes_len: usize, +} + +trait Felt252DictExtension { + fn store_u256(ref self: Felt252Dict, element: u256, index: usize); + fn read_u256(ref self: Felt252Dict, index: usize) -> u256; +} + +impl Felt252DictExtensionImpl of Felt252DictExtension { + /// Stores a u256 element into the dictionary. + /// The element will be stored as two distinct u128 elements, + /// thus taking two indexes. + /// + /// # Arguments + /// * `self` - A mutable reference to the `Felt252Dict` instance. + /// * `element` - The element to store, of type `u256`. + /// * `index` - The `usize` index at which to store the element. + fn store_u256(ref self: Felt252Dict, element: u256, index: usize) { + let index: felt252 = index.into(); + self.insert(index, element.high.into()); + self.insert(index + 1, element.low.into()); + } + + /// Reads a u256 element from the dictionary. + /// The element is stored as two distinct u128 elements, + /// thus we have to read the low and high parts and combine them. + /// The memory is big-endian organized, so the high part is stored first. + /// + /// # Arguments + /// * `self` - A mutable reference to the `Felt252Dict` instance. + /// * `index` - The `usize` index at which the element is stored. + /// + /// # Returns + /// * The element read, of type `u256`. + fn read_u256(ref self: Felt252Dict, index: usize) -> u256 { + let index: felt252 = index.into(); + let high: u128 = self.get(index); + let low: u128 = self.get(index + 1); + u256 { low: low, high: high } + } +} + +/// A custom print trait for the `Memory` struct. +trait MemoryPrintTrait { + fn print_segment(ref self: Memory, begin: usize, end: usize); +} + +impl MemoryPrintImpl of MemoryPrintTrait { + /// Prints the memory content between offset begin and end + fn print_segment(ref self: Memory, mut begin: usize, end: usize) { + '____MEMORY_BEGIN___'.print(); + loop { + if begin >= end { + break (); + } + self.items.get(begin.into()).print(); + begin += 1; + }; + '____MEMORY_END___'.print(); + } +} + +trait MemoryTrait { + fn new() -> Memory; + fn store(ref self: Memory, element: u256, offset: usize); + fn store_n(ref self: Memory, elements: Span, offset: usize); + fn store_aligned_words( + ref self: Memory, chunk_index: usize, chunk_index_f: usize, elements: Span + ); + fn _load(ref self: Memory, offset: usize) -> u256; + fn _load_n(ref self: Memory, elements_len: usize, ref elements: Array, offset: usize); + fn load_aligned_words( + ref self: Memory, chunk_index: usize, chunk_index_f: usize, ref elements: Array + ); + fn expand(ref self: Memory, length: usize) -> usize; + fn ensure_length(ref self: Memory, length: usize) -> usize; + fn load(ref self: Memory, offset: usize) -> (u256, usize); + fn load_n( + ref self: Memory, elements_len: usize, ref elements: Array, offset: usize + ) -> usize; +} + +impl MemoryImpl of MemoryTrait { + /// Initializes a new `Memory` instance. + /// + /// # Returns + /// + /// * A new `Memory` instance. + fn new() -> Memory { + Memory { items: Felt252DictTrait::new(), bytes_len: 0, } + } + + /// Stores a 32-bytes element into the memory. + /// # Arguments + /// + /// * `self` - A mutable reference to the `Memory` instance. + /// * `element` - The element to store, of type `u256`. + /// * `offset` - The `usize` offset at which to store the element. + fn store(ref self: Memory, element: u256, offset: usize) { + let new_min_bytes_len = helpers::ceil_bytes_len_to_next_32_bytes_word(offset + 32); + + let new_bytes_len = if new_min_bytes_len > self.bytes_len { + new_min_bytes_len + } else { + self.bytes_len + }; + self.bytes_len = new_bytes_len; + + // Check alignment of offset to bytes16 chunks + let (chunk_index, offset_in_chunk) = u32_safe_divmod(offset, u32_as_non_zero(16)); + + if offset_in_chunk == 0 { + // Offset is aligned. This is the simplest and most efficient case, + // so we optimize for it. + self.items.store_u256(element, chunk_index); + return (); + } + + // Offset is misaligned. + // | W0 | W1 | w2 | + // | EL_H | EL_L | + // ^---^ + // |-- mask = 256 ** offset_in_chunk + + let mask: u256 = helpers::pow256_rev(offset_in_chunk); + let mask_c: u256 = utils::pow(256, 16).into() / mask; + + // Split the 2 input bytes16 chunks at offset_in_chunk. + + let (el_hh, el_hl) = u256_safe_divmod( + u256 { low: element.high, high: 0 }, u256_as_non_zero(mask_c) + ); + + let (el_lh, el_ll) = u256_safe_divmod( + u256 { low: element.low, high: 0 }, u256_as_non_zero(mask_c) + ); + + // Read the words at chunk_index, chunk_index + 2. + let w0: u128 = self.items.get(chunk_index.into()); + let w2: u128 = self.items.get(chunk_index.into() + 2); + + // Compute the new words + let w0_h: u256 = (w0.into() / mask); + let w2_l: u256 = (w2.into() / mask); + + // We can convert them back to felt252 as we know they fit in one word. + let new_w0: u128 = (w0_h.into() * mask + el_hh).try_into().unwrap(); + let new_w1: u128 = (el_hl.into() * mask + el_lh).try_into().unwrap(); + let new_w2: u128 = (el_ll.into() * mask + w2_l).try_into().unwrap(); + + // Write the new words + self.items.insert(chunk_index.into(), new_w0); + self.items.insert(chunk_index.into() + 1, new_w1); + self.items.insert(chunk_index.into() + 2, new_w2); + } + + /// Stores N bytes into the memory. + /// + /// # Arguments + /// * `self` - A mutable reference to the `Memory` instance. + /// * `elements` - The bytes to store as a Span. + /// * `offset` - The `usize` offset at which to store the element. + fn store_n(ref self: Memory, elements: Span, offset: usize) { + if elements.len() == 0 { + return (); + } + + // Compute new bytes_len. + let new_min_bytes_len = helpers::ceil_bytes_len_to_next_32_bytes_word( + offset + elements.len() + ); + let new_bytes_len = if new_min_bytes_len > self.bytes_len { + new_min_bytes_len + } else { + self.bytes_len + }; + self.bytes_len = new_bytes_len; + + // Check alignment of offset to bytes16 chunks. + let (chunk_index_i, offset_in_chunk_i) = u32_safe_divmod(offset, u32_as_non_zero(16)); + let (chunk_index_f, offset_in_chunk_f) = u32_safe_divmod( + offset + elements.len() - 1, u32_as_non_zero(16) + ); + let offset_in_chunk_f = offset_in_chunk_f + 1; + let mask_i: u256 = helpers::pow256_rev(offset_in_chunk_i); + let mask_f: u256 = helpers::pow256_rev(offset_in_chunk_f); + + // Special case: within the same word. + if chunk_index_i == chunk_index_f { + let w: u128 = self.items.get(offset_in_chunk_i.into()); + let (w_h, w_l) = u256_safe_divmod(u256 { low: w, high: 0 }, u256_as_non_zero(mask_i)); + let (_, w_ll) = u256_safe_divmod(w_l, u256_as_non_zero(mask_f)); + let x = helpers::load_word(elements.len(), elements); + let new_w: u128 = (w_h * mask_i + x.into() * mask_f + w_ll).try_into().unwrap(); + self.items.insert(chunk_index_i.into(), new_w); + return (); + } + + // Otherwise, fill first word. + let w_i = self.items.get(chunk_index_i.into()); + let w_i_h = (w_i.into() / mask_i); + let x_i = helpers::load_word(16 - offset_in_chunk_i, elements); + let w1: u128 = (w_i_h * mask_i + x_i.into()).try_into().unwrap(); + self.items.insert(chunk_index_i.into(), w1); + + // Write blocks + let mut elements_clone = elements.clone(); + elements_clone.pop_front_n(elements.len() + 15 - offset_in_chunk_i); + self.store_aligned_words(chunk_index_i + 1, chunk_index_f, elements_clone); + + // Fill last word + let w_f = self.items.get(chunk_index_f.into()); + let w_f_l = (w_f.into() % mask_f); + let mut elements_clone = elements.clone(); + elements_clone.pop_front_n(elements.len() - offset_in_chunk_f); + let x_f = helpers::load_word(offset_in_chunk_f, elements_clone); + let w2: u128 = (x_f.into() * mask_f + w_f_l).try_into().unwrap(); + self.items.insert(chunk_index_f.into(), w2); + } + + /// Stores a sequence of bytes into memory in chunks of 16 bytes each. + /// The function combines each byte together into a single 16 bytes value, + /// big-endian order, and stores this value in memory. + /// + /// # Arguments + /// * `self` - A mutable reference to the `Memory` instance. + /// * `chunk_index` - The index of the chunk to start storing at. + /// * `chunk_index_f` - The index of the chunk to stop storing at. + /// * `elements` - The bytes to store as a Span. + fn store_aligned_words( + ref self: Memory, mut chunk_index: usize, chunk_index_f: usize, mut elements: Span + ) { + loop { + if chunk_index == chunk_index_f { + break (); + } + + let current: felt252 = ((*elements[0]).into() * utils::pow(256, 15) + + (*elements[1]).into() * utils::pow(256, 14) + + (*elements[2]).into() * utils::pow(256, 13) + + (*elements[3]).into() * utils::pow(256, 12) + + (*elements[4]).into() * utils::pow(256, 11) + + (*elements[5]).into() * utils::pow(256, 10) + + (*elements[6]).into() * utils::pow(256, 9) + + (*elements[7]).into() * utils::pow(256, 8) + + (*elements[8]).into() * utils::pow(256, 7) + + (*elements[9]).into() * utils::pow(256, 6) + + (*elements[10]).into() * utils::pow(256, 5) + + (*elements[11]).into() * utils::pow(256, 4) + + (*elements[12]).into() * utils::pow(256, 3) + + (*elements[13]).into() * utils::pow(256, 2) + + (*elements[14]).into() * utils::pow(256, 1) + + (*elements[15]).into() * utils::pow(256, 0)); + + self.items.insert(chunk_index.into(), current.try_into().unwrap()); + chunk_index += 1; + elements.pop_front_n(16); + } + } + + /// Loads a 32 bytes element from the memory. + /// + /// # Arguments + /// * `self` - A reference to the `Memory` instance. + /// * `offset` - The offset to load the element from. + fn _load(ref self: Memory, offset: usize) -> u256 { + let (chunk_index, offset_in_chunk) = u32_safe_divmod(offset, u32_as_non_zero(16)); + + if offset == 0 { + // Offset is aligned. This is the simplest and most efficient case, + // so we optimize for it. Note that no locals were allocated at all. + return self.items.read_u256(chunk_index); + } + + // Offset is misaligned. + // | W0 | W1 | w2 | + // | EL_H | EL_L | + // ^---^ + // |-- mask = 256 ** offset_in_chunk + + // Compute mask. + + let mask: u256 = helpers::pow256_rev(offset_in_chunk); + let mask_c: u256 = utils::pow(2, 128).into() / mask; + + // Read the words at chunk_index, +1, +2. + let w0: u128 = self.items.get(chunk_index.into()); + let w1: u128 = self.items.get(chunk_index.into() + 1); + let w2: u128 = self.items.get(chunk_index.into() + 2); + + // Compute element words + let w0_l: u256 = w0.into() % mask; + let (w1_h, w1_l): (u256, u256) = u256_safe_divmod(w1.into(), u256_as_non_zero(mask)); + let w2_h: u256 = w2.into() / mask; + let el_h: u128 = (w0_l * mask_c + w1_h).try_into().unwrap(); + let el_l: u128 = (w1_l * mask_c + w2_h).try_into().unwrap(); + + return u256 { low: el_l, high: el_h }; + } + + /// Loads N bytes from the memory + /// + /// # Arguments + /// * `self` - A reference to the `Memory` instance. + /// * `elements_len` - The number of bytes to load. + /// * `elements` - Output array that will contain the loaded bytes. + /// * `offset` - The memory offset to load from. + fn _load_n(ref self: Memory, elements_len: usize, ref elements: Array, offset: usize) { + if elements_len == 0 { + return (); + } + + // Check alignment of offset to bytes16 chunks. + let (chunk_index_i, offset_in_chunk_i) = u32_safe_divmod(offset, u32_as_non_zero(16)); + let (chunk_index_f, mut offset_in_chunk_f) = u32_safe_divmod( + offset + elements_len - 1, u32_as_non_zero(16) + ); + offset_in_chunk_f += 1; + let mask_i: u256 = helpers::pow256_rev(offset_in_chunk_i); + let mask_f: u256 = helpers::pow256_rev(offset_in_chunk_f); + + // Special case: within the same word. + if chunk_index_i == chunk_index_f { + let w: u128 = self.items.get(chunk_index_i.into()); + let w_l = w.into() % mask_i; + let w_lh = w_l / mask_f; + helpers::split_word(w_lh, elements_len, ref elements) + } + + // Otherwise. + // Get first word. + let w_i = self.items.get(chunk_index_i.into()); + let w_i_l = (w_i.into() % mask_i); + let elements_first_word = helpers::split_word(w_i_l, 16 - offset_in_chunk_i, ref elements); + + // Get blocks. + self.load_aligned_words(chunk_index_i + 1, chunk_index_f, ref elements); + + // Get last word. + let w_f = self.items.get(chunk_index_f.into()); + let w_f_h = w_f.into() / mask_f; + let elements_last_word = helpers::split_word(w_f_h, offset_in_chunk_f, ref elements); + } + + + /// Retrieves aligned values from the memory structure, converts them back into a bytes array, + /// and appends them to the `elements` array. + /// + /// # Arguments + /// * `self` - A reference to the `Memory` instance. + /// * `chunk_index` - The index of the chunk to load from. + /// * `chunk_index_f` - The index of the last chunk to load from. + /// * `elements` - Output array to which the loaded bytes will be appended. + fn load_aligned_words( + ref self: Memory, mut chunk_index: usize, chunk_index_f: usize, ref elements: Array + ) { + loop { + if chunk_index == chunk_index_f { + break (); + } + let value = self.items.get(chunk_index.into()); + // Pushes 16 items to `elements` + helpers::split_word_128(value.into(), ref elements); + chunk_index += 1; + } + } + + /// Expands the memory by `length` bytes. + /// + /// # Arguments + /// * `self` - A reference to the `Memory` instance. + /// * `length` - The number of bytes to expand the memory by. + /// + /// # Returns + /// * `usize` - The cost of expanding the memory. + fn expand(ref self: Memory, length: usize) -> usize { + let last_memory_size_word = (self.bytes_len + 31) / 32; + let mut last_memory_cost = (last_memory_size_word * last_memory_size_word) / 512; + last_memory_cost += (3 * last_memory_size_word); + + let new_bytes_len = self.bytes_len + length; + let new_memory_size_word = (new_bytes_len + 31) / 32; + let new_memory_cost = (new_memory_size_word * new_memory_size_word) / 512; + let new_memory_cost = new_memory_cost + (3 * new_memory_size_word); + + let cost = new_memory_cost - last_memory_cost; + + // Update memory size. + self.bytes_len = new_bytes_len; + + cost + } + + /// Ensures that the memory is at least `length` bytes long. Expands if necessary. + /// + /// # Arguments + /// * `self` - A reference to the `Memory` instance. + /// * `length` - The minimum number of bytes the memory should be. + /// + /// # Returns + /// The gas cost of expanding the memory. + fn ensure_length(ref self: Memory, length: usize) -> usize { + if self.bytes_len < length { + let cost = self.expand(length - self.bytes_len); + return cost; + } else { + return 0; + } + } + + /// Expands memory if necessary, then load 32 bytes from it at the given offset. + /// + /// # Arguments + /// * `self` - A reference to the `Memory` instance. + /// * `offset` - The offset to load from and the number of bytes to add. + /// + /// # Returns + /// * `u256` - The loaded value. + /// * `usize` - The gas cost of expanding the memory. + fn load(ref self: Memory, offset: usize) -> (u256, usize) { + let gas_cost = self.ensure_length(32 + offset); + let loaded_element = self._load(offset); + (loaded_element, gas_cost) + } + + /// Expands memory if necessary, then load elements_len bytes from it at given offset. + /// + /// # Arguments + /// * `self` - A reference to the `Memory` instance. + /// * `elements_len` - The number of bytes to load. + /// * `elements` - The array to append the loaded elements to. + /// * `offset` - The offset to load from and number of bytes to expand. + /// # Returns + /// * `usize` - The gas cost of expanding the memory. + fn load_n( + ref self: Memory, elements_len: usize, ref elements: Array, offset: usize + ) -> usize { + let gas_cost = self.ensure_length(elements_len + offset); + self._load_n(elements_len, ref elements, offset); + gas_cost + } +} diff --git a/src/stack.cairo b/src/stack.cairo index 6983afa05..52a0a4312 100644 --- a/src/stack.cairo +++ b/src/stack.cairo @@ -50,7 +50,7 @@ impl StackImpl of StackTrait { /// * Stack The new stack instance. fn new() -> Stack { let items = Felt252DictTrait::::new(); - Stack { items, len: 0} + Stack { items, len: 0 } } /// Pushes a new item onto the stack. @@ -66,7 +66,7 @@ impl StackImpl of StackTrait { /// Returns /// * Option The popped item, or None if the stack is empty. fn pop(ref self: Stack) -> Option { - if self.len() == 0{ + if self.len() == 0 { Option::None(()) } else { let last_index = self.dict_len() - 2; @@ -79,7 +79,7 @@ impl StackImpl of StackTrait { /// Returns /// * Option The top item, or None if the stack is empty. fn peek(ref self: Stack) -> Option { - if self.len() == 0{ + if self.len() == 0 { Option::None(()) } else { let last_index = self.dict_len() - 2; @@ -102,7 +102,7 @@ impl StackImpl of StackTrait { /// Returns /// * bool True if the stack is empty, false otherwise. fn is_empty(self: @Stack) -> bool { - *self.len == 0 + *self.len == 0 } } @@ -167,7 +167,7 @@ mod tests { #[test] fn test_insert_u256() { let mut stack = StackTrait::new(); - let expected: u256 = u256 { low: 100, high: 100}; + let expected: u256 = u256 { low: 100, high: 100 }; stack.insert_u256(expected, 0); let low = stack.items.get(0); let high = stack.items.get(1); @@ -178,7 +178,7 @@ mod tests { #[test] fn test_get_u256() { let mut stack = StackTrait::new(); - let expected: u256 = u256 { low: 100, high: 100}; + let expected: u256 = u256 { low: 100, high: 100 }; stack.insert_u256(expected, 0); let item = stack.get_u256(0); assert(expected == item, 'u256 item should be 1'); diff --git a/src/tests.cairo b/src/tests.cairo deleted file mode 100644 index 5e57fff65..000000000 --- a/src/tests.cairo +++ /dev/null @@ -1,2 +0,0 @@ -mod kakarot_test; -mod stack_test; diff --git a/src/utils.cairo b/src/utils.cairo index a087a528a..2c88e7dbf 100644 --- a/src/utils.cairo +++ b/src/utils.cairo @@ -2,6 +2,9 @@ use array::ArrayTrait; use option::OptionTrait; +mod helpers; +mod constants; + /// Panic with a custom message. /// # Arguments /// * `msg` - The message to panic with. Must be a short string to fit in a felt252. @@ -59,9 +62,10 @@ fn max(a: usize, b: usize) -> usize { /// # Returns /// * `felt252` - The result of base raised to the power of exp. fn pow(base: felt252, exp: felt252) -> felt252 { - match exp { - 0 => 1, - _ => base * pow(base, exp - 1), + if exp == 0 { + return 1; + } else { + return base * pow(base, exp - 1); } } diff --git a/src/utils/constants.cairo b/src/utils/constants.cairo new file mode 100644 index 000000000..b20e1b689 --- /dev/null +++ b/src/utils/constants.cairo @@ -0,0 +1 @@ +const FELT252_PRIME: u256 = 0x800000000000011000000000000000000000000000000000000000000000001; diff --git a/src/utils/helpers.cairo b/src/utils/helpers.cairo new file mode 100644 index 000000000..838e10094 --- /dev/null +++ b/src/utils/helpers.cairo @@ -0,0 +1,259 @@ +use array::ArrayTrait; +use array::SpanTrait; +use traits::{Into, TryInto}; +use kakarot::utils::constants; +use option::OptionTrait; +use debug::PrintTrait; + +/// Ceils a number of bits to the next word (32 bytes) +/// +/// # Arguments +/// * `bytes_len` - The number of bits to ceil +/// +/// # Returns +/// The number of bytes that are needed to store `bytes_len` bits in 32-bytes words. +/// +/// # Examples +/// ceil_bytes_len_to_next_32_bytes_word(2) = 32 +/// ceil_bytes_len_to_next_32_bytes_word(34) = 64 +fn ceil_bytes_len_to_next_32_bytes_word(bytes_len: usize) -> usize { + let q = (bytes_len + 31) / 32; + return q * 32; +} + +/// Computes 256 ** (16 - i) for 0 <= i <= 16. +fn pow256_rev(i: usize) -> u256 { + if (i > 16) { + panic_with_felt252('pow256_rev: i > 16'); + } + + if i == 0 { + return 340282366920938463463374607431768211456; + } else if i == 1 { + return 1329227995784915872903807060280344576; + } else if i == 2 { + return 5192296858534827628530496329220096; + } else if i == 3 { + return 20282409603651670423947251286016; + } else if i == 4 { + return 79228162514264337593543950336; + } else if i == 5 { + return 309485009821345068724781056; + } else if i == 6 { + return 1208925819614629174706176; + } else if i == 7 { + return 4722366482869645213696; + } else if i == 8 { + return 18446744073709551616; + } else if i == 9 { + return 72057594037927936; + } else if i == 10 { + return 281474976710656; + } else if i == 11 { + return 1099511627776; + } else if i == 12 { + return 4294967296; + } else if i == 13 { + return 16777216; + } else if i == 14 { + return 65536; + } else if i == 15 { + return 256; + } else { + return 1; + } +} + + +/// Splits a u256 into `len` bytes, big-endian, and appends the result to `dst`. +fn split_word(mut value: u256, mut len: usize, ref dst: Array) { + let little_endian = split_word_little(value, len); + let big_endian = reverse_array(little_endian.span()); + concat_array(ref dst, big_endian.span()); +} + +/// Splits a u256 into `len` bytes, little-endian, and returns the bytes array. +fn split_word_little(mut value: u256, mut len: usize) -> Array { + let mut dst: Array = ArrayTrait::new(); + loop { + if len == 0 { + assert(value == 0, 'split_words:value not 0'); + break (); + } + + let base = 256; + let bound = 256; + let low_part = (value % constants::FELT252_PRIME) % base; + dst.append(low_part.try_into().unwrap()); + + len = len - 1; + value = (value - low_part) / 256; + }; + dst +} + +/// Splits a u256 into 16 bytes, big-endien, and appends the result to `dst`. +fn split_word_128(value: u256, ref dst: Array) { + split_word(value, 16, ref dst) +} + + +/// Loads a sequence of bytes into a single u128 in big-endian +/// +/// # Arguments +/// * `len` - The number of bytes to load +/// * `words` - The bytes to load +/// +/// # Returns +/// The packed u256 +fn load_word(mut len: usize, words: Span) -> u256 { + if len == 0 { + return 0; + } + + let mut current: u256 = 0; + let mut counter = 0; + + loop { + if len == 0 { + break (); + } + let loaded: u8 = *words[counter]; + let tmp = current * 256; + current = tmp + loaded.into(); + len -= 1; + counter += 1; + }; + + current +} + +/// Converts a u256 to a bytes array represented by an array of felts (1 felt represents 1 byte). +/// +/// # Arguments +/// * `value` - The value to convert +/// +/// # Returns +/// The bytes array representation of the value. +fn u256_to_bytes_array(mut value: u256) -> Array { + let mut counter = 0; + let mut bytes_vec: Array = ArrayTrait::new(); + // low part + loop { + if counter == 16 { + break (); + } + bytes_vec.append((value.low % 256).try_into().unwrap()); + value.low /= 256; + counter += 1; + }; + + let mut counter = 0; + // high part + loop { + if counter == 16 { + break (); + } + bytes_vec.append((value.high % 256).try_into().unwrap()); + value.high /= 256; + counter += 1; + }; + + // Reverse the array as memory is arranged in big endian order. + let mut counter = bytes_vec.len(); + let mut bytes_vec_reversed: Array = ArrayTrait::new(); + loop { + if counter == 0 { + break (); + } + bytes_vec_reversed.append(*bytes_vec[counter - 1]); + counter -= 1; + }; + bytes_vec_reversed +} + + +// Concatenates two arrays by adding the elements of arr2 to arr1. +fn concat_array, impl TDrop: Drop>( + ref arr1: Array, mut arr2: Span +) { + loop { + if arr2.len() == 0 { + break (); + } + let elem = *arr2.pop_front().unwrap(); + arr1.append(elem); + } +} + +/// Reverses an array +fn reverse_array, impl TDrop: Drop>(src: Span) -> Array { + let mut counter = src.len(); + let mut dst: Array = ArrayTrait::new(); + loop { + if counter == 0 { + break (); + } + dst.append(*src[counter - 1]); + counter -= 1; + }; + dst +} + +/// Tries to convert a u256 into a u8. +impl U256TryIntoU8 of TryInto { + fn try_into(self: u256) -> Option { + if self.high != 0 { + return Option::None(()); + } + self.low.try_into() + } +} + +/// Converts a u8 into a u256. +impl U8IntoU256 of Into { + fn into(self: u8) -> u256 { + u256 { low: self.into(), high: 0 } + } +} + + +//TODO(eni) make PR and add this in corelib + +trait SpanExtensionTrait { + fn pop_front_n(ref self: Span, n: usize); +} + +impl SpanExtensionImpl of SpanExtensionTrait { + /// Removes the first `n` elements from the Span. + fn pop_front_n(ref self: Span, mut n: usize) { + loop { + if n == 0 { + break (); + } + self.pop_front(); + n = n - 1; + }; + } +} + +impl U128IntoU256 of Into { + fn into(self: u128) -> u256 { + u256 { low: self, high: 0 } + } +} + +impl U32IntoU256 of Into { + fn into(self: u32) -> u256 { + u256 { low: self.into(), high: 0 } + } +} + +impl U256TryIntoU128 of TryInto { + fn try_into(self: u256) -> Option { + if self.high != 0 { + return Option::None(()); + } + Option::Some(self.low) + } +} diff --git a/tests/lib.cairo b/tests/lib.cairo new file mode 100644 index 000000000..2bddda703 --- /dev/null +++ b/tests/lib.cairo @@ -0,0 +1,4 @@ +mod test_kakarot; +mod test_stack; +mod test_memory; +mod utils; diff --git a/src/tests/kakarot_test.cairo b/tests/test_kakarot.cairo similarity index 100% rename from src/tests/kakarot_test.cairo rename to tests/test_kakarot.cairo diff --git a/tests/test_memory.cairo b/tests/test_memory.cairo new file mode 100644 index 000000000..b8f3d6164 --- /dev/null +++ b/tests/test_memory.cairo @@ -0,0 +1,261 @@ +use core::dict::Felt252DictTrait; +use core::debug::PrintTrait; +use kakarot::memory::MemoryTrait; +use kakarot::memory::MemoryPrintTrait; +use kakarot::utils::helpers; +use kakarot::utils; +use array::{ArrayTrait, SpanTrait}; +use traits::{Into, TryInto}; +use option::OptionTrait; + +#[test] +#[available_gas(2000000)] +fn test_init_should_return_an_empty_memory() { + // When + let result = MemoryTrait::new(); + + // Then + assert(result.bytes_len == 0, 'memory not empty'); +} + +#[test] +#[available_gas(2000000)] +fn test_len_should_return_the_length_of_the_memory() { + // Given + let memory = MemoryTrait::new(); + + // When + let result = memory.bytes_len; + + // Then + assert(result == 0, 'memory not empty'); +} + +#[test] +#[available_gas(2000000)] +fn test_store_should_add_an_element_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let value: u256 = 1; + let result = memory.store(value, 0); + + // Then + let len = memory.bytes_len; + assert(len == 32, 'memory should be 32bytes'); +} + +#[test] +#[available_gas(20000000)] +fn test_store_should_add_n_elements_to_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + + // When + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + let result = memory.store_n(bytes_array.span(), 0); + + // Then + let len = memory.bytes_len; + assert(len == 32, 'memory should be 32bytes'); +} + +#[test] +#[available_gas(20000000)] +fn test__load__should_load_an_element_from_the_memory() { + // Given + let mut memory = MemoryTrait::new(); + // In the memory, the following values are stored in the order 1, 2, 3, 4 (Big Endian) + let first_value: u256 = u256 { low: 2, high: 1 }; + let second_value = u256 { low: 4, high: 3 }; + let first_bytes_array = helpers::u256_to_bytes_array(first_value); + let second_bytes_array = helpers::u256_to_bytes_array(second_value); + + memory.store_n(first_bytes_array.span(), 0); + + memory.store_n(second_bytes_array.span(), 32); + + // When + let result: u256 = memory._load(0); + + // Then + assert(result == first_value, 'res not u256{2,1}'); + + // When + let result: u256 = memory._load(32); + + // Then + assert(result == second_value, 'res not u256{4,3}'); + + // When + let result: u256 = memory._load(16); + + // Then + assert(result == u256 { low: 3, high: 2 }, 'res not u256{3,2}'); +} +fn _load_should_load_an_element_from_the_memory_with_offset(offset: usize, low: u128, high: u128) { + // Given + let mut memory = MemoryTrait::new(); + // In the memory, the following values are stored in the order 1, 2, 3, 4 (Big Endian) + let first_value: u256 = u256 { low: 2, high: 1 }; + let second_value = u256 { low: 4, high: 3 }; + + let first_bytes_array = helpers::u256_to_bytes_array(first_value); + let second_bytes_array = helpers::u256_to_bytes_array(second_value); + + memory.store_n(first_bytes_array.span(), 0); + memory.store_n(second_bytes_array.span(), 32); + + // When + let result: u256 = memory._load(offset); + + // Then + assert(result == u256 { low: low, high: high }, 'result not matching expected'); +} + + +#[test] +#[available_gas(200000000)] +fn test__load__should_load_an_element_from_the_memory_with_offset_1() { + _load_should_load_an_element_from_the_memory_with_offset( + 8, 2 * utils::pow(256, 8).try_into().unwrap(), utils::pow(256, 8).try_into().unwrap() + ); +} +#[test] +#[available_gas(200000000)] +fn test__load__should_load_an_element_from_the_memory_with_offset_2() { + _load_should_load_an_element_from_the_memory_with_offset( + 7, 2 * utils::pow(256, 7).try_into().unwrap(), utils::pow(256, 7).try_into().unwrap() + ); +} +#[test] +#[available_gas(200000000)] +fn test__load__should_load_an_element_from_the_memory_with_offset_3() { + _load_should_load_an_element_from_the_memory_with_offset( + 23, 3 * utils::pow(256, 7).try_into().unwrap(), 2 * utils::pow(256, 7).try_into().unwrap() + ); +} + +#[test] +#[available_gas(200000000)] +fn test__load__should_load_an_element_from_the_memory_with_offset_4() { + _load_should_load_an_element_from_the_memory_with_offset( + 33, 4 * utils::pow(256, 1).try_into().unwrap(), 3 * utils::pow(256, 1).try_into().unwrap() + ); +} +#[test] +#[available_gas(200000000)] +fn test__load__should_load_an_element_from_the_memory_with_offset_5() { + _load_should_load_an_element_from_the_memory_with_offset( + 63, 0, 4 * utils::pow(256, 15).try_into().unwrap() + ); +} + +#[test] +#[available_gas(200000000)] +fn test__load__should_load_an_element_from_the_memory_with_offset_6() { + _load_should_load_an_element_from_the_memory_with_offset(500, 0, 0); +} + +#[test] +#[available_gas(200000000)] +fn test__expand__should_return_the_same_memory_and_no_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + memory.store_n(bytes_array.span(), 0); + + // When + let cost = memory.expand(0); + + // Then + assert(cost == 0, 'cost should be 0'); + assert(memory.bytes_len == 32, 'memory should be 32bytes'); + let value = memory._load(0); + assert(value == 1, 'value should be 1'); +} + +#[test] +#[available_gas(2000000000)] +fn test__expand__should_return_expanded_memory_and_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + + memory.store_n(bytes_array.span(), 0); + + // When + let cost = memory.expand(1); + + // Then + assert(cost >= 0, 'cost should be positive'); + assert(memory.bytes_len == 33, 'memory should be 33bytes'); + let value = memory._load(0); + assert(value == 1, 'value should be 1'); +} + +#[test] +#[available_gas(2000000000)] +fn test__ensure_length__should_return_the_same_memory_and_no_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + + memory.store_n(bytes_array.span(), 0); + + // When + let cost = memory.ensure_length(1); + + // Then + assert(cost == 0, 'cost should be 0'); + assert(memory.bytes_len == 32, 'memory should be 32bytes'); + let value = memory._load(0); + assert(value == 1, 'value should be 1'); +} + +#[test] +#[available_gas(20000000000)] +fn test__ensure_length__should_return_expanded_memory_and_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + + memory.store_n(bytes_array.span(), 0); + + // When + let cost = memory.ensure_length(33); + + // Then + assert(cost >= 0, 'cost should be positive'); + assert(memory.bytes_len == 33, 'memory should be 33bytes'); + let value = memory._load(0); + assert(value == 1, 'value should be 1'); +} + +#[test] +#[available_gas(20000000000)] +fn test__expand_and_load__should_return_expanded_memory_and_element_and_cost() { + // Given + let mut memory = MemoryTrait::new(); + let value: u256 = 1; + let bytes_array = helpers::u256_to_bytes_array(value); + memory.store_n(bytes_array.span(), 0); + + // When + let (loaded_element, cost) = memory.load(32); + + // Then + assert(cost >= 0, 'cost should be positive'); + assert(memory.bytes_len == 64, 'memory should be 64 bytes'); + let value = memory._load(0); + assert(value == 1, 'loaded_element should be 1'); + + let value = memory._load(32); + assert(value == 0, 'value should be 0'); +} diff --git a/src/tests/stack_test.cairo b/tests/test_stack.cairo similarity index 100% rename from src/tests/stack_test.cairo rename to tests/test_stack.cairo diff --git a/tests/utils.cairo b/tests/utils.cairo new file mode 100644 index 000000000..7a2b7bc6a --- /dev/null +++ b/tests/utils.cairo @@ -0,0 +1 @@ +mod test_helpers; diff --git a/tests/utils/test_helpers.cairo b/tests/utils/test_helpers.cairo new file mode 100644 index 000000000..22338b0a5 --- /dev/null +++ b/tests/utils/test_helpers.cairo @@ -0,0 +1,157 @@ +use kakarot::utils::helpers; +use array::{ArrayTrait, SpanTrait}; +use debug::PrintTrait; + +#[test] +#[available_gas(2000000000)] +fn test_u256_to_bytes_array() { + let value: u256 = 256; + + let bytes_array = helpers::u256_to_bytes_array(value); + assert(1 == *bytes_array[30], 'wrong conversion'); +} + +#[test] +#[available_gas(2000000000)] +fn test_load_word() { + // No bytes to load + let res0 = helpers::load_word(0, ArrayTrait::new().span()); + assert(0 == res0, 'res0: wrong load'); + + // Single bytes value + let mut arr1 = ArrayTrait::new(); + arr1.append(0x01); + let res1 = helpers::load_word(1, arr1.span()); + assert(1 == res1, 'res1: wrong load'); + + let mut arr2 = ArrayTrait::new(); + arr2.append(0xff); + let res2 = helpers::load_word(1, arr2.span()); + assert(255 == res2, 'res2: wrong load'); + + // Two byte values + let mut arr3 = ArrayTrait::new(); + arr3.append(0x01); + arr3.append(0x00); + let res3 = helpers::load_word(2, arr3.span()); + assert(256 == res3, 'res3: wrong load'); + + let mut arr4 = ArrayTrait::new(); + arr4.append(0xff); + arr4.append(0xff); + let res4 = helpers::load_word(2, arr4.span()); + assert(65535 == res4, 'res4: wrong load'); + + // Four byte values + let mut arr5 = ArrayTrait::new(); + arr5.append(0xff); + arr5.append(0xff); + arr5.append(0xff); + arr5.append(0xff); + let res5 = helpers::load_word(4, arr5.span()); + assert(4294967295 == res5, 'res5: wrong load'); + + // 16 bytes values + let mut arr6 = ArrayTrait::new(); + arr6.append(0xff); + let mut counter: u128 = 0; + loop { + if counter >= 15 { + break (); + } + arr6.append(0xff); + counter += 1; + }; + let res6 = helpers::load_word(16, arr6.span()); + assert(340282366920938463463374607431768211455 == res6, 'res6: wrong load'); +} + + +#[test] +#[available_gas(2000000000)] +fn test_split_word_little() { + // Test with 0 value and 0 len + let res0 = helpers::split_word_little(0, 0); + assert(res0.len() == 0, 'res0: wrong length'); + + // Test with single byte value + let res1 = helpers::split_word_little(1, 1); + assert(res1.len() == 1, 'res1: wrong length'); + assert(*res1[0] == 1, 'res1: wrong value'); + + // Test with two byte value + let res2 = helpers::split_word_little(257, 2); // 257 = 0x0101 + assert(res2.len() == 2, 'res2: wrong length'); + assert(*res2[0] == 1, 'res2: wrong value at index 0'); + assert(*res2[1] == 1, 'res2: wrong value at index 1'); + + // Test with four byte value + let res3 = helpers::split_word_little(67305985, 4); // 67305985 = 0x04030201 + assert(res3.len() == 4, 'res3: wrong length'); + assert(*res3[0] == 1, 'res3: wrong value at index 0'); + assert(*res3[1] == 2, 'res3: wrong value at index 1'); + assert(*res3[2] == 3, 'res3: wrong value at index 2'); + assert(*res3[3] == 4, 'res3: wrong value at index 3'); + + // Test with 16 byte value (u128 max value) + let max_u128: u256 = 340282366920938463463374607431768211454; // u128 max value - 1 + let res4 = helpers::split_word_little(max_u128, 16); + assert(res4.len() == 16, 'res4: wrong length'); + assert(*res4[0] == 0xfe, 'res4: wrong MSB value'); + + let mut counter: usize = 1; + loop { + if counter >= 16 { + break (); + } + assert(*res4[counter] == 0xff, 'res4: wrong value at index'); + counter += 1; + }; +} + +#[test] +#[available_gas(2000000000)] +fn test_split_word() { + // Test with 0 value and 0 len + let mut dst0: Array = ArrayTrait::new(); + helpers::split_word(0, 0, ref dst0); + assert(dst0.len() == 0, 'dst0: wrong length'); + + // Test with single byte value + let mut dst1: Array = ArrayTrait::new(); + helpers::split_word(1, 1, ref dst1); + assert(dst1.len() == 1, 'dst1: wrong length'); + assert(*dst1[0] == 1, 'dst1: wrong value'); + + // Test with two byte value + let mut dst2: Array = ArrayTrait::new(); + helpers::split_word(257, 2, ref dst2); // 257 = 0x0101 + assert(dst2.len() == 2, 'dst2: wrong length'); + assert(*dst2[0] == 1, 'dst2: wrong value at index 0'); + assert(*dst2[1] == 1, 'dst2: wrong value at index 1'); + + // Test with four byte value + let mut dst3: Array = ArrayTrait::new(); + helpers::split_word(16909060, 4, ref dst3); // 16909060 = 0x01020304 + assert(dst3.len() == 4, 'dst3: wrong length'); + assert(*dst3[0] == 1, 'dst3: wrong value at index 0'); + assert(*dst3[1] == 2, 'dst3: wrong value at index 1'); + assert(*dst3[2] == 3, 'dst3: wrong value at index 2'); + assert(*dst3[3] == 4, 'dst3: wrong value at index 3'); + + // Test with 16 byte value (u128 max value) + let max_u128: u256 = 340282366920938463463374607431768211454; // u128 max value -1 + let mut dst4: Array = ArrayTrait::new(); + helpers::split_word(max_u128, 16, ref dst4); + assert(dst4.len() == 16, 'dst4: wrong length'); + let mut counter: usize = 0; + assert(*dst4[15] == 0xfe, 'dst4: wrong LSB value'); + loop { + if counter >= 15 { + break (); + } + assert(*dst4[counter] == 0xff, 'dst4: wrong value at index'); + counter += 1; + }; +} +