Repository: paritytech/ink
Pull request #270 (2019-11-22 21:35:20)

Initial implementation of the global buffer arena
Implements the buffer pool mentioned in this issue: https://github.com/paritytech/ink/issues/245

The implementation of the `BufferArena` shall solve the problem of throttling heap memory allocations due to heavy usage in the layer between SRML contracts and ink!. Most often buffers are short-lived since they are just used for encoding and decoding intermediate values. The `BufferArena` acts as a globally accessible heap allocation cache.

Provides a single API:

- `fn get_buffer(&self) -> BufferRef`

Where `BufferRef` implements all traits required for ink! and SRML contracts interaction.
When such a `BufferRef` goes out of scope its internal buffer is added back to the global `BufferArena` instance.

The implementation tries to be as efficient as possible while maintaining checks to identify malicious smart contracts that accumulate many cached buffers.

This small module will be the backbone of the future dynamic allocator and maybe also of a reimplementation of the environmental accessor.

codecov-io: # [Codecov](https://codecov.io/gh/paritytech/ink/pull/270?src=pr&el=h1) Report
> Merging [#270](https://codecov.io/gh/paritytech/ink/pull/270?src=pr&el=desc) into [master](https://codecov.io/gh/paritytech/ink/commit/72c1f1877d7e7005dd108906e569b1b58eae51a9?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/paritytech/ink/pull/270/graphs/tree.svg?width=650&token=tYyVaCZb7o&height=150&src=pr)](https://codecov.io/gh/paritytech/ink/pull/270?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master     #270   +/-   ##
=======================================
  Coverage   46.37%   46.37%           
=======================================
  Files           7        7           
  Lines         345      345           
=======================================
  Hits          160      160           
  Misses        185      185
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/paritytech/ink/pull/270?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/paritytech/ink/pull/270?src=pr&el=footer). Last update [72c1f18...216ccc4](https://codecov.io/gh/paritytech/ink/pull/270?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 0ab3551771..c7c2bd1496 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -18,7 +18,7 @@ include = ["Cargo.toml", "src/**/*.rs", "README.md", "LICENSE"]
 ink_abi = { path = "../abi/", default-features = false, features = ["derive"], optional = true }
 ink_alloc = { path = "../alloc/", default-features = false }
 ink_utils = { path = "../utils/" }
-ink_core_derive = { version = "0.1.0", path = "derive", default-features = false, optional = true }
+ink_core_derive = { version = "0.1.0", path = "derive", default-features = false }
 
 scale = { package = "parity-scale-codec", version = "1.1", default-features = false, features = ["derive", "full"] }
 type-metadata = { git = "https://github.com/type-metadata/type-metadata.git", default-features = false, features = ["derive"], optional = true }
@@ -28,9 +28,6 @@ cfg-if = "0.1"
 
 [features]
 default = ["test-env"]
-derive = [
-    "ink_core_derive",
-]
 test-env = [
     "std",
 ]
diff --git a/core/derive/src/lib.rs b/core/derive/src/lib.rs
index ccf71892e6..4aff732664 100644
--- a/core/derive/src/lib.rs
+++ b/core/derive/src/lib.rs
@@ -51,8 +51,6 @@ pub(crate) fn flush_derive(mut s: synstructure::Structure) -> TokenStream2 {
         quote! {}
     };
     s.gen_impl(quote! {
-        extern crate ink_core;
-
         gen impl ink_core::storage::Flush for @Self {
             fn flush(&mut self) {
                 #body
@@ -81,8 +79,6 @@ pub(crate) fn allocate_using_derive(mut s: synstructure::Structure) -> TokenStre
         }
     });
     s.gen_impl(quote! {
-        extern crate ink_core;
-
         gen impl ink_core::storage::alloc::AllocateUsing for @Self {
             unsafe fn allocate_using<A>(alloc: &mut A) -> Self
             where
diff --git a/core/src/env/srml/types.rs b/core/src/env/srml/types.rs
index 19622a4cad..2ecc5cd228 100644
--- a/core/src/env/srml/types.rs
+++ b/core/src/env/srml/types.rs
@@ -12,21 +12,20 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+use crate::{
+    env::EnvTypes,
+    ink_core,
+    storage::Flush,
+};
 use core::{
     array::TryFromSliceError,
     convert::TryFrom,
 };
-
 use scale::{
     Decode,
     Encode,
 };
 
-use crate::{
-    env::EnvTypes,
-    storage::Flush,
-};
-
 #[cfg(feature = "ink-generate-abi")]
 use type_metadata::Metadata;
 
@@ -66,7 +65,7 @@ impl EnvTypes for DefaultSrmlTypes {
 }
 
 /// The default SRML address type.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode, Flush)]
 #[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct AccountId([u8; 32]);
 
@@ -89,7 +88,7 @@ impl<'a> TryFrom<&'a [u8]> for AccountId {
 pub type Balance = u128;
 
 /// The default SRML hash type.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode, Flush)]
 #[cfg_attr(feature = "ink-generate-abi", derive(Metadata))]
 pub struct Hash([u8; 32]);
 
@@ -119,11 +118,3 @@ pub type Moment = u64;
 
 /// The default SRML blocknumber type.
 pub type BlockNumber = u64;
-
-impl Flush for AccountId {
-    fn flush(&mut self) {}
-}
-
-impl Flush for Hash {
-    fn flush(&mut self) {}
-}
diff --git a/core/src/env2/buffer_arena.rs b/core/src/env2/buffer_arena.rs
new file mode 100644
index 0000000000..1de3a1622c
--- /dev/null
+++ b/core/src/env2/buffer_arena.rs
@@ -0,0 +1,372 @@
+// Copyright 2018-2019 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.
+
+//! This file defines the global buffer arena that is accessible globally
+//! and acts as a cache for allocated heap memory to avoid heap memory
+//! throttling resulting from heap usage for intermediate computations.
+//!
+//! Exactly this happens a lot in the boundaries between SRML contracts
+//! and ink! since encoding and decoding of SCALE values has to be done
+//! in such an intermediate buffer.
+//!
+//! Users and systems are advised to share a common set of allocated buffers
+//! provided by the global buffer arena.
+
+use crate::{
+    env2::utils::{
+        EnlargeTo,
+        Reset,
+    },
+    memory::vec::Vec,
+};
+use cfg_if::cfg_if;
+use core::cell::{
+    Cell,
+    RefCell,
+};
+
+/// The maximum amount of used byte buffers at the same time.
+///
+/// Since the whole point behind this byte buffer arena is to cache
+/// allocated heap memory the number of concurrent byte buffers that are
+/// in use at the same time should be kept small.
+const IN_USE_LIMIT: usize = 1000;
+
+cfg_if! {
+    if #[cfg(feature = "std")] {
+        thread_local! {
+            /// Global buffer arena that provides shared recycled buffers to avoid
+            /// constantly allocating and deallocating heap memory during contract execution.
+            ///
+            /// # Note
+            ///
+            /// This is mainly useful to interact with the environment because
+            /// it requires intermediate buffers for its encoding and decoding.
+            ///
+            /// # API
+            ///
+            /// - Can be accessed through [`std::thread::LocalKey::with`].
+            /// - Provides recycled buffers through the `get_buffer` call.
+            pub static BUFFER_ARENA: BufferArena = BufferArena::new();
+        }
+    } else {
+        /// Global buffer arena that provides shared recycled buffers to avoid
+        /// constantly allocating and deallocating heap memory during contract execution.
+        ///
+        /// # Note
+        ///
+        /// This is mainly useful to interact with the environment because
+        /// it requires intermediate buffers for its encoding and decoding.
+        ///
+        /// # API
+        ///
+        /// - Can be accessed through [`std::thread::LocalKey::with`].
+        /// - Provides recycled buffers through the `get_buffer` call.
+        pub static BUFFER_ARENA: GlobalBufferArena = GlobalBufferArena::new(BufferArena::new());
+
+        /// Wrapper around `BufferArena` to provide similar interface
+        /// as `std::thread::LocalKey` provided by `thread_local` does.
+        ///
+        /// Also acts as safety guard to prevent references to `BufferRef`
+        /// escape the closure using the [`GlobalBufferArena::with`] API.
+        pub struct GlobalBufferArena {
+            /// The wrapped buffer arena.
+            arena: BufferArena,
+        }
+
+        /// CRITICAL NOTE
+        /// =============
+        ///
+        /// The wrapped `BufferArena` type itself is __NOT__ `Sync` since it is using
+        /// `Cell` and `RefCell` internally instead of the thread-safe alternatives.
+        /// However, since Wasm smart contracts are guaranteed to operate single
+        /// threaded we can allow for this unsafe `Sync` implementation to allow
+        /// for having the global static `BUFFER_ARENA` variable and as long as we
+        /// are only operating single threaded this shouldn't be unsafe.
+        unsafe impl Sync for GlobalBufferArena {}
+
+        impl GlobalBufferArena {
+            /// Creates a new `GlobalBufferArena` from the given `BufferArena`.
+            pub const fn new(arena: BufferArena) -> Self {
+                Self { arena }
+            }
+
+            /// Runs the given closure for the wrapped `BufferArena`.
+            ///
+            /// This way no references may escape the closure.
+            pub fn with<F>(&self, f: F)
+            where
+                F: FnOnce(&BufferArena),
+            {
+                f(&self.arena)
+            }
+        }
+    }
+}
+
+/// A byte buffer arena that acts as a cache for allocated heap memory.
+pub struct BufferArena {
+    /// The currently available byte buffers.
+    free: RefCell<Vec<Buffer>>,
+    /// Counts the buffers that are currently in use at the same time.
+    ///
+    /// # Note
+    ///
+    /// This value is purely used as diagnostic measures to provide
+    /// smart contract writers with feedback if their implementation
+    /// is abusing the buffer arena.
+    /// We might want to turn these checks off for Wasm compilation.
+    in_use: Cell<usize>,
+}
+
+impl BufferArena {
+    /// Returns a new empty buffer arena.
+    ///
+    /// Since this acts as cache we only require one instance of this type
+    /// that we use as `thread_local` global which is safe since
+    /// Wasm smart contracts are guaranteed to run in a single thread.
+    pub(self) const fn new() -> Self {
+        Self {
+            free: RefCell::new(Vec::new()),
+            in_use: Cell::new(0),
+        }
+    }
+
+    /// Returns a fresh buffer that can be used for intermediate computations.
+    ///
+    /// Buffers returned through this API implement all the necessary traits
+    /// required to use them as environment buffer.
+    ///
+    /// - [`Reset`]: Allows resetting the buffer. This clears all the elements,
+    ///            however, it retains the memory allocation.
+    /// - [`EnlargeTo`]: Safely enlarges the buffer to the required minimum size
+    ///                  if it isn't already large enough.
+    /// - [`core::convert::AsRef`]`<[u8]>`: Returns a shared view into the byte buffer.
+    /// - [`core::convert::AsMut`]`<[u8]>`: Returns an exclusive view into the byte buffer.
+    pub fn get_buffer(&self) -> BufferRef {
+        self.in_use.set(self.in_use() + 1);
+        if self.in_use() > IN_USE_LIMIT {
+            panic!("too many concurrent byte buffers")
+        }
+        self.free
+            .borrow_mut()
+            .pop()
+            .unwrap_or_else(Buffer::new)
+            .into_ref()
+    }
+
+    /// Returns the buffer to the arena.
+    ///
+    /// This is only called from the `Drop` implementation of `BufferRef`
+    /// to return the wrapped buffer back to the global buffer arena instance.
+    pub(self) fn return_buffer(&self, buffer: Buffer) {
+        self.in_use.set(self.in_use() - 1);
+        self.free.borrow_mut().push(buffer)
+    }
+
+    /// Returns the number of buffers that are currently in use at the same time.
+    pub fn in_use(&self) -> usize {
+        self.in_use.get()
+    }
+
+    /// Returns the number of buffers that are not in use.
+    pub fn free(&self) -> usize {
+        self.free.borrow().len()
+    }
+
+    /// Returns the current number of cached buffers.
+    pub fn allocated(&self) -> usize {
+        self.in_use() + self.free()
+    }
+}
+
+/// A byte buffer.
+///
+/// This is a thin wrapper around a byte vector providing only
+/// the minimal interface to be operable as environmental intermediate
+/// buffer.
+pub struct Buffer {
+    /// The warpper internal raw byte buffer.
+    buffer: Vec<u8>,
+}
+
+impl<'a> Buffer
+where
+    Self: 'a,
+{
+    /// Returns a new empty byte buffer.
+    pub(self) const fn new() -> Self {
+        Self { buffer: Vec::new() }
+    }
+
+    /// Wraps `self` in a buffer reference.
+    pub(self) fn into_ref(self) -> BufferRef<'a> {
+        BufferRef {
+            buffer: self,
+            lt: core::marker::PhantomData,
+        }
+    }
+}
+
+impl Reset for Buffer {
+    fn reset(&mut self) {
+        Reset::reset(&mut self.buffer)
+    }
+}
+
+impl EnlargeTo for Buffer {
+    fn enlarge_to(&mut self, new_size: usize) {
+        EnlargeTo::enlarge_to(&mut self.buffer, new_size)
+    }
+}
+
+impl core::convert::AsRef<[u8]> for Buffer {
+    fn as_ref(&self) -> &[u8] {
+        &self.buffer
+    }
+}
+
+impl core::convert::AsMut<[u8]> for Buffer {
+    fn as_mut(&mut self) -> &mut [u8] {
+        &mut self.buffer
+    }
+}
+
+/// A buffer reference providing lifetime constrained access
+/// to the wrapper byte buffer.
+///
+/// Buffer references are created by the only global buffer arena
+/// instance and make sure that their wrapper buffer is returned to
+/// the arena upon `Drop`.
+pub struct BufferRef<'a> {
+    /// The wrapped byte buffer.
+    buffer: Buffer,
+    /// The emulated lifetime.
+    lt: core::marker::PhantomData<fn() -> &'a ()>,
+}
+
+impl BufferRef<'_> {
+    /// Takes the buffer out of the buffer reference
+    /// leaving behind an empty byte buffer without
+    /// associated heap allocation.
+    ///
+    /// Also resets the byte buffer.
+    fn take_buffer(&mut self) -> Buffer {
+        Reset::reset(&mut self.buffer);
+        core::mem::replace(&mut self.buffer, Buffer::new())
+    }
+}
+
+impl Reset for BufferRef<'_> {
+    fn reset(&mut self) {
+        Reset::reset(&mut self.buffer)
+    }
+}
+
+impl EnlargeTo for BufferRef<'_> {
+    fn enlarge_to(&mut self, new_size: usize) {
+        EnlargeTo::enlarge_to(&mut self.buffer, new_size)
+    }
+}
+
+impl core::convert::AsRef<[u8]> for BufferRef<'_> {
+    fn as_ref(&self) -> &[u8] {
+        core::convert::AsRef::<[u8]>::as_ref(&self.buffer)
+    }
+}
+
+impl core::convert::AsMut<[u8]> for BufferRef<'_> {
+    fn as_mut(&mut self) -> &mut [u8] {
+        core::convert::AsMut::<[u8]>::as_mut(&mut self.buffer)
+    }
+}
+
+impl Drop for BufferRef<'_> {
+    fn drop(&mut self) {
+        // Returns the byte buffer back to the global buffer arena.
+        BUFFER_ARENA.with(|arena| arena.return_buffer(self.take_buffer()))
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    macro_rules! assert_arena {
+        (
+            $arena:ident,
+            in_use: $expected_in_use:literal,
+            free: $expected_free:literal,
+            allocated: $expected_allocated:literal
+        ) => {{
+            assert_eq!(
+                $arena.in_use(),
+                $expected_in_use,
+                "number of buffers in use doesn't match expected"
+            );
+            assert_eq!(
+                $arena.free(),
+                $expected_free,
+                "number of free buffers doens't match expected"
+            );
+            assert_eq!(
+                $arena.allocated(),
+                $expected_allocated,
+                "number of allocated buffers doesn't match expected"
+            );
+        }};
+    }
+
+    #[test]
+    fn it_works() {
+        BUFFER_ARENA.with(|arena| {
+            assert_arena!(arena, in_use: 0, free: 0, allocated: 0);
+            // Allocate a single buffer for a short time.
+            {
+                let _b = arena.get_buffer();
+                assert_arena!(arena, in_use: 1, free: 0, allocated: 1);
+            }
+            // We should now have a single allocated buffer
+            // but none in use.
+            assert_arena!(arena, in_use: 0, free: 1, allocated: 1);
+            // Allocate a single buffer again so that we see
+            // it is being reused.
+            {
+                let _b = arena.get_buffer();
+                assert_arena!(arena, in_use: 1, free: 0, allocated: 1);
+            }
+            assert_arena!(arena, in_use: 0, free: 1, allocated: 1);
+            // Now we allocate 3 buffers in their own scope
+            // and check the `in_use` and `allocated`.
+            {
+                let _b0 = arena.get_buffer();
+                {
+                    let _b1 = arena.get_buffer();
+                    {
+                        // At this point we should have 3 buffers
+                        // allocated and in use.
+                        let _b2 = arena.get_buffer();
+                        assert_arena!(arena, in_use: 3, free: 0, allocated: 3);
+                    }
+                    assert_arena!(arena, in_use: 2, free: 1, allocated: 3);
+                }
+                assert_arena!(arena, in_use: 1, free: 2, allocated: 3);
+            }
+            // At this point we dropped all 3 buffers again
+            // so none is in use but we still have 3 allocated
+            // buffers.
+            assert_arena!(arena, in_use: 0, free: 3, allocated: 3);
+        });
+    }
+}
diff --git a/core/src/env2/env_access/immutable.rs b/core/src/env2/env_access/immutable.rs
index 252fb2c96e..8d45eda30d 100644
--- a/core/src/env2/env_access/immutable.rs
+++ b/core/src/env2/env_access/immutable.rs
@@ -122,10 +122,7 @@ impl<E> AllocateUsing for EnvAccess<E> {
     }
 }
 
