Skip to content

IBC‐go `08‐wasm` Guide

Abdullah Eryuzlu edited this page Mar 8, 2024 · 2 revisions

Introduction

Since the beginning of ibc-go, native light client implementations that are embedded into the chain's code was the only way of developing a light client. These had a few downsides:

  1. Light client code updates were only possible by doing a chain-upgrade.
  2. It was hard to deploy a new light client code to a chain. Because for this, you would have to convince the chain to do an upgrade with your light client implementation. And this is harder than developing a light client.

The fact that IBC started to expand to the other ecosystems brought the need of having a way of uploading light client codes without needing to do a chain upgrade. This is exactly the purpose of 08-wasm module which enables wasm smart contracts to be deployed.

Architecture Overview

Before digging into the implementation details, let's see how wasm light client support is achieved.

The module uses libwasmvm under the hood which is developed for CosmWasm. The main difference between the traditional CosmWasm and wasm smart contracts is you can't run submessages in wasm smart contracts and you have a more strict querier which we will get into that later.

The module is very simple to integrate to. The smart contract is being called when a light client specific computation is needed to be done such as header or membership verification. Other than that, the 08-wasm module handles all the internal routing and boilerplate itself. It provides an API for the light clients to implement which is passed into the contract as a message like how the regular CosmWasm functions.

TODO: mermaid

Implementation Details

API

There are read-only and read-write API's that are needed to be implemented. Read-write API's are called via using the sudo entrypoint while the read-only API's are being called via using the query entrypoint. The implementation should handle all the messages as if it's a regular CosmWasm contract. Here is an example:

// Go to the following link to see all the sudo messages.
// https://github.com/cosmos/ibc-go/blob/wasm-v8.0.0/modules/light-clients/08-wasm/types/contract_api.go#L52
fn sudo(
    deps: DepsMut<Self::CustomQuery>,
    env: Env,
    msg: SudoMsg,
) -> Result<Binary, Error> {
    match msg {
       SudoMsg::VerifyMembership { ... } => {}
       SudoMsg::UpdateState { ... } => {}
       // ...other messages
    }
}

// Go to the following link to see all the query messages.
// https://github.com/cosmos/ibc-go/blob/wasm-v8.0.0/modules/light-clients/08-wasm/types/contract_api.go#L19
fn query(
    deps: Deps<Self::CustomQuery>,
    env: Env,
    msg: QueryMsg,
) -> Result<Binary, Error> {
    match msg {
        QueryMsg::VerifyClientMessage { ... } => { }
        QueryMsg::CheckForMisbehaviour { ... } => { }
        // ...other messages
    }
}

The contract API defines all the messages that are needed to be implemented.

Instantiate the contract

The light client contract needs to get instantiated first. We implement the following entrypoint for this:

#[entry_point]
pub fn instantiate(
    mut deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, Error>;

InstantiateMessage is passed to this entrypoint as the msg field.

Here is the definition of the InstantiateMessage.

type InstantiateMessage struct {
	ClientState    []byte `json:"client_state"`
	ConsensusState []byte `json:"consensus_state"`
	Checksum       []byte `json:"checksum"`
}

The light client can do sanity checks here but it's mandatory for it to commit the states to the correct store paths with the correct format.

Note on client and consensus states

Client and consensus states are defined as follows:

// https://github.com/cosmos/ibc-go/blob/wasm-v8.0.0/modules/light-clients/08-wasm/types/wasm.pb.go#L28
type ClientState struct {
	// bytes encoding the client state of the underlying light client
	// implemented as a Wasm contract.
	Data         []byte       `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
	Checksum     []byte       `protobuf:"bytes,2,opt,name=checksum,proto3" json:"checksum,omitempty"`
	LatestHeight types.Height `protobuf:"bytes,3,opt,name=latest_height,json=latestHeight,proto3" json:"latest_height"`
}
// https://github.com/cosmos/ibc-go/blob/wasm-v8.0.0/modules/light-clients/08-wasm/types/wasm.pb.go#L70
type ConsensusState struct {
	// bytes encoding the consensus state of the underlying light client
	// implemented as a Wasm contract.
	Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
}

Note that both the client state and the consensus state are wrapped with the ClientState and ConsensusState that are defined under the wasm types. The light client developer can choose what concrete ClientState and ConsensusState they use and how it is being encoded. The data fields in both of the data types will store the concrete state and it will be treated as opaque bytes.

The most important think to note here is the light client MUST save its state under the corresponding key with the following format:

Any {
  type_url: "..",
  value: ProtoEncoded(wasm.ConsensusState {
    data: [...]
  }) 
}

The commitment keys are defined in the IBC spec.