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.

Reminder: You can find the code for this example in the cw-orch counter-contract folder.

If you are a fast or visual learner, you can find a Before-After view of the cw-orch integration process in the sample contract.

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.17.0 to optional dependencies.

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

[dependencies]
cw-orch = {version = "0.17.0", 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 a powerful interface macro that 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.

To use this macro, 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;

This allows importing the interface.rs file only when the interface feature is enabled on the contract. Again, this is done because we don’t want cw-orch dependencies to end up in your resulting Wasm contract. Using this syntax won’t change anything to the actual wasm file that you are uploading on-chain if you don’t enable this feature by default.

Then in counter/src/interface.rs:

use cw_orch::{interface, prelude::*};

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

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

impl<Chain: CwEnv> Uploadable for CounterContract<Chain> {
    /// Return the path to the wasm file corresponding to the contract
    fn wasm(&self) -> WasmPath {
        artifacts_dir_from_workspace!()
            .find_wasm_path("counter_contract")
            .unwrap()
    }
    /// Returns 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.

It can be beneficial to re-export the structure in our lib.rs file.

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

#[cfg(feature = "interface")]
pub use crate::interface::CounterContract;

NOTE: You can see that we have used the artifacts_dir_from_workspace macro inside the wasm trait function. This macro helps you locate the workspace artifacts folder. It actually looks for any directory named artifacts from the root of the current crate going up. For instance if the project is located in /path1/path2/counter, it will look for the artifacts folder inside the following directories in order and return as soon as it finds such a folder:

  • /path1/path2/counter
  • /path1/path2
  • /path1/

This works for single contracts as well as workspace setups. If you have a specific setup, you can still specify the path yourself. If you do so, we advise indicating the wasm location from the current crate directory, using something like:

#![allow(unused)]
fn main() {
 let crate_path = env!("CARGO_MANIFEST_DIR");
 let wasm_path = format!("{}/../../artifacts/counter_contract.wasm", crate_path);
 WasmPath::new(wasm_path).unwrap()
}

Constructor

The interface macro implements 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. This argument is a &str.
  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. You can find more information later in the Integrations section for how to create this chain variable

Interacting with your contracts

Now, you are able to interact directly with your contracts with ensured type safety.

The environments that are currently supported are:

  1. cw-multi-test by using Mock as the chain variable.
  2. Actual Cosmos SDK nodes for interacting with lives chains (mainnet, testnet, local). Use Daemon as the chain variable.
  3. osmosis-test-tube or testing against actual chain binaries. This allows for fast testing with actual on-chain modules. This is particularly useful when testing against chain-specific modules. Use OsmosisTestTube as the chain variable.

Generic functions

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) -> anyhow::Result<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()?;

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

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

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

    // Return the interface
    Ok(contract)
}

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 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
/// Execute methods for counter
pub enum ExecuteMsg {
    /// Increment count by one
    Increment {},
    /// Reset count
    Reset {
        /// Count value after 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 as an entrypoint message.

Using the trait then becomes as simple as:

    // in integration_tests.rs
    // Reset
    use counter_contract::CounterExecuteMsgFns;
    contract.reset(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)]
/// Query methods for counter
pub enum QueryMsg {
    /// GetCount returns the current count as a json-encoded number
    #[returns(GetCountResponse)]
    GetCount {},
}

// Custom response for the query
#[cw_serde]
/// Response from get_count query
pub struct GetCountResponse {
    /// Current count in the state
    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()?;

    // or query it manually
    let count2: GetCountResponse = contract.query(&QueryMsg::GetCount {})?;
    assert_eq!(count1.count, count2.count);

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};

Additional Remarks on QueryFns and ExecuteFns

The QueryFns and ExecuteFns derive macros generate traits that are implemented on any Contract structure (defined by the interface macro) that have the matching execute and query types. Because of the nature of rust traits, you need to import the traits in your application to use the simplifying syntax. Those traits are named ExecuteMsgFns and QueryMsgFns.

Any variant of the ExecuteMsg and QueryMsg that has a #[derive(ExecuteFns)] or #[derive(QueryFns)] will have a function implemented on the interface (e.g. CounterContract) through a trait. Here are the main things you need to know about the behavior of those macros:

