Interfaces

Interfaces are virtual wrappers around CosmWasm contracts. They allow you to interact with your contracts in a type-safe way, and provide a convenient way to reason about contract interactions. Interfaces are the core reason why we built cw-orchestrator and we hope that you’ll find them as useful as we do.

Setup

Before we can create an interface we need to add cw-orch to the contract’s Cargo.toml file. In counter run:

$ cargo add --optional cw-orch
> Adding cw-orch v0.13.3 to optional dependencies.

or add it manually to the counter/Cargo.toml file:

[dependencies]
cw-orch = {version = "0.13.3", optional = true } # Latest version at time of writing

We add cw-orch as an optional dependency to ensure that it is not included in the wasm artifact of the contract. This way there are no trust assumptions made about the code added by cw-orch, making it safe to use for production contracts.

However, we will need a way to enable the dependency when we want to use it. To do this add an interface feature to the Cargo.toml and enable cw-orch when it is enabled.

You can do this by including the following in the counter/Cargo.toml:

[features]
interface = ["dep:cw-orch"] # Adds the dependency when the feature is enabled

Features (aka. feature flags) are a way to enable or disable parts of your code. In this case, we are including cw-orch as a dependency when the interface feature is enabled. This is a common pattern for feature flags.

Creating an Interface

Now that we have our dependency set up we can create the interface. cw-orch provides two methods to easily create an interface for your contract.

The first is the interface_entry_point macro. This macro will generate an interface for your contract by calling it at the entry points of your contract. We’ll cover this macro first as it’s the easiest to use.

Alternatively you can also use the interface macro. This macro is more flexible and allows you to create an interface for your contract without having to call it at the entry points, as well as the ability to specify the contract’s source more easily. We’ll cover this macro in the end of this section.

Entry Point Macro

As mentioned this macro is the easiest to use. It will generate an interface for your contract by calling it at the entry points of your contract. Here’s an example of how to use it.

In counter/src/contract.rs:

#[cfg_attr(feature = "export", entry_point)]
#[cfg_attr(feature = "interface", cw_orch::interface_entry_point)]
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> Result<Response, ContractError> {
    let state = State {
        count: msg.count,
        owner: info.sender.clone(),
    };
    set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
    STATE.save(deps.storage, &state)?;

    Ok(Response::new()
        .add_attribute("method", "instantiate")
        .add_attribute("owner", info.sender)
        .add_attribute("count", msg.count.to_string()))
}

#[cfg_attr(feature = "export", entry_point)]
#[cfg_attr(feature = "interface", cw_orch::interface_entry_point)]
pub fn execute(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> Result<Response, ContractError> {
    match msg {
        ExecuteMsg::Increment {} => execute::increment(deps),
        ExecuteMsg::Reset { count } => execute::reset(deps, info, count),
    }
}

#[cfg_attr(feature = "export", entry_point)]
#[cfg_attr(feature = "interface", cw_orch::interface_entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::GetCount {} => to_binary(&query::count(deps)?),
    }
}

#[cfg_attr(feature = "export", entry_point)]
#[cfg_attr(feature = "interface", cw_orch::interface_entry_point)]
pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
    Ok(Response::default().add_attribute("action", "migrate"))
}

Most of this should look familiar but if you’re wondering about the two lines that contain #[...] here’s what they do:

  1. #[cfg_attr(feature = "export", entry_point)]

    This is a CosmWasm macro. It enables the Wasm runtime to call into the function. You can read more about the macro in the CosmWasm book. We only enable this macro when the export feature is enabled. This prevents conflicts with other entry points when the contract is a dependency of another contract.

  2. #[cfg_attr(feature = "interface", cw_orch::interface_entry_point)]

    This is the cw-orch provided macro. It will generate an interface for your contract by analyzing the messages passed to the entry points. This is possible because the entry point function definitions have strict parameter requirements. With this information the macro can generate a type safe interface for your contract. We only enable this macro when the interface feature is enabled.

Customizable Interface Macro

The second method to create an interface is the interface macro. To use it, first create a new file in the contract/src directory called interface.rs. This is where we will expose our interface.

In counter/src/lib.rs:

#[cfg(feature = "interface")]
mod interface;

Then in counter/src/interface.rs:

use cw_orch::{
    anyhow::Result,
    interface,
    prelude::{queriers::Node, *},
};

use crate::{
    contract::CONTRACT_NAME,
    msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg},
    CounterContract,
};

#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)]
pub struct Counter;

impl<Chain: CwEnv> Uploadable for Counter<Chain> {
    // Return the path to the wasm file
    fn wasm(&self) -> WasmPath {
        let crate_path = env!("CARGO_MANIFEST_DIR");
        let wasm_path = format!("{}/../artifacts/{}", crate_path, "mock.wasm");

        WasmPath::new(wasm_path).unwrap()
    }
    // Return a CosmWasm contract wrapper
    fn wrapper(&self) -> Box<dyn MockContract<Empty>> {
        Box::new(
            ContractWrapper::new_with_empty(
                crate::contract::execute,
                crate::contract::instantiate,
                crate::contract::query,
            )
            .with_migrate(crate::contract::migrate),
        )
    }
}

This use of the interface macro even allows you to have generic arguments in the message types. Any generics will be added to the interface under a PhantomData attribute.

Constructor

Both macros implement a new function on the interface:

    // Construct the counter interface
    let contract = CounterContract::new(CONTRACT_NAME, chain.clone());

The constructor takes two arguments:

  1. contract_id: The unique identifier for this contract. This is used as the key when retrieving address and code_id information for the contract.
  2. chain: The CosmWasm supported environment to use when calling the contract. Also includes the default sender information that will be used to call the contract.

