-
Notifications
You must be signed in to change notification settings - Fork 480
Description
The chain extension feature on the Substrate side takes shape and therefore it makes sense to finalize its implementation in ink!.
Missing from a final implementation is how a user on the ink! side will interact with this feature.
Inspired from past experiences with direct runtime dispatches we could simply do the same and provide yet another associated type on our ink_env::Environment trait called Extension.
In practical use a user assigns this Extension assoc. type to some enum that has a variant for each and every function that is callable under the given chain. The chain extension feature denotes a unique ID for every callable extension as well as some input parameters and one output parameter.
To make the feature more user friendly we should actually go a slightly different way:
- We provide an attribute proc. macro on trait definitions. The user would use this proc. macro on a trait definition that resembles the API of the chain extension with all functions, their inputs and outputs.
- This attribute proc. macro outputs an
enumthat implements this trait as well as a more genericBaseExtensiontrait provided byink_env. - The
BaseExtensiontrait has methods likefn func_id() -> usizeandfn encode_inputs(&self, &mut scale::Output)to SCALE encode its inputs that are to be implemented by the attribute proc. macro. Therefore we have a trait bound for theExtensionassoc. type on theink_env::Environmenttrait. - Finally the
ink_langcodegen generates code to make using those chain extension features simple by providing type aliases and shortcuts for the user.
The end result should look similar to this:
// In file custom_environment.rs
#[ink::chain_extension]
pub trait MyChainExtension {
#[extension(id = 1)]
fn query_storage(key: &[u8], buffer: &mut [u8]) -> Result<(), ink_env::Error>;
#[extension(id = 2)]
fn dispatch_runtime(scale_input: &[u8]) -> Result<(), ink_env::Error>;
#[extension(id = 5)]
fn register_me(account_id: AccountId, balance: Balance);
}This will create a type called MyChainExtension that has the same API as the trait defines. Note that the trait definition is not going to be (re-)generated by the macro. Its only purpose is to define the resulting types API.
This MyChainExtension type can then be fed into ink!'s Environment as the Extension assoc. type:
impl ink_env::Environment for MyEnvironment {
type Balance = u64;
...
type Extension = MyChainExtension;
}The ink_lang_codegen will generate aliases for this assoc. type so that a user in the end can write something along the lines of:
#[ink(constructor)]
fn new(initial_supply: Balance) -> Self {
let caller = Self::env().caller();
Extension::register_me(caller, initial_supply);
// ...
}Future Updates
As a future update we could extend the self.env() or Self::env() API to also embrace the extension by an interface similar to this:
self.env().extension().register_me(caller, initial_supply);
// or
Self::env().extension().register_me(caller, initial_supply);ToDo List
- Add trait
BaseExtensiontrait as described above toink_env. - Add
Extensionassoc. type toink_env::Environmentwith trait boundBaseExtension. - Implement the attribute proc. macro
#[ink::chain_extension]inink_env.- It shall be very restriction at the start. So an implementation will only allow it to define some basic member methods without a self receiver. No associated constants or types. No generics or dynamic dispatch. No
unsafe,async, etc. We might allow all of this at a later time with consideration for the implications. The trait definition must be public. - The attribute proc. macro shall generate the extension type and implement
BaseExtensionfor it as well as the API defined by the trait. - The attribute proc. macro must enforce that every trait member function is flagged with an
#[extension(id: u32)]unique identifier. We (currently) do not allow implicit IDs.
- It shall be very restriction at the start. So an implementation will only allow it to define some basic member methods without a self receiver. No associated constants or types. No generics or dynamic dispatch. No
- The
ink_lang_codegencrate shall generate aliases and anything else that is convenient for usability. - Tests shall be written in order to guarantee that the feature works as expected with regards to the tested base set of features.