diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d3a5228d..24c6581bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased] ### Added +- Implements the API for the `pallet-revive` host functions `set_storage_or_clear`, `get_storage_or_zero`, `call_data_load`, `return_data_copy` - [#2739](https://github.com/use-ink/ink/pull/2739) - Implements the API for the `pallet-revive` host functions `chain_id`, `balance_of`, `base_fee`, `origin`, `code_size`, `block_hash`, `block_author` - [#2719](https://github.com/use-ink/ink/pull/2719) - Implement `From` for "ink-as-dependency" contract refs - [#2728](https://github.com/use-ink/ink/pull/2728) diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index f1ce8dc252..cb2bd4185b 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -170,6 +170,61 @@ pub fn block_author() -> Address { ::on_instance(TypedEnvBackend::block_author) } +/// Returns the U256 value at the given offset from the input passed by the caller, +/// akin to the EVM [CALLDATALOAD](https://www.evm.codes/?fork=cancun#35) opcode. +/// +/// # Note +/// +/// - If `offset` is out of bounds, a value of zero will be returned. +/// - If `offset` is in bounds but there is not enough call data, the available data is +/// right-padded in order to fill a whole U256 value. +/// - The data returned is a little endian U256 integer value. +pub fn call_data_load(offset: u32) -> U256 { + ::on_instance(|instance| { + TypedEnvBackend::call_data_load(instance, offset) + }) +} + +/// Sets the storage entry for a fixed 256-bit key with a fixed 256-bit value, +/// akin to the EVM [SSTORE](https://www.evm.codes/?fork=cancun#55) opcode. +/// +/// If the provided value is all zeros then the key is cleared (i.e. deleted). +/// Returns the size (in bytes) of the pre-existing value at the specified key, if any. +pub fn set_storage(key: U256, value: &[u8; 32]) -> Option { + ::on_instance(|instance| { + TypedEnvBackend::set_storage(instance, key, value) + }) +} + +/// Sets the transient storage entry for a fixed 256-bit key with a fixed 256-bit value, +/// akin to the EVM [TSTORE](https://www.evm.codes/?fork=cancun#5D) opcode. +/// +/// If the provided value is all zeros then the key is cleared (i.e. deleted). +/// Returns the size (in bytes) of the pre-existing value at the specified key, if any. +pub fn set_transient_storage(key: U256, value: &[u8; 32]) -> Option { + ::on_instance(|instance| { + TypedEnvBackend::set_transient_storage(instance, key, value) + }) +} + +/// Retrieves the storage entry for a fixed 256-bit key. +/// If the key does not exist, it returns 32 zero bytes. +/// This is akin to the EVM [SLOAD](https://www.evm.codes/?fork=cancun#54) opcode. +pub fn get_storage(key: U256) -> [u8; 32] { + ::on_instance(|instance| { + TypedEnvBackend::get_storage(instance, key) + }) +} + +/// Retrieves the transient storage entry for a fixed 256-bit key. +/// If the key does not exist, it returns 32 zero bytes. +/// This is akin to the EVM [TLOAD](https://www.evm.codes/?fork=cancun#5C) opcode. +pub fn get_transient_storage(key: U256) -> [u8; 32] { + ::on_instance(|instance| { + TypedEnvBackend::get_transient_storage(instance, key) + }) +} + /// Returns the transferred value for the contract execution. /// /// # Errors diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 6c0048d3ed..ac76fa7f03 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -369,6 +369,57 @@ pub trait TypedEnvBackend: EnvBackend { /// For more details visit: [`block_author`][`crate::block_author`] fn block_author(&mut self) -> Address; + /// Returns the U256 value at given `offset` from the input passed by the caller. + /// This is akin to the EVM [CALLDATALOAD](https://www.evm.codes/?fork=cancun#35) opcode. + /// + /// # Note + /// + /// - If `offset` is out of bounds, a value of zero will be returned. + /// - If `offset` is in bounds but there is not enough call data, the available data + /// is right-padded in order to fill a whole U256 value. + /// - The data returned is a little endian U256 integer value. + /// + /// For more details visit: [`call_data_load`][`crate::call_data_load`] + fn call_data_load(&mut self, offset: u32) -> U256; + + /// Sets the storage entry for a fixed 256‑bit key with a fixed 256‑bit value. + /// If the provided 32‑byte value is all zeros then the key is cleared (i.e. deleted). + /// Returns the size (in bytes) of the pre‑existing value at the specified key, if + /// any. This is akin to the EVM [SSTORE](https://www.evm.codes/?fork=cancun#55) opcode. + /// + /// # Note + /// + /// For more details visit: [`set_storage`][`crate::set_storage`] + fn set_storage(&mut self, key: U256, value: &[u8; 32]) -> Option; + + /// Sets the transient storage entry for a fixed 256‑bit key with a fixed 256‑bit + /// value. If the provided 32‑byte value is all zeros then the key is cleared + /// (i.e. deleted). Returns the size (in bytes) of the pre‑existing value at the + /// specified key, if any. This is akin to the EVM [TSTORE](https://www.evm.codes/?fork=cancun#5D) opcode. + /// + /// # Note + /// + /// For more details visit: [`set_transient_storage`][`crate::set_transient_storage`] + fn set_transient_storage(&mut self, key: U256, value: &[u8; 32]) -> Option; + + /// Retrieves the storage entry for a fixed 256‑bit key. + /// If the key does not exist, it returns 32 zero bytes. + /// This is akin to the EVM [SLOAD](https://www.evm.codes/?fork=cancun#54) opcode. + /// + /// # Note + /// + /// For more details visit: [`get_storage`][`crate::get_storage`] + fn get_storage(&mut self, key: U256) -> [u8; 32]; + + /// Retrieves the transient storage entry for a fixed 256‑bit key. + /// If the key does not exist, it returns 32 zero bytes. + /// This is akin to the EVM [TLOAD](https://www.evm.codes/?fork=cancun#5C) opcode. + /// + /// # Note + /// + /// For more details visit: [`get_transient_storage`][`crate::get_transient_storage`] + fn get_transient_storage(&mut self, key: U256) -> [u8; 32]; + /// Returns the transferred value for the contract execution. /// /// # Note diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index 57143385c3..43967359a1 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -616,6 +616,26 @@ impl TypedEnvBackend for EnvInstance { unimplemented!("not implemented, the off-chain environment will be removed"); } + fn call_data_load(&mut self, _offset: u32) -> U256 { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + + fn set_storage(&mut self, _key: U256, _value: &[u8; 32]) -> Option { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + + fn set_transient_storage(&mut self, _key: U256, _value: &[u8; 32]) -> Option { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + + fn get_storage(&mut self, _key: U256) -> [u8; 32] { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + + fn get_transient_storage(&mut self, _key: U256) -> [u8; 32] { + unimplemented!("not implemented, the off-chain environment will be removed"); + } + fn transferred_value(&mut self) -> U256 { self.get_property(Engine::value_transferred) .unwrap_or_else(|error| { diff --git a/crates/env/src/engine/on_chain/pallet_revive.rs b/crates/env/src/engine/on_chain/pallet_revive.rs index 7706047a35..89272e07f0 100644 --- a/crates/env/src/engine/on_chain/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/pallet_revive.rs @@ -647,6 +647,7 @@ fn decode_bytes(input: &[u8], out: &mut [u8]) -> usize { } const STORAGE_FLAGS: StorageFlags = StorageFlags::empty(); +const TRANSIENT_STORAGE_FLAGS: StorageFlags = StorageFlags::TRANSIENT; impl EnvBackend for EnvInstance { fn set_contract_storage(&mut self, key: &K, value: &V) -> Option @@ -1093,6 +1094,42 @@ impl TypedEnvBackend for EnvInstance { h160.into() } + fn call_data_load(&mut self, offset: u32) -> U256 { + let mut scope = self.scoped_buffer(); + let u256: &mut [u8; 32] = scope.take(32).try_into().unwrap(); + + ext::call_data_load(u256, offset); + U256::from_le_bytes(*u256) + } + + fn set_storage(&mut self, key: U256, value: &[u8; 32]) -> Option { + let mut scope = self.scoped_buffer(); + let key: &mut [u8; 32] = scope.take_encoded(&key).try_into().unwrap(); + ext::set_storage_or_clear(STORAGE_FLAGS, key, value) + } + + fn set_transient_storage(&mut self, key: U256, value: &[u8; 32]) -> Option { + let mut scope = self.scoped_buffer(); + let key: &mut [u8; 32] = scope.take_encoded(&key).try_into().unwrap(); + ext::set_storage_or_clear(TRANSIENT_STORAGE_FLAGS, key, value) + } + + fn get_storage(&mut self, key: U256) -> [u8; 32] { + let mut scope = self.scoped_buffer(); + let key: &mut [u8; 32] = scope.take_encoded(&key).try_into().unwrap(); + let value: &mut [u8; 32] = scope.take(32).try_into().unwrap(); + ext::get_storage_or_zero(STORAGE_FLAGS, key, value); + *value + } + + fn get_transient_storage(&mut self, key: U256) -> [u8; 32] { + let mut scope = self.scoped_buffer(); + let key: &mut [u8; 32] = scope.take_encoded(&key).try_into().unwrap(); + let value: &mut [u8; 32] = scope.take(32).try_into().unwrap(); + ext::get_storage_or_zero(TRANSIENT_STORAGE_FLAGS, key, value); + *value + } + fn transferred_value(&mut self) -> U256 { let mut scope = self.scoped_buffer(); let u256: &mut [u8; 32] = scope.take(32).try_into().unwrap(); diff --git a/crates/ink/src/env_access.rs b/crates/ink/src/env_access.rs index 1fc931acce..57aef1b5eb 100644 --- a/crates/ink/src/env_access.rs +++ b/crates/ink/src/env_access.rs @@ -274,6 +274,188 @@ where ink_env::return_data_size() } + /// Returns the U256 value at the given offset from the input passed by the caller, + /// akin to the EVM [CALLDATALOAD](https://www.evm.codes/?fork=cancun#35) opcode. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// use ink::U256; + /// + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn get_call_data_at_offset(&self, offset: u32) -> U256 { + /// self.env().call_data_load(offset) + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::call_data_load`] + pub fn call_data_load(self, offset: u32) -> U256 { + ink_env::call_data_load(offset) + } + + /// Sets the storage entry for a fixed 256-bit key with a fixed 256-bit value. + /// If the provided 32-byte value is all zeros then the key is cleared (i.e. deleted). + /// Returns the size (in bytes) of the pre-existing value at the specified key, if + /// any. This is akin to the EVM [SSTORE](https://www.evm.codes/?fork=cancun#55) opcode. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// use ink::U256; + /// + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn store_value(&self) { + /// let key = U256::from(42u32); + /// let value = [0xDEu8; 32]; + /// self.env().set_storage(key, &value); + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::set_storage`] + pub fn set_storage(self, key: U256, value: &[u8; 32]) -> Option { + ink_env::set_storage(key, value) + } + + /// Sets the transient storage entry for a fixed 256-bit key with a fixed 256-bit + /// value. If the provided 32-byte value is all zeros then the key is cleared + /// (i.e. deleted). Returns the size (in bytes) of the pre-existing value at the + /// specified key, if any. This is akin to the EVM [TSTORE](https://www.evm.codes/?fork=cancun#5D) opcode. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// use ink::U256; + /// + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn store_transient(&self) { + /// let key = U256::from(42u32); + /// let value = [0xABu8; 32]; + /// self.env().set_transient_storage(key, &value); + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::set_transient_storage`] + pub fn set_transient_storage(self, key: U256, value: &[u8; 32]) -> Option { + ink_env::set_transient_storage(key, value) + } + + /// Retrieves the storage entry for a fixed 256-bit key. + /// If the key does not exist, it returns 32 zero bytes. + /// This is akin to the EVM [SLOAD](https://www.evm.codes/?fork=cancun#54) opcode. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// use ink::U256; + /// + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn load_value(&self) -> [u8; 32] { + /// let key = U256::from(42u32); + /// self.env().get_storage(key) + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::get_storage`] + pub fn get_storage(self, key: U256) -> [u8; 32] { + ink_env::get_storage(key) + } + + /// Retrieves the transient storage entry for a fixed 256-bit key. + /// If the key does not exist, it returns 32 zero bytes. + /// This is akin to the EVM [TLOAD](https://www.evm.codes/?fork=cancun#5C) opcode. + /// + /// # Example + /// + /// ``` + /// #[ink::contract] + /// mod my_contract { + /// use ink::U256; + /// + /// #[ink(storage)] + /// pub struct MyContract; + /// + /// impl MyContract { + /// #[ink(constructor)] + /// pub fn new() -> Self { + /// Self {} + /// } + /// + /// #[ink(message)] + /// pub fn load_transient(&self) -> [u8; 32] { + /// let key = U256::from(42u32); + /// self.env().get_transient_storage(key) + /// } + /// } + /// } + /// ``` + /// + /// # Note + /// + /// For more details visit: [`ink_env::get_transient_storage`] + pub fn get_transient_storage(self, key: U256) -> [u8; 32] { + ink_env::get_transient_storage(key) + } + /// Returns the [EIP-155](https://eips.ethereum.org/EIPS/eip-155) chain ID, /// akin to the EVM [CHAINID](https://www.evm.codes/?fork=cancun#46) opcode. /// diff --git a/crates/ink/tests/ui/contract/pass/env-access.rs b/crates/ink/tests/ui/contract/pass/env-access.rs index 672443cff6..9e534bba08 100644 --- a/crates/ink/tests/ui/contract/pass/env-access.rs +++ b/crates/ink/tests/ui/contract/pass/env-access.rs @@ -25,6 +25,7 @@ mod contract { let _ = Self::env().block_author(); let _ = Self::env().transferred_value(); let _ = Self::env().weight_to_fee(0); + let _ = Self::env().call_data_load(0); Self {} } @@ -47,6 +48,7 @@ mod contract { let _ = self.env().block_author(); let _ = self.env().transferred_value(); let _ = self.env().weight_to_fee(0); + let _ = self.env().call_data_load(0); } } } diff --git a/integration-tests/internal/misc-evm-getters-hostfns/lib.rs b/integration-tests/internal/misc-evm-getters-hostfns/lib.rs index 1e6b82ab33..ab59a021a0 100644 --- a/integration-tests/internal/misc-evm-getters-hostfns/lib.rs +++ b/integration-tests/internal/misc-evm-getters-hostfns/lib.rs @@ -60,6 +60,12 @@ mod misc_evm_getters_hostfns { pub fn block_author(&self) -> Address { self.env().block_author() } + + /// Checks that the host function `call_data_load` works + #[ink(message)] + pub fn call_data_load(&self, offset: u32) -> U256 { + self.env().call_data_load(offset) + } } #[cfg(all(test, feature = "e2e-tests"))] @@ -269,5 +275,33 @@ mod misc_evm_getters_hostfns { Ok(()) } + + #[ink_e2e::test] + async fn e2e_call_data_load_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let contract = client + .instantiate( + "misc_evm_getters_hostfns", + &ink_e2e::alice(), + &mut MiscEVMGettersfnsRef::new(), + ) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // then + let _call_res = client + .call(&ink_e2e::alice(), &call_builder.call_data_load(0)) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + Ok(()) + } } } diff --git a/integration-tests/internal/storage-hostfns/Cargo.toml b/integration-tests/internal/storage-hostfns/Cargo.toml new file mode 100755 index 0000000000..1256041306 --- /dev/null +++ b/integration-tests/internal/storage-hostfns/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "storage-hostfns" +description = "E2E tests for storage related host functions" +version = "6.0.0-beta" +authors = ["Use Ink "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e" } +ink_primitives = { path = "../../../crates/primitives" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] + +[package.metadata.ink-lang] +abi = "ink" diff --git a/integration-tests/internal/storage-hostfns/lib.rs b/integration-tests/internal/storage-hostfns/lib.rs new file mode 100644 index 0000000000..3184988b46 --- /dev/null +++ b/integration-tests/internal/storage-hostfns/lib.rs @@ -0,0 +1,299 @@ +#![cfg_attr(not(feature = "std"), no_std, no_main)] +#![allow(clippy::new_without_default)] + +#[ink::contract] +mod storage_hostfns { + use ink::U256; + + #[ink(storage)] + pub struct Storagefns {} + + impl Storagefns { + #[ink(constructor)] + pub fn new() -> Self { + Self {} + } + + /// Sets a value in persistent storage. + #[ink(message)] + pub fn set_storage(&self, key: U256, value: [u8; 32]) -> Option { + self.env().set_storage(key, &value) + } + + /// Sets a value in transient storage. + #[ink(message)] + pub fn set_transient_storage(&self, key: U256, value: [u8; 32]) -> Option { + self.env().set_transient_storage(key, &value) + } + + /// Clears a persistent storage entry by setting value to all zeros. + #[ink(message)] + pub fn clear_storage(&self, key: U256) -> Option { + self.env().set_storage(key, &[0u8; 32]) + } + + /// Clears a transient storage entry by setting value to all zeros. + #[ink(message)] + pub fn set_clear_transient_storage( + &self, + key: U256, + value: [u8; 32], + ) -> Option { + self.env().set_transient_storage(key, &value); + self.env().set_transient_storage(key, &[0u8; 32]) + } + + /// Retrieves a value from persistent storage. + #[ink(message)] + pub fn get_storage(&self, key: U256) -> [u8; 32] { + self.env().get_storage(key) + } + + /// Retrieves a value from transient storage. + #[ink(message)] + pub fn get_transient_storage(&self, key: U256) -> [u8; 32] { + self.env().get_transient_storage(key) + } + + /// Sets a transient value and immediately retrieves it in the same transaction. + #[ink(message)] + pub fn set_and_get_transient_storage( + &self, + key: U256, + value: [u8; 32], + ) -> [u8; 32] { + self.env().set_transient_storage(key, &value); + self.env().get_transient_storage(key) + } + } + + #[cfg(all(test, feature = "e2e-tests"))] + mod e2e_tests { + use super::*; + use ink_e2e::ContractsBackend; + + type E2EResult = std::result::Result>; + + #[ink_e2e::test] + async fn set_storage_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StoragefnsRef::new(); + let contract = client + .instantiate("storage-hostfns", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // when - set a value + let key = U256::from(42u32); + let value = [0xDEu8; 32]; + let result = client + .call(&ink_e2e::alice(), &call_builder.set_storage(key, value)) + .submit() + .await?; + + // then - first set returns None (no previous value) + assert_eq!(result.return_value(), None); + + Ok(()) + } + + #[ink_e2e::test] + async fn clear_storage_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StoragefnsRef::new(); + let contract = client + .instantiate("storage-hostfns", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + let key = U256::from(42u32); + let value = [0xDEu8; 32]; + + // when - set a value + let _ = client + .call(&ink_e2e::alice(), &call_builder.set_storage(key, value)) + .submit() + .await?; + + // when - clear it + let result = client + .call(&ink_e2e::alice(), &call_builder.clear_storage(key)) + .submit() + .await?; + + // then - should return size of previous value (32 bytes) + assert_eq!(result.return_value(), Some(32u32)); + + Ok(()) + } + + #[ink_e2e::test] + async fn set_transient_storage_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StoragefnsRef::new(); + let contract = client + .instantiate("storage-hostfns", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + // when - set a transient value + let key = U256::from(50u32); + let value = [0xABu8; 32]; + let result = client + .call( + &ink_e2e::alice(), + &call_builder.set_transient_storage(key, value), + ) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + // then - first set returns None + assert_eq!(result.return_value(), None); + + Ok(()) + } + + #[ink_e2e::test] + async fn clear_transient_storage_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StoragefnsRef::new(); + let contract = client + .instantiate("storage-hostfns", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + let key = U256::from(50u32); + let value = [0x1u8; 32]; + + let result = client + .call( + &ink_e2e::alice(), + &call_builder.set_clear_transient_storage(key, value), + ) + .submit() + .await + .unwrap_or_else(|err| { + panic!("call failed: {:#?}", err); + }); + + // then - should return size of previous value (32 bytes) + assert_eq!(result.return_value(), Some(32u32)); + + Ok(()) + } + + #[ink_e2e::test] + async fn get_storage_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StoragefnsRef::new(); + let contract = client + .instantiate("storage-hostfns", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + let key = U256::from(100u32); + let value = [0xAAu8; 32]; + + // when - set a value + let _ = client + .call(&ink_e2e::alice(), &call_builder.set_storage(key, value)) + .submit() + .await?; + + // when - get the value + let result = client + .call(&ink_e2e::alice(), &call_builder.get_storage(key)) + .submit() + .await?; + + // then - should retrieve the same value + assert_eq!(result.return_value(), value); + + Ok(()) + } + + #[ink_e2e::test] + async fn set_and_get_transient_storage_works( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StoragefnsRef::new(); + let contract = client + .instantiate("storage-hostfns", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + let key = U256::from(101u32); + let value = [0xBBu8; 32]; + + // when - set and get transient value in same call + let result = client + .call( + &ink_e2e::alice(), + &call_builder.set_and_get_transient_storage(key, value), + ) + .submit() + .await?; + + // then - should retrieve the same value + assert_eq!(result.return_value(), value); + + Ok(()) + } + + #[ink_e2e::test] + async fn get_nonexistent_storage_returns_zeros( + mut client: Client, + ) -> E2EResult<()> { + // given + let mut constructor = StoragefnsRef::new(); + let contract = client + .instantiate("storage-hostfns", &ink_e2e::alice(), &mut constructor) + .submit() + .await + .expect("instantiate failed"); + let call_builder = contract.call_builder::(); + + let nonexistent_key = U256::from(999u32); + + // when - get a nonexistent value + let result = client + .call( + &ink_e2e::alice(), + &call_builder.get_storage(nonexistent_key), + ) + .submit() + .await?; + + // then - should return 32 zero bytes + assert_eq!(result.return_value(), [0u8; 32]); + + Ok(()) + } + } +}