Custom Functions

Now you can start implementing custom functions for your interfaces with ensured type safety.

The environments that are currently supported are:

  1. cw-multi-test by using Mock as the environment.
  2. Blockchain daemons like junod, osmosisd, etc. These use the Daemon environment.
  3. Chain-backed mock deps for unit-testing. This uses the MockQuerier that resolves all queries on a real node over gRPC.

Generic function

Generic functions can be executed over any environment. Setup functions are a good example of this.

/// Instantiate the contract in any CosmWasm environment
fn setup<Chain: CwEnv>(chain: Chain) -> CounterContract<Chain> {
    // Construct the counter interface
    let contract = CounterContract::new(CONTRACT_NAME, chain.clone());
    let admin = Addr::unchecked(ADMIN);

    // Upload the contract
    let upload_resp = contract.upload().unwrap();

    // Get the code-id from the response.
    let code_id = upload_resp.uploaded_code_id().unwrap();
    // or get it from the interface.
    assert_eq!(code_id, contract.code_id().unwrap());

    // Instantiate the contract
    let msg = InstantiateMsg { count: 1i32 };
    let init_resp = contract.instantiate(&msg, Some(&admin), None).unwrap();

    // Get the address from the response
    let contract_addr = init_resp.instantiated_contract_address().unwrap();
    // or get it from the interface.
    assert_eq!(contract_addr, contract.address().unwrap());

    // Return the interface
    contract
}

Daemon-only functions

impl Counter<Daemon> {
    /// Deploys the counter contract at a specific block height
    pub fn await_launch(&self) -> Result<()> {
        let daemon = self.get_chain();
        let rt = daemon.rt_handle.clone();

        rt.block_on(async {
            // Get the node query client, there are a lot of other clients available.
            let node = daemon.query_client::<Node>();
            let mut latest_block = node.latest_block().await.unwrap();

            while latest_block.header.height.value() < 100 {
                // wait for the next block
                daemon.next_block().unwrap();
                latest_block = node.latest_block().await.unwrap();
            }
        });

        let contract = CounterContract::new(CONTRACT_NAME, daemon.clone());

        // Upload the contract
        contract.upload().unwrap();

        // Instantiate the contract
        let msg = InstantiateMsg { count: 1i32 };
        contract.instantiate(&msg, None, None).unwrap();

        Ok(())
    }
}

Entry Point Function Generation

Contract execution and querying is so common that we felt the need to improve the method of calling them. To do this we created two macros: ExecuteFns and QueryFns. As their name implies they can be used to automatically generate functions for executing and querying your contract through the interface.

Execution

To get started, find the ExecuteMsg definition for your contract. In our case it’s located in contracts/counter/src/msg.rs. Then add the following line to your ExecuteMsg enum:

#[cw_serde]
#[cfg_attr(feature = "interface", derive(cw_orch::ExecuteFns))] // Function generation
pub enum ExecuteMsg {
    Increment {},
    Reset { count: i32 },
}

Again we feature flag the function generation to prevent cw-orchestrator entering as a dependency when building your contract.

The functions are implemented as a trait named ExecuteMsgFns which is implemented on any interface that uses this ExecuteMsg.

Using the trait then becomes as simple as:

// in integration_tests.rs
    // Reset
    use counter_contract::CounterExecuteMsgFns;
    contract.reset(0).unwrap();

    let count = contract.get_count().unwrap();
    assert_eq!(count.count, 0);

Query

Generating query functions is a similar process but has the added advantage of using the cosmwasm-schema return tags to detect the query’s return type. This allows for type-safe query functions!

#[cw_serde]
#[cfg_attr(feature = "interface", derive(cw_orch::QueryFns))] // Function generation
#[derive(QueryResponses)]
pub enum QueryMsg {
    // GetCount returns the current count as a json-encoded number
    #[returns(GetCountResponse)]
    GetCount {},
}

// Custom response for the query
#[cw_serde]
pub struct GetCountResponse {
    pub count: i32,
}

Using it is just as simple as the execution functions:

// in integration_tests.rs
    // Get the count.
    use counter_contract::CounterQueryMsgFns;
    let count1 = contract.get_count().unwrap();

Just like the interface it can be beneficial to re-export the trait in your lib.rs or interface.rs file.

In the counter contract we re-export in lib.rs;

#[cfg(feature = "interface")]
pub use crate::msg::{ExecuteMsgFns as CounterExecuteMsgFns, QueryMsgFns as CounterQueryMsgFns};

impl_into Attribute

For nested messages (execute and query) you can add an impl_into attribute. This expects the enum to implement the Into trait for the provided type. This is extremely useful when working with generic messages:

#![allow(unused)]
fn main() {
use cw_orch::interface;
use cw_orch::prelude::*;

// An execute message that is generic.
#[cosmwasm_schema::cw_serde]
pub enum GenericExecuteMsg<T> {
    Generic(T),
}

// Now the following is possible:
type ExecuteMsg = GenericExecuteMsg<Foo>;

#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
#[impl_into(ExecuteMsg)]
pub enum Foo {
    Bar { a: String },
}

impl From<Foo> for ExecuteMsg {
    fn from(msg: Foo) -> Self {
        ExecuteMsg::Generic(msg)
    }
}

#[interface(Empty, ExecuteMsg, Empty, Empty)]
struct Example<Chain>;

impl<Chain: CwEnv> Example<Chain> {
    pub fn test_macro(&self) {
        // function `bar` is available because of the `impl_into` attribute!
        self.bar("hello".to_string()).unwrap();
    }
}
}

Learn more

Got questions? Join the Abstract Discord and ask in the #cw-orchestrator channel. Learn more about Abstract at abstract.money.

References