-impl<E> Flush for EnvAccess<E> {
-    #[inline(always)]
-    fn flush(&mut self) {}
-}
+impl<E> Flush for EnvAccess<E> {}
 
 impl<E> Initialize for EnvAccess<E> {
     type Args = ();
diff --git a/core/src/env2/env_access/mutable.rs b/core/src/env2/env_access/mutable.rs
index 0f7e308410..eabcfa0f56 100644
--- a/core/src/env2/env_access/mutable.rs
+++ b/core/src/env2/env_access/mutable.rs
@@ -74,10 +74,7 @@ impl<E> AllocateUsing for EnvAccessMut<E> {
     }
 }
 
-impl<E> Flush for EnvAccessMut<E> {
-    #[inline]
-    fn flush(&mut self) {}
-}
+impl<E> Flush for EnvAccessMut<E> {}
 
 impl<E> Initialize for EnvAccessMut<E> {
     type Args = ();
diff --git a/core/src/env2/mod.rs b/core/src/env2/mod.rs
index c9fa9151ad..a5892df924 100644
--- a/core/src/env2/mod.rs
+++ b/core/src/env2/mod.rs
@@ -42,6 +42,7 @@
 //! - For `&mut self` messages prefer using `EnvAccessMut` or `DynEnvAccessMut`.
 //! - Direct access to `SrmlEnv` or `TestEnv` is always the least optimal solution and generally not preferred.
 
+mod buffer_arena;
 pub mod call;
 mod dyn_env;
 mod env_access;
@@ -86,7 +87,15 @@ cfg_if! {
     }
 }
 