  • The function created will have the snake_case name of the variant and will take the same arguments as the variant.
  • The arguments are ordered in alphabetical order to prevent attribute ordering from changing the function signature.
  • If coins need to be sent along with the message you can add #[payable] to the variant and the function will take a Vec<Coin> as the last argument.
  • The cw_orch::QueryFns macro needs your QueryMsg struct to have the cosmwasm_schema::QueryResponses macro implemented (this is good practice even outside of use with cw-orch).

Additional configuration

payable Attribute

Let’s see an example for executing a message (from a money market for instance).

    money_market.deposit_stable()?;

There’s a problem with the above function. The money market only knows how much you deposit into it by looking at the funds you send along with the transaction. Cw-orchestrator doesn’t ask for funds by default. However, to allow attaching funds to a transaction, you can add the #[payable] attribute on your enum variant like so:

    #[derive(ExecuteFns)]
    enum ExecuteMsg{
        UpdateConfig{
            config_field: String
        },
        #[payable]
        DepositStable{}
        ...
    }

Be defining this attribute, you can now use:

    use cosmwasm_std::coins;
    money_market.deposit_stable(&coins(456, "ujunox"))?;

fn_name Attribute

#![allow(unused)]
fn main() {
#[derive(cw_orch::ExecuteFns)] 
pub enum ExecuteMsg{
    Execute{
        msg: CosmoMsg
    }
}
}

The following command will error because the execute function is reserved for contract execution. This will not even compile actually.

#![allow(unused)]
fn main() {
money_market.execute(message_to_execute_via_a_proxy)?;
}

This can happen in numerous cases actually, when using reserved keywords of cw-orch (or even rust). If this happens, you can use the fn_name attribute to rename a generated function.

#![allow(unused)]
fn main() {
#[derive(cw_orch::ExecuteFns)] 
pub enum ExecuteMsg{
    #[fn_name("proxy_execute")]
    Execute{
        msg: CosmoMsg
    }
}
// This works smoothly !
money_market.proxy_execute(message_to_execute_via_a_proxy)?;
}

This is also true for query functions.

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),
    Nested(NestedMessageType),
}

// This is the message that will be used on our contract
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)
    }
}

#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
#[impl_into(ExecuteMsg)]
pub enum NestedMessageType {
    Test { b: u64 },
}

impl From<NestedMessageType> for ExecuteMsg {
    fn from(msg: NestedMessageType) -> Self {
        ExecuteMsg::Nested(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();

        // function `test` is available because of the `impl_into` attribute!
        self.test(65).unwrap();
    }
}

}

disable_fields_sorting Attribute

By default the ExecuteFns and QueryFns derived traits will sort the fields of each enum member. For instance,

#![allow(unused)]
fn main() {
#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum ExecuteMsgOrdered {
    Test { b: u64, a: String },
}
}

will generate

#![allow(unused)]
fn main() {
pub fn bar(a: String, b: u64) -> ...{
   ...
} 
}

You see in this example that the fields of the bar function are sorted lexicographically. We decided to put this behavior as default to prevent potential errors when rearranging the order of enum fields. If you don’t want this behavior, you can disable it by using the disable_fields_sorting attribute. This is the resulting behavior:

#![allow(unused)]
fn main() {
#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
#[disable_fields_sorting]
pub enum ExecuteMsg {
    Test { b: u64, a: String },
}

 
 pub fn bar(b: u64, a: String) -> ...{
    ...
 } 
}

NOTE: This behavior CAN be dangerous if your struct members have the same type. In that case, if you want to rearrange the order of the members inside the struct definition, you will have to be careful that you respect the orders in which you want to pass them.

Learn more

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

References