Repository: paritytech/ink
Pull request #800 (2021-06-01 21:14:04)

ERC-1155 Example
This is getting big, so I think it's a good time to get some feedback and also get
some of my questions answered.

This PR aims to inkplement ERC-1155 as specified [here](https://eips.ethereum.org/EIPS/eip-1155).

It implements the "core" APIs, the ERC1155 Receivable interface (even though the just
implementation panics and rejects tokens, lol), and also has some additional
functionality such as a way to easily mint tokens.

Note that this contract implementation is missing some extensions which are mentioned in
the ERC-1155 specification document:
- Missing ERC1155 Metadata URI Interface
- No differentiation between fungible and non-fungible tokens
- Tokens are not officially burnable
- No batch minting

I won't be adding these here since a) this PR is already pretty big and b) they could
make for good first contributor issues.

I also think there are some improvements to be made in terms of how "Rusty" some
things are (having events return `None` vs. `0x00`), but I don't want to deviate too
far from the ERC-1155 spec.


HCastano: @Robbepop @cmichi can I get some eyes on this 👀
HCastano: I didn't get around to fixing all your comments today, will continue tomorrow

Patch:
diff --git a/.config/cargo_spellcheck.dic b/.config/cargo_spellcheck.dic
index bd384c808a..16f0852d6a 100644
--- a/.config/cargo_spellcheck.dic
+++ b/.config/cargo_spellcheck.dic
@@ -12,6 +12,7 @@ Polkadot
 RPC
 SHA
 UI
+URI
 Wasm
 Wasm32
 WebAssembly
@@ -26,6 +27,7 @@ cryptographic
 deallocate
 deallocation
 decodable
+decrement
 defrag
 defragmentation
 deploy
@@ -42,17 +44,26 @@ invariants
 kB
 layed
 multisig
+multi
 postfix
 prefilled
 recurse
+refcount
 scalability
 scalable
 stdin
 stdout
+tuple
+unordered
 untyped
 validator
 variadic
+metadata
+timestamp
+prepend
 
+Django/S
+IP/S
 NFT/S
 accessor/S
 allocator/S
@@ -67,6 +78,7 @@ hashmap/S
 instantiation/S
 layout/JG
 namespace/S
+parameterize/SD
 runtime/S
 struct/S
 vec/S
diff --git a/.config/cargo_spellcheck.toml b/.config/cargo_spellcheck.toml
index 58efcc5295..f5c0b7b3dc 100644
--- a/.config/cargo_spellcheck.toml
+++ b/.config/cargo_spellcheck.toml
@@ -3,6 +3,16 @@ lang = "en_US"
 search_dirs = ["."]
 extra_dictionaries = ["cargo_spellcheck.dic"]
 
+# If set to `true`, the OS specific default search paths are skipped and only explicitly
+# specified ones are used.
+skip_os_lookups = true
+
+# Use the builtin dictionaries if none were found in in the configured lookup paths.
+# Usually combined with `skip_os_lookups=true` to enforce the `builtin` usage for
+# consistent results across distributions and CI runs.  Setting this will still use the
+# dictionaries specified in `extra_dictionaries = [..]` for topic specific lingo.
+use_builtin = true
+
 [Hunspell.quirks]
 allow_concatenation = true
 allow_dashes = true
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ce75f1c9c7..6034ef4b3c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,6 +9,7 @@ stages:
   - check
   - workspace
   - examples
+  - ink-waterfall
   - fuzz
   - publish
 
@@ -153,12 +154,12 @@ spellcheck:
     stage:                           workspace
     <<:                              *docker-env
     script:
-        - cargo spellcheck check --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1
+        - cargo spellcheck check -vvv --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1
         - for example in examples/*/; do
-            cargo spellcheck check --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1 ${example};
+            cargo spellcheck check -vvv --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1 ${example};
           done
         - for contract in ${DELEGATOR_SUBCONTRACTS}; do
-            cargo spellcheck check --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1 examples/delegator/${contract}/;
+            cargo spellcheck check -vvv --cfg=.config/cargo_spellcheck.toml --checkers hunspell --code 1 examples/delegator/${contract}/;
           done
 
 codecov:
@@ -266,6 +267,7 @@ examples-test-experimental-engine:
     # We test only the examples for which the tests have already been migrated to
     # use the experimental engine.
     - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/erc20/Cargo.toml
+    - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/erc1155/Cargo.toml
     - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/contract-terminate/Cargo.toml
     - cargo test --no-default-features --features std, ink-experimental-engine --verbose --manifest-path examples/contract-transfer/Cargo.toml
 
@@ -345,6 +347,20 @@ examples-docs:
             cargo doc --manifest-path examples/delegator/${contract}/Cargo.toml --document-private-items --verbose --no-deps;
           done
 
+
+#### stage:                        ink-waterfall
+
+ink-waterfall:
+  stage:                           ink-waterfall
+  variables:
+    UPSTREAM_PR_ID:                $CI_COMMIT_REF_NAME
+    CI_IMAGE:                      "paritytech/ink-waterfall-ci:latest"
+  trigger:
+    project:                       parity/ink-waterfall
+    branch:                        master
+    strategy:                      depend
+
+
 #### stage:                        publish
 
 publish-docs:
diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs
index ed69f42835..3a9081db16 100644
--- a/crates/env/src/api.rs
+++ b/crates/env/src/api.rs
@@ -90,7 +90,7 @@ where
 /// # Errors
 ///
 /// If the returned value cannot be properly decoded.
-pub fn gas_left<T>() -> Result<T::Balance>
+pub fn gas_left<T>() -> Result<u64>
 where
     T: Environment,
 {
@@ -436,7 +436,8 @@ pub fn restore_contract<T>(
     })
 }
 
-/// Terminates the existence of the currently executed smart contract.
+/// Terminates the existence of the currently executed smart contract
+/// without creating a tombstone.
 ///
 /// This removes the calling account and transfers all remaining balance
 /// to the given beneficiary.
@@ -561,6 +562,15 @@ pub fn debug_message(message: &str) {
 }
 
 /// Conducts the crypto hash of the given input and stores the result in `output`.
+///
+/// # Example
+///
+/// ```
+/// use ink_env::hash::{Sha2x256, HashOutput};
+/// let input: &[u8] = &[13, 14, 15];
+/// let mut output = <Sha2x256 as HashOutput>::Type::default(); // 256-bit buffer
+/// let hash  = ink_env::hash_bytes::<Sha2x256>(input, &mut output);
+/// ```
 pub fn hash_bytes<H>(input: &[u8], output: &mut <H as HashOutput>::Type)
 where
     H: CryptoHash,
diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs
index 150b7a8504..f4525b977f 100644
--- a/crates/env/src/backend.rs
+++ b/crates/env/src/backend.rs
@@ -203,7 +203,7 @@ pub trait TypedEnvBackend: EnvBackend {
     /// # Note
     ///
     /// For more details visit: [`gas_left`][`crate::gas_left`]
-    fn gas_left<T: Environment>(&mut self) -> Result<T::Balance>;
+    fn gas_left<T: Environment>(&mut self) -> Result<u64>;
 
     /// Returns the timestamp of the current block.
     ///
diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs
index fc30223050..e3488755a3 100644
--- a/crates/env/src/call/call_builder.rs
+++ b/crates/env/src/call/call_builder.rs
@@ -302,6 +302,7 @@ mod seal {
     impl Sealed for () {}
     impl<T> Sealed for super::ReturnType<T> {}
 }
+
 /// Types that can be used in [`CallBuilder::returns`] to signal return type.
 pub trait IndicateReturnType: Default + self::seal::Sealed {}
 impl IndicateReturnType for () {}
diff --git a/crates/env/src/engine/experimental_off_chain/impls.rs b/crates/env/src/engine/experimental_off_chain/impls.rs
index 90f00e384f..171a630b9d 100644
--- a/crates/env/src/engine/experimental_off_chain/impls.rs
+++ b/crates/env/src/engine/experimental_off_chain/impls.rs
@@ -284,8 +284,8 @@ impl TypedEnvBackend for EnvInstance {
         self.get_property::<T::Balance>(Engine::value_transferred)
     }
 
-    fn gas_left<T: Environment>(&mut self) -> Result<T::Balance> {
-        self.get_property::<T::Balance>(Engine::gas_left)
+    fn gas_left<T: Environment>(&mut self) -> Result<u64> {
+        self.get_property::<u64>(Engine::gas_left)
     }
 
     fn block_timestamp<T: Environment>(&mut self) -> Result<T::Timestamp> {
diff --git a/crates/env/src/engine/off_chain/db/exec_context.rs b/crates/env/src/engine/off_chain/db/exec_context.rs
index 1d9a70ca56..ba1536dd52 100644
--- a/crates/env/src/engine/off_chain/db/exec_context.rs
+++ b/crates/env/src/engine/off_chain/db/exec_context.rs
@@ -37,7 +37,7 @@ pub struct ExecContext {
     /// The transferred value from caller to callee.
     pub transferred_value: OffBalance,
     /// The gas provided for the whole execution.
-    pub gas: OffBalance,
+    pub gas: u64,
     /// The inputs provided for the whole execution.
     ///
     /// # Note
@@ -82,11 +82,11 @@ impl ExecContext {
     }
 
     /// Returns the gas.
-    pub fn gas<T>(&self) -> Result<T::Balance>
+    pub fn gas<T>(&self) -> u64
     where
         T: Environment,
     {
-        self.gas.decode().map_err(Into::into)
+        self.gas
     }
 
     /// Returns the call data.
@@ -122,7 +122,7 @@ where
     /// The transferred value from caller to callee.
     transferred_value: Option<T::Balance>,
     /// The gas provided for the contract execution from caller to callee.
-    gas: Option<T::Balance>,
+    gas: Option<u64>,
     /// The inputs given to the contract execution.
     call_data: Option<CallData>,
 }
@@ -173,7 +173,7 @@ where
     /// # Panics
     ///
     /// If there has already been set provided gas.
-    pub fn gas(mut self, gas: T::Balance) -> Self {
+    pub fn gas(mut self, gas: u64) -> Self {
         if self.gas.is_some() {
             panic!("already has provided gas");
         }
@@ -223,7 +223,7 @@ where
             caller: TypedEncoded::new(&caller),
             callee: TypedEncoded::new(&callee),
             transferred_value: TypedEncoded::new(&transferred_value),
-            gas: TypedEncoded::new(&gas),
+            gas,
             call_data: self.call_data.unwrap(),
             output: None,
         }
diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs
index 17909e7059..00a260777d 100644
--- a/crates/env/src/engine/off_chain/impls.rs
+++ b/crates/env/src/engine/off_chain/impls.rs
@@ -316,12 +316,11 @@ impl TypedEnvBackend for EnvInstance {
             .saturating_mul(gas.try_into().unwrap_or_else(|_| Bounded::max_value())))
     }
 
-    fn gas_left<T: Environment>(&mut self) -> Result<T::Balance> {
-        self.exec_context()
+    fn gas_left<T: Environment>(&mut self) -> Result<u64> {
+        Ok(self
+            .exec_context()
             .expect(UNINITIALIZED_EXEC_CONTEXT)
-            .gas::<T>()
-            .map_err(|_| scale::Error::from("could not decode gas left"))
-            .map_err(Into::into)
+            .gas::<T>())
     }
 
     fn block_timestamp<T: Environment>(&mut self) -> Result<T::Timestamp> {
diff --git a/crates/env/src/engine/off_chain/mod.rs b/crates/env/src/engine/off_chain/mod.rs
index 8df55ddfbb..6ff31e65d8 100644
--- a/crates/env/src/engine/off_chain/mod.rs
+++ b/crates/env/src/engine/off_chain/mod.rs
@@ -205,7 +205,7 @@ impl EnvInstance {
             ExecContext::build::<T>()
                 .caller(default_accounts.alice)
                 .callee(contract_account_id)
-                .gas(T::Balance::from(500_000u32))
+                .gas(500_000u64)
                 .transferred_value(T::Balance::from(500u32))
                 .call_data(CallData::new(Selector::new(selector_bytes_for_call)))
                 .finish(),
diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs
index 6fd5809715..f5b801aa6f 100644
--- a/crates/env/src/engine/on_chain/impls.rs
+++ b/crates/env/src/engine/on_chain/impls.rs
@@ -309,8 +309,8 @@ impl TypedEnvBackend for EnvInstance {
         self.get_property::<T::Balance>(ext::value_transferred)
     }
 
-    fn gas_left<T: Environment>(&mut self) -> Result<T::Balance> {
-        self.get_property::<T::Balance>(ext::gas_left)
+    fn gas_left<T: Environment>(&mut self) -> Result<u64> {
+        self.get_property::<u64>(ext::gas_left)
     }
 
     fn block_timestamp<T: Environment>(&mut self) -> Result<T::Timestamp> {
diff --git a/crates/env/src/types.rs b/crates/env/src/types.rs
index 7b358ffd68..12fd6e53b5 100644
--- a/crates/env/src/types.rs
+++ b/crates/env/src/types.rs
@@ -77,7 +77,7 @@ pub trait Environment {
         + AsRef<[u8]>
         + AsMut<[u8]>;
 
-    /// The type of timestamps.
+    /// The type of a timestamp.
     type Timestamp: 'static
         + scale::Codec
         + Copy
diff --git a/crates/lang/Cargo.toml b/crates/lang/Cargo.toml
index 462057aeb7..97db22d95a 100644
--- a/crates/lang/Cargo.toml
+++ b/crates/lang/Cargo.toml
@@ -26,6 +26,10 @@ scale = { package = "parity-scale-codec", version = "2.1", default-features = fa
 derive_more = { version = "0.99", default-features = false, features = ["from"] }
 static_assertions = "1.1"
 
+[dev-dependencies]
+# required for the doctest of `env_access::EnvAccess::instantiate_contract`
+scale-info = { version = "0.6", default-features = false, features = ["derive"] }
+
 [features]
 default = ["std"]
 std = [
diff --git a/crates/lang/ir/src/ir/config.rs b/crates/lang/ir/src/ir/config.rs
index 52cd4d80cc..a6fdb6449c 100644
--- a/crates/lang/ir/src/ir/config.rs
+++ b/crates/lang/ir/src/ir/config.rs
@@ -48,12 +48,12 @@ where
 {
     format_err!(
         snd.span(),
-        "encountered duplicate ink! `{}` config argument",
+        "encountered duplicate ink! `{}` configuration argument",
         name,
     )
     .into_combine(format_err!(
         fst.span(),
-        "first `{}` config argument here",
+        "first `{}` configuration argument here",
         name
     ))
 }
@@ -170,7 +170,7 @@ impl Default for Environment {
 mod tests {
     use super::*;
 
-    /// Asserts that the given input config attribute argument are converted
+    /// Asserts that the given input configuration attribute argument are converted
     /// into the expected ink! configuration or yields the expected error message.
     fn assert_try_from(
         input: ast::AttributeArgs,
@@ -273,7 +273,7 @@ mod tests {
                 env = ::my::env::Types,
                 env = ::my::other::env::Types,
             },
-            Err("encountered duplicate ink! `env` config argument"),
+            Err("encountered duplicate ink! `env` configuration argument"),
         );
     }
 }
diff --git a/crates/lang/ir/src/ir/item_impl/callable.rs b/crates/lang/ir/src/ir/item_impl/callable.rs
index bd7d88522d..32f3fa2694 100644
--- a/crates/lang/ir/src/ir/item_impl/callable.rs
+++ b/crates/lang/ir/src/ir/item_impl/callable.rs
@@ -417,6 +417,19 @@ pub(super) fn ensure_callable_invariants(
             kind,
         ))
     }
+
+    if let Some(arg) = method_item.sig.inputs.iter().find(|input| {
+        match input {
+            syn::FnArg::Typed(pat) => !matches!(*pat.pat, syn::Pat::Ident(_)),
+            _ => false,
+        }
+    }) {
+        return Err(format_err_spanned!(
+            arg,
+            "ink! {} arguments must have an identifier",
+            kind
+        ))
+    }
     Ok(())
 }
 
diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs
index cff3ae743b..66924b9f20 100644
--- a/crates/lang/src/env_access.rs
+++ b/crates/lang/src/env_access.rs
@@ -112,6 +112,32 @@ where
 {
     /// Returns the address of the caller of the executed contract.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// #
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// #[ink(message)]
+    /// pub fn call_me(&self) {
+    ///     let caller = self.env().caller();
+    ///     ink_env::debug_println!("got a call from {:?}", &caller);
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::caller`]
@@ -121,6 +147,34 @@ where
 
     /// Returns the transferred balance for the contract execution.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// #
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Allows funding the contract. Prints a debug message with the transferred balance.
+    /// #[ink(message, payable)]
+    /// pub fn fund(&self) {
+    ///     let caller = self.env().caller();
+    ///     let value = self.env().transferred_balance();
+    ///     ink_env::debug_println!("thanks for the funding of {:?} from {:?}", value, caller);
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::transferred_balance`]
@@ -130,6 +184,40 @@ where
 
     /// Returns the price for the specified amount of gas.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// #
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Returns a tuple of
+    /// ///   - the result of adding the `rhs` to the `lhs`
+    /// ///   - the gas costs of this addition operation
+    /// ///   - the price for the gas
+    /// #[ink(message)]
+    /// pub fn addition_gas_cost(&self, rhs: i32, lhs: i32) -> (i32, u64, Balance) {
+    ///     let before = self.env().gas_left();
+    ///     let result = rhs + lhs;
+    ///     let after = self.env().gas_left();
+    ///     let gas_used = after - before;
+    ///     let gas_cost = self.env().weight_to_fee(gas_used);
+    ///     (result, gas_used, gas_cost)
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::weight_to_fee`]
@@ -139,15 +227,76 @@ where
 
     /// Returns the amount of gas left for the contract execution.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Returns a tuple of
+    /// ///   - the result of adding the `rhs` to the `lhs` and
+    /// ///   - the gas used for this addition operation.
+    /// #[ink(message)]
+    /// pub fn addition_gas_cost(&self, rhs: i32, lhs: i32) -> (i32, u64) {
+    ///     let before = self.env().gas_left();
+    ///     let result = rhs + lhs;
+    ///     let after = self.env().gas_left();
+    ///     (result, after - before)
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::gas_left`]
-    pub fn gas_left(self) -> T::Balance {
+    pub fn gas_left(self) -> u64 {
         ink_env::gas_left::<T>().expect("couldn't decode gas left")
     }
 
     /// Returns the timestamp of the current block.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// use ink_lang as ink;
+    ///
+    /// #[ink::contract]
+    /// pub mod my_contract {
+    ///     #[ink(storage)]
+    ///     pub struct MyContract {
+    ///         last_invocation: Timestamp
+    ///     }
+    ///
+    ///     impl MyContract {
+    ///         #[ink(constructor)]
+    ///         pub fn new() -> Self {
+    ///             Self {
+    ///                 last_invocation: Self::env().block_timestamp()
+    ///             }
+    ///         }
+    ///
+    ///         /// The function can be executed at most once every 100 blocks.
+    ///         #[ink(message)]
+    ///         pub fn execute_me(&mut self) {
+    ///             let now = self.env().block_timestamp();
+    ///             assert!(now - self.last_invocation > 100);
+    ///             self.last_invocation = now;
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::block_timestamp`]
@@ -157,6 +306,43 @@ where
 
     /// Returns the account ID of the executed contract.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// use ink_lang as ink;
+    ///
+    /// #[ink::contract]
+    /// pub mod only_owner {
+    ///     #[ink(storage)]
+    ///     pub struct OnlyOwner {
+    ///         owner: AccountId,
+    ///         value: u32,
+    ///     }
+    ///
+    ///     impl OnlyOwner {
+    ///         #[ink(constructor)]
+    ///         pub fn new() -> Self {
+    ///             Self {
+    ///                 owner: Self::env().caller(),
+    ///                 value: 0,
+    ///             }
+    ///         }
+    ///
+    ///         /// Allows incrementing the contract's `value` only
+    ///         /// for the owner (i.e. the account which instantiated
+    ///         /// this contract.
+    ///         ///
+    ///         /// The contract panics if the caller is not the owner.
+    ///         #[ink(message)]
+    ///         pub fn increment(&mut self) {
+    ///             let caller = self.env().caller();
+    ///             assert!(self.owner == caller);
+    ///             self.value = self.value + 1;
+    ///         }
+    ///     }
+    /// }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::account_id`]
@@ -166,6 +352,31 @@ where
 
     /// Returns the balance of the executed contract.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Returns the contract's balance.
+    /// #[ink(message)]
+    /// pub fn my_balance(&self) -> Balance {
+    ///     self.env().balance()
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::balance`]
@@ -175,6 +386,32 @@ where
 
     /// Returns the current rent allowance for the executed contract.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Returns the amount of the contract's balance which
+    /// /// can be used for paying rent.
+    /// #[ink(message)]
+    /// pub fn rent_allowance(&self) -> Balance {
+    ///     self.env().rent_allowance()
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::rent_allowance`]
@@ -182,8 +419,69 @@ where
         ink_env::rent_allowance::<T>().expect("couldn't decode contract rent allowance")
     }
 
+    /// Sets the rent allowance of the executed contract to the new value.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Limits the amount of contract balance which can be used for paying rent
+    /// /// to half of the contract's total balance.
+    /// #[ink(message)]
+    /// pub fn limit_rent_allowance(&self) {
+    ///     self.env().set_rent_allowance(self.env().balance() / 2);
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
+    /// # Note
+    ///
+    /// For more details visit: [`ink_env::set_rent_allowance`]
+    pub fn set_rent_allowance(self, new_value: T::Balance) {
+        ink_env::set_rent_allowance::<T>(new_value)
+    }
+
     /// Returns information needed for rent calculations.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Returns the balance every contract needs to deposit on this chain
+    /// /// to stay alive indefinitely.
+    /// #[ink(message)]
+    /// pub fn deposit_per_contract(&self) -> Balance {
+    ///     self.env().rent_params().deposit_per_contract
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::RentParams`]
@@ -214,6 +512,30 @@ where
 
     /// Returns the current block number.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// #[ink(message)]
+    /// pub fn block_number(&self) -> BlockNumber {
+    ///     self.env().block_number()
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::block_number`]
@@ -223,6 +545,30 @@ where
 
     /// Returns the minimum balance that is required for creating an account.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// #[ink(message)]
+    /// pub fn minimum_balance(&self) -> Balance {
+    ///     self.env().minimum_balance()
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::minimum_balance`]
@@ -232,6 +578,30 @@ where
 
     /// Returns the tombstone deposit for the contracts chain.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// #[ink(message)]
+    /// pub fn tombstone_deposit(&self) -> Balance {
+    ///     self.env().tombstone_deposit()
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::tombstone_deposit`]
@@ -239,16 +609,132 @@ where
         ink_env::tombstone_deposit::<T>().expect("couldn't decode tombstone deposits")
     }
 
-    /// Sets the rent allowance of the executed contract to the new value.
+    /// Instantiates another contract.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     use ink_lang as ink;
+    /// #     #[ink::contract(compile_as_dependency = true)]
+    /// #     pub mod other_contract {
+    /// #         #[ink(storage)]
+    /// #         pub struct OtherContract { }
+    /// #
+    /// #         impl OtherContract {
+    /// #             #[ink(constructor)]
+    /// #             pub fn new() -> Self {
+    /// #                 Self {}
+    /// #             }
+    /// #
+    /// #             #[ink(message)]
+    /// #             pub fn some_operation(&self) {
+    /// #                 // ...
+    /// #             }
+    /// #         }
+    /// #     }
+    /// #
+    /// use ink_env::{
+    ///     DefaultEnvironment,
+    ///     call::{build_create, Selector, ExecutionInput}
+    /// };
+    /// use other_contract::OtherContract;
+    /// #
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    ///
+    /// /// Instantiates another contract.
+    /// #[ink(message)]
+    /// pub fn instantiate_contract(&self) -> AccountId {
+    ///     let create_params = build_create::<DefaultEnvironment, OtherContract>()
+    ///         .code_hash(Hash::from([0x42; 32]))
+    ///         .gas_limit(4000)
+    ///         .endowment(25)
+    ///         .exec_input(
+    ///             ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE]))
+    ///                 .push_arg(42)
+    ///                 .push_arg(true)
+    ///                 .push_arg(&[0x10u8; 32])
+    ///             )
+    ///         .salt_bytes(&[0xCA, 0xFE, 0xBA, 0xBE])
+    ///         .params();
+    ///     self.env().instantiate_contract(&create_params).expect("instantiation must succeed")
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
+    /// See [our `delegator` example](https://github.com/paritytech/ink/tree/master/examples/delegator)
+    /// for a complete contract example.
     ///
     /// # Note
     ///
-    /// For more details visit: [`ink_env::set_rent_allowance`]
-    pub fn set_rent_allowance(self, new_value: T::Balance) {
-        ink_env::set_rent_allowance::<T>(new_value)
+    /// For more details visit: [`ink_env::instantiate_contract`]
+    pub fn instantiate_contract<Args, Salt, C>(
+        self,
+        params: &CreateParams<T, Args, Salt, C>,
+    ) -> Result<T::AccountId>
+    where
+        Args: scale::Encode,
+        Salt: AsRef<[u8]>,
+    {
+        ink_env::instantiate_contract::<T, Args, Salt, C>(params)
     }
 
-    /// Invokes a contract message.
+    /// Invokes a contract message without fetching its result.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// use ink_env::{
+    ///     DefaultEnvironment,
+    ///     call::{build_call, Selector, ExecutionInput}
+    /// };
+    ///
+    /// #
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Invokes another contract message without fetching the result.
+    /// #[ink(message)]
+    /// pub fn invoke_contract(&self) {
+    ///     let call_params = build_call::<DefaultEnvironment>()
+    ///         .callee(AccountId::from([0x42; 32]))
+    ///         .gas_limit(5000)
+    ///         .transferred_value(10)
+    ///         .exec_input(
+    ///             ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE]))
+    ///                 .push_arg(42)
+    ///                 .push_arg(true)
+    ///                 .push_arg(&[0x10u8; 32])
+    ///         )
+    ///         .returns::<()>()
+    ///         .params();
+    ///     self.env().invoke_contract(&call_params).expect("call invocation must succeed");
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
     ///
     /// # Note
     ///
@@ -262,6 +748,49 @@ where
 
     /// Evaluates a contract message and returns its result.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// use ink_env::{
+    ///     DefaultEnvironment,
+    ///     call::{build_call, Selector, ExecutionInput, utils::ReturnType}
+    /// };
+    ///
+    /// #
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Evaluates a contract message and fetches the result.
+    /// #[ink(message)]
+    /// pub fn evaluate_contract(&self) -> i32 {
+    ///     let call_params = build_call::<DefaultEnvironment>()
+    ///         .callee(AccountId::from([0x42; 32]))
+    ///         .gas_limit(5000)
+    ///         .transferred_value(10)
+    ///         .exec_input(
+    ///             ExecutionInput::new(Selector::new([0xCA, 0xFE, 0xBA, 0xBE]))
+    ///                 .push_arg(42)
+    ///                 .push_arg(true)
+    ///                 .push_arg(&[0x10u8; 32])
+    ///         )
+    ///         .returns::<ReturnType<i32>>()
+    ///         .params();
+    ///     self.env().eval_contract(&call_params).expect("call invocation must succeed")
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::eval_contract`]
@@ -276,23 +805,37 @@ where
         ink_env::eval_contract::<T, Args, R>(params)
     }
 
-    /// Instantiates another contract.
-    ///
-    /// # Note
-    ///
-    /// For more details visit: [`ink_env::instantiate_contract`]
-    pub fn instantiate_contract<Args, Salt, C>(
-        self,
-        params: &CreateParams<T, Args, Salt, C>,
-    ) -> Result<T::AccountId>
-    where
-        Args: scale::Encode,
-        Salt: AsRef<[u8]>,
-    {
-        ink_env::instantiate_contract::<T, Args, Salt, C>(params)
-    }
-
-    /// Restores a smart contract in tombstone state.
+    /// Restores a smart contract from its tombstone state.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Simple resurrection of a contract.
+    /// #[ink(message)]
+    /// pub fn resurrect(&self, contract: AccountId) {
+    ///     self.env().restore_contract(
+    ///         contract,
+    ///         Hash::from([0x42; 32]),
+    ///         1000,
+    ///         &[]
+    ///     )
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
     ///
     /// # Note
     ///
@@ -312,7 +855,32 @@ where
         )
     }
 
-    /// Terminates the existence of a smart contract.
+    /// Terminates the existence of a contract without creating a tombstone.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Terminates with the caller as beneficiary.
+    /// #[ink(message)]
+    /// pub fn terminate_me(&mut self) {
+    ///     self.env().terminate_contract(self.env().caller());
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
     ///
     /// # Note
     ///
@@ -323,6 +891,32 @@ where
 
     /// Transfers value from the contract to the destination account ID.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// /// Transfers the token amount ten to the caller.
+    /// #[ink(message)]
+    /// pub fn give_me_ten(&mut self) {
+    ///     let value: Balance = 10;
+    ///     self.env().transfer(self.env().caller(), value).expect("transfer failed");
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::transfer`]
@@ -332,6 +926,32 @@ where
 
     /// Returns a random hash seed.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// # use ink_lang as ink;
+    /// # #[ink::contract]
+    /// # pub mod my_contract {
+    /// #     #[ink(storage)]
+    /// #     pub struct MyContract { }
+    /// #
+    /// #     impl MyContract {
+    /// #         #[ink(constructor)]
+    /// #         pub fn new() -> Self {
+    /// #             Self {}
+    /// #         }
+    /// #
+    /// #[ink(message)]
+    /// pub fn random_bool(&self) -> bool {
+    ///     let additional_randomness = b"seed";
+    ///     let (hash, _block_number) = self.env().random(additional_randomness);
+    ///     hash.as_ref()[0] != 0
+    /// }
+    /// #
+    /// #     }
+    /// # }
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::random`]
@@ -341,6 +961,16 @@ where
 
     /// Computes the hash of the given bytes using the cryptographic hash `H`.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// use ink_env::hash::{Sha2x256, HashOutput};
+    ///
+    /// let input: &[u8] = &[13, 14, 15];
+    /// let mut output = <Sha2x256 as HashOutput>::Type::default(); // 256-bit buffer
+    /// let hash  = ink_env::hash_bytes::<Sha2x256>(input, &mut output);
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::hash_bytes`]
@@ -355,6 +985,22 @@ where
 
     /// Computes the hash of the given SCALE encoded value using the cryptographic hash `H`.
     ///
+    /// # Example
+    ///
+    /// ```
+    /// use ink_env::hash::{Sha2x256, HashOutput};
+    ///
+    /// let encodable = (42, "foo", true); // Implements `scale::Encode`
+    /// let mut output = <Sha2x256 as HashOutput>::Type::default(); // 256-bit buffer
+    /// ink_env::hash_encoded::<Sha2x256, _>(&encodable, &mut output);
+    ///
+    /// const EXPECTED: [u8; 32] = [
+    ///   243, 242, 58, 110, 205, 68, 100, 244, 187, 55, 188, 248,  29, 136, 145, 115,
+    ///   186, 134, 14, 175, 178, 99, 183,  21,   4, 94,  92,  69, 199, 207, 241, 179,
+    /// ];
+    /// assert_eq!(output, EXPECTED);
+    /// ```
+    ///
     /// # Note
     ///
     /// For more details visit: [`ink_env::hash_encoded`]
diff --git a/examples/dns/lib.rs b/examples/dns/lib.rs
index d3e6618a14..a59aa704e7 100644
--- a/examples/dns/lib.rs
+++ b/examples/dns/lib.rs
@@ -185,7 +185,7 @@ mod dns {
 
         const DEFAULT_CALLEE_HASH: [u8; 32] = [0x07; 32];
         const DEFAULT_ENDOWMENT: Balance = 1_000_000;
-        const DEFAULT_GAS_LIMIT: Balance = 1_000_000;
+        const DEFAULT_GAS_LIMIT: u64 = 1_000_000;
 
         fn default_accounts(
         ) -> ink_env::test::DefaultAccounts<ink_env::DefaultEnvironment> {
diff --git a/examples/erc1155/.gitignore b/examples/erc1155/.gitignore
new file mode 100644
index 0000000000..bf910de10a
--- /dev/null
+++ b/examples/erc1155/.gitignore
@@ -0,0 +1,9 @@
+# Ignore build artifacts from the local tests sub-crate.
+/target/
+
+# Ignore backup files creates by cargo fmt.
+**/*.rs.bk
+
+# Remove Cargo.lock when creating an executable, leave it for libraries
+# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+Cargo.lock
\ No newline at end of file
diff --git a/examples/erc1155/Cargo.toml b/examples/erc1155/Cargo.toml
new file mode 100644
index 0000000000..87a0a4abee
--- /dev/null
+++ b/examples/erc1155/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "erc1155"
+version = "3.0.0-rc3"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+
+[dependencies]
+ink_primitives = { version = "3.0.0-rc3", path = "../../crates/primitives", default-features = false }
+ink_metadata = { version = "3.0.0-rc3", path = "../../crates/metadata", default-features = false, features = ["derive"], optional = true }
+ink_env = { version = "3.0.0-rc3", path = "../../crates/env", default-features = false, features = ["ink-debug"] }
+ink_storage = { version = "3.0.0-rc3", path = "../../crates/storage", default-features = false }
+ink_lang = { version = "3.0.0-rc3", path = "../../crates/lang", default-features = false }
+ink_prelude = { version = "3.0.0-rc3", path = "../../crates/prelude", default-features = false }
+
+scale = { package = "parity-scale-codec", version = "2.1", default-features = false, features = ["derive"] }
+scale-info = { version = "0.6", default-features = false, features = ["derive"], optional = true }
+
+[lib]
+name = "erc1155"
+path = "lib.rs"
+crate-type = ["cdylib"]
+
+[features]
+default = ["std"]
+std = [
+    "ink_primitives/std",
+    "ink_metadata",
+    "ink_metadata/std",
+    "ink_env/std",
+    "ink_storage/std",
+    "ink_lang/std",
+    "ink_prelude/std",
+    "scale/std",
+    "scale-info",
+    "scale-info/std",
+]
+ink-as-dependency = []
+ink-experimental-engine = ["ink_env/ink-experimental-engine"]
diff --git a/examples/erc1155/lib.rs b/examples/erc1155/lib.rs
new file mode 100644
index 0000000000..7ced21add2
--- /dev/null
+++ b/examples/erc1155/lib.rs
@@ -0,0 +1,827 @@
+// 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.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use ink_env::AccountId;
+use ink_lang as ink;
+use ink_prelude::vec::Vec;
+
+// This is the return value that we expect if a smart contract supports receiving ERC-1155
+// tokens.
+//
+// It is calculated with
+// `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`, and corresponds
+// to 0xf23a6e61.
+#[cfg_attr(test, allow(dead_code))]
+const ON_ERC_1155_RECEIVED_SELECTOR: [u8; 4] = [0xF2, 0x3A, 0x6E, 0x61];
+
+// This is the return value that we expect if a smart contract supports batch receiving ERC-1155
+// tokens.
+//
+// It is calculated with
+// `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`, and
+// corresponds to 0xbc197c81.
+const _ON_ERC_1155_BATCH_RECEIVED_SELECTOR: [u8; 4] = [0xBC, 0x19, 0x7C, 0x81];
+
+/// A type representing the unique IDs of tokens managed by this contract.
+pub type TokenId = u128;
+
+type Balance = <ink_env::DefaultEnvironment as ink_env::Environment>::Balance;
+
+// The ERC-1155 error types.
+#[derive(Debug, PartialEq, scale::Encode, scale::Decode)]
+#[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+pub enum Error {
+    /// This token ID has not yet been created by the contract.
+    UnexistentToken,
+    /// The caller tried to sending tokens to the zero-address (`0x00`).
+    ZeroAddressTransfer,
+    /// The caller is not approved to transfer tokens on behalf of the account.
+    NotApproved,
+    /// The account does not have enough funds to complete the transfer.
+    InsufficientBalance,
+    /// An account does not need to approve themselves to transfer tokens.
+    SelfApproval,
+    /// The number of tokens being transferred does not match the specified number of transfers.
+    BatchTransferMismatch,
+}
+
+// The ERC-1155 result types.
+pub type Result<T> = core::result::Result<T, Error>;
+
+/// Evaluate `$x:expr` and if not true return `Err($y:expr)`.
+///
+/// Used as `ensure!(expression_to_ensure, expression_to_return_on_false)`.
+macro_rules! ensure {
+    ( $condition:expr, $error:expr $(,)? ) => {{
+        if !$condition {
+            return ::core::result::Result::Err(::core::convert::Into::into($error))
+        }
+    }};
+}
+
+/// The interface for an ERC-1155 compliant contract.
+///
+/// The interface is defined here: <https://eips.ethereum.org/EIPS/eip-1155>.
+///
+/// The goal of ERC-1155 is to allow a single deployed contract to manage a variety of assets.
+/// These assets can be fungible, non-fungible, or a combination.
+///
+/// By tracking multiple assets the ERC-1155 standard is able to support batch transfers, which
+/// make it easy to transfer a mix of multiple tokens at once.
+#[ink::trait_definition]
+pub trait Erc1155 {
+    /// Transfer a `value` amount of `token_id` tokens to the `to` account from the `from`
+    /// account.
+    ///
+    /// Note that the call does not have to originate from the `from` account, and may originate
+    /// from any account which is approved to transfer `from`'s tokens.
+    #[ink(message)]
+    fn safe_transfer_from(
+        &mut self,
+        from: AccountId,
+        to: AccountId,
+        token_id: TokenId,
+        value: Balance,
+        data: Vec<u8>,
+    ) -> Result<()>;
+
+    /// Perform a batch transfer of `token_ids` to the `to` account from the `from` account.
+    ///
+    /// The number of `values` specified to be transfer must match the number of `token_ids`,
+    /// otherwise this call will revert.
+    ///
+    /// Note that the call does not have to originate from the `from` account, and may originate
+    /// from any account which is approved to transfer `from`'s tokens.
+    #[ink(message)]
+    fn safe_batch_transfer_from(
+        &mut self,
+        from: AccountId,
+        to: AccountId,
+        token_ids: Vec<TokenId>,
+        values: Vec<Balance>,
+        data: Vec<u8>,
+    ) -> Result<()>;
+
+    /// Query the balance of a specific token for the provided account.
+    #[ink(message)]
+    fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance;
+
+    /// Query the balances for a set of tokens for a set of accounts.
+    ///
+    /// E.g use this call if you want to query what Alice and Bob's balances are for Tokens ID 1 and
+    /// ID 2.
+    ///
+    /// This will return all the balances for a given owner before moving on to the next owner. In
+    /// the example above this means that the return value should look like:
+    ///
+    /// [Alice Balance of Token ID 1, Alice Balance of Token ID 2, Bob Balance of Token ID 1, Bob Balance of Token ID 2]
+    #[ink(message)]
+    fn balance_of_batch(
+        &self,
+        owners: Vec<AccountId>,
+        token_ids: Vec<TokenId>,
+    ) -> Vec<Balance>;
+
+    /// Enable or disable a third party, known as an `operator`, to control all tokens on behalf of
+    /// the caller.
+    #[ink(message)]
+    fn set_approval_for_all(&mut self, operator: AccountId, approved: bool)
+        -> Result<()>;
+
+    /// Query if the given `operator` is allowed to control all of `owner`'s tokens.
+    #[ink(message)]
+    fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool;
+}
+
+/// The interface for an ERC-1155 Token Receiver contract.
+///
+/// The interface is defined here: <https://eips.ethereum.org/EIPS/eip-1155>.
+///
+/// Smart contracts which want to accept token transfers must implement this interface. By default
+/// if a contract does not support this interface any transactions originating from an ERC-1155
+/// compliant contract which attempt to transfer tokens directly to the contract's address must be
+/// reverted.
+#[ink::trait_definition]
+pub trait Erc1155TokenReceiver {
+    /// Handle the receipt of a single ERC-1155 token.
+    ///
+    /// This should be called by a compliant ERC-1155 contract if the intended recipient is a smart
+    /// contract.
+    ///
+    /// If the smart contract implementing this interface accepts token transfers then it must
+    /// return `ON_ERC_1155_RECEIVED_SELECTOR` from this function. To reject a transfer it must revert.
+    ///
+    /// Any callers must revert if they receive anything other than `ON_ERC_1155_RECEIVED_SELECTOR` as a return
+    /// value.
+    #[ink(message)]
+    fn on_received(
+        &mut self,
+        operator: AccountId,
+        from: AccountId,
+        token_id: TokenId,
+        value: Balance,
+        data: Vec<u8>,
+    ) -> Vec<u8>;
+
+    /// Handle the receipt of multiple ERC-1155 tokens.
+    ///
+    /// This should be called by a compliant ERC-1155 contract if the intended recipient is a smart
+    /// contract.
+    ///
+    /// If the smart contract implementing this interface accepts token transfers then it must
+    /// return `BATCH_ON_ERC_1155_RECEIVED_SELECTOR` from this function. To reject a transfer it must revert.
+    ///
+    /// Any callers must revert if they receive anything other than `BATCH_ON_ERC_1155_RECEIVED_SELECTOR` as a return
+    /// value.
+    #[ink(message)]
+    fn on_batch_received(
+        &mut self,
+        operator: AccountId,
+        from: AccountId,
+        token_ids: Vec<TokenId>,
+        values: Vec<Balance>,
+        data: Vec<u8>,
+    ) -> Vec<u8>;
+}
+
+#[ink::contract]
+mod erc1155 {
+    use super::*;
+
+    use ink_prelude::collections::BTreeMap;
+    use ink_storage::traits::{
+        PackedLayout,
+        SpreadLayout,
+    };
+
+    /// Indicate that a token transfer has occured.
+    ///
+    /// This must be emitted even if a zero value transfer occurs.
+    #[ink(event)]
+    pub struct TransferSingle {
+        #[ink(topic)]
+        operator: Option<AccountId>,
+        #[ink(topic)]
+        from: Option<AccountId>,
+        #[ink(topic)]
+        to: Option<AccountId>,
+        token_id: TokenId,
+        value: Balance,
+    }
+
+    /// Indicate that an approval event has happened.
+    #[ink(event)]
+    pub struct ApprovalForAll {
+        #[ink(topic)]
+        owner: AccountId,
+        #[ink(topic)]
+        operator: AccountId,
+        approved: bool,
+    }
+
+    /// Indicate that a token's URI has been updated.
+    #[ink(event)]
+    pub struct Uri {
+        value: ink_prelude::string::String,
+        #[ink(topic)]
+        token_id: TokenId,
+    }
+
+    /// Represents an (Owner, Operator) pair, in which the operator is allowed to spend funds on
+    /// behalf of the operator.
+    #[derive(
+        Copy,
+        Clone,
+        Debug,
+        Ord,
+        PartialOrd,
+        Eq,
+        PartialEq,
+        PackedLayout,
+        SpreadLayout,
+        scale::Encode,
+        scale::Decode,
+    )]
+    #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
+    struct Approval {
+        owner: AccountId,
+        operator: AccountId,
+    }
+
+    /// An ERC-1155 contract.
+    #[ink(storage)]
+    #[derive(Default)]
+    pub struct Contract {
+        /// Tracks the balances of accounts across the different tokens that they might be holding.
+        balances: BTreeMap<(AccountId, TokenId), Balance>,
+        /// Which accounts (called operators) have been approved to spend funds on behalf of an owner.
+        approvals: BTreeMap<Approval, ()>,
+        /// A unique identifier for the tokens which have been minted (and are therefore supported)
+        /// by this contract.
+        token_id_nonce: TokenId,
+    }
+
+    impl Contract {
+        /// Initialize a default instance of this ERC-1155 implementation.
+        #[ink(constructor)]
+        pub fn new() -> Self {
+            Default::default()
+        }
+
+        /// Create the initial supply for a token.
+        ///
+        /// The initial supply will be provided to the caller (a.k.a the minter), and the
+        /// `token_id` will be assigned by the smart contract.
+        ///
+        /// Note that as implemented anyone can create tokens. If you were to deploy this to a
+        /// production environment you'd probably want to lock down the addresses that are allowed
+        /// to create tokens.
+        #[ink(message)]
+        pub fn create(&mut self, value: Balance) -> TokenId {
+            let caller = self.env().caller();
+
+            // Given that TokenId is a `u128` the likelihood of this overflowing is pretty slim.
+            self.token_id_nonce += 1;
+            self.balances.insert((caller, self.token_id_nonce), value);
+
+            // Emit transfer event but with mint semantics
+            self.env().emit_event(TransferSingle {
+                operator: Some(caller),
+                from: None,
+                to: if value == 0 { None } else { Some(caller) },
+                token_id: self.token_id_nonce,
+                value,
+            });
+
+            self.token_id_nonce
+        }
+
+        /// Mint a `value` amount of `token_id` tokens.
+        ///
+        /// It is assumed that the token has already been `create`-ed. The newly minted supply will
+        /// be assigned to the caller (a.k.a the minter).
+        ///
+        /// Note that as implemented anyone can mint tokens. If you were to deploy this to a
+        /// production environment you'd probably want to lock down the addresses that are allowed
+        /// to mint tokens.
+        #[ink(message)]
+        pub fn mint(&mut self, token_id: TokenId, value: Balance) -> Result<()> {
+            ensure!(token_id <= self.token_id_nonce, Error::UnexistentToken);
+
+            let caller = self.env().caller();
+            self.balances.insert((caller, token_id), value);
+
+            // Emit transfer event but with mint semantics
+            self.env().emit_event(TransferSingle {
+                operator: Some(caller),
+                from: None,
+                to: Some(caller),
+                token_id,
+                value,
+            });
+
+            Ok(())
+        }
+
+        // Helper function for performing single token transfers.
+        //
+        // Should not be used directly since it's missing certain checks which are important to the
+        // ERC-1155 standard (it is expected that the caller has already performed these).
+        fn perform_transfer(
+            &mut self,
+            from: AccountId,
+            to: AccountId,
+            token_id: TokenId,
+            value: Balance,
+        ) {
+            self.balances
+                .entry((from, token_id))
+                .and_modify(|b| *b -= value);
+
+            self.balances
+                .entry((to, token_id))
+                .and_modify(|b| *b += value)
+                .or_insert(value);
+
+            let caller = self.env().caller();
+            self.env().emit_event(TransferSingle {
+                operator: Some(caller),
+                from: Some(from),
+                to: Some(from),
+                token_id,
+                value,
+            });
+        }
+
+        // Check if the address at `to` is a smart contract which accepts ERC-1155 token transfers.
+        //
+        // If they're a smart contract which **doesn't** accept tokens transfers this call will
+        // revert. Otherwise we risk locking user funds at in that contract with no chance of
+        // recovery.
+        #[cfg_attr(test, allow(unused_variables))]
+        fn transfer_acceptance_check(
+            &mut self,
+            caller: AccountId,
+            from: AccountId,
+            to: AccountId,
+            token_id: TokenId,
+            value: Balance,
+            data: Vec<u8>,
+        ) {
+            // This is disabled during tests due to the use of `eval_contract()` not being
+            // supported (tests end up panicking).
+            #[cfg(not(test))]
+            {
+                use ink_env::call::{
+                    build_call,
+                    utils::ReturnType,
+                    ExecutionInput,
+                    Selector,
+                };
+
+                // If our recipient is a smart contract we need to see if they accept or
+                // reject this transfer. If they reject it we need to revert the call.
+                let params = build_call::<Environment>()
+                    .callee(to)
+                    .gas_limit(5000)
+                    .exec_input(
+                        ExecutionInput::new(Selector::new(ON_ERC_1155_RECEIVED_SELECTOR))
+                            .push_arg(caller)
+                            .push_arg(from)
+                            .push_arg(token_id)
+                            .push_arg(value)
+                            .push_arg(data),
+                    )
+                    .returns::<ReturnType<Vec<u8>>>()
+                    .params();
+
+                match ink_env::eval_contract(&params) {
+                    Ok(v) => {
+                        ink_env::debug_println!(
+                            "Received return value \"{:?}\" from contract {:?}",
+                            v,
+                            from
+                        );
+                        assert_eq!(
+                            v,
+                            &ON_ERC_1155_RECEIVED_SELECTOR[..],
+                            "The recipient contract at {:?} does not accept token transfers.\n
+                            Expected: {:?}, Got {:?}", to, ON_ERC_1155_RECEIVED_SELECTOR, v
+                        )
+                    }
+                    Err(e) => {
+                        match e {
+                            ink_env::Error::CodeNotFound
+                            | ink_env::Error::NotCallable => {
+                                // Our recipient wasn't a smart contract, so there's nothing more for
+                                // us to do
+                                ink_env::debug_println!("Recipient at {:?} from is not a smart contract ({:?})", from, e);
+                            }
+                            _ => {
+                                // We got some sort of error from the call to our recipient smart
+                                // contract, and as such we must revert this call
+                                let msg = ink_prelude::format!(
+                                    "Got error \"{:?}\" while trying to call {:?}",
+                                    e,
+                                    from
+                                );
+                                ink_env::debug_println!("{}", &msg);
+                                panic!("{}", &msg)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    impl super::Erc1155 for Contract {
+        #[ink(message)]
+        fn safe_transfer_from(
+            &mut self,
+            from: AccountId,
+            to: AccountId,
+            token_id: TokenId,
+            value: Balance,
+            data: Vec<u8>,
+        ) -> Result<()> {
+            let caller = self.env().caller();
+            if caller != from {
+                ensure!(self.is_approved_for_all(from, caller), Error::NotApproved);
+            }
+
+            ensure!(to != AccountId::default(), Error::ZeroAddressTransfer);
+
+            let balance = self.balance_of(from, token_id);
+            ensure!(balance >= value, Error::InsufficientBalance);
+
+            self.perform_transfer(from, to, token_id, value);
+            self.transfer_acceptance_check(caller, from, to, token_id, value, data);
+
+            Ok(())
+        }
+
+        #[ink(message)]
+        fn safe_batch_transfer_from(
+            &mut self,
+            from: AccountId,
+            to: AccountId,
+            token_ids: Vec<TokenId>,
+            values: Vec<Balance>,
+            data: Vec<u8>,
+        ) -> Result<()> {
+            let caller = self.env().caller();
+            if caller != from {
+                ensure!(self.is_approved_for_all(from, caller), Error::NotApproved);
+            }
+
+            ensure!(to != AccountId::default(), Error::ZeroAddressTransfer);
+            ensure!(!token_ids.is_empty(), Error::BatchTransferMismatch);
+            ensure!(
+                token_ids.len() == values.len(),
+                Error::BatchTransferMismatch,
+            );
+
+            let transfers = token_ids.iter().zip(values.iter());
+            for (&id, &v) in transfers.clone() {
+                let balance = self.balance_of(from, id);
+                ensure!(balance >= v, Error::InsufficientBalance);
+            }
+
+            for (&id, &v) in transfers {
+                self.perform_transfer(from, to, id, v);
+            }
+
+            // Can use the any token ID/value here, we really just care about knowing if `to` is a
+            // smart contract which accepts transfers
+            self.transfer_acceptance_check(
+                caller,
+                from,
+                to,
+                token_ids[0],
+                values[0],
+                data,
+            );
+
+            Ok(())
+        }
+
+        #[ink(message)]
+        fn balance_of(&self, owner: AccountId, token_id: TokenId) -> Balance {
+            *self.balances.get(&(owner, token_id)).unwrap_or(&0)
+        }
+
+        #[ink(message)]
+        fn balance_of_batch(
+            &self,
+            owners: Vec<AccountId>,
+            token_ids: Vec<TokenId>,
+        ) -> Vec<Balance> {
+            let mut output = Vec::new();
+            for o in &owners {
+                for t in &token_ids {
+                    let amount = self.balance_of(*o, *t);
+                    output.push(amount);
+                }
+            }
+            output
+        }
+
+        #[ink(message)]
+        fn set_approval_for_all(
+            &mut self,
+            operator: AccountId,
+            approved: bool,
+        ) -> Result<()> {
+            let caller = self.env().caller();
+            ensure!(operator != caller, Error::SelfApproval);
+
+            let approval = Approval {
+                owner: caller,
+                operator,
+            };
+
+            if approved {
+                self.approvals.insert(approval, ());
+            } else {
+                self.approvals.remove(&approval);
+            }
+
+            self.env().emit_event(ApprovalForAll {
+                owner: approval.owner,
+                operator,
+                approved,
+            });
+
+            Ok(())
+        }
+
+        #[ink(message)]
+        fn is_approved_for_all(&self, owner: AccountId, operator: AccountId) -> bool {
+            self.approvals.get(&Approval { owner, operator }).is_some()
+        }
+    }
+
+    impl super::Erc1155TokenReceiver for Contract {
+        #[ink(message, selector = "0xF23A6E61")]
+        fn on_received(
+            &mut self,
+            _operator: AccountId,
+            _from: AccountId,
+            _token_id: TokenId,
+            _value: Balance,
+            _data: Vec<u8>,
+        ) -> Vec<u8> {
+            // The ERC-1155 standard dictates that if a contract does not accept token transfers
+            // directly to the contract, then the contract must revert.
+            //
+            // This prevents a user from unintentionally transferring tokens to a smart contract
+            // and getting their funds stuck without any sort of recovery mechanism.
+            //
+            // Note that the choice of whether or not to accept tokens is implementation specific,
+            // and we've decided to not accept them in this implementation.
+            unimplemented!("This smart contract does not accept token transfer.")
+        }
+
+        #[ink(message, selector = "0xBC197C81")]
+        fn on_batch_received(
+            &mut self,
+            _operator: AccountId,
+            _from: AccountId,
+            _token_ids: Vec<TokenId>,
+            _values: Vec<Balance>,
+            _data: Vec<u8>,
+        ) -> Vec<u8> {
+            // The ERC-1155 standard dictates that if a contract does not accept token transfers
+            // directly to the contract, then the contract must revert.
+            //
+            // This prevents a user from unintentionally transferring tokens to a smart contract
+            // and getting their funds stuck without any sort of recovery mechanism.
+            //
+            // Note that the choice of whether or not to accept tokens is implementation specific,
+            // and we've decided to not accept them in this implementation.
+            unimplemented!("This smart contract does not accept batch token transfers.")
+        }
+    }
+
+    #[cfg(test)]
+    mod tests {
+        /// Imports all the definitions from the outer scope so we can use them here.
+        use super::*;
+        use crate::Erc1155;
+
+        use ink_lang as ink;
+
+        #[cfg(feature = "ink-experimental-engine")]
+        fn set_sender(sender: AccountId) {
+            ink_env::test::set_caller::<Environment>(sender);
+        }
+
+        #[cfg(not(feature = "ink-experimental-engine"))]
+        fn set_sender(sender: AccountId) {
+            const WALLET: [u8; 32] = [7; 32];
+            ink_env::test::push_execution_context::<Environment>(
+                sender,
+                WALLET.into(),
+                1000000,
+                1000000,
+                ink_env::test::CallData::new(ink_env::call::Selector::new([0x00; 4])), /* dummy */
+            );
+        }
+
+        #[cfg(feature = "ink-experimental-engine")]
+        fn default_accounts() -> ink_env::test::DefaultAccounts<Environment> {
+            ink_env::test::default_accounts::<Environment>()
+        }
+
+        #[cfg(not(feature = "ink-experimental-engine"))]
+        fn default_accounts() -> ink_env::test::DefaultAccounts<Environment> {
+            ink_env::test::default_accounts::<Environment>()
+                .expect("off-chain environment should have been initialized already")
+        }
+
+        fn alice() -> AccountId {
+            default_accounts().alice
+        }
+
+        fn bob() -> AccountId {
+            default_accounts().bob
+        }
+
+        fn charlie() -> AccountId {
+            default_accounts().charlie
+        }
+
+        fn init_contract() -> Contract {
+            let mut erc = Contract::new();
+            erc.balances.insert((alice(), 1), 10);
+            erc.balances.insert((alice(), 2), 20);
+            erc.balances.insert((bob(), 1), 10);
+
+            erc
+        }
+
+        #[ink::test]
+        fn can_get_correct_balance_of() {
+            let erc = init_contract();
+
+            assert_eq!(erc.balance_of(alice(), 1), 10);
+            assert_eq!(erc.balance_of(alice(), 2), 20);
+            assert_eq!(erc.balance_of(alice(), 3), 0);
+            assert_eq!(erc.balance_of(bob(), 2), 0);
+        }
+
+        #[ink::test]
+        fn can_get_correct_batch_balance_of() {
+            let erc = init_contract();
+
+            assert_eq!(
+                erc.balance_of_batch(vec![alice()], vec![1, 2, 3]),
+                vec![10, 20, 0]
+            );
+            assert_eq!(
+                erc.balance_of_batch(vec![alice(), bob()], vec![1]),
+                vec![10, 10]
+            );
+
+            assert_eq!(
+                erc.balance_of_batch(vec![alice(), bob(), charlie()], vec![1, 2]),
+                vec![10, 20, 10, 0, 0, 0]
+            );
+        }
+
+        #[ink::test]
+        fn can_send_tokens_between_accounts() {
+            let mut erc = init_contract();
+
+            assert!(erc.safe_transfer_from(alice(), bob(), 1, 5, vec![]).is_ok());
+            assert_eq!(erc.balance_of(alice(), 1), 5);
+            assert_eq!(erc.balance_of(bob(), 1), 15);
+
+            assert!(erc.safe_transfer_from(alice(), bob(), 2, 5, vec![]).is_ok());
+            assert_eq!(erc.balance_of(alice(), 2), 15);
+            assert_eq!(erc.balance_of(bob(), 2), 5);
+        }
+
+        #[ink::test]
+        fn sending_too_many_tokens_fails() {
+            let mut erc = init_contract();
+            let res = erc.safe_transfer_from(alice(), bob(), 1, 99, vec![]);
+            assert_eq!(res.unwrap_err(), Error::InsufficientBalance);
+        }
+
+        #[ink::test]
+        fn sending_tokens_to_zero_address_fails() {
+            let burn: AccountId = [0; 32].into();
+
+            let mut erc = init_contract();
+            let res = erc.safe_transfer_from(alice(), burn, 1, 10, vec![]);
+            assert_eq!(res.unwrap_err(), Error::ZeroAddressTransfer);
+        }
+
+        #[ink::test]
+        fn can_send_batch_tokens() {
+            let mut erc = init_contract();
+            assert!(erc
+                .safe_batch_transfer_from(alice(), bob(), vec![1, 2], vec![5, 10], vec![])
+                .is_ok());
+
+            let balances = erc.balance_of_batch(vec![alice(), bob()], vec![1, 2]);
+            assert_eq!(balances, vec![5, 10, 15, 10])
+        }
+
+        #[ink::test]
+        fn rejects_batch_if_lengths_dont_match() {
+            let mut erc = init_contract();
+            let res = erc.safe_batch_transfer_from(
+                alice(),
+                bob(),
+                vec![1, 2, 3],
+                vec![5],
+                vec![],
+            );
+            assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch);
+        }
+
+        #[ink::test]
+        fn batch_transfers_fail_if_len_is_zero() {
+            let mut erc = init_contract();
+            let res =
+                erc.safe_batch_transfer_from(alice(), bob(), vec![], vec![], vec![]);
+            assert_eq!(res.unwrap_err(), Error::BatchTransferMismatch);
+        }
+
+        #[ink::test]
+        fn operator_can_send_tokens() {
+            let mut erc = init_contract();
+
+            let owner = alice();
+            let operator = bob();
+
+            set_sender(owner);
+            assert!(erc.set_approval_for_all(operator, true).is_ok());
+
+            set_sender(operator);
+            assert!(erc
+                .safe_transfer_from(owner, charlie(), 1, 5, vec![])
+                .is_ok());
+            assert_eq!(erc.balance_of(alice(), 1), 5);
+            assert_eq!(erc.balance_of(charlie(), 1), 5);
+        }
+
+        #[ink::test]
+        fn approvals_work() {
+            let mut erc = init_contract();
+            let owner = alice();
+            let operator = bob();
+            let another_operator = charlie();
+
+            // Note: All of these tests are from the context of the owner who is either allowing or
+            // disallowing an operator to control their funds.
+            set_sender(owner);
+            assert!(erc.is_approved_for_all(owner, operator) == false);
+
+            assert!(erc.set_approval_for_all(operator, true).is_ok());
+            assert!(erc.is_approved_for_all(owner, operator));
+
+            assert!(erc.set_approval_for_all(another_operator, true).is_ok());
+            assert!(erc.is_approved_for_all(owner, another_operator));
+
+            assert!(erc.set_approval_for_all(operator, false).is_ok());
+            assert!(erc.is_approved_for_all(owner, operator) == false);
+        }
+
+        #[ink::test]
+        fn minting_tokens_works() {
+            let mut erc = Contract::new();
+
+            set_sender(alice());
+            assert_eq!(erc.create(0), 1);
+            assert_eq!(erc.balance_of(alice(), 1), 0);
+
+            assert!(erc.mint(1, 123).is_ok());
+            assert_eq!(erc.balance_of(alice(), 1), 123);
+        }
+
+        #[ink::test]
+        fn minting_not_allowed_for_nonexistent_tokens() {
+            let mut erc = Contract::new();
+
+            let res = erc.mint(1, 123);
+            assert_eq!(res.unwrap_err(), Error::UnexistentToken);
+        }
+    }
+}


Test patch:
diff --git a/crates/env/src/engine/off_chain/test_api.rs b/crates/env/src/engine/off_chain/test_api.rs
index 0554e2e2b2..da2dce7215 100644
--- a/crates/env/src/engine/off_chain/test_api.rs
+++ b/crates/env/src/engine/off_chain/test_api.rs
@@ -44,7 +44,7 @@ use std::panic::UnwindSafe;
 pub fn push_execution_context<T>(
     caller: T::AccountId,
     callee: T::AccountId,
-    gas_limit: T::Balance,
+    gas_limit: u64,
     endowment: T::Balance,
     call_data: CallData,
 ) where
diff --git a/crates/lang/macro/tests/compile_tests.rs b/crates/lang/macro/tests/compile_tests.rs
index adf9ef1cc1..ed2d792c42 100644
--- a/crates/lang/macro/tests/compile_tests.rs
+++ b/crates/lang/macro/tests/compile_tests.rs
@@ -38,6 +38,7 @@ fn compile_tests() {
     t.compile_fail("tests/ui/fail/C-13-abi-constructor.rs");
     t.compile_fail("tests/ui/fail/C-14-payable-constructor.rs");
     t.compile_fail("tests/ui/fail/C-15-payable-trait-constructor.rs");
+    t.compile_fail("tests/ui/fail/C-16-function-arg-struct-destructuring.rs");
 
     t.compile_fail("tests/ui/fail/H-01-invalid-dyn-alloc.rs");
     t.compile_fail("tests/ui/fail/H-02-invalid-as-dependency.rs");
diff --git a/crates/lang/macro/tests/ui/fail/C-16-function-arg-struct-destructuring.rs b/crates/lang/macro/tests/ui/fail/C-16-function-arg-struct-destructuring.rs
new file mode 100644
index 0000000000..b811c1f226
--- /dev/null
+++ b/crates/lang/macro/tests/ui/fail/C-16-function-arg-struct-destructuring.rs
@@ -0,0 +1,24 @@
+use ink_lang as ink;
+
+#[ink::contract]
+mod noop {
+
+    struct Args {
+        foo: (),
+    }
+
+    #[ink(storage)]
+    pub struct Noop {}
+
+    impl Noop {
+        #[ink(constructor)]
+        pub fn new() -> Self {
+            Self {}
+        }
+
+        #[ink(message)]
+        pub fn noop(&self, Foo { foo }: Foo) {}
+    }
+}
+
+fn main() {}
diff --git a/crates/lang/macro/tests/ui/fail/C-16-function-arg-struct-destructuring.stderr b/crates/lang/macro/tests/ui/fail/C-16-function-arg-struct-destructuring.stderr
new file mode 100644
index 0000000000..33ada0340d
--- /dev/null
+++ b/crates/lang/macro/tests/ui/fail/C-16-function-arg-struct-destructuring.stderr
@@ -0,0 +1,5 @@
+error: ink! message arguments must have an identifier
+  --> $DIR/C-16-function-arg-struct-destructuring.rs:20:28
+   |
+20 |         pub fn noop(&self, Foo { foo }: Foo) {}
+   |                            ^^^^^^^^^^^^^^^^
diff --git a/crates/lang/macro/tests/ui/pass/08-static-env.rs b/crates/lang/macro/tests/ui/pass/08-static-env.rs
index dcf71a39f9..80e967db2d 100644
--- a/crates/lang/macro/tests/ui/pass/08-static-env.rs
+++ b/crates/lang/macro/tests/ui/pass/08-static-env.rs
@@ -13,7 +13,7 @@ mod static_env {
         }
 
         #[ink(message)]
-        pub fn gas_left(&mut self) -> Balance {
+        pub fn gas_left(&mut self) -> u64 {
             Self::env().gas_left()
         }
     }