+#[cfg(not(feature = "std"))]
+pub use self::buffer_arena::GlobalBufferArena;
+
 pub use self::{
+    buffer_arena::{
+        BufferArena,
+        BufferRef,
+        BUFFER_ARENA,
+    },
     dyn_env::DynEnv,
     env_access::{
         AccessEnv,
diff --git a/core/src/env2/types.rs b/core/src/env2/types.rs
index 48282349c8..ffb55ca252 100644
--- a/core/src/env2/types.rs
+++ b/core/src/env2/types.rs
@@ -119,10 +119,7 @@ impl<'a> TryFrom<&'a [u8]> for AccountId {
     }
 }
 
-impl Flush for AccountId {
-    #[inline]
-    fn flush(&mut self) {}
-}
+impl Flush for AccountId {}
 
 /// The default SRML `Hash` type.
 #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Encode, Decode, From, Default)]
@@ -138,7 +135,4 @@ impl<'a> TryFrom<&'a [u8]> for Hash {
     }
 }
 
-impl Flush for Hash {
-    #[inline]
-    fn flush(&mut self) {}
-}
+impl Flush for Hash {}
diff --git a/core/src/lib.rs b/core/src/lib.rs
index 83835ce82c..4ee56a2981 100644
--- a/core/src/lib.rs
+++ b/core/src/lib.rs
@@ -58,3 +58,6 @@ pub mod env;
 pub mod env2;
 pub mod memory;
 pub mod storage;
