Repository: paritytech/ink
Pull request #995 (2021-10-31 12:11:36)

Follow-up: SpreadLayout
Implements
- https://github.com/paritytech/ink/issues/993
- https://github.com/paritytech/ink/issues/992
- https://github.com/paritytech/ink/issues/991

Also partially implements https://github.com/paritytech/ink/issues/988 .
Note: Only partial since fallible ink! constructors remain artificially forbidden as of now. Another follow-up PR might change that soon.

All in all this heavily simplifies working with the new `SpreadLayout` traits in ink! smart contracts. As a consequence we can further simplify https://github.com/paritytech/ink/pull/979 after merging this PR.

codecov-commenter: # [Codecov](https://codecov.io/gh/paritytech/ink/pull/995?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) Report
> Merging [#995](https://codecov.io/gh/paritytech/ink/pull/995?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) (bd84d77) into [master](https://codecov.io/gh/paritytech/ink/commit/ee42309d830b1b4b4f3874c2f53c3f5d6ae9b47f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) (ee42309) will **increase** coverage by `14.96%`.
> The diff coverage is `51.51%`.

[![Impacted file tree graph](https://codecov.io/gh/paritytech/ink/pull/995/graphs/tree.svg?width=650&height=150&src=pr&token=tYyVaCZb7o&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech)](https://codecov.io/gh/paritytech/ink/pull/995?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech)

```diff
@@             Coverage Diff             @@
##           master     #995       +/-   ##
===========================================
+ Coverage   64.00%   78.96%   +14.96%     
===========================================
  Files         244      245        +1     
  Lines        9179     9204       +25     
===========================================
+ Hits         5875     7268     +1393     
+ Misses       3304     1936     -1368     
```


| [Impacted Files](https://codecov.io/gh/paritytech/ink/pull/995?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) | Coverage Δ | |
|---|---|---|
| [crates/lang/codegen/src/generator/dispatch.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL2xhbmcvY29kZWdlbi9zcmMvZ2VuZXJhdG9yL2Rpc3BhdGNoLnJz) | `100.00% <ø> (+100.00%)` | :arrow_up: |
| [crates/lang/codegen/src/generator/storage.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL2xhbmcvY29kZWdlbi9zcmMvZ2VuZXJhdG9yL3N0b3JhZ2UucnM=) | `100.00% <ø> (+100.00%)` | :arrow_up: |
| [crates/lang/src/codegen/dispatch/execution.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL2xhbmcvc3JjL2NvZGVnZW4vZGlzcGF0Y2gvZXhlY3V0aW9uLnJz) | `0.00% <0.00%> (ø)` | |
| [crates/storage/src/traits/mod.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL3N0b3JhZ2Uvc3JjL3RyYWl0cy9tb2QucnM=) | `100.00% <ø> (+17.39%)` | :arrow_up: |
| [crates/storage/derive/src/spread\_allocate.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL3N0b3JhZ2UvZGVyaXZlL3NyYy9zcHJlYWRfYWxsb2NhdGUucnM=) | `93.33% <93.33%> (ø)` | |
| [crates/primitives/src/key.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL3ByaW1pdGl2ZXMvc3JjL2tleS5ycw==) | `93.42% <100.00%> (+0.17%)` | :arrow_up: |
| [...ates/storage/src/collections/hashmap/fuzz\_tests.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL3N0b3JhZ2Uvc3JjL2NvbGxlY3Rpb25zL2hhc2htYXAvZnV6el90ZXN0cy5ycw==) | `100.00% <0.00%> (+1.06%)` | :arrow_up: |
| [crates/storage/src/traits/impls/prims.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL3N0b3JhZ2Uvc3JjL3RyYWl0cy9pbXBscy9wcmltcy5ycw==) | `96.92% <0.00%> (+1.53%)` | :arrow_up: |
| [crates/lang/ir/src/ir/attrs.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2F0dHJzLnJz) | `83.07% <0.00%> (+4.07%)` | :arrow_up: |
| [crates/lang/ir/src/ir/trait\_def/item/mod.rs](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL3RyYWl0X2RlZi9pdGVtL21vZC5ycw==) | `90.08% <0.00%> (+4.95%)` | :arrow_up: |
| ... and [46 more](https://codecov.io/gh/paritytech/ink/pull/995/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/paritytech/ink/pull/995?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/paritytech/ink/pull/995?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech). Last update [ee42309...bd84d77](https://codecov.io/gh/paritytech/ink/pull/995?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=paritytech).


Patch:
diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic
index ba8bea6f4f..6a430c8bc3 100644
--- a/.config/cargo_spellcheck.dic
+++ b/.config/cargo_spellcheck.dic
@@ -103,3 +103,4 @@ wildcard/S
 natively
 payability
 unpayable
+initializer
diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml
index a7506aaa31..08325e1bfe 100644
--- a/crates/engine/Cargo.toml
+++ b/crates/engine/Cargo.toml
@@ -15,7 +15,7 @@ categories = ["no-std", "embedded"]
 include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
 
 [dependencies]
-scale = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] }
+scale = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive", "full"] }
 derive_more = { version = "0.99", default-features = false, features = ["from", "display"] }
 
 sha2 = { version = "0.9" }
diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml
index 3c0edd0db8..abafb26179 100644
--- a/crates/env/Cargo.toml
+++ b/crates/env/Cargo.toml
@@ -20,7 +20,7 @@ ink_allocator = { version = "3.0.0-rc6", path = "../allocator/", default-feature
 ink_primitives = { version = "3.0.0-rc6", path = "../primitives/", default-features = false }
 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 = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive", "full"] }
 derive_more = { version = "0.99", default-features = false, features = ["from", "display"] }
 num-traits = { version = "0.2", default-features = false, features = ["i128"] }
 cfg-if = "1.0"
diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml
index 7a40fd18f7..033957a79d 100644
--- a/crates/lang/Cargo.toml
+++ b/crates/lang/Cargo.toml
@@ -23,7 +23,7 @@ ink_prelude = { version = "3.0.0-rc6", path = "../prelude", default-features = f
 ink_eth_compatibility = { version = "3.0.0-rc6", path = "../eth_compatibility", default-features = false }
 ink_lang_macro = { version = "3.0.0-rc6", path = "macro", default-features = false }
 
-scale = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] }
+scale = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive", "full"] }
 derive_more = { version = "0.99", default-features = false, features = ["from"] }
 
 [dev-dependencies]
diff --git a/crates/lang/codegen/Cargo.toml b/crates/lang/codegen/Cargo.toml
index 20ab7c9a2a..0c4db60d99 100644
--- a/crates/lang/codegen/Cargo.toml
+++ b/crates/lang/codegen/Cargo.toml
@@ -27,7 +27,7 @@ itertools = "0.10"
 either = { version = "1.5", default-features = false }
 blake2 = "0.9"
 heck = "0.3.1"
-scale = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] }
+scale = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive", "full"] }
 impl-serde = "0.3.1"
 
 [features]
diff --git a/crates/lang/codegen/src/generator/dispatch.rs b/crates/lang/codegen/src/generator/dispatch.rs
index b9395e7e21..3937f21455 100644
--- a/crates/lang/codegen/src/generator/dispatch.rs
+++ b/crates/lang/codegen/src/generator/dispatch.rs
@@ -511,7 +511,7 @@ impl Dispatch<'_> {
                 .is_dynamic_storage_allocator_enabled();
             quote_spanned!(constructor_span=>
                 Self::#constructor_ident(input) => {
-                    ::ink_lang::codegen::execute_constructor::<#storage_ident, _>(
+                    ::ink_lang::codegen::execute_constructor::<#storage_ident, _, _>(
                         ::ink_lang::codegen::ExecuteConstructorConfig {
                             dynamic_storage_alloc: #is_dynamic_storage_allocation_enabled
                         },
diff --git a/crates/lang/codegen/src/generator/storage.rs b/crates/lang/codegen/src/generator/storage.rs
index c68e29824a..d7f68e5f0f 100644
--- a/crates/lang/codegen/src/generator/storage.rs
+++ b/crates/lang/codegen/src/generator/storage.rs
@@ -39,8 +39,8 @@ impl GenerateCode for Storage<'_> {
                 quote! { use ::ink_lang::codegen::EmitEvent as _; }
             });
         quote_spanned!(storage_span =>
-            #access_env_impls
             #storage_struct
+            #access_env_impls
 
             const _: () = {
                 // Used to make `self.env()` and `Self::env()` available in message code.
@@ -106,6 +106,10 @@ impl Storage<'_> {
                 impl ::ink_lang::reflect::ContractName for #ident {
                     const NAME: &'static str = ::core::stringify!(#ident);
                 }
+
+                impl ::ink_lang::codegen::ContractRootKey for #ident {
+                    const ROOT_KEY: ::ink_primitives::Key = ::ink_primitives::Key::new([0x00; 32]);
+                }
             };
         )
     }
