Cw-Orchestrator

docs.rs Crates.io Codecov

cw-orchestrator is an advanced testing and deployment tool for CosmWasm smart-contracts. It’s designed to make it easy to test and deploy contracts in a variety of environments including cw-multi-test, local, testnet, and mainnet. It does this by providing the ability to write environment-generic code that interacts with CosmWasm contracts and by doing so removing the need to keep maintain deployment code for multiple environments. In short, cw-orchestrator should be your go-to tool for testing and deploying CosmWasm contracts.

Features

  • Testing: cw-orchestrator provides a testing framework that makes it easy to write tests for CosmWasm contracts. It does this by providing a testing environment that mimics the behavior of a CosmWasm blockchain. This allows you to write tests that interact with your contract in the same way that it would be interacted with on a real blockchain. These kinds of tests allow developers to more easily test contract-to-contract interactions without having to deal with the overhead of running a local node. The testing framework also provides a number of utilities that make it easy to write tests for CosmWasm contracts. These utilities include the ability to easily set and query balances, set block height/time and more. Additionally by creating a wrapper interface around a project’s deployment developers can share their testing infrastructure with other developers, allowing them to easily test how their contracts interact with the project.

  • Deployment: cw-orchestrator also provides the ability to deploy to real networks. It does this by providing an easy to use interface to a blockchain node that can be used to submit transactions, query state and inspect transaction results.

  • Interface Generation: Interacting with a smart-contract is often verbose, leading to a lot of boilerplate code. cw-orchestrator solves this problem by providing a macro that generates an interface to a contract. This interface can be used to easily interact with a contract and improves the readability of your tests and deployments. Making it easier to write and maintain tests and deployments. Additionally, because this library is written in Rust, any breaking changes to your contract’s interface will cause a compile-time error, making it easy to keep your tests and deployments up to date.

Getting Started

These docs contain a quick-start and a longer tutorial-style walkthrough.

Quick-Start Guide

This guide will show you how to use the cw-orchestrator with your smart contract. Follow the steps below to add cw-orch to your contract’s TOML file, enable the interface feature, add the interface macro to your contract’s endpoints, and use interaction helpers to simplify contract calls and queries.

Adding cw-orch to Your Cargo.toml File

To use the cw-orchestrator, you need to add cw-orch to your contract’s TOML file. Run the command below in your contract’s directory:

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

Alternatively, you can add it manually in your Cargo.toml file as shown below:

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

Now that we have added cw-orch as an optional dependency we will want to enable it through a feature. This ensures that the code added by cw-orch is not included in the wasm artifact of the contract. To do this add an interface feature to the Cargo.toml and enable cw-orch when it is enabled.

To do this include the following in the Cargo.toml:

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

Creating an Interface

Now that we have the dependency set up you can add the interface_entry_point macro to your contract’s entry points. This macro will generate an interface to your contract that you will be able to use to interact with your contract. Get started by adding the feature-flagged interface macro to the contract’s entry points:

use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, StdResult};
pub struct InstantiateMsg;
pub struct ExecuteMsg;

// In `contract.rs`
#[cfg_attr(feature="interface", cw_orch::interface_entry_point)] // <--- Add this line
pub fn instantiate(
   deps: DepsMut,
   env: Env,
   info: MessageInfo,
   msg: InstantiateMsg,
) -> StdResult<Response> {
    // ...
    Ok(Response::new())
}

#[cfg_attr(feature="interface", cw_orch::interface_entry_point)] // <--- Add this line
pub fn execute(
   deps: DepsMut,
   env: Env,
   info: MessageInfo,
   msg: ExecuteMsg,
) -> StdResult<Response> {
    // ...
    Ok(Response::new())
}

fn main() {}
// ... Do the same for the other entry points (query, migrate, reply, sudo)

By adding these lines, we generate code whenever the interface feature is enabled. The code generates a contract interface, the name of which will be the PascalCase of the crate’s name.

