Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make ink! contracts viable dependencies for the runtime #1674

Open
Tracked by #114
athei opened this issue Feb 23, 2023 · 6 comments
Open
Tracked by #114

Make ink! contracts viable dependencies for the runtime #1674

athei opened this issue Feb 23, 2023 · 6 comments
Assignees
Labels
B-feature-request A request for a new feature.

Comments

@athei
Copy link
Contributor

athei commented Feb 23, 2023

When a contract A depends on another contract B and sets the ink-as-dependency feature we generate code that allows A to easily call into B. The generated code is specifically constructed to do a cross contract call and can't be used for other purposes easily. We want to change this.

The objective is to generate additional code that is more general and can be consumed by other targets than an ink! contract. The target we have in mind here are substrate runtimes: We want to make it convenient for substrate runtimes to call into ink! contracts.

The idea is that a runtime or pallet can depend on a ink! contract in its Cargo.toml just like a contract would. This will enable the runtime to learn about the messages and constructors of the contract and conveniently call these functions.

The e2e framework is probably doing something similar. I didn't check. I think it is most easily understood with an example. I wrote some example code how I imagine the system working. It is basically how I think the generated code could look like and how I would integrate it into pallet-contracts. It is by no means perfect. But I think it gets the point across:
https://github.com/athei/call-ink-example/blob/master/src/main.rs

Its quite a bit of code. But it is a complete example.

@mike1729
Copy link

The option of calling SCs from pallets conveniently (as it's possible right now but definitely not conveniently) would be very useful! We were discussing recently several use cases for that, e.g sth similar to this PR paritytech/substrate#13203 but using a SC DEX instead of a DEX pallet.

@cmichi
Copy link
Collaborator

cmichi commented Apr 11, 2023

@ascjones Please specify this out in a way that an external contributor could work on it.

@deep-ink-ventures
Copy link

We are looking into this as well atm.

@ascjones
Copy link
Collaborator

The generated code is specifically constructed to do a cross contract call and can't be used for other purposes easily. We want to change this.

The first thing I would do here is to see what is possible to achieve with the existing CallBuilder codegen that we are currently using for cross contract calls. It would be nice to have a similar API to that for consistency, and less codegen for different usage contexts.

The e2e framework is probably doing something similar.

Have a look at this PR which was an experiment I did to see if it was possible to use the existing contract ref messages which return a CallBuilder for which you can set parameters e.g. (gas limit, transferred value). The outcome was that it was possible with minimal modifications to the CallBuilder. See the erc20 example in that PR.

To get started with experimenting with this, you could reference a contract using ink-as-dependency from your pallet, exactly as you would for a cross contract call. This contract could then have an ink dependency pointing at your local copy of the ink source where you could make any tweaks to the API.

Again, ideally I would like to see a unified API for building contract calls for cross contract/e2e testing and calling contracts from substrate pallets. And I believe this will be possible while reusing most of the codegen we already have.

@xgreenx
Copy link
Collaborator

xgreenx commented Jul 30, 2023

The first thing I would do here is to see what is possible to achieve with the existing CallBuilder codegen that we are currently using for cross contract calls. It would be nice to have a similar API to that for consistency, and less codegen for different usage contexts.

It is possible to achieve this with minor changes in the call builder and a new trait. The call builder's current implementation(invoking of the contract) only works with the EnvInstance, which limits it only to the contract's context.

But if we make it configurable, we will be able to work with e2e or substrate runtime through the same API.

image

Have a look at #1669 which was an experiment I did to see if it was possible to use the existing contract ref messages which return a CallBuilder for which you can set parameters e.g. (gas limit, transferred value). The outcome was that it was possible with minimal modifications to the CallBuilder. See the erc20 example in that PR.

It is clear that we need a selector, arguments, and output type from the CallBuilder. All other information belongs to the executor/invoker and only defines the parameters of the call.

The substrate already has access to this information via ink-as-depdendecy, so it should be possible to do.

Again, ideally I would like to see a unified API for building contract calls for cross contract/e2e testing and calling contracts from substrate pallets. And I believe this will be possible while reusing most of the codegen we already have.

Maybe we could have something like this:

pub trait InkInvoker {
    fn invoke<Input, Output>(&self, selector: Selector, arguments: Input) -> Output {
        self.try_invoke(selector, arguments).expect("Got an error durign invoking")
    }
    
    fn try_invoke<Input, Output>(&self, selector: Selector, arguments: Input) -> Result<Output, SomeError>;
}

/// `ContractInvoker` has `AccountId`, some configuration about `CallType` and `CallFlags`.
impl<E: Environment> InkInvoker for ContractInvoker<E> {
    fn try_invoke<Input, Output>(&self, selector: Selector, arguments: Input) -> Result<Output, SomeError> {
        let params = CallParams::from(self, selector, arguments);
        <EnvInstance as OnInstance>::on_instance(|instance| {
            TypedEnvBackend::invoke_contract::<E, Input, Output>(instance, params)
        })
    }
}

/// `E2EDryRunInvoker` has `&E2EClient` and dry run configuration.
/// The same ideas is appliable to `E2ECallInvoker`.
impl InkInvoker for E2EDryRunInvoker {
    fn try_invoke<Input, Output>(&self, selector: Selector, arguments: Input) -> Result<Output, SomeError> {
        let exec_input = ExecInput::from(selector, arguments);
        self
         .api
         .call_dry_run(
             Signer::account_id(self.signer).clone(),
             dest,
             exec_input,
             self.value,
             self.storage_deposit_limit,
         )
    }
}

impl InkInvoker for SubstrateInvoker {
    ...
}

Selector, Input, and Output are the core of the any call. Having them, we can create an invoker instance for a specific method in a specific context. The usage transforms only into defining the right type and implementing the try_invoke function, but mostly it is one-time work.

@SkymanOne
Copy link
Contributor

SkymanOne commented Jul 30, 2023

I’m refactoring the envcrate to make agnostic of OnInstance implementation as part of the #1279. This might also be relevant for this issue

master...gn/configurable-buffer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
B-feature-request A request for a new feature.
Projects
None yet
Development

No branches or pull requests

8 participants