diff --git a/crates/lang/macro/Cargo.toml b/crates/lang/macro/Cargo.toml
index 60ba50a2fe..fbfeb28f9a 100644
--- a/crates/lang/macro/Cargo.toml
+++ b/crates/lang/macro/Cargo.toml
@@ -19,7 +19,7 @@ ink_lang_ir = { version = "3.0.0-rc6", path = "../ir", default-features = false
 ink_lang_codegen = { version = "3.0.0-rc6", path = "../codegen", default-features = false }
 ink_primitives = { version = "3.0.0-rc6", path = "../../primitives/", default-features = false }
 
-scale = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] }
+scale = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive"] }
 syn = "1"
 proc-macro2 = "1"
 
diff --git a/crates/lang/src/codegen/dispatch/execution.rs b/crates/lang/src/codegen/dispatch/execution.rs
index 74ab31fe24..72f1c05c22 100644
--- a/crates/lang/src/codegen/dispatch/execution.rs
+++ b/crates/lang/src/codegen/dispatch/execution.rs
@@ -18,23 +18,41 @@ use crate::reflect::{
 };
 use core::{
     any::TypeId,
+    convert::Infallible,
     mem::ManuallyDrop,
 };
 use ink_env::{
     Environment,
     ReturnFlags,
 };