When uploading to a blockchain the marco will search for an artifacts directory in the project’s root. If this is not what you want you can specify the paths yourself using the interface macro covered in interfaces.

The name of the crate is defined in the Cargo.toml file of your contract.

It can be helpful to re-expose the interface in the crate’s root so that it is easy to import:

// in lib.rs
#[cfg(feature = "interface")]
pub use crate::contract::MyContract

You can now create a test in contract/tests or an executable in contract/bin and start interacting with the contract.

Interaction helpers

cw-orchestrator provides an additional macro to simplify contract calls and queries. The macro generates functions on the interface for each variant of the contract’s ExecuteMsg and QueryMsg.

Enabling this functionality is very straight-forward. Find your ExecuteMsg and QueryMsg definitions and add the ExecuteFns and QueryFns derive macros to them like below:

use cosmwasm_schema::{QueryResponses, cw_serde};

#[cfg_attr(feature = "interface", derive(cw_orch::ExecuteFns))]
#[cw_serde]
pub enum ExecuteMsg {
    Increment {},
    // ...
}

#[cfg_attr(feature = "interface", derive(cw_orch::QueryFns))]
#[derive(QueryResponses)]
#[cw_serde]
pub enum QueryMsg {
    #[returns(String)]
    Config {}
    // ...
}

fn main() {}

Any variant of the ExecuteMsg and QueryMsg that has a #[derive(ExecuteFns)] or #[derive(QueryFns)] will have a function implemented on the interface through a trait. The function 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.

You can access these functions by importing the generated traits form the message file. The generated traits are named ExecuteMsgFns and QueryMsgFns. Again it’s helpful to re-export these traits in the crate’s root so that they are easy to import:

// in lib.rs
#[cfg(feature = "interface")]
pub use crate::msg::{ExecuteMsgFns as MyContractExecuteFns, QueryMsgFns as MyContractQueryFns};

Example Counter Contract

To show all this functionality in action, we will use an example counter contract. The example counter contract is a simple contract that allows you to increment and decrement a counter. The contract also allows you to query the current value of the counter. The contract is available here.

We have already added the interface_entry_point macro to the contract’s endpoints. We can now create a test in contract/tests to interact with the contract. The test will use the Mock struct from cw-orchestrator to mock the environment and the CounterContract struct generated by the interface_entry_point macro to interact with the contract.

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