+
+// Needed for derive macros of `core/derive` sub crate.
+pub(crate) use crate as ink_core;
diff --git a/core/src/storage/alloc/mod.rs b/core/src/storage/alloc/mod.rs
index 5bd179fe75..7435eb891b 100644
--- a/core/src/storage/alloc/mod.rs
+++ b/core/src/storage/alloc/mod.rs
@@ -32,5 +32,4 @@ pub use self::{
     },
 };
 
-#[cfg(feature = "derive")]
 pub use ink_core_derive::AllocateUsing;
diff --git a/core/src/storage/collections/bitvec/block.rs b/core/src/storage/collections/bitvec/block.rs
index 1c3a4fedc8..4b3ca501ca 100644
--- a/core/src/storage/collections/bitvec/block.rs
+++ b/core/src/storage/collections/bitvec/block.rs
@@ -30,9 +30,7 @@ pub struct BitBlock {
     packs: [BitPack; Self::PACKS as usize],
 }
 
-impl Flush for BitBlock {
-    fn flush(&mut self) {}
-}
+impl Flush for BitBlock {}
 
 /// Error indicating an invalid bit pack index.
 #[derive(Debug, Copy, Clone)]
diff --git a/core/src/storage/collections/stash/impls.rs b/core/src/storage/collections/stash/impls.rs
index 8c3530b8ea..a71b5132c1 100644
--- a/core/src/storage/collections/stash/impls.rs
+++ b/core/src/storage/collections/stash/impls.rs
@@ -26,16 +26,19 @@ use scale::{
 #[cfg(feature = "ink-generate-abi")]
 use type_metadata::Metadata;
 
-use crate::storage::{
-    self,
-    alloc::{
-        Allocate,
-        AllocateUsing,
-        Initialize,
+use crate::{
+    ink_core,
+    storage::{
+        self,
+        alloc::{
+            Allocate,
+            AllocateUsing,
+            Initialize,
+        },
+        chunk::SyncChunk,
+        Flush,
+        Key,
     },
-    chunk::SyncChunk,
-    Flush,
-    Key,
 };
 
 /// A stash collection.
@@ -91,14 +94,8 @@ struct StashHeader {
     max_len: u32,
 }
 
-impl Flush for StashHeader {
-    #[inline]
-    fn flush(&mut self) {
-        self.next_vacant.flush();
-        self.len.flush();
-        self.max_len.flush();
-    }
-}
+/// No need to forward flush to fields.
+impl ink_core::storage::Flush for StashHeader {}
 
 /// Iterator over the values of a stash.
 #[derive(Debug)]
diff --git a/core/src/storage/flush.rs b/core/src/storage/flush.rs
index 38ecfc055e..72525d60ea 100644
--- a/core/src/storage/flush.rs
+++ b/core/src/storage/flush.rs
@@ -37,20 +37,18 @@ pub trait Flush {
     /// Needs to take `self` by `&mut` since `SyncChunk` and `SyncCell`
     /// and potentially other abstraction facilities are required to
     /// write back their cached values which is a mutable operation.
-    fn flush(&mut self);
+    #[inline(always)]
+    fn flush(&mut self) {}
 }
 
-#[cfg(feature = "derive")]
 pub use ink_core_derive::Flush;
 
 macro_rules! impl_empty_flush_for {
-	( $($ty:ty),* ) => {
-		$(
-			impl Flush for $ty {
-				fn flush(&mut self) {}
-			}
-		)*
-	};
+    ( $($ty:ty),* ) => {
+        $(
+            impl Flush for $ty {}
+        )*
+    };
 }
 
 impl_empty_flush_for! {
@@ -60,20 +58,20 @@ impl_empty_flush_for! {
 }
 
 macro_rules! impl_tuple_flush_for {
-	( $(($n:tt, $name:ident)),* ) => {
-		impl< $($name),* > Flush for ($($name,)*)
-		where
-			$(
-				$name: Flush,
-			)*
-		{
-			fn flush(&mut self) {
-				$(
-					self.$n.flush();
-				)*
-			}
-		}
-	}
+    ( $(($n:tt, $name:ident)),* ) => {
+        impl< $($name),* > Flush for ($($name,)*)
+        where
+            $(
+                $name: Flush,
+            )*
+        {
+            fn flush(&mut self) {
+                $(
+                    self.$n.flush();
+                )*
+            }
+        }
+    }
 }
 
 impl_tuple_flush_for!();
@@ -167,11 +165,7 @@ where
     }
 }
 
-impl Flush for crate::memory::string::String {
-    fn flush(&mut self) {
-        // Note: Strings contain only characters that need no flushing.
-    }
-}
+impl Flush for crate::memory::string::String {}
 
 impl<K, V> Flush for crate::memory::collections::btree_map::BTreeMap<K, V>
 where
@@ -185,11 +179,7 @@ where
     }
 }
 
