From 5392f8bf84167dc27d0623a3b72a9d7497965752 Mon Sep 17 00:00:00 2001 From: Cameron Carstens <54727135+bitzoic@users.noreply.github.com> Date: Tue, 26 Sep 2023 13:03:03 +0200 Subject: [PATCH] Enable storing of a `Vec` with the `StorageVec` type (#5123) ## Description Currently, `StorageVec` and `Vec` do not have any relation with one another. This PR takes advantage of the recent optimization and refactoring done to the Storage API `read()` and `write()` functions in https://github.com/FuelLabs/sway/pull/4795 and enables the storing of a `Vec` using the `StorageVec` type. To do this, the `StorageVec` now stores things sequentially rather than a different key for every element. Due to the optimizations done to the Storage API, this has become feasible as we can now load a single element of the sequential `StorageVec`. The storing of elements sequentially mimics the existing `Vec`, allowing us to store it as a `raw_slice` with a ***single*** read/write to storage as opposed to looping over the `Vec` and having ***n*** read/writes. It should be noted that due to https://github.com/FuelLabs/sway/issues/409, the storing of a `Vec` is written in the `StorageVec` file. This is the resulting syntax: ```sway let my_vec = Vec::new(); storage.storage_vec.store_vec(my_vec); // Store the Vec let other_vec = storage.storage_vec.load_vec(); // Read the Vec ``` When this issue is resolved, this should be changed to a `From` implementation changing the syntax to: ```sway let my_vec = Vec::new(); storage.storage_vec = my_vec.into(); // Store the Vec let other_vec = Vec::from(storage.storage_vec); // Read the Vec ``` Closes https://github.com/FuelLabs/sway/issues/2439 ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [ ] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: bitzoic --- sway-lib-std/src/storage/storage_vec.sw | 236 ++++++++-- test/src/sdk-harness/Forc.lock | 439 +++++++++--------- test/src/sdk-harness/Forc.toml | 1 + test/src/sdk-harness/test_projects/harness.rs | 1 + .../storage_vec_to_vec/Forc.toml | 8 + .../test_projects/storage_vec_to_vec/mod.rs | 157 +++++++ .../storage_vec_to_vec/src/main.sw | 211 +++++++++ 7 files changed, 793 insertions(+), 260 deletions(-) create mode 100644 test/src/sdk-harness/test_projects/storage_vec_to_vec/Forc.toml create mode 100644 test/src/sdk-harness/test_projects/storage_vec_to_vec/mod.rs create mode 100644 test/src/sdk-harness/test_projects/storage_vec_to_vec/src/main.sw diff --git a/sway-lib-std/src/storage/storage_vec.sw b/sway-lib-std/src/storage/storage_vec.sw index 4243b12268c..d90a189d664 100644 --- a/sway-lib-std/src/storage/storage_vec.sw +++ b/sway-lib-std/src/storage/storage_vec.sw @@ -1,11 +1,12 @@ library; -use ::alloc::alloc; +use ::alloc::{alloc_bytes, realloc_bytes}; use ::assert::assert; use ::hash::*; use ::option::Option::{self, *}; use ::storage::storage_api::*; use ::storage::storage_key::*; +use ::vec::Vec; /// A persistant vector struct. pub struct StorageVec {} @@ -42,8 +43,9 @@ impl StorageKey> { let len = read::(self.field_id, 0).unwrap_or(0); // Storing the value at the current length index (if this is the first item, starts off at 0) - let key = sha256((len, self.field_id)); - write::(key, 0, value); + let key = sha256(self.field_id); + let offset = offset_calculator::(len); + write::(key, offset, value); // Incrementing the length write(self.field_id, 0, len + 1); @@ -90,8 +92,9 @@ impl StorageKey> { // reduces len by 1, effectively removing the last item in the vec write(self.field_id, 0, len - 1); - let key = sha256((len - 1, self.field_id)); - read::(key, 0) + let key = sha256(self.field_id); + let offset = offset_calculator::(len - 1); + read::(key, offset) } /// Gets the value in the given index, `None` if index is out of bounds. @@ -134,10 +137,15 @@ impl StorageKey> { return None; } + let key = sha256(self.field_id); + let offset = offset_calculator::(index); + // This StorageKey can be read by the standard storage api. + // Field Id must be unique such that nested storage vecs work as they have a + // __size_of() zero and will there forefore always have an offset of zero. Some(StorageKey::::new( - sha256((index, self.field_id)), - 0, - sha256((index, self.field_id)) + key, + offset, + sha256((index, key)) )) } @@ -191,16 +199,19 @@ impl StorageKey> { assert(index < len); // gets the element before removing it, so it can be returned - let removed_element = read::(sha256((index, self.field_id)), 0).unwrap(); + let key = sha256(self.field_id); + let removed_offset = offset_calculator::(index); + let removed_element = read::(key, removed_offset).unwrap(); // for every element in the vec with an index greater than the input index, // shifts the index for that element down one let mut count = index + 1; while count < len { - // gets the storage location for the previous index - let key = sha256((count - 1, self.field_id)); + // gets the storage location for the previous index and // moves the element of the current index into the previous index - write::(key, 0, read::(sha256((count, self.field_id)), 0).unwrap()); + let write_offset = offset_calculator::(count - 1); + let read_offset = offset_calculator::(count); + write::(key, write_offset, read::(key, read_offset).unwrap()); count += 1; } @@ -257,12 +268,15 @@ impl StorageKey> { // if the index is larger or equal to len, there is no item to remove assert(index < len); - let hash_of_to_be_removed = sha256((index, self.field_id)); + let key = sha256(self.field_id); // gets the element before removing it, so it can be returned - let element_to_be_removed = read::(hash_of_to_be_removed, 0).unwrap(); + let element_offset = offset_calculator::(index); + let element_to_be_removed = read::(key, element_offset).unwrap(); - let last_element = read::(sha256((len - 1, self.field_id)), 0).unwrap(); - write::(hash_of_to_be_removed, 0, last_element); + let last_offset = offset_calculator::(len - 1); + let last_element = read::(key, last_offset).unwrap(); + + write::(key, element_offset, last_element); // decrements len by 1 write(self.field_id, 0, len - 1); @@ -312,8 +326,9 @@ impl StorageKey> { // if the index is higher than or equal len, there is no element to set assert(index < len); - let key = sha256((index, self.field_id)); - write::(key, 0, value); + let key = sha256(self.field_id); + let offset = offset_calculator::(index); + write::(key, offset, value); } /// Inserts the value at the given index, moving the current index's value @@ -365,9 +380,10 @@ impl StorageKey> { assert(index <= len); // if len is 0, index must also be 0 due to above check + let key = sha256(self.field_id); if len == index { - let key = sha256((index, self.field_id)); - write::(key, 0, value); + let offset = offset_calculator::(index); + write::(key, offset, value); // increments len by 1 write(self.field_id, 0, len + 1); @@ -380,17 +396,18 @@ impl StorageKey> { // performed in reverse to prevent data overwriting let mut count = len - 1; while count >= index { - let key = sha256((count + 1, self.field_id)); // shifts all the values up one index - write::(key, 0, read::(sha256((count, self.field_id)), 0).unwrap()); + let write_offset = offset_calculator::(count + 1); + let read_offset = offset_calculator::(count); + write::(key, write_offset, read::(key, read_offset).unwrap()); if count == 0 { break; } count -= 1; } // inserts the value into the now unused index - let key = sha256((index, self.field_id)); - write::(key, 0, value); + let offset = offset_calculator::(index); + write::(key, offset, value); // increments len by 1 write(self.field_id, 0, len + 1); @@ -537,12 +554,14 @@ impl StorageKey> { return; } - let element1_key = sha256((element1_index, self.field_id)); - let element2_key = sha256((element2_index, self.field_id)); + let key = sha256(self.field_id); + let element1_offset = offset_calculator::(element1_index); + let element2_offset = offset_calculator::(element2_index); + + let element1_value = read::(key, element1_offset).unwrap(); - let element1_value = read::(element1_key, 0).unwrap(); - write::(element1_key, 0, read::(element2_key, 0).unwrap()); - write::(element2_key, 0, element1_value); + write::(key, element1_offset, read::(key, element2_offset).unwrap()); + write::(key, element2_offset, element1_value); } /// Returns the first element of the vector, or `None` if it is empty. @@ -573,12 +592,13 @@ impl StorageKey> { /// ``` #[storage(read)] pub fn first(self) -> Option> { + let key = sha256(self.field_id); match read::(self.field_id, 0).unwrap_or(0) { 0 => None, _ => Some(StorageKey::::new( - sha256((0, self.field_id)), + key, 0, - sha256((0, self.field_id)) + sha256((0, key)) )), } } @@ -612,13 +632,17 @@ impl StorageKey> { /// ``` #[storage(read)] pub fn last(self) -> Option> { + let key = sha256(self.field_id); match read::(self.field_id, 0).unwrap_or(0) { 0 => None, - len => Some(StorageKey::::new( - sha256((len - 1, self.field_id)), - 0, - sha256((0, self.field_id)) - )), + len => { + let offset = offset_calculator::(len - 1); + Some(StorageKey::::new( + key, + offset, + sha256((len - 1, key)) + )) + }, } } @@ -655,15 +679,17 @@ impl StorageKey> { return; } + let key = sha256(self.field_id); let mid = len / 2; let mut i = 0; while i < mid { - let element1_key = sha256((i, self.field_id)); - let element2_key = sha256((len - i - 1, self.field_id)); + let i_offset = offset_calculator::(i); + let other_offset = offset_calculator::(len - i - 1); - let element1_value = read::(element1_key, 0).unwrap(); - write::(element1_key, 0, read::(element2_key, 0).unwrap()); - write::(element2_key, 0, element1_value); + let element1_value = read::(key, i_offset).unwrap(); + + write::(key, i_offset, read::(key, other_offset).unwrap()); + write::(key, other_offset, element1_value); i += 1; } @@ -702,9 +728,11 @@ impl StorageKey> { pub fn fill(self, value: V) { let len = read::(self.field_id, 0).unwrap_or(0); + let key = sha256(self.field_id); let mut i = 0; while i < len { - write::(sha256((i, self.field_id)), 0, value); + let offset = offset_calculator::(i); + write::(key, offset, value); i += 1; } } @@ -755,10 +783,132 @@ impl StorageKey> { #[storage(read, write)] pub fn resize(self, new_len: u64, value: V) { let mut len = read::(self.field_id, 0).unwrap_or(0); + let key = sha256(self.field_id); while len < new_len { - write::(sha256((len, self.field_id)), 0, value); + let offset = offset_calculator::(len); + write::(key, offset, value); len += 1; } write::(self.field_id, 0, new_len); } + + // TODO: This should be moved into the vec.sw file and `From> for Vec` + // implemented instead of this when https://github.com/FuelLabs/sway/issues/409 is resolved. + // Implementation will change from this: + // ```sway + // let my_vec = Vec::new(); + // storage.storage_vec.store_vec(my_vec); + // let other_vec = storage.storage_vec.load_vec(); + // ``` + // To this: + // ```sway + // let my_vec = Vec::new(); + // storage.storage_vec = my_vec.into(); + // let other_vec = Vec::from(storage.storage_vec); + // ``` + /// Stores a `Vec` as a `StorageVec`. + /// + /// # Additional Information + /// + /// This will overwrite any existing values in the `StorageVec`. + /// + /// # Arguments + /// + /// * `vec`: [Vec] - The vector to store in storage. + /// + /// # Number of Storage Accesses + /// + /// * Writes - `2` + /// + /// # Examples + /// + /// ```sway + /// storage { + /// vec: StorageVec = StorageVec {}, + /// } + /// + /// fn foo() { + /// let mut vec = Vec::::new(); + /// vec.push(5); + /// vec.push(10); + /// vec.push(15); + /// + /// storage.vec.store_vec(vec); + /// + /// assert(5 == storage.vec.get(0).unwrap()); + /// assert(10 == storage.vec.get(1).unwrap()); + /// assert(15 == storage.vec.get(2).unwrap()); + /// } + /// ``` + #[storage(write)] + pub fn store_vec(self, vec: Vec) { + let slice = vec.as_raw_slice(); + // Get the number of storage slots needed based on the size of bytes. + let number_of_bytes = slice.number_of_bytes(); + let number_of_slots = (number_of_bytes + 31) >> 5; + let mut ptr = slice.ptr(); + + // The capacity needs to be a multiple of 32 bytes so we can + // make the 'quad' storage instruction store without accessing unallocated heap memory. + ptr = realloc_bytes(ptr, number_of_bytes, number_of_slots * 32); + + // Store `number_of_slots * 32` bytes starting at storage slot `key`. + let _ = __state_store_quad(sha256(self.field_id), ptr, number_of_slots); + + // Store the length, NOT the bytes. + // This differs from the existing `write_slice()` function to be compatible with `StorageVec`. + write::(self.field_id, 0, number_of_bytes / __size_of::()); + } + + /// Load a `Vec` from the `StorageVec`. + /// + /// # Returns + /// + /// * [Option>] - The vector constructed from storage or `None`. + /// + /// # Number of Storage Accesses + /// + /// * Reads - `2` + /// + /// # Examples + /// + /// ```sway + /// storage { + /// vec: StorageVec = StorageVec {}, + /// } + /// + /// fn foo() { + /// let mut vec = Vec::::new(); + /// vec.push(5); + /// vec.push(10); + /// vec.push(15); + /// + /// storage.vec.store_vec(vec); + /// let returned_vec = storage.vec.load_vec(); + /// + /// assert(5 == returned_vec.get(0).unwrap()); + /// assert(10 == returned_vec.get(1).unwrap()); + /// assert(15 == returned_vec.get(2).unwrap()); + /// } + /// ``` + #[storage(read)] + pub fn load_vec(self) -> Vec { + // Get the length of the slice that is stored. + match read::(self.field_id, 0).unwrap_or(0) { + 0 => Vec::new(), + len => { + // Get the number of storage slots needed based on the size. + let bytes = len * __size_of::(); + let number_of_slots = (bytes + 31) >> 5; + let ptr = alloc_bytes(number_of_slots * 32); + // Load the stored slice into the pointer. + let _ = __state_load_quad(sha256(self.field_id), ptr, number_of_slots); + Vec::from(asm(ptr: (ptr, bytes)) { ptr: raw_slice }) + } + } + } +} + +fn offset_calculator(offset: u64) -> u64 { + (offset * __size_of::()) / 8 } diff --git a/test/src/sdk-harness/Forc.lock b/test/src/sdk-harness/Forc.lock index 8d7d6db9be4..733146cf2f2 100644 --- a/test/src/sdk-harness/Forc.lock +++ b/test/src/sdk-harness/Forc.lock @@ -1,376 +1,381 @@ [[package]] -name = 'abi_impl_methods_callable' -source = 'member' +name = "abi_impl_methods_callable" +source = "member" [[package]] -name = 'auth_caller_contract' -source = 'member' +name = "auth_caller_contract" +source = "member" dependencies = [ - 'auth_testing_abi', - 'std', + "auth_testing_abi", + "std", ] [[package]] -name = 'auth_caller_script' -source = 'member' +name = "auth_caller_script" +source = "member" dependencies = [ - 'auth_testing_abi', - 'std', + "auth_testing_abi", + "std", ] [[package]] -name = 'auth_testing_abi' -source = 'member' -dependencies = ['std'] +name = "auth_testing_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'auth_testing_contract' -source = 'member' +name = "auth_testing_contract" +source = "member" dependencies = [ - 'auth_testing_abi', - 'std', + "auth_testing_abi", + "std", ] [[package]] -name = 'balance_contract' -source = 'member' -dependencies = ['std'] +name = "balance_contract" +source = "member" +dependencies = ["std"] [[package]] -name = 'block' -source = 'member' +name = "block" +source = "member" dependencies = [ - 'block_test_abi', - 'std', + "block_test_abi", + "std", ] [[package]] -name = 'block_test_abi' -source = 'member' -dependencies = ['std'] +name = "block_test_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'call_frames' -source = 'member' +name = "call_frames" +source = "member" dependencies = [ - 'call_frames_test_abi', - 'std', + "call_frames_test_abi", + "std", ] [[package]] -name = 'call_frames_test_abi' -source = 'member' -dependencies = ['std'] +name = "call_frames_test_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'configurables_in_contract' -source = 'member' -dependencies = ['std'] +name = "configurables_in_contract" +source = "member" +dependencies = ["std"] [[package]] -name = 'configurables_in_script' -source = 'member' -dependencies = ['std'] +name = "configurables_in_script" +source = "member" +dependencies = ["std"] [[package]] -name = 'context' -source = 'member' +name = "context" +source = "member" dependencies = [ - 'context_testing_abi', - 'std', + "context_testing_abi", + "std", ] [[package]] -name = 'context_caller_contract' -source = 'member' +name = "context_caller_contract" +source = "member" dependencies = [ - 'context_testing_abi', - 'std', + "context_testing_abi", + "std", ] [[package]] -name = 'context_testing_abi' -source = 'member' -dependencies = ['std'] +name = "context_testing_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'contract_bytecode' -source = 'member' -dependencies = ['std'] +name = "contract_bytecode" +source = "member" +dependencies = ["std"] [[package]] -name = 'core' -source = 'path+from-root-5781B31F5E4458CC' +name = "core" +source = "path+from-root-5781B31F5E4458CC" [[package]] -name = 'ec_recover' -source = 'member' -dependencies = ['std'] +name = "ec_recover" +source = "member" +dependencies = ["std"] [[package]] -name = 'ec_recover_and_match_predicate' -source = 'member' -dependencies = ['std'] +name = "ec_recover_and_match_predicate" +source = "member" +dependencies = ["std"] [[package]] -name = 'evm' -source = 'member' +name = "evm" +source = "member" dependencies = [ - 'evm_test_abi', - 'std', + "evm_test_abi", + "std", ] [[package]] -name = 'evm_ec_recover' -source = 'member' -dependencies = ['std'] +name = "evm_ec_recover" +source = "member" +dependencies = ["std"] [[package]] -name = 'evm_test_abi' -source = 'member' -dependencies = ['std'] +name = "evm_test_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'generics_in_abi' -source = 'member' -dependencies = ['std'] +name = "generics_in_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'hashing' -source = 'member' -dependencies = ['std'] +name = "hashing" +source = "member" +dependencies = ["std"] [[package]] -name = 'logging' -source = 'member' -dependencies = ['std'] +name = "logging" +source = "member" +dependencies = ["std"] [[package]] -name = 'messages' -source = 'member' -dependencies = ['std'] +name = "messages" +source = "member" +dependencies = ["std"] [[package]] -name = 'methods_abi' -source = 'member' +name = "methods_abi" +source = "member" [[package]] -name = 'methods_contract' -source = 'member' +name = "methods_contract" +source = "member" dependencies = [ - 'methods_abi', - 'std', + "methods_abi", + "std", ] [[package]] -name = 'option_field_order' -source = 'member' -dependencies = ['std'] +name = "option_field_order" +source = "member" +dependencies = ["std"] [[package]] -name = 'option_in_abi' -source = 'member' -dependencies = ['std'] +name = "option_in_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'parsing_logs' -source = 'member' +name = "parsing_logs" +source = "member" dependencies = [ - 'parsing_logs_test_abi', - 'std', + "parsing_logs_test_abi", + "std", ] [[package]] -name = 'parsing_logs_test_abi' -source = 'member' -dependencies = ['std'] +name = "parsing_logs_test_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'pow' -source = 'member' -dependencies = ['std'] +name = "pow" +source = "member" +dependencies = ["std"] [[package]] -name = 'predicate_data_simple' -source = 'member' -dependencies = ['std'] +name = "predicate_data_simple" +source = "member" +dependencies = ["std"] [[package]] -name = 'predicate_data_struct' -source = 'member' -dependencies = ['std'] +name = "predicate_data_struct" +source = "member" +dependencies = ["std"] [[package]] -name = 'registers' -source = 'member' -dependencies = ['std'] +name = "registers" +source = "member" +dependencies = ["std"] [[package]] -name = 'result_in_abi' -source = 'member' -dependencies = ['std'] +name = "result_in_abi" +source = "member" +dependencies = ["std"] [[package]] -name = 'script_bytecode' -source = 'member' -dependencies = ['std'] +name = "script_bytecode" +source = "member" +dependencies = ["std"] [[package]] -name = 'script_data' -source = 'member' -dependencies = ['std'] +name = "script_data" +source = "member" +dependencies = ["std"] [[package]] -name = 'std' -source = 'path+from-root-5781B31F5E4458CC' -dependencies = ['core'] +name = "std" +source = "path+from-root-5781B31F5E4458CC" +dependencies = ["core"] [[package]] -name = 'storage' -source = 'member' -dependencies = ['std'] +name = "storage" +source = "member" +dependencies = ["std"] [[package]] -name = 'storage_access' -source = 'member' -dependencies = ['std'] +name = "storage_access" +source = "member" +dependencies = ["std"] [[package]] -name = 'storage_bytes' -source = 'member' -dependencies = ['std'] +name = "storage_bytes" +source = "member" +dependencies = ["std"] [[package]] -name = 'storage_init' -source = 'member' -dependencies = ['std'] +name = "storage_init" +source = "member" +dependencies = ["std"] [[package]] -name = 'storage_map' -source = 'member' -dependencies = ['std'] +name = "storage_map" +source = "member" +dependencies = ["std"] [[package]] -name = 'storage_map_nested' -source = 'member' -dependencies = ['std'] +name = "storage_map_nested" +source = "member" +dependencies = ["std"] [[package]] -name = 'storage_string' -source = 'member' -dependencies = ['std'] +name = "storage_string" +source = "member" +dependencies = ["std"] [[package]] -name = 'storage_vec_nested' -source = 'member' -dependencies = ['std'] +name = "storage_vec_nested" +source = "member" +dependencies = ["std"] [[package]] -name = 'superabi' -source = 'member' -dependencies = ['std'] +name = "storage_vec_to_vec" +source = "member" +dependencies = ["std"] [[package]] -name = 'superabi_supertrait' -source = 'member' -dependencies = ['std'] +name = "superabi" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_array' -source = 'member' -dependencies = ['std'] +name = "superabi_supertrait" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_b256' -source = 'member' -dependencies = ['std'] +name = "svec_array" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_bool' -source = 'member' -dependencies = ['std'] +name = "svec_b256" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_enum' -source = 'member' -dependencies = ['std'] +name = "svec_bool" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_str' -source = 'member' -dependencies = ['std'] +name = "svec_enum" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_struct' -source = 'member' -dependencies = ['std'] +name = "svec_str" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_tuple' -source = 'member' -dependencies = ['std'] +name = "svec_struct" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_u16' -source = 'member' -dependencies = ['std'] +name = "svec_tuple" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_u32' -source = 'member' -dependencies = ['std'] +name = "svec_u16" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_u64' -source = 'member' -dependencies = ['std'] +name = "svec_u32" +source = "member" +dependencies = ["std"] [[package]] -name = 'svec_u8' -source = 'member' -dependencies = ['std'] +name = "svec_u64" +source = "member" +dependencies = ["std"] [[package]] -name = 'test_contract' -source = 'member' -dependencies = ['std'] +name = "svec_u8" +source = "member" +dependencies = ["std"] [[package]] -name = 'test_script' -source = 'member' -dependencies = ['std'] +name = "test_contract" +source = "member" +dependencies = ["std"] [[package]] -name = 'test_script_bytes' -source = 'member' -dependencies = ['std'] +name = "test_script" +source = "member" +dependencies = ["std"] [[package]] -name = 'token_ops' -source = 'member' -dependencies = ['std'] +name = "test_script_bytes" +source = "member" +dependencies = ["std"] [[package]] -name = 'tx_contract' -source = 'member' -dependencies = ['std'] +name = "token_ops" +source = "member" +dependencies = ["std"] [[package]] -name = 'tx_output_predicate' -source = 'member' -dependencies = ['std'] +name = "tx_contract" +source = "member" +dependencies = ["std"] [[package]] -name = 'tx_predicate' -source = 'member' -dependencies = ['std'] +name = "tx_output_predicate" +source = "member" +dependencies = ["std"] [[package]] -name = 'type_aliases' -source = 'member' -dependencies = ['std'] +name = "tx_predicate" +source = "member" +dependencies = ["std"] [[package]] -name = 'vec_in_abi' -source = 'member' -dependencies = ['std'] +name = "type_aliases" +source = "member" +dependencies = ["std"] + +[[package]] +name = "vec_in_abi" +source = "member" +dependencies = ["std"] diff --git a/test/src/sdk-harness/Forc.toml b/test/src/sdk-harness/Forc.toml index e4edd9b55c3..622bfa856aa 100644 --- a/test/src/sdk-harness/Forc.toml +++ b/test/src/sdk-harness/Forc.toml @@ -34,6 +34,7 @@ members = [ "test_projects/storage_map_nested", "test_projects/storage_string", "test_projects/storage_vec_nested", + "test_projects/storage_vec_to_vec", "test_projects/superabi", "test_projects/superabi_supertrait", "test_projects/token_ops", diff --git a/test/src/sdk-harness/test_projects/harness.rs b/test/src/sdk-harness/test_projects/harness.rs index 7c9b31253c9..39fbc015420 100644 --- a/test/src/sdk-harness/test_projects/harness.rs +++ b/test/src/sdk-harness/test_projects/harness.rs @@ -37,6 +37,7 @@ mod storage_map_nested; mod storage_string; mod storage_vec; mod storage_vec_nested; +mod storage_vec_to_vec; mod superabi; mod superabi_supertrait; mod token_ops; diff --git a/test/src/sdk-harness/test_projects/storage_vec_to_vec/Forc.toml b/test/src/sdk-harness/test_projects/storage_vec_to_vec/Forc.toml new file mode 100644 index 00000000000..0841b7ac887 --- /dev/null +++ b/test/src/sdk-harness/test_projects/storage_vec_to_vec/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "storage_vec_to_vec" + +[dependencies] +std = { path = "../../../../../sway-lib-std" } diff --git a/test/src/sdk-harness/test_projects/storage_vec_to_vec/mod.rs b/test/src/sdk-harness/test_projects/storage_vec_to_vec/mod.rs new file mode 100644 index 00000000000..af78a9b2004 --- /dev/null +++ b/test/src/sdk-harness/test_projects/storage_vec_to_vec/mod.rs @@ -0,0 +1,157 @@ +use fuels::accounts::wallet::WalletUnlocked; +use fuels::prelude::*; + +abigen!(Contract( + name = "TestStorageVecToVecContract", + abi = "test_projects/storage_vec_to_vec/out/debug/storage_vec_to_vec-abi.json", +)); + +async fn test_storage_vec_to_vec_instance() -> TestStorageVecToVecContract { + let wallet = launch_provider_and_get_wallet().await; + let id = Contract::load_from( + "test_projects/storage_vec_to_vec/out/debug/storage_vec_to_vec.bin", + LoadConfiguration::default(), + ) + .unwrap() + .deploy(&wallet, TxParameters::default()) + .await + .unwrap(); + + TestStorageVecToVecContract::new(id.clone(), wallet) +} + + +#[tokio::test] +async fn test_conversion_u64() { + let instance = test_storage_vec_to_vec_instance().await; + + let mut test_vec = Vec::::new(); + test_vec.push(5u64); + test_vec.push(7u64); + test_vec.push(9u64); + test_vec.push(11u64); + + let _ = instance.methods().store_vec_u64(test_vec).call().await; + + let returned_vec = instance.methods().read_vec_u64().call().await.unwrap().value; + + assert_eq!(returned_vec.len(), 4); + assert_eq!(*returned_vec.get(0).unwrap(), 5u64); + assert_eq!(*returned_vec.get(1).unwrap(), 7u64); + assert_eq!(*returned_vec.get(2).unwrap(), 9u64); + assert_eq!(*returned_vec.get(3).unwrap(), 11u64); +} + +#[tokio::test] +async fn test_push_u64() { + let instance = test_storage_vec_to_vec_instance().await; + + let mut test_vec = Vec::::new(); + test_vec.push(5u64); + test_vec.push(7u64); + test_vec.push(9u64); + test_vec.push(11u64); + + let _ = instance.methods().store_vec_u64(test_vec).call().await; + + let _ = instance.methods().push_vec_u64(13u64).call().await; + + let returned_vec = instance.methods().read_vec_u64().call().await.unwrap().value; + + assert_eq!(returned_vec.len(), 5); + assert_eq!(*returned_vec.get(0).unwrap(), 5u64); + assert_eq!(*returned_vec.get(1).unwrap(), 7u64); + assert_eq!(*returned_vec.get(2).unwrap(), 9u64); + assert_eq!(*returned_vec.get(3).unwrap(), 11u64); + assert_eq!(*returned_vec.get(4).unwrap(), 13u64); +} + +#[tokio::test] +async fn test_pop_u64() { + let instance = test_storage_vec_to_vec_instance().await; + + let mut test_vec = Vec::::new(); + test_vec.push(5u64); + test_vec.push(7u64); + test_vec.push(9u64); + test_vec.push(11u64); + + let _ = instance.methods().store_vec_u64(test_vec).call().await; + + assert_eq!(11u64, instance.methods().pop_vec_u64().call().await.unwrap().value); + + let returned_vec = instance.methods().read_vec_u64().call().await.unwrap().value; + + assert_eq!(returned_vec.len(), 3); + assert_eq!(*returned_vec.get(0).unwrap(), 5u64); + assert_eq!(*returned_vec.get(1).unwrap(), 7u64); + assert_eq!(*returned_vec.get(2).unwrap(), 9u64); +} + + +#[tokio::test] +async fn test_conversion_struct() { + let instance = test_storage_vec_to_vec_instance().await; + + let mut test_vec = Vec::::new(); + test_vec.push(TestStruct{val_1: 0u64, val_2: 1u64, val_3: 2u64}); + test_vec.push(TestStruct{val_1: 1u64, val_2: 2u64, val_3: 3u64}); + test_vec.push(TestStruct{val_1: 2u64, val_2: 3u64, val_3: 4u64}); + test_vec.push(TestStruct{val_1: 3u64, val_2: 4u64, val_3: 5u64}); + + let _ = instance.methods().store_vec_struct(test_vec).call().await; + + let returned_vec = instance.methods().read_vec_struct().call().await.unwrap().value; + + assert_eq!(returned_vec.len(), 4); + assert_eq!(*returned_vec.get(0).unwrap(), TestStruct{val_1: 0u64, val_2: 1u64, val_3: 2u64}); + assert_eq!(*returned_vec.get(1).unwrap(), TestStruct{val_1: 1u64, val_2: 2u64, val_3: 3u64}); + assert_eq!(*returned_vec.get(2).unwrap(), TestStruct{val_1: 2u64, val_2: 3u64, val_3: 4u64}); + assert_eq!(*returned_vec.get(3).unwrap(), TestStruct{val_1: 3u64, val_2: 4u64, val_3: 5u64}); +} + +#[tokio::test] +async fn test_push_struct() { + let instance = test_storage_vec_to_vec_instance().await; + + let mut test_vec = Vec::::new(); + test_vec.push(TestStruct{val_1: 0u64, val_2: 1u64, val_3: 2u64}); + test_vec.push(TestStruct{val_1: 1u64, val_2: 2u64, val_3: 3u64}); + test_vec.push(TestStruct{val_1: 2u64, val_2: 3u64, val_3: 4u64}); + test_vec.push(TestStruct{val_1: 3u64, val_2: 4u64, val_3: 5u64}); + + let _ = instance.methods().store_vec_struct(test_vec).call().await; + + let _ = instance.methods().push_vec_struct(TestStruct{val_1: 4u64, val_2: 5u64, val_3: 6u64}).call().await; + + let returned_vec = instance.methods().read_vec_struct().call().await.unwrap().value; + + assert_eq!(returned_vec.len(), 5); + assert_eq!(*returned_vec.get(0).unwrap(), TestStruct{val_1: 0u64, val_2: 1u64, val_3: 2u64}); + assert_eq!(*returned_vec.get(1).unwrap(), TestStruct{val_1: 1u64, val_2: 2u64, val_3: 3u64}); + assert_eq!(*returned_vec.get(2).unwrap(), TestStruct{val_1: 2u64, val_2: 3u64, val_3: 4u64}); + assert_eq!(*returned_vec.get(3).unwrap(), TestStruct{val_1: 3u64, val_2: 4u64, val_3: 5u64}); + assert_eq!(*returned_vec.get(4).unwrap(), TestStruct{val_1: 4u64, val_2: 5u64, val_3: 6u64}); +} + +#[tokio::test] +async fn test_pop_struct() { + let instance = test_storage_vec_to_vec_instance().await; + + let mut test_vec = Vec::::new(); + test_vec.push(TestStruct{val_1: 0u64, val_2: 1u64, val_3: 2u64}); + test_vec.push(TestStruct{val_1: 1u64, val_2: 2u64, val_3: 3u64}); + test_vec.push(TestStruct{val_1: 2u64, val_2: 3u64, val_3: 4u64}); + test_vec.push(TestStruct{val_1: 3u64, val_2: 4u64, val_3: 5u64}); + + let _ = instance.methods().store_vec_struct(test_vec).call().await; + + assert_eq!(TestStruct{val_1: 3u64, val_2: 4u64, val_3: 5u64}, instance.methods().pop_vec_struct().call().await.unwrap().value); + + let returned_vec = instance.methods().read_vec_struct().call().await.unwrap().value; + + assert_eq!(returned_vec.len(), 3); + assert_eq!(*returned_vec.get(0).unwrap(), TestStruct{val_1: 0u64, val_2: 1u64, val_3: 2u64}); + assert_eq!(*returned_vec.get(1).unwrap(), TestStruct{val_1: 1u64, val_2: 2u64, val_3: 3u64}); + assert_eq!(*returned_vec.get(2).unwrap(), TestStruct{val_1: 2u64, val_2: 3u64, val_3: 4u64}); +} diff --git a/test/src/sdk-harness/test_projects/storage_vec_to_vec/src/main.sw b/test/src/sdk-harness/test_projects/storage_vec_to_vec/src/main.sw new file mode 100644 index 00000000000..8e9f9ef1635 --- /dev/null +++ b/test/src/sdk-harness/test_projects/storage_vec_to_vec/src/main.sw @@ -0,0 +1,211 @@ +contract; + +use std::storage::storage_vec::*; + +pub struct TestStruct { + val1: u64, + val2: u64, + val3: u64 +} + +impl Eq for TestStruct { + fn eq(self, other: Self) -> bool { + self.val1 == other.val1 && self.val2 == other.val2 && self.val3 == other.val3 + } +} + +storage { + storage_vec_u64: StorageVec = StorageVec {}, + storage_vec_struct: StorageVec = StorageVec {}, +} + +abi VecToVecStorageTest { + #[storage(read, write)] + fn store_vec_u64(vec: Vec); + #[storage(read)] + fn read_vec_u64() -> Vec; + #[storage(read, write)] + fn push_vec_u64(val: u64); + #[storage(read, write)] + fn pop_vec_u64() -> u64; + #[storage(read, write)] + fn store_vec_struct(vec: Vec); + #[storage(read)] + fn read_vec_struct() -> Vec; + #[storage(read, write)] + fn push_vec_struct(val: TestStruct); + #[storage(read, write)] + fn pop_vec_struct() -> TestStruct; +} + +impl VecToVecStorageTest for Contract { + #[storage(read, write)] + fn store_vec_u64(vec: Vec) { + storage.storage_vec_u64.store_vec(vec); + } + + #[storage(read)] + fn read_vec_u64() -> Vec { + storage.storage_vec_u64.load_vec() + } + + #[storage(read, write)] + fn push_vec_u64(val: u64) { + storage.storage_vec_u64.push(val); + } + + #[storage(read, write)] + fn pop_vec_u64() -> u64 { + storage.storage_vec_u64.pop().unwrap_or(0) + } + + #[storage(read, write)] + fn store_vec_struct(vec: Vec) { + storage.storage_vec_struct.store_vec(vec); + } + + #[storage(read)] + fn read_vec_struct() -> Vec { + storage.storage_vec_struct.load_vec() + } + + #[storage(read, write)] + fn push_vec_struct(val: TestStruct) { + storage.storage_vec_struct.push(val); + } + + #[storage(read, write)] + fn pop_vec_struct() -> TestStruct { + storage.storage_vec_struct.pop().unwrap_or(TestStruct{val1: 0, val2: 0, val3: 0}) + } +} + +#[test] +fn test_conversion_u64() { + let vec_abi = abi(VecToVecStorageTest, CONTRACT_ID); + let mut test_vec = Vec::::new(); + test_vec.push(5); + test_vec.push(7); + test_vec.push(9); + test_vec.push(11); + + vec_abi.store_vec_u64(test_vec); + + let returned_vec = vec_abi.read_vec_u64(); + + assert(returned_vec.len() == 4); + assert(returned_vec.get(0).unwrap() == 5); + assert(returned_vec.get(1).unwrap() == 7); + assert(returned_vec.get(2).unwrap() == 9); + assert(returned_vec.get(3).unwrap() == 11); +} + +#[test] +fn test_push_u64() { + let vec_abi = abi(VecToVecStorageTest, CONTRACT_ID); + let mut test_vec = Vec::::new(); + test_vec.push(5); + test_vec.push(7); + test_vec.push(9); + test_vec.push(11); + + vec_abi.store_vec_u64(test_vec); + + vec_abi.push_vec_u64(13); + + let returned_vec = vec_abi.read_vec_u64(); + + assert(returned_vec.len() == 5); + assert(returned_vec.get(0).unwrap() == 5); + assert(returned_vec.get(1).unwrap() == 7); + assert(returned_vec.get(2).unwrap() == 9); + assert(returned_vec.get(3).unwrap() == 11); + assert(returned_vec.get(4).unwrap() == 13); +} + +#[test] +fn test_pop_u64() { + let vec_abi = abi(VecToVecStorageTest, CONTRACT_ID); + let mut test_vec = Vec::::new(); + test_vec.push(5); + test_vec.push(7); + test_vec.push(9); + test_vec.push(11); + + vec_abi.store_vec_u64(test_vec); + + assert(11 == vec_abi.pop_vec_u64()); + + let returned_vec = vec_abi.read_vec_u64(); + + assert(returned_vec.len() == 3); + assert(returned_vec.get(0).unwrap() == 5); + assert(returned_vec.get(1).unwrap() == 7); + assert(returned_vec.get(2).unwrap() == 9); +} + + +#[test] +fn test_conversion_struct() { + let vec_abi = abi(VecToVecStorageTest, CONTRACT_ID); + let mut test_vec = Vec::::new(); + test_vec.push(TestStruct{val1: 0, val2: 1, val3: 2}); + test_vec.push(TestStruct{val1: 1, val2: 2, val3: 3}); + test_vec.push(TestStruct{val1: 2, val2: 3, val3: 4}); + test_vec.push(TestStruct{val1: 3, val2: 4, val3: 5}); + + vec_abi.store_vec_struct(test_vec); + + let returned_vec = vec_abi.read_vec_struct(); + + assert(returned_vec.len() == 4); + assert(returned_vec.get(0).unwrap() == TestStruct{val1: 0, val2: 1, val3: 2}); + assert(returned_vec.get(1).unwrap() == TestStruct{val1: 1, val2: 2, val3: 3}); + assert(returned_vec.get(2).unwrap() == TestStruct{val1: 2, val2: 3, val3: 4}); + assert(returned_vec.get(3).unwrap() == TestStruct{val1: 3, val2: 4, val3: 5}); +} + +#[test] +fn test_push_struct() { + let vec_abi = abi(VecToVecStorageTest, CONTRACT_ID); + let mut test_vec = Vec::::new(); + test_vec.push(TestStruct{val1: 0, val2: 1, val3: 2}); + test_vec.push(TestStruct{val1: 1, val2: 2, val3: 3}); + test_vec.push(TestStruct{val1: 2, val2: 3, val3: 4}); + test_vec.push(TestStruct{val1: 3, val2: 4, val3: 5}); + + vec_abi.store_vec_struct(test_vec); + + vec_abi.push_vec_struct(TestStruct{val1: 4, val2: 5, val3: 6}); + + let returned_vec = vec_abi.read_vec_struct(); + + assert(returned_vec.len() == 5); + assert(returned_vec.get(0).unwrap() == TestStruct{val1: 0, val2: 1, val3: 2}); + assert(returned_vec.get(1).unwrap() == TestStruct{val1: 1, val2: 2, val3: 3}); + assert(returned_vec.get(2).unwrap() == TestStruct{val1: 2, val2: 3, val3: 4}); + assert(returned_vec.get(3).unwrap() == TestStruct{val1: 3, val2: 4, val3: 5}); + assert(returned_vec.get(4).unwrap() == TestStruct{val1: 4, val2: 5 , val3: 6}); +} + +#[test] +fn test_pop_struct() { + let vec_abi = abi(VecToVecStorageTest, CONTRACT_ID); + let mut test_vec = Vec::::new(); + test_vec.push(TestStruct{val1: 0, val2: 1, val3: 2}); + test_vec.push(TestStruct{val1: 1, val2: 2, val3: 3}); + test_vec.push(TestStruct{val1: 2, val2: 3, val3: 4}); + test_vec.push(TestStruct{val1: 3, val2: 4, val3: 5}); + + vec_abi.store_vec_struct(test_vec); + + assert(TestStruct{val1: 3, val2: 4, val3: 5} == vec_abi.pop_vec_struct()); + + let returned_vec = vec_abi.read_vec_struct(); + + assert(returned_vec.len() == 3); + assert(returned_vec.get(0).unwrap() == TestStruct{val1: 0, val2: 1, val3: 2}); + assert(returned_vec.get(1).unwrap() == TestStruct{val1: 1, val2: 2, val3: 3}); + assert(returned_vec.get(2).unwrap() == TestStruct{val1: 2, val2: 3, val3: 4}); +} +