Repository: paritytech/ink
Pull request #456 (2020-06-18 13:20:48)

[examples] remove config made redundant by cargo-contract
Remove stuff which is now generated by `cargo-contract`:
- abi/metadata packages (https://github.com/paritytech/cargo-contract/pull/58)
- `profile.release` sections (https://github.com/paritytech/cargo-contract/pull/55)

**Note:** `cargo-contract` will not be released to crates.io until `ink 3.0`, so this PR changes the CI to install from a github tag.

## todo

- [x] fix `dns`
- [x] fix `delegator`
- [ ] update `check-examples` to use `cargo-contract`

Robbepop: > Nice! I really like this change.
> I'm already hating you @ascjones because it is a breaking change facepalm
> However it is okay to keep it for ink! 3.0.

It is really important that we get rid of all the cruft from ink! before the next big milestones.

Patch:
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b191bb50bb..db8222d80c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -166,8 +166,10 @@ fmt:
 #### stage:                        examples
 
 .update-cargo-contract:            &update-cargo-contract
-  # `cargo install` returns an error if there is nothing to update, so have to supress it here temporarily
-  - cargo install cargo-contract || echo $?
+  # `cargo install` returns an error if there is nothing to update, so have to suppress it here temporarily
+# restore this once ink! 3.0 is published and the new version of cargo-contract has been released to crates.io
+#  - cargo install cargo-contract || echo $?
+  - cargo install --git https://github.com/paritytech/cargo-contract --tag ink-ci || echo $?
   - cargo contract -V
 
 examples-test:
diff --git a/core/Cargo.toml b/core/Cargo.toml
index fbdb9f05b8..66a701bb77 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -72,5 +72,9 @@ std = [
 ]
 
 [[bench]]
-name = "bench"
+name = "bench_vec"
+harness = false
+
+[[bench]]
+name = "bench_stash"
 harness = false
diff --git a/core/benches/bench_stash.rs b/core/benches/bench_stash.rs
new file mode 100644
index 0000000000..52c1054537
--- /dev/null
+++ b/core/benches/bench_stash.rs
@@ -0,0 +1,138 @@
+// Copyright 2019-2020 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 criterion::{
+    black_box,
+    criterion_group,
+    criterion_main,
+    Criterion,
+};
+
+use ink_core::{
+    env,
+    storage2::{
+        collections::Stash as StorageStash,
+        traits::{
+            KeyPtr,
+            SpreadLayout,
+        },
+    },
+};
+use ink_primitives::Key;
+
+criterion_group!(populated_cache, bench_remove_occupied_populated_cache,);
+criterion_group!(empty_cache, bench_remove_occupied_empty_cache,);
+criterion_main!(populated_cache, empty_cache,);
+
+/// Returns some test values for use in benchmarks.
+#[rustfmt::skip]
+fn test_values() -> &'static [u8] {
+    &[
+        b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9',
+        b'A', b'B', b'C', b'D', b'E', b'F'
+    ]
+}
+
+/// Creates a storage stash from the given slice.
+fn storage_stash_from_slice(slice: &[u8]) -> StorageStash<u8> {
+    slice.iter().copied().collect::<StorageStash<u8>>()
+}
+
+/// Creates a storage stash and pushes it to the contract storage.
+fn push_storage_stash() {
+    let stash = storage_stash_from_slice(test_values());
+    let root_key = Key::from([0x00; 32]);
+    SpreadLayout::push_spread(&stash, &mut KeyPtr::from(root_key));
+}
+
+/// Pulls a lazily loading storage stash instance from the contract storage.
+fn pull_storage_stash() -> StorageStash<u8> {
+    let root_key = Key::from([0x00; 32]);
+    <StorageStash<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key))
+}
+
+mod populated_cache {
+    use super::*;
+
+    pub fn remove_occupied_all(test_values: &[u8]) {
+        let mut stash = storage_stash_from_slice(test_values);
+        for (index, _value) in test_values.iter().enumerate() {
+            black_box(unsafe { stash.remove_occupied(index as u32) });
+        }
+    }
+
+    pub fn take_all(test_values: &[u8]) {
+        let mut stash = storage_stash_from_slice(test_values);
+        for (index, _value) in test_values.iter().enumerate() {
+            black_box(stash.take(index as u32));
+        }
+    }
+}
+
+fn bench_remove_occupied_populated_cache(c: &mut Criterion) {
+    let mut group = c.benchmark_group(
+        "Compare: `remove_occupied_all` and `take_all` (populated cache)",
+    );
+    let test_values = [b'A', b'B', b'C', b'D', b'E', b'F'];
+    group.bench_with_input("remove_occupied_all", &test_values, |b, i| {
+        b.iter(|| populated_cache::remove_occupied_all(i))
+    });
+    group.bench_with_input("take_all", &test_values, |b, i| {
+        b.iter(|| populated_cache::take_all(i))
+    });
+    group.finish();
+}
+
+mod empty_cache {
+    use super::*;
+
+    /// In this case we lazily load the stash from storage using `pull_spread`.
+    /// This will just load lazily and won't pull anything from the storage.
+    pub fn remove_occupied_all() {
+        push_storage_stash();
+        let mut stash = pull_storage_stash();
+        for index in 0..stash.len() {
+            black_box(unsafe { stash.remove_occupied(index) });
+        }
+    }
+
+    /// In this case we lazily load the stash from storage using `pull_spread`.
+    /// This will just load lazily and won't pull anything from the storage.
+    /// `take` will then result in loading from storage.
+    pub fn take_all() {
+        push_storage_stash();
+        let mut stash = pull_storage_stash();
+        for index in 0..stash.len() {
+            black_box(stash.take(index));
+        }
+    }
+}
+
+/// In this case we lazily instantiate a `StorageStash` by first `create_and_store`-ing
+/// into the contract storage. We then load the stash from storage lazily in each
+/// benchmark iteration.
+fn bench_remove_occupied_empty_cache(c: &mut Criterion) {
+    let _ = env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
+        let mut group = c.benchmark_group(
+            "Compare: `remove_occupied_all` and `take_all` (empty cache)",
+        );
+        group.bench_function("remove_occupied_all", |b| {
+            b.iter(|| empty_cache::remove_occupied_all())
+        });
+        group.bench_function("take_all", |b| b.iter(|| empty_cache::take_all()));
+        group.finish();
+        Ok(())
+    })
+    .unwrap();
+}
diff --git a/core/benches/bench.rs b/core/benches/bench_vec.rs
similarity index 98%
rename from core/benches/bench.rs
rename to core/benches/bench_vec.rs
index 3839ba6fa9..1b5a268d43 100644
--- a/core/benches/bench.rs
+++ b/core/benches/bench_vec.rs
@@ -84,7 +84,7 @@ mod populated_cache {
     pub fn set(test_values: &[u8]) {
         let mut vec = storage_vec_from_slice(&test_values);
         for (index, _value) in test_values.iter().enumerate() {
-            black_box(vec.set(index as u32, b'X'));
+            let _ = black_box(vec.set(index as u32, b'X'));
         }
     }
 
@@ -159,7 +159,7 @@ mod empty_cache {
         push_storage_vec();
         let mut vec = pull_storage_vec();
         for index in 0..vec.len() {
-            black_box(vec.set(index, b'X'));
+            let _ = black_box(vec.set(index, b'X'));
         }
     }
 }