-impl<T> Flush for crate::memory::collections::btree_set::BTreeSet<T> {
-    fn flush(&mut self) {
-        // Note: Values within a `BTreeSet` are immutable and thus need not be flushed.
-    }
-}
+impl<T> Flush for crate::memory::collections::btree_set::BTreeSet<T> {}
 
 impl<T> Flush for crate::memory::collections::linked_list::LinkedList<T>
 where
@@ -213,11 +203,4 @@ where
     }
 }
 
-impl<T> Flush for crate::memory::collections::binary_heap::BinaryHeap<T>
-where
-    T: Flush,
-{
-    fn flush(&mut self) {
-        // Note: Values within a `BinaryHeap` are immutable and thus need not be flushed.
-    }
-}
+impl<T> Flush for crate::memory::collections::binary_heap::BinaryHeap<T> {}
diff --git a/examples/lang/delegator/src/lib.rs b/examples/lang/delegator/src/lib.rs
index fee5c5a1ba..800f377df0 100644
--- a/examples/lang/delegator/src/lib.rs
+++ b/examples/lang/delegator/src/lib.rs
@@ -27,9 +27,7 @@ pub enum Which {
     Subber,
 }
 
-impl ink_core::storage::Flush for Which {
-    fn flush(&mut self) {}
-}
+impl ink_core::storage::Flush for Which {}
 
 contract! {
     #![env = ink_core::env::DefaultSrmlTypes]
diff --git a/examples/lang/shared_vec/src/lib.rs b/examples/lang/shared_vec/src/lib.rs
index 2b55a00060..41f4f8d830 100644
--- a/examples/lang/shared_vec/src/lib.rs
+++ b/examples/lang/shared_vec/src/lib.rs
@@ -29,9 +29,7 @@ pub struct Access {
     pub end: Option<u32>,
 }
 
-impl Flush for Access {
-	fn flush(&mut self) {}
-}
+impl Flush for Access {}
 
 impl Access {
     /// Creates unlimited access rights.
diff --git a/examples/lang2/delegator/lib.rs b/examples/lang2/delegator/lib.rs
index e73a3d2596..c42c01ce0b 100644
--- a/examples/lang2/delegator/lib.rs
+++ b/examples/lang2/delegator/lib.rs
@@ -15,7 +15,10 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 #![feature(proc_macro_hygiene)]
 
-use ink_core::storage;
+use ink_core::storage::{
+    self,
+    Flush,
+};
 use ink_lang2 as ink;
 
 use accumulator::Accumulator;
@@ -30,17 +33,13 @@ mod delegator {
     /// and in `Subber` state will delegate to the `Subber` contract.
     ///
     /// The initial state is `Adder`.
-    #[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode)]
+    #[derive(Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode, Flush)]
     #[cfg_attr(feature = "ink-generate-abi", derive(type_metadata::Metadata))]
     pub enum Which {
         Adder,
         Subber,
     }
 
-    impl ink_core::storage::Flush for Which {
-        fn flush(&mut self) {}
-    }
-
     /// Delegates calls to an adder or subber contract to mutate
     /// a value in an accumulator contract.
     ///
diff --git a/lang/src/gen/as_dependency.rs b/lang/src/gen/as_dependency.rs
index 5a7384132f..d8ab6e996d 100644
--- a/lang/src/gen/as_dependency.rs
+++ b/lang/src/gen/as_dependency.rs
@@ -88,9 +88,7 @@ fn generate_state_as_dependency(contract: &hir::Contract) -> TokenStream2 {
             account_id: AccountId,
         }
 
-        impl ink_core::storage::Flush for #name {
-            fn flush(&mut self) {}
-        }
+        impl ink_core::storage::Flush for #name {}
 
         /// Allows to enhance calls to `&self` contract messages.
         pub struct CallEnhancer<'a> {
diff --git a/lang2/macro/src/codegen/cross_calling.rs b/lang2/macro/src/codegen/cross_calling.rs
index e54033182e..8de969884d 100644
--- a/lang2/macro/src/codegen/cross_calling.rs
+++ b/lang2/macro/src/codegen/cross_calling.rs
@@ -156,12 +156,7 @@ impl CrossCalling<'_> {
 
     fn generate_storage_impls(&self) -> TokenStream2 {
         quote! {
-            impl ink_core::storage::Flush for StorageAsDependency {
-                #[inline(always)]
-                fn flush(&mut self) {
-                    // Nothing to do here!
-                }
-            }
+            impl ink_core::storage::Flush for StorageAsDependency {}
 
             #[cfg(feature = "ink-generate-abi")]
             impl ink_core::storage::alloc::AllocateUsing for StorageAsDependency {


Test patch:
diff --git a/lang/src/tests/events.rs b/lang/src/tests/events.rs
index 486726949c..2c3a0d7b5b 100644
--- a/lang/src/tests/events.rs
+++ b/lang/src/tests/events.rs
@@ -402,9 +402,7 @@ fn contract_compiles() {
                     account_id: AccountId,
                 }
 
-                impl ink_core::storage::Flush for CallCounter {
-                    fn flush(&mut self) {}
-                }
+                impl ink_core::storage::Flush for CallCounter {}
 
                 /// Allows to enhance calls to `&self` contract messages.
                 pub struct CallEnhancer<'a> {
diff --git a/lang/src/tests/flipper.rs b/lang/src/tests/flipper.rs
index af727aa640..d4f2e89a79 100644
--- a/lang/src/tests/flipper.rs
+++ b/lang/src/tests/flipper.rs
@@ -264,9 +264,7 @@ fn contract_compiles() {
                     account_id: AccountId,
                 }
 
-                impl ink_core::storage::Flush for Flipper {
-                    fn flush(&mut self) {}
-                }
+                impl ink_core::storage::Flush for Flipper {}
 
                 /// Allows to enhance calls to `&self` contract messages.
                 pub struct CallEnhancer<'a> {
diff --git a/lang/src/tests/incrementer.rs b/lang/src/tests/incrementer.rs
index 4721f3b6dc..a65b6d4702 100644
--- a/lang/src/tests/incrementer.rs
+++ b/lang/src/tests/incrementer.rs
@@ -333,9 +333,7 @@ fn contract_compiles() {
                     account_id: AccountId,
                 }
 
-                impl ink_core::storage::Flush for Incrementer {
-                    fn flush(&mut self) {}
-                }
+                impl ink_core::storage::Flush for Incrementer {}
 
                 /// Allows to enhance calls to `&self` contract messages.
                 pub struct CallEnhancer<'a> {
diff --git a/lang/src/tests/noop.rs b/lang/src/tests/noop.rs
index f3c176eae9..73fae58b3b 100644
--- a/lang/src/tests/noop.rs
+++ b/lang/src/tests/noop.rs
@@ -205,9 +205,7 @@ fn contract_compiles() {
                     account_id: AccountId,
                 }
 
-                impl ink_core::storage::Flush for Noop {
-                    fn flush(&mut self) {}
-                }
+                impl ink_core::storage::Flush for Noop {}
 
                 /// Allows to enhance calls to `&self` contract messages.
                 pub struct CallEnhancer<'a> {