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.

Creating an Interface

Now that we have our filesystem and crate setup, we are able to create our contract interface using the cw-orch::interface macro. It 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.

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

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

pub const CONTRACT_ID: &str = "counter_contract";

#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)]
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),
        )
    }
}

The 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:

 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(chain.clone());

The constructor takes one argument:

  • 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

NOTE: If you prefer working with different contract addresses for the same contract interface, you can remove the id argument in the interface macro:

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

The generated constructor will now take 2 arguments, the contract_id and the chain. This contract_id will allow you to specify which contract you want to interact with.

let contract = CounterContract::new("specific_counter_contract", chain.clone());

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(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

Tired of having to use endless schemas? Tired of having to redeclare your field names every time you want to declare an struct?
{
    "swap": {
        "offer_asset": {
            "native":{
                "denom":"ujuno"
            }
        },
        "ask_asset": {
            "native":{
                "denom":"uluna"
            }
        },
        "amount": "3465"
    }
}
dex::core::swap::ExecuteMsg::Swap{
    offer_asset: CwAsset::Native("ujuno"),
    ask_asset: CwAsset::Native("uluna"),
    amount: 3465u128.into()
}

Learn more in the next section about entry-point functions how to do just that!

Learn more

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

References