Unverified Commit ee9015df authored by Robin Freyler's avatar Robin Freyler Committed by GitHub
Browse files

Base Key on [u8; 32] (#971)

* implement new Key2 primitive that is based on [u8; 32]

* implement Key::add_assign_u64_using method

* integrate new Key type into the rest of the ink! codebase

* rename key2.rs -> key.rs

* fix some Wasm build bugs with new Key type integration

* add #[inline] to Key::add_assign_u64_using method

* optimize Key add assign methods

Somehow those new implementations optimize better ...

* apply rustfmt

* fix hunspell issues

* fix clippy issues

* improve LazyArray and LazyIndexMap impls

* rename add_assign_u64_using to add_assign_using and make it generic

Generic over T where T: Into<u64> so it accepts u32 as well etc.

* remove unnecessary cast
parent e8d47396
Pipeline #163090 passed with stages
in 31 minutes and 22 seconds
100
110
ABI
AST
......@@ -99,5 +99,6 @@ vector/S
implementer/S
deduplicated
wildcard/S
natively
payability
unpayable
......@@ -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<R>(&mut self, key: &Key) -> Result<Option<R>>
......@@ -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<T>(&mut self) -> Result<T>
......@@ -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,
......
......@@ -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::<crate::DefaultEnvironment, _>(|_| {
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::<i32>(&key10), Ok(Some(42)));
crate::set_contract_storage(&key10, &1337);
......@@ -51,9 +57,9 @@ fn key_add_sub() -> Result<()> {
crate::test::run_test::<crate::DefaultEnvironment, _>(|_| {
// 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);
......
......@@ -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<R>(&mut self, key: &Key) -> Result<Option<R>>
......@@ -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<T>(&mut self) -> Result<T>
......
......@@ -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<Key> for LayoutKey {
fn from(key: Key) -> Self {
Self {
key: key.to_bytes(),
}
Self { key: *key.as_ref() }
}
}
......
......@@ -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"
......
......@@ -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<T: scale::Output + ?Sized>(&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<I: scale::Input>(input: &mut I) -> Result<Self, scale::Error> {
Ok(Self::from(<[u8; 32] as scale::Decode>::decode(input)?))
fn encode_to<O>(&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<R, F>(&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<u64> 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<I>(input: &mut I) -> Result<Self, scale::Error>
where
I: scale::Input,
{
let bytes = <[u8; 32] as scale::Decode>::decode(input)?;
Ok(Self::from(bytes))
}
}
impl Add<u64> for &Key {
type Output = Key;
fn add(self, rhs: u64) -> Self::Output {
<Key as Add<u64>>::add(*self, rhs)
#[inline(always)]
fn encoded_fixed_size() -> Option<usize> {
Some(32)
}
}
impl Add<&u64> for Key {
type Output = Key;
fn add(self, rhs: &u64) -> Self::Output {
<Key as Add<u64>>::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<u64>>::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.