-use ink_primitives::Key;
+use ink_primitives::{
+    Key,
+    KeyPtr,
+};
 use ink_storage::{
     alloc,
     alloc::ContractPhase,
     traits::{
         pull_spread_root,
         push_spread_root,
+        SpreadAllocate,
         SpreadLayout,
     },
 };
 
+/// The root key of the ink! smart contract.
+///
+/// # Note
+///
+/// - This is the key where storage allocation, pushing and pulling is rooted
+///   using the `SpreadLayout` and `SpreadAllocate` traits primarily.
+/// - This trait is automatically implemented by the ink! codegen.
+/// - The existence of this trait allows to customize the root key in future
+///   versions of ink! if needed.
+pub trait ContractRootKey {
+    const ROOT_KEY: Key;
+}
+
 /// Returns `Ok` if the caller did not transfer additional value to the callee.
 ///
 /// # Errors
@@ -70,24 +88,192 @@ pub struct ExecuteConstructorConfig {
 /// The closure is supposed to already contain all the arguments that the real
 /// constructor message requires and forwards them.
 #[inline]
-pub fn execute_constructor<S, F>(
+pub fn execute_constructor<Contract, F, R>(
     config: ExecuteConstructorConfig,
     f: F,
 ) -> Result<(), DispatchError>
 where
-    S: ink_storage::traits::SpreadLayout,
-    F: FnOnce() -> S,
+    Contract: SpreadLayout + ContractRootKey,
+    F: FnOnce() -> R,
+    <private::Seal<R> as ConstructorReturnType<Contract>>::ReturnValue: scale::Encode,
+    private::Seal<R>: ConstructorReturnType<Contract>,
 {
     if config.dynamic_storage_alloc {
         alloc::initialize(ContractPhase::Deploy);
     }
-    let storage = ManuallyDrop::new(f());
-    let root_key = Key::from([0x00; 32]);
-    push_spread_root::<S>(&storage, &root_key);
-    if config.dynamic_storage_alloc {
-        alloc::finalize();
+    let result = ManuallyDrop::new(private::Seal(f()));
+    match result.as_result() {
+        Ok(contract) => {
+            // Constructor is infallible or is fallible but succeeded.
+            //
+            // This requires us to sync back the changes of the contract storage.
+            let root_key = <Contract as ContractRootKey>::ROOT_KEY;
+            push_spread_root::<Contract>(contract, &root_key);
+            if config.dynamic_storage_alloc {
+                alloc::finalize();
+            }
+            Ok(())
+        }
+        Err(_) => {
+            // Constructor is fallible and failed.
+            //
+            // We need to revert the state of the transaction.
+            ink_env::return_value::<
+                <private::Seal<R> as ConstructorReturnType<Contract>>::ReturnValue,
+            >(
+                ReturnFlags::default().set_reverted(true),
+                result.return_value(),
+            )
+        }
+    }
+}
+
+/// Initializes the ink! contract using the given initialization routine.
+///
+/// # Note
+///
+/// - This uses `SpreadAllocate` trait in order to default initialize the
+///   ink! smart contract before calling the initialization routine.
+/// - This either returns `Contract` or `Result<Contract, E>` depending
+///   on the return type `R` of the initializer closure `F`.
+///   If `R` is `()` then `Contract` is returned and if `R` is any type of
+///   `Result<(), E>` then `Result<Contract, E>` is returned.
+///   Other return types for `F` than the ones listed above are not allowed.
+#[inline]
+pub fn initialize_contract<Contract, F, R>(
+    initializer: F,
+) -> <R as InitializerReturnType<Contract>>::Wrapped
+where
+    Contract: ContractRootKey + SpreadAllocate,
+    F: FnOnce(&mut Contract) -> R,
+    R: InitializerReturnType<Contract>,
+{
+    let mut key_ptr = KeyPtr::from(<Contract as ContractRootKey>::ROOT_KEY);
+    let mut instance = <Contract as SpreadAllocate>::allocate_spread(&mut key_ptr);
+    let result = initializer(&mut instance);
+    result.into_wrapped(instance)
+}
+
+mod private {
+    /// Seals the implementation of `ContractInitializerReturnType`.
+    pub trait Sealed {}
+    impl Sealed for () {}
+    impl<T, E> Sealed for Result<T, E> {}
+    /// A thin-wrapper type that automatically seals its inner type.
+    ///
+    /// Since it is private it can only be used from within this crate.
+    /// We need this type in order to properly seal the `ConstructorReturnType`
+    /// trait from unwanted external trait implementations.
+    #[repr(transparent)]
+    pub struct Seal<T>(pub T);
+    impl<T> Sealed for Seal<T> {}
+}
+
+/// Guards against using invalid contract initializer types.
+///
+/// # Note
+///
+/// Currently the only allowed types are `()` and `Result<(), E>`
+/// where `E` is some unspecified error type.
+/// If the contract initializer returns `Result::Err` the utility
+/// method that is used to initialize an ink! smart contract will
+/// revert the state of the contract instantiation.
+pub trait ConstructorReturnType<C>: private::Sealed {
+    /// Is `true` if `Self` is `Result<C, E>`.
+    const IS_RESULT: bool = false;
+
+    /// The error type of the constructor return type.
+    ///
+    /// # Note
+    ///
+    /// For infallible constructors this is `core::convert::Infallible`.
+    type Error;
+
+    /// The type of the return value of the constructor.
+    ///
+    /// # Note
+    ///
+    /// For infallible constructors this is `()` whereas for fallible
+    /// constructors this is the actual return value. Since we only ever
+    /// return a value in case of `Result::Err` the `Result::Ok` value
+    /// does not matter.
+    type ReturnValue;
+
+    /// Converts the return value into a `Result` instance.
+    ///
+    /// # Note
+    ///
+    /// For infallible constructor returns this always yields `Ok`.
+    fn as_result(&self) -> Result<&C, &Self::Error>;
+
+    /// Returns the actual return value of the constructor.
+    ///
+    /// # Note
+    ///
+    /// For infallible constructor returns this always yields `()`
+    /// and is basically ignored since this does not get called
+    /// if the constructor did not fail.
+    fn return_value(&self) -> &Self::ReturnValue;
+}
+
+impl<C> ConstructorReturnType<C> for private::Seal<C> {
+    type Error = Infallible;
+    type ReturnValue = ();
+
+    #[inline(always)]
+    fn as_result(&self) -> Result<&C, &Self::Error> {
+        Ok(&self.0)
+    }
+
+    #[inline(always)]
+    fn return_value(&self) -> &Self::ReturnValue {
+        &()
+    }
+}
+
+impl<C, E> ConstructorReturnType<C> for private::Seal<Result<C, E>> {
+    const IS_RESULT: bool = true;
+    type Error = E;
+    type ReturnValue = Result<C, E>;
+
+    #[inline(always)]
+    fn as_result(&self) -> Result<&C, &Self::Error> {
+        self.0.as_ref()
+    }
+
+    #[inline(always)]
+    fn return_value(&self) -> &Self::ReturnValue {
+        &self.0
+    }
+}
+
+/// Trait used to convert return types of contract initializer routines.
+///
+/// Only `()` and `Result<(), E>` are allowed contract initializer return types.
+/// For `WrapReturnType<C>` where `C` is the contract type the trait converts
+/// `()` into `C` and `Result<(), E>` into `Result<C, E>`.
+pub trait InitializerReturnType<C>: private::Sealed {
+    type Wrapped;
+
+    /// Performs the type conversion of the initialization routine return type.
+    fn into_wrapped(self, wrapped: C) -> Self::Wrapped;
+}
+
+impl<C> InitializerReturnType<C> for () {
+    type Wrapped = C;
+
+    #[inline]
+    fn into_wrapped(self, wrapped: C) -> C {
+        wrapped
+    }
+}
+impl<C, E> InitializerReturnType<C> for Result<(), E> {
+    type Wrapped = Result<C, E>;
+
+    #[inline]
+    fn into_wrapped(self, wrapped: C) -> Self::Wrapped {
+        self.map(|_| wrapped)
     }
-    Ok(())
 }
 
 /// Configuration for execution of ink! messages.
diff --git a/crates/lang/src/codegen/dispatch/mod.rs b/crates/lang/src/codegen/dispatch/mod.rs
index 8f1fecb8c3..89e912dda4 100644
--- a/crates/lang/src/codegen/dispatch/mod.rs
+++ b/crates/lang/src/codegen/dispatch/mod.rs
@@ -21,7 +21,9 @@ pub use self::{
         deny_payment,
         execute_constructor,
         finalize_message,
+        initialize_contract,
         initiate_message,
+        ContractRootKey,
         ExecuteConstructorConfig,
         ExecuteMessageConfig,
     },
diff --git a/crates/lang/src/codegen/mod.rs b/crates/lang/src/codegen/mod.rs
index 2c44a20a0d..d4d317050b 100644
--- a/crates/lang/src/codegen/mod.rs
+++ b/crates/lang/src/codegen/mod.rs
@@ -26,8 +26,10 @@ pub use self::{
         deny_payment,
         execute_constructor,
         finalize_message,
+        initialize_contract,
         initiate_message,
         ContractCallBuilder,
+        ContractRootKey,
         DispatchInput,
         DispatchOutput,
         ExecuteConstructorConfig,
diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml
index 2e196ce3cc..108fa86348 100644
--- a/crates/primitives/Cargo.toml
+++ b/crates/primitives/Cargo.toml
@@ -16,7 +16,7 @@ include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"]
 
 [dependencies]
 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 = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive", "full"] }
 scale-info = { version = "1.0", default-features = false, features = ["derive"], optional = true }
 cfg-if = "1"
 
diff --git a/crates/primitives/src/key.rs b/crates/primitives/src/key.rs
index 4f3c2b0a89..954055da75 100644
--- a/crates/primitives/src/key.rs
+++ b/crates/primitives/src/key.rs
@@ -46,10 +46,23 @@ use scale_info::{
 #[repr(transparent)]
 pub struct Key([u8; 32]);
 
+impl Key {
+    /// Creates a new key instance from the given bytes.
+    ///
+    /// # Note
+    ///
+    /// This constructor only exists since it is not yet possible to define
+    /// the `From` trait implementation as const.
+    #[inline]
+    pub const fn new(bytes: [u8; 32]) -> Self {
+        Self(bytes)
+    }
+}
+
 impl From<[u8; 32]> for Key {
     #[inline]
     fn from(bytes: [u8; 32]) -> Self {
-        Self(bytes)
+        Self::new(bytes)
     }
 }
 
diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml
index 291537ee37..dda5f8d48c 100644
--- a/crates/storage/Cargo.toml
+++ b/crates/storage/Cargo.toml
@@ -21,7 +21,7 @@ ink_primitives = { version = "3.0.0-rc6", path = "../primitives/", default-featu
 ink_storage_derive = { version = "3.0.0-rc6", path = "derive", default-features = false }
 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 = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive", "full"] }
 derive_more = { version = "0.99", default-features = false, features = ["from", "display"] }
 scale-info = { version = "1.0", default-features = false, features = ["derive"], optional = true }
 cfg-if = "1.0"
diff --git a/crates/storage/derive/Cargo.toml b/crates/storage/derive/Cargo.toml
index d5b6640148..69f3f9a8c9 100644
--- a/crates/storage/derive/Cargo.toml
+++ b/crates/storage/derive/Cargo.toml
@@ -24,7 +24,7 @@ proc-macro2 = "1"
 synstructure = "0.12.4"
 
 [dev-dependencies]
-scale = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive", "full"] }
+scale = { package = "parity-scale-codec", version = "2.3", default-features = false, features = ["derive", "full"] }
 ink_env = { version = "3.0.0-rc6", path = "../../env" }
 ink_primitives = { version = "3.0.0-rc6", path = "../../primitives" }
 ink_metadata = { version = "3.0.0-rc6", path = "../../metadata" }
diff --git a/crates/storage/derive/src/lib.rs b/crates/storage/derive/src/lib.rs
index b28637b527..b922bb2801 100644
--- a/crates/storage/derive/src/lib.rs
+++ b/crates/storage/derive/src/lib.rs
@@ -51,6 +51,7 @@
 extern crate proc_macro;
 
 mod packed_layout;
+mod spread_allocate;
 mod spread_layout;
 mod storage_layout;
 
@@ -59,6 +60,7 @@ mod tests;
 
 use self::{
     packed_layout::packed_layout_derive,
+    spread_allocate::spread_allocate_derive,
     spread_layout::spread_layout_derive,
     storage_layout::storage_layout_derive,
 };
@@ -132,6 +134,44 @@ synstructure::decl_derive!(
     /// ```
     packed_layout_derive
 );
+
+synstructure::decl_derive!(
+    [SpreadAllocate] =>
+    /// Derives `ink_storage`'s `SpreadAllocate` trait for the given `struct`.
+    ///
+    /// # Note
+    ///
+    /// As of now `enum` types are not supported!
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// use ink_primitives::Key;
+    /// use ink_storage::traits::{
+    ///     SpreadAllocate,
+    ///     SpreadLayout,
+    ///     allocate_spread_root,
+    ///};
+    ///
+    /// #[derive(SpreadAllocate, SpreadLayout)]
+    /// # #[derive(Debug, PartialEq)]
+    /// struct NamedFields {
+    ///     a: u32,
+    ///     b: [u32; 32],
+    /// }
+    ///
+    /// let allocated: NamedFields = allocate_spread_root(&Key::from([0x42; 32]));
+    /// assert_eq!(
+    ///     allocated,
+    ///     NamedFields {
+    ///         a: 0,
+    ///         b: [0; 32],
+    ///     }
+    /// );
+    /// ```
+    spread_allocate_derive
+);
+
 synstructure::decl_derive!(
     [StorageLayout] =>
     /// Derives `ink_storage`'s `StorageLayout` trait for the given `struct` or `enum`.
diff --git a/crates/storage/derive/src/spread_allocate.rs b/crates/storage/derive/src/spread_allocate.rs
new file mode 100644
index 0000000000..abb27eb617
--- /dev/null
+++ b/crates/storage/derive/src/spread_allocate.rs
@@ -0,0 +1,51 @@
+// 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 proc_macro2::TokenStream as TokenStream2;
+use quote::quote;
+
+/// Derives `ink_storage`'s `SpreadAllocate` trait for the given type.
+pub fn spread_allocate_derive(mut s: synstructure::Structure) -> TokenStream2 {
+    s.bind_with(|_| synstructure::BindStyle::Move)
+        .add_bounds(synstructure::AddBounds::Generics)
+        .underscore_const(true);
+    match s.ast().data {
+        syn::Data::Struct(_) => derive_struct(s),
+        syn::Data::Enum(_) => {
+            panic!("cannot derive `SpreadAllocate` for `enum` types")
+        }
+        syn::Data::Union(_) => {
+            panic!("cannot derive `SpreadAllocate` for `union` types")
+        }
+    }
+}
+
+/// Derives `ink_storage`'s `SpreadAllocate` trait for the given `struct`.
+fn derive_struct(s: synstructure::Structure) -> TokenStream2 {
+    assert!(s.variants().len() == 1, "can only operate on structs");
+    let variant = &s.variants()[0];
+    let allocate_body = variant.construct(|field, _index| {
+        let ty = &field.ty;
+        quote! {
+            <#ty as ::ink_storage::traits::SpreadAllocate>::allocate_spread(__key_ptr)
+        }
+    });
+    s.gen_impl(quote! {
+        gen impl ::ink_storage::traits::SpreadAllocate for @Self {
+            fn allocate_spread(__key_ptr: &mut ::ink_primitives::KeyPtr) -> Self {
+                #allocate_body
+            }
+        }
+    })
+}
diff --git a/crates/storage/src/traits/mod.rs b/crates/storage/src/traits/mod.rs
index d812bb1ae1..ffd530d585 100644
--- a/crates/storage/src/traits/mod.rs
+++ b/crates/storage/src/traits/mod.rs
@@ -68,6 +68,7 @@ pub use self::{
 use ink_primitives::Key;
 pub use ink_storage_derive::{
     PackedLayout,
+    SpreadAllocate,
     SpreadLayout,
     StorageLayout,
 };


Test patch:
diff --git a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr
index e4b39579d2..44cf44c34b 100644
--- a/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr
+++ b/crates/lang/tests/ui/contract/fail/message-returns-non-codec.stderr
@@ -6,9 +6,9 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied
    |
    = note: required because of the requirements on the impl of `Encode` for `NonCodecType`
 note: required by a bound in `DispatchOutput`
-  --> src/codegen/dispatch/type_check.rs:69:8
+  --> src/codegen/dispatch/type_check.rs
    |
-69 |     T: scale::Encode + 'static;
+   |     T: scale::Encode + 'static;
    |        ^^^^^^^^^^^^^ required by this bound in `DispatchOutput`
 
 error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied
@@ -21,9 +21,9 @@ error[E0277]: the trait bound `NonCodecType: WrapperTypeEncode` is not satisfied
     |
     = note: required because of the requirements on the impl of `Encode` for `NonCodecType`
 note: required by a bound in `finalize_message`
-   --> src/codegen/dispatch/execution.rs:169:8
+   --> src/codegen/dispatch/execution.rs
     |
-169 |     R: scale::Encode + 'static,
+    |     R: scale::Encode + 'static,
     |        ^^^^^^^^^^^^^ required by this bound in `finalize_message`
 
 error[E0599]: the method `fire` exists for struct `ink_env::call::CallBuilder<DefaultEnvironment, Set<ink_env::AccountId>, Unset<u64>, Unset<u128>, Set<ExecutionInput<ArgumentList<ArgumentListEnd, ArgumentListEnd>>>, Set<ReturnType<NonCodecType>>>`, but its trait bounds were not satisfied
diff --git a/crates/storage/derive/src/tests/mod.rs b/crates/storage/derive/src/tests/mod.rs
index a90cc0a545..8354ef6591 100644
--- a/crates/storage/derive/src/tests/mod.rs
+++ b/crates/storage/derive/src/tests/mod.rs
@@ -13,5 +13,6 @@
 // limitations under the License.
 
 mod packed_layout;
+mod spread_allocate;
 mod spread_layout;
 mod storage_layout;
diff --git a/crates/storage/derive/src/tests/spread_allocate.rs b/crates/storage/derive/src/tests/spread_allocate.rs
new file mode 100644
index 0000000000..0202b9693d
--- /dev/null
+++ b/crates/storage/derive/src/tests/spread_allocate.rs
@@ -0,0 +1,108 @@
+// 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 crate::spread_allocate_derive;
+use syn::parse_quote;
+
+#[test]
+#[should_panic(expected = "cannot derive `SpreadAllocate` for `enum` types")]
+fn enum_fails() {
+    spread_allocate_derive(synstructure::Structure::new(&parse_quote! {
+        enum Enum { A, B, C }
+    }));
+}
+
+#[test]
+#[should_panic(expected = "Unable to create synstructure::Structure: \
+                           Error(\"unexpected unsupported untagged union\")")]
+fn union_fails() {
+    spread_allocate_derive(synstructure::Structure::new(&parse_quote! {
+        union Union { a: i32, b: u32 }
+    }));
+}
+
+#[test]
+fn unit_struct_works() {
+    synstructure::test_derive! {
+        spread_allocate_derive {
+            struct UnitStruct;
+        }
+        expands to {
+            const _: () = {
+                impl ::ink_storage::traits::SpreadAllocate for UnitStruct {
+                    fn allocate_spread(__key_ptr: &mut ::ink_primitives::KeyPtr) -> Self {
+                        UnitStruct
+                    }
+                }
+            };
+        }
+        no_build
+    }
+}
+
+#[test]
+fn struct_works() {
+    synstructure::test_derive! {
+        spread_allocate_derive {
+            struct NamedFields {
+                a: i32,
+                b: [u8; 32],
+                c: Box<i32>,
+            }
+        }
+        expands to {
+            const _: () = {
+                impl ::ink_storage::traits::SpreadAllocate for NamedFields {
+                    fn allocate_spread(__key_ptr: &mut ::ink_primitives::KeyPtr) -> Self {
+                        NamedFields {
+                            a: <i32 as ::ink_storage::traits::SpreadAllocate>::allocate_spread(__key_ptr),
+                            b: <[u8; 32] as ::ink_storage::traits::SpreadAllocate>::allocate_spread(__key_ptr),
+                            c: <Box<i32> as ::ink_storage::traits::SpreadAllocate>::allocate_spread(__key_ptr),
+                        }
+                    }
+                }
+            };
+        }
+        no_build
+    }
+}
+
+#[test]
+fn generic_struct_works() {
+    synstructure::test_derive! {
+        spread_allocate_derive {
+            struct GenericStruct<T1, T2> {
+                a: T1,
+                b: (T1, T2),
+            }
+        }
+        expands to {
+            const _: () = {
+                impl<T1, T2> ::ink_storage::traits::SpreadAllocate for GenericStruct<T1, T2>
+                where
+                    T1: ::ink_storage::traits::SpreadAllocate,
+                    T2: ::ink_storage::traits::SpreadAllocate
+                {
+                    fn allocate_spread(__key_ptr: &mut ::ink_primitives::KeyPtr) -> Self {
+                        GenericStruct {
+                            a: <T1 as ::ink_storage::traits::SpreadAllocate>::allocate_spread(__key_ptr),
+                            b: <(T1, T2) as ::ink_storage::traits::SpreadAllocate>::allocate_spread(__key_ptr),
+                        }
+                    }
+                }
+            };
+        }
+        no_build
+    }
+}