Repository: paritytech/ink
Pull request #592 (2020-11-24 14:35:17)

Implement ink! chain extensions
Implements https://github.com/paritytech/ink/issues/585.

## ToDo List

- [x] Add unit tests for IR.
- [x] Add a UI test for the whole feature + new syntax.
- [x] Adjust `ink_env` definitions and remove crate feature.
- [x] Add IR implementation.
- [x] Add codegen implementation.
- [x] Add proc. macro definitions.
- [x] Create a design for handling and managing errors and error codes from chain extension methods.
- [x] Implement `#[ink::chain_extension]` code generation for the new design.
- [x] Write docs for usage and documenting details.
- [x] Rename `#[ink::contract]` `env_types` parameter to `env`.
- [x] Implement chain extension calling for the off-chain environment.
    - As the previous implementation the current off-chain environment implementation for calling and registering chain extension methods does not support access to the environment such as the runtime storage. This is a huge disadvantage and should be fixed as soon as possible in the rework of the off-chain environment.
- [x] Find solution for using `Self::ErrorCode` in chain extension method signatures. Ideally users should be able to use it.
    - Actually users do not really need to make use of it since return type of `Result<T, Self::ErrorCode>` is implcit for `expect_ok && !expect_output`.
- [x] Make chain extension codegen work for all configs:
    - [x] `expect_ok: false` and `expect_output: false`
    - [x] `expect_ok: false` and `expect_output: true`
    - [x] `expect_ok: true` and `expect_output: false`
    - [x] `expect_ok: true` and `expect_output: true`
- [x] Think about better names for `expect_output` and `expect_ok`.
    - Found better design with `handle_status: bool` and `returns_result: bool` attributes that default to `true`.

