Repository: NethermindEth/warp (language: python)

Issue:
Felt as a Cairo Custom Type
Cairo 1 introduces `uX`, an unsigned integer equivalent to each of Soiditys `uintX`.  It is expected from the Warp of the future to take advantage of this new types due to all the advantages they offer compared to simple `felt`s. Warp 3.0 should transpile `uintX` types as `uX`. 

The problem arise when there is need of the Felt type, Warp 3.0 is left without a mechanism to write simple felts. Examples where felts are needed:
* storage locations are felts
* memory locations are felts
* string_hash result is a felt



Discussion / hints:

Fix patch:
diff --git a/.starknet_accounts_devnet/starknet_open_zeppelin_accounts.json b/.starknet_accounts_devnet/starknet_open_zeppelin_accounts.json
deleted file mode 100644
index ea16f358c..000000000
--- a/.starknet_accounts_devnet/starknet_open_zeppelin_accounts.json
+++ /dev/null
@@ -1,9 +0,0 @@
-{
-  "alpha-goerli": {
-    "__default__": {
-      "private_key": "0xe3e70682c2094cac629f6fbed82c07cd",
-      "public_key": "0x7e52885445756b313ea16849145363ccb73fb4ab0440dbac333cf9d13de82b9",
-      "address": "0x7e00d496e324876bbc8531f2d9a82bf154d1a04a50218ee74cdd372f75a551a"
-    }
-  }
-}
diff --git a/src/cairoUtilFuncGen/memory/arrayLiteral.ts b/src/cairoUtilFuncGen/memory/arrayLiteral.ts
index 1fcb398f9..e8f9205a6 100644
--- a/src/cairoUtilFuncGen/memory/arrayLiteral.ts
+++ b/src/cairoUtilFuncGen/memory/arrayLiteral.ts
@@ -20,9 +20,10 @@ import { createCairoGeneratedFunction, createCallToFunction } from '../../utils/
 import {
   ARRAY,
   ARRAY_TRAIT,
-  MEMORY_TRAIT,
+  WARP_MEMORY_TRAIT,
   U32_TO_FELT,
   WARP_MEMORY,
+  WARP_MEMORY_IMPL,
 } from '../../utils/importPaths';
 import { createNumberLiteral } from '../../utils/nodeTemplates';
 import {
@@ -130,7 +131,8 @@ export class MemoryArrayLiteralGen extends StringIndexedFuncGen {
       ...mapRange(size, (n) => elementCairoType.serialiseMembers(`e${n}`))
         .flat()
         .map(
-          (name, index) => `warp_memory.insert(
+          // TODO: use create from WarpMemoryAccessor instead
+          (name, index) => `warp_memory.write(start,
             ${add('start', dynamic ? index + 2 : index)},
             ${name}
           );`,
@@ -139,9 +141,10 @@ export class MemoryArrayLiteralGen extends StringIndexedFuncGen {
     return {
       name: funcName,
       code: endent`
-        #[implicit(warp_memory)]
+        #[implicits(warp_memory: WarpMemory)]
         fn ${funcName}(${argString}) -> felt252 {
           let start = warp_memory.pointer;
+          warp_memory.alloc(${alloc_len})
           ${writes.join('\n')}
           return start;
         }`,
@@ -150,7 +153,8 @@ export class MemoryArrayLiteralGen extends StringIndexedFuncGen {
         this.requireImport(...ARRAY_TRAIT),
         this.requireImport(...U32_TO_FELT),
         this.requireImport(...WARP_MEMORY),
-        this.requireImport(...MEMORY_TRAIT),
+        this.requireImport(...WARP_MEMORY_TRAIT),
+        this.requireImport(...WARP_MEMORY_IMPL),
       ],
     };
   }
diff --git a/src/cairoUtilFuncGen/memory/memoryRead.ts b/src/cairoUtilFuncGen/memory/memoryRead.ts
index f5a02a00f..fbaa57c5b 100644
--- a/src/cairoUtilFuncGen/memory/memoryRead.ts
+++ b/src/cairoUtilFuncGen/memory/memoryRead.ts
@@ -1,3 +1,4 @@
+import endent from 'endent';
 import {
   Expression,
   TypeName,
@@ -6,7 +7,6 @@ import {
   FunctionStateMutability,
   generalizeType,
   TypeNode,
-  FunctionDefinition,
 } from 'solc-typed-ast';
 import { CairoFunctionDefinition, typeNameFromTypeNode } from '../../export';
 import {
@@ -18,18 +18,23 @@ import {
 } from '../../utils/cairoTypeSystem';
 import { cloneASTNode } from '../../utils/cloning';
 import { createCairoGeneratedFunction, createCallToFunction } from '../../utils/functionGeneration';
-import { DICT_READ, WARPLIB_MEMORY, WM_READ_FELT, WM_READ_ID } from '../../utils/importPaths';
+import {
+  ACCESSOR,
+  ACCESSOR_TRAIT,
+  WARPLIB_MEMORY,
+  WM_READ_FELT,
+  WM_READ_ID,
+} from '../../utils/importPaths';
 import { createNumberLiteral, createNumberTypeName } from '../../utils/nodeTemplates';
 import { isDynamicArray, safeGetNodeType } from '../../utils/nodeTypeProcessing';
-import { add, GeneratedFunctionInfo, locationIfComplexType, StringIndexedFuncGen } from '../base';
-import { serialiseReads } from '../serialisation';
+import { GeneratedFunctionInfo, locationIfComplexType, StringIndexedFuncGen } from '../base';
 
 /*
   Produces functions that when given a start location in warp_memory, deserialise all necessary
   felts to produce a full value. For example, a function to read a Uint256 reads the given location
   and the next one, and combines them into a Uint256 struct
 */
-
+// TODO: Replace the whole class with a call to warp_memory.retrieve?
 export class MemoryReadGen extends StringIndexedFuncGen {
   gen(memoryRef: Expression): FunctionCall {
     const valueType = generalizeType(safeGetNodeType(memoryRef, this.ast.inference))[0];
@@ -102,31 +107,16 @@ export class MemoryReadGen extends StringIndexedFuncGen {
   }
 
   private getOrCreate(typeToRead: CairoType): GeneratedFunctionInfo {
-    const functionsCalled: FunctionDefinition[] = [this.requireImport(...DICT_READ)];
-
     const funcName = `WM${this.generatedFunctionsDef.size}_READ_${typeToRead.typeName}`;
-    const resultCairoType = typeToRead.toString();
-    const [reads, pack, requiredImports] = serialiseReads(typeToRead, readFelt, readFelt);
-
-    requiredImports.map((i) => {
-      functionsCalled.push(this.requireImport(...i.import, [], [], { isTrait: i.isTrait }));
-    });
-
     const funcInfo: GeneratedFunctionInfo = {
       name: funcName,
-      code: [
-        `func ${funcName}{range_check_ptr, warp_memory : DictAccess*}(loc: felt) ->(val: ${resultCairoType}){`,
-        `    alloc_locals;`,
-        ...reads.map((s) => `    ${s}`),
-        `    return (${pack},);`,
-        '}',
-      ].join('\n'),
-      functionsCalled: functionsCalled,
+      code: endent`
+      #[implicits(warp_memory: WarpMemory)]
+      fn ${funcName}(loc: felt) -> ${typeToRead.toString()}{
+        warp_memory.retrieve(loc, loc + ${typeToRead.width})
+      }`,
+      functionsCalled: [this.requireImport(...ACCESSOR), this.requireImport(...ACCESSOR_TRAIT)],
     };
     return funcInfo;
   }
 }
-
-function readFelt(offset: number): string {
-  return `let (read${offset}) = dict_read{dict_ptr=warp_memory}(${add('loc', offset)});`;
-}
diff --git a/src/passes/sourceUnitSplitter.ts b/src/passes/sourceUnitSplitter.ts
index 3ade2b956..a928f0316 100644
--- a/src/passes/sourceUnitSplitter.ts
+++ b/src/passes/sourceUnitSplitter.ts
@@ -109,7 +109,11 @@ function mangleFreeFilePath(path: string): string {
   return join(path, FREE_FILE_NAME);
 }
 
-function mangleContractFilePath(path: string, contractName: string, extension: string): string {
+export function mangleContractFilePath(
+  path: string,
+  contractName: string,
+  extension: string,
+): string {
   return join(path, 'src', contractName + extension);
 }
 
diff --git a/src/utils/importFuncGenerator.ts b/src/utils/importFuncGenerator.ts
index f72e5b6ac..05a2a9ac0 100644
--- a/src/utils/importFuncGenerator.ts
+++ b/src/utils/importFuncGenerator.ts
@@ -147,7 +147,7 @@ export function createImport(
     Paths.ARRAY,
     Paths.ARRAY_TRAIT,
     Paths.WARP_MEMORY,
-    Paths.MEMORY_TRAIT,
+    Paths.WARP_MEMORY_TRAIT,
     Paths.BOOL_INTO_FELT252,
     Paths.FELT252_INTO_BOOL,
   ];
diff --git a/src/utils/importPaths.ts b/src/utils/importPaths.ts
index b6d01f10d..d5ab7fd63 100644
--- a/src/utils/importPaths.ts
+++ b/src/utils/importPaths.ts
@@ -235,10 +235,13 @@ export const CONTRACT_ADDRESS_FROM_FELT: [string[], string] = [
 ];
 
 export const INTO: [string[], string] = [['traits'], 'Into'];
-
 export const OPTION_TRAIT: [string[], string] = [['option'], 'OptionTrait'];
 
-export const WARP_MEMORY: [string[], string] = [['warplib', 'warp_memory'], 'WarpMemory'];
-export const MEMORY_TRAIT: [string[], string] = [['warplib', 'warp_memory'], 'MemoryTrait'];
+export const MEMORY_MODULE = ['warplib', 'warp_memory'];
+export const WARP_MEMORY: [string[], string] = [MEMORY_MODULE, 'WarpMemory'];
+export const WARP_MEMORY_TRAIT: [string[], string] = [MEMORY_MODULE, 'WarpMemoryTrait'];
+export const WARP_MEMORY_IMPL: [string[], string] = [MEMORY_MODULE, 'WarpMemoryImpl'];
+export const ACCESSOR: [string[], string] = [MEMORY_MODULE, 'WarpMemoryAccesssor'];
+export const ACCESSOR_TRAIT: [string[], string] = [MEMORY_MODULE, 'WarpMemoryAccesssorTrait'];
 
 export const SUPER: string[] = ['super'];
diff --git a/warplib/src/lib.cairo b/warplib/src/lib.cairo
index 3e1bd66dd..024deec74 100644
--- a/warplib/src/lib.cairo
+++ b/warplib/src/lib.cairo
@@ -2,3 +2,6 @@
 mod integer;
 mod maths;
 mod warp_memory;
+
+#[cfg(test)]
+mod test;
diff --git a/warplib/src/warp_memory.cairo b/warplib/src/warp_memory.cairo
index 5e6ae6736..37b48f809 100644
--- a/warplib/src/warp_memory.cairo
+++ b/warplib/src/warp_memory.cairo
@@ -1,9 +1,20 @@
 use dict::Felt252Dict;
 use dict::Felt252DictTrait;
+use integer::u256_from_felt252;
+
+mod arrays;
+use arrays::WarpMemoryArraysTrait;
+
+mod bytes;
+use bytes::WarpMemoryBytesTrait;
+
+mod accessors;
+use accessors::WarpMemoryAccesssorTrait;
+use accessors::WarpMemoryMultiCellAccessorTrait;
 
 
 struct WarpMemory {
-    pointer: felt252,
+    free_space_pointer: felt252,
     memory: Felt252Dict::<felt252>
 }
 
@@ -14,24 +25,70 @@ impl DestructWarpMemory of Destruct::<WarpMemory> {
 }
 
 
-trait MemoryTrait {
+trait WarpMemoryTrait {
     fn initialize() -> WarpMemory;
-    fn insert(ref self: WarpMemory, position: felt252, value: felt252);
-    fn append(ref self: WarpMemory, value: felt252);
+
+    fn unsafe_write(ref self: WarpMemory, position: felt252, value: felt252);
+    fn write(ref self: WarpMemory, position: felt252, value: felt252);
+
+    fn unsafe_read(ref self: WarpMemory, position: felt252) -> felt252;
+    fn read(ref self: WarpMemory, position: felt252) -> felt252;
+
+    // Given a certain size, it allocates the space for writing
+    fn unsafe_alloc(ref self: WarpMemory, size: felt252) -> felt252;
+    fn alloc(ref self: WarpMemory, size: felt252) -> felt252;
 }
 
 
-impl WarpMemoryImpl of MemoryTrait {
+impl WarpMemoryImpl of WarpMemoryTrait {
     fn initialize() -> WarpMemory {
-        return WarpMemory {memory: Felt252DictTrait::new(), pointer: 0};
+        return WarpMemory {memory: Felt252DictTrait::new(), free_space_pointer: 0};
+    }
+
+    fn unsafe_write(ref self: WarpMemory, position: felt252, value: felt252){
+        self.memory.insert(position, value);
     }
 
-    fn insert(ref self: WarpMemory, position: felt252, value: felt252) {
+    fn write(ref self: WarpMemory, position: felt252, value: felt252){
+        let position_256 = u256_from_felt252(position);
+        let pointer_256 = u256_from_felt252(self.free_space_pointer);
+        if position_256 >= pointer_256 {
+           panic_with_felt252('Writing on unreserved position');
+        }
         self.memory.insert(position, value);
-        self.pointer += 1;
     }
 
-    fn append(ref self: WarpMemory, value: felt252) {
-        self.insert(self.pointer, value);
+    fn unsafe_read(ref self: WarpMemory, position: felt252) -> felt252 {
+        self.memory.get(position)
+    }
+
+    fn read(ref self: WarpMemory, position: felt252) -> felt252 {
+        let position_256 = u256_from_felt252(position);
+        let pointer_256 = u256_from_felt252(self.free_space_pointer);
+        if position_256 >= pointer_256 {
+            panic_with_felt252('Reading on unreserved position');
+        }
+        self.memory.get(position)
+    }
+
+    fn unsafe_alloc(ref self: WarpMemory, size: felt252) -> felt252 {
+        let reserved_pointer = self.free_space_pointer;
+        self.free_space_pointer += size;
+        
+        reserved_pointer
+    }
+
+    fn alloc(ref self: WarpMemory, size: felt252) -> felt252 {
+        let pointer_256 = u256_from_felt252(self.free_space_pointer);
+        let size_256 = u256_from_felt252(size);
+
+        let MAX_FELT_256: u256 = u256_from_felt252(-1);
+        if (pointer_256 + size_256 > MAX_FELT_256){
+            panic_with_felt252('Maximum memory size exceded');
+        }
+
+        let reserved_pointer = self.free_space_pointer;
+        self.free_space_pointer += size;
+        reserved_pointer
     }
 }
diff --git a/warplib/src/warp_memory/accessors.cairo b/warplib/src/warp_memory/accessors.cairo
new file mode 100644
index 000000000..452ddc082
--- /dev/null
+++ b/warplib/src/warp_memory/accessors.cairo
@@ -0,0 +1,118 @@
+use array::Array;
+use array::ArrayTrait;
+use integer::u32_to_felt252;
+use integer::u256_from_felt252;
+use option::OptionTrait;
+use serde::Serde;
+use traits::Into;
+
+use warplib::warp_memory::WarpMemory;
+use warplib::warp_memory::WarpMemoryTrait;
+use warplib::warp_memory::WarpMemoryImpl;
+
+
+trait WarpMemoryAccesssorTrait {
+    fn store<T, impl TSerde: Serde<T>, impl TDrop: Drop<T>>(ref self: WarpMemory, position: felt252, value: T);
+    fn retrieve<T, impl TSerde: Serde<T>, impl TDrop: Drop<T>>(ref self: WarpMemory, position: felt252, size: felt252) -> T;
+    fn create<T, impl TSerde: Serde<T>, impl TDrop: Drop<T>>(ref self: WarpMemory, value: T);
+}
+
+trait WarpMemoryMultiCellAccessorTrait {
+    fn write_multiple(ref self: WarpMemory, position: felt252, ref value: Array::<felt252>);
+    fn read_multiple(ref self: WarpMemory, position: felt252, size: felt252) -> Array::<felt252>;
+}
+
+
+impl WarpMemoryMultiCellAccessor of WarpMemoryMultiCellAccessorTrait {
+    fn write_multiple(ref self: WarpMemory, mut position: felt252, ref value: Array::<felt252>) {
+        let position_256 = u256_from_felt252(position);
+        let len_256: u256 = u256_from_felt252(u32_to_felt252(value.len()));
+        let final_location_256 = position_256 + len_256;
+
+        let MAX_FELT_256 = u256_from_felt252(-1);
+        if final_location_256 > MAX_FELT_256 {
+            panic_with_felt252('Multiple writing overflow');
+        }
+
+        let pointer_256 = u256_from_felt252(self.free_space_pointer);
+        if final_location_256 > pointer_256 {
+            panic_with_felt252('MWriting on unreserved position')
+        }
+
+        loop {
+            match gas::withdraw_gas() {
+               Option::Some(_) => {},
+               Option::None(_) => {
+                    panic_with_felt252('Out of gas');
+               }
+            }
+
+            if value.len() == 0 {
+                break();
+            }
+            self.unsafe_write(position, value.pop_front().unwrap());
+            position += 1;
+        }
+    }
+
+    fn read_multiple(ref self: WarpMemory, position: felt252, size: felt252) -> Array::<felt252> {
+        let start_256 = u256_from_felt252(position);
+        let size_256 = u256_from_felt252(size);
+        let final_location_256 = start_256 + size_256;
+
+        let MAX_FELT_256 = u256_from_felt252(-1);
+        if final_location_256 > MAX_FELT_256 {
+            panic_with_felt252('Multiple reading overflow');
+        }
+
+        let pointer_256 = u256_from_felt252(self.free_space_pointer);
+        if final_location_256 > pointer_256 {
+            panic_with_felt252('MReading on unreserved position')
+        }
+
+        let mut index = position;
+        let mut array: Array<felt252> = ArrayImpl::<felt252>::new();
+        let final_location = position + size;
+        loop {
+            match gas::withdraw_gas() {
+               Option::Some(_) => {},
+               Option::None(_) => {
+                    panic_with_felt252('Out of gas');
+               }
+            }
+
+            if index == final_location {
+                break();
+            }
+            array.append(self.read(index));
+            index += 1;
+        };
+        array
+    }
+}
+
+
+impl WarpMemoryAccesssor of WarpMemoryAccesssorTrait {
+    fn store<T, impl TSerde: Serde<T>, impl TDrop: Drop<T>>(ref self: WarpMemory, position: felt252, value: T) {
+        let mut serialization_array: Array<felt252> = ArrayImpl::<felt252>::new();
+        TSerde::serialize(ref serialization_array, value);
+
+        self.write_multiple(position, ref serialization_array);
+    }
+
+    fn create<T, impl TSerde: Serde<T>, impl TDrop: Drop<T>>(ref self: WarpMemory, value: T) {
+        let mut serialization_array: Array<felt252> = ArrayImpl::<felt252>::new();
+        TSerde::serialize(ref serialization_array, value);
+
+        let position = self.alloc(serialization_array.len().into());
+        self.write_multiple(position, ref serialization_array);
+    }
+
+    fn retrieve<T, impl TSerde: Serde<T>, impl TDrop: Drop<T>>(ref self: WarpMemory, position: felt252, size: felt252) -> T {
+        let serialization_array: Array<felt252> = self.read_multiple(position, size);
+        let mut span = ArrayImpl::<felt252>::span(@serialization_array);
+        let value = TSerde::deserialize(ref span);
+
+        value.unwrap()
+    }
+}
diff --git a/warplib/src/warp_memory/arrays.cairo b/warplib/src/warp_memory/arrays.cairo
new file mode 100644
index 000000000..d41f6d739
--- /dev/null
+++ b/warplib/src/warp_memory/arrays.cairo
@@ -0,0 +1,96 @@
+use warplib::warp_memory::WarpMemory;
+use warplib::warp_memory::WarpMemoryTrait;
+use warplib::warp_memory::WarpMemoryImpl;
+use integer::u256_from_felt252;
+use integer::u128_try_from_felt252;
+use integer::Into;
+use option::OptionTrait;
+
+trait WarpMemoryArraysTrait {
+    /// Reads the pointer location expecting to find a complex memory structure. If it
+    /// finds none then it creates one with default values.
+    /// The size param specify how many memory cell the structure requires. For example:
+    /// dynamic arrays: size is 1, because you need the length
+    /// static arrays: size is its length times it's element type size
+    /// structs: size is the sum of it's members
+    fn get_or_create_id(ref self: WarpMemory, location: felt252, size: felt252) -> felt252;
+
+    /// Given the length and the felt size of an element, allocates the space
+    /// in memory and returns a pointer to it
+    fn new_dynamic_array(ref self: WarpMemory, len: felt252, elem_width: felt252) -> felt252;
+
+    /// Given a pointer to a dynamic array, the index you want to access from it
+    /// and the size of the element in such array, returns a pointer to the element
+    fn index_dyn(ref self: WarpMemory, array_ptr: felt252, index: felt252, elem_width: felt252) -> felt252;
+
+    /// Given a pointer to a static array, the index you want to access from it,
+    /// the size of the elements in the array and the it's length,  returns a pointer
+    /// to the element
+    fn index_static(ref self: WarpMemory, array_ptr: felt252, index: felt252, elem_width: felt252, length: felt252) -> felt252;
+
+    /// Given the pointer to a dynamic array, it returns it's length
+    fn length_dyn(ref self: WarpMemory, array_ptr: felt252) -> felt252;
+}
+
+
+impl WarpMemoryArraysImpl of WarpMemoryArraysTrait {
+    fn get_or_create_id(ref self: WarpMemory, pointer_position: felt252, size: felt252) -> felt252{
+        // Get the the array location at the current ptr position
+        let array_location = self.read(pointer_position);
+        if array_location != 0 {
+            return array_location;
+        }
+
+        // If no id was found create a new one
+        let array_location = self.alloc(size);
+        self.unsafe_write(pointer_position, array_location);
+        array_location
+    }
+
+    fn new_dynamic_array(ref self: WarpMemory, len: felt252, elem_width: felt252) -> felt252 {
+        // The max space needed for the array in memory
+        // plus one to store the length
+        let len_256 = u256_from_felt252(len);
+        let elem_width_256 = u256{low: u128_try_from_felt252(elem_width).unwrap(), high: 0_u128};
+        let one_256 = u256{low: 1_u128, high: 0_u128};
+
+        let max_size_256 = len_256 * elem_width_256 + one_256;
+
+        let MAX_FELT_256 = u256_from_felt252(-1);
+        if max_size_256 > MAX_FELT_256 {
+           panic_with_felt252('Overflow in new_dynamic_array');
+        }
+
+        let max_size: felt252 =  len * elem_width + 1;
+
+        let array_ptr = self.alloc(max_size);
+        self.unsafe_write(array_ptr, len);
+        array_ptr
+    }
+
+    fn index_dyn(ref self: WarpMemory, array_ptr: felt252, index: felt252, elem_width: felt252) -> felt252 {
+        let length = self.read(array_ptr);
+
+        let index_256 = u256_from_felt252(index);
+        let length_256 = u256_from_felt252(length);
+        if index_256 >= length_256 {
+            panic_with_felt252('Index out of range');
+        } 
+
+        array_ptr + 1 + index * elem_width
+    }
+
+    fn index_static(ref self: WarpMemory, array_ptr: felt252, index: felt252, elem_width: felt252, length: felt252) -> felt252 {
+        let index_256 = u256_from_felt252(index);
+        let length_256 = u256_from_felt252(length);
+        if index_256 >= length_256 {
+             panic_with_felt252('Index out of range');
+        } 
+
+        array_ptr + index * elem_width
+    }
+
+    fn length_dyn(ref self: WarpMemory, array_ptr: felt252) -> felt252 {
+        self.read(array_ptr)
+    }
+}
diff --git a/warplib/src/warp_memory/bytes.cairo b/warplib/src/warp_memory/bytes.cairo
new file mode 100644
index 000000000..10262cefc
--- /dev/null
+++ b/warplib/src/warp_memory/bytes.cairo
@@ -0,0 +1,6 @@
+use warplib::warp_memory::WarpMemory;
+use warplib::warp_memory::WarpMemoryTrait;
+
+trait WarpMemoryBytesTrait{
+    fn bytes_to_fixed_bytes32(ref self: WarpMemory, location: felt252) -> u256;
+}


Test patch:
diff --git a/.github/workflows/test-behaviour.yml b/.github/workflows/test-behaviour.yml
index f5f0ec7fa..777fd2671 100644
--- a/.github/workflows/test-behaviour.yml
+++ b/.github/workflows/test-behaviour.yml
@@ -62,17 +62,19 @@ jobs:
         uses: actions/setup-python@v2.2.2
         with:
           python-version: 3.9 # Cairo was tested with this version
-
       - name: Install dependencies
         run: |
           make
           make compile
 
+      - name: Setup warp_venv
+        run: bin/warp install --python python --verbose
+
       - name: Build warplib
         run: yarn warplib
 
-      - name: Setup warp_venv
-        run: bin/warp install --python python --verbose
+      - name: Test warplib
+        run: yarn test:lib
 
       - name: Running compilation tests
         run: make test_compilation
diff --git a/tests/behaviour/expectations/types.ts b/tests/behaviour/expectations/types.ts
index 2ca07edf0..34a3ff832 100644
--- a/tests/behaviour/expectations/types.ts
+++ b/tests/behaviour/expectations/types.ts
@@ -21,7 +21,7 @@ export class AsyncTest {
   get cairo() {
     return path.join(
       `${OUTPUT_DIR}`,
-      `${mangleContractFilePath(this.name + '.sol', this.contract)}`,
+      `${mangleContractFilePath(this.name + '.sol', this.contract, '.cairo')}`,
     );
   }
   get compiled() {
diff --git a/tests/util.ts b/tests/util.ts
index 7dabe9c12..c1773ef8f 100644
--- a/tests/util.ts
+++ b/tests/util.ts
@@ -6,6 +6,8 @@ import { expect } from 'chai';
 import { hashFilename } from '../src/utils/postCairoWrite';
 import { declare } from './testnetInterface';
 import { AsyncTest, OUTPUT_DIR } from './behaviour/expectations/types';
+import { getPlatform } from '../src/nethersolc';
+import { BASE_PATH } from '../src/starknetCli';
 
 interface AsyncTestCluster {
   asyncTest: AsyncTest;
@@ -49,6 +51,11 @@ export function starknetCompile(
   );
 }
 
+export function cairoTest(cairoProjectPath: string): Promise<{ stdout: string; stderr: string }> {
+  const cairoTestBin = path.resolve(BASE_PATH, 'cairo1', getPlatform(), 'bin', 'cairo-test');
+  return sh(`${cairoTestBin} ${cairoProjectPath}`);
+}
+
 export function batchPromises<In, Out>(
   inputs: In[],
   parallelCount: number,
diff --git a/tests/warplib.test.ts b/tests/warplib.test.ts
index 679289f8f..ebb4c1f66 100644
--- a/tests/warplib.test.ts
+++ b/tests/warplib.test.ts
@@ -1,31 +1,23 @@
 import { expect } from 'chai';
 import { describe, it } from 'mocha';
-import { findCairoSourceFilePaths } from '../src/io';
-import { cleanupSync, SafePromise, starknetCompile, wrapPromise } from './util';
+import path from 'path';
+import { BASE_PATH } from '../src/export';
+import { cairoTest, SafePromise, wrapPromise } from './util';
 
-const paths = findCairoSourceFilePaths('warplib', true);
-describe('Warplib files should compile', function () {
+describe('Warplib files should compile and execute correctly', function () {
   this.timeout(1800000);
 
-  let compileResults: SafePromise<{ stderr: string }>[];
+  it('warplib unit test', async function () {
+    const compileResult: SafePromise<{ stderr: string }> = wrapPromise(
+      cairoTest(path.resolve(BASE_PATH, 'warplib')),
+    );
 
-  before(function () {
-    compileResults = paths.map((file) => wrapPromise(starknetCompile(file, `${file}.json`)));
+    try {
+      const { success, result } = await compileResult;
+      expect(result, 'cairo-test printed errors').to.include({ stderr: '' });
+      expect(success).to.be.true;
+    } catch (e) {
+      expect(false, `${e}`).to.be.true;
+    }
   });
-
-  for (let i = 0; i < paths.length; ++i) {
-    it(paths[i], async function () {
-      try {
-        const { success, result } = await compileResults[i];
-        expect(result, 'starknet-compile printed errors').to.include({ stderr: '' });
-        expect(success).to.be.true;
-      } catch (e) {
-        expect(false, `${e}`).to.be.true;
-      }
-    });
-
-    after(function () {
-      cleanupSync(`${paths[i]}.json`);
-    });
-  }
 });
diff --git a/warplib/src/test.cairo b/warplib/src/test.cairo
new file mode 100644
index 000000000..9f70bccd3
--- /dev/null
+++ b/warplib/src/test.cairo
@@ -0,0 +1,2 @@
+
+mod warp_memory_test;
diff --git a/warplib/src/test/warp_memory_test.cairo b/warplib/src/test/warp_memory_test.cairo
new file mode 100644
index 000000000..f7c2a2305
--- /dev/null
+++ b/warplib/src/test/warp_memory_test.cairo
@@ -0,0 +1,309 @@
+use warplib::warp_memory::WarpMemory;
+use warplib::warp_memory::WarpMemoryTrait;
+use warplib::warp_memory::WarpMemoryImpl;
+
+const MAX_FELT: felt252 = 3618502788666131213697322783095070105623107215331596699973092056135872020480;
+
+// ==================== Basic Functions ====================
+#[test]
+fn test_alloc(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    assert(warp_memory.free_space_pointer == 0, 'Pointer should be 0');
+
+    warp_memory.unsafe_alloc(5);
+    assert(warp_memory.free_space_pointer == 5, 'Pointer should be 5');
+
+    warp_memory.alloc(10);
+    assert(warp_memory.free_space_pointer == 15, 'Pointer should be 15');
+}
+
+#[test]
+#[should_panic]
+fn test_alloc_overflow_should_panic(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+
+    warp_memory.alloc(10);
+
+    // this should crash due to pointer overflow
+    warp_memory.alloc(MAX_FELT);
+
+}
+
+#[test]
+fn test_unsafe_access() {
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    warp_memory.unsafe_write(3, 5); 
+    
+    let read_from_unallocated = warp_memory.unsafe_read(3);
+    assert(read_from_unallocated == 5, 'Invalid read value');
+    assert(warp_memory.free_space_pointer == 0, 'Free space pointer should be 0');
+}
+
+#[test] 
+#[should_panic]
+fn test_unallocated_write_should_panic() {
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    warp_memory.write(3, 5); 
+}
+
+#[test] 
+#[should_panic]
+fn test_unallocated_read_should_panic() {
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    warp_memory.read(3); 
+}
+
+#[test]
+fn test_access(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+
+    let ptr1 = warp_memory.unsafe_alloc(2);
+    assert(ptr1 == 0, 'Allocated mem ptr should be 0');
+    assert(warp_memory.free_space_pointer == 2, 'Free space pointer should be 2');
+
+    warp_memory.write(ptr1, 2); 
+    warp_memory.write(ptr1 + 1, 3); 
+    assert(warp_memory.read(ptr1) == 2, 'Invalid read value on pos 0');
+    assert(warp_memory.read(ptr1 + 1) == 3, 'Invalid read value on pos 1');
+
+    let ptr2 = warp_memory.unsafe_alloc(3);
+    assert(ptr2 == 2, 'Allocated mem ptr should be 2');
+    assert(warp_memory.free_space_pointer == 5, 'Free space pointer should be 5');
+
+    warp_memory.write(ptr2, 4); 
+    warp_memory.write(ptr2 + 1, 5); 
+    warp_memory.write(ptr2 + 2, 6); 
+    assert(warp_memory.read(ptr2) == 4, 'Invalid read value on pos 2');
+    assert(warp_memory.read(ptr2 + 1) == 5, 'Invalid read value on pos 3');
+    assert(warp_memory.read(ptr2 + 2) == 6, 'Invalid read value on pos 4');
+}
+
+// ==================== Complex Functions ====================
+use warplib::warp_memory::WarpMemoryArraysTrait;
+
+#[test]
+fn test_get_or_create_id(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+
+    let ptr1 = warp_memory.unsafe_alloc(2);
+    warp_memory.write(ptr1, 3);
+
+    let read_id1 = warp_memory.get_or_create_id(ptr1, 10);
+    assert(read_id1 == 3, 'Invalid read id value');
+
+    let read_id2 = warp_memory.get_or_create_id(ptr1 + 1, 10);
+    assert(read_id2 == 2, 'Invalid read id value');
+    assert(warp_memory.free_space_pointer == 12, 'Invalid pointer value');
+}
+
+#[test]
+fn test_new_dynamic_array(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+
+    let dyn_array = warp_memory.new_dynamic_array(MAX_FELT-1, 1);
+    assert(warp_memory.free_space_pointer == MAX_FELT, 'Invalid pointer value');
+    assert(warp_memory.read(dyn_array) == MAX_FELT - 1, 'Invalid length value');
+}
+
+#[test]
+#[should_panic]
+fn test_new_dynamic_array_overflow_should_panic(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+
+    let dyn_array = warp_memory.new_dynamic_array(MAX_FELT, 1);
+}
+
+#[test]
+#[should_panic]
+fn test_complex_element_dynamic_array_overflow__should_panic(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+
+    let dyn_array = warp_memory.new_dynamic_array(MAX_FELT - 1, 2);
+}
+
+#[test]
+fn test_index_dyn(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let dyn_array = warp_memory.new_dynamic_array(5, 2);
+    assert(warp_memory.free_space_pointer == 11, 'Invalid pointer value');
+
+    let value_loc = warp_memory.index_dyn(dyn_array, 0, 2);
+    assert(value_loc == 1, 'Invalid index location (1)');
+
+    let value_loc = warp_memory.index_dyn(dyn_array, 1, 2);
+    assert(value_loc == 3, 'Invalid index location (3)');
+
+    let value_loc = warp_memory.index_dyn(dyn_array, 4, 2);
+    assert(value_loc == 9, 'Invalid index location (9)');
+
+    let value_loc = warp_memory.index_dyn(dyn_array, 1, 4);
+    assert(value_loc == 5, 'Invalid index location (5)');
+}
+
+#[test]
+#[should_panic]
+fn test_index_dyn_out_of_range_should_panic(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let dyn_array = warp_memory.new_dynamic_array(5, 2);
+
+    warp_memory.index_dyn(dyn_array, 5, 2);
+}
+
+#[test]
+fn test_index_static(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let length = 10;
+    let static_array = warp_memory.unsafe_alloc(length);
+
+    let value_loc = warp_memory.index_static(static_array, 0, 2, length);
+    assert(value_loc == 0, 'Invalid index location');
+
+    let value_loc = warp_memory.index_static(static_array, 1, 2, length);
+    assert(value_loc == 2, 'Invalid index location');
+
+    let value_loc = warp_memory.index_static(static_array, 4, 2, length);
+    assert(value_loc == 8, 'Invalid index location');
+
+    let value_loc = warp_memory.index_static(static_array, 1, 4, length);
+    assert(value_loc == 4, 'Invalid index location');
+}
+
+#[test]
+#[should_panic]
+fn test_index_static_out_of_range_should_panic(){
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let elem_size = 2;
+    let length = 5;
+    let static_array = warp_memory.unsafe_alloc(length * elem_size);
+
+    warp_memory.index_static(static_array, 5, elem_size, length);
+}
+
+#[test]
+fn test_dyn_length() {
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let dyn_array = warp_memory.new_dynamic_array(5, 3);
+
+    assert(warp_memory.length_dyn(dyn_array) == 5, 'Expected array with length 5');
+}
+
+// ==================== Accessor Functions ====================
+use array::ArrayTrait;
+use integer::u32_to_felt252;
+use option::OptionTrait;
+use warplib::warp_memory::WarpMemoryMultiCellAccessorTrait;
+use warplib::warp_memory::WarpMemoryAccesssorTrait;
+
+#[test]
+#[available_gas(1000000)]
+fn test_access_multiple() {
+    let mut values = ArrayTrait::new();
+    values.append(2);
+    values.append(3);
+    values.append(5);
+
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let pointer = warp_memory.unsafe_alloc(3);
+    
+    warp_memory.write_multiple(pointer, ref values);
+
+    let mut read_values = warp_memory.read_multiple(pointer, u32_to_felt252(values.len()));
+    loop {
+        match gas::withdraw_gas() {
+           Option::Some(_) => {},
+           Option::None(_) => {
+                panic_with_felt252('Out of gas');
+           }
+        }
+
+        if  values.len() == 0 {
+            break();
+        }
+        let val = values.pop_front().unwrap();
+        let read_val = read_values.pop_front().unwrap();
+        assert(val == read_val, 'Values do not match');
+    };
+}
+
+#[test]
+#[available_gas(1000000)]
+#[should_panic]
+fn test_write_multiple_overflow_should_panic() {
+
+    let mut values = ArrayTrait::new();
+    values.append(2);
+    values.append(3);
+    values.append(5);
+
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    warp_memory.unsafe_alloc(MAX_FELT);
+    
+    warp_memory.write_multiple(MAX_FELT - 2, ref values);
+}
+
+#[test]
+#[available_gas(1000000)]
+#[should_panic]
+fn test_write_multiple_unallocated_should_panic() {
+
+    let mut values = ArrayTrait::new();
+    values.append(2);
+    values.append(3);
+    values.append(5);
+
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let pointer = warp_memory.unsafe_alloc(2);
+    
+    warp_memory.write_multiple(pointer, ref values);
+}
+
+#[test]
+#[available_gas(1000000)]
+#[should_panic]
+fn test_read_multiple_overflow_should_panic() {
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    warp_memory.unsafe_alloc(MAX_FELT);
+    warp_memory.read_multiple(MAX_FELT - 2, 3);
+}
+
+#[test]
+#[available_gas(1000000)]
+#[should_panic]
+fn test_read_multiple_unallocated_should_panic() {
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let pointer = warp_memory.unsafe_alloc(2);
+    warp_memory.read_multiple(pointer, 3);
+}
+
+#[test]
+#[available_gas(1000000)]
+fn test_store_retrieve() {
+    let mut warp_memory = WarpMemoryTrait::initialize();
+    let pointer = warp_memory.unsafe_alloc(6);
+    
+    // Store u256
+    let val1 = u256{low: 2, high: 0};
+    warp_memory.store(pointer, val1);
+    let val2 = u256{low: 4, high: 0};
+    warp_memory.store(pointer + 2, val2);
+
+    // Store u128
+    let val3 = 105_u128;
+    warp_memory.store(pointer + 4, val3);
+
+    // Store u8
+    let val4 = 15_u8;
+    warp_memory.store(pointer + 5, val4);
+
+    let readVal1: u256 = warp_memory.retrieve(pointer,  2);
+    assert(readVal1 == val1, 'Incorrect value 1');
+
+    let readVal2: u256 = warp_memory.retrieve(pointer + 2, 2);
+    assert(readVal2 == val2, 'Incorrect value 2');
+
+    let readVal3: u128 = warp_memory.retrieve(pointer + 4, 1);
+    assert(readVal3 == val3, 'Incorrect value 3');
+
+    let readVal4: u8 = warp_memory.retrieve(pointer + 5, 1);
+    assert(readVal4 == val4, 'Incorrect value 4');
+}