diff --git a/crates/lang/codegen/src/generator/dispatch.rs b/crates/lang/codegen/src/generator/dispatch.rs index 8209f2ee0f0..f2b21733e68 100644 --- a/crates/lang/codegen/src/generator/dispatch.rs +++ b/crates/lang/codegen/src/generator/dispatch.rs @@ -566,17 +566,12 @@ impl Dispatch<'_> { }>>::IDS[#index] }>>::PAYABLE ); - let is_dynamic_storage_allocation_enabled = self - .contract - .config() - .is_dynamic_storage_allocator_enabled(); quote_spanned!(constructor_span=> Self::#constructor_ident(input) => { ::ink_lang::codegen::execute_constructor::<#storage_ident, _, _>( ::ink_lang::codegen::ExecuteConstructorConfig { payable: #accepts_payment, - dynamic_storage_alloc: #is_dynamic_storage_allocation_enabled }, move || { #constructor_callable(input) } ) @@ -745,17 +740,12 @@ impl Dispatch<'_> { }>>::IDS[#index] }>>::MUTATES ); - let is_dynamic_storage_allocation_enabled = self - .contract - .config() - .is_dynamic_storage_allocator_enabled(); quote_spanned!(message_span=> Self::#message_ident(input) => { let config = ::ink_lang::codegen::ExecuteMessageConfig { payable: #accepts_payment, mutates: #mutates_storage, - dynamic_storage_alloc: #is_dynamic_storage_allocation_enabled, }; let mut contract: ::core::mem::ManuallyDrop<#storage_ident> = ::core::mem::ManuallyDrop::new( diff --git a/crates/lang/ir/src/ir/config.rs b/crates/lang/ir/src/ir/config.rs index 576e0f3b0f8..1b9be33ad5c 100644 --- a/crates/lang/ir/src/ir/config.rs +++ b/crates/lang/ir/src/ir/config.rs @@ -24,11 +24,6 @@ use syn::spanned::Spanned; /// The ink! configuration. #[derive(Debug, Default, PartialEq, Eq)] pub struct Config { - /// If `true` enables the dynamic storage allocator - /// facilities and code generation of the ink! smart - /// contract. Does incur some overhead. The default is - /// `true`. - dynamic_storage_allocator: Option, /// If `true` compiles this ink! smart contract always as /// if it was a dependency of another smart contract. /// This configuration is mainly needed for testing and @@ -125,29 +120,12 @@ impl TryFrom for Config { type Error = syn::Error; fn try_from(args: ast::AttributeArgs) -> Result { - let mut dynamic_storage_allocator: Option<(bool, ast::MetaNameValue)> = None; let mut as_dependency: Option<(bool, ast::MetaNameValue)> = None; let mut env: Option<(Environment, ast::MetaNameValue)> = None; let mut whitelisted_attributes = WhitelistedAttributes::default(); for arg in args.into_iter() { - if arg.name.is_ident("dynamic_storage_allocator") { - if let Some((_, ast)) = dynamic_storage_allocator { - return Err(duplicate_config_err( - ast, - arg, - "dynamic_storage_allocator", - )) - } - if let ast::PathOrLit::Lit(syn::Lit::Bool(lit_bool)) = &arg.value { - dynamic_storage_allocator = Some((lit_bool.value, arg)) - } else { - return Err(format_err_spanned!( - arg, - "expected a bool literal for `dynamic_storage_allocator` ink! configuration argument", - )) - } - } else if arg.name.is_ident("compile_as_dependency") { + if arg.name.is_ident("compile_as_dependency") { if let Some((_, ast)) = as_dependency { return Err(duplicate_config_err(ast, arg, "compile_as_dependency")) } @@ -183,7 +161,6 @@ impl TryFrom for Config { } } Ok(Config { - dynamic_storage_allocator: dynamic_storage_allocator.map(|(value, _)| value), as_dependency: as_dependency.map(|(value, _)| value), env: env.map(|(value, _)| value), whitelisted_attributes, @@ -203,14 +180,6 @@ impl Config { .unwrap_or(Environment::default().path) } - /// Returns `true` if the dynamic storage allocator facilities are enabled - /// for the ink! smart contract, `false` otherwise. - /// - /// If nothing has been specified returns the default which is `false`. - pub fn is_dynamic_storage_allocator_enabled(&self) -> bool { - self.dynamic_storage_allocator.unwrap_or(false) - } - /// Return `true` if this ink! smart contract shall always be compiled as /// if it was a dependency of another smart contract, returns `false` /// otherwise. @@ -263,29 +232,6 @@ mod tests { assert_try_from(syn::parse_quote! {}, Ok(Config::default())) } - #[test] - fn storage_alloc_works() { - assert_try_from( - syn::parse_quote! { - dynamic_storage_allocator = true - }, - Ok(Config { - dynamic_storage_allocator: Some(true), - as_dependency: None, - env: None, - whitelisted_attributes: Default::default(), - }), - ) - } - - #[test] - fn storage_alloc_invalid_value_fails() { - assert_try_from( - syn::parse_quote! { dynamic_storage_allocator = "invalid" }, - Err("expected a bool literal for `dynamic_storage_allocator` ink! configuration argument"), - ) - } - #[test] fn as_dependency_works() { assert_try_from( @@ -293,7 +239,6 @@ mod tests { compile_as_dependency = false }, Ok(Config { - dynamic_storage_allocator: None, as_dependency: Some(false), env: None, whitelisted_attributes: Default::default(), @@ -318,7 +263,6 @@ mod tests { env = ::my::env::Types }, Ok(Config { - dynamic_storage_allocator: None, as_dependency: None, env: Some(Environment { path: syn::parse_quote! { ::my::env::Types }, @@ -365,7 +309,6 @@ mod tests { keep_attr = "foo, bar" }, Ok(Config { - dynamic_storage_allocator: None, as_dependency: None, env: None, whitelisted_attributes: attrs, diff --git a/crates/lang/ir/src/ir/contract.rs b/crates/lang/ir/src/ir/contract.rs index 335ed5a7533..3ae0e9e8ec2 100644 --- a/crates/lang/ir/src/ir/contract.rs +++ b/crates/lang/ir/src/ir/contract.rs @@ -100,10 +100,6 @@ impl Contract { /// /// - `types`: To specify `Environment` different from the default environment /// types. - /// - `storage-alloc`: If `true` enables the dynamic storage allocator - /// facilities and code generation of the ink! smart - /// contract. Does incur some overhead. The default is - /// `true`. /// - `as-dependency`: If `true` compiles this ink! smart contract always as /// if it was a dependency of another smart contract. /// This configuration is mainly needed for testing and diff --git a/crates/lang/macro/src/lib.rs b/crates/lang/macro/src/lib.rs index 2069b36f0ab..4a50de36442 100644 --- a/crates/lang/macro/src/lib.rs +++ b/crates/lang/macro/src/lib.rs @@ -119,53 +119,6 @@ pub fn selector_bytes(input: TokenStream) -> TokenStream { /// The `#[ink::contract]` macro can be provided with some additional comma-separated /// header arguments: /// -/// - `dynamic_storage_allocator: bool` -/// -/// Tells the ink! code generator to allow usage of ink!'s built-in dynamic -/// storage allocator. -/// - `true`: Use the dynamic storage allocator provided by ink!. -/// - `false`: Do NOT use the dynamic storage allocator provided by ink!. -/// -/// This feature is generally only needed for smart contracts that try to model -/// their data in a way that contains storage entities within other storage -/// entities. -/// -/// Contract writers should try to write smart contracts that do not depend on the -/// dynamic storage allocator since enabling it comes at a cost of increased Wasm -/// file size. Although it will enable interesting use cases. Use it with care! -/// -/// An example for this is the following type that could potentially be used -/// within a contract's storage struct definition: -/// -/// -/// ```ignore -/// # // Tracking issue [#1119]: Right now we've hidden the `StorageVec` from public access so -/// # // this doesn't compile. -/// # use ink_storage as storage; -/// # type _unused = -/// storage::Vec> -/// # ; -/// ``` -/// -/// **Usage Example:** -/// ``` -/// # use ink_lang as ink; -/// #[ink::contract(dynamic_storage_allocator = true)] -/// mod my_contract { -/// # #[ink(storage)] -/// # pub struct MyStorage; -/// # impl MyStorage { -/// # #[ink(constructor)] -/// # pub fn construct() -> Self { MyStorage {} } -/// # #[ink(message)] -/// # pub fn message(&self) {} -/// # } -/// // ... -/// } -/// ``` -/// -/// **Default value:** `false` -/// /// - `compile_as_dependency: bool` /// /// Tells the ink! code generator to **always** or **never** @@ -710,7 +663,7 @@ pub fn contract(attr: TokenStream, item: TokenStream) -> TokenStream { /// pub trait TraitDefinition { /// #[ink(message)] /// fn message1(&self); -/// +/// /// #[ink(message, selector = 42)] /// fn message2(&self); /// } diff --git a/crates/lang/src/codegen/dispatch/execution.rs b/crates/lang/src/codegen/dispatch/execution.rs index 0e1a871c695..363e3b802f2 100644 --- a/crates/lang/src/codegen/dispatch/execution.rs +++ b/crates/lang/src/codegen/dispatch/execution.rs @@ -29,15 +29,11 @@ use ink_primitives::{ Key, KeyPtr, }; -use ink_storage::{ - alloc, - alloc::ContractPhase, - traits::{ - pull_spread_root, - push_spread_root, - SpreadAllocate, - SpreadLayout, - }, +use ink_storage::traits::{ + pull_spread_root, + push_spread_root, + SpreadAllocate, + SpreadLayout, }; /// The root key of the ink! smart contract. @@ -75,12 +71,6 @@ where pub struct ExecuteConstructorConfig { /// Yields `true` if the ink! constructor accepts payment. pub payable: bool, - /// Yields `true` if the dynamic storage allocator has been enabled. - /// - /// # Note - /// - /// Authors can enable it via `#[ink::contract(dynamic_storage_allocator = true)]`. - pub dynamic_storage_alloc: bool, } /// Executes the given ink! constructor. @@ -103,9 +93,6 @@ where if !config.payable { deny_payment::<::Env>()?; } - if config.dynamic_storage_alloc { - alloc::initialize(ContractPhase::Deploy); - } let result = ManuallyDrop::new(private::Seal(f())); match result.as_result() { Ok(contract) => { @@ -114,9 +101,6 @@ where // This requires us to sync back the changes of the contract storage. let root_key = ::ROOT_KEY; push_spread_root::(contract, &root_key); - if config.dynamic_storage_alloc { - alloc::finalize(); - } Ok(()) } Err(_) => { @@ -292,12 +276,6 @@ pub struct ExecuteMessageConfig { /// /// This is usually true for `&mut self` ink! messages. pub mutates: bool, - /// Yields `true` if the dynamic storage allocator has been enabled. - /// - /// # Note - /// - /// Authors can enable it via `#[ink::contract(dynamic_storage_allocator = true)]`. - pub dynamic_storage_alloc: bool, } /// Initiates an ink! message call with the given configuration. @@ -319,9 +297,6 @@ where if !config.payable { deny_payment::<::Env>()?; } - if config.dynamic_storage_alloc { - alloc::initialize(ContractPhase::Call); - } let root_key = Key::from([0x00; 32]); let contract = pull_spread_root::(&root_key); Ok(contract) @@ -374,9 +349,6 @@ where let root_key = Key::from([0x00; 32]); push_spread_root::(contract, &root_key); } - if config.dynamic_storage_alloc { - alloc::finalize(); - } if TypeId::of::() != TypeId::of::<()>() { // In case the return type is `()` we do not return a value. ink_env::return_value::(ReturnFlags::default(), result) diff --git a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-01.rs b/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-01.rs deleted file mode 100644 index 99217339287..00000000000 --- a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-01.rs +++ /dev/null @@ -1,19 +0,0 @@ -use ink_lang as ink; - -#[ink::contract(dynamic_storage_allocator = "foo")] -mod contract { - #[ink(storage)] - pub struct Contract {} - - impl Contract { - #[ink(constructor)] - pub fn constructor() -> Self { - Self {} - } - - #[ink(message)] - pub fn message(&self) {} - } -} - -fn main() {} diff --git a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-01.stderr b/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-01.stderr deleted file mode 100644 index 51071b94c35..00000000000 --- a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-01.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: expected a bool literal for `dynamic_storage_allocator` ink! configuration argument - --> tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-01.rs:3:17 - | -3 | #[ink::contract(dynamic_storage_allocator = "foo")] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-02.rs b/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-02.rs deleted file mode 100644 index 7a05d13161d..00000000000 --- a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-02.rs +++ /dev/null @@ -1,19 +0,0 @@ -use ink_lang as ink; - -#[ink::contract(dynamic_storage_allocator = 42)] -mod contract { - #[ink(storage)] - pub struct Contract {} - - impl Contract { - #[ink(constructor)] - pub fn constructor() -> Self { - Self {} - } - - #[ink(message)] - pub fn message(&self) {} - } -} - -fn main() {} diff --git a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-02.stderr b/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-02.stderr deleted file mode 100644 index 63b1373fb1d..00000000000 --- a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-02.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: expected a bool literal for `dynamic_storage_allocator` ink! configuration argument - --> tests/ui/contract/fail/config-dynamic-storage-allocator-invalid-type-02.rs:3:17 - | -3 | #[ink::contract(dynamic_storage_allocator = 42)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-missing-arg.rs b/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-missing-arg.rs deleted file mode 100644 index c30ebd1c9e8..00000000000 --- a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-missing-arg.rs +++ /dev/null @@ -1,19 +0,0 @@ -use ink_lang as ink; - -#[ink::contract(dynamic_storage_allocator)] -mod contract { - #[ink(storage)] - pub struct Contract {} - - impl Contract { - #[ink(constructor)] - pub fn constructor() -> Self { - Self {} - } - - #[ink(message)] - pub fn message(&self) {} - } -} - -fn main() {} diff --git a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-missing-arg.stderr b/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-missing-arg.stderr deleted file mode 100644 index b19ec2b4275..00000000000 --- a/crates/lang/tests/ui/contract/fail/config-dynamic-storage-allocator-missing-arg.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: ink! config options require an argument separated by '=' - --> tests/ui/contract/fail/config-dynamic-storage-allocator-missing-arg.rs:3:17 - | -3 | #[ink::contract(dynamic_storage_allocator)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/crates/lang/tests/ui/contract/pass/config-dynamic-storage-allocator-false.rs b/crates/lang/tests/ui/contract/pass/config-dynamic-storage-allocator-false.rs deleted file mode 100644 index 953d0afb4fe..00000000000 --- a/crates/lang/tests/ui/contract/pass/config-dynamic-storage-allocator-false.rs +++ /dev/null @@ -1,19 +0,0 @@ -use ink_lang as ink; - -#[ink::contract(dynamic_storage_allocator = true)] -mod contract { - #[ink(storage)] - pub struct Contract {} - - impl Contract { - #[ink(constructor)] - pub fn constructor() -> Self { - Self {} - } - - #[ink(message)] - pub fn message(&self) {} - } -} - -fn main() {} diff --git a/crates/lang/tests/ui/contract/pass/config-dynamic-storage-allocator-true.rs b/crates/lang/tests/ui/contract/pass/config-dynamic-storage-allocator-true.rs deleted file mode 100644 index 953d0afb4fe..00000000000 --- a/crates/lang/tests/ui/contract/pass/config-dynamic-storage-allocator-true.rs +++ /dev/null @@ -1,19 +0,0 @@ -use ink_lang as ink; - -#[ink::contract(dynamic_storage_allocator = true)] -mod contract { - #[ink(storage)] - pub struct Contract {} - - impl Contract { - #[ink(constructor)] - pub fn constructor() -> Self { - Self {} - } - - #[ink(message)] - pub fn message(&self) {} - } -} - -fn main() {} diff --git a/crates/storage/src/alloc/allocation.rs b/crates/storage/src/alloc/allocation.rs deleted file mode 100644 index 542d0aac51b..00000000000 --- a/crates/storage/src/alloc/allocation.rs +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2018-2022 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 ink_env::hash::{ - Blake2x256, - CryptoHash, - HashOutput, -}; -use ink_primitives::Key; - -/// A unique dynamic allocation. -/// -/// This can refer to a dynamically allocated storage cell. -/// It has been created by a dynamic storage allocator. -/// The initiator of the allocation has to make sure to deallocate -/// this dynamic allocation again using the same dynamic allocator -/// if it is no longer in use. -#[derive( - Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, scale::Encode, scale::Decode, -)] -#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] -pub struct DynamicAllocation(pub(super) u32); - -/// Wraps a bytes buffer and turns it into an accumulator. -/// -/// # Panics -/// -/// Upon hash calculation if the underlying buffer length does not suffice the -/// needs of the accumulated hash buffer. -struct Wrap<'a> { - /// The underlying wrapped buffer. - buffer: &'a mut [u8], - /// The current length of the filled area. - len: usize, -} - -impl Wrap<'_> { - /// Returns the capacity of the underlying buffer. - fn capacity(&self) -> usize { - self.buffer.len() - } - - /// Returns the length of the underlying buffer. - fn len(&self) -> usize { - self.len - } - - /// Appends the given bytes to the end of the wrapped buffer. - fn append_bytes(&mut self, bytes: &[u8]) { - debug_assert!(self.len() + bytes.len() <= self.capacity()); - let len = self.len; - let bytes_len = bytes.len(); - self.buffer[len..(len + bytes_len)].copy_from_slice(bytes); - self.len += bytes_len; - } -} - -#[cfg(feature = "std")] -impl<'a> std::io::Write for Wrap<'a> { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - self.append_bytes(buf); - Ok(buf.len()) - } - - fn flush(&mut self) -> std::io::Result<()> { - Ok(()) - } -} - -impl<'a> From<&'a mut [u8]> for Wrap<'a> { - fn from(buffer: &'a mut [u8]) -> Self { - Self { buffer, len: 0 } - } -} - -#[cfg(not(feature = "std"))] -impl<'a> scale::Output for Wrap<'a> { - fn write(&mut self, bytes: &[u8]) { - self.append_bytes(bytes) - } - - fn push_byte(&mut self, byte: u8) { - debug_assert!(self.len() < self.capacity()); - self.buffer[self.len] = byte; - self.len += 1; - } -} - -impl DynamicAllocation { - /// Returns the allocation identifier as `u32`. - pub(super) fn get(self) -> u32 { - self.0 - } - - /// Returns the storage key associated with this dynamic allocation. - pub fn key(self) -> Key { - // We create a 25-bytes buffer for the hashing. - // This is due to the fact that we prepend the `u32` encoded identifier - // with the `b"DYNAMICALLY ALLOCATED"` byte string which has a length - // 21 bytes. Since `u32` always has an encoding length of 4 bytes we - // end up requiring 25 bytes in total. - // Optimization Opportunity: - // Since ink! always runs single threaded we could make this buffer - // static and instead reuse its contents with every invocation of this - // method. However, this would introduce `unsafe` Rust usage. - pub struct EncodeWrapper(u32); - impl scale::Encode for EncodeWrapper { - #[rustfmt::skip] - fn encode_to(&self, output: &mut O) - where - O: scale::Output + ?Sized, - { - <[u8; 21] as scale::Encode>::encode_to(&[ - b'D', b'Y', b'N', b'A', b'M', b'I', b'C', b'A', b'L', b'L', b'Y', - b' ', - b'A', b'L', b'L', b'O', b'C', b'A', b'T', b'E', b'D', - ], output); - ::encode_to(&self.0, output); - } - } - // Encode the `u32` identifier requires a 4 bytes buffer. - #[rustfmt::skip] - let mut buffer: [u8; 25] = [ - b'D', b'Y', b'N', b'A', b'M', b'I', b'C', b'A', b'L', b'L', b'Y', - b' ', - b'A', b'L', b'L', b'O', b'C', b'A', b'T', b'E', b'D', - b'_', b'_', b'_', b'_', - ]; - { - let mut wrapped = Wrap::from(&mut buffer[21..25]); - ::encode_to(&self.0, &mut wrapped); - } - let mut output = ::Type::default(); - ::hash(&buffer, &mut output); - Key::from(output) - } -} - -#[test] -fn get_works() { - let expected_keys = [ - b"\ - \x0A\x0F\xF5\x30\xBD\x5A\xB6\x67\ - \x85\xC9\x74\x6D\x01\x33\xD7\xE1\ - \x24\x40\xC4\x67\xA9\xF0\x6D\xCA\ - \xE7\xED\x2E\x78\x32\x77\xE9\x10", - b"\ - \x11\x5A\xC0\xB2\x29\xA5\x34\x10\ - \xB0\xC0\x2D\x47\x49\xDC\x7A\x09\ - \xB9\x6D\xF9\x51\xB6\x1D\x4F\x3B\ - \x4E\x75\xAC\x3B\x14\x57\x47\x96", - ]; - assert_eq!(DynamicAllocation(0).key(), Key::from(*expected_keys[0])); - assert_eq!(DynamicAllocation(1).key(), Key::from(*expected_keys[1])); -} diff --git a/crates/storage/src/alloc/allocator.rs b/crates/storage/src/alloc/allocator.rs deleted file mode 100644 index d2149fdb99a..00000000000 --- a/crates/storage/src/alloc/allocator.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2018-2022 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::DynamicAllocation; -use crate::{ - collections::BitStash, - traits::{ - KeyPtr, - SpreadLayout, - }, -}; - -/// The dynamic allocator. -/// -/// Manages dynamic storage allocations in a very efficient and economical way. -#[derive(Debug, Default, PartialEq, Eq)] -pub struct DynamicAllocator { - allocations: BitStash, -} - -#[cfg(feature = "std")] -const _: () = { - use crate::traits::StorageLayout; - use ink_metadata::layout::{ - FieldLayout, - Layout, - StructLayout, - }; - - impl StorageLayout for DynamicAllocator { - fn layout(key_ptr: &mut KeyPtr) -> Layout { - Layout::Struct(StructLayout::new([FieldLayout::new( - "allocations", - ::layout(key_ptr), - )])) - } - } -}; - -impl SpreadLayout for DynamicAllocator { - const FOOTPRINT: u64 = ::FOOTPRINT; - - fn pull_spread(ptr: &mut KeyPtr) -> Self { - Self { - allocations: SpreadLayout::pull_spread(ptr), - } - } - - fn push_spread(&self, ptr: &mut KeyPtr) { - SpreadLayout::push_spread(&self.allocations, ptr); - } - - fn clear_spread(&self, ptr: &mut KeyPtr) { - SpreadLayout::clear_spread(&self.allocations, ptr); - } -} - -impl DynamicAllocator { - /// Returns a new dynamic storage allocation. - /// - /// # Panics - /// - /// If the dynamic allocator ran out of free dynamic allocations. - pub fn alloc(&mut self) -> DynamicAllocation { - DynamicAllocation(self.allocations.put()) - } - - /// Frees the given dynamic storage allocation. - /// - /// This makes the given dynamic storage allocation available again - /// for new dynamic storage allocations. - /// - /// # Panics - /// - /// Panics if the given dynamic allocation is invalid. - /// A dynamic allocation is invalid if it is not represented as occupied - /// in the `free` list. - pub fn free(&mut self, allocation: DynamicAllocation) { - let index = allocation.get(); - if !self - .allocations - .take(index) - .expect("invalid dynamic storage allocation") - { - panic!( - "encountered double free of dynamic storage: at index {}", - index - ) - } - } -} diff --git a/crates/storage/src/alloc/boxed/impls.rs b/crates/storage/src/alloc/boxed/impls.rs deleted file mode 100644 index 0a31c4f0b17..00000000000 --- a/crates/storage/src/alloc/boxed/impls.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2018-2022 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::Box as StorageBox; -use crate::traits::{ - clear_spread_root, - SpreadLayout, -}; - -impl Drop for StorageBox -where - T: SpreadLayout, -{ - fn drop(&mut self) { - clear_spread_root::(self, &self.allocation.key()); - crate::alloc::free(self.allocation); - } -} - -impl core::cmp::PartialEq for StorageBox -where - T: PartialEq + SpreadLayout, -{ - fn eq(&self, other: &Self) -> bool { - PartialEq::eq(StorageBox::get(self), StorageBox::get(other)) - } -} - -impl core::cmp::Eq for StorageBox where T: Eq + SpreadLayout {} - -impl core::cmp::PartialOrd for StorageBox -where - T: PartialOrd + SpreadLayout, -{ - fn partial_cmp(&self, other: &Self) -> Option { - PartialOrd::partial_cmp(StorageBox::get(self), StorageBox::get(other)) - } - fn lt(&self, other: &Self) -> bool { - PartialOrd::lt(StorageBox::get(self), StorageBox::get(other)) - } - fn le(&self, other: &Self) -> bool { - PartialOrd::le(StorageBox::get(self), StorageBox::get(other)) - } - fn ge(&self, other: &Self) -> bool { - PartialOrd::ge(StorageBox::get(self), StorageBox::get(other)) - } - fn gt(&self, other: &Self) -> bool { - PartialOrd::gt(StorageBox::get(self), StorageBox::get(other)) - } -} - -impl core::cmp::Ord for StorageBox -where - T: core::cmp::Ord + SpreadLayout, -{ - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - Ord::cmp(StorageBox::get(self), StorageBox::get(other)) - } -} - -impl core::fmt::Display for StorageBox -where - T: core::fmt::Display + SpreadLayout, -{ - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - core::fmt::Display::fmt(StorageBox::get(self), f) - } -} - -impl core::hash::Hash for StorageBox -where - T: core::hash::Hash + SpreadLayout, -{ - fn hash(&self, state: &mut H) { - StorageBox::get(self).hash(state) - } -} - -impl core::convert::AsRef for StorageBox -where - T: SpreadLayout, -{ - fn as_ref(&self) -> &T { - StorageBox::get(self) - } -} - -impl core::convert::AsMut for StorageBox -where - T: SpreadLayout, -{ - fn as_mut(&mut self) -> &mut T { - StorageBox::get_mut(self) - } -} - -impl ink_prelude::borrow::Borrow for StorageBox -where - T: SpreadLayout, -{ - fn borrow(&self) -> &T { - StorageBox::get(self) - } -} - -impl ink_prelude::borrow::BorrowMut for StorageBox -where - T: SpreadLayout, -{ - fn borrow_mut(&mut self) -> &mut T { - StorageBox::get_mut(self) - } -} - -impl core::ops::Deref for StorageBox -where - T: SpreadLayout, -{ - type Target = T; - - fn deref(&self) -> &Self::Target { - StorageBox::get(self) - } -} - -impl core::ops::DerefMut for StorageBox -where - T: SpreadLayout, -{ - fn deref_mut(&mut self) -> &mut Self::Target { - StorageBox::get_mut(self) - } -} diff --git a/crates/storage/src/alloc/boxed/mod.rs b/crates/storage/src/alloc/boxed/mod.rs deleted file mode 100644 index 2c6b4abe8ae..00000000000 --- a/crates/storage/src/alloc/boxed/mod.rs +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2018-2022 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. - -mod impls; -mod storage; - -#[cfg(test)] -mod tests; - -use crate::{ - alloc::{ - alloc, - DynamicAllocation, - }, - lazy::Lazy, - traits::SpreadLayout, -}; -use ink_primitives::Key; - -/// A dynamically allocated storage entity. -/// -/// Users can use this in order to make certain `SpreadLayout` storage entities -/// used in contexts that require a `PackedLayout` storage entity by simply -/// packing the storage entity within a `storage::Box`. -/// -/// Dynamic allocations caused by the creation of `storage::Box` instances do -/// have some limited overhead: -/// -/// - The dynamic allocation itself has to be provided by some dynamic storage -/// allocator that needs to be invoked. -/// - Each dynamic storage allocation implies roughly 1.12 bits of overhead. -/// - Upon ever first dereferencing of a `storage::Box` instance a cryptographic -/// hash routine is run in order to compute the underlying storage key. -/// -/// Use this abstraction with caution due to the aforementioned performance -/// implications. -#[derive(Debug)] -pub struct Box -where - T: SpreadLayout, -{ - /// The storage area where the boxed storage entity is stored. - allocation: DynamicAllocation, - /// The cache for the boxed storage entity. - value: Lazy, -} - -impl Box -where - T: SpreadLayout, -{ - /// Creates a new boxed entity. - pub fn new(value: T) -> Self { - Self { - allocation: alloc(), - value: Lazy::new(value), - } - } - - /// Creates a new boxed entity that has not yet loaded its value. - fn lazy(allocation: DynamicAllocation) -> Self { - Self { - allocation, - value: Lazy::from_key(allocation.key()), - } - } - - /// Returns the underlying storage key for the dynamic allocated entity. - fn key(&self) -> Key { - self.allocation.key() - } -} - -impl Box -where - T: SpreadLayout, -{ - /// Returns a shared reference to the boxed value. - /// - /// # Note - /// - /// This loads the value from the pointed to contract storage - /// if this did not happen before. - /// - /// # Panics - /// - /// If loading from contract storage failed. - #[must_use] - pub fn get(boxed: &Self) -> &T { - &boxed.value - } - - /// Returns an exclusive reference to the boxed value. - /// - /// # Note - /// - /// This loads the value from the pointed to contract storage - /// if this did not happen before. - /// - /// # Panics - /// - /// If loading from contract storage failed. - #[must_use] - pub fn get_mut(boxed: &mut Self) -> &mut T { - &mut boxed.value - } -} diff --git a/crates/storage/src/alloc/boxed/storage.rs b/crates/storage/src/alloc/boxed/storage.rs deleted file mode 100644 index 2740b9ba1a6..00000000000 --- a/crates/storage/src/alloc/boxed/storage.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2018-2022 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::Box as StorageBox; -use crate::{ - alloc::DynamicAllocation, - traits::{ - forward_clear_packed, - forward_pull_packed, - forward_push_packed, - KeyPtr, - PackedLayout, - SpreadLayout, - }, -}; -use ink_prelude::vec::Vec; -use ink_primitives::Key; - -#[cfg(feature = "std")] -const _: () = { - use crate::traits::StorageLayout; - use ink_metadata::layout::{ - CellLayout, - Layout, - LayoutKey, - }; - - impl StorageLayout for StorageBox - where - T: SpreadLayout, - { - fn layout(key_ptr: &mut KeyPtr) -> Layout { - Layout::Cell(CellLayout::new::(LayoutKey::from( - key_ptr.advance_by(1), - ))) - } - } - - impl scale_info::TypeInfo for StorageBox - where - T: SpreadLayout + 'static, - { - type Identity = Self; - - fn type_info() -> scale_info::Type { - scale_info::Type::builder() - .path( - scale_info::Path::from_segments(["ink_storage", "alloc", "Box"]) - .expect("encountered invalid Rust path"), - ) - // Unfortunately we cannot encode the type parameters of the box since they - // have to be `T: scale::Codec`. However, them not requiring to be encodable - // is the purpose of the storage `Box`. - // Until we found a solution to this problem we cannot uncomment the below - // line of code: - // - // .type_params(vec![scale_info::MetaType::new::()]) - .composite( - scale_info::build::Fields::named() - .field(|f| f - .name("allocation") - .ty::() - .type_name("DynamicAllocation"), - )) - } - } -}; - -impl SpreadLayout for StorageBox -where - T: SpreadLayout, -{ - const FOOTPRINT: u64 = 1; - - fn pull_spread(ptr: &mut KeyPtr) -> Self { - forward_pull_packed::(ptr) - } - - fn push_spread(&self, ptr: &mut KeyPtr) { - forward_push_packed::(self, ptr) - } - - fn clear_spread(&self, ptr: &mut KeyPtr) { - forward_clear_packed::(self, ptr) - } -} - -impl scale::Encode for StorageBox -where - T: SpreadLayout, -{ - fn size_hint(&self) -> usize { - ::size_hint(&self.allocation) - } - - fn encode_to(&self, dest: &mut O) { - ::encode_to(&self.allocation, dest) - } - - fn encode(&self) -> Vec { - ::encode(&self.allocation) - } - - fn using_encoded R>(&self, f: F) -> R { - ::using_encoded(&self.allocation, f) - } -} - -impl scale::Decode for StorageBox -where - T: SpreadLayout, -{ - fn decode(value: &mut I) -> Result { - Ok(StorageBox::lazy( - ::decode(value)?, - )) - } -} - -impl PackedLayout for StorageBox -where - T: SpreadLayout, -{ - fn pull_packed(&mut self, _at: &Key) {} - - fn push_packed(&self, _at: &Key) { - ::push_spread(Self::get(self), &mut KeyPtr::from(self.key())) - } - - fn clear_packed(&self, _at: &Key) { - ::clear_spread(Self::get(self), &mut KeyPtr::from(self.key())) - } -} diff --git a/crates/storage/src/alloc/boxed/tests.rs b/crates/storage/src/alloc/boxed/tests.rs deleted file mode 100644 index 3955daeb1d5..00000000000 --- a/crates/storage/src/alloc/boxed/tests.rs +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2018-2022 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::Box as StorageBox; -use crate::{ - alloc, - alloc::ContractPhase, - traits::{ - KeyPtr, - SpreadLayout, - }, - Pack, -}; -use core::{ - cmp::Ordering, - convert::{ - AsMut, - AsRef, - }, - ops::{ - Deref, - DerefMut, - }, -}; -use ink_env::test::DefaultAccounts; -use ink_prelude::borrow::{ - Borrow, - BorrowMut, -}; -use ink_primitives::Key; - -fn run_test(f: F) -where - F: FnOnce(DefaultAccounts), -{ - ink_env::test::run_test::(|default_accounts| { - alloc::initialize(ContractPhase::Deploy); - f(default_accounts); - Ok(()) - }) - .unwrap() -} - -#[test] -fn new_works() { - run_test(|_| { - let mut expected = 1; - let mut boxed = StorageBox::new(expected); - assert_eq!(StorageBox::get(&boxed), &expected); - assert_eq!(StorageBox::get_mut(&mut boxed), &mut expected); - assert_eq!(Deref::deref(&boxed), &expected); - assert_eq!(DerefMut::deref_mut(&mut boxed), &mut expected); - assert_eq!(AsRef::as_ref(&boxed), &expected); - assert_eq!(AsMut::as_mut(&mut boxed), &mut expected); - assert_eq!(Borrow::::borrow(&boxed), &expected); - assert_eq!(BorrowMut::::borrow_mut(&mut boxed), &mut expected); - }) -} - -#[test] -fn partial_eq_works() { - run_test(|_| { - let b1 = StorageBox::new(b'X'); - let b2 = StorageBox::new(b'Y'); - let b3 = StorageBox::new(b'X'); - assert!( as PartialEq>::ne(&b1, &b2)); - assert!( as PartialEq>::eq(&b1, &b3)); - }) -} - -#[test] -fn partial_ord_works() { - run_test(|_| { - let b1 = StorageBox::new(1); - let b2 = StorageBox::new(2); - let b3 = StorageBox::new(1); - assert_eq!( - as PartialOrd>::partial_cmp(&b1, &b2), - Some(Ordering::Less) - ); - assert_eq!( - as PartialOrd>::partial_cmp(&b2, &b1), - Some(Ordering::Greater) - ); - assert_eq!( - as PartialOrd>::partial_cmp(&b1, &b3), - Some(Ordering::Equal) - ); - }) -} - -#[test] -fn spread_layout_push_pull_works() { - run_test(|_| { - let b1 = StorageBox::new(b'A'); - assert_eq!(*b1, b'A'); - let root_key = Key::from([0x42; 32]); - SpreadLayout::push_spread(&b1, &mut KeyPtr::from(root_key)); - // Now load another instance of storage box from the same key and check - // if both instances are equal: - let b2 = SpreadLayout::pull_spread(&mut KeyPtr::from(root_key)); - assert_eq!(b1, b2); - // We have to forget one of the storage boxes because we otherwise get - // a double free panic since their `Drop` implementations both try to - // free the same dynamic allocation. - core::mem::forget(b2); - }) -} - -#[test] -#[should_panic(expected = "storage entry was empty")] -fn spread_layout_clear_works() { - run_test(|_| { - let b1 = StorageBox::new(b'A'); - assert_eq!(*b1, b'A'); - let root_key = Key::from([0x42; 32]); - // Manually clear the storage of `b1`. Then another load from the same - // key region should panic since the entry is empty: - SpreadLayout::push_spread(&b1, &mut KeyPtr::from(root_key)); - SpreadLayout::clear_spread(&b1, &mut KeyPtr::from(root_key)); - let b2: StorageBox = SpreadLayout::pull_spread(&mut KeyPtr::from(root_key)); - // We have to forget one of the storage boxes because we otherwise get - // a double free panic since their `Drop` implementations both try to - // free the same dynamic allocation. - core::mem::forget(b2); - }) -} - -#[test] -fn packed_layout_works() { - run_test(|_| { - let p1 = Pack::new((StorageBox::new(b'A'), StorageBox::new([0x01; 4]))); - assert_eq!(*p1.0, b'A'); - assert_eq!(*p1.1, [0x01; 4]); - let root_key = Key::from([0x42; 32]); - SpreadLayout::push_spread(&p1, &mut KeyPtr::from(root_key)); - // Now load another instance of storage box from the same key and check - // if both instances are equal: - let p2: Pack<(StorageBox, StorageBox<[i32; 4]>)> = - SpreadLayout::pull_spread(&mut KeyPtr::from(root_key)); - assert_eq!(p1, p2); - // We have to forget one of the storage boxes because we otherwise get - // a double free panic since their `Drop` implementations both try to - // free the same dynamic allocation. - core::mem::forget(p2); - }) -} - -#[test] -fn recursive_pull_push_works() { - run_test(|_| { - let rec1 = StorageBox::new(StorageBox::new(b'A')); - assert_eq!(**rec1, b'A'); - let root_key = Key::from([0x42; 32]); - SpreadLayout::push_spread(&rec1, &mut KeyPtr::from(root_key)); - // Now load another instance of storage box from the same key and check - // if both instances are equal: - let rec2: StorageBox> = - SpreadLayout::pull_spread(&mut KeyPtr::from(root_key)); - assert_eq!(rec1, rec2); - // We have to forget one of the storage boxes because we otherwise get - // a double free panic since their `Drop` implementations both try to - // free the same dynamic allocation. - core::mem::forget(rec2); - }) -} - -#[test] -#[should_panic(expected = "storage entry was empty")] -fn recursive_clear_works() { - run_test(|_| { - let rec1 = StorageBox::new(StorageBox::new(b'A')); - assert_eq!(**rec1, b'A'); - let root_key = Key::from([0x42; 32]); - // Manually clear the storage of `rec1`. Then another load from the same - // key region should panic since the entry is empty: - SpreadLayout::push_spread(&rec1, &mut KeyPtr::from(root_key)); - SpreadLayout::clear_spread(&rec1, &mut KeyPtr::from(root_key)); - let rec2: StorageBox> = - SpreadLayout::pull_spread(&mut KeyPtr::from(root_key)); - // We have to forget one of the storage boxes because we otherwise get - // a double free panic since their `Drop` implementations both try to - // free the same dynamic allocation. - core::mem::forget(rec2); - }) -} - -#[test] -#[should_panic(expected = "encountered double free of dynamic storage: at index 0")] -fn double_free_panics() { - run_test(|_| { - let b1 = StorageBox::new(b'A'); - let root_key = Key::from([0x42; 32]); - // Manually clear the storage of `rec1`. Then another load from the same - // key region should panic since the entry is empty: - SpreadLayout::push_spread(&b1, &mut KeyPtr::from(root_key)); - let b2: StorageBox = SpreadLayout::pull_spread(&mut KeyPtr::from(root_key)); - assert_eq!(b1, b2); - // At this point both `b1` and `b2` are getting dropped trying to free - // the same dynamic allocation which panics. - }) -} diff --git a/crates/storage/src/alloc/init.rs b/crates/storage/src/alloc/init.rs deleted file mode 100644 index a2e714c6235..00000000000 --- a/crates/storage/src/alloc/init.rs +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright 2018-2022 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::DynamicAllocator; -use crate::traits::{ - pull_spread_root, - push_spread_root, -}; -use cfg_if::cfg_if; -use core::{ - mem, - mem::ManuallyDrop, -}; -use ink_primitives::Key; - -/// The default dynamic allocator key offset. -/// -/// This is where the dynamic allocator is stored on the contract storage. -const DYNAMIC_ALLOCATOR_KEY_OFFSET: [u8; 32] = [0xFE; 32]; - -/// The phase in which a contract execution can be. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum ContractPhase { - /// Initializes the global dynamic storage allocator from scratch. - /// - /// Upon initialization, it will be created from scratch as if the - /// contract has been deployed for the first time. - Deploy, - /// Initializes the global dynamic storage allocator from storage. - /// - /// Upon initialization, the dynamic storage allocator will be pulled - /// from the contract storage with the assumption that a former - /// contract deployment has already taken place in the past. - Call, -} - -/// The state of the dynamic allocator global instance. -#[derive(Debug)] -#[allow(clippy::large_enum_variant)] -enum DynamicAllocatorState { - /// The global instance has not yet been initialized. - /// - /// Upon initialization, it will be created from scratch as if the - /// contract has been deployed for the first time. - UninitDeploy, - /// The global instance has not yet been initialized. - /// - /// Upon initialization, it will be pulled from the contract storage - /// with the assumption that a former contract deployment has already - /// taken place in the past. - UninitCall, - /// The global instance has been initialized successfully and can be used. - Initialized(DynamicAllocator), - /// The global instance has been finalized and can no longer be used. - Finalized, -} - -impl From for DynamicAllocatorState { - fn from(phase: ContractPhase) -> Self { - match phase { - ContractPhase::Deploy => DynamicAllocatorState::UninitDeploy, - ContractPhase::Call => DynamicAllocatorState::UninitCall, - } - } -} - -impl DynamicAllocatorState { - /// Initializes the global dynamic storage allocator instance. - /// - /// The `phase` parameter describes for which execution phase the dynamic - /// storage allocator needs to be initialized since this is different - /// in contract instantiations and calls. - pub fn initialize(&mut self, phase: ContractPhase) { - match self { - DynamicAllocatorState::Initialized(_) - // We only perform this check on Wasm compilation to avoid - // some overly constrained check for the off-chain testing. - if cfg!(all(not(feature = "std"), target_arch = "wasm32")) => - { - panic!( - "cannot initialize the dynamic storage \ - allocator instance twice in Wasm", - ) - } - DynamicAllocatorState::Finalized => { - panic!( - "cannot initialize the dynamic storage \ - allocator after it has been finalized", - ) - } - state => { - *state = phase.into(); - } - } - } - - /// Finalizes the global instance for the dynamic storage allocator. - /// - /// The global dynamic storage allocator must not be used after this! - pub fn finalize(&mut self) { - match self { - DynamicAllocatorState::Initialized(allocator) => { - // Push all state of the global dynamic storage allocator - // instance back onto the contract storage. - push_spread_root::( - allocator, - &Key::from(DYNAMIC_ALLOCATOR_KEY_OFFSET), - ); - // Prevent calling `drop` on the dynamic storage allocator - // instance since this would clear all contract storage - // again. - let _ = ManuallyDrop::new(mem::take(allocator)); - *self = DynamicAllocatorState::Finalized; - } - DynamicAllocatorState::Finalized => { - panic!( - "cannot finalize the dynamic storage allocator \ - after it has already been finalized" - ) - } - DynamicAllocatorState::UninitCall | DynamicAllocatorState::UninitDeploy => { - // Nothing to do in these states. - } - } - } - - /// Runs the closure on the global instance for the dynamic storage allocator. - /// - /// Will automatically initialize the global allocator instance if it has not - /// yet been initialized. - /// - /// # Panics - /// - /// If the global dynamic storage allocator instance has already been finalized. - pub fn on_instance(&mut self, f: F) -> R - where - F: FnOnce(&mut DynamicAllocator) -> R, - { - match self { - DynamicAllocatorState::UninitDeploy => { - let mut allocator = DynamicAllocator::default(); - let result = f(&mut allocator); - *self = DynamicAllocatorState::Initialized(allocator); - result - } - DynamicAllocatorState::UninitCall => { - let mut allocator = pull_spread_root::(&Key::from( - DYNAMIC_ALLOCATOR_KEY_OFFSET, - )); - let result = f(&mut allocator); - *self = DynamicAllocatorState::Initialized(allocator); - result - } - DynamicAllocatorState::Initialized(ref mut allocator) => f(allocator), - DynamicAllocatorState::Finalized => { - panic!( - "cannot operate on the dynamic storage \ - allocator after it has been finalized" - ); - } - } - } -} - -cfg_if! { - if #[cfg(all(not(feature = "std"), target_arch = "wasm32"))] { - // Procedures for the Wasm compilation: - - /// The global instance for the dynamic storage allocator. - static mut GLOBAL_INSTANCE: DynamicAllocatorState = DynamicAllocatorState::UninitDeploy; - - /// Forwards to the `initialize` of the global dynamic storage allocator instance. - pub fn initialize(phase: ContractPhase) { - // SAFETY: Accessing the global allocator in Wasm mode is single - // threaded and will not return back a reference to its - // internal state. Also the `initialize` method won't - // re-enter the dynamic storage in any possible way. - unsafe { &mut GLOBAL_INSTANCE }.initialize(phase); - } - - /// Forwards to the `finalize` of the global dynamic storage allocator instance. - pub fn finalize() { - // SAFETY: Accessing the global allocator in Wasm mode is single - // threaded and will not return back a reference to its - // internal state. Also the `finalize` method won't - // re-enter the dynamic storage in any possible way. - unsafe { &mut GLOBAL_INSTANCE }.finalize(); - } - - /// Forwards to the `on_instance` of the global dynamic storage allocator instance. - pub fn on_instance(f: F) -> R - where - F: FnOnce(&mut DynamicAllocator) -> R, - { - // SAFETY: Accessing the global allocator in Wasm mode is single - // threaded and will not return back a reference to its - // internal state. Also this is an internal API only called - // through `alloc` and `free` both of which do not return - // anything that could allow to re-enter the dynamic storage - // allocator instance. - unsafe { &mut GLOBAL_INSTANCE }.on_instance(f) - } - - } else if #[cfg(feature = "std")] { - // Procedures for the off-chain environment and testing compilation: - - use ::core::cell::RefCell; - thread_local!( - /// The global instance for the dynamic storage allocator. - static GLOBAL_INSTANCE: RefCell = RefCell::new( - DynamicAllocatorState::UninitDeploy - ); - ); - /// Forwards to the `initialize` of the global dynamic storage allocator instance. - pub fn initialize(phase: ContractPhase) { - GLOBAL_INSTANCE.with(|instance| { - instance.borrow_mut().initialize(phase) - }); - } - - /// Forwards to the `finalize` of the global dynamic storage allocator instance. - pub fn finalize() { - GLOBAL_INSTANCE.with(|instance| { - instance.borrow_mut().finalize() - }); - } - - /// Forwards to the `on_instance` of the global dynamic storage allocator instance. - pub fn on_instance(f: F) -> R - where - F: FnOnce(&mut DynamicAllocator) -> R, - { - GLOBAL_INSTANCE.with(|instance| { - instance.borrow_mut().on_instance(f) - }) - } - - } else { - compile_error! { - "ink! only support compilation as `std` or `no_std` + `wasm32-unknown`" - } - } -} diff --git a/crates/storage/src/alloc/mod.rs b/crates/storage/src/alloc/mod.rs deleted file mode 100644 index 8223cc66d9e..00000000000 --- a/crates/storage/src/alloc/mod.rs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2018-2022 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. - -//! The default dynamic storage allocator. -//! -//! Allows to allocate storage cells in a dynamic fashion. -//! This is important if users want to combine types of varying storage -//! footprints. For example, dynamic allocations are required whenever -//! a user wants to use a storage collection (e.g. `storage::Vec`) in -//! another storage collection: `storage::Vec>` -//! -//! # Simplification -//! -//! The contracts pallet is using 256-bit keys for identifying storage cells. -//! This implies a storage space of `2^256` cells which is big enough to say that -//! there are probably never going to happen collisions anywhere at any time -//! if keys are chosen randomly. Using the built-in crypto hashers on unique -//! input we can be sure that there are never going to be collisions in this -//! space of `2^256` cells. -//! -//! This way we can reduce the problem of finding another region in our storage -//! that fits certain requirements (e.g. a minimum size) to the problem of -//! finding another uniform slot. Since we are on 32-bit WebAssembly we have -//! memory limitations that make it impractical to have more than `2^32` dynamic -//! allocated entities, so we can create another limitation for having a total of -//! `2^32` dynamic allocations at any point in time. -//! This enables us to have 32-bit keys instead of 256-bit keys. -//! -//! We can convert such 32-bit keys (represented by e.g. a `u32`) into 256-bit -//! keys by using one of the built-in crypto hashes that has a 256-bit output, -//! e.g. KECCAK, SHA-2 or BLAKE-2. For technical reasons we should prepend the -//! bytes of the 32-bit key by some unique byte sequence, e.g.: -//! ```no_compile -//! let key256 = blake2x256(b"DYNAMICALLY ALLOCATED", bytes(key32)); -//! ``` -//! -//! # Internals -//! -//! As described in [# Simplification] there are `2^32` possible uniform dynamic -//! allocations available. For each such slot the dynamic allocator stores via -//! a single bit in a bitvector if that slot is free or occupied. -//! This bitvector is called the `free` list. -//! However, searching in this `free` list for a 0 bit and thus a free slot -//! for a dynamic allocation would mean that for every 256 consecutively -//! occupied dynamic allocations there was a contract storage lookup required. -//! This might seem a lot but given that there could be thousands or -//! tens of thousands of dynamic allocations at any given time this might not scale -//! well. -//! For the reason of improving scalability we added another vector: the -//! so-called `set_bits` vector. -//! In this vector every `u8` element densely stores the number of set bits -//! (bits that are `1` or `true`) for each 256-bit package in the `free` list. -//! (Note that the `free` list is organized in 256-bit chunks of bits.) -//! -//! This way, to search for an unoccupied dynamic allocation we iterate over -//! the set-bits vector which is 32 times more dense than our `free` list. -//! The additional density implies that we can query up to 8192 potential -//! dynamic storage allocations with a single contract storage look-up. - -mod allocation; -mod allocator; - -// Tracking issue [#1119]: We allow `dead_code` here since we're purposefully hiding the `box` -// module and will remove at a later time. -#[allow(dead_code)] -mod boxed; -mod init; - -#[cfg(test)] -mod tests; - -use self::allocator::DynamicAllocator; -pub use self::{ - allocation::DynamicAllocation, - init::ContractPhase, -}; - -/// Returns a new dynamic storage allocation. -pub fn alloc() -> DynamicAllocation { - init::on_instance(DynamicAllocator::alloc) -} - -/// Frees the given dynamic storage allocation. -/// -/// This makes the given dynamic storage allocation available again -/// for new dynamic storage allocations. -pub fn free(allocation: DynamicAllocation) { - init::on_instance(|allocator| allocator.free(allocation)) -} - -/// Tells the global dynamic storage allocator instance how it shall initialize. -/// -/// # Note -/// -/// Normally users of ink! do not have to call this function directly as it is -/// automatically being use in the correct order and way by the generated code. -/// -/// - The `phase` parameter describes for which execution phase the dynamic -/// storage allocator needs to be initialized since this is different -/// in contract instantiations and calls. -/// - This has to be issued before the first interaction with the global allocator. -/// - The actual instantiation will happen only upon the first interaction with -/// the global allocator, e.g. using the `alloc` or `free` calls. Until then, -/// it remains uninitialized. -/// -/// If this function is not called before the first global allocator interaction -/// then the default initialization scheme is for contract instantiation. -/// However, this behavior might change and must not be relied upon. -pub fn initialize(phase: ContractPhase) { - init::initialize(phase); -} - -/// Finalizes the global dynamic storage allocator instance. -/// -/// This pushes all the accumulated state from this contract execution back to -/// the contract storage to be used in the next contract execution for the same -/// contract instance. -/// -/// The global dynamic storage allocator must not be used after this! -/// -/// # Note -/// -/// Normally users of ink! do not have to call this function directly as it is -/// automatically being use in the correct order and way by the generated code. -pub fn finalize() { - init::finalize() -} diff --git a/crates/storage/src/alloc/tests.rs b/crates/storage/src/alloc/tests.rs deleted file mode 100644 index f064248ad65..00000000000 --- a/crates/storage/src/alloc/tests.rs +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2018-2022 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::{ - alloc, - free, - ContractPhase, - DynamicAllocation, - DynamicAllocator, -}; -use crate::{ - alloc, - traits::{ - KeyPtr, - SpreadLayout, - }, -}; -use ink_env::{ - test, - DefaultEnvironment, -}; -use ink_primitives::Key; - -fn run_default_test(f: F) -where - F: FnOnce(), -{ - alloc::initialize(ContractPhase::Deploy); - test::run_test::(|_| { - f(); - Ok(()) - }) - .unwrap(); -} - -#[test] -fn alloc_works() { - run_default_test(|| { - assert_eq!(alloc(), DynamicAllocation(0)); - }) -} - -cfg_if::cfg_if! { - if #[cfg(miri)] { - // We need to lower the test allocations because miri's stacked borrows - // analysis currently is super linear for some work loads. - // Read more here: https://github.com/rust-lang/miri/issues/1367 - const TEST_ALLOCATIONS: u32 = 10; - } else { - const TEST_ALLOCATIONS: u32 = 10_000; - } -} - -#[test] -fn many_allocs_works() { - run_default_test(|| { - for i in 0..TEST_ALLOCATIONS { - assert_eq!(alloc(), DynamicAllocation(i)); - } - }) -} - -#[test] -fn free_works() { - run_default_test(|| { - // Check that this pattern does not panic. - free(alloc()); - }) -} - -#[test] -fn many_alloc_and_free_works() { - run_default_test(|| { - for i in 0..TEST_ALLOCATIONS { - assert_eq!(alloc(), DynamicAllocation(i)); - } - for i in 0..TEST_ALLOCATIONS { - free(DynamicAllocation(i)) - } - assert_eq!(alloc(), DynamicAllocation(0)); - }) -} - -#[test] -fn alloc_free_in_the_middle() { - run_default_test(|| { - for i in 0..TEST_ALLOCATIONS { - assert_eq!(alloc(), DynamicAllocation(i)); - } - for i in 0..TEST_ALLOCATIONS { - free(DynamicAllocation(i)); - assert_eq!(alloc(), DynamicAllocation(i)); - } - }) -} - -#[test] -#[should_panic(expected = "encountered double free of dynamic storage: at index 0")] -fn double_free_panics() { - run_default_test(|| { - let a0 = alloc(); - let _ = alloc(); - free(a0); - free(a0); - }) -} - -#[test] -#[should_panic(expected = "invalid dynamic storage allocation")] -fn free_out_of_bounds() { - run_default_test(|| { - free(DynamicAllocation(0)); - }) -} - -fn spread_layout_alloc_setup() -> DynamicAllocator { - let mut alloc = DynamicAllocator::default(); - assert_eq!(alloc.alloc(), DynamicAllocation(0)); - assert_eq!(alloc.alloc(), DynamicAllocation(1)); - assert_eq!(alloc.alloc(), DynamicAllocation(2)); - assert_eq!(alloc.alloc(), DynamicAllocation(3)); - assert_eq!(alloc.alloc(), DynamicAllocation(4)); - alloc.free(DynamicAllocation(3)); - alloc.free(DynamicAllocation(1)); - alloc -} - -#[test] -fn spread_pull_push_works() { - run_default_test(|| { - let mut alloc = spread_layout_alloc_setup(); - let root_key = Key::from([0x77; 32]); - // Push the current state of the dynamic storage allocator to the storage: - SpreadLayout::push_spread(&alloc, &mut KeyPtr::from(root_key)); - // Now check if the new allocations are filling the freed ones: - assert_eq!(alloc.alloc(), DynamicAllocation(1)); - assert_eq!(alloc.alloc(), DynamicAllocation(3)); - // Pull another instance of the storage allocator from storage, - // then check if both allocators are equal after also allocating the same - // allocation slots: - let mut alloc2 = - ::pull_spread(&mut KeyPtr::from(root_key)); - assert_eq!(alloc2.alloc(), DynamicAllocation(1)); - assert_eq!(alloc2.alloc(), DynamicAllocation(3)); - assert_eq!(alloc2, alloc); - }) -} - -#[test] -#[should_panic(expected = "encountered empty storage cell")] -fn spread_clear_works() { - run_default_test(|| { - let alloc = spread_layout_alloc_setup(); - let root_key = Key::from([0x42; 32]); - // Push the current state of the dynamic storage allocator to the storage: - SpreadLayout::push_spread(&alloc, &mut KeyPtr::from(root_key)); - // Pull another instance of the storage allocator from storage, - // then check if both allocators are equal after also allocating the same - // allocation slots: - let alloc2 = - ::pull_spread(&mut KeyPtr::from(root_key)); - assert_eq!(alloc2, alloc); - // Now clear the storage associated with `alloc2` again and test if another - // loaded instance from the same storage region panics upon pulling: - SpreadLayout::clear_spread(&alloc2, &mut KeyPtr::from(root_key)); - // We have to prevent calling `Drop` of `alloc3` since it has been created - // deliberately upon invalid contract storage. Since interacting with `alloc3` - // panics which immediately initiates the dropping routines we have to - // wrap it in `ManuallyDrop` before we interact with it to avoid to panic - // while panicking. - let alloc3 = - ::pull_spread(&mut KeyPtr::from(root_key)); - let mut alloc3 = core::mem::ManuallyDrop::new(alloc3); - // Now interact with `alloc3` to make it load from the invalid storage: - let _ = alloc3.alloc(); - }) -} - -#[test] -fn test_call_setup_works() { - test::run_test::(|_| { - let mut allocator = DynamicAllocator::default(); - assert_eq!(allocator.alloc(), DynamicAllocation(0)); - assert_eq!(allocator.alloc(), DynamicAllocation(1)); - let root_key = Key::from([0xFE; 32]); - DynamicAllocator::push_spread(&allocator, &mut KeyPtr::from(root_key)); - alloc::initialize(ContractPhase::Call); - assert_eq!(alloc(), DynamicAllocation(2)); - assert_eq!(alloc(), DynamicAllocation(3)); - free(DynamicAllocation(0)); - free(DynamicAllocation(2)); - Ok(()) - }) - .unwrap(); -} diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs index 34755851c59..6c13fbbac80 100644 --- a/crates/storage/src/lib.rs +++ b/crates/storage/src/lib.rs @@ -47,7 +47,6 @@ #[macro_use(quickcheck)] extern crate quickcheck_macros; -pub mod alloc; pub mod traits; // Tracking issue [#1119]: We allow `dead_code` here since we're purposefully hiding the