Cw-Orchestrator
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.tomlfile 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
- Define interfaces for your contracts.
- Environment File
- Configure your mnemonics and log settings.
- Scripting
- Write runnable scripts with your interfaces.
- Integration Tests
- Write an integration test for your contract.
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:
-
#[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
exportfeature is enabled. This prevents conflicts with other entry points when the contract is a dependency of another contract. -
#[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
interfacefeature 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:
contract_id: The unique identifier for this contract. This is used as the key when retrieving address and code_id information for the contract.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:
- cw-multi-test by using
Mockas the environment. - Blockchain daemons like junod, osmosisd, etc. These use the
Daemonenvironment. - Chain-backed mock
depsfor unit-testing. This uses theMockQuerierthat 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:
- Fork the repository to your own GitHub account.
- Clone your fork to your local machine.
- Create a new branch for your changes using the
git checkout -b feature/your-feature-namecommand. - Make your changes and commit them with a clear and concise commit message.
- Push your branch to your fork on GitHub.
- 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.