codecov-io: # [Codecov](https://codecov.io/gh/paritytech/ink/pull/592?src=pr&el=h1) Report
> Merging [#592](https://codecov.io/gh/paritytech/ink/pull/592?src=pr&el=desc) (b20e76e) into [master](https://codecov.io/gh/paritytech/ink/commit/dd890b8914a9b1d43b9d5e21c7a710cd4749fc4d?el=desc) (dd890b8) will **decrease** coverage by `1.11%`.
> The diff coverage is `40.46%`.

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

```diff
@@            Coverage Diff             @@
##           master     #592      +/-   ##
==========================================
- Coverage   82.36%   81.25%   -1.12%     
==========================================
  Files         155      157       +2     
  Lines        6914     7038     +124     
==========================================
+ Hits         5695     5719      +24     
- Misses       1219     1319     +100     
```


| [Impacted Files](https://codecov.io/gh/paritytech/ink/pull/592?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [crates/lang/macro/src/chain\_extension.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvbWFjcm8vc3JjL2NoYWluX2V4dGVuc2lvbi5ycw==) | `0.00% <0.00%> (ø)` | |
| [crates/lang/macro/src/lib.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvbWFjcm8vc3JjL2xpYi5ycw==) | `50.00% <0.00%> (-16.67%)` | :arrow_down: |
| [crates/lang/ir/src/ir/chain\_extension.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2NoYWluX2V4dGVuc2lvbi5ycw==) | `2.85% <2.85%> (ø)` | |
| [crates/lang/ir/src/ir/item/storage.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2l0ZW0vc3RvcmFnZS5ycw==) | `77.77% <50.00%> (ø)` | |
| [crates/lang/ir/src/ir/attrs.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2F0dHJzLnJz) | `80.30% <68.42%> (-5.28%)` | :arrow_down: |
| [crates/lang/ir/src/ir/item/event.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2l0ZW0vZXZlbnQucnM=) | `88.88% <80.00%> (-0.67%)` | :arrow_down: |
| [crates/lang/ir/src/ir/item/mod.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2l0ZW0vbW9kLnJz) | `60.97% <100.00%> (ø)` | |
| [crates/lang/ir/src/ir/item\_impl/constructor.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2l0ZW1faW1wbC9jb25zdHJ1Y3Rvci5ycw==) | `98.30% <100.00%> (ø)` | |
| [crates/lang/ir/src/ir/item\_impl/impl\_item.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2l0ZW1faW1wbC9pbXBsX2l0ZW0ucnM=) | `60.00% <100.00%> (ø)` | |
| [crates/lang/ir/src/ir/item\_impl/message.rs](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree#diff-Y3JhdGVzL2xhbmcvaXIvc3JjL2lyL2l0ZW1faW1wbC9tZXNzYWdlLnJz) | `96.25% <100.00%> (ø)` | |
| ... and [6 more](https://codecov.io/gh/paritytech/ink/pull/592/diff?src=pr&el=tree-more) | |

------

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

Robbepop: Design idea to solve problem with custom error handling and error management for chain extension methods:

| Must Yield | Always Ok | Return Type                      | Flags                   |
|:----------:|:---------:|:---------------------------------|:------------------------|
|      false |     false | `Result<Result<T, E>, Ext::Error>` |                         |
|      false |      true | `Result<T, Ext::Error>`            | `always_ok`              |
|       true |     false | `Result<T, E>`                     | `must_yield`             |
|       true |      true | `T`                                | `must_yield, always_ok` |

```rust
#[ink(extension = 1)]
fn might_not_yield_and_may_fail() -> Result<T, E>;
    // - enforced to return a Result type, works with type aliases.
    // - actually returns: Result<T, ExtError<Self::Error, E>>

#[ink(extension = 2, always_ok)]
fn might_not_yield_but_always_ok() -> T;
    // - enforced to not return a Result type, also not a type alias to one.
    // - actually returns: Result<T, Self::Error>

#[ink(extension = 3, must_yield)]
fn always_yields_but_may_fail() -> Result<T, E>;
    // - enforced to return a Result type, works with type aliases.
    // - asserts that chain extension call always yields return type.
    // - actually returns: Result<T, E>

#[ink(extension = 4, must_yield, always_ok)]
fn always_yields_and_always_ok() -> T;
    // - asserts that chain extension call always yields return type.
    // - actually returns: T
```
Robbepop: New design:

- `expect_output`: If this flag exists the `u32` status code of the chain extension
  method call is going to be ignored and assumed to be indicating a successful
  execution. The output buffer in this case is always going to be queried and decoded
  into the method's output type.
  If the flag does not exist the returned `u32` status code is queried and
  `<Self::ErrorCode as ink_lang::FromStatusCode>::from_status_code` is called with it.
  The call to `from_status_code` returns `Result<(), Self::ErrorCode>`. In case of
  `Ok(())` the output buffer is queried and decoded as described above. In case of
  `Err(Self::ErrorCode)` the `Self::ErrorCode` is converted into `E` where `E` is the `E`
  in `Result<T, E>` if the chain extension method returns a `Result` type itself.
  In case the chain extension method does not return a `Result` type the call returns with
  `Result<T, Self::ErrorCode>` where `T` is the chain extension method's return type. 
- `expect_ok`: If this flag exists the proc. macro no longer tries to enforce that
  the returned type encoded into the output buffer of the chain extension method call
  is of type `Result<T, E>`. Also `E` is no longer required to implement `From<Self::ErrorCode>`
  in case `expect_output` flag does not exist.

```rust
#[ink::chain_extension]
pub trait MyChainExtension {
    type ErrorCode = MyErrorCode;

    /// The proc. macro enforces that the method returns a `Result` type.
    /// The type `E` in `Result<T, E>` is required to implement: `From<Self::ErrorCode>`.
    /// Returns type `Result<T, E>`.
    #[ink(extension = 1)]
    fn method_1() -> Result<T, E>;

    /// Returns type `Result<T, Self::ErrorCode>`
    #[ink(extension = 2, expect_ok)]
    fn method_2() -> T;

    /// Returns type `Result<(), Self::ErrorCode>`
    #[ink(extension = 2, expect_ok)]
    fn method_3();

    /// Ignores the `u32` return value of the chain extension method call.
    ///     - Implicitely assumes it to be a successful call.
    ///     - Expects an output of type `Result<T, E>` in the output buffer of the call.
    /// Returns type `Result<T, E>`
    #[ink(extension = 3, expect_output)]
    fn method_4() -> Result<T, E>;

    /// Ignores the `u32` return value of the chain extension method call.
    ///     - Implicitely assumes it to be a successful call.
    ///     - Expects an output of type `T` in the output buffer of the call.
    /// Returns type `T`
    #[ink(extension = 4, expect_output, always_ok)]
    fn method_5() -> T;

    /// Ignores the `u32` return value of the chain extension method call.
    ///     - Implicitely assumes it to be a successful call.
    ///     - Expects the output buffer of the call to be empty.
    /// Returns type `()`
    #[ink(extension = 4, expect_output, always_ok)]
    fn method_6();
}
```

Where `FromStatusCode` is defined as follows:

```rust
/// Implemented by error codes in order to construct them from status codes.
///
/// A status code is returned by calling an ink! chain extension method.
/// It is the `u32` return value.
///
/// The purpose of an `ErrorCode` type that implements this trait is to provide
/// more context information about the status of an ink! chain extension method call.
pub trait FromStatusCode: Sized {
    /// Returns `Ok` if the status code for the called chain extension method is valid.
    ///
    /// Returning `Ok` will query the output buffer of the call if the chain extension
    /// method definition has a return value.
    ///
    /// # Note
    ///
    /// The convention is to use `0` as the only `raw` value that yields `Ok` whereas
    /// every other value represents one error code. By convention this mapping should
    /// never panic and therefore every `raw` value must map to either `Ok` or to a proper
    /// `ErrorCode` variant.
    fn from_status_code(status_code: u32) -> Result<(), Self>;
}
```
athei: The new design is much better. I like how it does not hardcode return codes anymore.
athei: Fixes #558
Robbepop: View https://github.com/paritytech/ink/blob/robin-impl-chain-extension/crates/lang/macro/tests/ui/chain_extension/simple.rs for a test case covering all of the 4 previously described cases.
Robbepop: We concluded to try out how a new proposed syntax would look like instead of going with `expect_output` and `expect_ok`:
```
#[ink(
    extension = 1,
    handle_status = "true|false",
    returns_result = "true|false"
)]
fn my_extension_method();
```
Where `handle_status` and `returns_result` both default to `"true"`.

Patch:
diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml
index 3a7d4fdff7..04b54a7b4f 100644
--- a/crates/env/Cargo.toml
+++ b/crates/env/Cargo.toml
@@ -60,4 +60,3 @@ std = [
     "sha3",
     "blake2",
 ]
-ink-unstable-chain-extensions = []
diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs
index 91fc0bc30f..9216001e39 100644
--- a/crates/env/src/api.rs
+++ b/crates/env/src/api.rs
@@ -436,27 +436,6 @@ where
     })
 }
 
-/// Calls the chain extension with the given ID and inputs.
-///
-/// Returns the given output type.
-///
-/// # Errors
-///
-/// - If the given function ID does not exist in the runtime.
-/// - If the given inputs cannot be properly decoded by the runtime.
-/// - If the given output type cannot be properly decoded by the contract.
-/// - If some chain extension specific conditions are not met.
-#[cfg(feature = "ink-unstable-chain-extensions")]
-pub fn call_chain_extension<I, O>(func_id: u32, input: &I) -> Result<O>
-where
-    I: scale::Codec + 'static,
-    O: scale::Codec + 'static,
-{
-    <EnvInstance as OnInstance>::on_instance(|instance| {
-        EnvBackend::call_chain_extension(instance, func_id, input)
-    })
-}
-
 /// Returns the execution input to the executed contract and decodes it as `T`.
 ///
 /// # Note
diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs
index 9aa2aa6031..dabd9e6c7b 100644
--- a/crates/env/src/backend.rs
+++ b/crates/env/src/backend.rs
@@ -128,7 +128,7 @@ pub trait EnvBackend {
         H: CryptoHash,
         T: scale::Encode;
 
-    /// Calls the chain extension with the given ID and inputs.
+    /// Low-level interface to call a chain extension method.
     ///
     /// Returns the output of the chain extension of the specified type.
     ///
@@ -138,11 +138,29 @@ pub trait EnvBackend {
     /// - If the inputs had an unexpected encoding.
     /// - If the output could not be properly decoded.
     /// - If some extension specific condition has not been met.
-    #[cfg(feature = "ink-unstable-chain-extensions")]
-    fn call_chain_extension<I, O>(&mut self, func_id: u32, input: &I) -> Result<O>
+    ///
+    /// # Dev. Note
+    ///
+    /// A valid implementation applies the `status_to_result` closure on
+    /// the status code returned by the actual call to the chain extension
+    /// method.
+    /// Only if the closure finds that the given status code indicates a
+    /// successful call to the chain extension method is the resulting
+    /// output buffer passed to the `decode_to_result` closure, in order to
+    /// drive the decoding and error management process from the outside.
+    fn call_chain_extension<I, T, E, ErrorCode, F, D>(
+        &mut self,
+        func_id: u32,
+        input: &I,
+        status_to_result: F,
+        decode_to_result: D,
+    ) -> ::core::result::Result<T, E>
     where
-        I: scale::Codec + 'static,
-        O: scale::Codec + 'static;
+        I: scale::Encode,
+        T: scale::Decode,
+        E: From<ErrorCode>,
+        F: FnOnce(u32) -> ::core::result::Result<(), ErrorCode>,
+        D: FnOnce(&[u8]) -> ::core::result::Result<T, E>;
 }
 
 /// Environmental contract functionality.
diff --git a/crates/env/src/call/call_builder.rs b/crates/env/src/call/call_builder.rs
index 261a4f4104..02670f41f1 100644
--- a/crates/env/src/call/call_builder.rs
+++ b/crates/env/src/call/call_builder.rs
@@ -206,7 +206,7 @@ where
     E: Environment,
 {
     CallBuilder {
-        env_types: Default::default(),
+        env: Default::default(),
         callee: Default::default(),
         gas_limit: Default::default(),
         transferred_value: Default::default(),
@@ -220,7 +220,7 @@ pub struct CallBuilder<E, Callee, GasLimit, TransferredValue, Args, RetType>
 where
     E: Environment,
 {
-    env_types: PhantomData<fn() -> E>,
+    env: PhantomData<fn() -> E>,
     /// The current parameters that have been built up so far.
     callee: Callee,
     gas_limit: GasLimit,
@@ -242,7 +242,7 @@ where
     ) -> CallBuilder<E, Set<E::AccountId>, GasLimit, TransferredValue, Args, RetType>
     {
         CallBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             callee: Set(callee),
             gas_limit: self.gas_limit,
             transferred_value: self.transferred_value,
@@ -264,7 +264,7 @@ where
         gas_limit: u64,
     ) -> CallBuilder<E, Callee, Set<u64>, TransferredValue, Args, RetType> {
         CallBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             callee: self.callee,
             gas_limit: Set(gas_limit),
             transferred_value: self.transferred_value,
@@ -286,7 +286,7 @@ where
         transferred_value: E::Balance,
     ) -> CallBuilder<E, Callee, GasLimit, Set<E::Balance>, Args, RetType> {
         CallBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             callee: self.callee,
             gas_limit: self.gas_limit,
             transferred_value: Set(transferred_value),
@@ -327,7 +327,7 @@ where
         R: IndicateReturnType,
     {
         CallBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             callee: self.callee,
             gas_limit: self.gas_limit,
             transferred_value: self.transferred_value,
@@ -362,7 +362,7 @@ where
         RetType,
     > {
         CallBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             callee: self.callee,
             gas_limit: self.gas_limit,
             transferred_value: self.transferred_value,
diff --git a/crates/env/src/call/create_builder.rs b/crates/env/src/call/create_builder.rs
index 054fc7ce21..455709a9bd 100644
--- a/crates/env/src/call/create_builder.rs
+++ b/crates/env/src/call/create_builder.rs
@@ -113,7 +113,7 @@ pub struct CreateBuilder<E, CodeHash, GasLimit, Endowment, Args, R>
 where
     E: Environment,
 {
-    env_types: PhantomData<fn() -> E>,
+    env: PhantomData<fn() -> E>,
     code_hash: CodeHash,
     gas_limit: GasLimit,
     endowment: Endowment,
@@ -181,7 +181,7 @@ where
     R: FromAccountId<E>,
 {
     CreateBuilder {
-        env_types: Default::default(),
+        env: Default::default(),
         code_hash: Default::default(),
         gas_limit: Default::default(),
         endowment: Default::default(),
@@ -202,7 +202,7 @@ where
         code_hash: E::Hash,
     ) -> CreateBuilder<E, Set<E::Hash>, GasLimit, Endowment, Args, R> {
         CreateBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             code_hash: Set(code_hash),
             gas_limit: self.gas_limit,
             endowment: self.endowment,
@@ -224,7 +224,7 @@ where
         gas_limit: u64,
     ) -> CreateBuilder<E, CodeHash, Set<u64>, Endowment, Args, R> {
         CreateBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             code_hash: self.code_hash,
             gas_limit: Set(gas_limit),
             endowment: self.endowment,
@@ -246,7 +246,7 @@ where
         endowment: E::Balance,
     ) -> CreateBuilder<E, CodeHash, GasLimit, Set<E::Balance>, Args, R> {
         CreateBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             code_hash: self.code_hash,
             gas_limit: self.gas_limit,
             endowment: Set(endowment),
@@ -276,7 +276,7 @@ where
     ) -> CreateBuilder<E, CodeHash, GasLimit, Endowment, Set<ExecutionInput<Args>>, R>
     {
         CreateBuilder {
-            env_types: Default::default(),
+            env: Default::default(),
             code_hash: self.code_hash,
             gas_limit: self.gas_limit,
             endowment: self.endowment,
diff --git a/crates/env/src/chain_extension.rs b/crates/env/src/chain_extension.rs
new file mode 100644
index 0000000000..b0d8b36bc0
--- /dev/null
+++ b/crates/env/src/chain_extension.rs
@@ -0,0 +1,440 @@
+// 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.
+
+//! Definitions and utilities for calling chain extension methods.
+//!
+//! Users should not use these types and definitions directly but rather use the provided
+//! `#[ink::chain_extension]` procedural macro defined in the `ink_lang` crate.
+
+use crate::{
+    backend::EnvBackend,
+    engine::{
+        EnvInstance,
+        OnInstance,
+    },
+};
+use core::marker::PhantomData;
+
+/// Implemented by error codes in order to construct them from status codes.
+///
+/// A status code is returned by calling an ink! chain extension method.
+/// It is the `u32` return value.
+///
+/// The purpose of an `ErrorCode` type that implements this trait is to provide
+/// more context information about the status of an ink! chain extension method call.
+pub trait FromStatusCode: Sized {
+    /// Returns `Ok` if the status code for the called chain extension method is valid.
+    ///
+    /// Returning `Ok` will query the output buffer of the call if the chain extension
+    /// method definition has a return value.
+    ///
+    /// # Note
+    ///
+    /// The convention is to use `0` as the only `raw` value that yields `Ok` whereas
+    /// every other value represents one error code. By convention this mapping should
+    /// never panic and therefore every `raw` value must map to either `Ok` or to a proper
+    /// `ErrorCode` variant.
+    fn from_status_code(status_code: u32) -> Result<(), Self>;
+}
+
+/// A concrete instance of a chain extension method.
+///
+/// This is a utility type used to drive the execution of a chain extension method call.
+/// It has several different specializations of its `call` method for different ways to
+/// manage error handling when calling a predefined chain extension method.
+///
+/// - `I` represents the input type of the chain extension method.
+///   All tuple types that may act as input parameters for the chain extension method are valid.
+///   Examples include `()`, `i32`, `(u8, [u8; 5], i32)`, etc.
+/// - `O` represents the return (or output) type of the chain extension method.
+///   Only `Result<T, E>` or `NoResult<O>` generic types are allowed for `O`.
+///   The `Result<T, E>` type says that the chain extension method returns a `Result` type
+///   whereas the `NoResult<O>` type says that the chain extension method returns a non-`Result` value
+///   of type `O`.
+/// - `ErrorCode` represents how the chain extension method handles the chain extension's error code.
+///   Only `HandleErrorCode<E>` and `IgnoreErrorCode` types are allowed that each say to either properly
+///   handle or ignore the chain extension's error code respectively.
+///
+/// The type states for type parameter `O` and `ErrorCode` represent 4 different states:
+///
+/// 1. The chain extension method makes use of the chain extension's error code: `HandleErrorCode(E)`
+///     - **A:** The chain extension method returns a `Result<T, E>` type.
+///     - **B:** The chain extension method returns a type `T` that is not a `Result` type: `NoResult<T>`
+/// 2. The chain extension ignores the chain extension's error code: `IgnoreErrorCode`
+///     - **A:** The chain extension method returns a `Result<T, E>` type.
+///     - **B:** The chain extension method returns a type `T` that is not a `Result` type: `NoResult<T>`
+#[derive(Debug)]
+pub struct ChainExtensionMethod<I, O, ErrorCode> {
+    func_id: u32,
+    #[allow(clippy::type_complexity)]
+    state: PhantomData<fn() -> (I, O, ErrorCode)>,
+}
+
+impl ChainExtensionMethod<(), (), ()> {
+    /// Creates a new chain extension method instance.
+    #[inline(always)]
+    pub fn build(func_id: u32) -> Self {
+        Self {
+            func_id,
+            state: Default::default(),
+        }
+    }
+}
+
+impl<O, ErrorCode> ChainExtensionMethod<(), O, ErrorCode> {
+    /// Sets the input types of the chain extension method call to `I`.
+    ///
+    /// # Note
+    ///
+    /// `I` represents the input type of the chain extension method.
+    /// All tuple types that may act as input parameters for the chain extension method are valid.
+    /// Examples include `()`, `i32`, `(u8, [u8; 5], i32)`, etc.
+    #[inline(always)]
+    pub fn input<I>(self) -> ChainExtensionMethod<I, O, ErrorCode>
+    where
+        I: scale::Encode,
+    {
+        ChainExtensionMethod {
+            func_id: self.func_id,
+            state: Default::default(),
+        }
+    }
+}
+
+impl<I, ErrorCode> ChainExtensionMethod<I, (), ErrorCode> {
+    /// Sets the output type of the chain extension method call to `Result<T, E>`.
+    ///
+    /// # Note
+    ///
+    /// This indicates that the chain extension method return value might represent a failure.
+    #[inline(always)]
+    pub fn output_result<T, E>(self) -> ChainExtensionMethod<I, Result<T, E>, ErrorCode>
+    where
+        Result<T, E>: scale::Decode,
+        E: From<scale::Error>,
+    {
+        ChainExtensionMethod {
+            func_id: self.func_id,
+            state: Default::default(),
+        }
+    }
+
+    /// Sets the output type of the chain extension method call to `O`.
+    ///
+    /// # Note
+    ///
+    /// The set returned type `O` must not be of type `Result<T, E>`.
+    /// When using the `#[ink::chain_extension]` procedural macro to define
+    /// this chain extension method the above constraint is enforced at
+    /// compile time.
+    #[inline(always)]
+    pub fn output<O>(self) -> ChainExtensionMethod<I, state::NoResult<O>, ErrorCode>
+    where
+        O: scale::Decode,
+    {
+        ChainExtensionMethod {
+            func_id: self.func_id,
+            state: Default::default(),
+        }
+    }
+}
+
+impl<I, O> ChainExtensionMethod<I, O, ()> {
+    /// Makes the chain extension method call assume that the returned status code is always success.
+    ///
+    /// # Note
+    ///
+    /// This will avoid handling of failure status codes returned by the chain extension method call.
+    /// Use this only if you are sure that the chain extension method call will never return an error
+    /// code that represents failure.
+    ///
+    /// The output of the chain extension method call is always decoded and returned in this case.
+    #[inline(always)]
+    pub fn ignore_error_code(self) -> ChainExtensionMethod<I, O, state::IgnoreErrorCode> {
+        ChainExtensionMethod {
+            func_id: self.func_id,
+            state: Default::default(),
+        }
+    }
+
+    /// Makes the chain exntesion method call handle the returned status code.
+    ///
+    /// # Note
+    ///
+    /// This will handle the returned status code and only loads and decodes the value
+    /// returned in the output of the chain extension method call in case of success.
+    #[inline(always)]
+    pub fn handle_error_code<ErrorCode>(
+        self,
+    ) -> ChainExtensionMethod<I, O, state::HandleErrorCode<ErrorCode>>
+    where
+        ErrorCode: FromStatusCode,
+    {
+        ChainExtensionMethod {
+            func_id: self.func_id,
+            state: Default::default(),
+        }
+    }
+}
+
+/// Type states of the chain extension method instance.
+pub mod state {
+    use core::marker::PhantomData;
+
+    /// Type state meaning that the chain extension method ignores the chain extension's error code.
+    #[derive(Debug)]
+    pub enum IgnoreErrorCode {}
+
+    /// Type state meaning that the chain extension method uses the chain extension's error code.
+    #[derive(Debug)]
+    pub struct HandleErrorCode<T> {
+        error_code: PhantomData<fn() -> T>,
+    }
+
+    /// Type state meaning that the chain extension method deliberately does not return a `Result` type.
+    ///
+    /// Additionally this is enforced by the `#[ink::chain_extension]` proc. macro when used.
+    #[derive(Debug)]
+    pub struct NoResult<T> {
+        no_result: PhantomData<fn() -> T>,
+    }
+}
+
+impl<I, T, E, ErrorCode>
+    ChainExtensionMethod<I, Result<T, E>, state::HandleErrorCode<ErrorCode>>
+where
+    I: scale::Encode,
+    T: scale::Decode,
+    E: scale::Decode + From<ErrorCode> + From<scale::Error>,
+    ErrorCode: FromStatusCode,
+{
+    /// Calls the chain extension method for case 1A described [here].
+    ///
+    /// [here]: [`ChainExtensionMethodInstance`]
+    ///
+    /// # Errors
+    ///
+    /// - If the called chain extension method returns a non-successful error code.
+    /// - If the `Result` return value of the called chain extension represents an error.
+    /// - If the `Result` return value cannot be SCALE decoded properly.
+    /// - If custom constraints specified by the called chain extension method are violated.
+    ///     - These constraints are determined and defined by the author of the chain extension method.
+    ///
+    /// # Example
+    ///
+    /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32`
+    /// as input parameters and returns a `Result<i32, MyError>` upon completion.
+    /// It will handle the shared error code from the chain extension.
+    /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input
+    /// parameter respectively.
+    ///
+    /// ```should_panic
+    /// # // Panics because the off-chain environment has not
+    /// # // registered a chain extension method for the ID.
+    /// # use ink_env::chain_extension::{ChainExtensionMethod, FromStatusCode};
+    /// let result = ChainExtensionMethod::build(5)
+    ///     .input::<(bool, i32)>()
+    ///     .output_result::<i32, MyError>()
+    ///     .handle_error_code::<MyErrorCode>()
+    ///     .call(&(true, 42));
+    /// # #[derive(scale::Encode, scale::Decode)]
+    /// # pub struct MyError {}
+    /// # impl From<scale::Error> for MyError {
+    /// #     fn from(_error: scale::Error) -> Self { Self {} }
+    /// # }
+    /// # impl From<MyErrorCode> for MyError {
+    /// #     fn from(_error: MyErrorCode) -> Self { Self {} }
+    /// # }
+    /// # pub struct MyErrorCode {}
+    /// # impl FromStatusCode for MyErrorCode {
+    /// #     fn from_status_code(status_code: u32) -> Result<(), Self> { Ok(()) }
+    /// # }
+    /// ```
+    #[inline(always)]
+    pub fn call(self, input: &I) -> Result<T, E> {
+        <EnvInstance as OnInstance>::on_instance(|instance| {
+            EnvBackend::call_chain_extension::<I, T, E, ErrorCode, _, _>(
+                instance,
+                self.func_id,
+                input,
+                ErrorCode::from_status_code,
+                |output| scale::Decode::decode(&mut &output[..]).map_err(Into::into),
+            )
+        })
+    }
+}
+
+impl<I, T, E> ChainExtensionMethod<I, Result<T, E>, state::IgnoreErrorCode>
+where
+    I: scale::Encode,
+    T: scale::Decode,
+    E: scale::Decode + From<scale::Error>,
+{
+    /// Calls the chain extension method for case 2A described [here].
+    ///
+    /// [here]: [`ChainExtensionMethodInstance`]
+    ///
+    /// # Errors
+    ///
+    /// - If the `Result` return value of the called chain extension represents an error.
+    /// - If the `Result` return value cannot be SCALE decoded properly.
+    /// - If custom constraints specified by the called chain extension method are violated.
+    ///     - These constraints are determined and defined by the author of the chain extension method.
+    ///
+    /// # Example
+    ///
+    /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32`
+    /// as input parameters and returns a `Result<i32, MyError>` upon completion.
+    /// It will ignore the shared error code from the chain extension and assumes that the call succeeds.
+    /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input
+    /// parameter respectively.
+    ///
+    /// ```should_panic
+    /// # // Panics because the off-chain environment has not
+    /// # // registered a chain extension method for the ID.
+    /// # use ink_env::chain_extension::{ChainExtensionMethod};
+    /// let result = ChainExtensionMethod::build(5)
+    ///     .input::<(bool, i32)>()
+    ///     .output_result::<i32, MyError>()
+    ///     .ignore_error_code()
+    ///     .call(&(true, 42));
+    /// # #[derive(scale::Encode, scale::Decode)]
+    /// # pub struct MyError {}
+    /// # impl From<scale::Error> for MyError {
+    /// #     fn from(_error: scale::Error) -> Self { Self {} }
+    /// # }
+    /// ```
+    #[inline(always)]
+    pub fn call(self, input: &I) -> Result<T, E> {
+        <EnvInstance as OnInstance>::on_instance(|instance| {
+            EnvBackend::call_chain_extension::<I, T, E, E, _, _>(
+                instance,
+                self.func_id,
+                input,
+                |_status_code| Ok(()),
+                |output| scale::Decode::decode(&mut &output[..]).map_err(Into::into),
+            )
+        })
+    }
+}
+
+impl<I, O, ErrorCode>
+    ChainExtensionMethod<I, state::NoResult<O>, state::HandleErrorCode<ErrorCode>>
+where
+    I: scale::Encode,
+    O: scale::Decode,
+    ErrorCode: FromStatusCode,
+{
+    /// Calls the chain extension method for case 1B described [here].
+    ///
+    /// [here]: [`ChainExtensionMethodInstance`]
+    ///
+    /// # Errors
+    ///
+    /// - If the called chain extension method returns a non-successful error code.
+    /// - If custom constraints specified by the called chain extension method are violated.
+    ///     - These constraints are determined and defined by the author of the chain extension method.
+    ///
+    /// # Panics
+    ///
+    /// - If the return value cannot be SCALE decoded properly.
+    ///
+    /// # Example
+    ///
+    /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32`
+    /// as input parameters and returns a `Result<i32, MyErrorCode>` upon completion.
+    /// It will handle the shared error code from the chain extension.
+    /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input
+    /// parameter respectively.
+    ///
+    /// ```should_panic
+    /// # // Panics because the off-chain environment has not
+    /// # // registered a chain extension method for the ID.
+    /// # use ink_env::chain_extension::{ChainExtensionMethod, FromStatusCode};
+    /// let result = ChainExtensionMethod::build(5)
+    ///     .input::<(bool, i32)>()
+    ///     .output::<i32>()
+    ///     .handle_error_code::<MyErrorCode>()
+    ///     .call(&(true, 42));
+    /// # pub struct MyErrorCode {}
+    /// # impl FromStatusCode for MyErrorCode {
+    /// #     fn from_status_code(status_code: u32) -> Result<(), Self> { Ok(()) }
+    /// # }
+    /// ```
+    #[inline(always)]
+    pub fn call(self, input: &I) -> Result<O, ErrorCode> {
+        <EnvInstance as OnInstance>::on_instance(|instance| {
+            EnvBackend::call_chain_extension::<I, O, ErrorCode, ErrorCode, _, _>(
+                instance,
+                self.func_id,
+                input,
+                ErrorCode::from_status_code,
+                |output| {
+                    let decoded = <O as scale::Decode>::decode(&mut &output[..])
+                        .expect("encountered error while decoding chain extension method call return value");
+                    Ok(decoded)
+                },
+            )
+        })
+    }
+}
+
+impl<I, O> ChainExtensionMethod<I, state::NoResult<O>, state::IgnoreErrorCode>
+where
+    I: scale::Encode,
+    O: scale::Decode,
+{
+    /// Calls the chain extension method for case 2B described [here].
+    ///
+    /// [here]: [`ChainExtensionMethodInstance`]
+    ///
+    /// # Panics
+    ///
+    /// - If the return value cannot be SCALE decoded properly.
+    ///
+    /// # Example
+    ///
+    /// Declares a chain extension method with the unique ID of 5 that requires a `bool` and an `i32`
+    /// as input parameters and returns a `Result<i32, MyErrorCode>` upon completion.
+    /// It will ignore the shared error code from the chain extension and assumes that the call succeeds.
+    /// The call is finally invoked with arguments `true` and `42` for the `bool` and `i32` input
+    /// parameter respectively.
+    ///
+    /// ```should_panic
+    /// # // Panics because the off-chain environment has not
+    /// # // registered a chain extension method for the ID.
+    /// # use ink_env::chain_extension::ChainExtensionMethod;
+    /// let result = ChainExtensionMethod::build(5)
+    ///     .input::<(bool, i32)>()
+    ///     .output::<i32>()
+    ///     .ignore_error_code()
+    ///     .call(&(true, 42));
+    /// ```
+    #[inline(always)]
+    pub fn call(self, input: &I) -> O {
+        <EnvInstance as OnInstance>::on_instance(|instance| {
+            EnvBackend::call_chain_extension::<I, O, (), (), _, _>(
+                instance,
+                self.func_id,
+                input,
+                |_status_code| Ok(()),
+                |output| {
+                    let decoded = <O as scale::Decode>::decode(&mut &output[..])
+                        .expect("encountered error while decoding chain extension method call return value");
+                    Ok(decoded)
+                },
+            ).expect("assume the chain extension method never fails")
+        })
+    }
+}
diff --git a/crates/env/src/engine/off_chain/chain_extension.rs b/crates/env/src/engine/off_chain/chain_extension.rs
index 89e842e426..ecec775a12 100644
--- a/crates/env/src/engine/off_chain/chain_extension.rs
+++ b/crates/env/src/engine/off_chain/chain_extension.rs
@@ -14,23 +14,32 @@
 
 use super::OffChainError;
 use crate::Result;
-use std::collections::HashMap;
+use derive_more::From;
+use std::collections::{
+    hash_map::Entry,
+    HashMap,
+};
 
-type FuncId = u32;
+/// Chain extension registry.
+///
+/// Allows to register chain extension methods and call them.
+pub struct ChainExtensionHandler {
+    /// The currently registered runtime call handler.
+    registered: HashMap<ExtensionId, Box<dyn ChainExtension>>,
+    /// The output buffer used and reused for chain extension method call results.
+    output: Vec<u8>,
+}
+
+/// The unique ID of the registered chain extension method.
+#[derive(
+    Debug, From, scale::Encode, scale::Decode, PartialEq, Eq, PartialOrd, Ord, Hash,
+)]
+pub struct ExtensionId(u32);
 
 /// Types implementing this trait can be used as chain extensions.
 ///
 /// This trait is only useful for testing contract via the off-chain environment.
 pub trait ChainExtension {
-    /// The expected input type.
-    ///
-    /// # Note
-    ///
-    /// This can be a tuple to expect multiple input types.
-    type Input: scale::Codec;
-    /// The expected output type.
-    type Output: scale::Codec;
-
     /// The static function ID of the chain extension.
     ///
     /// # Note
@@ -39,22 +48,9 @@ pub trait ChainExtension {
     fn func_id(&self) -> u32;
 
     /// Calls the chain extension with the given input.
-    fn call(&mut self, input: &Self::Input) -> Result<Self::Output>;
-}
-
-/// A raw chain extension function.
-///
-/// This is mostly a wrapper closure around the real chain extension function
-/// that handles marshalling of types between their encoded and decoded
-/// representations.
-type ChainExtensionFn = Box<dyn FnMut(Vec<u8>) -> Result<Vec<u8>>>;
-
-/// Runtime call handler.
-///
-/// More generically a mapping from bytes to bytes.
-pub struct ChainExtensionHandler {
-    /// The currently registered runtime call handler.
-    registered: HashMap<FuncId, ChainExtensionFn>,
+    ///
+    /// Returns an error code and may fill the `output` buffer with a SCALE encoded result.
+    fn call(&mut self, input: &[u8], output: &mut Vec<u8>) -> u32;
 }
 
 impl ChainExtensionHandler {
@@ -64,47 +60,33 @@ impl ChainExtensionHandler {
     pub fn new() -> Self {
         Self {
             registered: HashMap::new(),
+            output: Vec::new(),
         }
     }
 
     /// Resets the chain extension handler to uninitialized state.
     pub fn reset(&mut self) {
-        self.registered.clear()
+        self.registered.clear();
+        self.output.clear();
     }
 
     /// Register a new chain extension.
-    pub fn register<I, O>(
-        &mut self,
-        mut extension: Box<dyn ChainExtension<Input = I, Output = O>>,
-    ) where
-        I: scale::Codec + 'static,
-        O: scale::Codec + 'static,
-    {
+    pub fn register(&mut self, extension: Box<dyn ChainExtension>) {
         let func_id = extension.func_id();
-        self.registered.insert(
-            func_id,
-            Box::new(move |encoded_input: Vec<u8>| {
-                let decoded_input = scale::Decode::decode(&mut &encoded_input[..])?;
-                let decoded_output = extension.call(&decoded_input)?;
-                Ok(scale::Encode::encode(&decoded_output))
-            }),
-        );
+        self.registered
+            .insert(ExtensionId::from(func_id), extension);
     }
 
     /// Evaluates the chain extension with the given parameters.
     ///
     /// Upon success returns the values returned by the evaluated chain extension.
-    pub fn eval<I, O>(&mut self, func_id: FuncId, input: &I) -> Result<O>
-    where
-        I: scale::Codec + 'static,
-        O: scale::Codec + 'static,
-    {
-        use std::collections::hash_map::Entry;
-        match self.registered.entry(func_id) {
-            Entry::Occupied(mut occupied) => {
-                let encoded_input = scale::Encode::encode(input);
-                let encoded_output = occupied.get_mut()(encoded_input)?;
-                scale::Decode::decode(&mut &encoded_output[..]).map_err(Into::into)
+    pub fn eval(&mut self, func_id: u32, input: &[u8]) -> Result<(u32, &[u8])> {
+        self.output.clear();
+        let extension_id = ExtensionId::from(func_id);
+        match self.registered.entry(extension_id) {
+            Entry::Occupied(occupied) => {
+                let status_code = occupied.into_mut().call(input, &mut self.output);
+                Ok((status_code, &mut self.output))
             }
             Entry::Vacant(_vacant) => {
                 Err(OffChainError::UnregisteredChainExtension.into())
diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs
index a1ffa34969..d971de478b 100644
--- a/crates/env/src/engine/off_chain/impls.rs
+++ b/crates/env/src/engine/off_chain/impls.rs
@@ -190,13 +190,28 @@ impl EnvBackend for EnvInstance {
         self.hash_bytes::<H>(&encoded[..], output)
     }
 
-    #[cfg(feature = "ink-unstable-chain-extensions")]
-    fn call_chain_extension<I, O>(&mut self, func_id: u32, input: &I) -> Result<O>
+    fn call_chain_extension<I, T, E, ErrorCode, F, D>(
+        &mut self,
+        func_id: u32,
+        input: &I,
+        status_to_result: F,
+        decode_to_result: D,
+    ) -> ::core::result::Result<T, E>
     where
-        I: scale::Codec + 'static,
-        O: scale::Codec + 'static,
+        I: scale::Encode,
+        T: scale::Decode,
+        E: From<ErrorCode>,
+        F: FnOnce(u32) -> ::core::result::Result<(), ErrorCode>,
+        D: FnOnce(&[u8]) -> ::core::result::Result<T, E>,
     {
-        self.chain_extension_handler.eval(func_id, input)
+        let encoded_input = input.encode();
+        let (status_code, output) = self
+            .chain_extension_handler
+            .eval(func_id, &encoded_input)
+            .expect("encountered unexpected missing chain extension method");
+        status_to_result(status_code)?;
+        let decoded = decode_to_result(&mut &output[..])?;
+        Ok(decoded)
     }
 }
 
diff --git a/crates/env/src/engine/off_chain/mod.rs b/crates/env/src/engine/off_chain/mod.rs
index 7ab8a34ce1..59cafeadba 100644
--- a/crates/env/src/engine/off_chain/mod.rs
+++ b/crates/env/src/engine/off_chain/mod.rs
@@ -13,7 +13,6 @@
 // limitations under the License.
 
 mod call_data;
-#[cfg(feature = "ink-unstable-chain-extensions")]
 mod chain_extension;
 mod db;
 mod hashing;
@@ -25,8 +24,6 @@ mod types;
 #[cfg(test)]
 mod tests;
 
-#[cfg(feature = "ink-unstable-chain-extensions")]
-use self::chain_extension::ChainExtensionHandler;
 pub use self::{
     call_data::CallData,
     db::{
@@ -37,6 +34,7 @@ pub use self::{
     typed_encoded::TypedEncodedError,
 };
 use self::{
+    chain_extension::ChainExtensionHandler,
     db::{
         Account,
         AccountsDb,
@@ -89,7 +87,6 @@ pub struct EnvInstance {
     /// The console to print debug contents.
     console: Console,
     /// Handler for registered chain extensions.
-    #[cfg(feature = "ink-unstable-chain-extensions")]
     chain_extension_handler: ChainExtensionHandler,
     /// Emitted events recorder.
     emitted_events: EmittedEventsRecorder,
@@ -106,7 +103,6 @@ impl EnvInstance {
             chain_spec: ChainSpec::uninitialized(),
             blocks: Vec::new(),
             console: Console::new(),
-            #[cfg(feature = "ink-unstable-chain-extensions")]
             chain_extension_handler: ChainExtensionHandler::new(),
             emitted_events: EmittedEventsRecorder::new(),
             clear_storage_disabled: false,
@@ -138,7 +134,6 @@ impl EnvInstance {
         self.chain_spec.reset();
         self.blocks.clear();
         self.console.reset();
-        #[cfg(feature = "ink-unstable-chain-extensions")]
         self.chain_extension_handler.reset();
         self.emitted_events.reset();
         self.clear_storage_disabled = false;
diff --git a/crates/env/src/engine/on_chain/ext.rs b/crates/env/src/engine/on_chain/ext.rs
index ae10d7092c..7ca131eadb 100644
--- a/crates/env/src/engine/on_chain/ext.rs
+++ b/crates/env/src/engine/on_chain/ext.rs
@@ -179,6 +179,13 @@ where
 #[repr(transparent)]
 pub struct ReturnCode(u32);
 
+impl ReturnCode {
+    /// Returns the raw underlying `u32` representation.
+    pub fn into_u32(self) -> u32 {
+        self.0
+    }
+}
+
 type Result = core::result::Result<(), Error>;
 
 mod sys {
@@ -255,7 +262,6 @@ mod sys {
         );
         pub fn seal_terminate(beneficiary_ptr: Ptr32<[u8]>, beneficiary_len: u32) -> !;
 
-        #[cfg(feature = "ink-unstable-chain-extensions")]
         pub fn seal_call_chain_extension(
             func_id: u32,
             input_ptr: Ptr32<[u8]>,
@@ -485,12 +491,7 @@ pub fn terminate(beneficiary: &[u8]) -> ! {
     }
 }
 
-#[cfg(feature = "ink-unstable-chain-extensions")]
-pub fn call_chain_extension(
-    func_id: u32,
-    input: &[u8],
-    output: &mut &mut [u8],
-) -> Result {
+pub fn call_chain_extension(func_id: u32, input: &[u8], output: &mut &mut [u8]) -> u32 {
     let mut output_len = output.len() as u32;
     let ret_code = {
         unsafe {
@@ -504,7 +505,7 @@ pub fn call_chain_extension(
         }
     };
     extract_from_slice(output, output_len as usize);
-    ret_code.into()
+    ret_code.into_u32()
 }
 
 pub fn input(output: &mut &mut [u8]) {
diff --git a/crates/env/src/engine/on_chain/impls.rs b/crates/env/src/engine/on_chain/impls.rs
index 8abb158ca0..cbc20428d8 100644
--- a/crates/env/src/engine/on_chain/impls.rs
+++ b/crates/env/src/engine/on_chain/impls.rs
@@ -272,17 +272,26 @@ impl EnvBackend for EnvInstance {
         <H as CryptoHash>::hash(enc_input, output)
     }
 
-    #[cfg(feature = "ink-unstable-chain-extensions")]
-    fn call_chain_extension<I, O>(&mut self, func_id: u32, input: &I) -> Result<O>
+    fn call_chain_extension<I, T, E, ErrorCode, F, D>(
+        &mut self,
+        func_id: u32,
+        input: &I,
+        status_to_result: F,
+        decode_to_result: D,
+    ) -> ::core::result::Result<T, E>
     where
         I: scale::Encode,
-        O: scale::Decode,
+        T: scale::Decode,
+        E: From<ErrorCode>,
+        F: FnOnce(u32) -> ::core::result::Result<(), ErrorCode>,
+        D: FnOnce(&[u8]) -> ::core::result::Result<T, E>,
     {
         let mut scope = self.scoped_buffer();
         let enc_input = scope.take_encoded(input);
         let output = &mut scope.take_rest();
-        ext::call_chain_extension(func_id, enc_input, output)?;
-        scale::Decode::decode(&mut &output[..]).map_err(Into::into)
+        status_to_result(ext::call_chain_extension(func_id, enc_input, output))?;
+        let decoded = decode_to_result(&mut &output[..])?;
+        Ok(decoded)
     }
 }
 
diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs
index ac2c138309..5e3a5e6422 100644
--- a/crates/env/src/lib.rs
+++ b/crates/env/src/lib.rs
@@ -63,6 +63,7 @@ mod api;
 mod arithmetic;
 mod backend;
 pub mod call;
+pub mod chain_extension;
 mod engine;
 mod error;
 pub mod hash;
@@ -95,5 +96,6 @@ pub use self::{
         DefaultEnvironment,
         Environment,
         Hash,
+        NoChainExtension,
     },
 };
diff --git a/crates/env/src/types.rs b/crates/env/src/types.rs
index 4336bb8999..f015925dce 100644
--- a/crates/env/src/types.rs
+++ b/crates/env/src/types.rs
@@ -92,8 +92,19 @@ pub trait Environment {
         + PartialEq
         + Eq
         + AtLeast32BitUnsigned;
+
+    /// The chain extension for the environment.
+    ///
+    /// This is a type that is defined through the `#[ink::chain_extension]` proc. macro.
+    /// For more information about usage and definition click [this][chain_extension] link.
+    ///
+    /// [chain_extension]: https://paritytech.github.io/ink/ink_lang/attr.chain_extension.html
+    type ChainExtension;
 }
 
+/// Placeholder for chains that have no defined chain extension.
+pub enum NoChainExtension {}
+
 /// The fundamental types of the default configuration.
 #[derive(Debug, Clone, PartialEq, Eq)]
 #[cfg_attr(feature = "std", derive(TypeInfo))]
@@ -107,6 +118,7 @@ impl Environment for DefaultEnvironment {
     type Hash = Hash;
     type Timestamp = Timestamp;
     type BlockNumber = BlockNumber;
+    type ChainExtension = NoChainExtension;
 }
 
 /// The default balance type.
diff --git a/crates/lang/codegen/src/generator/chain_extension.rs b/crates/lang/codegen/src/generator/chain_extension.rs
new file mode 100644
index 0000000000..04c8ce2ac6
--- /dev/null
+++ b/crates/lang/codegen/src/generator/chain_extension.rs
@@ -0,0 +1,178 @@
+// Copyright 2018-2021 Parity Technologies (UK) Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::GenerateCode;
+use derive_more::From;
+use ir::ChainExtensionMethod;
+use proc_macro2::TokenStream as TokenStream2;
+use quote::{
+    format_ident,
+    quote_spanned,
+};
+use syn::spanned::Spanned;
+
+/// Generator to create an ink! chain extension.
+#[derive(From)]
+pub struct ChainExtension<'a> {
+    extension: &'a ir::ChainExtension,
+}
+
+impl ChainExtension<'_> {
+    fn generate_for_instance_method(
+        method: &ChainExtensionMethod,
+        error_code: &syn::Type,
+    ) -> TokenStream2 {
+        let span = method.span();
+        let attrs = method.attrs();
+        let ident = method.ident();
+        let func_id = method.id().into_u32();
+        let sig = method.sig();
+        let inputs = &sig.inputs;
+        let input_bindings = method.inputs().map(|pat_type| &pat_type.pat);
+        let input_types = method.inputs().map(|pat_type| &pat_type.ty);
+
+        // Mostly tuple-based type representation of the inputs:
+        // Special case for when there is exactly one input type.
+        // - 0 inputs          -> ()
+        // - 1 input T         -> T
+        // - n inputs A, B. .. -> (A, B, ..)
+        let compound_input_type = match inputs.len() {
+            0 => quote_spanned!(span=> ()),
+            1 => quote_spanned!(span=> #( #input_types )* ),
+            _n => quote_spanned!(span=> ( #( #input_types ),* ) ),
+        };
+
+        // Mostly tuple-based value representation of the inputs:
+        // Special case for when there is exactly one input value.
+        // - 0 inputs          -> ()
+        // - 1 input a         -> a
+        // - n inputs a, b. .. -> (a, b, ..)
+        let compound_input_bindings = match inputs.len() {
+            0 => quote_spanned!(span=> ()),
+            1 => quote_spanned!(span=> #( #input_bindings )* ),
+            _n => quote_spanned!(span=> ( #( #input_bindings ),* ) ),
+        };
+
+        let output = &sig.output;
+        let output_type = match output {
+            syn::ReturnType::Default => quote_spanned!(output.span()=> ()),
+            syn::ReturnType::Type(_arrow, ty) => {
+                quote_spanned!(output.span()=> #ty)
+            }
+        };
+
+        let handle_status = method.handle_status();
+        let returns_result = method.returns_result();
+
+        let error_code_handling = if handle_status {
+            quote_spanned!(span=>
+                .handle_error_code::<#error_code>()
+            )
+        } else {
+            quote_spanned!(span=>
+                .ignore_error_code()
+            )
+        };
+
+        let result_handling = if returns_result {
+            quote_spanned!(span=>
+                .output_result::<
+                    <#output_type as ::ink_lang::IsResultType>::Ok,
+                    <#output_type as ::ink_lang::IsResultType>::Err,
+                >()
+            )
+        } else {
+            quote_spanned!(span=>
+                .output::<#output_type>()
+            )
+        };
+
+        let returned_type = match (returns_result, handle_status) {
+            (false, true) => {
+                quote_spanned!(span=>
+                    ::core::result::Result<#output_type, #error_code>
+                )
+            }
+            _ => {
+                quote_spanned!(span=>
+                    #output_type
+                )
+            }
+        };
+
+        let where_output_is_result = Some(quote_spanned!(span=>
+            #output_type: ::ink_lang::IsResultType,
+        ))
+        .filter(|_| returns_result);
+
+        let where_output_impls_from_error_code = Some(quote_spanned!(span=>
+            <#output_type as ::ink_lang::IsResultType>::Err: ::core::convert::From<#error_code>,
+        )).filter(|_| returns_result && handle_status);
+
+        quote_spanned!(span=>
+            #( #attrs )*
+            #[inline]
+            pub fn #ident(self, #inputs) -> #returned_type
+            where
+                #where_output_is_result
+                #where_output_impls_from_error_code
+            {
+                ::ink_env::chain_extension::ChainExtensionMethod::build(#func_id)
+                    .input::<#compound_input_type>()
+                    #result_handling
+                    #error_code_handling
+                    .call(&#compound_input_bindings)
+            }
+        )
+    }
+}
+
+impl GenerateCode for ChainExtension<'_> {
+    fn generate_code(&self) -> TokenStream2 {
+        let span = self.extension.span();
+        let attrs = self.extension.attrs();
+        let ident = self.extension.ident();
+        let error_code = self.extension.error_code();
+        let instance_methods = self
+            .extension
+            .iter_methods()
+            .map(|method| Self::generate_for_instance_method(method, error_code));
+        let instance_ident = format_ident!("__ink_{}Instance", ident);
+        quote_spanned!(span =>
+            #(#attrs)*
+            pub enum #ident {}
+
+            const _: () = {
+                #[allow(non_camel_case_types)]
+                struct __ink_Private;
+                #[allow(non_camel_case_types)]
+                pub struct #instance_ident {
+                    __ink_private: __ink_Private
+                }
+
+                impl #instance_ident {
+                    #( #instance_methods )*
+                }
+
+                impl ::ink_lang::ChainExtensionInstance for #ident {
+                    type Instance = #instance_ident;
+
+                    fn instantiate() -> Self::Instance {
+                        Self::Instance { __ink_private: __ink_Private }
+                    }
+                }
+            };
+        )
+    }
+}
diff --git a/crates/lang/codegen/src/generator/env.rs b/crates/lang/codegen/src/generator/env.rs
index 483e8845c2..bb9682f1cd 100644
--- a/crates/lang/codegen/src/generator/env.rs
+++ b/crates/lang/codegen/src/generator/env.rs
@@ -25,11 +25,11 @@ pub struct Env<'a> {
 
 impl GenerateCode for Env<'_> {
     fn generate_code(&self) -> TokenStream2 {
-        let env_types = self.contract.config().env_types();
+        let env = self.contract.config().env();
         let storage_ident = self.contract.module().storage().ident();
         quote! {
             impl ::ink_lang::ContractEnv for #storage_ident {
-                type Env = #env_types;
+                type Env = #env;
             }
 
             type Environment = <#storage_ident as ::ink_lang::ContractEnv>::Env;
diff --git a/crates/lang/codegen/src/generator/mod.rs b/crates/lang/codegen/src/generator/mod.rs
index 7b0b363666..b0406c7589 100644
--- a/crates/lang/codegen/src/generator/mod.rs
+++ b/crates/lang/codegen/src/generator/mod.rs
@@ -12,6 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+mod chain_extension;
 mod contract;
 mod cross_calling;
 mod dispatch;
@@ -24,6 +25,7 @@ mod storage;
 mod trait_def;
 
 pub use self::{
+    chain_extension::ChainExtension,
     contract::Contract,
     cross_calling::{
         CrossCalling,
diff --git a/crates/lang/codegen/src/lib.rs b/crates/lang/codegen/src/lib.rs
index 306ecb57bf..09a4bece29 100644
--- a/crates/lang/codegen/src/lib.rs
+++ b/crates/lang/codegen/src/lib.rs
@@ -19,7 +19,6 @@ use self::traits::{
     GenerateCode,
     GenerateCodeUsing,
 };
-
 use proc_macro2::TokenStream as TokenStream2;
 
 /// Types for which code can be generated by this crate.
@@ -40,6 +39,10 @@ impl<'a> CodeGenerator for &'a ir::InkTest {
     type Generator = generator::InkTest<'a>;
 }
 
+impl<'a> CodeGenerator for &'a ir::ChainExtension {
+    type Generator = generator::ChainExtension<'a>;
+}
+
 /// Generates the entire code for the given ink! contract.
 pub fn generate_code<T>(entity: T) -> TokenStream2
 where
diff --git a/crates/lang/ir/src/ast/attr_args.rs b/crates/lang/ir/src/ast/attr_args.rs
index 4591dd277d..c2a7582557 100644
--- a/crates/lang/ir/src/ast/attr_args.rs
+++ b/crates/lang/ir/src/ast/attr_args.rs
@@ -29,8 +29,8 @@ use syn::{
 
 /// The attribute arguments for the configuration of an ink! smart contract.
 ///
-/// These are the segments `env_types = ::my::env_types::Environment` and `compile_as_dependency = true`
-/// in `#[ink::contract(env_types = ::my::env_types::Environment, compile_as_dependency = true`.
+/// These are the segments `env = ::my::env::Environment` and `compile_as_dependency = true`
+/// in `#[ink::contract(env = ::my::env::Environment, compile_as_dependency = true`.
 #[derive(Debug, PartialEq, Eq)]
 pub struct AttributeArgs {
     args: Punctuated<MetaNameValue, Token![,]>,
diff --git a/crates/lang/ir/src/ast/mod.rs b/crates/lang/ir/src/ast/mod.rs
index 5692c31955..77514d5974 100644
--- a/crates/lang/ir/src/ast/mod.rs
+++ b/crates/lang/ir/src/ast/mod.rs
@@ -20,7 +20,7 @@
 //! syntax.
 //!
 //! At the time of this writing we currently only use this for the argument
-//! parsing of ink! config header `#[ink(env_types = my::env_types::Types, etc...)]` in order
+//! parsing of ink! config header `#[ink(env = my::env::Types, etc...)]` in order
 //! to be able to parse identifiers in `name = value` segments for the `value`
 //! part.
 
diff --git a/crates/lang/ir/src/ir/attrs.rs b/crates/lang/ir/src/ir/attrs.rs
index 3a20d77796..2732d66f53 100644
--- a/crates/lang/ir/src/ir/attrs.rs
+++ b/crates/lang/ir/src/ir/attrs.rs
@@ -15,7 +15,10 @@
 use crate::{
     error::ExtError as _,
     ir,
-    ir::Selector,
+    ir::{
+        ExtensionId,
+        Selector,
+    },
 };
 use core::{
     convert::TryFrom,
@@ -26,6 +29,7 @@ use proc_macro2::{
     Span,
 };
 use regex::Regex;
+use std::collections::HashMap;
 use syn::spanned::Spanned;
 
 /// Either an ink! specific attribute, or another uninterpreted attribute.
@@ -104,7 +108,7 @@ impl Attrs for syn::Item {
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 pub struct InkAttribute {
     /// The internal non-empty sequence of arguments of the ink! attribute.
-    args: Vec<AttributeArg>,
+    args: Vec<AttributeFrag>,
 }
 
 impl Spanned for InkAttribute {
@@ -125,7 +129,7 @@ impl InkAttribute {
     ///
     /// If the first ink! attribute argument is not of expected kind.
     pub fn ensure_first(&self, expected: &AttributeArgKind) -> Result<(), syn::Error> {
-        if &self.first().kind != expected {
+        if &self.first().arg.kind() != expected {
             return Err(format_err!(
                 self.span(),
                 "unexpected first ink! attribute argument",
@@ -142,11 +146,12 @@ impl InkAttribute {
     /// If the given iterator yields duplicate ink! attribute arguments.
     fn ensure_no_duplicate_args<'a, A>(args: A) -> Result<(), syn::Error>
     where
-        A: IntoIterator<Item = &'a ir::AttributeArg>,
+        A: IntoIterator<Item = &'a ir::AttributeFrag>,
     {
         use crate::error::ExtError as _;
         use std::collections::HashSet;
-        let mut seen: HashSet<&AttributeArg> = HashSet::new();
+        let mut seen: HashSet<&AttributeFrag> = HashSet::new();
+        let mut seen2: HashMap<AttributeArgKind, Span> = HashMap::new();
         for arg in args.into_iter() {
             if let Some(seen) = seen.get(arg) {
                 return Err(format_err!(
@@ -158,7 +163,18 @@ impl InkAttribute {
                     "first equal ink! attribute argument here"
                 )))
             }
+            if let Some(seen) = seen2.get(&arg.kind().kind()) {
+                return Err(format_err!(
+                    arg.span(),
+                    "encountered ink! attribute arguments with equal kinds"
+                )
+                .into_combine(format_err!(
+                    *seen,
+                    "first equal ink! attribute argument with equal kind here"
+                )))
+            }
             seen.insert(arg);
+            seen2.insert(arg.kind().kind(), arg.span());
         }
         Ok(())
     }
@@ -195,7 +211,7 @@ impl InkAttribute {
     }
 
     /// Returns the first ink! attribute argument.
-    pub fn first(&self) -> &AttributeArg {
+    pub fn first(&self) -> &AttributeFrag {
         self.args
             .first()
             .expect("encountered invalid empty ink! attribute list")
@@ -206,14 +222,14 @@ impl InkAttribute {
     /// # Note
     ///
     /// This yields at least one ink! attribute flag.
-    pub fn args(&self) -> ::core::slice::Iter<AttributeArg> {
+    pub fn args(&self) -> ::core::slice::Iter<AttributeFrag> {
         self.args.iter()
     }
 
     /// Returns the namespace of the ink! attribute if any.
     pub fn namespace(&self) -> Option<ir::Namespace> {
         self.args().find_map(|arg| {
-            if let ir::AttributeArgKind::Namespace(namespace) = arg.kind() {
+            if let ir::AttributeArg::Namespace(namespace) = arg.kind() {
                 return Some(namespace.clone())
             }
             None
@@ -223,7 +239,7 @@ impl InkAttribute {
     /// Returns the selector of the ink! attribute if any.
     pub fn selector(&self) -> Option<ir::Selector> {
         self.args().find_map(|arg| {
-            if let ir::AttributeArgKind::Selector(selector) = arg.kind() {
+            if let ir::AttributeArg::Selector(selector) = arg.kind() {
                 return Some(*selector)
             }
             None
@@ -233,39 +249,88 @@ impl InkAttribute {
     /// Returns `true` if the ink! attribute contains the `payable` argument.
     pub fn is_payable(&self) -> bool {
         self.args()
-            .any(|arg| matches!(arg.kind(), AttributeArgKind::Payable))
+            .any(|arg| matches!(arg.kind(), AttributeArg::Payable))
     }
 
     /// Returns `true` if the ink! attribute contains the `anonymous` argument.
     pub fn is_anonymous(&self) -> bool {
         self.args()
-            .any(|arg| matches!(arg.kind(), AttributeArgKind::Anonymous))
+            .any(|arg| matches!(arg.kind(), AttributeArg::Anonymous))
+    }
+
+    /// Returns `false` if the ink! attribute contains the `handle_status = false` argument.
+    ///
+    /// Otherwise returns `true`.
+    pub fn is_handle_status(&self) -> bool {
+        !self
+            .args()
+            .any(|arg| matches!(arg.kind(), AttributeArg::HandleStatus(false)))
+    }
+
+    /// Returns `false` if the ink! attribute contains the `returns_result = false` argument.
+    ///
+    /// Otherwise returns `true`.
+    pub fn is_returns_result(&self) -> bool {
+        !self
+            .args()
+            .any(|arg| matches!(arg.kind(), AttributeArg::ReturnsResult(false)))
     }
 }
 
 /// An ink! specific attribute argument.
 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
-pub struct AttributeArg {
+pub struct AttributeFrag {
     pub ast: syn::Meta,
-    pub kind: AttributeArgKind,
+    pub arg: AttributeArg,
 }
 
-impl AttributeArg {
+impl AttributeFrag {
     /// Returns a shared reference to the attribute argument kind.
-    pub fn kind(&self) -> &AttributeArgKind {
-        &self.kind
+    pub fn kind(&self) -> &AttributeArg {
+        &self.arg
     }
 }
 
-impl Spanned for AttributeArg {
+impl Spanned for AttributeFrag {
     fn span(&self) -> Span {
         self.ast.span()
     }
 }
 
-/// An ink! specific attribute flag.
+/// The kind of an ink! attribute argument.
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
 pub enum AttributeArgKind {
+    /// `#[ink(storage)]`
+    Storage,
+    /// `#[ink(event)]`
+    Event,
+    /// `#[ink(anonymous)]`
+    Anonymous,
+    /// `#[ink(topic)]`
+    Topic,
+    /// `#[ink(message)]`
+    Message,
+    /// `#[ink(constructor)]`
+    Constructor,
+    /// `#[ink(payable)]`
+    Payable,
+    /// `#[ink(selector = "0xDEADBEEF")]`
+    Selector,
+    /// `#[ink(extension = N: u32)]`
+    Extension,
+    /// `#[ink(namespace = "my_namespace")]`
+    Namespace,
+    /// `#[ink(impl)]`
+    Implementation,
+    /// `#[ink(handle_status = flag: bool)]`
+    HandleStatus,
+    /// `#[ink(returns_result = flag: bool)]`
+    ReturnsResult,
+}
+
+/// An ink! specific attribute flag.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub enum AttributeArg {
     /// `#[ink(storage)]`
     ///
     /// Applied on `struct` types in order to flag them for being the
@@ -324,6 +389,25 @@ pub enum AttributeArgKind {
     /// etc. Note that ink! messages and constructors still need to be explicitly
     /// flagged as such.
     Implementation,
+    /// `#[ink(extension = N: u32)]`
+    ///
+    /// Applies on ink! chain extension method to set their `func_id` parameter.
+    /// Every chain extension method must have exactly one ink! `extension` attribute.
+    ///
+    /// Used by the `#[ink::chain_extension]` proc. macro.
+    Extension(ExtensionId),
+    /// `#[ink(handle_status = flag: bool)]`
+    ///
+    /// Used by the `#[ink::chain_extension]` proc. macro.
+    ///
+    /// Default value: `true`
+    HandleStatus(bool),
+    /// `#[ink(returns_result = flag: bool)]`
+    ///
+    /// Used by the `#[ink::chain_extension]` proc. macro.
+    ///
+    /// Default value: `true`
+    ReturnsResult(bool),
 }
 
 impl core::fmt::Display for AttributeArgKind {
@@ -336,11 +420,65 @@ impl core::fmt::Display for AttributeArgKind {
             Self::Message => write!(f, "message"),
             Self::Constructor => write!(f, "constructor"),
             Self::Payable => write!(f, "payable"),
-            Self::Selector(selector) => write!(f, "selector = {:?}", selector.as_bytes()),
+            Self::Selector => {
+                write!(f, "selector = S:[u8; 4]")
+            }
+            Self::Extension => {
+                write!(f, "extension = N:u32)")
+            }
+            Self::Namespace => {
+                write!(f, "namespace = N:string")
+            }
+            Self::Implementation => write!(f, "impl"),
+            Self::HandleStatus => write!(f, "handle_status"),
+            Self::ReturnsResult => write!(f, "returns_result"),
+        }
+    }
+}
+
+impl AttributeArg {
+    /// Returns the kind of the ink! attribute argument.
+    pub fn kind(&self) -> AttributeArgKind {
+        match self {
+            Self::Storage => AttributeArgKind::Storage,
+            Self::Event => AttributeArgKind::Event,
+            Self::Anonymous => AttributeArgKind::Anonymous,
+            Self::Topic => AttributeArgKind::Topic,
+            Self::Message => AttributeArgKind::Message,
+            Self::Constructor => AttributeArgKind::Constructor,
+            Self::Payable => AttributeArgKind::Payable,
+            Self::Selector(_) => AttributeArgKind::Selector,
+            Self::Extension(_) => AttributeArgKind::Extension,
+            Self::Namespace(_) => AttributeArgKind::Namespace,
+            Self::Implementation => AttributeArgKind::Implementation,
+            Self::HandleStatus(_) => AttributeArgKind::HandleStatus,
+            Self::ReturnsResult(_) => AttributeArgKind::ReturnsResult,
+        }
+    }
+}
+
+impl core::fmt::Display for AttributeArg {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
+        match self {
+            Self::Storage => write!(f, "storage"),
+            Self::Event => write!(f, "event"),
+            Self::Anonymous => write!(f, "anonymous"),
+            Self::Topic => write!(f, "topic"),
+            Self::Message => write!(f, "message"),
+            Self::Constructor => write!(f, "constructor"),
+            Self::Payable => write!(f, "payable"),
+            Self::Selector(selector) => {
+                write!(f, "selector = {:?}", selector.as_bytes())
+            }
+            Self::Extension(extension) => {
+                write!(f, "extension = {:?}", extension.into_u32())
+            }
             Self::Namespace(namespace) => {
                 write!(f, "namespace = {:?}", namespace.as_bytes())
             }
             Self::Implementation => write!(f, "impl"),
+            Self::HandleStatus(value) => write!(f, "handle_status = {:?}", value),
+            Self::ReturnsResult(value) => write!(f, "returns_result = {:?}", value),
         }
     }
 }
@@ -449,7 +587,7 @@ pub fn sanitize_attributes<I, C>(
 ) -> Result<(InkAttribute, Vec<syn::Attribute>), syn::Error>
 where
     I: IntoIterator<Item = syn::Attribute>,
-    C: FnMut(&AttributeArgKind) -> bool,
+    C: FnMut(&AttributeArg) -> bool,
 {
     let (ink_attrs, other_attrs) = ir::partition_attributes(attrs)?;
     let normalized = ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
@@ -525,7 +663,7 @@ impl TryFrom<syn::Attribute> for InkAttribute {
                 let args = meta_list
                     .nested
                     .into_iter()
-                    .map(<AttributeArg as TryFrom<_>>::try_from)
+                    .map(<AttributeFrag as TryFrom<_>>::try_from)
                     .collect::<Result<Vec<_>, syn::Error>>()?;
                 Self::ensure_no_duplicate_args(&args)?;
                 if args.is_empty() {
@@ -551,7 +689,7 @@ impl InkAttribute {
         mut is_conflicting: P,
     ) -> Result<(), syn::Error>
     where
-        P: FnMut(&'a ir::AttributeArg) -> bool,
+        P: FnMut(&'a ir::AttributeFrag) -> bool,
     {
         for arg in self.args() {
             if is_conflicting(arg) {
@@ -578,7 +716,7 @@ fn invalid_selector_err_regex(meta: &syn::Meta) -> syn::Error {
     )
 }
 
-impl TryFrom<syn::NestedMeta> for AttributeArg {
+impl TryFrom<syn::NestedMeta> for AttributeFrag {
     type Error = syn::Error;
 
     fn try_from(nested_meta: syn::NestedMeta) -> Result<Self, Self::Error> {
@@ -588,13 +726,28 @@ impl TryFrom<syn::NestedMeta> for AttributeArg {
                     syn::Meta::NameValue(name_value) => {
                         if name_value.path.is_ident("selector") {
                             if let syn::Lit::Str(lit_str) = &name_value.lit {
-                                let regex = Regex::new(
-                                    r"0x([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})"
-                                ).map_err(|_| invalid_selector_err_regex(&meta))?;
+                                let regex = Regex::new(r"0x([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})([\da-fA-F]{2})")
+                                    .map_err(|_| {
+                                    invalid_selector_err_regex(&meta)
+                                })?;
                                 let str = lit_str.value();
-                                let cap = regex
-                                    .captures(&str)
-                                    .ok_or_else(|| invalid_selector_err_regex(&meta))?;
+                                let cap =
+                                    regex.captures(&str).ok_or_else(|| {
+                                        invalid_selector_err_regex(&meta)
+                                    })?;
+                                if !regex.is_match(&str) {
+                                    return Err(invalid_selector_err_regex(
+                                        &meta,
+                                    ))
+                                }
+                                let len_digits = (str.as_bytes().len() - 2) / 2;
+                                if len_digits != 4 {
+                                    return Err(format_err!(
+                                            name_value,
+                                            "expected 4-digit hexcode for `selector` argument, found {} digits",
+                                            len_digits,
+                                        ))
+                                }
                                 let selector_bytes = [
                                     u8::from_str_radix(&cap[1], 16)
                                         .map_err(|_| err_non_hex(&meta, 0))?,
@@ -605,24 +758,63 @@ impl TryFrom<syn::NestedMeta> for AttributeArg {
                                     u8::from_str_radix(&cap[4], 16)
                                         .map_err(|_| err_non_hex(&meta, 3))?,
                                 ];
-                                return Ok(AttributeArg {
+                                return Ok(AttributeFrag {
                                     ast: meta,
-                                    kind: AttributeArgKind::Selector(Selector::new(
+                                    arg: AttributeArg::Selector(Selector::new(
                                         selector_bytes,
                                     )),
                                 })
                             }
+                            return Err(format_err!(name_value, "expecteded 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"))
                         }
                         if name_value.path.is_ident("namespace") {
                             if let syn::Lit::Str(lit_str) = &name_value.lit {
                                 let bytes = lit_str.value().into_bytes();
-                                return Ok(AttributeArg {
+                                return Ok(AttributeFrag {
                                     ast: meta,
-                                    kind: AttributeArgKind::Namespace(Namespace::from(
-                                        bytes,
-                                    )),
+                                    arg: AttributeArg::Namespace(
+                                        Namespace::from(bytes),
+                                    ),
+                                })
+                            }
+                            return Err(format_err!(name_value, "expecteded string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]"))
+                        }
+                        if name_value.path.is_ident("extension") {
+                            if let syn::Lit::Int(lit_int) = &name_value.lit {
+                                let id = lit_int.base10_parse::<u32>().map_err(|parse_err| {
+                                    format_err!(
+                                        name_value,
+                                        "could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
+                                    ).into_combine(parse_err)
+                                })?;
+                                return Ok(AttributeFrag {
+                                    ast: meta,
+                                    arg: AttributeArg::Extension(
+                                        ExtensionId::from_u32(id),
+                                    ),
+                                })
+                            }
+                            return Err(format_err!(name_value, "expecteded `u32` integer type for `N` in #[ink(extension = N)]"))
+                        }
+                        if name_value.path.is_ident("handle_status") {
+                            if let syn::Lit::Bool(lit_bool) = &name_value.lit {
+                                let value = lit_bool.value;
+                                return Ok(AttributeFrag {
+                                    ast: meta,
+                                    arg: AttributeArg::HandleStatus(value),
                                 })
                             }
+                            return Err(format_err!(name_value, "expecteded `bool` value type for `flag` in #[ink(handle_status = flag)]"))
+                        }
+                        if name_value.path.is_ident("returns_result") {
+                            if let syn::Lit::Bool(lit_bool) = &name_value.lit {
+                                let value = lit_bool.value;
+                                return Ok(AttributeFrag {
+                                    ast: meta,
+                                    arg: AttributeArg::ReturnsResult(value),
+                                })
+                            }
+                            return Err(format_err!(name_value, "expecteded `bool` value type for `flag` in #[ink(returns_result = flag)]"))
                         }
                         Err(format_err_spanned!(
                             meta,
@@ -630,24 +822,44 @@ impl TryFrom<syn::NestedMeta> for AttributeArg {
                         ))
                     }
                     syn::Meta::Path(path) => {
-                        let kind: Option<AttributeArgKind> =
-                            path.get_ident().map(Ident::to_string).and_then(|ident| {
-                                match ident.as_str() {
-                                    "storage" => Some(AttributeArgKind::Storage),
-                                    "message" => Some(AttributeArgKind::Message),
-                                    "constructor" => Some(AttributeArgKind::Constructor),
-                                    "event" => Some(AttributeArgKind::Event),
-                                    "anonymous" => Some(AttributeArgKind::Anonymous),
-                                    "topic" => Some(AttributeArgKind::Topic),
-                                    "payable" => Some(AttributeArgKind::Payable),
-                                    "impl" => Some(AttributeArgKind::Implementation),
-                                    _ => None,
-                                }
-                            });
-                        if let Some(kind) = kind {
-                            return Ok(AttributeArg { ast: meta, kind })
-                        }
-                        Err(format_err_spanned!(meta, "unknown ink! attribute (path)"))
+                        path
+                            .get_ident()
+                            .map(Ident::to_string)
+                            .ok_or_else(|| format_err_spanned!(meta, "unknown ink! attribute (path)"))
+                            .and_then(|ident| match ident.as_str() {
+                                "storage" => Ok(AttributeArg::Storage),
+                                "message" => Ok(AttributeArg::Message),
+                                "constructor" => Ok(AttributeArg::Constructor),
+                                "event" => Ok(AttributeArg::Event),
+                                "anonymous" => Ok(AttributeArg::Anonymous),
+                                "topic" => Ok(AttributeArg::Topic),
+                                "payable" => Ok(AttributeArg::Payable),
+                                "impl" => Ok(AttributeArg::Implementation),
+                                "namespace" => Err(format_err!(
+                                    meta,
+                                    "encountered #[ink(namespace)] that is missing its string parameter. \
+                                    Did you mean #[ink(namespace = name: str)] ?"
+                                )),
+                                "extension" => Err(format_err!(
+                                    meta,
+                                    "encountered #[ink(extension)] that is missing its N parameter. \
+                                    Did you mean #[ink(extension = N: u32)] ?"
+                                )),
+                                "handle_status" => Err(format_err!(
+                                    meta,
+                                    "encountered #[ink(handle_status)] that is missing its `flag: bool` parameter. \
+                                    Did you mean #[ink(handle_status = flag: bool)] ?"
+                                )),
+                                "returns_result" => Err(format_err!(
+                                    meta,
+                                    "encountered #[ink(returns_result)] that is missing its `flag: bool` parameter. \
+                                    Did you mean #[ink(returns_result = flag: bool)] ?"
+                                )),
+                                _ => Err(format_err_spanned!(
+                                    meta, "unknown ink! attribute (path)"
+                                ))
+                            })
+                            .map(|kind| AttributeFrag { ast: meta, arg: kind, })
                     }
                     syn::Meta::List(_) => {
                         Err(format_err_spanned!(
@@ -695,16 +907,13 @@ mod tests {
     /// Can be used to assert against the success and failure path.
     fn assert_first_ink_attribute(
         input: &[syn::Attribute],
-        expected: Result<Option<Vec<ir::AttributeArgKind>>, &'static str>,
+        expected: Result<Option<Vec<ir::AttributeArg>>, &'static str>,
     ) {
         assert_eq!(
             first_ink_attribute(input)
                 .map(|maybe_attr: Option<ir::InkAttribute>| {
                     maybe_attr.map(|attr: ir::InkAttribute| {
-                        attr.args
-                            .into_iter()
-                            .map(|arg| arg.kind)
-                            .collect::<Vec<_>>()
+                        attr.args.into_iter().map(|arg| arg.arg).collect::<Vec<_>>()
                     })
                 })
                 .map_err(|err| err.to_string()),
@@ -717,7 +926,7 @@ mod tests {
         assert_first_ink_attribute(&[], Ok(None));
         assert_first_ink_attribute(
             &[syn::parse_quote! { #[ink(storage)] }],
-            Ok(Some(vec![AttributeArgKind::Storage])),
+            Ok(Some(vec![AttributeArg::Storage])),
         );
         assert_first_ink_attribute(
             &[syn::parse_quote! { #[ink(invalid)] }],
@@ -731,7 +940,7 @@ mod tests {
         /// Mock for `ir::Attribute` to improve testability.
         #[derive(Debug, PartialEq, Eq)]
         pub enum Attribute {
-            Ink(Vec<ir::AttributeArgKind>),
+            Ink(Vec<ir::AttributeArg>),
             Other(syn::Attribute),
         }
 
@@ -743,7 +952,7 @@ mod tests {
                             ink_attr
                                 .args
                                 .into_iter()
-                                .map(|arg| arg.kind)
+                                .map(|arg| arg.arg)
                                 .collect::<Vec<_>>(),
                         )
                     }
@@ -761,7 +970,7 @@ mod tests {
         /// Mock for `ir::InkAttribute` to improve testability.
         #[derive(Debug, PartialEq, Eq)]
         pub struct InkAttribute {
-            args: Vec<ir::AttributeArgKind>,
+            args: Vec<ir::AttributeArg>,
         }
 
         impl From<ir::InkAttribute> for InkAttribute {
@@ -770,7 +979,7 @@ mod tests {
                     args: ink_attr
                         .args
                         .into_iter()
-                        .map(|arg| arg.kind)
+                        .map(|arg| arg.arg)
                         .collect::<Vec<_>>(),
                 }
             }
@@ -778,7 +987,7 @@ mod tests {
 
         impl<I> From<I> for InkAttribute
         where
-            I: IntoIterator<Item = ir::AttributeArgKind>,
+            I: IntoIterator<Item = ir::AttributeArg>,
         {
             fn from(args: I) -> Self {
                 Self {
@@ -808,7 +1017,7 @@ mod tests {
             syn::parse_quote! {
                 #[ink(storage)]
             },
-            Ok(test::Attribute::Ink(vec![AttributeArgKind::Storage])),
+            Ok(test::Attribute::Ink(vec![AttributeArg::Storage])),
         );
     }
 
@@ -820,7 +1029,7 @@ mod tests {
             syn::parse_quote! {
                 #[ink(impl)]
             },
-            Ok(test::Attribute::Ink(vec![AttributeArgKind::Implementation])),
+            Ok(test::Attribute::Ink(vec![AttributeArg::Implementation])),
         );
     }
 
@@ -830,24 +1039,223 @@ mod tests {
             syn::parse_quote! {
                 #[ink(selector = "0xDEADBEEF")]
             },
-            Ok(test::Attribute::Ink(vec![AttributeArgKind::Selector(
+            Ok(test::Attribute::Ink(vec![AttributeArg::Selector(
                 Selector::new([0xDE, 0xAD, 0xBE, 0xEF]),
             )])),
         );
     }
 
+    #[test]
+    fn selector_non_hexcode() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(selector = "0xhelloworld")]
+            },
+            Err("invalid selector - a selector must consist of four bytes in hex (e.g. `selector = \"0xCAFEBABE\"`)"),
+        );
+    }
+
+    #[test]
+    fn selector_too_long() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(selector = "0xDEADBEEFC0FEBABE")]
+            },
+            Err("expected 4-digit hexcode for `selector` argument, found 8 digits"),
+        );
+    }
+
+    #[test]
+    fn selector_invalid_type() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(selector = 42)]
+            },
+            Err("expecteded 4-digit hexcode for `selector` argument, e.g. #[ink(selector = 0xC0FEBABE]"),
+        );
+    }
+
     #[test]
     fn namespace_works() {
         assert_attribute_try_from(
             syn::parse_quote! {
                 #[ink(namespace = "my_namespace")]
             },
-            Ok(test::Attribute::Ink(vec![AttributeArgKind::Namespace(
+            Ok(test::Attribute::Ink(vec![AttributeArg::Namespace(
                 Namespace::from("my_namespace".to_string().into_bytes()),
             )])),
         );
     }
 
+    #[test]
+    fn namespace_invalid_type() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(namespace = 42)]
+            },
+            Err("expecteded string type for `namespace` argument, e.g. #[ink(namespace = \"hello\")]"),
+        );
+    }
+
+    #[test]
+    fn namespace_missing_parameter() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(namespace)]
+            },
+            Err(
+                "encountered #[ink(namespace)] that is missing its string parameter. \
+                Did you mean #[ink(namespace = name: str)] ?",
+            ),
+        );
+    }
+
+    #[test]
+    fn extension_works() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(extension = 42)]
+            },
+            Ok(test::Attribute::Ink(vec![AttributeArg::Extension(
+                ExtensionId::from_u32(42),
+            )])),
+        );
+    }
+
+    #[test]
+    fn extension_invalid_value_type() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(extension = "string")]
+            },
+            Err("expecteded `u32` integer type for `N` in #[ink(extension = N)]"),
+        );
+    }
+
+    #[test]
+    fn extension_negative_integer() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(extension = -1)]
+            },
+            Err("could not parse `N` in `#[ink(extension = N)]` into a `u32` integer"),
+        );
+    }
+
+    #[test]
+    fn extension_too_big_integer() {
+        let max_u32_plus_1 = (u32::MAX as u64) + 1;
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(extension = #max_u32_plus_1)]
+            },
+            Err("could not parse `N` in `#[ink(extension = N)]` into a `u32` integer"),
+        );
+    }
+
+    #[test]
+    fn extension_missing_parameter() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(extension)]
+            },
+            Err(
+                "encountered #[ink(extension)] that is missing its N parameter. \
+                Did you mean #[ink(extension = N: u32)] ?",
+            ),
+        );
+    }
+
+    #[test]
+    fn handle_status_works() {
+        fn expected_ok(value: bool) -> Result<test::Attribute, &'static str> {
+            Ok(test::Attribute::Ink(vec![AttributeArg::HandleStatus(
+                value,
+            )]))
+        }
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(handle_status = true)]
+            },
+            expected_ok(true),
+        );
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(handle_status = false)]
+            },
+            expected_ok(false),
+        );
+    }
+
+    #[test]
+    fn handle_status_missing_parameter() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(handle_status)]
+            },
+            Err(
+                "encountered #[ink(handle_status)] that is missing its `flag: bool` parameter. \
+                Did you mean #[ink(handle_status = flag: bool)] ?",
+            ),
+        );
+    }
+
+    #[test]
+    fn handle_status_invalid_parameter_type() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(handle_status = "string")]
+            },
+            Err(
+                "expecteded `bool` value type for `flag` in #[ink(handle_status = flag)]",
+            ),
+        );
+    }
+
+    #[test]
+    fn returns_result_works() {
+        fn expected_ok(value: bool) -> Result<test::Attribute, &'static str> {
+            Ok(test::Attribute::Ink(vec![AttributeArg::ReturnsResult(
+                value,
+            )]))
+        }
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(returns_result = true)]
+            },
+            expected_ok(true),
+        );
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(returns_result = false)]
+            },
+            expected_ok(false),
+        );
+    }
+
+    #[test]
+    fn returns_result_missing_parameter() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(returns_result)]
+            },
+            Err(
+                "encountered #[ink(returns_result)] that is missing its `flag: bool` parameter. \
+                Did you mean #[ink(returns_result = flag: bool)] ?",
+            ),
+        );
+    }
+
+    #[test]
+    fn returns_result_invalid_parameter_type() {
+        assert_attribute_try_from(
+            syn::parse_quote! {
+                #[ink(returns_result = "string")]
+            },
+            Err("expecteded `bool` value type for `flag` in #[ink(returns_result = flag)]"),
+        );
+    }
+
     #[test]
     fn compound_mixed_works() {
         assert_attribute_try_from(
@@ -855,8 +1263,8 @@ mod tests {
                 #[ink(message, namespace = "my_namespace")]
             },
             Ok(test::Attribute::Ink(vec![
-                AttributeArgKind::Message,
-                AttributeArgKind::Namespace(Namespace::from(
+                AttributeArg::Message,
+                AttributeArg::Namespace(Namespace::from(
                     "my_namespace".to_string().into_bytes(),
                 )),
             ])),
@@ -878,13 +1286,13 @@ mod tests {
                 )]
             },
             Ok(test::Attribute::Ink(vec![
-                AttributeArgKind::Storage,
-                AttributeArgKind::Message,
-                AttributeArgKind::Constructor,
-                AttributeArgKind::Event,
-                AttributeArgKind::Topic,
-                AttributeArgKind::Payable,
-                AttributeArgKind::Implementation,
+                AttributeArg::Storage,
+                AttributeArg::Message,
+                AttributeArg::Constructor,
+                AttributeArg::Event,
+                AttributeArg::Topic,
+                AttributeArg::Payable,
+                AttributeArg::Implementation,
             ])),
         );
     }
@@ -954,7 +1362,7 @@ mod tests {
                 syn::parse_quote! { #[non_ink_attribute] },
             ],
             Ok((
-                vec![test::InkAttribute::from(vec![AttributeArgKind::Message])],
+                vec![test::InkAttribute::from(vec![AttributeArg::Message])],
                 vec![syn::parse_quote! { #[non_ink_attribute] }],
             )),
         )
diff --git a/crates/lang/ir/src/ir/chain_extension.rs b/crates/lang/ir/src/ir/chain_extension.rs
new file mode 100644
index 0000000000..55bc5aaca2
--- /dev/null
+++ b/crates/lang/ir/src/ir/chain_extension.rs
@@ -0,0 +1,985 @@
+// Copyright 2018-2021 Parity Technologies (UK) Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::{
+    error::ExtError,
+    ir,
+    ir::idents_lint,
+};
+use core::{
+    convert::TryFrom,
+    slice::Iter as SliceIter,
+};
+use proc_macro2::TokenStream as TokenStream2;
+use std::collections::HashMap;
+use syn::{
+    spanned::Spanned as _,
+    Result,
+};
+
+/// An ink! chain extension.
+#[derive(Debug, PartialEq, Eq)]
+pub struct ChainExtension {
+    item: syn::ItemTrait,
+    error_code: syn::TraitItemType,
+    methods: Vec<ChainExtensionMethod>,
+}
+
+impl ChainExtension {
+    /// Returns the Rust attributes of the ink! chain extension.
+    pub fn attrs(&self) -> Vec<syn::Attribute> {
+        let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
+            .expect("encountered unexpected invalid attributes for ink! chain extension");
+        attrs
+    }
+
+    /// Returns the span of the ink! chain extension.
+    pub fn span(&self) -> proc_macro2::Span {
+        self.item.span()
+    }
+
+    /// Returns the identifier of the ink! chain extension.
+    pub fn ident(&self) -> &proc_macro2::Ident {
+        &self.item.ident
+    }
+
+    /// Returns a slice over all the chain extension methods.
+    pub fn iter_methods(&self) -> SliceIter<ChainExtensionMethod> {
+        self.methods.iter()
+    }
+
+    /// Returns the type of the error code of the chain extension.
+    pub fn error_code(&self) -> &syn::Type {
+        self.error_code
+            .default
+            .as_ref()
+            .map(|(_token, ty)| ty)
+            .expect("unexpected missing default type for error code")
+    }
+}
+
+/// An ink! chain extension method.
+#[derive(Debug, PartialEq, Eq)]
+pub struct ChainExtensionMethod {
+    /// The underlying validated AST of the chain extension method.
+    item: syn::TraitItemMethod,
+    /// The unique identifier of the chain extension method.
+    id: ExtensionId,
+    /// If `false` the `u32` status code of the chain extension method call is going to be
+    /// ignored and assumed to be always successful. The output buffer in this case is going
+    /// to be queried and decoded into the chain extension method's output type.
+    ///
+    /// If `true` the returned `u32` status code `code` is queried and
+    /// `<Self::ErrorCode as ink_lang::FromStatusCode>::from_status_code(code)` is called.
+    /// The call to `from_status_code` returns `Result<(), Self::ErrorCode>`. If `Ok(())`
+    /// the output buffer is queried and decoded as described above.
+    /// If `Err(Self::ErrorCode)` the `Self::ErrorCode` is converted into `E` of `Result<T, E>`
+    /// if the chain extension method returns a `Result` type.
+    /// In case the chain extension method does _NOT_ return a `Result` type the call returns
+    /// `Result<T, Self::ErrorCode>` where `T` is the chain extension method's return type.
+    ///
+    /// The default for this flag is `true`.
+    handle_status: bool,
+    /// If `false` the proc. macro no longer tries to enforce that the returned type encoded
+    /// into the output buffer of the chain extension method call is of type `Result<T, E>`.
+    /// Also `E` is no longer required to implement `From<Self::ErrorCode>` in case `handle_status`
+    /// flag does not exist.
+    ///
+    /// The default for this flag is `true`.
+    returns_result: bool,
+}
+
+impl ChainExtensionMethod {
+    /// Returns the Rust attributes of the ink! chain extension method.
+    pub fn attrs(&self) -> Vec<syn::Attribute> {
+        let (_, attrs) = ir::partition_attributes(self.item.attrs.iter().cloned())
+            .expect(
+            "encountered unexpected invalid attributes for ink! chain extension method",
+        );
+        attrs
+    }
+
+    /// Returns the span of the ink! chain extension method.
+    pub fn span(&self) -> proc_macro2::Span {
+        self.item.span()
+    }
+
+    /// Returns the identifier of the ink! chain extension method.
+    pub fn ident(&self) -> &proc_macro2::Ident {
+        &self.item.sig.ident
+    }
+
+    /// Returns the method signature of the ink! chain extension method.
+    pub fn sig(&self) -> &syn::Signature {
+        &self.item.sig
+    }
+
+    /// Returns the unique ID of the chain extension method.
+    pub fn id(&self) -> ExtensionId {
+        self.id
+    }
+
+    /// Returns an iterator over the inputs of the chain extension method.
+    pub fn inputs(&self) -> ChainExtensionMethodInputs {
+        ChainExtensionMethodInputs {
+            iter: self.item.sig.inputs.iter(),
+        }
+    }
+
+    /// Returns `true` if the chain extension method was flagged with `#[ink(handle_status)]`.
+    pub fn handle_status(&self) -> bool {
+        self.handle_status
+    }
+
+    /// Returns `true` if the chain extension method was flagged with `#[ink(returns_result)]`.
+    pub fn returns_result(&self) -> bool {
+        self.returns_result
+    }
+}
+
+pub struct ChainExtensionMethodInputs<'a> {
+    iter: syn::punctuated::Iter<'a, syn::FnArg>,
+}
+
+impl<'a> Iterator for ChainExtensionMethodInputs<'a> {
+    type Item = &'a syn::PatType;
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        self.iter.size_hint()
+    }
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let item = self.iter.next()?;
+        match item {
+            syn::FnArg::Receiver(receiver) => {
+                panic!("encountered unexpected receiver in chain extension method input: {:?}", receiver)
+            }
+            syn::FnArg::Typed(pat_type) => Some(pat_type),
+        }
+    }
+}
+
+/// The unique ID of an ink! chain extension method.
+///
+/// # Note
+///
+/// The ink! attribute `#[ink(extension = N: u32)]` for chain extension methods.
+///
+/// Has a `func_id` extension ID to identify the associated chain extension method.
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct ExtensionId {
+    index: u32,
+}
+
+impl ExtensionId {
+    /// Creates a new chain extension method ID from the given `u32`.
+    pub fn from_u32(index: u32) -> Self {
+        Self { index }
+    }
+
+    /// Returns the underlying raw `u32` index.
+    pub fn into_u32(self) -> u32 {
+        self.index
+    }
+}
+
+impl TryFrom<syn::ItemTrait> for ChainExtension {
+    type Error = syn::Error;
+
+    fn try_from(item_trait: syn::ItemTrait) -> core::result::Result<Self, Self::Error> {
+        idents_lint::ensure_no_ink_identifiers(&item_trait)?;
+        Self::analyse_properties(&item_trait)?;
+        let (error_code, methods) = Self::analyse_items(&item_trait)?;
+        Ok(Self {
+            item: item_trait,
+            error_code,
+            methods,
+        })
+    }
+}
+
+impl ChainExtension {
+    /// Returns `Ok` if the trait matches all requirements for an ink! chain extension.
+    pub fn new(attr: TokenStream2, input: TokenStream2) -> Result<Self> {
+        if !attr.is_empty() {
+            return Err(format_err_spanned!(
+                attr,
+                "unexpected attribute input for ink! chain extension"
+            ))
+        }
+        let item_trait = syn::parse2::<syn::ItemTrait>(input)?;
+        ChainExtension::try_from(item_trait)
+    }
+
+    /// Analyses the properties of the ink! chain extension.
+    ///
+    /// # Errors
+    ///
+    /// - If the input trait has been defined as `unsafe`.
+    /// - If the input trait is an automatically implemented trait (`auto trait`).
+    /// - If the input trait is generic over some set of types.
+    /// - If the input trait's visibility is not public (`pub`).
+    /// - If the input trait has supertraits.
+    fn analyse_properties(item_trait: &syn::ItemTrait) -> Result<()> {
+        if let Some(unsafety) = &item_trait.unsafety {
+            return Err(format_err_spanned!(
+                unsafety,
+                "ink! chain extensions cannot be unsafe"
+            ))
+        }
+        if let Some(auto) = &item_trait.auto_token {
+            return Err(format_err_spanned!(
+                auto,
+                "ink! chain extensions cannot be automatically implemented traits"
+            ))
+        }
+        if !item_trait.generics.params.is_empty() {
+            return Err(format_err_spanned!(
+                item_trait.generics.params,
+                "ink! chain extensions must not be generic"
+            ))
+        }
+        if !matches!(item_trait.vis, syn::Visibility::Public(_)) {
+            return Err(format_err_spanned!(
+                item_trait.vis,
+                "ink! chain extensions must have public visibility"
+            ))
+        }
+        if !item_trait.supertraits.is_empty() {
+            return Err(format_err_spanned!(
+                item_trait.supertraits,
+                "ink! chain extensions with supertraits are not supported, yet"
+            ))
+        }
+        Ok(())
+    }
+
+    /// Checks if the associated trait item type is a proper chain extension error code.
+    ///
+    /// # Errors
+    ///
+    /// - If the associated type is not called `ErrorCode`.
+    /// - If the associated type is generic, has where bounds or has a default type.
+    /// - If there are multiple associated `ErrorCode` types.
+    fn analyse_error_code(
+        item_type: &syn::TraitItemType,
+        previous: &mut Option<syn::TraitItemType>,
+    ) -> Result<()> {
+        if item_type.ident != "ErrorCode" {
+            return Err(format_err_spanned!(
+                item_type.ident,
+                "chain extensions expect an associated type with name `ErrorCode` but found {}",
+                item_type.ident,
+            ))
+        }
+        if !item_type.generics.params.is_empty() {
+            return Err(format_err_spanned!(
+                item_type.generics,
+                "generic chain extension `ErrorCode` types are not supported",
+            ))
+        }
+        if !item_type.bounds.is_empty() {
+            return Err(format_err_spanned!(
+                item_type.bounds,
+                "bounded chain extension `ErrorCode` types are not supported",
+            ))
+        }
+        if item_type.default.is_none() {
+            return Err(format_err_spanned!(
+                item_type,
+                "expected a default type for the ink! chain extension ErrorCode",
+            ))
+        }
+        match previous {
+            Some(previous_error_code) => {
+                return Err(format_err_spanned!(
+                    item_type,
+                    "encountered duplicate `ErrorCode` associated types for the chain extension",
+                )).map_err(|err| err.into_combine(format_err_spanned!(
+                    previous_error_code,
+                    "first `ErrorCode` associated type here",
+                )))
+            }
+            None => {
+                *previous = Some(item_type.clone());
+            }
+        }
+        Ok(())
+    }
+
+    /// Returns `Ok` if all trait items respect the requirements for an ink! chain extension.
+    ///
+    /// # Errors
+    ///
+    /// - If the trait contains an unsupported trait item such as
+    ///     - associated constants (`const`)
+    ///     - associated types (`type`)
+    ///     - macros definitions or usages
+    ///     - unknown token sequences (verbatims)
+    ///     - methods with default implementations
+    /// - If the trait contains methods which do not respect the ink! trait definition requirements:
+    ///     - All trait methods must not have a `self` receiver.
+    ///     - All trait methods must have an `#[ink(extension = N: u32)]` attribute that is the ID that
+    ///       corresponds with the function ID of the respective chain extension call.
+    ///
+    /// # Note
+    ///
+    /// The input Rust trait item is going to be replaced with a concrete chain extension type definition
+    /// as a result of this proc. macro invocation.
+    fn analyse_items(
+        item_trait: &syn::ItemTrait,
+    ) -> Result<(syn::TraitItemType, Vec<ChainExtensionMethod>)> {
+        let mut methods = Vec::new();
+        let mut seen_ids = HashMap::new();
+        let mut error_code = None;
+        for trait_item in &item_trait.items {
+            match trait_item {
+                syn::TraitItem::Const(const_trait_item) => {
+                    return Err(format_err_spanned!(
+                        const_trait_item,
+                        "associated constants in ink! chain extensions are not supported, yet"
+                    ))
+                }
+                syn::TraitItem::Macro(macro_trait_item) => {
+                    return Err(format_err_spanned!(
+                        macro_trait_item,
+                        "macros in ink! chain extensions are not supported"
+                    ))
+                }
+                syn::TraitItem::Type(type_trait_item) => {
+                    Self::analyse_error_code(type_trait_item, &mut error_code)?;
+                }
+                syn::TraitItem::Verbatim(verbatim) => {
+                    return Err(format_err_spanned!(
+                        verbatim,
+                        "encountered unsupported item in ink! chain extensions"
+                    ))
+                }
+                syn::TraitItem::Method(method_trait_item) => {
+                    let method = Self::analyse_methods(method_trait_item)?;
+                    let method_id = method.id();
+                    if let Some(previous) = seen_ids.get(&method_id) {
+                        return Err(format_err!(
+                            method.span(),
+                            "encountered duplicate extension identifiers for the same chain extension",
+                        ).into_combine(format_err!(
+                            *previous,
+                            "previous duplicate extension identifier here",
+                        )))
+                    }
+                    seen_ids.insert(method_id, method.span());
+                    methods.push(method);
+                }
+                unknown => {
+                    return Err(format_err_spanned!(
+                        unknown,
+                        "encountered unknown or unsupported item in ink! chain extensions"
+                    ))
+                }
+            }
+        }
+        let error_code = match error_code {
+            Some(error_code) => error_code,
+            None => {
+                return Err(format_err_spanned!(
+                    item_trait,
+                    "missing ErrorCode associated type from ink! chain extension",
+                ))
+            }
+        };
+        Ok((error_code, methods))
+    }
+
+    /// Analyses a chain extension method.
+    ///
+    /// # Errors
+    ///
+    /// - If the method is missing the `#[ink(extension = N: u32)]` attribute.
+    /// - If the method has a `self` receiver.
+    /// - If the method declared as `unsafe`, `const` or `async`.
+    /// - If the method has some explicit API.
+    /// - If the method is variadic or has generic parameters.
+    fn analyse_methods(method: &syn::TraitItemMethod) -> Result<ChainExtensionMethod> {
+        if let Some(default_impl) = &method.default {
+            return Err(format_err_spanned!(
+                default_impl,
+                "ink! chain extension methods with default implementations are not supported"
+            ))
+        }
+        if let Some(constness) = &method.sig.constness {
+            return Err(format_err_spanned!(
+                constness,
+                "const ink! chain extension methods are not supported"
+            ))
+        }
+        if let Some(asyncness) = &method.sig.asyncness {
+            return Err(format_err_spanned!(
+                asyncness,
+                "async ink! chain extension methods are not supported"
+            ))
+        }
+        if let Some(unsafety) = &method.sig.unsafety {
+            return Err(format_err_spanned!(
+                unsafety,
+                "unsafe ink! chain extension methods are not supported"
+            ))
+        }
+        if let Some(abi) = &method.sig.abi {
+            return Err(format_err_spanned!(
+                abi,
+                "ink! chain extension methods with non default ABI are not supported"
+            ))
+        }
+        if let Some(variadic) = &method.sig.variadic {
+            return Err(format_err_spanned!(
+                variadic,
+                "variadic ink! chain extension methods are not supported"
+            ))
+        }
+        if !method.sig.generics.params.is_empty() {
+            return Err(format_err_spanned!(
+                method.sig.generics.params,
+                "generic ink! chain extension methods are not supported"
+            ))
+        }
+        match ir::first_ink_attribute(&method.attrs)?
+                .map(|attr| attr.first().kind().clone()) {
+            Some(ir::AttributeArg::Extension(extension)) => {
+                Self::analyse_chain_extension_method(method, extension)
+            }
+            Some(_unsupported) => {
+                Err(format_err_spanned!(
+                    method,
+                    "encountered unsupported ink! attribute for ink! chain extension method. expected #[ink(extension = N: u32)] attribute"
+                ))
+            }
+            None => {
+                Err(format_err_spanned!(
+                    method,
+                    "missing #[ink(extension = N: u32)] flag on ink! chain extension method"
+                ))
+            }
+        }
+    }
+
+    /// Analyses the properties of an ink! chain extension method.
+    ///
+    /// # Errors
+    ///
+    /// - If the chain extension method has a `self` receiver as first argument.
+    fn analyse_chain_extension_method(
+        item_method: &syn::TraitItemMethod,
+        extension: ExtensionId,
+    ) -> Result<ChainExtensionMethod> {
+        let (ink_attrs, _) = ir::sanitize_attributes(
+            item_method.span(),
+            item_method.attrs.clone(),
+            &ir::AttributeArgKind::Extension,
+            |c| {
+                !matches!(
+                    c,
+                    ir::AttributeArg::Extension(_)
+                        | ir::AttributeArg::HandleStatus(_)
+                        | ir::AttributeArg::ReturnsResult(_)
+                )
+            },
+        )?;
+        if let Some(receiver) = item_method.sig.receiver() {
+            return Err(format_err_spanned!(
+                receiver,
+                "ink! chain extension method must not have a `self` receiver",
+            ))
+        }
+        let result = ChainExtensionMethod {
+            id: extension,
+            item: item_method.clone(),
+            handle_status: ink_attrs.is_handle_status(),
+            returns_result: ink_attrs.is_returns_result(),
+        };
+        Ok(result)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    /// Checks if the token stream in `$chain_extension` results in the expected error message.
+    macro_rules! assert_ink_chain_extension_eq_err {
+        ( error: $err_str:literal, $($chain_extension:tt)* ) => {
+            assert_eq!(
+                <ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
+                    $( $chain_extension )*
+                })
+                .map_err(|err| err.to_string()),
+                Err(
+                    $err_str.to_string()
+                )
+            )
+        };
+    }
+
+    #[test]
+    fn unsafe_chain_extension_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extensions cannot be unsafe",
+            pub unsafe trait MyChainExtension {}
+        );
+    }
+
+    #[test]
+    fn auto_chain_extension_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extensions cannot be automatically implemented traits",
+            pub auto trait MyChainExtension {}
+        );
+    }
+
+    #[test]
+    fn non_pub_chain_extension_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extensions must have public visibility",
+            trait MyChainExtension {}
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extensions must have public visibility",
+            pub(crate) trait MyChainExtension {}
+        );
+    }
+
+    #[test]
+    fn generic_chain_extension_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extensions must not be generic",
+            pub trait MyChainExtension<T> {}
+        );
+    }
+
+    #[test]
+    fn chain_extension_with_supertraits_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extensions with supertraits are not supported, yet",
+            pub trait MyChainExtension: SuperChainExtension {}
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_const_item_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "associated constants in ink! chain extensions are not supported, yet",
+            pub trait MyChainExtension {
+                const T: i32;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_invalid_associated_type_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "chain extensions expect an associated type with name `ErrorCode` but found Type",
+            pub trait MyChainExtension {
+                type Type;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_with_invalid_error_code() {
+        assert_ink_chain_extension_eq_err!(
+            error: "chain extensions expect an associated type with name `ErrorCode` but found IncorrectName",
+            pub trait MyChainExtension {
+                type IncorrectName = ();
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "generic chain extension `ErrorCode` types are not supported",
+            pub trait MyChainExtension {
+                type ErrorCode<T> = ();
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "bounded chain extension `ErrorCode` types are not supported",
+            pub trait MyChainExtension {
+                type ErrorCode: Copy = ();
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "expected a default type for the ink! chain extension ErrorCode",
+            pub trait MyChainExtension {
+                type ErrorCode;
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "encountered duplicate `ErrorCode` associated types for the chain extension",
+            pub trait MyChainExtension {
+                type ErrorCode = ();
+                type ErrorCode = ();
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_macro_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "macros in ink! chain extensions are not supported",
+            pub trait MyChainExtension {
+                my_macro_call!();
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_non_flagged_method_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
+            pub trait MyChainExtension {
+                fn non_flagged_1(&self);
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
+            pub trait MyChainExtension {
+                fn non_flagged_2(&mut self);
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "missing #[ink(extension = N: u32)] flag on ink! chain extension method",
+            pub trait MyChainExtension {
+                fn non_flagged_3() -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_default_implemented_methods_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extension methods with default implementations are not supported",
+            pub trait MyChainExtension {
+                #[ink(constructor)]
+                fn default_implemented() -> Self {}
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_const_methods_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "const ink! chain extension methods are not supported",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                const fn const_constructor() -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_async_methods_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "async ink! chain extension methods are not supported",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                async fn const_constructor() -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_unsafe_methods_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "unsafe ink! chain extension methods are not supported",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                unsafe fn const_constructor() -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_methods_using_explicit_abi_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extension methods with non default ABI are not supported",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                extern fn const_constructor() -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_variadic_methods_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "variadic ink! chain extension methods are not supported",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                fn const_constructor(...) -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_generic_methods_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "generic ink! chain extension methods are not supported",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                fn const_constructor<T>() -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_method_with_unsupported_ink_attribute_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "\
+                encountered unsupported ink! attribute for ink! chain extension method. \
+                expected #[ink(extension = N: u32)] attribute",
+            pub trait MyChainExtension {
+                #[ink(message)]
+                fn unsupported_ink_attribute(&self);
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "unknown ink! attribute (path)",
+            pub trait MyChainExtension {
+                #[ink(unknown)]
+                fn unknown_ink_attribute(&self);
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_method_with_invalid_marker() {
+        assert_ink_chain_extension_eq_err!(
+            error: "could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
+            pub trait MyChainExtension {
+                #[ink(extension = -1)]
+                fn has_self_receiver();
+            }
+        );
+        let too_large = (u32::MAX as u64) + 1;
+        assert_ink_chain_extension_eq_err!(
+            error: "could not parse `N` in `#[ink(extension = N)]` into a `u32` integer",
+            pub trait MyChainExtension {
+                #[ink(extension = #too_large)]
+                fn has_self_receiver();
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "expecteded `u32` integer type for `N` in #[ink(extension = N)]",
+            pub trait MyChainExtension {
+                #[ink(extension = "Hello, World!")]
+                fn has_self_receiver();
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "encountered #[ink(extension)] that is missing its N parameter. Did you mean #[ink(extension = N: u32)] ?",
+            pub trait MyChainExtension {
+                #[ink(extension)]
+                fn has_self_receiver();
+            }
+        );
+
+        assert_ink_chain_extension_eq_err!(
+            error: "encountered duplicate ink! attribute",
+            pub trait MyChainExtension {
+                #[ink(extension = 42)]
+                #[ink(extension = 42)]
+                fn duplicate_attributes() -> Self;
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "encountered ink! attribute arguments with equal kinds",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                #[ink(extension = 2)]
+                fn duplicate_attributes() -> Self;
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "encountered conflicting ink! attribute argument",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                #[ink(message)]
+                fn conflicting_attributes() -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_containing_method_with_self_receiver_is_denied() {
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extension method must not have a `self` receiver",
+            pub trait MyChainExtension {
+                type ErrorCode = ();
+
+                #[ink(extension = 1)]
+                fn has_self_receiver(&self) -> Self;
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extension method must not have a `self` receiver",
+            pub trait MyChainExtension {
+                type ErrorCode = ();
+
+                #[ink(extension = 1)]
+                fn has_self_receiver(&mut self) -> Self;
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extension method must not have a `self` receiver",
+            pub trait MyChainExtension {
+                type ErrorCode = ();
+
+                #[ink(extension = 1)]
+                fn has_self_receiver(self) -> Self;
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extension method must not have a `self` receiver",
+            pub trait MyChainExtension {
+                type ErrorCode = ();
+
+                #[ink(extension = 1)]
+                fn has_self_receiver(self: &Self) -> Self;
+            }
+        );
+        assert_ink_chain_extension_eq_err!(
+            error: "ink! chain extension method must not have a `self` receiver",
+            pub trait MyChainExtension {
+                type ErrorCode = ();
+
+                #[ink(extension = 1)]
+                fn has_self_receiver(self: Self) -> Self;
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_with_overlapping_extension_ids() {
+        assert_ink_chain_extension_eq_err!(
+            error: "encountered duplicate extension identifiers for the same chain extension",
+            pub trait MyChainExtension {
+                #[ink(extension = 1)]
+                fn same_id_1();
+                #[ink(extension = 1)]
+                fn same_id_2();
+            }
+        );
+    }
+
+    #[test]
+    fn chain_extension_is_ok() {
+        let chain_extension = <ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
+                pub trait MyChainExtension {
+                    type ErrorCode = ();
+
+                    #[ink(extension = 1)]
+                    fn extension_1();
+                    #[ink(extension = 2)]
+                    fn extension_2(input: i32);
+                    #[ink(extension = 3)]
+                    fn extension_3() -> i32;
+                    #[ink(extension = 4)]
+                    fn extension_4(input: i32) -> i32;
+                    #[ink(extension = 5)]
+                    fn extension_5(in1: i8, in2: i16, in3: i32, in4: i64) -> (u8, u16, u32, u64);
+                }
+            }).unwrap();
+        assert_eq!(chain_extension.methods.len(), 5);
+        for (actual, expected) in chain_extension
+            .methods
+            .iter()
+            .map(|method| method.id())
+            .zip(1..=5u32)
+        {
+            assert_eq!(actual.index, expected);
+        }
+        for (actual, expected) in chain_extension
+            .methods
+            .iter()
+            .map(|method| method.ident().to_string())
+            .zip(
+                [
+                    "extension_1",
+                    "extension_2",
+                    "extension_3",
+                    "extension_4",
+                    "extension_5",
+                ]
+                .iter()
+                .map(ToString::to_string),
+            )
+        {
+            assert_eq!(actual, expected);
+        }
+    }
+
+    #[test]
+    fn chain_extension_with_params_is_ok() {
+        let chain_extension =
+            <ChainExtension as TryFrom<syn::ItemTrait>>::try_from(syn::parse_quote! {
+                pub trait MyChainExtension {
+                    type ErrorCode = ();
+
+                    #[ink(extension = 1, handle_status = false)]
+                    fn extension_a();
+                    #[ink(extension = 2, returns_result = false)]
+                    fn extension_b();
+                    #[ink(extension = 3, handle_status = false, returns_result = false)]
+                    fn extension_c();
+
+                    #[ink(extension = 4)]
+                    #[ink(handle_status = false)]
+                    fn extension_d();
+                    #[ink(extension = 5)]
+                    #[ink(returns_result = false)]
+                    fn extension_e();
+                    #[ink(extension = 6)]
+                    #[ink(handle_status = false)]
+                    #[ink(returns_result = false)]
+                    fn extension_f();
+                }
+            })
+            .unwrap();
+        let expected_methods = 6;
+        assert_eq!(chain_extension.methods.len(), expected_methods);
+        for (actual, expected) in chain_extension
+            .methods
+            .iter()
+            .map(|method| method.id())
+            .zip(1..=expected_methods as u32)
+        {
+            assert_eq!(actual.index, expected);
+        }
+        for (actual, expected) in chain_extension
+            .methods
+            .iter()
+            .map(|method| method.ident().to_string())
+            .zip(
+                [
+                    "extension_a",
+                    "extension_b",
+                    "extension_c",
+                    "extension_d",
+                    "extension_e",
+                    "extension_f",
+                ]
+                .iter()
+                .map(ToString::to_string),
+            )
+        {
+            assert_eq!(actual, expected);
+        }
+    }
+}
diff --git a/crates/lang/ir/src/ir/config.rs b/crates/lang/ir/src/ir/config.rs
index b5c981791b..d392d94669 100644
--- a/crates/lang/ir/src/ir/config.rs
+++ b/crates/lang/ir/src/ir/config.rs
@@ -37,7 +37,7 @@ pub struct Config {
     /// This must be a type that implements `ink_env::Environment` and can
     /// be used to change the underlying environmental types of an ink! smart
     /// contract.
-    env_types: Option<Environment>,
+    env: Option<Environment>,
 }
 
 /// Return an error to notify about duplicate ink! config arguments.
@@ -64,7 +64,7 @@ impl TryFrom<ast::AttributeArgs> for Config {
     fn try_from(args: ast::AttributeArgs) -> Result<Self, Self::Error> {
         let mut dynamic_storage_allocator: Option<(bool, ast::MetaNameValue)> = None;
         let mut as_dependency: Option<(bool, ast::MetaNameValue)> = None;
-        let mut env_types: Option<(Environment, ast::MetaNameValue)> = None;
+        let mut env: Option<(Environment, ast::MetaNameValue)> = None;
         for arg in args.into_iter() {
             if arg.name.is_ident("dynamic_storage_allocator") {
                 if let Some((_, ast)) = dynamic_storage_allocator {
@@ -94,16 +94,16 @@ impl TryFrom<ast::AttributeArgs> for Config {
                         "expected a bool literal for `compile_as_dependency` ink! config argument",
                     ))
                 }
-            } else if arg.name.is_ident("env_types") {
-                if let Some((_, ast)) = env_types {
-                    return Err(duplicate_config_err(ast, arg, "env_types"))
+            } else if arg.name.is_ident("env") {
+                if let Some((_, ast)) = env {
+                    return Err(duplicate_config_err(ast, arg, "env"))
                 }
                 if let ast::PathOrLit::Path(path) = &arg.value {
-                    env_types = Some((Environment { path: path.clone() }, arg))
+                    env = Some((Environment { path: path.clone() }, arg))
                 } else {
                     return Err(format_err_spanned!(
                         arg,
-                        "expected a path for `env_types` ink! config argument",
+                        "expected a path for `env` ink! config argument",
                     ))
                 }
             } else {
@@ -116,7 +116,7 @@ impl TryFrom<ast::AttributeArgs> for Config {
         Ok(Config {
             dynamic_storage_allocator: dynamic_storage_allocator.map(|(value, _)| value),
             as_dependency: as_dependency.map(|(value, _)| value),
-            env_types: env_types.map(|(value, _)| value),
+            env: env.map(|(value, _)| value),
         })
     }
 }
@@ -125,10 +125,10 @@ impl Config {
     /// Returns the environmental types definition if specified.
     /// Otherwise returns the default environmental types definition provided
     /// by ink!.
-    pub fn env_types(&self) -> syn::Path {
-        self.env_types
+    pub fn env(&self) -> syn::Path {
+        self.env
             .as_ref()
-            .map(|env_types| &env_types.path)
+            .map(|env| &env.path)
             .cloned()
             .unwrap_or(Environment::default().path)
     }
@@ -197,7 +197,7 @@ mod tests {
             Ok(Config {
                 dynamic_storage_allocator: Some(true),
                 as_dependency: None,
-                env_types: None,
+                env: None,
             }),
         )
     }
@@ -219,7 +219,7 @@ mod tests {
             Ok(Config {
                 dynamic_storage_allocator: None,
                 as_dependency: Some(false),
-                env_types: None,
+                env: None,
             }),
         )
     }
@@ -235,15 +235,15 @@ mod tests {
     }
 
     #[test]
-    fn env_types_works() {
+    fn env_works() {
         assert_try_from(
             syn::parse_quote! {
-                env_types = ::my::env::Types
+                env = ::my::env::Types
             },
             Ok(Config {
                 dynamic_storage_allocator: None,
                 as_dependency: None,
-                env_types: Some(Environment {
+                env: Some(Environment {
                     path: syn::parse_quote! { ::my::env::Types },
                 }),
             }),
@@ -251,10 +251,10 @@ mod tests {
     }
 
     #[test]
-    fn env_types_invalid_value_fails() {
+    fn env_invalid_value_fails() {
         assert_try_from(
-            syn::parse_quote! { env_types = "invalid" },
-            Err("expected a path for `env_types` ink! config argument"),
+            syn::parse_quote! { env = "invalid" },
+            Err("expected a path for `env` ink! config argument"),
         );
     }
 
@@ -270,10 +270,10 @@ mod tests {
     fn duplicate_args_fails() {
         assert_try_from(
             syn::parse_quote! {
-                env_types = ::my::env::Types,
-                env_types = ::my::other::env::Types,
+                env = ::my::env::Types,
+                env = ::my::other::env::Types,
             },
-            Err("encountered duplicate ink! `env_types` config argument"),
+            Err("encountered duplicate ink! `env` config argument"),
         );
     }
 }
diff --git a/crates/lang/ir/src/ir/item/event.rs b/crates/lang/ir/src/ir/item/event.rs
index 2795156ca0..32dedbdc74 100644
--- a/crates/lang/ir/src/ir/item/event.rs
+++ b/crates/lang/ir/src/ir/item/event.rs
@@ -73,7 +73,7 @@ impl Event {
         // an ink! event or an invalid ink! attribute.
         let attr = ir::first_ink_attribute(&item_struct.attrs)?
             .expect("missing expected ink! attribute for struct");
-        Ok(matches!(attr.first().kind(), ir::AttributeArgKind::Event))
+        Ok(matches!(attr.first().kind(), ir::AttributeArg::Event))
     }
 }
 
@@ -86,12 +86,7 @@ impl TryFrom<syn::ItemStruct> for Event {
             struct_span,
             item_struct.attrs,
             &ir::AttributeArgKind::Event,
-            |kind| {
-                !matches!(
-                    kind,
-                    ir::AttributeArgKind::Event | ir::AttributeArgKind::Anonymous
-                )
-            },
+            |kind| !matches!(kind, ir::AttributeArg::Event | ir::AttributeArg::Anonymous),
         )?;
         if !item_struct.generics.params.is_empty() {
             return Err(format_err_spanned!(
@@ -110,14 +105,14 @@ impl TryFrom<syn::ItemStruct> for Event {
                 ir::InkAttribute::from_expanded(ink_attrs).map_err(|err| {
                     err.into_combine(format_err!(field_span, "at this invocation",))
                 })?;
-            if !matches!(normalized.first().kind(), ir::AttributeArgKind::Topic) {
+            if !matches!(normalized.first().kind(), ir::AttributeArg::Topic) {
                 return Err(format_err!(
                     field_span,
                     "first optional ink! attribute of an event field must be #[ink(topic)]",
                 ))
             }
             for arg in normalized.args() {
-                if !matches!(arg.kind(), ir::AttributeArgKind::Topic) {
+                if !matches!(arg.kind(), ir::AttributeArg::Topic) {
                     return Err(format_err!(
                         arg.span(),
                         "encountered conflicting ink! attribute for event field",
@@ -214,9 +209,7 @@ impl<'a> Iterator for EventFieldsIter<'a> {
             Some(field) => {
                 let is_topic = ir::first_ink_attribute(&field.attrs)
                     .unwrap_or_default()
-                    .map(|attr| {
-                        matches!(attr.first().kind(), ir::AttributeArgKind::Topic)
-                    })
+                    .map(|attr| matches!(attr.first().kind(), ir::AttributeArg::Topic))
                     .unwrap_or_default();
                 Some(EventField { is_topic, field })
             }
diff --git a/crates/lang/ir/src/ir/item/mod.rs b/crates/lang/ir/src/ir/item/mod.rs
index 246b22f9b6..c553cf1afa 100644
--- a/crates/lang/ir/src/ir/item/mod.rs
+++ b/crates/lang/ir/src/ir/item/mod.rs
@@ -68,12 +68,12 @@ impl TryFrom<syn::Item> for Item {
                 let attr = ir::first_ink_attribute(&item_struct.attrs)?
                     .expect("missing expected ink! attribute for struct");
                 match attr.first().kind() {
-                    ir::AttributeArgKind::Storage => {
+                    ir::AttributeArg::Storage => {
                         <ir::Storage as TryFrom<_>>::try_from(item_struct)
                             .map(Into::into)
                             .map(Self::Ink)
                     }
-                    ir::AttributeArgKind::Event => {
+                    ir::AttributeArg::Event => {
                         <ir::Event as TryFrom<_>>::try_from(item_struct)
                             .map(Into::into)
                             .map(Self::Ink)
diff --git a/crates/lang/ir/src/ir/item/storage.rs b/crates/lang/ir/src/ir/item/storage.rs
index a56c285b2f..fe72c5fe79 100644
--- a/crates/lang/ir/src/ir/item/storage.rs
+++ b/crates/lang/ir/src/ir/item/storage.rs
@@ -73,7 +73,7 @@ impl Storage {
         // an ink! event or an invalid ink! attribute.
         let attr = ir::first_ink_attribute(&item_struct.attrs)?
             .expect("missing expected ink! attribute for struct");
-        Ok(matches!(attr.first().kind(), ir::AttributeArgKind::Storage))
+        Ok(matches!(attr.first().kind(), ir::AttributeArg::Storage))
     }
 }
 
@@ -86,7 +86,7 @@ impl TryFrom<syn::ItemStruct> for Storage {
             struct_span,
             item_struct.attrs,
             &ir::AttributeArgKind::Storage,
-            |kind| !matches!(kind, ir::AttributeArgKind::Storage),
+            |kind| !matches!(kind, ir::AttributeArg::Storage),
         )?;
         if !item_struct.generics.params.is_empty() {
             return Err(format_err_spanned!(
diff --git a/crates/lang/ir/src/ir/item_impl/constructor.rs b/crates/lang/ir/src/ir/item_impl/constructor.rs
index 5ab650350e..de83b48a04 100644
--- a/crates/lang/ir/src/ir/item_impl/constructor.rs
+++ b/crates/lang/ir/src/ir/item_impl/constructor.rs
@@ -156,7 +156,7 @@ impl Constructor {
             |kind| {
                 !matches!(
                     kind,
-                    ir::AttributeArgKind::Constructor | ir::AttributeArgKind::Selector(_)
+                    ir::AttributeArg::Constructor | ir::AttributeArg::Selector(_)
                 )
             },
         )
diff --git a/crates/lang/ir/src/ir/item_impl/impl_item.rs b/crates/lang/ir/src/ir/item_impl/impl_item.rs
index c0cbd9868b..dd55141489 100644
--- a/crates/lang/ir/src/ir/item_impl/impl_item.rs
+++ b/crates/lang/ir/src/ir/item_impl/impl_item.rs
@@ -69,12 +69,12 @@ impl TryFrom<syn::ImplItem> for ImplItem {
                 let attr = ir::first_ink_attribute(&method_item.attrs)?
                     .expect("missing expected ink! attribute for struct");
                 match attr.first().kind() {
-                    ir::AttributeArgKind::Message => {
+                    ir::AttributeArg::Message => {
                         <Message as TryFrom<_>>::try_from(method_item)
                             .map(Into::into)
                             .map(Self::Message)
                     }
-                    ir::AttributeArgKind::Constructor => {
+                    ir::AttributeArg::Constructor => {
                         <Constructor as TryFrom<_>>::try_from(method_item)
                             .map(Into::into)
                             .map(Self::Constructor)
diff --git a/crates/lang/ir/src/ir/item_impl/message.rs b/crates/lang/ir/src/ir/item_impl/message.rs
index 0312bd8935..13d9f48d85 100644
--- a/crates/lang/ir/src/ir/item_impl/message.rs
+++ b/crates/lang/ir/src/ir/item_impl/message.rs
@@ -183,9 +183,9 @@ impl Message {
             |kind| {
                 !matches!(
                     kind,
-                    ir::AttributeArgKind::Message
-                        | ir::AttributeArgKind::Payable
-                        | ir::AttributeArgKind::Selector(_)
+                    ir::AttributeArg::Message
+                        | ir::AttributeArg::Payable
+                        | ir::AttributeArg::Selector(_)
                 )
             },
         )
diff --git a/crates/lang/ir/src/ir/item_impl/mod.rs b/crates/lang/ir/src/ir/item_impl/mod.rs
index 1a8b6c05fe..6fb402ba46 100644
--- a/crates/lang/ir/src/ir/item_impl/mod.rs
+++ b/crates/lang/ir/src/ir/item_impl/mod.rs
@@ -197,8 +197,9 @@ impl ItemImpl {
                     let attr = ir::first_ink_attribute(&method_item.attrs)?
                         .expect("missing expected ink! attribute for struct");
                     match attr.first().kind() {
-                        ir::AttributeArgKind::Constructor
-                        | ir::AttributeArgKind::Message => return Ok(true),
+                        ir::AttributeArg::Constructor | ir::AttributeArg::Message => {
+                            return Ok(true)
+                        }
                         _ => continue 'repeat,
                     }
                 }
@@ -300,8 +301,7 @@ impl TryFrom<syn::ItemImpl> for ItemImpl {
             normalized.ensure_no_conflicts(|arg| {
                 !matches!(
                     arg.kind(),
-                    ir::AttributeArgKind::Implementation
-                        | ir::AttributeArgKind::Namespace(_)
+                    ir::AttributeArg::Implementation | ir::AttributeArg::Namespace(_)
                 )
             })?;
             namespace = normalized.namespace();
diff --git a/crates/lang/ir/src/ir/mod.rs b/crates/lang/ir/src/ir/mod.rs
index a31af794a9..255716a694 100644
--- a/crates/lang/ir/src/ir/mod.rs
+++ b/crates/lang/ir/src/ir/mod.rs
@@ -15,6 +15,7 @@
 #![allow(dead_code)]
 
 mod attrs;
+mod chain_extension;
 mod config;
 mod contract;
 mod idents_lint;
@@ -36,10 +37,16 @@ use self::attrs::{
     sanitize_attributes,
     AttributeArg,
     AttributeArgKind,
+    AttributeFrag,
     InkAttribute,
 };
 pub use self::{
     attrs::Namespace,
+    chain_extension::{
+        ChainExtension,
+        ChainExtensionMethod,
+        ExtensionId,
+    },
     config::Config,
     contract::Contract,
     ink_test::InkTest,
diff --git a/crates/lang/ir/src/ir/trait_def.rs b/crates/lang/ir/src/ir/trait_def.rs
index ae949c55da..5d5fd23e9c 100644
--- a/crates/lang/ir/src/ir/trait_def.rs
+++ b/crates/lang/ir/src/ir/trait_def.rs
@@ -148,12 +148,12 @@ impl<'a> Iterator for IterInkTraitItems<'a> {
                         .kind()
                         .clone();
                     match first_attr {
-                        ir::AttributeArgKind::Constructor => {
+                        ir::AttributeArg::Constructor => {
                             return Some(InkTraitItem::Constructor(InkTraitConstructor {
                                 item: method,
                             }))
                         }
-                        ir::AttributeArgKind::Message => {
+                        ir::AttributeArg::Message => {
                             return Some(InkTraitItem::Message(InkTraitMessage {
                                 item: method,
                             }))
@@ -462,10 +462,10 @@ impl InkTrait {
         match ir::first_ink_attribute(&method.attrs) {
             Ok(Some(ink_attr)) => {
                 match ink_attr.first().kind() {
-                    ir::AttributeArgKind::Message => {
+                    ir::AttributeArg::Message => {
                         Self::analyse_message(method)?;
                     }
-                    ir::AttributeArgKind::Constructor => {
+                    ir::AttributeArg::Constructor => {
                         Self::analyse_constructor(method)?;
                     }
                     _unsupported => {
@@ -498,7 +498,7 @@ impl InkTrait {
             constructor.span(),
             constructor.attrs.clone(),
             &ir::AttributeArgKind::Constructor,
-            |c| !matches!(c, ir::AttributeArgKind::Constructor),
+            |c| !matches!(c, ir::AttributeArg::Constructor),
         )?;
         if let Some(receiver) = constructor.sig.receiver() {
             return Err(format_err_spanned!(
@@ -545,7 +545,7 @@ impl InkTrait {
             message.span(),
             message.attrs.clone(),
             &ir::AttributeArgKind::Message,
-            |c| !matches!(c, ir::AttributeArgKind::Message),
+            |c| !matches!(c, ir::AttributeArg::Message),
         )?;
         match message.sig.receiver() {
             None | Some(syn::FnArg::Typed(_)) => {
diff --git a/crates/lang/ir/src/lib.rs b/crates/lang/ir/src/lib.rs
index 7411e08b71..e051fb76f1 100644
--- a/crates/lang/ir/src/lib.rs
+++ b/crates/lang/ir/src/lib.rs
@@ -37,10 +37,13 @@ pub use self::ir::{
     Callable,
     CallableKind,
     CallableWithSelector,
+    ChainExtension,
+    ChainExtensionMethod,
     Config,
     Constructor,
     Contract,
     Event,
+    ExtensionId,
     ImplItem,
     InkItem,
     InkTest,
diff --git a/crates/lang/macro/src/chain_extension.rs b/crates/lang/macro/src/chain_extension.rs
new file mode 100644
index 0000000000..7e030ae9b2
--- /dev/null
+++ b/crates/lang/macro/src/chain_extension.rs
@@ -0,0 +1,29 @@
+// Copyright 2018-2021 Parity Technologies (UK) Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use ink_lang_codegen::generate_code;
+use proc_macro2::TokenStream as TokenStream2;
+use syn::Result;
+
+pub fn generate(attr: TokenStream2, input: TokenStream2) -> TokenStream2 {
+    match generate_or_err(attr, input) {
+        Ok(tokens) => tokens,
+        Err(err) => err.to_compile_error(),
+    }
+}
+
+pub fn generate_or_err(attr: TokenStream2, input: TokenStream2) -> Result<TokenStream2> {
+    let chain_extension = ink_lang_ir::ChainExtension::new(attr, input)?;
+    Ok(generate_code(&chain_extension))
+}
diff --git a/crates/lang/macro/src/lib.rs b/crates/lang/macro/src/lib.rs
index bb0bd8baaa..737156888b 100644
--- a/crates/lang/macro/src/lib.rs
+++ b/crates/lang/macro/src/lib.rs
@@ -14,6 +14,7 @@
 
 extern crate proc_macro;
 
+mod chain_extension;
 mod contract;
 mod ink_test;
 mod trait_def;
@@ -151,13 +152,14 @@ use proc_macro::TokenStream;
 ///         type Hash = [u8; 32];
 ///         type Timestamp = u64;
 ///         type BlockNumber = u32;
+///         type ChainExtension = ::ink_env::NoChainExtension;
 ///     }
 ///     ```
 ///     A user might implement their ink! smart contract using the above custom `Environment`
 ///     implementation as demonstrated below:
 ///     ```
 ///     # use ink_lang as ink;
-///     #[ink::contract(env_types = MyEnvironment)]
+///     #[ink::contract(env = MyEnvironment)]
 ///     mod my_contract {
 ///         # pub struct MyEnvironment;
 ///         #
@@ -168,6 +170,7 @@ use proc_macro::TokenStream;
 ///         #     type Hash = [u8; 32];
 ///         #     type Timestamp = u64;
 ///         #     type BlockNumber = u32;
+///         #     type ChainExtension = ::ink_env::NoChainExtension;
 ///         # }
 ///         #
 ///         # #[ink(storage)]
@@ -632,5 +635,439 @@ pub fn test(attr: TokenStream, item: TokenStream) -> TokenStream {
     ink_test::generate(attr.into(), item.into()).into()
 }
 
+/// Defines the interface for a chain extension.
+///
+/// # Structure
+///
+/// The interface consists of an error code that indicates lightweight errors
+/// as well as the definition of some chain extension methods.
+///
+/// The overall structure follows that of a simple Rust trait definition.
+/// The error code is defined as an associated type definition of the trait definition.
+/// The methods are defined as associated trait methods without implementation.
+///
+/// Chain extension methods must not have a `self` receiver such as `&self` or `&mut self`
+/// and must have inputs and output that implement SCALE codec. Their return value follows
+/// specific rules that can be altered using the `handle_status` and `returns_result` attributes
+/// which are described in more detail below.
+///
+/// # Usage
+///
+/// Usually the chain extension definition using this proc. macro is provided
+/// by the author of the chain extension in a separate crate.
+/// ink! smart contracts using this chain extension simply depend on this crate
+/// and use its associated environment definition in order to make use of
+/// the methods provided by the chain extension.
+///
+/// # Attributes
+///
+/// There are three different attributes with which the chain extension methods
+/// can be flagged:
+///
+/// | Attribute | Required | Default Value | Description |
+/// |:----------|:--------:|:--------------|:-----------:|
+/// | `ink(extension = N: u32)` | Yes | - | Determines the unique function ID of the chain extension method. |
+/// | `ink(handle_status = flag: bool)` | Optional | `true` | Assumes that the returned status code of the chain extension method always indicates success and therefore always loads and decodes the output buffer of the call. |
+/// | `ink(returns_result = flag: bool)` | Optional | `true` | By default chain extension methods are assumed to return a `Result<T, E>` in the output buffer. Using `returns_result = false` this check is disabled and the chain extension method may return any other type. |
+///
+/// As with all ink! attributes multiple of them can either appear in a contiguous list:
+/// ```
+/// # type Access = i32;
+/// # use ink_lang as ink;
+/// # #[ink::chain_extension]
+/// # pub trait MyChainExtension {
+/// #     type ErrorCode = i32;
+/// #[ink(extension = 5, handle_status = false, returns_result = false)]
+/// fn key_access_for_account(key: &[u8], account: &[u8]) -> Access;
+/// # }
+/// ```
+/// ... or as multiple stand alone ink! attributes applied to the same item:
+/// ```
+/// # type Access = i32;
+/// # use ink_lang as ink;
+/// # #[ink::chain_extension]
+/// # pub trait MyChainExtension {
+/// #     type ErrorCode = i32;
+/// #[ink(extension = 5)]
+/// #[ink(handle_status = false)]
+/// #[ink(returns_result = false)]
+/// fn key_access_for_account(key: &[u8], account: &[u8]) -> Access;
+/// # }
+/// ```
+///
+/// ## Details: `handle_status`
+///
+/// Default value: `true`
+///
+/// By default all chain extension methods return a `Result<T, E>` where `E: From<Self::ErrorCode>`.
+/// The `Self::ErrorCode` represents the error code of the chain extension.
+/// This means that a smart contract calling such a chain extension method first queries the returned
+/// status code of the chain extension method and only loads and decodes the output if the returned
+/// status code indicates a successful call.
+/// This design was chosen as it is more efficient when no output besides the error
+/// code is required for a chain extension call. When designing a chain extension try to utilize the
+/// error code to return errors and only use the output buffer for information that does not fit in
+/// a single `u32` value.
+///
+/// A chain extension method that is flagged with `handle_status = false` assumes that the returned error code
+/// will always indicate success. Therefore it will always load and decode the output buffer and loses
+/// the `E: From<Self::ErrorCode` constraint for the call.
+///
+/// ## Details: `returns_result`
+///
+/// Default value: `true`
+///
+/// By default chain extension methods are assumed to return a value of type `Result<T, E>` through the
+/// output buffer. Using `returns_result = false` this check is disabled and the chain extension method may return
+/// any other type.
+///
+/// Note that if a chain extension method is attributed with `returns_result = false`
+/// and with `handle_status = true` it will still return a value of type `Result<T, Self::ErrorCode>`.
+///
+/// ## Usage: `handle_status` + `returns_result`
+///
+/// Use both `handle_status = false` and `returns_result = false` for the same chain extension method
+/// if a call to it may never fail and never returns a `Result` type.
+///
+/// # Combinations
+///
+/// Due to the possibility to flag a chain extension method with `handle_status` and `returns_result`
+/// there are 4 different cases with slightly varying semantics:
+///
+/// | `handle_status` | `returns_result` | Effects |
+/// |:---------------:|:----------------:|:--------|
+/// |`true` |`true` | The chain extension method is required to return a value of type `Result<T, E>` where `E: From<Self::ErrorCode>`. A call will always check if the returned status code indicates success and only then will load and decode the value in the output buffer. |
+/// |`true` |`false`| The chain extension method may return any non-`Result` type. A call will always check if the returned status code indicates success and only then will load and decode the value in the output buffer. The actual return type of the chain extension method is still `Result<T, Self::ErrorCode>` when the chain extension method was defined to return a value of type `T`. |
+/// |`false`|`true` | The chain extension method is required to return a value of type `Result<T, E>`. A call will always assume that the returned status code indicates success and therefore always load and decode the output buffer directly. |
+/// |`false`|`false`| The chain extension method may return any non-`Result` type. A call will always assume that the returned status code indicates success and therefore always load and decode the output buffer directly. |
+///
+/// # Error Code
+///
+/// Every chain extension defines exactly one `ErrorCode` using the following syntax:
+///
+/// ```
+/// use ink_lang as ink;
+///
+/// #[ink::chain_extension]
+/// pub trait MyChainExtension {
+///     type ErrorCode = MyErrorCode;
+///
+///     // more definitions ...
+/// }
+/// ```
+///
+/// The defined `ErrorCode` must implement `FromStatusCode` which should be implemented as a
+/// more or less trivial conversion from the `u32` status code to a `Result<(), Self::ErrorCode>`.
+/// The `Ok(())` value indicates that the call to the chain extension method was successful.
+///
+/// By convention an error code of `0` represents success.
+/// However, chain extension authors may use whatever suits their needs.
+///
+/// # Example: Definition
+///
+/// In the below example a chain extension is defined that allows its users to read and write
+/// from and to the runtime storage using access privileges:
+///
+/// ```
+/// use ink_lang as ink;
+///
+/// /// Custom chain extension to read to and write from the runtime.
+/// #[ink::chain_extension]
+/// pub trait RuntimeReadWrite {
+///     type ErrorCode = ReadWriteErrorCode;
+///
+///     /// Reads from runtime storage.
+///     ///
+///     /// # Note
+///     ///
+///     /// Actually returns a value of type `Result<Vec<u8>, Self::ErrorCode>`.
+///     #[ink(extension = 1, returns_result = false)]
+///     fn read(key: &[u8]) -> Vec<u8>;
+///
+///     /// Reads from runtime storage.
+///     ///
+///     /// Returns the number of bytes read and up to 32 bytes of the
+///     /// read value. Unused bytes in the output are set to 0.
+///     ///
+///     /// # Errors
+///     ///
+///     /// If the runtime storage cell stores a value that requires more than
+///     /// 32 bytes.
+///     ///
+///     /// # Note
+///     ///
+///     /// This requires `ReadWriteError` to implement `From<ReadWriteErrorCode>`
+///     /// and may potentially return any `Self::ErrorCode` through its return value.
+///     #[ink(extension = 2)]
+///     fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>;
+///
+///     /// Writes into runtime storage.
+///     ///
+///     /// # Note
+///     ///
+///     /// Actually returns a value of type `Result<(), Self::ErrorCode>`.
+///     #[ink(extension = 3, returns_result = false)]
+///     fn write(key: &[u8], value: &[u8]);
+///
+///     /// Returns the access allowed for the key for the caller.
+///     ///
+///     /// # Note
+///     ///
+///     /// Assumes to never fail the call and therefore always returns `Option<Access>`.
+///     #[ink(extension = 4, returns_result = false, handle_status = false)]
+///     fn access(key: &[u8]) -> Option<Access>;
+///
+///     /// Unlocks previously aquired permission to access key.
+///     ///
+///     /// # Errors
+///     ///
+///     /// If the permission was not granted.
+///     ///
+///     /// # Note
+///     ///
+///     /// Assumes the call to never fail and therefore does _NOT_ require `UnlockAccessError`
+///     /// to implement `From<Self::ErrorCode>` as in the `read_small` method above.
+///     #[ink(extension = 5, handle_status = false)]
+///     fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>;
+/// }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub enum ReadWriteErrorCode {
+/// #     InvalidKey,
+/// #     CannotWriteToKey,
+/// #     CannotReadFromKey,
+/// # }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub enum ReadWriteError {
+/// #     ErrorCode(ReadWriteErrorCode),
+/// #     BufferTooSmall { required_bytes: u32 },
+/// # }
+/// # impl From<ReadWriteErrorCode> for ReadWriteError {
+/// #     fn from(error_code: ReadWriteErrorCode) -> Self {
+/// #         Self::ErrorCode(error_code)
+/// #     }
+/// # }
+/// # impl From<scale::Error> for ReadWriteError {
+/// #     fn from(_: scale::Error) -> Self {
+/// #         panic!("encountered unexpected invalid SCALE encoding")
+/// #     }
+/// # }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub struct UnlockAccessError {
+/// #     reason: String,
+/// # }
+/// # impl From<scale::Error> for UnlockAccessError {
+/// #     fn from(_: scale::Error) -> Self {
+/// #         panic!("encountered unexpected invalid SCALE encoding")
+/// #     }
+/// # }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub enum Access {
+/// #     ReadWrite,
+/// #     ReadOnly,
+/// #     WriteOnly,
+/// # }
+/// # impl ink_env::chain_extension::FromStatusCode for ReadWriteErrorCode {
+/// #     fn from_status_code(status_code: u32) -> Result<(), Self> {
+/// #         match status_code {
+/// #             0 => Ok(()),
+/// #             1 => Err(Self::InvalidKey),
+/// #             2 => Err(Self::CannotWriteToKey),
+/// #             3 => Err(Self::CannotReadFromKey),
+/// #             _ => panic!("encountered unknown status code"),
+/// #         }
+/// #     }
+/// # }
+/// ```
+///
+/// All the error types and other utility types used in the chain extension definition
+/// above are often required to implement various traits such as SCALE's `Encode` and `Decode`
+/// as well as `scale-info`'s `TypeInfo` trait.
+///
+/// A full example of the above chain extension definition can be seen
+/// [here](https://github.com/paritytech/ink/blob/017f71d60799b764425334f86b732cc7b7065fe6/crates/lang/macro/tests/ui/chain_extension/simple.rs).
+///
+/// # Example: Environment
+///
+/// In order to allow ink! smart contracts to use the above defined chain extension it needs
+/// to be integrated into an `Environment` definition as shown below:
+///
+/// ```
+/// # type RuntimeReadWrite = i32;
+/// #
+/// use ink_env::{Environment, DefaultEnvironment};
+///
+/// pub enum CustomEnvironment {}
+///
+/// impl Environment for CustomEnvironment {
+///     const MAX_EVENT_TOPICS: usize =
+///         <DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;
+///
+///     type AccountId = <DefaultEnvironment as Environment>::AccountId;
+///     type Balance = <DefaultEnvironment as Environment>::Balance;
+///     type Hash = <DefaultEnvironment as Environment>::Hash;
+///     type BlockNumber = <DefaultEnvironment as Environment>::BlockNumber;
+///     type Timestamp = <DefaultEnvironment as Environment>::Timestamp;
+///
+///     type ChainExtension = RuntimeReadWrite;
+/// }
+/// ```
+///
+/// Above we defined the `CustomEnvironment` which defaults to ink!'s `DefaultEnvironment`
+/// for all constants and types but the `ChainExtension` type which is assigned to our newly
+/// defined chain extension.
+///
+/// # Example: Usage
+///
+/// An ink! smart contract can use the above defined chain extension through the `Environment`
+/// definition defined in the last example section using the `env` macro parameter as
+/// shown below.
+///
+/// Note that chain extension methods are accessible through `Self::extension()` or
+/// `self.extension()`. For example as in `Self::extension().read(..)` or `self.extension().read(..)`.
+///
+/// ```
+/// # use ink_lang as ink;
+/// #
+/// #[ink::contract(env = CustomEnvironment)]
+/// mod read_writer {
+///     # use ink_lang as ink;
+///     #
+///     #[ink(storage)]
+///     pub struct ReadWriter {}
+///
+///     impl ReadWriter {
+///         #[ink(constructor)]
+///         pub fn new() -> Self { Self {} }
+///
+///         #[ink(message)]
+///         pub fn read(&self, key: Vec<u8>) -> Result<Vec<u8>, ReadWriteErrorCode> {
+///             self.env()
+///                 .extension()
+///                 .read(&key)
+///         }
+///
+///         #[ink(message)]
+///         pub fn read_small(&self, key: Vec<u8>) -> Result<(u32, [u8; 32]), ReadWriteError> {
+///             self.env()
+///                 .extension()
+///                 .read_small(&key)
+///         }
+///
+///         #[ink(message)]
+///         pub fn write(
+///             &self,
+///             key: Vec<u8>,
+///             value: Vec<u8>,
+///         ) -> Result<(), ReadWriteErrorCode> {
+///             self.env()
+///                 .extension()
+///                 .write(&key, &value)
+///         }
+///
+///         #[ink(message)]
+///         pub fn access(&self, key: Vec<u8>) -> Option<Access> {
+///             self.env()
+///                 .extension()
+///                 .access(&key)
+///         }
+///
+///         #[ink(message)]
+///         pub fn unlock_access(&self, key: Vec<u8>, access: Access) -> Result<(), UnlockAccessError> {
+///             self.env()
+///                 .extension()
+///                 .unlock_access(&key, access)
+///         }
+///     }
+/// # /// Custom chain extension to read to and write from the runtime.
+/// # #[ink::chain_extension]
+/// # pub trait RuntimeReadWrite {
+/// #     type ErrorCode = ReadWriteErrorCode;
+/// #     #[ink(extension = 1, returns_result = false)]
+/// #     fn read(key: &[u8]) -> Vec<u8>;
+/// #     #[ink(extension = 2)]
+/// #     fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>;
+/// #     #[ink(extension = 3, returns_result = false)]
+/// #     fn write(key: &[u8], value: &[u8]);
+/// #     #[ink(extension = 4, returns_result = false, handle_status = false)]
+/// #     fn access(key: &[u8]) -> Option<Access>;
+/// #     #[ink(extension = 5, handle_status = false)]
+/// #     fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>;
+/// # }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub enum ReadWriteErrorCode {
+/// #     InvalidKey,
+/// #     CannotWriteToKey,
+/// #     CannotReadFromKey,
+/// # }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub enum ReadWriteError {
+/// #     ErrorCode(ReadWriteErrorCode),
+/// #     BufferTooSmall { required_bytes: u32 },
+/// # }
+/// # impl From<ReadWriteErrorCode> for ReadWriteError {
+/// #     fn from(error_code: ReadWriteErrorCode) -> Self {
+/// #         Self::ErrorCode(error_code)
+/// #     }
+/// # }
+/// # impl From<scale::Error> for ReadWriteError {
+/// #     fn from(_: scale::Error) -> Self {
+/// #         panic!("encountered unexpected invalid SCALE encoding")
+/// #     }
+/// # }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub struct UnlockAccessError {
+/// #     reason: String,
+/// # }
+/// # impl From<scale::Error> for UnlockAccessError {
+/// #     fn from(_: scale::Error) -> Self {
+/// #         panic!("encountered unexpected invalid SCALE encoding")
+/// #     }
+/// # }
+/// # #[derive(scale::Encode, scale::Decode, scale_info::TypeInfo)]
+/// # pub enum Access {
+/// #     ReadWrite,
+/// #     ReadOnly,
+/// #     WriteOnly,
+/// # }
+/// # impl ink_env::chain_extension::FromStatusCode for ReadWriteErrorCode {
+/// #     fn from_status_code(status_code: u32) -> Result<(), Self> {
+/// #         match status_code {
+/// #             0 => Ok(()),
+/// #             1 => Err(Self::InvalidKey),
+/// #             2 => Err(Self::CannotWriteToKey),
+/// #             3 => Err(Self::CannotReadFromKey),
+/// #             _ => panic!("encountered unknown status code"),
+/// #         }
+/// #     }
+/// # }
+/// # pub enum CustomEnvironment {}
+/// # impl ink_env::Environment for CustomEnvironment {
+/// #     const MAX_EVENT_TOPICS: usize =
+/// #         <ink_env::DefaultEnvironment as ink_env::Environment>::MAX_EVENT_TOPICS;
+/// #
+/// #     type AccountId = <ink_env::DefaultEnvironment as ink_env::Environment>::AccountId;
+/// #     type Balance = <ink_env::DefaultEnvironment as ink_env::Environment>::Balance;
+/// #     type Hash = <ink_env::DefaultEnvironment as ink_env::Environment>::Hash;
+/// #     type BlockNumber = <ink_env::DefaultEnvironment as ink_env::Environment>::BlockNumber;
+/// #     type Timestamp = <ink_env::DefaultEnvironment as ink_env::Environment>::Timestamp;
+/// #
+/// #     type ChainExtension = RuntimeReadWrite;
+/// # }
+/// }
+/// ```
+///
+/// # Technical Limitations
+///
+/// - Due to technical limitations it is not possible to refer to the `ErrorCode` associated type
+///   using `Self::ErrorCode` anywhere within the chain extension and its defined methods.
+///   Instead chain extension authors should directly use the error code type when required.
+///   This limitation might be lifted in future versions of ink!.
+/// - It is not possible to declare other chain extension traits as super traits or super
+///   chain extensions of another.
+#[proc_macro_attribute]
+pub fn chain_extension(attr: TokenStream, item: TokenStream) -> TokenStream {
+    chain_extension::generate(attr.into(), item.into()).into()
+}
+
 #[cfg(test)]
 pub use contract::generate_or_err;
diff --git a/crates/lang/src/chain_extension.rs b/crates/lang/src/chain_extension.rs
new file mode 100644
index 0000000000..b9f9e9d104
--- /dev/null
+++ b/crates/lang/src/chain_extension.rs
@@ -0,0 +1,60 @@
+// 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.
+
+/// Trait implemented by chain extensions.
+///
+/// Allows to use the `self.env().extension().my_chain_extension(..)` syntax.
+///
+/// # Note
+///
+/// This trait is automatically implemented when using `#[ink::chain_extension]` proc. macro.
+pub trait ChainExtensionInstance {
+    /// The type of the chain extension instance.
+    type Instance;
+
+    /// Creates a new instance of the chain extension to use methods with method chaining syntax.
+    fn instantiate() -> Self::Instance;
+}
+
+/// Implemented by chain extension types.
+///
+/// Every chain extension defines a set of chain extension methods
+/// that share a common error code type.
+pub trait ChainExtension {
+    /// The error code that determines whether a chain extension method call was successful.
+    type ErrorCode: ink_env::chain_extension::FromStatusCode;
+}
+
+/// Only implemented for `Result<T, E>`.
+///
+/// Used to check at compile time if the return type of a chain extension method
+/// is a `Result` type using the type system instead of the syntactic structure.
+#[doc(hidden)]
+pub trait IsResultType: private::Sealed {
+    /// The `T` type of the `Result<T, E>`.
+    type Ok;
+    /// The `E` type of the `Result<T, E>`.
+    type Err;
+}
+
+impl<T, E> private::Sealed for Result<T, E> {}
+impl<T, E> IsResultType for Result<T, E> {
+    type Ok = T;
+    type Err = E;
+}
+
+mod private {
+    /// Seals the `IsResultType` trait so that it cannot be implemented outside of this module.
+    pub trait Sealed {}
+}
diff --git a/crates/lang/src/env_access.rs b/crates/lang/src/env_access.rs
index 56cfd41d98..08a9471e89 100644
--- a/crates/lang/src/env_access.rs
+++ b/crates/lang/src/env_access.rs
@@ -28,6 +28,8 @@ use ink_env::{
 };
 use ink_primitives::Key;
 
+use crate::ChainExtensionInstance;
+
 /// The environment of the compiled ink! smart contract.
 pub trait ContractEnv {
     /// The environment type.
@@ -89,6 +91,19 @@ impl<'a, E> core::fmt::Debug for EnvAccess<'a, E> {
     }
 }
 
+impl<'a, T> EnvAccess<'a, T>
+where
+    T: Environment,
+    <T as Environment>::ChainExtension: ChainExtensionInstance,
+{
+    /// Allows to call one of the available defined chain extension methods.
+    pub fn extension(
+        self,
+    ) -> <<T as Environment>::ChainExtension as ChainExtensionInstance>::Instance {
+        <<T as Environment>::ChainExtension as ChainExtensionInstance>::instantiate()
+    }
+}
+
 impl<'a, T> EnvAccess<'a, T>
 where
     T: Environment,
diff --git a/crates/lang/src/lib.rs b/crates/lang/src/lib.rs
index e2d10fef5f..eb4fce178a 100644
--- a/crates/lang/src/lib.rs
+++ b/crates/lang/src/lib.rs
@@ -14,6 +14,7 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
+mod chain_extension;
 mod contract;
 mod cross_calling;
 mod dispatcher;
@@ -23,6 +24,10 @@ mod events;
 mod traits;
 
 pub use self::{
+    chain_extension::{
+        ChainExtensionInstance,
+        IsResultType,
+    },
     contract::{
         DispatchMode,
         DispatchUsingMode,
@@ -74,6 +79,7 @@ pub use self::{
 };
 pub use ::static_assertions;
 pub use ink_lang_macro::{
+    chain_extension,
     contract,
     test,
     trait_definition,
diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml
index 648e6aeb72..6f2228db42 100644
--- a/crates/storage/Cargo.toml
+++ b/crates/storage/Cargo.toml
@@ -35,8 +35,8 @@ generic-array = "0.14.1"
 criterion = { version = "0.3", optional = true }
 
 [dev-dependencies]
-quickcheck = "0.9"
-quickcheck_macros = "0.9"
+quickcheck = "1.0"
+quickcheck_macros = "1.0"
 itertools = "0.10"
 paste = "1.0"
 


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 7ee5164910..e60b74b103 100644
--- a/crates/env/src/engine/off_chain/test_api.rs
+++ b/crates/env/src/engine/off_chain/test_api.rs
@@ -14,19 +14,18 @@
 
 //! Operations on the off-chain testing environment.
 
-#[cfg(feature = "ink-unstable-chain-extensions")]
-use super::chain_extension::ChainExtension;
-pub use super::{
-    db::ChainSpec,
-    CallData,
-    EmittedEvent,
-};
 use super::{
+    chain_extension::ChainExtension,
     db::ExecContext,
     AccountError,
     EnvInstance,
     OnInstance,
 };
+pub use super::{
+    db::ChainSpec,
+    CallData,
+    EmittedEvent,
+};
 use crate::{
     Environment,
     Result,
@@ -180,12 +179,9 @@ where
 }
 
 /// Registers a new chain extension.
-#[cfg(feature = "ink-unstable-chain-extensions")]
-pub fn register_chain_extension<E, I, O>(extension: E)
+pub fn register_chain_extension<E>(extension: E)
 where
-    E: ChainExtension<Input = I, Output = O> + 'static,
-    I: scale::Codec + 'static,
-    O: scale::Codec + 'static,
+    E: ChainExtension + 'static,
 {
     <EnvInstance as OnInstance>::on_instance(|instance| {
         instance
diff --git a/crates/lang/macro/tests/compile_tests.rs b/crates/lang/macro/tests/compile_tests.rs
index e919cdfab1..0459f17189 100644
--- a/crates/lang/macro/tests/compile_tests.rs
+++ b/crates/lang/macro/tests/compile_tests.rs
@@ -15,6 +15,7 @@
 #[test]
 fn compile_tests() {
     let t = trybuild::TestCases::new();
+
     t.pass("tests/ui/pass/01-noop-contract.rs");
     t.pass("tests/ui/pass/02-flipper-contract.rs");
     t.pass("tests/ui/pass/03-incrementer-contract.rs");
@@ -53,4 +54,6 @@ fn compile_tests() {
     t.compile_fail("tests/ui/fail/S-04-non-storage-ink-impls.rs");
     t.compile_fail("tests/ui/fail/S-05-storage-as-event.rs");
     t.compile_fail("tests/ui/fail/S-06-event-as-storage.rs");
+
+    t.pass("tests/ui/chain_extension/E-01-simple.rs");
 }
diff --git a/crates/lang/macro/tests/ui/chain_extension/E-01-simple.rs b/crates/lang/macro/tests/ui/chain_extension/E-01-simple.rs
new file mode 100644
index 0000000000..96bbfef087
--- /dev/null
+++ b/crates/lang/macro/tests/ui/chain_extension/E-01-simple.rs
@@ -0,0 +1,178 @@
+use ink_env::Environment;
+use ink_lang as ink;
+
+/// Custom chain extension to read to and write from the runtime.
+#[ink::chain_extension]
+pub trait RuntimeReadWrite {
+    type ErrorCode = ReadWriteErrorCode;
+
+    /// Reads from runtime storage.
+    #[ink(extension = 1, returns_result = false)]
+    fn read(key: &[u8]) -> Vec<u8>;
+
+    /// Reads from runtime storage.
+    ///
+    /// Returns the number of bytes read and up to 32 bytes of the
+    /// read value. Unused bytes in the output are set to 0.
+    ///
+    /// # Errors
+    ///
+    /// If the runtime storage cell stores a value that requires more than
+    /// 32 bytes.
+    #[ink(extension = 2)]
+    fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>;
+
+    /// Writes into runtime storage.
+    #[ink(extension = 3, returns_result = false)]
+    fn write(key: &[u8], value: &[u8]);
+
+    /// Returns the access allowed for the key for the caller.
+    #[ink(extension = 4, returns_result = false, handle_status = false)]
+    fn access(key: &[u8]) -> Option<Access>;
+
+    /// Unlocks previously aquired permission to access key.
+    ///
+    /// # Errors
+    ///
+    /// If the permission was not granted.
+    #[ink(extension = 5, handle_status = false)]
+    fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>;
+}
+
+/// The shared error code for the read write chain extension.
+#[derive(
+    Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode, scale_info::TypeInfo,
+)]
+pub enum ReadWriteErrorCode {
+    InvalidKey,
+    CannotWriteToKey,
+    CannotReadFromKey,
+}
+
+/// Returned by `read_small` in case there were too few bytes available in the buffer.
+///
+/// Provides the number of bytes required to read the storage cell.
+#[derive(
+    Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode, scale_info::TypeInfo,
+)]
+pub enum ReadWriteError {
+    ErrorCode(ReadWriteErrorCode),
+    BufferTooSmall { required_bytes: u32 },
+}
+
+impl From<ReadWriteErrorCode> for ReadWriteError {
+    fn from(error_code: ReadWriteErrorCode) -> Self {
+        Self::ErrorCode(error_code)
+    }
+}
+
+impl From<scale::Error> for ReadWriteError {
+    fn from(_: scale::Error) -> Self {
+        panic!("encountered unexpected invalid SCALE encoding")
+    }
+}
+
+/// Returned by `unlock_access` if permission to access key was not granted with reason.
+#[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode, scale_info::TypeInfo)]
+pub struct UnlockAccessError {
+    reason: String,
+}
+
+impl From<scale::Error> for UnlockAccessError {
+    fn from(_: scale::Error) -> Self {
+        panic!("encountered unexpected invalid SCALE encoding")
+    }
+}
+
+/// The kind of access allows for a storage cell.
+#[derive(
+    Debug, Copy, Clone, PartialEq, Eq, scale::Encode, scale::Decode, scale_info::TypeInfo,
+)]
+pub enum Access {
+    ReadWrite,
+    ReadOnly,
+    WriteOnly,
+}
+
+impl ink_env::chain_extension::FromStatusCode for ReadWriteErrorCode {
+    fn from_status_code(status_code: u32) -> Result<(), Self> {
+        match status_code {
+            0 => Ok(()),
+            1 => Err(Self::InvalidKey),
+            2 => Err(Self::CannotWriteToKey),
+            3 => Err(Self::CannotReadFromKey),
+            _ => panic!("encountered unknown status code"),
+        }
+    }
+}
+
+pub enum CustomEnvironment {}
+
+impl Environment for CustomEnvironment {
+    const MAX_EVENT_TOPICS: usize =
+        <ink_env::DefaultEnvironment as Environment>::MAX_EVENT_TOPICS;
+
+    type AccountId = <ink_env::DefaultEnvironment as Environment>::AccountId;
+    type Balance = <ink_env::DefaultEnvironment as Environment>::Balance;
+    type Hash = <ink_env::DefaultEnvironment as Environment>::Hash;
+    type BlockNumber = <ink_env::DefaultEnvironment as Environment>::BlockNumber;
+    type Timestamp = <ink_env::DefaultEnvironment as Environment>::Timestamp;
+
+    type ChainExtension = RuntimeReadWrite;
+}
+
+#[ink::contract(env = crate::CustomEnvironment)]
+mod read_writer {
+    use super::{Access, ReadWriteErrorCode, ReadWriteError, UnlockAccessError};
+
+    #[ink(storage)]
+    pub struct ReadWriter {}
+
+    impl ReadWriter {
+        #[ink(constructor)]
+        pub fn new() -> Self {
+            Self {}
+        }
+
+        #[ink(message)]
+        pub fn read(&self, key: Vec<u8>) -> Result<Vec<u8>, ReadWriteErrorCode> {
+            self.env()
+                .extension()
+                .read(&key)
+        }
+
+        #[ink(message)]
+        pub fn read_small(&self, key: Vec<u8>) -> Result<(u32, [u8; 32]), ReadWriteError> {
+            self.env()
+                .extension()
+                .read_small(&key)
+        }
+
+        #[ink(message)]
+        pub fn write(
+            &self,
+            key: Vec<u8>,
+            value: Vec<u8>,
+        ) -> Result<(), ReadWriteErrorCode> {
+            self.env()
+                .extension()
+                .write(&key, &value)
+        }
+
+        #[ink(message)]
+        pub fn access(&self, key: Vec<u8>) -> Option<Access> {
+            self.env()
+                .extension()
+                .access(&key)
+        }
+
+        #[ink(message)]
+        pub fn unlock_access(&self, key: Vec<u8>, access: Access) -> Result<(), UnlockAccessError> {
+            self.env()
+                .extension()
+                .unlock_access(&key, access)
+        }
+    }
+}
+
+fn main() {}