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;
+ };
+}
+