diff --git a/sway-lib-std/src/storage/storage_api.sw b/sway-lib-std/src/storage/storage_api.sw index 099cf83a754..a61baba1ee9 100644 --- a/sway-lib-std/src/storage/storage_api.sw +++ b/sway-lib-std/src/storage/storage_api.sw @@ -3,7 +3,11 @@ library; use ::alloc::alloc; use ::option::Option::{self, *}; -/// Store a stack value in storage. Will not work for heap values. +/// Stores a stack value in storage. Will not work for heap values. +/// +/// # Additional Information +/// +/// If the value crosses the boundary of a storage slot, writing continues at the following slot. /// /// # Arguments /// @@ -34,26 +38,22 @@ pub fn write(slot: b256, offset: u64, value: T) { return; } - // Get the number of storage slots needed based on the size of `T` - let number_of_slots = (offset * 8 + __size_of::() + 31) >> 5; + // Determine how many slots and where the value is to be stored. + let (offset_slot, number_of_slots, place_in_slot) = slot_calculator::(slot, offset); // Allocate enough memory on the heap for `value` as well as any potential padding required due // to `offset`. let padded_value = alloc::(number_of_slots * 32); // Read the values that currently exist in the affected storage slots. - // NOTE: we can do better here by only reading from the slots that we know could be affected. - // These are the two slots where the start and end of `T` fall in considering `offset`. - // However, doing so requires that we perform addition on `b256` to compute the corresponding - // keys, and that is not possible today. - let _ = __state_load_quad(slot, padded_value, number_of_slots); + let _ = __state_load_quad(offset_slot, padded_value, number_of_slots); // Copy the value to be stored to `padded_value + offset`. - padded_value.add::(offset).write::(value); + padded_value.add::(place_in_slot).write::(value); // Now store back the data at `padded_value` which now contains the old data but partially // overwritten by the new data in the desired locations. - let _ = __state_store_quad(slot, padded_value, number_of_slots); + let _ = __state_store_quad(offset_slot, padded_value, number_of_slots); } /// Reads a value of type `T` starting at the location specified by `slot` and `offset`. If the @@ -91,9 +91,8 @@ pub fn read(slot: b256, offset: u64) -> Option { return None; } - // NOTE: we are leaking this value on the heap. - // Get the number of storage slots needed based on the size of `T` - let number_of_slots = (offset * 8 + __size_of::() + 31) >> 5; + // Determine how many slots and where the value is to be read. + let (offset_slot, number_of_slots, place_in_slot) = slot_calculator::(slot, offset); // Allocate a buffer for the result. Its size needs to be a multiple of 32 bytes so we can // make the 'quad' storage instruction read without overflowing. @@ -102,22 +101,19 @@ pub fn read(slot: b256, offset: u64) -> Option { // Read `number_of_slots * 32` bytes starting at storage slot `slot` and return an `Option` // wrapping the value stored at `result_ptr + offset` if all the slots are valid. Otherwise, // return `None`. - if __state_load_quad(slot, result_ptr, number_of_slots) { - Some(result_ptr.add::(offset).read::()) + if __state_load_quad(offset_slot, result_ptr, number_of_slots) { + Some(result_ptr.add::(place_in_slot).read::()) } else { None } } -/// Clear a sequence of consecutive storage slots starting at a some slot. +/// Clear a value starting at some slot with an offset. /// /// # Arguments /// -/// * `slot`: [b256] - The key of the first storage slot that will be cleared -/// -/// # Returns -/// -/// * [bool] - Indicates whether all of the storage slots cleared were previously set. +/// * `slot` - The key of the stored value that will be cleared +/// * `offset` - An offset, in words, from the start of `slot`, from which the value should be cleared. /// /// # Number of Storage Accesses /// @@ -137,11 +133,54 @@ pub fn read(slot: b256, offset: u64) -> Option { /// } /// ``` #[storage(write)] -pub fn clear(slot: b256) -> bool { - // Get the number of storage slots needed based on the size of `T` as the ceiling of - // `__size_of::() / 32` - let number_of_slots = (__size_of::() + 31) >> 5; +pub fn clear(slot: b256, offset: u64) -> bool { + if __size_of::() == 0 { + return true; + } + + // Determine how many slots and where the value is to be cleared. + let (offset_slot, number_of_slots, _place_in_slot) = slot_calculator::(slot, offset); // Clear `number_of_slots * 32` bytes starting at storage slot `slot`. - __state_clear(slot, number_of_slots) + __state_clear(offset_slot, number_of_slots) +} + +/// Given a slot, offset, and type this function determines where something should be stored. +/// +/// # Arguments +/// +/// * `slot`: [b256] - The starting address at which something should be stored. +/// * `offset`: [u64] - The offset from `slot` to store the value. +/// +/// # Returns +/// +/// [b256] - The calculated offset slot to store the value. +/// [u64] - The number of slots the value will occupy in storage. +/// [u64] - The word in the slot where the value will start. +fn slot_calculator(slot: b256, offset: u64) -> (b256, u64, u64) { + let size_of_t = __size_of::(); + + // Get the last storage slot needed based on the size of `T`. + // ((offset * bytes_in_word) + bytes + (bytes_in_slot - 1)) >> align_to_slot = last slot + let last_slot = ((offset * 8) + size_of_t + 31) >> 5; + + // Where in the storage slot to align `T` in order to pack word-aligned. + // offset % number_words_in_slot = word_place_in_slot + let place_in_slot = offset % 4; + + // Get the number of slots `T` spans based on its packed position. + // ((place_in_slot * bytes_in_word) + bytes + (bytes_in_slot - 1)) >> align_to_slot = number_of_slots + let number_of_slots = match __is_reference_type::() { + true => ((place_in_slot * 8) + size_of_t + 31) >> 5, + false => 1, + }; + + // TODO: Update when u256 <-> b256 conversions exist. + // Determine which starting slot `T` will be stored based on the offset. + let mut u256_slot = asm(r1: slot) {r1: u256}; + let u256_increment = asm(r1: (0, 0, 0, last_slot - number_of_slots)) { r1: u256 }; + u256_slot += u256_increment; + let offset_slot = asm(r1: u256_slot) { r1: b256 }; + + (offset_slot, number_of_slots, place_in_slot) } diff --git a/sway-lib-std/src/storage/storage_map.sw b/sway-lib-std/src/storage/storage_map.sw index 92022d153bd..b166f555f1c 100644 --- a/sway-lib-std/src/storage/storage_map.sw +++ b/sway-lib-std/src/storage/storage_map.sw @@ -108,6 +108,6 @@ impl StorageKey> where K: Hash { #[storage(write)] pub fn remove(self, key: K) -> bool where K: Hash { let key = sha256((key, self.slot)); - clear::(key) + clear::(key, 0) } } diff --git a/sway-lib-std/src/storage/storage_vec.sw b/sway-lib-std/src/storage/storage_vec.sw index ff7cdde7b35..4243b12268c 100644 --- a/sway-lib-std/src/storage/storage_vec.sw +++ b/sway-lib-std/src/storage/storage_vec.sw @@ -489,7 +489,7 @@ impl StorageKey> { /// ``` #[storage(write)] pub fn clear(self) { - let _ = clear::(self.field_id); + let _ = clear::(self.field_id, 0); } /// Swaps two elements. diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw index 8b8c6b2a2ba..058af773702 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_basic_storage/src/main.sw @@ -2,7 +2,7 @@ script; use basic_storage_abi::{BasicStorage, Quad}; fn main() -> u64 { - let addr = abi(BasicStorage, 0xb870f1a5893135ee3d0d688f57577837050bb720452b7afe67caca22e6984127); + let addr = abi(BasicStorage, 0xeadfcbbedf12c64a0b4e71389320c878a9182e6d68337649d6d38cdaee1c766c); let key = 0x0fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; let value = 4242; diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw index a9510a1ecb9..ec00f449876 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/call_increment_contract/src/main.sw @@ -3,7 +3,7 @@ script; use increment_abi::Incrementor; fn main() -> bool { - let the_abi = abi(Incrementor, 0xf517092581229b7264cfc956c2a93b275bf82ac8e9ed4727ebd2d8341d7fbe2e); + let the_abi = abi(Incrementor, 0x45e9597ef7e7625909b88184ee1d5aefd34260f9d84792c2d8c9fc14408bb7b1); the_abi.increment(5); the_abi.increment(5); let result = the_abi.get(); diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw index 33f19772df8..fba56d06836 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/require_contract_deployment/storage_access_caller/src/main.sw @@ -4,7 +4,7 @@ use storage_access_abi::*; use std::hash::*; fn main() -> bool { - let contract_id = 0x3a0c77d4c1705263d595a90f47c3546ee3fe18d935268335e7aa5dcc62956fc7; + let contract_id = 0x71e665c35ceb36cdccc84cded3dbc5a4eb7bcc6b8ae20d68f134c51d013ddec9; let caller = abi(StorageAccess, contract_id); // Test initializers