#[chain_extension]
Expand description
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 the SCALE encoding and decoding.
Their return value follows specific rules that can be altered using the
handle_status
attribute which is described in more detail below.
§Usage
Usually the chain extension definition using this procedural 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.
§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.
§Method Attributes
There are three different attributes with which the chain extension methods can be flagged:
Attribute | Required | Default Value | Description |
---|---|---|---|
ink(function = N: u16) | Yes | - | Determines the unique function ID within the |
chain extension. | ink(handle_status = flag: bool) | Optional | |
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:
#[ink(function = 5, handle_status = false)]
fn key_access_for_account(key: &[u8], account: &[u8]) -> Access;
…or as multiple stand alone ink! attributes applied to the same item:
#[ink(function = 5)]
#[ink(handle_status = false)]
fn key_access_for_account(key: &[u8], account: &[u8]) -> Access;
§Details: handle_status
Default value: true
By default all chain extension methods should 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.
Note that if a chain extension method does not return Result<T, E>
where E: From<Self::ErrorCode>
but handle_status = true
it will still return a value of type
Result<T, Self::ErrorCode>
.
§Usage: handle_status
Use both handle_status = false
and non-Result<T, E>
return type 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
return or not Result<T, E>
there are 4 different cases with slightly varying
semantics:
handle_status | Returns Result<T, E> | 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 | |
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 | |
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:
#[ink::chain_extension(extension = 0)]
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:
/// Custom chain extension to read to and write from the runtime.
#[ink::chain_extension(extension = 0)]
pub trait RuntimeReadWrite {
type ErrorCode = ReadWriteErrorCode;
/// Reads from runtime storage.
///
/// # Note
///
/// Actually returns a value of type `Result<Vec<u8>, Self::ErrorCode>`.
#[ink(function = 1)]
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(function = 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(function = 3)]
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(function = 4, handle_status = false)]
fn access(key: &[u8]) -> Option<Access>;
/// Unlocks previously acquired 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(function = 5, handle_status = false)]
fn unlock_access(key: &[u8], access: Access) -> Result<(), UnlockAccessError>;
}
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.
§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:
use ink_env::{
DefaultEnvironment,
Environment,
};
#[derive(Clone)]
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(...)
.
#[ink::contract(env = CustomEnvironment)]
mod read_writer {
#[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)
}
}
}
§Technical Limitations
- Due to technical limitations it is not possible to refer to the
ErrorCode
associated type usingSelf::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.