Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

https://github.com/Cardinal-Cryptography/ink-wrapper

Generator for typed wrappers for calling substrate smart contracts
https://github.com/Cardinal-Cryptography/ink-wrapper

Last synced: 4 days ago
JSON representation

Generator for typed wrappers for calling substrate smart contracts

Lists

README

        

# ink-wrapper

![ink-wrapper](https://img.shields.io/crates/v/ink-wrapper.svg)

`ink-wrapper` is a tool that generates type-safe code for calling a substrate smart contract based on the metadata
(`.json`) file for that contract.

## Installation

Install the tool from [crates.io](https://crates.io):

```bash
cargo +nightly-2023-04-19 install ink-wrapper --locked --force
```

Note that this uses `nightly-2023-04-19`. You will need to install the toolchain first. You may try with just `+nightly` to use whatever nightly you already have installed, but this particular version should work. The crate will most likely compile on another toolchain, but generate broken code afterwards, see https://github.com/udoprog/genco/issues/39.

## Usage

### Note on compatibility.

The last release compatible with `aleph_client` is version [`0.6.0`](https://crates.io/crates/ink-wrapper-types/0.6.0). Note though that it's compatible with `aleph-client` in version `3.0.0` as this is the last version available in [crates.io](https://crates.io/crates/aleph_client/versions). We do not guarantee it will work with Testnet or Mainnet since their runtimes may differ in ways that are not compatible with `aleph_client 3.0.0`.

Current release focuses on compatibility with [drink](https://crates.io/crates/drink).

Future releases will try to address the support for live chains.

### Setup

Given some metadata file like `my_contract.json` run the tool and save the output to a file in your project:

```bash
ink-wrapper -m my_contract.json > src/my_contract.rs
```

We only take minimal steps to format the output of the tool, so we recommend that you run it through a formatter when
(re)generating:

```bash
ink-wrapper -m my_contract.json | rustfmt --edition 2021 > src/my_contract.rs
```

The output should compile with no warnings, please create an issue if any warnings pop up in your project in the
generated code.

Make sure the file you generated is included in your module structure:

```rust
mod my_contract;
```
#### DRink!

Add the following to your `Cargo.toml:
```toml
[dependencies]
drink = "0.8.6"
ink_primitives = "4.3.0"
ink-wrapper-types = { version = "0.7.0", default-feauters = false, features = [ "drink" ] }
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = [ "derive" ] }
```

After generating the wrappers, the `my_contract` will contain `Instance` struct which represents the contract's state.

The easiest way to write a test is to use drink's unit test macro which will set up the `drink::session::Sessin` object:
```rust
// Auto-generated wrappers need to be added to the crate.
mod my_contract;

// Minimal imports
use ink_wrapper_types::{Connection, ToAccountId};
use drink::{session::Session, AccountId32};

#[drink::test]
fn my_test(mut session: Session) {
// Upload code to DRink! backend
let _code_hash = session.upload_code(my_contract::upload()).expect("Upload to succeed");

// Instantiate the contract.
let address = session.instantiate(my_contract::Instance::new(1000))
.expect("No pallet-contract errors")
.result // AccountId, address, of the new instance
.to_account_id() // Map to ink_primitives type
.into();

// Now we can call contract's methods. They're provided by the trait `my_contract::MyContract` (depends on your actual contract name)
use my_contract::MyContract as _;

// Construct the call object.
let exec_call = address.some_exec_call();

// Execute it.
let res = session.execute(exec_call);
}
```

For more comprehensive examples on actual contract wrappers, see `tests` directory.

#### `aleph_client` (deprecated from `0.7.0`)

You will need the following dependencies for the wrapper to work:

```toml
ink-wrapper-types = "0.6.0"
scale = { package = "parity-scale-codec", version = "3", default-features = false, features = ["derive"] }
ink_primitives = "4.2.1"

# You only need this one if you have messages of the form `Trait::message`, like the ones generated by openbrush, for
# example.
async-trait = "0.1.68"

# This one is optional, but you most likely need it as well if you're using the default `aleph_client` implementation
# for actually making calls. Otherwise, you will need to implement `ink_wrapper_types::Connection` and
# `ink_wrapper_types::SignedConnection` yourself.
aleph_client = "3.0.0"
```

##### Basic usage

With that, you're ready to use the wrappers in your code. The generated module will have an `Instance` struct that
represents an instance of your contract. You can either talk to an existing instance by converting an `account_id` to
an `Instance`:

```rust
let account_id: ink_primitives::AccountId = ...;
let instance: my_contract::Instance = account_id.into();
```

Or (assuming the contract code has already been uploaded) create an instance using one of the generated constructors:

```rust
let instance = conn.instantiate(my_contract::Instance::some_constructor(arg1, arg2)).await?;
```

And then call methods on your contract:

```rust
let result = conn.read(instance.some_getter(arg1, arg2)).await?;
let tx_info = conn.exec(instance.some_mutator(arg1, arg2)).await?;
```

Note that any methods that have names like `Trait::method_name` will be grouped into traits in the generated module. You
might encounter this if you're using openbrush, for example their `PSP22` implementation generates method names like
`PSP22::balance_of`. You need to `use` the generated traits to access these:

```rust
use my_contract::PSP22 as _;
conn.read(instance.balance_of(account_id)).await?
```

In the examples above, `conn` is anything that implements `ink_wrapper_types::Connection` (and
`ink_wrapper_types::SignedConnection` if you want to use constructors or mutators). Default implementations are provided
for the connection in `aleph_client`.

##### Events

`ink_wrapper_types::Connection` also allows you to fetch events for a given `TxInfo`:

```rust
use ink_wrapper_types::Connection as _;

let tx_info = conn.exec(instance.some_mutator(arg1, arg2)).await?;
let all_events = conn.get_contract_events(tx_info).await?;
let contract_events = all_events.for_contract(instance);
let sub_contract_events = all_events.for_contract(sub_contract);
```

The `all_events` object above may contain events from multiple contracts if the contract called into them. In that case,
you can filter and parse these events by calling `for_contract` on it, with the various contracts you're interested in.

##### Code upload

If you provide a compile-time path to the compiled `WASM`:

```bash
ink-wrapper -m my_contract.json --wasm-path ../contracts/target/ink/my_contract.wasm
```

you will also be able to use the generated wrapper to upload the contract:

```rust
conn.upload(my_contract::upload()).await
```

Note, that the `upload` function will return `Ok(TxInfo)` so long as the transaction was submitted successfully and the
code hash of the metadata matches the uploaded code. If the code already existed on the chain, no error is returned. You
can verify this condition yourself by looking at the events at the returned `TxInfo` and checking if they contain a
`CodeStored` event.

### Example

Look at `tests` in the project's repo for a fuller example. Note that `tests/drink` is missing the actual
wrappers, which are normally generated when testing. The easiest way to regenerate them is by running
`make all-dockerized` (requires docker) - see [Development](#development) for more on that.

## Development

Use the commands provided in the `Makefile` to replicate the build process run on CI:

```bash
make help
```

The most hassle-free is to just run everything in docker:

```bash
make all-dockerized
```

If you have the tooling installed on your host and start a node yourself, you can also run the build on your host:

```bash
make all
```

In case there are any runaway containers from `all-dockerized` you can kill them:

```bash
make kill
```