diff --git a/versioned_docs/version-5.x/faq/migrating-from-ink-4-to-5.md b/versioned_docs/version-5.x/faq/migrating-from-ink-4-to-5.md index 57b8af4c96..546f949b06 100644 --- a/versioned_docs/version-5.x/faq/migrating-from-ink-4-to-5.md +++ b/versioned_docs/version-5.x/faq/migrating-from-ink-4-to-5.md @@ -386,7 +386,7 @@ multiple chain extensions from ink!. This is a breaking change. You can now e.g. have a contract that utilizes a PSP22 chain extension together with one for random numbers. -The syntax for chain extensions changed slightly: +The syntax for chain extension functions changed slightly: ```diff -#[ink(extension = 0xfecb)] @@ -403,8 +403,21 @@ The argument type changed from `u32` to `u16`: +Function, ``` +The top level macro `#[ink::chain_extension]` now _requires_ an `(extension = N: u16)` argument to support multiple chain extensions. +If you are using only one extension, the ID can be any `u16` number, +otherwise please consult the [`#[ink::chain_extension]` macro documentation](https://use.ink/5.x/macros-attributes/chain-extension) +```diff +-#[ink::chain_extension] ++#[ink::chain_extension(extension = 1)] +``` + +:::Note +If the chain extension was not used in a tuple in the runtime configuration, +`extension = N: u16` can take any `u16` number. +::: + A migration in most cases should just be to rename `#[ink(extension = …)]` to -`#[ink(function = …)]`. +`#[ink(function = …)]`, and specifying `extension` argument in top level macro. We added an example contract that illustrates the usage of multiple chain extensions in one contract: diff --git a/versioned_docs/version-5.x/macros-attributes/chain-extension.md b/versioned_docs/version-5.x/macros-attributes/chain-extension.md index c4dcdd93d5..7320859c81 100644 --- a/versioned_docs/version-5.x/macros-attributes/chain-extension.md +++ b/versioned_docs/version-5.x/macros-attributes/chain-extension.md @@ -7,7 +7,7 @@ hide_title: true In the default configuration of the `contracts-pallet` a smart contract can only interact with the runtime -via its well defined set of basic smart contract interface. This API already allows a whole variety of +via its well defined set of basic smart contract interface functions. This API already allows a whole variety of interaction between the `contracts-pallet` and the executed smart contract. For example it is possible to call and instantiate other smart contracts on the same chain, emit events, query context information or run built-in cryptographic hashing procedures. @@ -23,7 +23,7 @@ With chain extensions you can expose parts of your runtime logic to smart contract developers. :::note -The ink! repository contains [the `rand-extension` example](https://github.com/paritytech/ink-examples/tree/main/rand-extension). +The ink! examples repository contains [the `rand-extension` example](https://github.com/paritytech/ink-examples/tree/main/rand-extension). This is a complete example of a chain extension implemented in both ink! and Substrate. ::: @@ -49,14 +49,31 @@ 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 +## Macro Attributes + +The macro supports only one required argument: `extension = N: u16`. +The runtime may have several chain extensions at the same time. The `extension` +identifier points to the corresponding chain extension in the runtime. +The value should be the same as during the definition of the chain extension. +You can consult the +[chain extension module documentation](https://paritytech.github.io/polkadot-sdk/master/pallet_contracts/chain_extension/index.html) +if you are unsure how to find the chain extension code. +Otherwise, you should consult the target chain's documentation +for specifications of any chain extensions it exposes. + +:::Note +If the chain extension is not used in a tuple in the runtime configuration, +`extension = N: u16` can take any `u16` number. +::: + +## Method Attributes There are two 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(function = N: u16)` | Yes | - | Determines the unique function ID within the chain extension. | | `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. | As with all ink! attributes multiple of them can either appear in a contiguous list: @@ -64,11 +81,11 @@ As with all ink! attributes multiple of them can either appear in a contiguous l ```rust type Access = i32; -#[ink::chain_extension] +#[ink::chain_extension(extension = 12)] pub trait MyChainExtension { type ErrorCode = i32; - #[ink(extension = 5, handle_status = false)] + #[ink(function = 5, handle_status = false)] fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; } ``` @@ -78,11 +95,11 @@ pub trait MyChainExtension { ```rust type Access = i32; -#[ink::chain_extension] +#[ink::chain_extension(extension = 12)] pub trait MyChainExtension { type ErrorCode = i32; - #[ink(extension = 5)] + #[ink(function = 5)] #[ink(handle_status = false)] fn key_access_for_account(key: &[u8], account: &[u8]) -> Access; } @@ -154,7 +171,7 @@ from and to the runtime storage using access privileges: ```rust /// Custom chain extension to read to and write from the runtime. -#[ink::chain_extension] +#[ink::chain_extension(extension = 12)] pub trait RuntimeReadWrite { type ErrorCode = ReadWriteErrorCode; @@ -163,8 +180,9 @@ pub trait RuntimeReadWrite { /// # Note /// /// Actually returns a value of type `Result, Self::ErrorCode>`. - /// #[ink(extension = 1, returns_result = false)] - /// fn read(key: &[u8]) -> Vec; + #[ink(function = 1, returns_result = false)] + fn read(key: &[u8]) -> Vec; + /// /// Reads from runtime storage. /// @@ -180,7 +198,7 @@ pub trait RuntimeReadWrite { /// /// This requires `ReadWriteError` to implement `From` /// and may potentially return any `Self::ErrorCode` through its return value. - #[ink(extension = 2)] + #[ink(function = 2)] fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>; /// Writes into runtime storage. @@ -188,7 +206,7 @@ pub trait RuntimeReadWrite { /// # Note /// /// Actually returns a value of type `Result<(), Self::ErrorCode>`. - #[ink(extension = 3)] + #[ink(function = 3)] fn write(key: &[u8], value: &[u8]); /// Returns the access allowed for the key for the caller. @@ -196,7 +214,7 @@ pub trait RuntimeReadWrite { /// # Note /// /// Assumes to never fail the call and therefore always returns `Option`. - #[ink(extension = 4, handle_status = false)] + #[ink(function = 4, handle_status = false)] fn access(key: &[u8]) -> Option; /// Unlocks previously acquired permission to access key. @@ -209,7 +227,7 @@ pub trait RuntimeReadWrite { /// /// Assumes the call to never fail and therefore does _NOT_ require `UnlockAccessError` /// to implement `From` as in the `read_small` method above. - #[ink(extension = 5, handle_status = false)] + #[ink(function = 5, handle_status = false)] fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>; } @@ -367,18 +385,18 @@ mod read_writer { } /// Custom chain extension to read to and write from the runtime. - #[ink::chain_extension] + #[ink::chain_extension(extension = 12)] pub trait RuntimeReadWrite { type ErrorCode = ReadWriteErrorCode; - #[ink(extension = 1)] + #[ink(function = 1)] fn read(key: &[u8]) -> Vec; - #[ink(extension = 2)] + #[ink(function = 2)] fn read_small(key: &[u8]) -> Result<(u32, [u8; 32]), ReadWriteError>; - #[ink(extension = 3)] + #[ink(function = 3)] fn write(key: &[u8], value: &[u8]); - #[ink(extension = 4, handle_status = false)] + #[ink(function = 4, handle_status = false)] fn access(key: &[u8]) -> Option; - #[ink(extension = 5, handle_status = false)] + #[ink(function = 5, handle_status = false)] fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>; } @@ -447,6 +465,90 @@ mod read_writer { } ``` +## Using Multiple Chain Extensions + +It is possible to use multiple exposed chain extensions in the single environment of a smart contract. +The declaration procedure of the chain extension stays the same. + +Suppose we want to combine two chain extension called `Psp22Extension` and `FetchRandom`, ink! provides +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 +the aforementioned chain extensions like so: +```rust +ink::combine_extensions! { + /// This extension combines the `FetchRandom` and `Psp22Extension` extensions. + /// It is possible to combine any number of extensions in this way. + /// + /// This structure is an instance that is returned by the `self.env().extension()` call. + pub struct CombinedChainExtension { + /// The instance of the `Psp22Extension` chain extension. + /// + /// It provides you access to `PSP22` functionality. + pub psp22: Psp22Extension, + /// The instance of the `FetchRandom` chain extension. + /// + /// It provides you access to randomness functionality. + pub rand: FetchRandom, + } +} +``` + +The combined structure is called `CombinedChainExtension`, and we can refer to it +when specifying the chain extension type in `Environment`: +```rust +type ChainExtension = CombinedChainExtension; +``` + +Each extension's method can be called by accessing it via the name of the field of `CombineChainExtension`: +```rust +self.env().extension().rand.() +// or +self.env().extension().psp22.() +// e.g. +self.env().extension().psp22.total_supply() +``` + +:::note +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 +and mock them for testing. +::: + + +## Mocking Chain Extension + +You can mock chain extensions for unit testing purposes. +This can be achieved by implementing the [`ink::env::test::ChainExtension`](https://docs.rs/ink_env/latest/ink_env/test/trait.ChainExtension.html) trait. + +```rust +/// Opaque structure +struct MockedPSP22Extension; + +// Implementing +impl ink::env::test::ChainExtension for MockedPSP22Extension { + fn ext_id(&self) -> u16 { + // It is identifier used by `psp22_extension::Psp22Extension` extension. + // Must be the same as the once specified in `#[ink::chain_extension(extension = _)]` + 13 + } + + // Call dispatcher. + // Call specific code based on the function id which is dispatched from the contract/ + fn call(&mut self, func_id: u16, _input: &[u8], output: &mut Vec) -> u32 { + match func_id { + // `func_id` of the `total_supply` function. + // must match `#[ink(function = _)]` of the corresponding method + 0x162d => { + ink::scale::Encode::encode_to(&TOTAL_SUPPLY, output); + 0 + }, + // Other functions + _ => { + 1 + } + } + } +} +``` + ## Technical Limitations - Due to technical limitations it is not possible to refer to the `ErrorCode` associated type @@ -454,4 +556,4 @@ mod read_writer { 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. + chain extensions of another. \ No newline at end of file