diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic index 0e090e288c4..5eef28f2adc 100644 --- a/.config/cargo_spellcheck.dic +++ b/.config/cargo_spellcheck.dic @@ -1,4 +1,4 @@ -100 +110 ABI AST @@ -99,5 +99,6 @@ vector/S implementer/S deduplicated wildcard/S +natively payability unpayable diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs index 375f85ab650..807ebf06349 100644 --- a/crates/env/src/engine/experimental_off_chain/impls.rs +++ b/crates/env/src/engine/experimental_off_chain/impls.rs @@ -189,7 +189,7 @@ impl EnvBackend for EnvInstance { V: scale::Encode, { let v = scale::Encode::encode(value); - self.engine.set_storage(key.as_bytes(), &v[..]); + self.engine.set_storage(key.as_ref(), &v[..]); } fn get_contract_storage(&mut self, key: &Key) -> Result> @@ -197,10 +197,7 @@ impl EnvBackend for EnvInstance { R: scale::Decode, { let mut output: [u8; 9600] = [0; 9600]; - match self - .engine - .get_storage(key.as_bytes(), &mut &mut output[..]) - { + match self.engine.get_storage(key.as_ref(), &mut &mut output[..]) { Ok(_) => (), Err(ext::Error::KeyNotFound) => return Ok(None), Err(_) => panic!("encountered unexpected error"), @@ -210,7 +207,7 @@ impl EnvBackend for EnvInstance { } fn clear_contract_storage(&mut self, key: &Key) { - self.engine.clear_storage(key.as_bytes()) + self.engine.clear_storage(key.as_ref()) } fn decode_input(&mut self) -> Result @@ -445,7 +442,7 @@ impl TypedEnvBackend for EnvInstance { let enc_rent_allowance = &scale::Encode::encode(&rent_allowance)[..]; let filtered: Vec<&[u8]> = - filtered_keys.iter().map(|k| &k.as_bytes()[..]).collect(); + filtered_keys.iter().map(|k| &k.as_ref()[..]).collect(); self.engine.restore_to( enc_account_id, enc_code_hash, diff --git a/crates/env/src/engine/off_chain/tests.rs b/crates/env/src/engine/off_chain/tests.rs index 3b75c73f1da..7b6597e11d5 100644 --- a/crates/env/src/engine/off_chain/tests.rs +++ b/crates/env/src/engine/off_chain/tests.rs @@ -31,13 +31,19 @@ fn store_load_clear() -> Result<()> { }) } +fn add_key(key: &Key, offset: u64) -> Key { + let mut result = *key; + result += offset; + result +} + #[test] fn key_add() -> Result<()> { crate::test::run_test::(|_| { let key00 = Key::from([0x0; 32]); - let key05 = key00 + 05_u64; // -> 5 - let key10 = key00 + 10_u64; // -> 10 | same as key55 - let key55 = key05 + 05_u64; // -> 5 + 5 = 10 | same as key10 + let key05 = add_key(&key00, 5); // -> 5 + let key10 = add_key(&key00, 10); // -> 10 | same as key55 + let key55 = add_key(&key05, 5); // -> 5 + 5 = 10 | same as key10 crate::set_contract_storage(&key55, &42); assert_eq!(crate::get_contract_storage::(&key10), Ok(Some(42))); crate::set_contract_storage(&key10, &1337); @@ -51,9 +57,9 @@ fn key_add_sub() -> Result<()> { crate::test::run_test::(|_| { // given let key0a = Key::from([0x0; 32]); - let key1a = key0a + 1337_u64; - let key2a = key0a + 42_u64; - let key3a = key0a + 52_u64; + let key1a = add_key(&key0a, 1337); + let key2a = add_key(&key0a, 42); + let key3a = add_key(&key0a, 52); // when crate::set_contract_storage(&key0a, &1); diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs index eecd3ba4475..71d5198cdc0 100644 --- a/crates/env/src/engine/on_chain/impls.rs +++ b/crates/env/src/engine/on_chain/impls.rs @@ -220,7 +220,7 @@ impl EnvBackend for EnvInstance { V: scale::Encode, { let buffer = self.scoped_buffer().take_encoded(value); - ext::set_storage(key.as_bytes(), &buffer[..]); + ext::set_storage(key.as_ref(), &buffer[..]); } fn get_contract_storage(&mut self, key: &Key) -> Result> @@ -228,7 +228,7 @@ impl EnvBackend for EnvInstance { R: scale::Decode, { let output = &mut self.scoped_buffer().take_rest(); - match ext::get_storage(key.as_bytes(), output) { + match ext::get_storage(key.as_ref(), output) { Ok(_) => (), Err(ExtError::KeyNotFound) => return Ok(None), Err(_) => panic!("encountered unexpected error"), @@ -238,7 +238,7 @@ impl EnvBackend for EnvInstance { } fn clear_contract_storage(&mut self, key: &Key) { - ext::clear_storage(key.as_bytes()) + ext::clear_storage(key.as_ref()) } fn decode_input(&mut self) -> Result diff --git a/crates/metadata/src/layout/mod.rs b/crates/metadata/src/layout/mod.rs index b33d1177136..ab4fec5bb0a 100644 --- a/crates/metadata/src/layout/mod.rs +++ b/crates/metadata/src/layout/mod.rs @@ -99,17 +99,13 @@ impl<'de> serde::Deserialize<'de> for LayoutKey { impl<'a> From<&'a Key> for LayoutKey { fn from(key: &'a Key) -> Self { - Self { - key: key.to_bytes(), - } + Self { key: *key.as_ref() } } } impl From for LayoutKey { fn from(key: Key) -> Self { - Self { - key: key.to_bytes(), - } + Self { key: *key.as_ref() } } } diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 5ce32e8fa41..c9968994bd0 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -18,6 +18,7 @@ include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"] ink_prelude = { version = "3.0.0-rc6", path = "../prelude/", default-features = false } scale = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] } scale-info = { version = "1.0", default-features = false, features = ["derive"], optional = true } +cfg-if = "1" [dev-dependencies] criterion = "0.3.1" diff --git a/crates/primitives/src/key.rs b/crates/primitives/src/key.rs index 28e30f307a2..4f3c2b0a89a 100644 --- a/crates/primitives/src/key.rs +++ b/crates/primitives/src/key.rs @@ -12,46 +12,85 @@ // See the License for the specific language governing permissions and // limitations under the License. +use cfg_if::cfg_if; use core::{ - fmt, - ops::{ - Add, - AddAssign, + fmt::{ + self, + Debug, + Display, + Formatter, }, + ops::AddAssign, +}; +#[cfg(feature = "std")] +use scale_info::{ + build::Fields, + Path, + Type, + TypeInfo, }; -/// Key into contract storage. -/// -/// Used to identify contract storage cells for read and write operations. -/// Can be compared to a raw pointer and features simple pointer arithmetic. +/// A key into the smart contract storage. /// /// # Note /// -/// This is the most low-level primitive to identify contract storage cells. -/// -/// # Unsafe -/// -/// Prefer using high-level types found in `ink_storage` to operate on the contract -/// storage. -#[derive(Copy, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +/// - The storage of an ink! smart contract can be viewed as a key-value store. +/// - In order to manipulate its storage an ink! smart contract is required +/// to indicate the respective cells using this primitive type. +/// - The `Key` type can be compared to a raw pointer and also allows operations +/// similar to pointer arithmetic. +/// - Users usually should not have to deal with this low-level primitive themselves +/// and instead use the more high-level primitives provided by the `ink_storage` +/// crate. +#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(transparent)] -pub struct Key([u64; 4]); +pub struct Key([u8; 32]); + +impl From<[u8; 32]> for Key { + #[inline] + fn from(bytes: [u8; 32]) -> Self { + Self(bytes) + } +} + +impl AsRef<[u8; 32]> for Key { + #[inline] + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl AsMut<[u8; 32]> for Key { + #[inline] + fn as_mut(&mut self) -> &mut [u8; 32] { + &mut self.0 + } +} impl Key { - fn write_bytes(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn write_bytes(&self, f: &mut Formatter) -> fmt::Result { write!(f, "0x")?; - for limb in &self.0 { - write!(f, "_")?; - for byte in &limb.to_le_bytes() { - write!(f, "{:02X}", byte)?; - } + let bytes = self.as_ref(); + let len_bytes = bytes.len(); + let len_chunk = 4; + let len_chunks = len_bytes / len_chunk; + for i in 0..len_chunks { + let offset = i * len_chunk; + write!( + f, + "_{:02X}{:02X}{:02X}{:02X}", + bytes[offset], + bytes[offset + 1], + bytes[offset + 2], + bytes[offset + 3] + )?; } Ok(()) } } -impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Debug for Key { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { write!(f, "Key(")?; self.write_bytes(f)?; write!(f, ")")?; @@ -59,86 +98,35 @@ impl fmt::Debug for Key { } } -impl fmt::Display for Key { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl Display for Key { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { self.write_bytes(f) } } -impl From<[u8; 32]> for Key { - #[inline] - fn from(bytes: [u8; 32]) -> Self { - if cfg!(target_endian = "little") { - // SAFETY: If the machine has little endian byte ordering we can - // simply transmute the input bytes into the correct `u64` - // byte ordering for the `Key` data structure. Otherwise - // we have to manually convert them via the - // `from_bytes_be_fallback` procedure. - // - // We decided to have the little endian as default format for Key - // instance since WebAssembly dictates little endian byte ordering - // for the execution environment. - Self(unsafe { ::core::mem::transmute::<[u8; 32], [u64; 4]>(bytes) }) - } else { - Self::from_bytes_be_fallback(bytes) - } - } -} - impl Key { - /// Creates a new key from the given bytes. + /// Reinterprets the underlying bytes of the key as `&[u64; 4]`. /// - /// # Note + /// # Safety /// - /// This is a fallback procedure in case the target machine does not have - /// little endian byte ordering. - #[inline] - fn from_bytes_be_fallback(bytes: [u8; 32]) -> Self { - #[inline] - fn carve_out_u64_bytes(bytes: &[u8; 32], offset: u8) -> [u8; 8] { - let o = (offset * 8) as usize; - [ - bytes[o], - bytes[o + 1], - bytes[o + 2], - bytes[o + 3], - bytes[o + 4], - bytes[o + 5], - bytes[o + 6], - bytes[o + 7], - ] - } - Self([ - u64::from_le_bytes(carve_out_u64_bytes(&bytes, 0)), - u64::from_le_bytes(carve_out_u64_bytes(&bytes, 1)), - u64::from_le_bytes(carve_out_u64_bytes(&bytes, 2)), - u64::from_le_bytes(carve_out_u64_bytes(&bytes, 3)), - ]) + /// This is only safe to do on little-endian systems therefore + /// this function is only enabled on these platforms. + #[cfg(target_endian = "little")] + fn reinterpret_as_u64x4(&self) -> &[u64; 4] { + // SAFETY: Conversion is only safe on little endian architectures. + unsafe { &*(&self.0 as *const [u8; 32] as *const [u64; 4]) } } - /// Tries to return the underlying bytes as slice. + /// Reinterprets the underlying bytes of the key as `&mut [u64; 4]`. /// - /// This only returns `Some` if the execution environment has little-endian - /// byte order. - pub fn try_as_bytes(&self) -> Option<&[u8; 32]> { - if cfg!(target_endian = "little") { - return Some(self.as_bytes()) - } - None - } - - /// Returns the underlying bytes of the key. + /// # Safety /// - /// This only works and is supported if the target machine has little-endian - /// byte ordering. Use [`Key::try_as_bytes`] as a general procedure instead. + /// This is only safe to do on little-endian systems therefore + /// this function is only enabled on these platforms. #[cfg(target_endian = "little")] - pub fn as_bytes(&self) -> &[u8; 32] { - // SAFETY: This pointer cast is possible since the outer struct - // (Key) is `repr(transparent)` and since we restrict - // ourselves to little-endian byte ordering. In any other - // case this is invalid which is why return `None` as - // fallback. - unsafe { &*(&self.0 as *const [u64; 4] as *const [u8; 32]) } + fn reinterpret_as_u64x4_mut(&mut self) -> &mut [u64; 4] { + // SAFETY: Conversion is only safe on little endian architectures. + unsafe { &mut *(&mut self.0 as *mut [u8; 32] as *mut [u64; 4]) } } } @@ -149,251 +137,203 @@ impl scale::Encode for Key { } #[inline] - fn encode_to(&self, dest: &mut T) { - if cfg!(target_endian = "little") { - dest.write(self.try_as_bytes().expect("little endian is asserted")) - } else { - dest.write(&self.to_bytes()) - } - } -} - -impl scale::Decode for Key { - #[inline] - fn decode(input: &mut I) -> Result { - Ok(Self::from(<[u8; 32] as scale::Decode>::decode(input)?)) + fn encode_to(&self, output: &mut O) + where + O: scale::Output + ?Sized, + { + output.write(self.as_ref()); } -} -#[cfg(target_endian = "little")] -impl Key { - /// Returns the bytes that are representing the key. #[inline] - pub fn to_bytes(self) -> [u8; 32] { - if cfg!(target_endian = "little") { - // SAFETY: This pointer cast is possible since the outer struct - // (Key) is `repr(transparent)` and since we restrict - // ourselves to little-endian byte ordering. In any other - // case this is invalid which is why return `None` as - // fallback. - unsafe { core::mem::transmute::<[u64; 4], [u8; 32]>(self.0) } - } else { - self.to_bytes_be_fallback() - } + fn using_encoded(&self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + f(self.as_ref()) } - /// Fallback big-endian procedure to return the underlying bytes of `self`. - fn to_bytes_be_fallback(self) -> [u8; 32] { - let mut result = [0x00; 32]; - for i in 0..4 { - let o = i * 8; - result[o..(o + 8)].copy_from_slice(&self.0[i].to_le_bytes()); - } - result + #[inline(always)] + fn encoded_size(&self) -> usize { + self.size_hint() } } -impl Add for Key { - type Output = Key; +impl scale::EncodeLike<[u8; 32]> for Key {} - fn add(mut self, rhs: u64) -> Self::Output { - self += rhs; - self +impl scale::Decode for Key { + #[inline] + fn decode(input: &mut I) -> Result + where + I: scale::Input, + { + let bytes = <[u8; 32] as scale::Decode>::decode(input)?; + Ok(Self::from(bytes)) } -} - -impl Add for &Key { - type Output = Key; - fn add(self, rhs: u64) -> Self::Output { - >::add(*self, rhs) + #[inline(always)] + fn encoded_fixed_size() -> Option { + Some(32) } } -impl Add<&u64> for Key { - type Output = Key; - - fn add(self, rhs: &u64) -> Self::Output { - >::add(self, *rhs) +#[cfg(feature = "std")] +impl TypeInfo for Key { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("Key", "ink_primitives")) + .composite( + Fields::unnamed().field(|f| f.ty::<[u8; 32]>().type_name("[u8; 32]")), + ) } } -impl Add<&u64> for &Key { - type Output = Key; +impl Key { + /// Adds the `u64` value to the `Key`. + /// + /// # Note + /// + /// This implementation is heavily optimized for little-endian Wasm platforms. + /// + /// # Developer Note + /// + /// Since we are operating on little-endian we can convert the underlying `[u8; 32]` + /// array to `[u64; 4]`. Since in WebAssembly `u64` is supported natively unlike `u8` + /// it is more efficient to work on chunks of `u8` represented as `u64`. + #[cfg(target_endian = "little")] + fn add_assign_u64_le(&mut self, rhs: u64) { + let words = self.reinterpret_as_u64x4_mut(); + let (res0, ovfl) = words[0].overflowing_add(rhs); + let (res1, ovfl) = words[1].overflowing_add(ovfl as u64); + let (res2, ovfl) = words[2].overflowing_add(ovfl as u64); + let (res3, _ovfl) = words[3].overflowing_add(ovfl as u64); + words[0] = res0; + words[1] = res1; + words[2] = res2; + words[3] = res3; + } - fn add(self, rhs: &u64) -> Self::Output { - <&Key as Add>::add(self, *rhs) + /// Adds the `u64` value to the key storing the result in `result`. + /// + /// # Note + /// + /// This implementation is heavily optimized for little-endian Wasm platforms. + /// + /// # Developer Note + /// + /// Since we are operating on little-endian we can convert the underlying `[u8; 32]` + /// array to `[u64; 4]`. Since in WebAssembly `u64` is supported natively unlike `u8` + /// it is more efficient to work on chunks of `u8` represented as `u64`. + #[cfg(target_endian = "little")] + fn add_assign_u64_le_using(&self, rhs: u64, result: &mut Key) { + let input = self.reinterpret_as_u64x4(); + let result = result.reinterpret_as_u64x4_mut(); + let (res0, ovfl) = input[0].overflowing_add(rhs); + let (res1, ovfl) = input[1].overflowing_add(ovfl as u64); + let (res2, ovfl) = input[2].overflowing_add(ovfl as u64); + let (res3, _ovfl) = input[3].overflowing_add(ovfl as u64); + result[0] = res0; + result[1] = res1; + result[2] = res2; + result[3] = res3; } -} -#[cfg(feature = "std")] -const _: () = { - use scale_info::{ - build::Fields, - Path, - Type, - TypeInfo, - }; + /// Adds the `u64` value to the `Key`. + /// + /// # Note + /// + /// This is a fallback implementation that has not been optimized for any + /// specific target platform or endianness. + #[cfg(target_endian = "big")] + fn add_assign_u64_be(&mut self, rhs: u64) { + let rhs_bytes = rhs.to_be_bytes(); + let lhs_bytes = self.as_mut(); + let len_rhs = rhs_bytes.len(); + let len_lhs = lhs_bytes.len(); + let mut carry = 0; + for i in 0..len_rhs { + let (res, ovfl) = + lhs_bytes[i].overflowing_add(rhs_bytes[i].wrapping_add(carry)); + lhs_bytes[i] = res; + carry = ovfl as u8; + } + for i in len_rhs..len_lhs { + let (res, ovfl) = lhs_bytes[i].overflowing_add(carry); + lhs_bytes[i] = res; + carry = ovfl as u8; + if carry == 0 { + return + } + } + } - impl TypeInfo for Key { - type Identity = Self; + /// Adds the `u64` value to the key storing the result in `result`. + /// + /// # Note + /// + /// This is a fallback implementation that has not been optimized for any + /// specific target platform or endianness. + #[cfg(target_endian = "big")] + fn add_assign_u64_be_using(&self, rhs: u64, result: &mut Key) { + let rhs_bytes = rhs.to_be_bytes(); + let lhs_bytes = self.as_ref(); + let result_bytes = result.as_mut(); + let len_rhs = rhs_bytes.len(); + let len_lhs = lhs_bytes.len(); + let mut carry = 0; + for i in 0..len_rhs { + let (res, ovfl) = + lhs_bytes[i].overflowing_add(rhs_bytes[i].wrapping_add(carry)); + result_bytes[i] = res; + carry = ovfl as u8; + } + for i in len_rhs..len_lhs { + let (res, ovfl) = lhs_bytes[i].overflowing_add(carry); + result_bytes[i] = res; + carry = ovfl as u8; + // Note: We cannot bail out early in this case in order to + // guarantee that we fully overwrite the result key. + } + } - fn type_info() -> Type { - Type::builder() - .path(Path::new("Key", "ink_primitives")) - .composite( - Fields::unnamed().field(|f| f.ty::<[u8; 32]>().type_name("[u8; 32]")), - ) + /// Adds the `u64` value to the key storing the result in `result`. + /// + /// # Note + /// + /// This will overwrite the contents of the `result` key. + #[inline] + pub fn add_assign_using(&self, rhs: T, result: &mut Key) + where + T: Into, + { + let rhs = rhs.into(); + cfg_if! { + if #[cfg(target_endian = "little")] { + self.add_assign_u64_le_using(rhs, result); + } else { + self.add_assign_u64_be_using(rhs, result); + } } } -}; +} impl AddAssign for Key { #[inline] - #[rustfmt::skip] fn add_assign(&mut self, rhs: u64) { - let (res_0, ovfl_0) = self.0[0].overflowing_add(rhs); - let (res_1, ovfl_1) = self.0[1].overflowing_add(ovfl_0 as u64); - let (res_2, ovfl_2) = self.0[2].overflowing_add(ovfl_1 as u64); - let (res_3, _ovfl_3) = self.0[3].overflowing_add(ovfl_2 as u64); - self.0[0] = res_0; - self.0[1] = res_1; - self.0[2] = res_2; - self.0[3] = res_3; + cfg_if! { + if #[cfg(target_endian = "little")] { + self.add_assign_u64_le(rhs); + } else { + self.add_assign_u64_be(rhs); + } + } } } -#[cfg(test)] -mod tests { - use super::*; - - fn test_bytes() -> [u8; 32] { - *b"\ - \x00\x01\x02\x03\x04\x05\x06\x07\ - \x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\ - \x10\x11\x12\x13\x14\x15\x16\x17\ - \x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\ - " - } - - #[test] - fn default_works() { - assert_eq!(::default().to_bytes(), [0x00; 32]); - } - - #[test] - fn debug_works() { - let key = Key::from(test_bytes()); - assert_eq!( - format!("{:?}", key), - String::from( - "Key(0x\ - _0001020304050607\ - _08090A0B0C0D0E0F\ - _1011121314151617\ - _18191A1B1C1D1E1F\ - )" - ), - ); - } - - #[test] - #[rustfmt::skip] - fn from_works() { - let test_bytes = test_bytes(); - assert_eq!(Key::from(test_bytes).to_bytes(), test_bytes); - assert_eq!(Key::from_bytes_be_fallback(test_bytes).to_bytes(), test_bytes); - assert_eq!(Key::from(test_bytes).to_bytes_be_fallback(), test_bytes); - assert_eq!(Key::from_bytes_be_fallback(test_bytes).to_bytes_be_fallback(), test_bytes); - } - - #[test] - fn add_one_to_zero() { - let bytes = [0x00; 32]; - let expected = { - let mut bytes = [0x00; 32]; - bytes[0] = 0x01; - bytes - }; - let mut key = Key::from(bytes); - key.add_assign(1u64); - assert_eq!(key.to_bytes(), expected); - } - - #[test] - fn add_with_ovfl() { - let bytes = *b"\ - \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ - \x00\x00\x00\x00\x00\x00\x00\x00\ - \x00\x00\x00\x00\x00\x00\x00\x00\ - \x00\x00\x00\x00\x00\x00\x00\x00\ - "; - let expected = { - let mut expected = [0x00; 32]; - expected[8] = 0x01; - expected - }; - let mut key = Key::from(bytes); - key.add_assign(1u64); - assert_eq!(key.to_bytes(), expected); - } - - #[test] - fn add_with_ovfl_2() { - let bytes = *b"\ - \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ - \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ - \x00\x00\x00\x00\x00\x00\x00\x00\ - \x00\x00\x00\x00\x00\x00\x00\x00\ - "; - let expected = { - let mut expected = [0x00; 32]; - expected[16] = 0x01; - expected - }; - let mut key = Key::from(bytes); - key.add_assign(1u64); - assert_eq!(key.to_bytes(), expected); - } - - #[test] - fn add_with_ovfl_3() { - let bytes = *b"\ - \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ - \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ - \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ - \x00\x00\x00\x00\x00\x00\x00\x00\ - "; - let expected = { - let mut expected = [0x00; 32]; - expected[24] = 0x01; - expected - }; - let mut key = Key::from(bytes); - key.add_assign(1u64); - assert_eq!(key.to_bytes(), expected); - } - - #[test] - fn add_with_wrap() { - let bytes = [0xFF; 32]; - let expected = [0x00; 32]; - let mut key = Key::from(bytes); - key.add_assign(1u64); - assert_eq!(key.to_bytes(), expected); - } - - #[test] - fn add_assign_to_zero() { - for test_value in &[0_u64, 1, 42, 10_000, u32::MAX as u64, u64::MAX] { - let mut key = ::default(); - let expected = { - let mut expected = [0x00; 32]; - expected[0..8].copy_from_slice(&test_value.to_le_bytes()); - expected - }; - key.add_assign(*test_value); - assert_eq!(key.to_bytes(), expected); - } +impl AddAssign<&u64> for Key { + #[inline] + fn add_assign(&mut self, rhs: &u64) { + >::add_assign(self, *rhs) } } diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 729e31679ae..4addb36532b 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -26,6 +26,9 @@ mod key; mod key_ptr; +#[cfg(test)] +mod tests; + pub use self::{ key::Key, key_ptr::KeyPtr, diff --git a/crates/primitives/src/tests.rs b/crates/primitives/src/tests.rs new file mode 100644 index 00000000000..3205990a523 --- /dev/null +++ b/crates/primitives/src/tests.rs @@ -0,0 +1,294 @@ +// Copyright 2018-2021 Parity Technologies (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::Key; + +const TEST_BYTES: [u8; 32] = *b"\ + \x00\x01\x02\x03\x04\x05\x06\x07\ + \x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\ + \x10\x11\x12\x13\x14\x15\x16\x17\ + \x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\ + "; + +mod key { + use super::*; + use core::ops::AddAssign; + use scale::{ + Decode, + Encode, + }; + + #[test] + fn default_works() { + let mut default_key = ::default(); + assert_eq!(default_key, Key::from([0x00_u8; 32])); + assert_eq!(default_key.as_ref(), &[0x00_u8; 32]); + assert_eq!(default_key.as_mut(), &mut [0x00_u8; 32]); + } + + #[test] + fn debug_works() { + let key = Key::from(TEST_BYTES); + assert_eq!( + format!("{:?}", key), + String::from( + "Key(0x\ + _00010203_04050607\ + _08090A0B_0C0D0E0F\ + _10111213_14151617\ + _18191A1B_1C1D1E1F\ + )" + ), + ); + } + + #[test] + fn display_works() { + let key = Key::from(TEST_BYTES); + assert_eq!( + format!("{}", key), + String::from( + "0x\ + _00010203_04050607\ + _08090A0B_0C0D0E0F\ + _10111213_14151617\ + _18191A1B_1C1D1E1F" + ), + ); + } + + #[test] + fn from_works() { + let mut bytes = TEST_BYTES; + assert_eq!(Key::from(TEST_BYTES).as_ref(), &bytes); + assert_eq!(Key::from(TEST_BYTES).as_mut(), &mut bytes); + } + + #[test] + fn encode_decode_works() { + let key = Key::from(TEST_BYTES); + let encoded = key.encode(); + let decoded = Key::decode(&mut &encoded[..]).unwrap(); + assert_eq!(key, decoded); + } + + #[test] + fn encode_works() { + let bytes = TEST_BYTES; + let encoded = Key::from(bytes).encode(); + assert_eq!(encoded, bytes); + } + + #[test] + fn decode_works() { + let bytes = TEST_BYTES; + let decoded = Key::decode(&mut &bytes[..]).unwrap(); + assert_eq!(decoded, Key::from(bytes)); + } + + #[test] + fn codec_hints_work() { + let key = Key::default(); + assert_eq!(key.size_hint(), 32); + assert_eq!(key.encoded_size(), 32); + assert_eq!(Key::encoded_fixed_size(), Some(32)); + } + + #[test] + fn add_assign_one_to_zero_works() { + let bytes = [0x00; 32]; + let expected = { + let mut bytes = [0x00; 32]; + bytes[0] = 0x01; + bytes + }; + let mut key = Key::from(bytes); + key.add_assign(1u64); + assert_eq!(key.as_ref(), &expected); + } + + #[test] + fn add_assign_using_one_to_zero_works() { + let bytes = [0x00; 32]; + let expected = { + let mut bytes = [0x00; 32]; + bytes[0] = 0x01; + bytes + }; + let input = Key::from(bytes); + let mut result = Key::default(); + input.add_assign_using(1u64, &mut result); + assert_eq!(result.as_ref(), &expected); + } + + const OVERFLOW_1_TEST_BYTES: [u8; 32] = *b"\ + \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ + \x00\x00\x00\x00\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x00\ + "; + + #[test] + fn add_assign_with_ovfl_1_works() { + let expected = { + let mut expected = [0x00; 32]; + expected[8] = 0x01; + expected + }; + let mut key = Key::from(OVERFLOW_1_TEST_BYTES); + key.add_assign(1u64); + assert_eq!(key.as_ref(), &expected); + } + + #[test] + fn add_assign_using_with_ovfl_1_works() { + let expected = { + let mut expected = [0x00; 32]; + expected[8] = 0x01; + expected + }; + let input = Key::from(OVERFLOW_1_TEST_BYTES); + let mut result = Key::default(); + input.add_assign_using(1u64, &mut result); + assert_eq!(result.as_ref(), &expected); + } + + const OVERFLOW_2_TEST_BYTES: [u8; 32] = *b"\ + \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ + \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ + \x00\x00\x00\x00\x00\x00\x00\x00\ + \x00\x00\x00\x00\x00\x00\x00\x00\ + "; + + #[test] + fn add_assign_with_ovfl_2_works() { + let expected = { + let mut expected = [0x00; 32]; + expected[16] = 0x01; + expected + }; + let mut key = Key::from(OVERFLOW_2_TEST_BYTES); + key.add_assign(1u64); + assert_eq!(key.as_ref(), &expected); + } + + #[test] + fn add_assign_using_with_ovfl_2_works() { + let expected = { + let mut expected = [0x00; 32]; + expected[16] = 0x01; + expected + }; + let input = Key::from(OVERFLOW_2_TEST_BYTES); + let mut result = Key::default(); + input.add_assign_using(1u64, &mut result); + assert_eq!(result.as_ref(), &expected); + } + + const OVERFLOW_3_TEST_BYTES: [u8; 32] = *b"\ + \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ + \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ + \xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\ + \x00\x00\x00\x00\x00\x00\x00\x00\ + "; + + #[test] + fn add_assign_with_ovfl_3_works() { + let expected = { + let mut expected = [0x00; 32]; + expected[24] = 0x01; + expected + }; + let mut key = Key::from(OVERFLOW_3_TEST_BYTES); + key.add_assign(1u64); + assert_eq!(key.as_ref(), &expected); + } + + #[test] + fn add_assign_using_with_ovfl_3_works() { + let expected = { + let mut expected = [0x00; 32]; + expected[24] = 0x01; + expected + }; + let input = Key::from(OVERFLOW_3_TEST_BYTES); + let mut result = Key::default(); + input.add_assign_using(1u64, &mut result); + assert_eq!(result.as_ref(), &expected); + } + + #[test] + fn add_assign_with_wrap_works() { + const BYTES: [u8; 32] = [0xFF; 32]; + let expected = [0x00; 32]; + let mut key = Key::from(BYTES); + key.add_assign(1u64); + assert_eq!(key.as_ref(), &expected); + } + + #[test] + fn add_assign_using_with_wrap_works() { + const BYTES: [u8; 32] = [0xFF; 32]; + let expected = [0x00; 32]; + let input = Key::from(BYTES); + let mut result = Key::default(); + input.add_assign_using(1u64, &mut result); + assert_eq!(result.as_ref(), &expected); + } + + #[test] + fn add_assign_to_zero_works() { + const TEST_VALUES: &[u64] = &[0, 1, 42, 10_000, u32::MAX as u64, u64::MAX]; + for test_value in TEST_VALUES { + let mut key = ::default(); + let expected = { + let mut expected = [0x00; 32]; + expected[0..8].copy_from_slice(&test_value.to_le_bytes()); + expected + }; + key += test_value; + assert_eq!(key.as_ref(), &expected); + } + } + + #[test] + fn add_assign_using_to_zero_works() { + const TEST_VALUES: &[u64] = &[0, 1, 42, 10_000, u32::MAX as u64, u64::MAX]; + let zero = ::default(); + for test_value in TEST_VALUES { + let expected = { + let mut expected = [0x00; 32]; + expected[0..8].copy_from_slice(&test_value.to_le_bytes()); + expected + }; + let mut result = Key::default(); + zero.add_assign_using(*test_value, &mut result); + assert_eq!(result.as_ref(), &expected); + } + } + + #[test] + fn add_assign_using_override_works() { + let bytes = [0x00; 32]; + let expected = { + let mut bytes = [0x00; 32]; + bytes[0] = 0x01; + bytes + }; + let input = Key::from(bytes); + let mut result = Key::from([0xFF; 32]); + input.add_assign_using(1u64, &mut result); + assert_eq!(result.as_ref(), &expected); + } +} diff --git a/crates/storage/src/lazy/lazy_array.rs b/crates/storage/src/lazy/lazy_array.rs index 58494c27d19..95eedafb8d4 100644 --- a/crates/storage/src/lazy/lazy_array.rs +++ b/crates/storage/src/lazy/lazy_array.rs @@ -381,10 +381,11 @@ where fn push_spread(&self, ptr: &mut KeyPtr) { let offset_key = ExtKeyPtr::next_for::(ptr); + let mut root_key = Key::default(); for (index, entry) in self.cached_entries().iter().enumerate() { if let Some(entry) = entry { - let key = offset_key + (index as u64); - entry.push_packed_root(&key); + offset_key.add_assign_using(index as u64, &mut root_key); + entry.push_packed_root(&root_key); } } } @@ -404,7 +405,10 @@ impl LazyArray { if at >= self.capacity() { return None } - self.key.as_ref().map(|key| key + at as u64) + self.key.map(|mut key| { + key += at as u64; + key + }) } } @@ -585,6 +589,12 @@ mod tests { assert_cached_entries(&default_larray, &[]); } + fn add_key(key: &Key, offset: u64) -> Key { + let mut result = *key; + result += offset; + result + } + #[test] fn lazy_works() { let key = Key::from([0x42; 32]); @@ -592,7 +602,7 @@ mod tests { // Key must be Some. assert_eq!(larray.key(), Some(&key)); assert_eq!(larray.key_at(0), Some(key)); - assert_eq!(larray.key_at(1), Some(key + 1u64)); + assert_eq!(larray.key_at(1), Some(add_key(&key, 1))); assert_eq!(larray.capacity(), 4); // Cached elements must be empty. assert_cached_entries(&larray, &[]); diff --git a/crates/storage/src/lazy/lazy_cell.rs b/crates/storage/src/lazy/lazy_cell.rs index 6a2301a3102..7350676a205 100644 --- a/crates/storage/src/lazy/lazy_cell.rs +++ b/crates/storage/src/lazy/lazy_cell.rs @@ -94,10 +94,10 @@ fn debug_impl_works() -> ink_env::Result<()> { format!("{:?}", &c3), "LazyCell { \ key: Some(Key(0x_\ - 0000000000000000_\ - 0000000000000000_\ - 0000000000000000_\ - 0000000000000000)\ + 00000000_00000000_\ + 00000000_00000000_\ + 00000000_00000000_\ + 00000000_00000000)\ ), \ cache: None \ }", diff --git a/crates/storage/src/lazy/lazy_imap.rs b/crates/storage/src/lazy/lazy_imap.rs index 318189881cf..f7622c8f8df 100644 --- a/crates/storage/src/lazy/lazy_imap.rs +++ b/crates/storage/src/lazy/lazy_imap.rs @@ -248,8 +248,9 @@ where fn push_spread(&self, ptr: &mut KeyPtr) { let offset_key = ExtKeyPtr::next_for::(ptr); + let mut root_key = Key::default(); for (&index, entry) in self.entries().iter() { - let root_key = offset_key + (index as u64); + offset_key.add_assign_using(index, &mut root_key); entry.push_packed_root(&root_key); } } @@ -299,9 +300,10 @@ where { /// Returns an offset key for the given index. pub fn key_at(&self, index: Index) -> Option { - let key = self.key?; - let offset_key = key + index as u64; - Some(offset_key) + self.key.map(|mut key| { + key += index as u64; + key + }) } /// Lazily loads the value at the given index. @@ -492,6 +494,12 @@ mod tests { assert_eq!(imap.entries(), default_imap.entries()); } + fn add_key(key: &Key, offset: u64) -> Key { + let mut result = *key; + result += offset; + result + } + #[test] fn lazy_works() { let key = Key::from([0x42; 32]); @@ -499,7 +507,7 @@ mod tests { // Key must be none. assert_eq!(imap.key(), Some(&key)); assert_eq!(imap.key_at(0), Some(key)); - assert_eq!(imap.key_at(1), Some(key + 1u64)); + assert_eq!(imap.key_at(1), Some(add_key(&key, 1))); // Cached elements must be empty. assert_cached_entries(&imap, &[]); }