Skip to content

Implement: Chain extension feature on the Environment trait #585

@Robbepop

Description

@Robbepop

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 enum that implements this trait as well as a more generic BaseExtension trait provided by ink_env.
  • The BaseExtension trait has methods like fn func_id() -> usize and fn 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 the Extension assoc. type on the ink_env::Environment trait.
  • Finally the ink_lang codegen 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 BaseExtension trait as described above to ink_env.
  • Add Extension assoc. type to ink_env::Environment with trait bound BaseExtension.
  • Implement the attribute proc. macro #[ink::chain_extension] in ink_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 BaseExtension for 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.
  • The ink_lang_codegen crate 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.

Metadata

Metadata

Assignees

Labels

B-designDesigning a new component, interface or functionality.B-enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions