Skip to content

Commit 4dea70d

Browse files
GhermanMichael Müller
andauthored
Update Chain Extension docs (#307)
* add additional sections * fix typo * fix typo * Apply suggestions from code review Co-authored-by: Michael Müller <[email protected]> * add migration notice * add notice on extension id * Apply suggestions from code review Co-authored-by: Michael Müller <[email protected]> --------- Co-authored-by: Michael Müller <[email protected]>
1 parent 208005e commit 4dea70d

File tree

2 files changed

+139
-24
lines changed

2 files changed

+139
-24
lines changed

versioned_docs/version-5.x/faq/migrating-from-ink-4-to-5.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ multiple chain extensions from ink!. This is a breaking change.
378378
You can now e.g. have a contract that utilizes a PSP22 chain extension together with one
379379
for random numbers.
380380

381-
The syntax for chain extensions changed slightly:
381+
The syntax for chain extension functions changed slightly:
382382

383383
```diff
384384
-#[ink(extension = 0xfecb)]
@@ -395,8 +395,21 @@ The argument type changed from `u32` to `u16`:
395395
+Function,
396396
```
397397

398+
The top level macro `#[ink::chain_extension]` now _requires_ an `(extension = N: u16)` argument to support multiple chain extensions.
399+
If you are using only one extension, the ID can be any `u16` number,
400+
otherwise please consult the [`#[ink::chain_extension]` macro documentation](https://use.ink/5.x/macros-attributes/chain-extension)
401+
```diff
402+
-#[ink::chain_extension]
403+
+#[ink::chain_extension(extension = 1)]
404+
```
405+
406+
:::Note
407+
If the chain extension was not used in a tuple in the runtime configuration,
408+
`extension = N: u16` can take any `u16` number.
409+
:::
410+
398411
A migration in most cases should just be to rename `#[ink(extension = …)]` to
399-
`#[ink(function = …)]`.
412+
`#[ink(function = …)]`, and specifying `extension` argument in top level macro.
400413

401414
We added an example contract that illustrates the usage of multiple chain extensions
402415
in one contract:

versioned_docs/version-5.x/macros-attributes/chain-extension.md

Lines changed: 124 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ hide_title: true
77
<img src="/img/title/text/chain-ext.svg" className="titlePic" />
88

99
In the default configuration of the `contracts-pallet` a smart contract can only interact with the runtime
10-
via its well defined set of basic smart contract interface. This API already allows a whole variety of
10+
via its well defined set of basic smart contract interface functions. This API already allows a whole variety of
1111
interaction between the `contracts-pallet` and the executed smart contract. For example it is possible
1212
to call and instantiate other smart contracts on the same chain, emit events, query context information
1313
or run built-in cryptographic hashing procedures.
@@ -23,7 +23,7 @@ With chain extensions you can expose parts of your runtime logic
2323
to smart contract developers.
2424

2525
:::note
26-
The ink! repository contains [the `rand-extension` example](https://github.com/paritytech/ink-examples/tree/main/rand-extension).
26+
The ink! examples repository contains [the `rand-extension` example](https://github.com/paritytech/ink-examples/tree/main/rand-extension).
2727
This is a complete example of a chain extension implemented in both ink! and Substrate.
2828
:::
2929

@@ -49,26 +49,43 @@ ink! smart contracts using this chain extension simply depend on this crate
4949
and use its associated environment definition in order to make use of
5050
the methods provided by the chain extension.
5151

52-
## Attributes
52+
## Macro Attributes
53+
54+
The macro supports only one required argument: `extension = N: u16`.
55+
The runtime may have several chain extensions at the same time. The `extension`
56+
identifier points to the corresponding chain extension in the runtime.
57+
The value should be the same as during the definition of the chain extension.
58+
You can consult the
59+
[chain extension module documentation](https://paritytech.github.io/polkadot-sdk/master/pallet_contracts/chain_extension/index.html)
60+
if you are unsure how to find the chain extension code.
61+
Otherwise, you should consult the target chain's documentation
62+
for specifications of any chain extensions it exposes.
63+
64+
:::Note
65+
If the chain extension is not used in a tuple in the runtime configuration,
66+
`extension = N: u16` can take any `u16` number.
67+
:::
68+
69+
## Method Attributes
5370

5471
There are two different attributes with which the chain extension methods
5572
can be flagged:
5673

5774
| Attribute | Required | Default Value | Description |
5875
|:----------|:--------:|:--------------|:-----------:|
59-
| `ink(extension = N: u32)` | Yes | - | Determines the unique function ID of the chain extension method. |
76+
| `ink(function = N: u16)` | Yes | - | Determines the unique function ID within the chain extension. |
6077
| `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. |
6178

6279
As with all ink! attributes multiple of them can either appear in a contiguous list:
6380

6481
```rust
6582
type Access = i32;
6683

67-
#[ink::chain_extension]
84+
#[ink::chain_extension(extension = 12)]
6885
pub trait MyChainExtension {
6986
type ErrorCode = i32;
7087

71-
#[ink(extension = 5, handle_status = false)]
88+
#[ink(function = 5, handle_status = false)]
7289
fn key_access_for_account(key: &[u8], account: &[u8]) -> Access;
7390
}
7491
```
@@ -78,11 +95,11 @@ pub trait MyChainExtension {
7895
```rust
7996
type Access = i32;
8097

81-
#[ink::chain_extension]
98+
#[ink::chain_extension(extension = 12)]
8299
pub trait MyChainExtension {
83100
type ErrorCode = i32;
84101

85-
#[ink(extension = 5)]
102+
#[ink(function = 5)]
86103
#[ink(handle_status = false)]
87104
fn key_access_for_account(key: &[u8], account: &[u8]) -> Access;
88105
}
@@ -154,7 +171,7 @@ from and to the runtime storage using access privileges:
154171

155172
```rust
156173
/// Custom chain extension to read to and write from the runtime.
157-
#[ink::chain_extension]
174+
#[ink::chain_extension(extension = 12)]
158175
pub trait RuntimeReadWrite {
159176
type ErrorCode = ReadWriteErrorCode;
160177

@@ -163,8 +180,9 @@ pub trait RuntimeReadWrite {
163180
/// # Note
164181
///
165182
/// Actually returns a value of type `Result<Vec<u8>, Self::ErrorCode>`.
166-
/// #[ink(extension = 1, returns_result = false)]
167-
/// fn read(key: &[u8]) -> Vec<u8>;
183+
#[ink(function = 1, returns_result = false)]
184+
fn read(key: &[u8]) -> Vec<u8>;
185+
168186
///
169187
/// Reads from runtime storage.
170188
///
@@ -180,23 +198,23 @@ pub trait RuntimeReadWrite {
180198
///
181199
/// This requires `ReadWriteError` to implement `From<ReadWriteErrorCode>`
182200
/// and may potentially return any `Self::ErrorCode` through its return value.
183-
#[ink(extension = 2)]
201+
#[ink(function = 2)]
184202
fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>;
185203

186204
/// Writes into runtime storage.
187205
///
188206
/// # Note
189207
///
190208
/// Actually returns a value of type `Result<(), Self::ErrorCode>`.
191-
#[ink(extension = 3)]
209+
#[ink(function = 3)]
192210
fn write(key: &[u8], value: &[u8]);
193211

194212
/// Returns the access allowed for the key for the caller.
195213
///
196214
/// # Note
197215
///
198216
/// Assumes to never fail the call and therefore always returns `Option<Access>`.
199-
#[ink(extension = 4, handle_status = false)]
217+
#[ink(function = 4, handle_status = false)]
200218
fn access(key: &[u8]) -> Option<Access>;
201219

202220
/// Unlocks previously acquired permission to access key.
@@ -209,7 +227,7 @@ pub trait RuntimeReadWrite {
209227
///
210228
/// Assumes the call to never fail and therefore does _NOT_ require `UnlockAccessError`
211229
/// to implement `From<Self::ErrorCode>` as in the `read_small` method above.
212-
#[ink(extension = 5, handle_status = false)]
230+
#[ink(function = 5, handle_status = false)]
213231
fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>;
214232
}
215233

@@ -367,18 +385,18 @@ mod read_writer {
367385
}
368386

369387
/// Custom chain extension to read to and write from the runtime.
370-
#[ink::chain_extension]
388+
#[ink::chain_extension(extension = 12)]
371389
pub trait RuntimeReadWrite {
372390
type ErrorCode = ReadWriteErrorCode;
373-
#[ink(extension = 1)]
391+
#[ink(function = 1)]
374392
fn read(key: &[u8]) -> Vec<u8>;
375-
#[ink(extension = 2)]
393+
#[ink(function = 2)]
376394
fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>;
377-
#[ink(extension = 3)]
395+
#[ink(function = 3)]
378396
fn write(key: &[u8], value: &[u8]);
379-
#[ink(extension = 4, handle_status = false)]
397+
#[ink(function = 4, handle_status = false)]
380398
fn access(key: &[u8]) -> Option<Access>;
381-
#[ink(extension = 5, handle_status = false)]
399+
#[ink(function = 5, handle_status = false)]
382400
fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>;
383401
}
384402

@@ -447,11 +465,95 @@ mod read_writer {
447465
}
448466
```
449467

468+
## Using Multiple Chain Extensions
469+
470+
It is possible to use multiple exposed chain extensions in the single environment of a smart contract.
471+
The declaration procedure of the chain extension stays the same.
472+
473+
Suppose we want to combine two chain extension called `Psp22Extension` and `FetchRandom`, ink! provides
474+
a useful macro [`ink::combine_extensions!`](https://docs.rs/ink/5.0.0-rc/ink/macro.combine_extensions.html) that allows to construct the structure combining
475+
the aforementioned chain extensions like so:
476+
```rust
477+
ink::combine_extensions! {
478+
/// This extension combines the `FetchRandom` and `Psp22Extension` extensions.
479+
/// It is possible to combine any number of extensions in this way.
480+
///
481+
/// This structure is an instance that is returned by the `self.env().extension()` call.
482+
pub struct CombinedChainExtension {
483+
/// The instance of the `Psp22Extension` chain extension.
484+
///
485+
/// It provides you access to `PSP22` functionality.
486+
pub psp22: Psp22Extension,
487+
/// The instance of the `FetchRandom` chain extension.
488+
///
489+
/// It provides you access to randomness functionality.
490+
pub rand: FetchRandom,
491+
}
492+
}
493+
```
494+
495+
The combined structure is called `CombinedChainExtension`, and we can refer to it
496+
when specifying the chain extension type in `Environment`:
497+
```rust
498+
type ChainExtension = CombinedChainExtension;
499+
```
500+
501+
Each extension's method can be called by accessing it via the name of the field of `CombineChainExtension`:
502+
```rust
503+
self.env().extension().rand.<method_name_in_rand_ext>()
504+
// or
505+
self.env().extension().psp22.<method_name_in_psp22_ext>()
506+
// e.g.
507+
self.env().extension().psp22.total_supply()
508+
```
509+
510+
:::note
511+
The ink! repository contains the [full example](https://github.com/paritytech/ink/tree/master/integration-tests/combined-extension) illustrating how to combine existing chain extensions
512+
and mock them for testing.
513+
:::
514+
515+
516+
## Mocking Chain Extension
517+
518+
You can mock chain extensions for unit testing purposes.
519+
This can be achieved by implementing the [`ink::env::test::ChainExtension`](https://docs.rs/ink_env/latest/ink_env/test/trait.ChainExtension.html) trait.
520+
521+
```rust
522+
/// Opaque structure
523+
struct MockedPSP22Extension;
524+
525+
// Implementing
526+
impl ink::env::test::ChainExtension for MockedPSP22Extension {
527+
fn ext_id(&self) -> u16 {
528+
// It is identifier used by `psp22_extension::Psp22Extension` extension.
529+
// Must be the same as the once specified in `#[ink::chain_extension(extension = _)]`
530+
13
531+
}
532+
533+
// Call dispatcher.
534+
// Call specific code based on the function id which is dispatched from the contract/
535+
fn call(&mut self, func_id: u16, _input: &[u8], output: &mut Vec<u8>) -> u32 {
536+
match func_id {
537+
// `func_id` of the `total_supply` function.
538+
// must match `#[ink(function = _)]` of the corresponding method
539+
0x162d => {
540+
ink::scale::Encode::encode_to(&TOTAL_SUPPLY, output);
541+
0
542+
},
543+
// Other functions
544+
_ => {
545+
1
546+
}
547+
}
548+
}
549+
}
550+
```
551+
450552
## Technical Limitations
451553

452554
- Due to technical limitations it is not possible to refer to the `ErrorCode` associated type
453555
using `Self::ErrorCode` anywhere within the chain extension and its defined methods.
454556
Instead chain extension authors should directly use the error code type when required.
455557
This limitation might be lifted in future versions of ink!.
456558
- It is not possible to declare other chain extension traits as super traits or super
457-
chain extensions of another.
559+
chain extensions of another.

0 commit comments

Comments
 (0)