diff --git a/core/src/storage2/collections/stash/mod.rs b/core/src/storage2/collections/stash/mod.rs
index f1f7d926b6..f9272270a3 100644
--- a/core/src/storage2/collections/stash/mod.rs
+++ b/core/src/storage2/collections/stash/mod.rs
@@ -344,49 +344,17 @@ where
         }
     }
 
-    /// Put the element into the stash at the next vacant position.
+    /// Returns the previous and next vacant entry for the entry at index `at`.
     ///
-    /// Returns the stash index that the element was put into.
-    pub fn put(&mut self, new_value: T) -> Index {
-        let new_entry = Some(Entry::Occupied(new_value));
-        let new_index = if let Some(index) = self.last_vacant_index() {
-            // Put the new element to the most recent vacant index if not all entries are occupied.
-            let old_entry = self
-                .entries
-                .put_get(index, new_entry)
-                .expect("a `last_vacant_index()` must point to an occupied cell");
-            let vacant_entry = match old_entry {
-                Entry::Vacant(vacant_entry) => vacant_entry,
-                Entry::Occupied(_) => {
-                    unreachable!("`last_vacant_index()` must point to a vacant entry")
-                }
-            };
-            self.remove_vacant_entry(index, vacant_entry);
-            index
-        } else {
-            // Push the new element to the end if all entries are occupied.
-            let new_index = self.header.len_entries;
-            self.entries.put(new_index, new_entry);
-            self.header.last_vacant += 1;
-            self.header.len_entries += 1;
-            new_index
-        };
-        self.header.len += 1;
-        new_index
-    }
-
-    /// Takes the element stored at the given index if any.
-    pub fn take(&mut self, at: Index) -> Option<T> {
-        // Cases:
-        // - There are vacant entries already.
-        // - There are no vacant entries before.
-        if at >= self.len_entries() {
-            // Early return since `at` index is out of bounds.
-            return None
-        }
-        // Precompute prev and next vacant entries as we might need them later.
-        // Due to borrow checker constraints we cannot have this at a later stage.
-        let (prev, next) = if let Some(index) = self.last_vacant_index() {
+    /// If there exists a last vacant entry, the return value is a tuple
+    /// `(index_of_previous_vacant, index_of_next_vacant)`.
+    /// The two `index_` values hereby are selected in a way that makes it
+    /// more likely that the stash is refilled from low indices.
+    ///
+    /// If no vacant entry exists a self-referential tuple of `(at, at)`
+    /// is returned.
+    fn fetch_prev_and_next_vacant_entry(&self, at: Index) -> (Index, Index) {
+        if let Some(index) = self.last_vacant_index() {
             let root_vacant = self
                 .entries
                 .get(index)
@@ -411,16 +379,16 @@ where
             // Default prev and next to the given at index.
             // So the resulting vacant index is pointing to itself.
             (at, at)
-        };
-        let entry_mut = self.entries.get_mut(at).expect("index is out of bounds");
-        if entry_mut.is_vacant() {
-            // Early return if the taken entry is already vacant.
-            return None
         }
-        // At this point we know that the entry is occupied with a value.
-        let new_vacant_entry = Entry::Vacant(VacantEntry { prev, next });
-        let taken_entry = core::mem::replace(entry_mut, new_vacant_entry);
-        // Update links from and to neighbouring vacant entries.
+    }
+
+    /// Updates links from and to neighbouring vacant entries.
+    fn update_neighboring_vacant_entry_links(
+        &mut self,
+        prev: Index,
+        next: Index,
+        at: Index,
+    ) {
         if prev == next {
             // Previous and next are the same so we can update the vacant
             // neighbour with a single look-up.
@@ -448,6 +416,60 @@ where
                 .expect("`next` must point to an existing vacant entry at this point")
                 .prev = at;
         }
+    }
+
+    /// Put the element into the stash at the next vacant position.
+    ///
+    /// Returns the stash index that the element was put into.
+    pub fn put(&mut self, new_value: T) -> Index {
+        let new_entry = Some(Entry::Occupied(new_value));
+        let new_index = if let Some(index) = self.last_vacant_index() {
+            // Put the new element to the most recent vacant index if not all entries are occupied.
+            let old_entry = self
+                .entries
+                .put_get(index, new_entry)
+                .expect("a `last_vacant_index()` must point to an occupied cell");
+            let vacant_entry = match old_entry {
+                Entry::Vacant(vacant_entry) => vacant_entry,
+                Entry::Occupied(_) => {
+                    unreachable!("`last_vacant_index()` must point to a vacant entry")
+                }
+            };
+            self.remove_vacant_entry(index, vacant_entry);
+            index
+        } else {
+            // Push the new element to the end if all entries are occupied.
+            let new_index = self.header.len_entries;
+            self.entries.put(new_index, new_entry);
+            self.header.last_vacant += 1;
+            self.header.len_entries += 1;
+            new_index
+        };
+        self.header.len += 1;
+        new_index
+    }
+
+    /// Takes the element stored at the given index if any.
+    pub fn take(&mut self, at: Index) -> Option<T> {
+        // Cases:
+        // - There are vacant entries already.
+        // - There are no vacant entries before.
+        if at >= self.len_entries() {
+            // Early return since `at` index is out of bounds.
+            return None
+        }
+        // Precompute prev and next vacant entries as we might need them later.
+        // Due to borrow checker constraints we cannot have this at a later stage.
+        let (prev, next) = self.fetch_prev_and_next_vacant_entry(at);
+        let entry_mut = self.entries.get_mut(at).expect("index is out of bounds");
+        if entry_mut.is_vacant() {
+            // Early return if the taken entry is already vacant.
+            return None
+        }
+        // At this point we know that the entry is occupied with a value.
+        let new_vacant_entry = Entry::Vacant(VacantEntry { prev, next });
+        let taken_entry = core::mem::replace(entry_mut, new_vacant_entry);
+        self.update_neighboring_vacant_entry_links(prev, next, at);
         // Take the value out of the taken occupied entry and return it.
         match taken_entry {
             Entry::Occupied(value) => {
@@ -463,6 +485,43 @@ where
         }
     }
 
+    /// Removes the element stored at the given index if any.
+    ///
+    /// This method acts similar to the take API and even still returns an Option.
+    /// However, it guarantees to make no contract storage reads to the indexed
+    /// element and will only write to its internal low-level lazy cache that the
+    /// element at the given index is going to be removed at the end of the contract
+    /// execution.
+    ///
+    /// Calling this method with an index out of bounds for the returns `None` and
+    /// does not `remove` the element, otherwise it returns `Some(())`.
+    ///
+    /// # Safety
+    ///
+    /// The caller must ensure that `at` refers to an occupied index. Behavior is
+    /// unspecified if `at` refers to a vacant index and could seriously damage the
+    /// contract storage integrity.
+    pub unsafe fn remove_occupied(&mut self, at: Index) -> Option<()> {
+        // This function is written similar to [`Stash::take`], with the exception
+        // that the caller has to ensure that `at` refers to an occupied entry whereby
+        // the procedure can avoid loading the occupied entry which might be handy if
+        // the stored `T` is especially costly to load from contract storage.
+        if at >= self.len_entries() {
+            // Early return since `at` index is out of bounds.
+            return None
+        }
+        // Precompute prev and next vacant entries as we might need them later.
+        // Due to borrow checker constraints we cannot have this at a later stage.
+        let (prev, next) = self.fetch_prev_and_next_vacant_entry(at);
+        let new_vacant_entry = Entry::Vacant(VacantEntry { prev, next });
+        self.entries.put(at, Some(new_vacant_entry));
+        self.update_neighboring_vacant_entry_links(prev, next, at);
+        use core::cmp::min;
+        self.header.last_vacant = min(self.header.last_vacant, min(at, min(prev, next)));
+        self.header.len -= 1;
+        Some(())
+    }
+
     /// Defragments the underlying storage to minimize footprint.
     ///
     /// Returns the number of storage cells freed this way.
diff --git a/examples/delegator/.ink/abi_gen/Cargo.toml b/examples/delegator/.ink/abi_gen/Cargo.toml
deleted file mode 100644
index 14b3bc85b6..0000000000
--- a/examples/delegator/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "delegator" }
-ink_lang = { path = "../../../../lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/delegator/.ink/abi_gen/main.rs b/examples/delegator/.ink/abi_gen/main.rs
deleted file mode 100644
index 0083dae1fd..0000000000
--- a/examples/delegator/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/delegator/Cargo.toml b/examples/delegator/Cargo.toml
index 136346b2ad..5a3d79980b 100644
--- a/examples/delegator/Cargo.toml
+++ b/examples/delegator/Cargo.toml
@@ -20,12 +20,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "delegator"
 path = "lib.rs"
-crate-type = [
-    # Used for normal contract Wasm blobs.
-    "cdylib",
-    # Used for ABI generation.
-    "rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -45,20 +40,9 @@ std = [
 ]
 ink-as-dependency = []
 
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
 [workspace]
 members = [
-    ".ink/abi_gen",
     "accumulator",
     "adder",
     "subber",
 ]
-exclude = [
-    ".ink"
-]
diff --git a/examples/delegator/lib.rs b/examples/delegator/lib.rs
index 1d644cfe70..abd21e0677 100644
--- a/examples/delegator/lib.rs
+++ b/examples/delegator/lib.rs
@@ -14,7 +14,6 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use self::delegator::Delegator;
 use ink_lang as ink;
 
 #[ink::contract(version = "0.1.0")]
diff --git a/examples/dns/.ink/abi_gen/Cargo.toml b/examples/dns/.ink/abi_gen/Cargo.toml
deleted file mode 100644
index 63e1f74ebf..0000000000
--- a/examples/dns/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "dns" }
-ink_lang = { path = "../../../../lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/dns/.ink/abi_gen/main.rs b/examples/dns/.ink/abi_gen/main.rs
deleted file mode 100644
index 0083dae1fd..0000000000
--- a/examples/dns/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/dns/Cargo.toml b/examples/dns/Cargo.toml
index 3a90281d4e..e2e37e7d6b 100644
--- a/examples/dns/Cargo.toml
+++ b/examples/dns/Cargo.toml
@@ -16,12 +16,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "dns"
 path = "lib.rs"
-crate-type = [
-    # Used for normal contract Wasm blobs.
-    "cdylib",
-    # Used for ABI generation.
-    "rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -36,18 +31,3 @@ std = [
     "scale-info/std",
 ]
 ink-as-dependency = []
-
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
-[workspace]
-members = [
-    ".ink/abi_gen"
-]
-exclude = [
-    ".ink"
-]
diff --git a/examples/dns/lib.rs b/examples/dns/lib.rs
index 58c0b58e37..12576c717f 100644
--- a/examples/dns/lib.rs
+++ b/examples/dns/lib.rs
@@ -14,7 +14,6 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use self::dns::DomainNameService;
 use ink_lang as ink;
 
 #[ink::contract(version = "0.1.0")]
diff --git a/examples/erc20/.ink/abi_gen/Cargo.toml b/examples/erc20/.ink/abi_gen/Cargo.toml
deleted file mode 100644
index 239f6d08ab..0000000000
--- a/examples/erc20/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "erc20" }
-ink_lang = { path = "../../../../lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/erc20/.ink/abi_gen/main.rs b/examples/erc20/.ink/abi_gen/main.rs
deleted file mode 100644
index 0083dae1fd..0000000000
--- a/examples/erc20/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/erc20/Cargo.toml b/examples/erc20/Cargo.toml
index d86b05e731..3ef266eee6 100644
--- a/examples/erc20/Cargo.toml
+++ b/examples/erc20/Cargo.toml
@@ -16,12 +16,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "erc20"
 path = "lib.rs"
-crate-type = [
-    # Used for normal contract Wasm blobs.
-    "cdylib",
-    # Used for ABI generation.
-    "rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -36,18 +31,3 @@ std = [
     "scale-info/std",
 ]
 ink-as-dependency = []
-
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
-[workspace]
-members = [
-    ".ink/abi_gen"
-]
-exclude = [
-    ".ink"
-]
diff --git a/examples/erc20/lib.rs b/examples/erc20/lib.rs
index 41059e84ad..3ae7ab364a 100644
--- a/examples/erc20/lib.rs
+++ b/examples/erc20/lib.rs
@@ -14,7 +14,6 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use self::erc20::Erc20;
 use ink_lang as ink;
 
 #[ink::contract(version = "0.1.0")]
diff --git a/examples/erc721/.ink/abi_gen/Cargo.toml b/examples/erc721/.ink/abi_gen/Cargo.toml
deleted file mode 100644
index eda6d16f6f..0000000000
--- a/examples/erc721/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "erc721" }
-ink_lang = { path = "../../../../lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/erc721/.ink/abi_gen/main.rs b/examples/erc721/.ink/abi_gen/main.rs
deleted file mode 100644
index 0083dae1fd..0000000000
--- a/examples/erc721/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/erc721/Cargo.toml b/examples/erc721/Cargo.toml
index 0fbac0cd37..0b2a5c38a0 100644
--- a/examples/erc721/Cargo.toml
+++ b/examples/erc721/Cargo.toml
@@ -16,12 +16,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "erc721"
 path = "lib.rs"
-crate-type = [
-	# Used for normal contract Wasm blobs.
-	"cdylib",
-	# Used for ABI generation.
-	"rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -36,18 +31,3 @@ std = [
     "scale-info/std",
 ]
 ink-as-dependency = []
-
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
-[workspace]
-members = [
-	".ink/abi_gen"
-]
-exclude = [
-	".ink"
-]
diff --git a/examples/erc721/lib.rs b/examples/erc721/lib.rs
index 94ad339e47..c5e6f7978e 100644
--- a/examples/erc721/lib.rs
+++ b/examples/erc721/lib.rs
@@ -64,7 +64,6 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use self::erc721::Erc721;
 use ink_lang as ink;
 
 #[ink::contract(version = "0.1.0")]
diff --git a/examples/flipper/.ink/abi_gen/Cargo.toml b/examples/flipper/.ink/abi_gen/Cargo.toml
deleted file mode 100644
index d25bd897da..0000000000
--- a/examples/flipper/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "flipper" }
-ink_lang = { path = "../../../../lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/flipper/.ink/abi_gen/main.rs b/examples/flipper/.ink/abi_gen/main.rs
deleted file mode 100644
index 0083dae1fd..0000000000
--- a/examples/flipper/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/flipper/Cargo.toml b/examples/flipper/Cargo.toml
index 8ed9a4b877..2333ff88a4 100644
--- a/examples/flipper/Cargo.toml
+++ b/examples/flipper/Cargo.toml
@@ -16,12 +16,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "flipper"
 path = "lib.rs"
-crate-type = [
-    # Used for normal contract Wasm blobs.
-    "cdylib",
-    # Used for ABI generation.
-    "rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -36,18 +31,3 @@ std = [
     "scale-info/std",
 ]
 ink-as-dependency = []
-
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
-[workspace]
-members = [
-    ".ink/abi_gen"
-]
-exclude = [
-    ".ink"
-]
diff --git a/examples/flipper/lib.rs b/examples/flipper/lib.rs
index 9ec274937b..0f511aa0c8 100644
--- a/examples/flipper/lib.rs
+++ b/examples/flipper/lib.rs
@@ -14,7 +14,6 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use self::flipper::Flipper;
 use ink_lang as ink;
 
 #[ink::contract(version = "0.1.0")]
diff --git a/examples/incrementer/.ink/abi_gen/Cargo.toml b/examples/incrementer/.ink/abi_gen/Cargo.toml
deleted file mode 100644
index db030cc86d..0000000000
--- a/examples/incrementer/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "incrementer" }
-ink_lang = { path = "../../../../lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/incrementer/.ink/abi_gen/main.rs b/examples/incrementer/.ink/abi_gen/main.rs
deleted file mode 100644
index 0083dae1fd..0000000000
--- a/examples/incrementer/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/incrementer/Cargo.toml b/examples/incrementer/Cargo.toml
index 58128a0bc6..dd3832ce91 100644
--- a/examples/incrementer/Cargo.toml
+++ b/examples/incrementer/Cargo.toml
@@ -16,12 +16,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "incrementer"
 path = "lib.rs"
-crate-type = [
-    # Used for normal contract Wasm blobs.
-    "cdylib",
-    # Used for ABI generation.
-    "rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -36,18 +31,3 @@ std = [
     "scale-info/std",
 ]
 ink-as-dependency = []
-
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
-[workspace]
-members = [
-    ".ink/abi_gen"
-]
-exclude = [
-    ".ink"
-]
diff --git a/examples/incrementer/lib.rs b/examples/incrementer/lib.rs
index c5cf0cfad9..cb9a48e737 100644
--- a/examples/incrementer/lib.rs
+++ b/examples/incrementer/lib.rs
@@ -14,7 +14,6 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use self::incrementer::Incrementer;
 use ink_lang as ink;
 
 #[ink::contract(version = "0.1.0")]
diff --git a/examples/multisig_plain/.ink/abi_gen/Cargo.toml b/examples/multisig_plain/.ink/abi_gen/Cargo.toml
deleted file mode 100755
index 07e62c4ed9..0000000000
--- a/examples/multisig_plain/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "multisig_plain" }
-ink_lang = { path = "../../../../lang", package = "ink_lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/multisig_plain/.ink/abi_gen/main.rs b/examples/multisig_plain/.ink/abi_gen/main.rs
deleted file mode 100755
index 0083dae1fd..0000000000
--- a/examples/multisig_plain/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/multisig_plain/Cargo.toml b/examples/multisig_plain/Cargo.toml
index 68866f186d..9ebe1f9968 100755
--- a/examples/multisig_plain/Cargo.toml
+++ b/examples/multisig_plain/Cargo.toml
@@ -17,12 +17,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "multisig_plain"
 path = "lib.rs"
-crate-type = [
-    # Used for normal contract Wasm blobs.
-    "cdylib",
-    # Used for ABI generation.
-    "rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -38,18 +33,3 @@ std = [
     "scale-info/std",
 ]
 ink-as-dependency = []
-
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
-[workspace]
-members = [
-    ".ink/abi_gen"
-]
-exclude = [
-    ".ink"
-]
diff --git a/examples/runtime-storage/.ink/abi_gen/Cargo.toml b/examples/runtime-storage/.ink/abi_gen/Cargo.toml
deleted file mode 100755
index dc0b3f67d8..0000000000
--- a/examples/runtime-storage/.ink/abi_gen/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "abi-gen"
-version = "0.1.0"
-authors = ["Parity Technologies <admin@parity.io>"]
-edition = "2018"
-publish = false
-
-[[bin]]
-name = "abi-gen"
-path = "main.rs"
-
-[dependencies]
-ink_abi = { path = "../../../../abi" }
-contract = { path = "../..", package = "runtime_storage" }
-ink_lang = { path = "../../../../lang" }
-serde = "1.0"
-serde_json = "1.0"
diff --git a/examples/runtime-storage/.ink/abi_gen/main.rs b/examples/runtime-storage/.ink/abi_gen/main.rs
deleted file mode 100755
index 0083dae1fd..0000000000
--- a/examples/runtime-storage/.ink/abi_gen/main.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-extern crate contract;
-
-extern "Rust" {
-    fn __ink_generate_metadata() -> ink_abi::InkProject;
-}
-
-fn main() -> Result<(), std::io::Error> {
-    let ink_project = unsafe { __ink_generate_metadata() };
-    let contents = serde_json::to_string_pretty(&ink_project)?;
-    std::fs::create_dir("target").ok();
-    std::fs::write("target/metadata.json", contents)?;
-    Ok(())
-}
diff --git a/examples/runtime-storage/Cargo.toml b/examples/runtime-storage/Cargo.toml
index bdb901e9a4..d37cfa83b8 100755
--- a/examples/runtime-storage/Cargo.toml
+++ b/examples/runtime-storage/Cargo.toml
@@ -17,12 +17,7 @@ scale-info = { version = "0.2", default-features = false, features = ["derive"],
 [lib]
 name = "runtime_storage"
 path = "lib.rs"
-crate-type = [
-    # Used for normal contract Wasm blobs.
-    "cdylib",
-    # Used for ABI generation.
-    "rlib",
-]
+crate-type = ["cdylib"]
 
 [features]
 default = ["std"]
@@ -37,18 +32,3 @@ std = [
     "scale-info/std",
 ]
 ink-as-dependency = []
-
-[profile.release]
-panic = "abort"
-lto = true
-opt-level = "z"
-overflow-checks = true
-codegen-units = 1
-
-[workspace]
-members = [
-    ".ink/abi_gen"
-]
-exclude = [
-    ".ink"
-]
diff --git a/examples/runtime-storage/lib.rs b/examples/runtime-storage/lib.rs
index fccdc16acf..faabe9b3ba 100755
--- a/examples/runtime-storage/lib.rs
+++ b/examples/runtime-storage/lib.rs
@@ -14,7 +14,6 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
-pub use self::runtime_storage::RuntimeStorage;
 use ink_lang as ink;
 
 #[ink::contract(version = "0.1.0")]


Test patch:
diff --git a/core/src/storage2/collections/stash/tests.rs b/core/src/storage2/collections/stash/tests.rs
index 2fb50bd286..61a2a90d80 100644
--- a/core/src/storage2/collections/stash/tests.rs
+++ b/core/src/storage2/collections/stash/tests.rs
@@ -106,6 +106,71 @@ fn take_out_of_bounds_works() {
     assert_eq!(stash.take(3), None);
 }
 
+#[test]
+fn remove_from_filled_works() {
+    let test_values = [b'A', b'B', b'C', b'D', b'E', b'F'];
+    let mut stash = test_values.iter().copied().collect::<StorageStash<_>>();
+
+    let mut count = stash.len();
+    for (index, val) in test_values.iter().enumerate() {
+        let index = index as u32;
+        assert_eq!(stash.get(index), Some(val));
+        assert_eq!(unsafe { stash.remove_occupied(index) }, Some(()));
+        assert_eq!(stash.get(index), None);
+        count -= 1;
+        assert_eq!(stash.len(), count);
+    }
+    assert_eq!(stash.len(), 0);
+}
+
+#[test]
+fn remove_from_empty_works() {
+    let mut stash = <StorageStash<u8>>::new();
+    assert_eq!(unsafe { stash.remove_occupied(0) }, None);
+}
+
+#[test]
+fn remove_out_of_bounds_works() {
+    let mut stash = [b'A', b'B', b'C']
+        .iter()
+        .copied()
+        .collect::<StorageStash<_>>();
+    assert_eq!(unsafe { stash.remove_occupied(3) }, None);
+}
+
+#[test]
+fn remove_works_with_spread_layout_push_pull() -> env::Result<()> {
+    env::test::run_test::<env::DefaultEnvTypes, _>(|_| {
+        // First populate some storage Stash and writes that to the contract storage using pull_spread
+        // and some known Key.
+        let stash = [b'A', b'B', b'C']
+            .iter()
+            .copied()
+            .collect::<StorageStash<_>>();
+        let root_key = Key::from([0x00; 32]);
+        SpreadLayout::push_spread(&stash, &mut KeyPtr::from(root_key));
+
+        // Then load another instance from the same key lazily and remove some of
+        // the known-to-be-populated entries from it. Afterwards push_spread this second instance and
+        // load yet another using pull_spread again.
+        let mut stash2 =
+            <StorageStash<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
+        assert_eq!(unsafe { stash2.remove_occupied(0) }, Some(()));
+        SpreadLayout::push_spread(&stash2, &mut KeyPtr::from(root_key));
+
+        // This time we check from the third instance using
+        // get if the expected cells are still there or have been successfully removed.
+        let stash3 =
+            <StorageStash<u8> as SpreadLayout>::pull_spread(&mut KeyPtr::from(root_key));
+        assert_eq!(stash3.get(0), None);
+        assert_eq!(stash3.get(1), Some(&b'B'));
+        assert_eq!(stash3.get(2), Some(&b'C'));
+        assert_eq!(stash3.len(), 2);
+
+        Ok(())
+    })
+}
+
 #[test]
 fn get_works() {
     let test_values = [b'A', b'B', b'C', b'D', b'E', b'F'];