#[test]
fn count() {
    // Create a sender
    let sender = Addr::unchecked(ADMIN);
    // Create the mock
    let mock = Mock::new(&sender);

    // Set up the contract
    let contract = setup(mock.clone());

    // Increment the count of the contract
    contract
        // Set the caller to user
        .call_as(&Addr::unchecked(USER))
        // Call the increment function (auto-generated function provided by CounterExecuteMsgFns)
        .increment()
        .unwrap();

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

    // or query it manually
    let count2: GetCountResponse = contract.query(&QueryMsg::GetCount {}).unwrap();

    assert_eq!(count1, count2);

    // Check the count
    assert_eq!(count1.count, 2);
    // Reset
    use counter_contract::CounterExecuteMsgFns;
    contract.reset(0).unwrap();

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

Tutorial

This tutorial will guide you through setting up a single contract for use with cw-orchestrator. By the end of this tutorial you should be able to:

  • Write deployment scripts for your contract.
  • Write integration tests for your contract.
  • Write executables for interacting with your contract.

In order to ensure that the code snippets shown here are correct we’ll be using the counter contract provided in the repository as the source for our code-snippets. You can find the contract here.

If you’re working within a cargo workspace environment you can follow along and read the Workspace docs after this tutorial.

Prerequisites

  • Contract Entry Point Messages: In order to use cw-orchestrator you need access to the entry point message types (InstantiateMsg,ExecuteMsg,…) of the contracts you want to interact with. Having them locally will enable you to generate helper functions for interacting with the contracts.

  • A gRPC endpoint (optional): If you want to perform on-chain transaction you will need access to the gRPC endpoint of a node. These are most-often available on port 9090. Look in the documentation of the chain you want to connect to if our defaults aren’t sufficient, or use the Cosmos Directory implementation to query one.

  • A desire to learn: This tutorial will cover the basics of using cw-orchestrator but it won’t cover everything. If you want to learn more about the features of cw-orchestrator you can check out the API Documentation.

The following sections detail setting up a contract, tests for the contract, and scripts for interacting with the contract on a blockchain network.

Following this example, the directory structure should eventually look like:

.
├── Cargo.toml
├── artifacts
│   └── counter.wasm (binary file)
└── counter
    ├── Cargo.toml
    ├── bin
    │   └── deploy.rs
    └── src
        ├── contract.rs (execute, instantiate, query, ...)
        └── msg.rs
        └── ..

Sections

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

Environment Variables

cw-orch needs some environment variables to be set in order to function properly when running as an executable.

IMPORTANT: Before proceeding, ensure that you add .env to your .gitignore. We are not responsible for any loss of funds due to leaked mnemonics.

Here are the optional environment variables:

# .env

# info, debug, trace (if using env_logger for logging)
RUST_LOG=info

# Where the contract wasms are located (used by ArtifactsDir::env())
ARTIFACTS_DIR="../artifacts"

# where to store the state of your deployments (default: ./state.json)
STATE_FILE="./daemon_state.json"

# Mnemonics of the account that will be used to sign transactions
# Can optionally be set on DaemonBuilder as well.
LOCAL_MNEMONIC=""
TEST_MNEMONIC=""
MAIN_MNEMONIC=""

Mnemonics

Only 24-word mnemonics are supported at this time. If you’re experienced with keychain and private key management we’d really appreciate your help in adding support for other formats. Please reach out to us on Discord if you’re interested in helping out.

Writing and Executing Scripts

Now that we have the interface written for our contract, we can start writing scripts to deploy and interact with it on a real blockchain. We’ll do this by adding a examples folder in our contract and add our deploy script there.

Setup

Before we get going we need to add the examples folder and tell cargo that it contains scripts. We can do this by creating a folder named examples in counter and creating a file in it called deploy.rs

mkdir counter/examples
touch counter/examples/deploy.rs

Then we want to add the required dependencies to the dev-dependencies section of our Cargo.toml file. We’ll need dotenv to load our environment variables and env_logger to log to stdout. We’re using examples instead of bin because setting a feature on an optional dependency is not supported.

[dev-dependencies]
# Deps for deployment
dotenv = { version = "0.15.0" } # Enables loading of .env files
env_logger = { version = "0.10.0" } # Enables logging to stdout

Finally, we need to add the examples to our Cargo.toml file. Add put a feature requirement on it:

[[example]]
name = "deploy"
path = "examples/deploy.rs"

Now we’re ready to start writing our script.

Main Function

With the setup done, we can start writing our script. Our initial plan is to deploy the counter contract to the chain. We’ll start by writing a main function that will call our deploy function.

use counter_contract::{msg::InstantiateMsg, CounterContract};
use cw_orch::{anyhow, prelude::*, tokio};
use tokio::runtime::Runtime;

/// Script that registers the first Account in abstract (our Account)
pub fn main() -> anyhow::Result<()> {
    dotenv::dotenv().ok();
    env_logger::init();

    let rt = Runtime::new()?;
    let network = networks::LOCAL_JUNO;
    let chain = DaemonBuilder::default()
        .handle(rt.handle())
        .chain(network)
        .build()?;

    let counter = CounterContract::new("counter_contract", chain);

    counter.upload()?;
    counter.instantiate(&InstantiateMsg { count: 0 }, None, None)?;
    Ok(())
}

Synchronous Daemon

Asynchronous Daemon

Integration Tests

Integration tests are very easy to write with cw-orch. Start by creating a tests folder in your contract’s dir.

mkdir counter/tests

Then create a file called integration_tests.rs in the tests folder.

touch counter/tests/integration_tests.rs

Now we can write our tests. Here’s an example of a test that deploys the contract, increments the counter and then resets it.

use counter_contract::{
    contract::CONTRACT_NAME,
    msg::{GetCountResponse, InstantiateMsg, QueryMsg},
    CounterContract,
};
// Use prelude to get all the necessary imports
use cw_orch::prelude::*;

use cosmwasm_std::Addr;

// consts for testing
const USER: &str = "user";
const ADMIN: &str = "admin";
/// 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
}

#[test]
fn count() {
    // Create a sender
    let sender = Addr::unchecked(ADMIN);
    // Create the mock
    let mock = Mock::new(&sender);

    // Set up the contract
    let contract = setup(mock.clone());

    // Increment the count of the contract
    contract
        // Set the caller to user
        .call_as(&Addr::unchecked(USER))
        // Call the increment function (auto-generated function provided by CounterExecuteMsgFns)
        .increment()
        .unwrap();

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

    // or query it manually
    let count2: GetCountResponse = contract.query(&QueryMsg::GetCount {}).unwrap();

    assert_eq!(count1, count2);

    // Check the count
    assert_eq!(count1.count, 2);
    // Reset
    use counter_contract::CounterExecuteMsgFns;
    contract.reset(0).unwrap();

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

Workspace Tutorial

WIP

Following this example, the project’s structure should look like:

.
├── Cargo.toml
├── artifacts
│   ├── other_contract.wasm
│   └── my_contract.wasm
├── contracts
│   ├── my-contract
│   │   ├── Cargo.toml
│   │   └── src
│   │       ├── contract.rs (execute, instantiate, query, ...)
│   │       └── ..
│   └── other-contract
│       └── ..
├── packages
│   ├── my-project (messages)
│   │   ├── Cargo.toml
│   │   └── src
│   │       ├── lib.rs
│   │       ├── my-contract.rs
│   │       ├── other-contract.rs
│   │       └── ..
│   └── my-project-interface (interface collection)
│       ├── Cargo.toml
│       └── src
│           ├── lib.rs
│           ├── my-project.rs
│           └── ..
└── scripts (executables)
    ├── .env
    ├── Cargo.toml
    └── src
        ├── deploy.rs
        └── test_project.rs

Continuous Integration and Deployment

One of the tools that can improve your developer productivity drastically is setting up pipelines for your contract deployments.

cw-orchestrator does not currently add additional support for actions, but an example using the directory structure specified in interfaces can be found below:

# .github/workflows/deploy.yml
---
name: Deploy Contracts  
on:  
  # https://docs.github.com/en/actions/reference/events-that-trigger-workflows#workflow_dispatch  
  workflow_dispatch:  
  push:  
    branches: [ 'mainline' ]  
    paths:  
      - 'contracts/src/**/*.rs'  
      - '.github/workflows/deploy.yml'  
  
env:
  VERSION_CONTROL_ADDRESS: juno16enwrxhdtsdk8mkkcaj37fgp37wz0r3err4hxfz52lcdyayexnxs4468mu  
  STATE_FILE: "./daemon_state.json"  
  ARTIFACTS_DIR: "./target/wasm32-unknown-unknown/release"  
  SCHEMA_DIR: "./schema"  
  
jobs:  
  deploy:  
    runs-on: ubuntu-latest  
    steps:  
      - uses: actions/checkout@v3  
      - uses: actions-rs/toolchain@v1  
        with:  
          toolchain: nightly  
          target: wasm32-unknown-unknown  
          override: true  
      - name: Run cargo wasm  
        uses: actions-rs/cargo@v1  
        with:  
          command: build  
          args: --package counter-app --release --target wasm32-unknown-unknown  
        env:  
          RUSTFLAGS: '-C link-arg=-s'  

      - name: Run deployment script  
        uses: actions-rs/cargo@v1  
        with:  
          command: run  
          args: --package scripts --bin deploy_app  
        env:  
          CHAIN: "juno"  
          DEPLOYMENT: "debugging"  
          NETWORK: "local"  
          RUST_LOG: info  
          ARTIFACTS_DIR: ${{ env.ARTIFACTS_DIR }}  
          STATE_FILE: ${{ env.STATE_FILE }}  
  
          VERSION_CONTROL_ADDRESS: ${{ env.VERSION_CONTROL_ADDRESS }}  
          TEST_MNEMONIC: ${{ secrets.TEST_MNEMONIC }}  

      - name: Upload deployment daemon state  
        uses: actions/upload-artifact@v2  
        with:  
          name: deployment.json  
          path: ${{ env.STATE_FILE }}  
      - name: Upload WASM  
        uses: actions/upload-artifact@v2  
        with:  
          # TODO: name env or from cargo  
          name: counter_app.wasm  
          path: ${{ env.ARTIFACTS_DIR }}/counter_app.wasm

Contributing to cw-orchestrator

Thank you for considering to contribute to the cw-orchestrator project! We appreciate your support and welcome contributions to help improve this multi-environment CosmWasm smart-contract scripting library. This document provides guidelines and instructions on how to contribute to the project effectively.

Table of Contents

Code of Conduct

By participating in this project, you are expected to uphold our Code of Conduct. Please read the Code of Conduct to ensure that you follow the community guidelines and contribute positively to the project.

Getting Started

To get started with contributing to the cw-orchestrator project, you should first familiarize yourself with the repository structure and the codebase. Please read the project’s README to understand the purpose, features, and usage of the cw-orchestrator library as well as its documentation.

How to Contribute

There are multiple ways to contribute to the cw-orchestrator project, including reporting bugs, suggesting enhancements, and submitting code contributions.

Reporting Bugs

If you encounter any bugs or issues while using the cw-orchestrator library, please report them by creating a new issue in the issue tracker. When reporting a bug, please provide the following information:

  • A clear and descriptive title
  • A detailed description of the issue, including steps to reproduce it
  • Any relevant logs, error messages, or screenshots
  • Information about your environment, such as the OS, software versions, and hardware specifications

Suggesting Enhancements

We welcome suggestions for new features or improvements to the existing functionality of the cw-orchestrator library. To suggest an enhancement, create a new issue in the issue tracker with the following information:

  • A clear and descriptive title
  • A detailed explanation of the proposed enhancement, including its benefits and potential use cases
  • If applicable, any examples or mockups of the proposed feature

Code Contributions

To contribute code to the cw-orchestrator project, please follow these steps:

  1. Fork the repository to your own GitHub account.
  2. Clone your fork to your local machine.
  3. Create a new branch for your changes using the git checkout -b feature/your-feature-name command.
  4. Make your changes and commit them with a clear and concise commit message.
  5. Push your branch to your fork on GitHub.
  6. Create a new pull request against the main branch of the cw-orchestrator repository.

Pull Requests

When submitting a pull request, please make sure that your code follows the Style Guide and that all tests pass. Please provide a detailed description of your changes, including the motivation for the changes and any potential impact on the project. This will help maintainers review your pull request more effectively.

Style Guide

The cw-orchestrator project follows the Rust coding style and conventions. Please ensure that your code adheres to these guidelines to maintain consistency and readability throughout the codebase.

  • Use proper indentation (4 spaces) and consistent formatting (cargo fmt).
  • Write descriptive variable and function names.
  • Use comments to explain complex or non-obvious code.
  • Follow the Rust API Guidelines for API design.
  • Add documentation for public functions, types, and modules.
  • Write doc tests for public functions and methods.

Community

To join the cw-orchestrator community, please join the Abstract Discord server and the #cw-orchestrator channel. You can also follow the project on Twitter and GitHub.

References

Easy, right? Try building your contracts with Abstract for the same experience with smart contracts. Get started here.