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. In doing so it removes the need to maintain deployment code for multiple environments. In short, cw-orchestrator is the go-to tool for testing and deploying CosmWasm contracts.
Features
Below we briefly outline the key features that cw-orchestrator has to offer.
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.
This framework allow developers to easily test contract-to-contract interactions without having to deal with the overhead of running a node locally. The testing framework also provides a number of utilities simplify the syntax for write tests. These utilities include the ability to easily set and query balances, set block height/time and more.
Additionally developers can share their infrastructure with other developers by creating a wrapper around their project’s deployment logic, allowing others to easily test how their contracts interact with the project.
The testing frameworks supported by cw-orchestrator includes:
Deployment + Scripting
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. Any blockchain transaction can be broadcasted using cw-orchestrator.
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
Get ready to change the way you interact with contracts. The following steps will allow you to write clean code such as:
counter.upload()?;
counter.instantiate(&InstantiateMsg { count: 0 }, None, None)?;
counter.increment()?;
let count = counter.get_count()?;
assert_eq!(count.count, 1);
In this quick-start guide, we will review the necessary steps in order to integrate cw-orch
into a simple contract crate. We review integration of rust-workspaces (multiple contracts) at the end of this page.
NOTE: Additional content
If you’re moving quicker than everybody else, we suggest looking at a before-after review of this example integration. This will help you catch the additions you need to make to your contract to be able to interact with it using cw-orchestrator.
Summary
Single Contract Integration
Adding cw-orch
to your Cargo.toml
file
To use 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 cw-orch
Alternatively, you can add it manually in your Cargo.toml
file as shown below:
[dependencies]
cw-orch = {version = "0.21.2" } # Latest version at time of writing
NOTE: Even if you include
cw-orch
in your dependencies here, it won’t be included in yourwasm
contract. Learn more about this behavior in the section about Wasm Compilation
Creating an Interface
When using a single contract, we advise creating an interface.rs
file inside your contract’s directory. You then need to add this module to your lib.rs
file. This file should not be included inside you final wasm. In order to do that, you need to add #[cfg(not(target_arch = "wasm32"))]
when importing the file.
#[cfg(not(target_arch = "wasm32"))]
mod interface;
Then, inside that interface.rs
file, you can define the interface for your contract:
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> Uploadable for CounterContract<Chain> {
/// Return the path to the wasm file corresponding to the contract
fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
artifacts_dir_from_workspace!()
.find_wasm_path("counter_contract")
.unwrap()
}
/// Returns a CosmWasm contract wrapper
fn wrapper() -> Box<dyn MockContract<Empty>> {
Box::new(
ContractWrapper::new_with_empty(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
)
.with_migrate(crate::contract::migrate),
)
}
}
Learn more about the content of the interface creation specifics on the interface page
NOTE: It can be useful to re-export this struct to simplify usage (in
lib.rs
):#[cfg(not(target_arch = "wasm32"))] pub use crate::interface::CounterContract;
Interaction helpers
cw-orchestrator provides a additional macros that simplify contract calls and queries. The macro implements functions on the interface for each variant of the contract’s ExecuteMsg
and QueryMsg
.
Enabling this functionality is very straightforward. Find your ExecuteMsg
and QueryMsg
definitions (in msg.rs
in our example) and add the ExecuteFns
and QueryFns
derive macros to them like below:
#[cw_serde]
#[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,
},
}
#[cw_serde]
#[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,
}
Make sure to derive the #[derive(cosmwasm_schema::QueryResponses)]
macro on your query messages !
Find out more about the interaction helpers on the interface page
NOTE: Again, it can be useful to re-export these generated traits to simplify usage (in
lib.rs
):pub use crate::msg::{ExecuteMsgFns as CounterExecuteMsgFns, QueryMsgFns as CounterQueryMsgFns};
Using the integration
Now that all the setup is done, you can use your contract in tests, integration-tests or scripts.
Start by importing your crate, in your [dev-dependencies]
for instance:
counter-contract = { path = "../counter-contract"}
You can now use:
use counter_contract::{
msg::InstantiateMsg, CounterContract, CounterExecuteMsgFns, CounterQueryMsgFns,
};
use cw_orch::{anyhow, prelude::*};
// From https://github.com/CosmosContracts/juno/blob/32568dba828ff7783aea8cb5bb4b8b5832888255/docker/test-user.env#L2
const LOCAL_MNEMONIC: &str = "clip hire initial neck maid actor venue client foam budget lock catalog sweet steak waste crater broccoli pipe steak sister coyote moment obvious choose";
pub fn main() -> anyhow::Result<()> {
std::env::set_var("LOCAL_MNEMONIC", LOCAL_MNEMONIC);
dotenv::dotenv().ok(); // Used to load the `.env` file if any
pretty_env_logger::init(); // Used to log contract and chain interactions
let network = networks::LOCAL_JUNO;
let chain = DaemonBuilder::default().chain(network).build()?;
let counter = CounterContract::new(chain);
counter.upload()?;
counter.instantiate(&InstantiateMsg { count: 0 }, None, None)?;
counter.increment()?;
let count = counter.get_count()?;
assert_eq!(count.count, 1);
Ok(())
}
Integration in a workspace
In this paragraph, we will use the cw-plus
repository as an example. You can review:
- The full integration code with
cw-orch
added - The complete diff that shows you all integration spots (if you want to go fast)
Handling dependencies
When using workspaces, you need to add cw-orch
as a dependency on all crates that include ExecuteMsg
and QueryMsg
used in your contracts.
You then add the #[derive(ExecuteFns)]
and #[derive(QueryFns)]
macros to those messages.
Refer above to Adding cw-orch
to your Cargo.toml
file for more details on how to do that.
For instance, for the cw20_base
contract, you need to execute those 2 steps on the cw20-base
contract (where the QueryMsg
are defined) as well as on the cw20
package (where the ExecuteMsg
are defined).
Creating an interface crate
When using workspace, we advise you to create a new crate inside your workspace for defining your contract’s interfaces. In order to do that, use:
cargo new interface --lib
cargo add cw-orch --package interface
Add the interface package to your workspace Cargo.toml
file
[workspace]
members = ["packages/*", "contracts/*", "interface"]
Inside this interface
crate, we advise to integrate all your contracts 1 by 1 in separate files. Here is the structure of the cw-plus
integration for reference:
interface (interface collection)
├── Cargo.toml
└── src
├── cw1_subkeys.rs
├── cw1_whitelist.rs
├── cw20_base.rs
├── cw20_ics20.rs
└── ..
When importing your crates to get the messages types, you can use the following command in the interface folder.
cargo add cw20-base --path ../contracts/cw20-base/
cargo add cw20 --path ../packages/cw20
Integrating single contracts
Now that you workspace is setup, you can integrate with single contracts using the above section
More examples and scripts
You can find more example interactions on the counter-contract
example directly in the cw-orchestrator
repo:
- Some examples showcase interacting with live chains.
- Some other examples show how to use the library for testing your contracts.
FINAL ADVICE: Continue to explore those docs to learn more about
cw-orch
. Why not go directly to environment variables?
Tutorial
This tutorial will guide you through creating a cw-orch
interface
for your contracts. 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 cw-orch
Github repository as the source for our code-snippets. You can find the contract here.
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. We provide chain definitions and constants for some of the more widely used Cosmos Chains. Learn more about this on the Daemon page.
-
A desire to learn (mandatory): 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.
Setup
Before being able to interact easily with your contracts, you need to add a few dependencies and files to your contract. Check out our dedicated setup tutorial for preparing the work !
Sections
The following sections detail setting up a contract, tests for the contract, and scripts for interacting with the contract on a blockchain network.
- Interfaces
- Define interfaces for your contracts.
- Entry Point Functions
- Simplify your syntax and call your contract endpoints (query and execution) as individual functions
- Environment File
- Configure your mnemonics, log settings and more.
- 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.
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> Uploadable for CounterContract<Chain> {
/// Return the path to the wasm file corresponding to the contract
fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
artifacts_dir_from_workspace!()
.find_wasm_path("counter_contract")
.unwrap()
}
/// Returns a CosmWasm contract wrapper
fn wrapper() -> 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(not(target_arch = "wasm32"))]
pub use crate::interface::CounterContract;
NOTE: You can see that we have used the
artifacts_dir_from_workspace
macro inside thewasm
trait function. This macro helps you locate the workspaceartifacts
folder. It actually looks for any directory namedartifacts
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 thischain
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 thechain
. Thiscontract_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:
- cw-multi-test by using
Mock
as thechain
variable. - Actual Cosmos SDK nodes for interacting with lives chains (
mainnet
,testnet
,local
). UseDaemon
as thechain
variable. - 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 thechain
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? |
|
|
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
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]
#[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,
},
}
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]
#[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,
}
Keep in mind that you NEED to derive the cosmwasm_schema::QueryResponses
trait on your QueryMsgs for the QueryFns
macro to compile.
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
;
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
#[cw_orch(payable)]
to the variant and the function will take aVec<Coin>
as the last argument. - The
cw_orch::QueryFns
macro needs yourQueryMsg
struct to have thecosmwasm_schema::QueryResponses
macro implemented (this is good practice even outside of use withcw-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 #[cw_orch(payable)]
attribute on your enum variant like so:
#[derive(ExecuteFns)]
enum ExecuteMsg{
UpdateConfig{
config_field: String
},
#[cw_orch(payable)]
DepositStable{}
...
}
Be defining this attribute, you can now use:
use cosmwasm_std::coins;
money_market.deposit_stable(&coins(456, "ujunox"))?;
fn_name
Attribute
#[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.
// Doesn't compile
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.
#[derive(cw_orch::ExecuteFns)]
pub enum ExecuteMsg{
#[cw_orch(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.
Nested Messages
For nested messages (execute and query), you need to do 2 things:
- Derive
ExecuteFns
orQueryFns
on the underlying structures - Implement
From<Underlying>
for your contract message type
In general, every structure that implements the Into
trait for the contract message will make the function available on the contract. To make that clearer, here’s an example:
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)]
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)]
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 now!
self.bar("hello".to_string()).unwrap();
// function `test` is available now!
self.test(65u64).unwrap();
}
}
Into
String
and Uint*
(e.g. Uint128
) types will have relaxed trait bounds on the argument that the generated function receives. This allows for easier handling of the cw-orch generated functions. For other types, you can add the #[cw_orch(into)]
attribute on a field to also accept structures that implement Into
the field type. For instance:
pub enum ExecuteMsg<T>{
#[cw_orch(payable)]
SecondMessage {
/// test doc-comment
#[cw_orch(into)]
t: T,
},
}
// Will produce:
fn second_message<T>(&self, t: impl Into<T>, funds: &[Coin]){}
disable_fields_sorting
Attribute
By default the ExecuteFns
and QueryFns
derived traits will sort the fields of each enum member. For instance,
#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
pub enum ExecuteMsgOrdered {
Test { b: u64, a: String },
}
will generate
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:
#[cw_serde]
#[derive(cw_orch::ExecuteFns)]
#[cw_orch(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.
Wasm Compilation
cw-orch
was designed to help you test, deploy, script and maintain your application. None of its features include in-contract operations. cw-orch interfaces, macros and helpers can’t be used inside a wasm smart contract.
In case you use one of the cw-orch features inside you smart-contract, the compilation will send an error anyway, so that we are SURE you are not using un-intended features inside your contract.
Importing cw-orch
Importing cw-orch
as a dependency in your smart contract is not a problem. We have built this library such that every feature that cw-orch imports, uses or exports is not included when compiled for Wasm. This means that you are safe to use any cw-orch feature when developing your application, creating interfaces, exporting features, because none of it will land in your contract.
In order to make sure you don’t encounter wasm compilation errors, you should follow the guidelines outlined in the next section.
Guidelines
Imports
Import cw-orch without a worry, this won’t include unnecessary dependencies and bloat your smart-contract. Be careful of the features you import cw-orch
with because they might increase your compile time (especially daemon
and osmosis-test-tube
).
Interface
The interface macro itself compiles to Wasm to empty traits. So this macro can be used anywhere in your contract. This IS smart-contract safe:
#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg, id = CONTRACT_ID)]
pub struct CounterContract;
However, the Uploadable
traits implementation IS NOT safe for smart contracts and needs to import namely cw-multi-test
elements that we don’t remove from WASM compilation. The following code needs to be flagged to not be compiled inside Wasm contracts:
#[cfg(not(target_arch = "wasm32"))]
impl<Chain> Uploadable for CounterContract<Chain> {
/// Return the path to the wasm file corresponding to the contract
fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
artifacts_dir_from_workspace!()
.find_wasm_path("counter_contract")
.unwrap()
}
/// Returns a CosmWasm contract wrapper
fn wrapper() -> Box<dyn MockContract<Empty>> {
Box::new(
ContractWrapper::new_with_empty(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
)
.with_migrate(crate::contract::migrate),
)
}
}
Entry Points
The entry points are easy to work with as they compile to empty traits inside Wasm. So you can define them, import and export them in your contract without having to care about compilation targets. Furthermore, those traits are optimized out when getting your contract ready to upload on a chain. The syntax use in the 2 following examples is WASM safe:
#[cw_serde]
#[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,
},
}
pub use crate::msg::{ExecuteMsgFns as CounterExecuteMsgFns, QueryMsgFns as CounterQueryMsgFns};
Integration Tests
Integration tests are very easy to write with cw-orch and are 100% compatible with actual on-chain deployment and scripting. We provide an overview of how they can be executed here. Find our more about how to setup your integration tests on the Cw Multi Test page
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::{
msg::{GetCountResponse, InstantiateMsg, QueryMsg},
ContractError, 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";
#[test]
fn count() -> anyhow::Result<()> {
// Create a user
let user = Addr::unchecked(USER);
// Create the mock. This will be our chain object throughout
let mock = Mock::new(ADMIN);
// Set up the contract (Definition below) ↓↓
let contract = setup(mock.clone())?;
// Increment the count of the contract
contract
// Set the caller to user
.call_as(&user)
// Call the increment function (auto-generated function provided by CounterExecuteMsgFns)
.increment()?;
// 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);
// Or get it manually from the chain
let count3: GetCountResponse = mock.query(&QueryMsg::GetCount {}, &contract.address()?)?;
assert_eq!(count1.count, count3.count);
// Check the count
assert_eq!(count1.count, 2);
// Reset
use counter_contract::CounterExecuteMsgFns;
contract.reset(0)?;
let count = contract.get_count()?;
assert_eq!(count.count, 0);
// Check negative case
let exec_res: Result<cw_orch::mock::cw_multi_test::AppResponse, CwOrchError> =
contract.call_as(&user).reset(0);
let expected_err = ContractError::Unauthorized {};
assert_eq!(
exec_res.unwrap_err().downcast::<ContractError>()?,
expected_err
);
Ok(())
}
/// 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)
}
Environment Variables
cw-orch leverages some environment variables to interact with contracts on actual blockchains. The environment variables are described here. You can find additional information about their usage, default values and types in the cw-orch
repo.
IMPORTANT: Before proceeding, ensure that you add .env
to your .gitignore
. We are not responsible for any loss of funds due to leaked mnemonics.
Mnemonics
You can provide mnemonics directly to the Daemon
using the following environment variables. Those variables will be used if the mnemonic
setter is not used. See the Daemon page for more detailed information on mnemonic usage.
MAIN_MNEMONIC
will be used when working with a Mainnet (PHOENIX_1
,OSMOSIS_1
…)TEST_MNEMONIC
will be used when working with a Testnet (PISCO_1
,UNI_6
…)LOCAL_MNEMONIC
will be used when working locally (LOCAL_JUNO
…)
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.
Saving and Loading State
STATE_FILE
Optional, accepted values: Path to a valid file
Default value: ~./cw-orchestrator/state.json
This environment variable indicates the location of the state file that cw-orch
will use to save on-chain state (addresses and code ids). Here is the behavior of this env variable:
folder/file.json
will resolve to~/.cw-orchestrator/folder/file.json
./folder/file.json
will resolve$pwd/folder/file.json
../folder/file.json
will resolve$pwd/../folder/file.json
/usr/var/file.json
will resolve to/usr/var/file.json
ARTIFACTS_DIR
Optional, accepted values: Path to a valid directory
Path where the wasms will be fetched. This is used by ArtifactsDir::env()
inside the code.
This is used in case you are using different tools than what is produced by rust-optimizer. Prefer using the following macro if you are using wasm
s produced by rust-optimizer
:
artifacts_dir_from_workspace!()
.find_wasm_path("contract_name")
.unwrap()
Transaction options
CW_ORCH_GAS_BUFFER
Optional, accepted values: float
This allows changing the gas buffer applied after tx simulation. Use this in case a transaction is blocked for insufficient gas reasons.
CW_ORCH_MIN_GAS
Optional, accepted values: integer
Minimum gas amount for every transaction. Useful when transaction still won’t pass even when setting a high gas_buffer or for mixed transaction scripts.
CW_ORCH_MAX_TX_QUERY_RETRIES
Optional, accepted values: integer
Defaults to 50
.
Changes the number of tx queries (~1 query per block) before it fails if it doesn’t find any result. Useful if the chain is slow or if the transaction has low gas price.
CW_ORCH_MIN_BLOCK_SPEED
Optional, accepted values:
(integer)ms
(e.g. 57ms), to indicate the min block speed in milliseconds(integer)s
(e.g. 57s), to indicate the min block speed in seconds(integer)
(e.g. 57), to indicate the min block speed in seconds
Defaults to 1s
.
Minimum block speed. This is used internally by cw-orch
when broadcasting transactions. Useful when the block speeds are varying a lot.
CW_ORCH_WALLET_BALANCE_ASSERTION
Optional, accepted values: true
, false
Defaults to true
By default, cw-orch
verifies that the account has sufficient balance to pay for gas fees. If it detects that the balance is too low, it propmts the user to fill up their wallet with gas tokens and allows for retrying at the press of a button.
If set to false
, it won’t check the user has enough balance before broadcasting transactions.
CW_ORCH_MANUAL_INTERACTION
Optional, accepted values: true
, false
Defaults to true
Some actions require user-intervention. If set to false
, this disables those interventions. Useful when working in scripts that can’t handle user-intervention.
Supported actions:
- Balance checks. When set to
false
, if the gas token balance is too low to submit a transaction, it will error. See Disable balance assertion. - Deployment checks. When set to
false
, if no deployment file is detected when deploying a structure using theDeploy::multi_deploy
function, it will deploy to all provided chains without asking for approval.
Logging
RUST_LOG
Optional, accepted values: debug
, error
, info
, warn
, trace
RUST_LOG
defines the Log level for the application. This is actually a Rust
flag that we use inside cw-orch
. If working with this environment variable, don’t forget to also initialize a logger at the beginning of you application to be able to see the output. You can work with pretty_env_logger for instance.
CW_ORCH_SERIALIZE_JSON
Optional, accepted values: false
, true
Defaults to false
If equals to true
, in the output logs, cw-orch will serialize the contract messages (instantiate, execute, query,… ) as JSON. This replaces the standard Rust Debug formatting and allows for easy copying and sharing of the executed messages.
CW_ORCH_LOGS_ACTIVATION_MESSAGE
Optional, accepted values: false
, true
Defaults to true
By default if the logs are not enabled, cw-orch
wil print a warning message to invite users to activate the logging capabilities of cw-orch. if equals to false
, the warning message is disabled.
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 the counter contract and add our deploy script there. We only provide an overview of how scripting can be done here. Find our more about how to make your scripting dreams come true on the Daemon page.
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 pretty_env_logger
to log to stdout.
[dev-dependencies]
# Deps for deployment
dotenv = { version = "0.15.0" } # Enables loading of .env files
pretty_env_logger = { version = "0.5.0" } # Enables logging to stdout and prettifies it
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.
We start by creating the chain object for specifying we want to interact with a local juno instance:
dotenv::dotenv().ok(); // Used to load the `.env` file if any
pretty_env_logger::init(); // Used to log contract and chain interactions
let network = networks::LOCAL_JUNO;
let chain = DaemonBuilder::default().chain(network).build()?;
Then we can interact with our contract using that chain
let counter = CounterContract::new(chain);
counter.upload()?;
counter.instantiate(&InstantiateMsg { count: 0 }, None, None)?;
counter.increment()?;
let count = counter.get_count()?;
assert_eq!(count.count, 1);
Asynchronous Daemon
All the functionalities described in this guide/tutorial only allow for synchronous interactions. If for whatever reason you need to interact with an actual chain in an asynchronous way, you can use the DaemonAsync
structure. However, this structure won’t allow for much interoperability as it’s not compatible with the cw-orch
Contract structure. Blockchain transactions have to be sequential because of the sequence
of an account and that’s why we provide limited support to asynchronous transaction broadcasting capabilities.
Setup
In this tutorial, you will find information on how to setup your working directory for cw-orchestrator integration. Because of the nature of smart contracts working directories, we have 2 separate tutorials depending on your setup:
- Single Contract setup tutorial, if you’re working with a single contract.
- Workspace setup tutorial, if you’re working with multiple contracts within a cargo workspace environment.
Single Contract Crate Setup
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.
Dependency
Before we can create an interface we need to add cw-orch to the contract’s Cargo.toml
file. In counter
run:
$ cargo add cw-orch
> Adding cw-orch v0.16.0 to dependencies.
or add it manually to the counter/Cargo.toml
file:
[dependencies]
cw-orch = {version = "0.21.2" } # Latest version at time of writing
Even if you include cw-orch
in your dependencies here, it won’t be included in your wasm
contract. Learn more about this behavior in the section about Wasm Compilation.
Crate Structure
Now that we have our dependency set up, we can create the files that will contain our interface. We will create that interface in an interface.rs
file inside the crate (counter/src/interface.rs
). Then, we need to include that file inside our project. However, that file will contain multiple cw-orch
elements that are not exposed when compiling your WASM. In order to prevent errors when compiling for Wasm, we will import our interface.rs
file and target-flag it like so, in counter/src/lib.rs
:
#[cfg(not(target_arch = "wasm32"))]
mod interface;
In the next section, we will learn how to define the interface to be able to interact with an actual contract inside this interface.rs
file.
Final structure
Following the steps above, the directory structure should eventually look like this:
.
├── Cargo.toml
├── artifacts
│ └── counter_contract.wasm (binary file)
└── counter
├── Cargo.toml
├── bin
│ └── deploy.rs
└── src
├── contract.rs (execute, instantiate, query, ...)
├── msg.rs (ExecuteMsg, QueryMsg, ...)
├── interface.rs (`cw-orch` contract structure definition)
└── ..
NOTE:
The artifacts folder is generated by the rust-optimizer, and contains all your compiled contracts
Place your scripts inside the
bin
directory. Those can be run using the following command:cargo run --bin deploy
Now that the setup is complete, you can go back to the Contracts page to create the interface for your contract and start scripting/testing your contracts withe ease.
Workspace Tutorial
In this tutorial, you will learn how to setup cw-orchestrator inside a workspace project. We present here the best practices for setting up an application composed of multiple contracts with the cw-orch
crate. If you want a short version on how to integrate your contracts, we advise running to our Quick Start guide.
NOTE: A rust workspace is a simple way to have multiple contracts inside the same repository and are especially suited for applications in which multiple contracts communicate with each other.
This tutorial has multiple components:
- Project Setup
- This tutorial helps you setup your project to have a rational workspace structure and all the dependencies needed for interacting with your contracts.
- Project Wrapper
- This tutorial allows you to go further and simplify the way you interact with all the interfaces you created included in your project. It presents best practices and usual project structure to keep your project organized.
- Collaborating
- This tutorial shows you how you can distribute your contract code, you deployment variables (code_ids, addresses…) for other projects to integrate with. This is the ultimate way to collaborate with other projects.
Workspace cw-orch
setup process
In all this tutorial, we will use the cw-plus
package as an example. This repository is maintained by CosmWasm
and contains standard contracts. It’s also a good workspace structure example.
Crate setup
If you are using multiple contracts in a single project, we advise you to centralize all the interfaces to your contracts in a single location. To do so, go inside your packages
folder and create a new interface
library crate:
cargo new interface --lib
cd interface
Then, going into this crate, we need to add cw-orch
as a dependency:
cargo add cw-orch --package interface
Individual contract interface
Now inside this crate, you can add the interfaces to all your contracts individually. A sane way to do so is to create a new file (also called module) for each contract. For instance, the interface
folder in the cw-plus
repository has the following structure:
.
├── Cargo.toml
└── src
├── cw1_subkeys.rs
├── cw1_whitelist.rs
├── cw20_base.rs
├── cw20_ics20.rs
├── cw3_fixed_multisig.rs
├── cw3_flex_multisig.rs
├── cw4_group.rs
├── cw4_stake.rs
└── lib.rs
Inside each file, you can define your contract interface. You can find the tutorial for that in the dedicated Interfaces page.
Scripts
We advise using a scripts
crate at the root of your workspace. This will help you centralize your scripts and have them easily accessible for eventual other users accessing your crate remotely. This crate should be a binary crate:
cargo new --bin scripts
Final project structure
Following the steps above, the directory structure should eventually look like this:
.
├── 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, renamed here)
│ ├── Cargo.toml
│ └── src
│ ├── lib.rs
│ ├── my-contract.rs
│ ├── other-contract.rs
│ └── ..
└── scripts (executables)
├── .env
├── Cargo.toml
└── src
├── deploy.rs
└── test_project.rs
NOTE:
- The artifacts folder is generated by the workspace-optimizer, and contains all your compiled contracts
- We advise you to place your scripts inside the
scripts
crate to centralize them and facilitate retrieving them:
Now that the setup is complete, you can go back to the Contracts page to create the interfaces for your contracts one by one and start scripting/testing your contracts with ease.
You can also continue this tutorial and discover how to:
- Deal with multiple contracts more easily
- Export all your deployments information and code for use in integrations and testing
Interface Collection
Follow the same process as for a single contract to generate the interfaces for your contract.
Now that you have your contract interfaces you can export a comprehensive wrapper of your application that can easily be used by others.
The idea is simple. If you can provide an easy way for others to deploy your contracts/application to their environments, then you’re making it extremely easy for them to use and build on your application.
As an example we will create a deployment for the Abstract smart-contract framework.
The deployment
The deployment can be represented by a struct containing all the contracts that are uploaded and instantiated when the protocol is deployed.
// Our Abstract deployment
pub struct Abstract<Chain> {
pub ans_host: AnsHost<Chain>,
pub version_control: VersionControl<Chain>,
}
Implementing Deploy
Now we can implement the cw_orch::Deploy
trait for the Abstract
struct.
use cw_orch::prelude::*;
use cw_orch::error::CwOrchError;
use cw_multi_test::ContractWrapper;
use abstract_interface::{AnsHost, VersionControl};
pub struct Abstract<Chain: CwEnv> {
pub ans_host: AnsHost<Chain>,
pub version_control: VersionControl<Chain>,
}
impl<Chain: CwEnv> cw_orch::Deploy<Chain> for Abstract<Chain> {
// We don't have a custom error type
type Error = CwOrchError;
type DeployData = semver::Version;
fn store_on(chain: Chain) -> Result<Self, Self::Error> {
// "abstract" is a reserved keyword in rust!
let mut abstrct = Abstract::new(chain);
// Upload the contracts to the chain
abstrct.ans_host.upload()?;
abstrct.version_control.upload()?;
Ok(abstrct)
}
fn deploy_on(chain: Chain, version: semver::Version) -> Result<Self, CwOrchError> {
// ########### Upload ##############
let abstrct = Self::store_on(chain)?;
// ########### Instantiate ##############
abstrct.ans_host.instantiate(
&abstract_core::ans_host::InstantiateMsg {},
Some(sender),
None,
)?;
abstrct.version_control.instantiate(
&abstract_core::version_control::InstantiateMsg {},
Some(sender),
None,
)?;
// ...
Ok(abstrct)
}
fn load_from(chain: Chain) -> Result<Self, Self::Error> {
let abstrct = Self::new(chain);
Ok(abstrct)
}
}
Now Abstract
is an application that can be deployed to a mock and real environment with one line of code.
fn setup_test(mock: Mock) -> Result<(), CwOrchError> {
let version = "1.0.0".parse().unwrap();
// Deploy abstract
let abstract_ = Abstract::deploy_on(mock.clone(), version)?;
}
And then when setting up your own deployment you can load these applications to access their contracts (for accessing configuration, addresses, …)
impl<Chain: CwEnv> cw_orch::Deploy<Chain> for MyApplication<Chain> {
/// ...
fn deploy_on(chain: Chain, _data: Empty) -> Result<Self, CwOrchError> {
let abstract_: Abstract = Abstract::load_from(chain)?;
/// ... do stuff with Abstract
}
}
Collaboration
The structure you defined in the Deploy Wrapper section is very useful for testing and scripting with your platform. However, in most cases you also want other developers and smart-contracts builders to be able to test their application against your application logic. This can be done by publishing your deployment structures to crates.io
We’ll continue with the Abstract platform as an example. This platform can be used by developers to create smart contacts enhancing the wallet experience of their users. They create applications (apps) on top of the framework and need the framework to be present on their test chain to be able to test or script against it.
On actual blockchains (Mainnets and Testnets), users won’t redeploy the full Abstract platform. Instead, we added the Abstract addresses and code_ids alongside the abstract interface so that users can access those contracts without having to specify all those variables by hand (or find them in some documentation scattered across the web).
This type of information can also be useful for users working with Dexes (which have pool, factory, router addresses).
We’ll present in this guide how projects can provide this kind of information to their users directly through Rust dependencies.
State File
In order for your users to get access to the state of your application (addresses and cods_ids), you need to implement the deployed_state_file
method on the Deploy
trait:
fn deployed_state_file_path(&self) -> Option<String> {
let crate_path = env!("CARGO_MANIFEST_DIR");
Some(
PathBuf::from(crate_path)
// State file of your deployment
.join("state.json")
.display()
.to_string(),
)
}
In this function, you indicate where the deployment file is located. To be able to have those addresses accessible to users importing the crate, we advise you need to specify the path using the crate_path
variable that is automatically set by Cargo.
NOTE: At Abstract for instance, we have a
scripts
crate which is different from theinterface
crate. In order to keep up with the deployment file location, we have symbolic link frominterface/state.json
toscripts/state.json
. This symbolic link is turned automatically into an actual file when it’s published the crate tocrates.io
.
Then, in the load_from
function, we advise using the following implementation:
fn load_from(chain: Chain) -> Result<Self, Self::Error> {
let mut abstr = Self::new(chain);
// We set all the contract's default state (addresses, code_ids)
abstr.set_contracts_state();
Ok(abstr)
}
where Self::new
simply creates the contract structures. You don’t need to reimplement the set_contracts_state
function. It will use the state indicated in Self::deployed_state_file_path
as long as contracts are not re-uploaded / instantiated.
You can customize the Deploy::deployed_state_file_path
and Deploy::load_from
methods, be we recommend doing something similar to what we show above to avoid mistakes and errors.
For visual learners, the workspace looks something like this:
.
├── artifacts
├── contracts
│ ├── contract1
│ │ └── src
│ │ ├── contract.rs
│ │ └── ...
│ └── contract2
│ └── src
│ ├── contract.rs
│ └── ...
├── packages
| └── interface
| ├── src
│ │ ├── deploy.rs // <-- Definition of the deploy struct and implementation of the Deploy trait.
| │ | // <-- Leverages contract1 and contract2 structures
│ │ └── ...
│ └── state.json // <-- Usually a symlink to the state.json file you use for deployment (by default in ~/.cw-orchestrator)
├── scripts
| └── src
| └── bin // <-- Your deployment script can be located here
└── .env // <-- Place your .env file at the root of your workspace
Integrations
cw-orchestrator aims at allowing developers to interact with different execution environments with the exact same code base and syntax.
In order to do so, it provides a set of traits and interfaces, e.g. for contracts that can be implemented on execution structures. As of writing this documentation, the following execution environments are supported:
Those environments allow developers to test, deploy, manage, migrate, maintain, without having to switch programming languages, care for types and version compatibilities as they are enforced by the compiler and even change the syntax of the code used.
The Unit Tests page shows you how you can unit-test your contracts against actual on-chain data!
Daemon
Daemon
is a CosmWasm execution environment for interacting with CosmosSDK chains. The Daemon
allows you to deploy/migrate/configure your contracts on main and testnets as well as locally running chain instances. Furthermore it provides a wide range of tools to interact with the chains. We describe those tools in depth in this page.
Quick Start
Interacting with the daemon
is really straightforward. Creating a daemon instance is shown below:
use cw_orch::prelude::*;
use tokio::runtime::Runtime;
// We start by creating a daemon. This daemon will be used to interact with the chain.
let daemon = Daemon::builder()
// set the network to use
.chain(cw_orch::daemon::networks::LOCAL_JUNO) // chain parameter
.build()
.unwrap();
- The
chain
parameter allows you to specify which chain you want to interact with. The chains that are officially supported can be found in thecw_orch::daemon::networks
module. You can also add additional chains yourself by simply defining a variable of typeChainInfo
and using it in your script. Don’t hesitate to open a PR on the cw-orchestrator repo, if you would like us to include a chain by default. The variables needed for creating the variable can be found in the documentation of the chain you want to connect to or in the Cosmos Directory.
This simple script actually hides another parameter which is the LOCAL_MNEMONIC
environment variable. This variable is used when interacting with local chains. See the part dedicated to Environment Vars for more details.
NOTE: When using
daemon
, you are interacting directly with a live chain. The program won’t ask you for your permission at each step of the script. We advise you to test ALL your deployments on test chain before deploying to mainnet.Under the hood, the
DaemonBuilder
struct creates atokio::Runtime
. Be careful because this builder is not usable in anasync
function. In such function, you can useDaemonAsync
When using multiple Daemons with the same state file, you should re-use a single Daemon State to avoid conflicts and panics:
let daemon1 = Daemon::builder()
.chain(OSMOSIS_1)
.build()?;
// If you don't use the `state` method here, this will fail with:
// State file <file-name> already locked, use another state file, clone daemon which holds the lock, or use `state` method of Builder
let daemon2 = Daemon::builder()
.chain(JUNO_1)
.state(daemon1.state())
.build()?;
Interacting with contracts
You can then use the resulting Daemon
variable to interact with your contracts:
let counter = CounterContract::new(daemon.clone());
let upload_res = counter.upload();
assert!(upload_res.is_ok());
let init_res = counter.instantiate(
&InstantiateMsg { count: 0 },
Some(&counter.environment().sender()),
None,
);
assert!(init_res.is_ok());
All contract operations will return an object of type cw_orch::prelude::CosmTxResponse
. This represents a successful transaction. Using the txhash of the tx, you can also inspect the operations on a chain explorer.
ADVICE: Add
RUST_LOG=INFO
to your environment and use theenv_logger::init()
initializer to get detailed information about your script execution. Cw-orchestrator provides enhanced logging tools for following the deployment and potentially pick up where you left off. This environment needs wasm artifacts to deploy the contracts to the chains. Don’t forget to compile all your wasms before deploying your contracts !
State management
In order to manage your contract deployments cw-orchestrator saves the contract addresses and code ids for each network you’re interacting with in a JSON formatted state file. This state file represents all your past. You can customize the path to this state file using the STATE_FILE
env variable.
When calling the upload
function on a contract, if the tx is successful, the daemon will get the uploaded code_id and save it to file, like so:
{
"juno": {
"juno-1": {
"code_ids": {
"counter_contract": 1356,
},
}
}
}
In this example: counter_contract
corresponds to the contract_id
variable (the one that you can set in the contract interface constructor).
When calling the instantiate
function, if the tx is successful, the daemon will get the contract address and save it to file, like so:
{
"juno": {
"juno-1": {
"code_ids": {
"counter_contract": 1356,
},
"default": {
"counter_contract": "juno1wug8sewp6cedgkmrmvhl3lf3tulagm9hnvy8p0rppz9yjw0g4wtqwrw37d"
}
}
}
}
In this example, the default
keyword corresponds to the deployment namespace. This can be set when building the daemon object (using the DaemonBuilder::deployment_id
method) in order to separate multiple deployments. For instance for a DEX (decentralized exchange), you can have a single code-id but multiple pool addresses for all your liquidity pools. You would have a juno-usdc
and a usdt-usdc
deployment, sharing the same code-ids but different contract instances.
Configuration
When creating a Daemon, use the DaemonBuilder
object to set options for the structure.
Here are the available options and fields you can use in the builder object:
chain
(required) specifies the chain thedaemon
object will interact with. Documentation Linkdeployment_id
(optional) is used when loading and saving blockchain state (addresses and code-ids). It is useful when you have multiple instances of the same contract on a single chain. It will allow you to keep those multiple instances in the same state file without overriding state.Documentation Linkhandle
(optional) is thetokio
runtime handled used to await async functions.cw-orch
provides a default runtime if not specified. Documentation Linkmnemonic
(optional) is the mnemonic that will be used to create the sender associated with the resultingDaemon
Object. It is not compatible with thesender
method. Documentation Linksender
(optional) is the sender that will be uses with theresulting
Daemon Object. It is not compatible with themnemonic
method. Documentation Linkauthz_granter
(optional) allows you to use the authz module. If this field is specified, the sender will send transactions wrapped inside an authz message sent by the specifiedgranter
. More info on the authz module. Documentation Linkfee_granter
(optional) allows you to use the fee-grant module. If this field is specified, the sender will try to pay for transactions using the specifiedgranter
. More info on the fee grant module. Documentation Linkhd_index
(optional) allows to set the index of the HD path for the account associated with theDaemon
object. More info on the derivation path and index. Documentation Link
NOTE: if none of
sender
ormnemonic
is specified, env variables will be used to construct the sender object.
Keep in mind that those options can’t be changed once the Daemon
object is built, using the build
function. It is possible to create a new DaemonBuilder
structure from a Daemon
object by using the rebuild
method and specifying the options that you need to change.
Additional tools
The Daemon
environment provides a bunch of tools for you to interact in a much easier way with the blockchain. Here is a non-exhaustive list:
-
Send usual transactions:
#![allow(unused)] fn main() { let wallet = daemon.wallet(); let rt = daemon.rt_handle.clone(); rt.block_on(wallet.bank_send("<address-of-my-sister>", coins(345, "ujunox")))?; }
-
Send any transaction type registered with
cosmrs
:#![allow(unused)] fn main() { let tx_msg = cosmrs::staking::MsgBeginRedelegate { // Delegator's address. delegator_address: AccountId::from_str("<my-address>").unwrap(), // Source validator's address. validator_src_address: AccountId::from_str("<my-least-favorite-validator>").unwrap(), // Destination validator's address. validator_dst_address: AccountId::from_str("<my-favorite-validator>").unwrap(), // Amount to UnDelegate amount: Coin { amount: 100_000_000_000_000u128, denom: Denom::from_str("ujuno").unwrap(), }, }; rt.block_on(wallet.commit_tx(vec![tx_msg.clone()], None))?; }
-
Send any type of transactions (Using an
Any
type):#![allow(unused)] fn main() { rt.block_on(wallet.commit_tx_any( vec![cosmrs::Any { type_url: "/cosmos.staking.v1beta1.MsgBeginRedelegate".to_string(), value: tx_msg.to_any().unwrap().value, }], None, ))?; }
-
Simulate a transaction without sending it
#![allow(unused)] fn main() { let (gas_needed, fee_needed) = rt.block_on(wallet.simulate(vec![tx_msg.to_any().unwrap()], None))?; log::info!( "Submitting this transaction will necessitate: - {gas_needed} gas - {fee_needed} for the tx fee" ); }
Queries
The daemon object can also be used to execute queries to the chains we are interacting with. This opens up a lot more applications to cw-orchestrator as this tools can also be used to manage off-chain applications.
Querying the chain for data using a daemon looks like:
let bank_query_client: Bank = daemon.querier();
let sender = "valid_sender_addr";
let balance_result = bank_query_client.balance(sender, None)?;
println!("Balance of {} : {:?}", sender, balance_result);
For more information and queries, visit the daemon querier implementations directly
Example of code leveraging Daemon capabilities
Here is an example of a script that deploys the counter contract only after a specific block_height.
impl CounterContract<Daemon> {
/// Deploys the counter contract at a specific block height
pub fn await_launch(&self) -> Result<()> {
let daemon = self.environment();
// Get the node query client, there are a lot of other clients available.
let node: Node = daemon.querier();
let mut latest_block = node.latest_block().unwrap();
while latest_block.height < 100 {
// wait for the next block
daemon.next_block().unwrap();
latest_block = node.latest_block().unwrap();
}
let contract = CounterContract::new(daemon.clone());
// Upload the contract
contract.upload().unwrap();
// Instantiate the contract
let msg = InstantiateMsg { count: 1i32 };
contract.instantiate(&msg, None, None).unwrap();
Ok(())
}
}
CW Multi Test
Cw Multi Test is a rust-based test framework that allows developers to test for multi-contract interactions without having to dispatch messages, storage and other variables themselves. With cw-orchestrator, most of the cw-multi-test
logic is abstracted away, but you can still learn more about the framework here.
⚠️ Custom-typed contracts: cw-orch’s integration with cw-multi-test is not compatible with custom-typed contracts.
Custom-typed contracts are contracts where the endpoints return
Response<M>
whereM
is notcosmwasm_std::Empty
OR where theDeps
andDepsMut
endpoint arguments have a nonEmpty
generic parameter specified.You can still test and deploy your contracts on actual nodes (testnets, mainnets). To achieve that, don’t specify the
wrapper
function in theUploadable
trait implementation on your interface.
Quick Start
The cw-multi-test
integration comes at no extra cost for the developer. Creating a test environement in cw-orchestrator that leverages cw-multi-test
goes along the lines of:
use cw_orch::prelude::*;
use cosmwasm_std::Addr;
let sender = Addr::unchecked("juno16g2rahf5846rxzp3fwlswy08fz8ccuwk03k57y");
let mock = Mock::new(&sender);
NOTE: When using
cw-multi-test
, the addresses ARE NOT validated like on a live chain. Therefore, you can use any string format for designating addresses. For instance,Addr::unchecked("my-first-sender")
is a validcw-multi-test
address.
NOTE: When using
cw-multi-test
, NO gas fees are charged to the sender address.
Interacting with contracts
You can then use the resulting Mock
variable to interact with your contracts:
let contract_counter = CounterContract::new(mock);
let upload_res = contract_counter.upload();
upload_res.unwrap();
When executing contracts in a cw-multi-test
environment, the messages and sub-messages sent along the Response of an endpoint, will be executed as well.
This environment mocks the actual on-chain execution exactly.
This environment uses the actual functions of your contract without having to compile them into WASM. When you are calling
upload
with this environment, no wasm files are included in the test environment. This allows for better debugging of your contract code.You will need to have implemented the
wrapper
function for interacting the theMock
environment. This function will allow you to “connect” your contract endpoints to yourContract
struct. See the dedicated page for more details.NOTE: Keep in mind that
cw-multi-test
is based solely in rust and that a lot of actual blockchain modules are not mocked in the environment. The main cosmos modules are there (Bank, Staking), but some very useful ones (tokenfactory, ibc) as well as Stargate messages are not supported by the environment.
Cloning
When cloning a cw_multi_test environment, you are not cloning the entire environment, but instead you are creating a new Mock
typed variable with the same underlying cw_multi_test::App
object reference. This is useful for objects that require to pass the chain as an object rather than by reference.
The underlying cw_multi_test::App
object is however not clonable.
Bech32
When testing your smart contracts, you may need to use valid bech32 addresses instead of arbitrary strings. Notably Bech32 addresses are a requirment whenever instantiate2
is used in your codebase. Therefore cw-orch
allows this behavior using the MockBech32
struct. This structure has the same features as the above described Mock
with the following differences:
-
The constructor now takes the bech32 prefix instead of the sender. Under the hood, the environment creates an address that will be used as the default sender for actions executed on that environment:
let mock = MockBech32::new("<chain-prefix>"); // For instance: let mock = MockBech32::new("juno"); // With chain id: let mock = MockBech32::new_with_chain_id("juno", "juno-1"); // Default sender address for this env let sender = mock.sender();
-
To facilitate the creation of additional valid bech32 addresses, we have added an
addr_make
function. This function uses its string argument to create a new address. That way you can create a new address without having to care about bech32 encoding/decoding yourself:let named_sender = mock.addr_make("sender"); /// This creates a new address with some funds inside of it let rich_sender = mock.addr_make_with_balance("sender_with_funds", coins(100_000_000_000_000, "ujuno"));
Snapshot testing
cw-orch
provides snapshot testing capabilities to assist you catching breaking changes to your contracts. The Mock::take_storage_snapshot
function allows you to dump all the deployed contracts’ storage values into insta.rs that executes snapshot testing. An example application of this feature is to make sure that the storage of your contracts don’t change when migrating a contract. Using this tool, you should have a test that looks something like this:
#[test]
fn storage_stays_the_same(){
let mock = Mock::new(Addr::unchecked("sender"));
... // Upload, instantiate, execute contracts
// Make sure that the operations have a fixed result
mock.take_storage_snapshot("mock_snapshot")?;
}
At any point of development, if the storage variables are modified, this test will fail and alert you that you are doing breaking changes to your storage variables. Learn more about the underlying tool in the official documentation.
Additional tools
The Mock
test environment allows you to change application variables (such as the balance of an account) using wrappers around the underlying cw_multi_test::App
object. Here are some examples of those wrappers in context:
let new_sender = Addr::unchecked("entirely-new-sender");
mock.set_balance(&new_sender, coins(100_000, "ujunox"))
.unwrap();
// Reuploads as the new sender
contract_counter.call_as(&new_sender).upload().unwrap();
// Here the contract_counter sender is again `sender`
// Sets the new_sender as the definite sender
contract_counter.set_sender(&new_sender);
// From now on the contract_counter sender is `new_sender`
Additional customization
As we don’t provide wrappers around each and every functionality that cw-multi-test
provides, you can also customize the underlying cw_multi_test::App
object to your specific needs. In the following example, we create a new validator in the test environment:
mock.app
.borrow_mut()
.init_modules(|router, api, storage| {
router.staking.add_validator(
api,
storage,
&BlockInfo {
height: 16736,
time: Timestamp::from_seconds(13345762376),
chain_id: "juno-1".to_string(),
},
cosmwasm_std::Validator {
address: "new-validator-address".to_string(),
commission: Decimal::from_str("0.5").unwrap(), // Greedy validator
max_commission: Decimal::from_str("1").unwrap(), // Dangerous validator
max_change_rate: Decimal::from_str("1").unwrap(), // Very dangerous validator
},
)
})
.unwrap();
Osmosis Test Tube
Osmosis Test Tube is a rust-based test framework that allows developers to test for multi-contract interactions without having to dispatch messages, storage and other variables themselves. This environment is even closer to the actual on-chain mechanisms than Cw Multi Test, because it runs tests directly on the actual chain binaries. With cw-orchestrator, most of the osmosis-test-tube
logic is abstracted away, but you can still learn more about the framework here.
Prerequesites
In order to use osmosis-test-tube
, the library needs to be able to compile and install the chain binaries. For that you will need to install a go compiler as well as the clang library. Run the following commands to install both of those libraries.
Ubuntu
- Install go
- Install the clang library
sudo apt install clang
Arch Linux
- Install go
sudo pacman -Sy go
- Install the clang library
sudo pacman -Sy clang
Quick Start
Creating a test environment in cw-orchestrator that leverages osmosis-test-tube
goes along the lines of:
use cw_orch::prelude::*;
use cosmwasm_std::coins;
let chain = OsmosisTestTube::new(coins(1_000_000_000_000, "uosmo"));
This snippet will create a new address, provide it with initial balances and create the osmosis-test-tube
environment.
The addresses are not handled like in the cw-multi-test
environment or in mock tests and can’t be decided upon manually. You will learn more later about handling addresses in the OsmosisTestTube environement.
NOTE: When using
osmosis-test-tube
, the addresses are validated like on a live chain.
NOTE: When using
osmosis-test-tube
, gas fees are charged to the sender address. The gas fees don’t represent the actual gas fees you will occur when interacting with the actual chain. That’s why in the test snippet above, the amount ofuosmo
instantiated with the account is very high.
Interacting with contracts
You can then use the resulting OsmosisTestTube
variable to interact with your contracts:
let contract_counter = CounterContract::new(chain);
let upload_res = contract_counter.upload();
assert!(upload_res.is_ok());
let init_res = contract_counter.instantiate(&InstantiateMsg { count: 0 }, None, None);
assert!(init_res.is_ok());
When executing contracts in an osmosis_test_tube
environment, the messages and sub-messages sent along the Response of an endpoint, will be executed as well. This environment mimics the actual on-chain execution by dispatching the messages inside the actual chain binaries.
This environment uses wasm files compiled from the project. Therefore, you need to compile the WASM artifacts from the project for your osmosis-test-tube integration tests. You will need to have the
wasm
function fom theUploadable
trait implemented . See the dedicated page for more details.
Cloning
When cloning an osmosis_test_tube
environment, you are not cloning the entire environment, but instead you are creating a new OsmosisTestTube
typed variable with the same underlying osmosis_test_tube::App
object reference. This is useful for objects that require to pass the chain as an object rather than by reference.
The underlying osmosis_test_tube::App
object is however not clonable.
Additional tools
The OsmosisTestTube
test environment allows you to change application variables (such as the balance of an account) using wrappers around the underlying osmosis_test_tube::App
object. Here are some examples of those wrappers in context:
let new_sender = chain.init_account(coins(100_000, "ujunox")).unwrap();
// Reuploads as the new sender
contract_counter.call_as(&new_sender).upload().unwrap();
// Here the contract_counter sender is again `sender`
// Sets the new_sender as the definite sender
contract_counter.set_sender(&new_sender);
// From now on the contract_counter sender is `new_sender`
NOTE: With OsmosisTestTube, you can’t create accounts with a specific address. Accounts are created by the app directly and you don’t have a lot of control over them. As in the example, use
OsmosisTestTube::init_account
to create a new account.
Additional customization
As we don’t provide wrappers around each and every functionality that osmosis_test_tube
provides, you can also customize the underlying osmosis_test_tube::App
object to your specific needs. In the following example, we increase the block time in the test environment:
chain.app.borrow_mut().increase_time(150);
Unit Tests
Cw-orchestrator provides an additional tool to help you with unit tests with context. The WasmMockQuerier
object allows you to leverage cw-orch
querying abilities to unit-test your contracts.
Capabilities
The WasmMockQuerier
(and associated mock_dependencies
helper function) allow developers to unit-test their contract against on-chain logic. This allows for a mix between unit and integration tests for application that need to rely on on-chain data. This structure works very similarly to cosmwasm_std::testing::mock_dependencies
but instead of querying for local scripts or data, it queries the information from an actual running blockchain.
Today, the following modules are supported by this querier:
- Wasm
- Bank
- Staking (support has not been finalized as of yet)
Example
Let’s imagine you want to build a lending aggregator. In this kind of application, you want to query the balance in staking tokens that your client has. In order to do that, you may want to use the following syntax inside your contract:
fn query_balance(deps: Deps, client: Addr, lending_contract: Addr) -> Result<BalanceResponse, ContractError>{
let balance: BalanceResponse = deps.querier.query(&QueryRequest::Wasm(WasmQuery::Smart{
contract_addr: lending_contract,
query: Binary::from({
StakingBalance{
...
}
})
}))?;
}
In order to unit-test that logic, you may want to test against actual on-chain data. The following code_snippet will allow you to test this piece of code against actual on-chain code.
#[test]
fn balance_is_available() -> anyhow::Result<()>{
let deps = cw_orch::prelude::live_mock::mock_dependencies(JUNO_1);
// assert the query is successful
let balance_response = query_balance(deps.as_ref(), Addr::unchecked("juno1..."), Addr::unchecked("juno1..")).unwrap();
}
Obviously, you need to specify existing on-chain addresses to be able to use the cw_orch
mock_dependencies
function. Those dependencies have a very similar behavior to cosmwasm_std::testing::mock_dependencies
, expect the query response is fetched from on-chain information.
Fork Testing
NOTE: This feature is not publicly available yet. If you want preview access to this testing feature, please reach out via our website.
Cw-orchestrator supports testing in a forked environment. With this feature, you can execute the application you are developing on a blockchain environment without having to spin up a node yourself and create a fork locally. All the code and application you will be running during the test will be rust code and the storage needed to execute is minimal as only the necessary data is downloaded from actual blockchain nodes.
Brief Overview
We leverage the beautiful cw-multi-test
package, created and maintained by Cosmwasm and added a few functionalities that allow you to execute your code just as if you were interacting with an actual on-chain node, but locally, and without any on-chain funds necessary.
Setup
Setting up the environment is really easy and only requires feeding a ChainData
object to the struct constructor:
use cw_orch::networks::JUNO_1;
use cw_orch::CloneTesting;
let app = CloneTesting::new(JUNO_1)?;
With this, you are ready to upload, instantiate, migrate and interact with on-chain contracts…
Execution Flow
This execution environment has a mixed behavior.
Execution
The blockchain modules logic is implemented in rust. Every module that is not standard has to be present and implemented in rust to be available. Because we are based on cw-multi-test
, our application is compatible with cw-multi-test modules. For now:
- The Wasm module is implemented locally via the
cosmwasm-vm
package that handles wasm execution. - The Bank module is implemented by
cw-multi-test
and available in this environment. - The Staking module is not fully implemented because distant storage is more difficult to query. It’s the next module we wish to implement.
Storage
This part is actually at the center of the innovation provided by this package.
-
When reading blockchain storage, the testing environment will execute in order:
- Check local storage for data availability. If some data was stored locally, use that value
- If not, check the registered blockchain node for data availability and error if it’s not available.
-
When writing value to storage, nothing changes.
Let’s take an example for clarity. Say I want to deposit some funds into Anchor Protocol. Here are the steps that a user would have to go through and how they are executed inside the environment.
-
The user needs funds to interact with the protocol. A
fork-helper
allows to increase the balance of an address.// Sender address. Can also be an actual account with funds. // Could also be app.sender() for creating an address automatically. let sender = "terra1..."; let sender_addr = Addr::unchecked(sender); app.set_sender(&sender_addr); // We add some funds specific for our application app.set_balance(&sender_addr, coins(10_000_000, "uusd"))?;
-
The user calls the following
ExecuteMsg
on the actual mainnet Anchor Moneymarket Contract:let market_addr = Addr::unchecked("terra1..."); // Actual contract address of the Anchor deployment. let market = AnchorMarket::new("anchor:money-market", app.clone()); market.set_address(&market_addr); market.deposit_stable(&coins(10_000, "uusd"))?;
-
During the whole message execution, when storage is queried, if it doesn’t exist locally it will be queried from the chain. This is true for storage during contract execution but this is also true for querying the actual Wasm Code when executing/querying a contract. No local storage is used until something is written to it 1.
-
Even in the case of multiple chained contract calls, storage is modified accordingly and usable by contracts.
-
After message execution, queries and states are modified according to the contract execution. After depositing, it is now possible to query your stake or even to withdraw your funds:
let a_currency = "terra1..."; // The contract address for the staking receipt /// This should give a non-zero value, even if no change occurred on the actual mainnet state let response: BalanceResponse = app .query(&Cw20QueryMsg::Balance { address: sender.to_string(), }, &Addr::unchecked(a_currency), )?; /// We can get our funds back, no problem, the state changes get propagated as well locally market.redeem_all_stable()?;
Usage
You use this fork environment as you would use the Mock
environment, with a few subtle changes:
-
You can’t use human readable addresses, because this environment uses actual APIs and needs to be able to verify addresses. When creating the Mock environment, a sender gets created along and attach automatically to the
CloneTesting
instance. If you need additional addresses, you can use:let new_sender: Addr = fork.init_account();
-
The environment doesn’t allow (yet) contracts to be defined using its functions (just like the
Mock
can). A contract in this environment is executed through its compiled wasm. When testing with this environment, make sure that you compile your project before running the tests2.
In the future, we might leverage a local storage cache to avoid querying distant RPCs too much (for more speed and less data consumption).
We are aware that this is not very easy on the user and we are working on being able to accept both structures that implement the Contract
trait AND wasm files for testing with this for environment.
Interchain Capabilities
Because of its asynchronous and decentralized nature, the Inter-Blockchain Communication protocol makes developing and debugging applications more difficult than simple blockchain transactions. Cw-orch simplifies those tasks by providing developers tools, full testing environments and standard interfaces to interact with applications that leverage IBC capabilities.
Here are a few examples of what cw-orchestrator allows:
Interchain packet following
Using some simple tools, one can follow the execution of IBC packets through their whole lifetime (Receive, Acknowledge or Timeout). This is mostly useful for packet analysis of certain channels, ports or connections.
let packet_lifetime = interchain.follow_packet(
"juno",
"transfer",
"channel-16",
"akash",
3u64.into()
).await?;
Interchain transaction waiting
To further simplify the developer experience and provide simple syntax, cw-orchestrator allows developers to await execution of transactions that create IBC packets. This is mostly useful when interacting with existing contracts in a script to simplify the execution pipeline.
// We order an ICA controller to burn some funds on juno from akash
// Upon submitting this transaction successfully, a packet will be sent from akash to juno to trigger the ICA action
let transaction_response = controller.send_msgs("channel-16", vec![
CosmosMsg::Bank(cosmwasm_std::BankMsg::Burn {
amount: vec![cosmwasm_std::coin(100u128, "ujuno")],
})
])?;
// This function won't return before the packet is relayed successfully or timeouts.
let packet_lifetime = interchain.wait_ibc(
"akash",
transaction_response
).await?;
// You can analyze the packet lifetime
match packet_lifetime.packets[0].outcome{
IbcPacketOutcome::Success(_) => {},
_ => panic!("Expected packet to be successfully transmitted")
};
// You can safely continue with the rest of your application, the packet has been successfully relayed
This namely removes the need for pausing the program and resuming manually or with a timer. This also allows to automatically get extra information about the relayed packet.
Interchain application testing
Cw-orch allows developers to test their IBC applications and smart-contracts using a common interface. As we know that setting an IBC testing environment is heavy on resources and can be time-consuming, we provide 2 testing environments that will help them streamline their development process:
Rust-only
The MockInterchainEnv
object allows developers to test their application without leaving Rust and without compromising on test speed.
Built on top of cw-multi-test, this environment replicates the actual on-chain IBC module (channel creation as well as packet relaying). This allows you to test any IBC application that leverages Smart-Contract or Bank-module IBC packets. It is really powerful and doesn’t rely on ANY external tools to work. No node setup, no relayer setup, no cluster setup, everything runs inside your crate. Visit the dedicated Mock Interchain Env page for more details and code snippets.
Cosmos SDK Node Testing
The Starship
object allows developers to test their application against the actual binaries of running chains. If you want to run your application with specific logic, custom messages or modules, this is the preferred way. This is the IBC version of the local chains that you can run locally. It can also spin up relayers and set up connections between your local chains automatically.
Visit the dedicated Starship page for more details and code snippets.
Cosmos SDK Node Scripting
The DaemonInterchainEnvironment
object allows developers to script, deploy and manage their application on running chains with attention to IBC functionalities. This enhances the developer experience with more tooling, more useful logging. This is the all-in-one toolbox cor the cosmwasm IBC developer.
Visit the dedicated Daemon Interchain page for more details and code snippets.
Access
The interchain features of cw-orchestrator are not open-source and freely available to all users. Learn more about pricing on our dedicated page.
Interchain Quickstart
General Example
Creating the environment
In order to interact with your environment using IBC capabilities, you first need to create an interchain structure. In this guide, we will create a mock environment for local testing. ↓Click here, if you want to interact with actual nodes.
With mock chains, you can create a mock environment simply by specifying chains ids and sender addresses.
For this guide, we will create 2 chains, juno
and osmosis
, with the same address as sender:
let sender = Addr::unchecked("sender_for_all_chains");
let interchain = MockInterchainEnv::new(vec![("juno", &sender), ("osmosis", &sender)]);
Interacting with the environment
Now, we will work with interchain accounts (ICA). There is a simple implementation of the ICA protocol on Github, and we will use that application with a few simplifications for brevity.
In this protocol, we have 2 smart-contracts that are able to create a connection between them.
The client
will send IBC messages to the host
that in turn will execute the messages on its chain.
Let’s first create the contracts:
let juno = interchain.chain("juno")?;
let osmosis = interchain.chain("osmosis")?;
let client = Client::new("test:client", juno.clone());
let host = Host::new("test:host", osmosis.clone());
client.upload()?;
host.upload()?;
client.instantiate(&Empty{}, None, None)?;
host.instantiate(&Empty{}, None, None)?;
The Client
and Host
structures here are cw-orchestrator Contracts with registered ibc endpoints.
Client contract definition (Click to get the full code)
use cw_orch::prelude::ContractWrapper;
use cw_orch::contract::WasmPath;
#[interface(
simple_ica_controller::msg::InstantiateMsg,
simple_ica_controller::msg::ExecuteMsg,
simple_ica_controller::msg::QueryMsg,
Empty
)]
struct Client;
impl<Chain> Uploadable for Client<Chain> {
// No wasm needed for this example
// You would need to get the contract wasm to be able to interact with actual Cosmos SDK nodes
fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
unimplemented!("No wasm")
}
// Return a CosmWasm contract wrapper with IBC capabilities
fn wrapper() -> Box<dyn MockContract<Empty>> {
Box::new(
ContractWrapper::new_with_empty(
simple_ica_controller::contract::execute,
simple_ica_controller::contract::instantiate,
simple_ica_controller::contract::query,
)
.with_ibc(
simple_ica_controller::ibc::ibc_channel_open,
simple_ica_controller::ibc::ibc_channel_connect,
simple_ica_controller::ibc::ibc_channel_close,
simple_ica_controller::ibc::ibc_packet_receive,
simple_ica_controller::ibc::ibc_packet_ack,
simple_ica_controller::ibc::ibc_packet_timeout,
),
)
}
}
Host contract definition (Click to get the full code)
// This is used because the simple_ica_host contract doesn't have an execute endpoint defined
pub fn host_execute(_: DepsMut, _: Env, _: MessageInfo, _: Empty) -> StdResult<Response> {
Err(StdError::generic_err("Execute not implemented for host"))
}
#[interface(
simple_ica_host::msg::InstantiateMsg,
Empty,
simple_ica_host::msg::QueryMsg,
Empty
)]
struct Host;
impl<Chain> Uploadable for Host<Chain> {
// No wasm needed for this example
// You would need to get the contract wasm to be able to interact with actual Cosmos SDK nodes
fn wasm(_chain: &ChainInfoOwned) -> WasmPath {
unimplemented!("No wasm")
}
// Return a CosmWasm contract wrapper with IBC capabilities
fn wrapper() -> Box<dyn MockContract<Empty>> {
Box::new(
ContractWrapper::new_with_empty(
host_execute,
simple_ica_host::contract::instantiate,
simple_ica_host::contract::query,
)
.with_reply(simple_ica_host::contract::reply)
.with_ibc(
simple_ica_host::contract::ibc_channel_open,
simple_ica_host::contract::ibc_channel_connect,
simple_ica_host::contract::ibc_channel_close,
simple_ica_host::contract::ibc_packet_receive,
simple_ica_host::contract::ibc_packet_ack,
simple_ica_host::contract::ibc_packet_timeout,
),
)
}
}
Then, we can create an IBC channel between the two contracts:
let channel_receipt = interchain.create_contract_channel(&client, &host, None, "simple-ica-v2").await?;
// After channel creation is complete, we get the channel id, which is necessary for ICA remote execution
let juno_channel = channel.0.get_chain("juno")?.channel.unwrap();
This step will also await until all the packets sent during channel creation are relayed. In the case of the ICA contracts, a {"who_am_i":{}}
packet is sent out right after channel creation and allows to identify the calling chain.
Finally, the two contracts can interact like so:
/// This broadcasts a transaction on the client
/// It sends an IBC packet to the host
let tx_response = client.send_msgs(
juno_channel.to_string(),
vec![CosmosMsg::Bank(cosmwasm_std::BankMsg::Burn {
amount: vec![cosmwasm_std::coin(100u128, "uosmo")],
})],
None
)?;
Now, we need to wait for the IBC execution to take place and the relayers to relay the packets. This is done through:
let packet_lifetime = interchain.wait_ibc("juno", tx_response).await?;
After that step, we make sure that the packets were relayed correctly
// For testing a successful outcome of the first packet sent out in the tx, you can use:
if let IbcPacketOutcome::Success{
ack,
..
} = packet_lifetime.packets[0].outcome{
if let IbcPacketAckDecode::Success(_) = ack{
/// Packet has been successfully acknowledged and decoded, the transaction has gone through correctly
}
/// Else, there was a decode error (maybe you are using the wrong acknowledgement format)
}else{
/// Else the packet timed-out, you may have a relayer error or something is wrong in your application
};
// OR you can use a helper, that will only error if one of the packets being relayed failed
assert_packets_success_decode(packet_lifetime)?;
If it was relayed correctly, we can proceed with our application.
With this simple guide, you should be able to test and debug your IBC application in no time. Learn more about the implementation and details of the IBC-enabled local testing environment.
With actual Cosmos SDK Nodes
You can also create an interchain environment that interacts with actual running chains. Keep in mind in that case that this type of environment doesn’t allow channel creation. This step will have to be done manually with external tooling. If you’re looking to test your application in a full local test setup, please turn to ↓Starship
use cw_orch::prelude::*;
use cw_orch::tokio;
// This is used to deal with async functions
let rt = tokio::runtime::Runtime::new().unwrap();
// We create the daemons ourselves to interact with actual running chains (testnet here)
let juno = Daemon::builder()
.chain(cw_orch::daemon::networks::UNI_6)
.build()
.unwrap();
// We create the daemons ourselves to interact with actual running chains (testnet here)
let osmosis = Daemon::builder()
.chain(cw_orch::daemon::networks::OSMO_5)
.build()
.unwrap();
// This will allow us to follow packets execution between juno and osmosis
let interchain = DaemonInterchainEnv::from_daemons(
vec![juno, osmosis],
&ChannelCreationValidator,
);
// In case one wants to analyze packets between more chains, you just need to add them to the interchain object
With this setup, you can now resume this quick-start guide from ↑Interacting with the environment.
You can also learn more about the interchain daemon implementation.
With Starship
You can also create you interchain environment using starship, which allows you to test your application against actual nodes and relayers. This time, an additional setup is necessary. Check out the official Starship Getting Started guide for more details.
Once starship is setup and all the ports forwarded, assuming that starship was run locally, you can execute the following:
use cw_orch::prelude::*;
use cw_orch::tokio;
// This is used to deal with async functions
let rt = tokio::runtime::Runtime::new().unwrap();
// This is the starship adapter
let starship = Starship::new(None).unwrap();
// You have now an interchain environment that you can use in your application
let interchain = starship.interchain_env();
This snippet will identify the local Starship setup and initialize all helpers and information needed for interaction using cw-orchestrator. With this setup, you can now resume this quick-start guide from ↑Interacting with the environment
You can also learn more about the interchain daemon implementation.
Execution Environment Integration
Daemon Interchain Environment
This environment allows to interact with actual COSMOS SDK Nodes. Let’s see how that work in details:
Environment creation
For scripting
When scripting with cw-orch-interchain
, developers don’t have to create chain Daemon
objects on their own. You can simply pass chain data to the interchain constructor, and it will create the daemons for you. Like so:
use cw_orch::prelude::*;
use cw_orch::tokio::runtime::Runtime;
use cw_orch::prelude::networks::{LOCAL_JUNO, LOCAL_OSMO};
use cw_orch_interchain::interchain::{ChannelCreationValidator,DaemonInterchainEnv};
fn main(){
let rt = Runtime::new()?;
let mut interchain = DaemonInterchainEnv::new(vec![
(LOCAL_JUNO, None),
(LOCAL_OSMO, None)
], &ChannelCreationValidator)?;
}
You can then access individual Daemon
objects like so:
use cw_orch::prelude::*;
use cw_orch_interchain::interchain::InterchainEnv;
fn main(){
let local_juno: Daemon = interchain.chain("testing")?;
let local_osmo: Daemon = interchain.chain("localosmosis")?;
}
where the argument of the chain
method is the chain id of the chain you are interacting with. Note that this environment can’t work with chains that have the same chain_id
.
NOTE: Here the
ChannelCreationValidator
struct is a helper that will simply wait for channel creation when it’s called in the script. More information on that channel creation later.
You can also add daemons manually to the interchain
object:
let local_migaloo = DaemonBuilder::default()
.chain(LOCAL_MIGALOO)
.build()?;
interchain.add_daemons(vec![local_migaloo]);
For testing
In some cases (we highly recommend it), you might want to interact with local nodes and relayers to test IBC interactions. To do so, we allow users to leverage Starship. Starship is developed by @cosmology_tech and allows developers to spin up a fully simulated mini-cosmos ecosystem. It sets up Cosmos SDK Nodes as well as relayers between them allowing you to focus on your application and less on the testing environment.
For setup, please refer to the official Quick Start. When all that is done, the starship adapter that we provide will detect the deployment and create the right cw-orchestrator
variables and structures for you to interact and test with.
use cw_orch_interchain::interchain::{Starship, ChannelCreator};
fn main(){
let rt = Runtime::new()?;
let starship = Starship::new(None)?;
let interchain = starship.interchain_env();
let _local_juno: Daemon = interchain.chain("juno-1")?;
let _local_osmo: Daemon = interchain.chain("osmosis-1")?;
}
NOTE: The second argument of the
Starship::new
function is the optional URL of the starship deployment. It defaults tohttp://localhost:8081
, but you can customize it if it doesn’t match your setup. All the starship data, daemons and relayer setup is loaded from that URL.
General Usage
All interchain environments are centered around the follow_packet
function. In the Daemon case (be it for testing or for scripting), this function is responsible for tracking the relayer interactions associated with the packet lifetime. The lifetime steps of this function are:
- ⬤ On the
source chain
, identify the packet and the destination chain. If the destination chain id is not registered in theinterchain
environment, it will error. Please make sure all the chains your are trying to inspect are included in the environment. - Then, it follows the time line of a packet. A packet can either timeout or be transmitted successfully. The function concurrently does the following steps. If one step returns successfully, the other step will be aborted (as a packet can only have one outcome).
a. Successful cycle:
- ⬤ On the
destination chain
, it looks for the receive transaction of that packet. The function logs the transaction hash as well as the acknowledgement when the receive transaction is found. - ⬤ On the
source chain
, it looks for the acknowledgement transaction of that packet. The function logs when the acknowledgement is received and returns with the transactions involved in the packet broadcast, as well as information about the acknowledgement. b. Timeout: - ⬤ On the
source chain
, it looks for the timeout transaction for that packet. The function logs the transaction hash of the transaction and returns the transaction response corresponding to that transaction.
- ⬤ On the
If you have followed the usage closely, you see that this function doesn’t error when the acknowledgement is an error, has a wrong format or if the packet timeouts. However, the function might error if either of the timeout/successful cycle takes too long. You can customize the wait time in the cw-orchestrator environment variables.
The wait_ibc
function is very similar except that instead of following a single packet, it follows all packets that are being sent within a transaction. This works in a very similar manner and will also not error as long as either a timeout or a successful cycle can be identified before cw-orchestrator
query function timeouts. This function is recursive as it will also look for packets inside the receive/ack/timeout transactions and also follow their IBC cycle. You can think of this function as going down the rabbit-hole of IBC execution and only returning when all IBC interactions are complete.
Analysis Usage
The follow_packet
and wait_ibc
function were coded for scripting usage in mind. They allow to await and repeatedly query Cosmos SDK Nodes until the cycle is complete. However, it is also possible to inspect past transactions using those tools.
Using the DaemonInterchainEnv::wait_ibc_from_txhash
function, one can inspect the history of packets linked to a transaction from a transaction hash only. This enables all kinds of analysis usage, here are some:
- Relayer activity
- Analysis of past transactions for fund recovery
- Whale account analysis
- …
IBC Channel creation
cw-orchestrator doesn’t provide1 relayer capabilities. We only provide tools to analyze IBC activity based on packet relaying mechanism that only relayers can provide. However, when testing your implementation with Starship, you might want to automatically create channels on your test setup.
This is what the second argument of the DaemonInterchainEnv::new
function is used for. You provide an object which will be responsible for creating an IBC channel between two ports. We provide 2 such structures, you can obviously create your own if your needs differ:
-
cw_orch_interchain::interchain::ChannelCreationValidator
This is used when you want to have full control over the channel creation. Wheninterchain.create_channel
is called, the script will stop and prompt you to create a channel with external tools. Once the channel creation process is done on your side, you simply have to input the connection-id on which you created the channel to be able to resume execution. This solution is not ideal at all but allows you to script on actual nodes without having to separate your scripts into multiple parts or change the syntax you coded for your tests.To create the interchain environment with this
ChannelCreator
, use the Validator syntax above. -
cw_orch_interchain::interchain::Starship
This is used when testing your application with Starship. When
interchain.create_channel
is called, the script will simply send a command to the starship cluster to create an IBC channel between the chains that you specified. Obviously, the relayer has to be specified in the starship configuration for this function to return successfully. With this function, you don’t have to worry about anything once your starship cluster is setup properly. The connection-id is returned automatically by the starship library and used throughout after that.To create the interchain environment with this
ChannelCreator
, use the Starship syntax above.
as of writing this documentation 09/28/2023
Mock Interchain Environment
This environment allows you to test your IBC application inside your Rust environment without having to create new crates, new packages or even run other programs in the background. This is perfect for iterating through your designs and testing on the go without additional wait time or setup tutorials.
Environment creation
You can create your interchain environment using the following simple setup script:
use cw_orch::prelude::*;
use cw_orch_interchain::interchain::MockInterchainEnv;
fn main(){
let sender = Addr::unchecked("sender");
let mut interchain = MockInterchainEnv::new(
vec![("juno-1", &sender), ("osmosis-1", &sender)],
);
}
Behind the scenes, Mock
objects are created with the specified chain ids. These mock environments can be used on their own to interact with testing environment directly. You can get those objects like so:
use cw_orch::prelude::*;
use cw_orch_interchain::interchain::InterchainEnv;
fn main(){
let local_juno: Daemon = interchain.chain("juno-1")?;
let local_osmo: Daemon = interchain.chain("osmosis-1")?;
}
where the argument of the chain
method is the chain id of the chain you are interacting with. Note that this environment can’t work with chains that have the same chain_id
.
You can also add mocks manually to the interchain
object, after instantiation:
fn main(){
use cosmwasm_std::Addr;
use cw_orch::prelude::*;
let sender = Addr::unchecked("sender");
let mut interchain = MockInterchainEnv::new(
vec![("juno-1", &sender), ("osmosis-1", &sender)],
);
let test_migaloo = Mock::new(&sender);
interchain.add_mocks(vec![test_migaloo]);
}
General Usage
All interchain environments are centered around the follow_packet
function. In the Mock case, this function is responsible for relaying the packets between the different chains. Using the exact same interface as with other environments, it takes care of all packet relaying procedures.
NOTE: Packets can either go through a successful cycle or timeout. In our current Mock implementation, it’s difficult to do it so that packets timeout. We are working on providing tools for developers to test all edge cases and timeout is part of the helpers we want to bring ASAP.
This function will relay a packets succesfully from the receiving chain back the the source chain. Here is what the full cycle looks like:
- ⬤ On the
source chain
, it queries the packet data associated with the packet channel and sequence. - ⬤ On the
destination chain
, it triggers a receive transaction for that packet. - ⬤ On the
source chain
, it finally triggers an acknowledgement transaction with the data thedestination_chain
returned.
The wait_ibc
function is very similar except that instead of following a single packet, it follows all packets that are being sent within a transaction. This works in a very similar manner and will never return a timeout transaction. This function is recursive as it will also look for packets inside the receive/ack transactions and also follow their IBC cycle. You can think of this function as going down the rabbit-hole of IBC execution and only returning when all IBC interactions are complete.
NOTE: most of the methods on the
interchain
variable presented here are async methods. We recommend reading more about async functions at the point. If you’re not working with any async functions, the gist here is:interchain.wait_ibc( &chain_id, tx_response )
IBC Channel creation
cw-orchestrator also provides tooling for creating channels between mock environments. Here is how you do it:
use ibc_relayer_types::core::ics24_host::identifier::PortId;
let rt = tokio::runtime::Runtime::new()?;
let src_chain = "juno-1".to_string();
let dst_chain = "juno-1".to_string();
let port_id = PortId::transfer();
let (channel, channel_creation_result) = rt.block_on(
interchain.create_channel(&src_chain, &dst_chain, None, &port_id, &port_id, "ics20-1")
)?;
- The resulting
channel
object allows you to identify the channel that was just created. It can be useful to retrieve the channel identifiers for instance - The resulting
channel_creation_result
object allows you to identify the different steps of channel creation as well as following all the packets that have been sent during this creation. This is very useful to analyze the effects of the channel creation on external contracts and structures.
Supported Chains
cw-orchestrator currently explicitly supports the chains in this section:
Support a new Cosmos Chain
Almost all Cosmos Chains can be added to cw-orchestrator. Depending on the on-chain modules (CosmWasm is not available on all chains for instance), action may be forbidden for some chains. However, connection should work out of the box for most of the chains. In order to add a new chain to your project, you can use the following objects:
use cw_orch::environment::{ChainInfo, ChainKind, NetworkInfo};
pub const NEW_NETWORK_INFO: NetworkInfo = NetworkInfo {
chain_name: "osmosis",
pub_address_prefix: "osmo",
coin_type: 118,
};
pub const NEW_CHAIN_INFO: ChainInfo = ChainInfo {
chain_id: "osmosis-4",
gas_denom: "uosmo",
gas_price: 7575.8,
grpc_urls: &["Some GRPC URLS"],
lcd_url: None, // Not necessary for cw-orch
fcd_url: None, // Not necessary for cw-orch
network_info: NEW_NETWORK_INFO,
kind: ChainKind::Mainnet,
};
This chain info can then be used inside your project just like any other chain defined inside cw-orch.
Alternatively, we suggest using the grpc_url and gas methods on the DaemonBuilder for quick and dirty fixes to the grpc url and the gas prices if needed.
If you would like to add explicit support for another chain, please feel free to open a PR!
Issues
Each of the gRPC endpoints has been battle-tested for deployments. If you find any issues, please open an issue!
Archway
Archway is a smart contract platform that directly rewards developers for their contributions. With built-in incentives, Archway equips developers with tools to craft and deploy scalable cross-chain dApps. As these dApps enhance the network’s value, developers enjoy further rewards.
pub const ARCHWAY_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "archway",
pub_address_prefix: "archway",
coin_type: 118u32,
};
/// Archway Docs: <https://docs.archway.io/resources/networks>
/// Parameters: <https://testnet.mintscan.io/archway-testnet/parameters>
pub const CONSTANTINE_3: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "constantine-3",
gas_denom: "aconst",
gas_price: 1000000000000.0,
grpc_urls: &["https://grpc.constantine.archway.io:443"],
network_info: ARCHWAY_NETWORK,
lcd_url: Some("https://api.constantine.archway.io"),
fcd_url: None,
};
/// Archway Docs: <https://docs.archway.io/resources/networks>
/// Parameters <https://www.mintscan.io/archway/parameters>
pub const ARCHWAY_1: ChainInfo = ChainInfo {
kind: ChainKind::Mainnet,
chain_id: "archway-1",
gas_denom: "aarch",
gas_price: 1000000000000.0,
grpc_urls: &["https://grpc.mainnet.archway.io:443"],
network_info: ARCHWAY_NETWORK,
lcd_url: Some("https://api.mainnet.archway.io"),
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::ARCHWAY_1
.
References
Injective
Injective is a unique blockchain tailored for finance, offering out-of-the-box modules like a fully decentralized orderbook. As an open smart contracts platform, it hosts a suite of decentralized apps designed for optimal user experience. Dive into Injective and unlock efficient capital allocation in decentralized financial markets.
pub const INJECTIVE_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "injective",
pub_address_prefix: "inj",
coin_type: 60u32,
};
/// <https://docs.injective.network/develop/public-endpoints/#mainnet>
/// <https://www.mintscan.io/injective/parameters>
/// <https://status.injective.network/>
pub const INJECTIVE_1: ChainInfo = ChainInfo {
kind: ChainKind::Mainnet,
chain_id: "injective-1",
gas_denom: "inj",
gas_price: 500_000_000.0,
grpc_urls: &["https://sentry.chain.grpc.injective.network:443"],
network_info: INJECTIVE_NETWORK,
lcd_url: None,
fcd_url: None,
};
/// <https://docs.injective.network/develop/public-endpoints/#testnet>
/// <https://testnet.status.injective.network/>
pub const INJECTIVE_888: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "injective-888",
gas_denom: "inj",
gas_price: 500_000_000.0,
grpc_urls: &["https://k8s.testnet.chain.grpc.injective.network:443"],
network_info: INJECTIVE_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
To interact with contracts on Injective, first enable the eth
feature for cw-orchestrator. Injective supports EVM-based addresses, and this will enable their use within cw-orchestrator.
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::INJECTIVE_1
.
References
Juno
Juno stands as the central hub for CosmWasm smart contracts, underpinned by the InterWasm DAO. As a global, open-source, and permission-less platform, Juno champions the forefront of CosmWasm development, enabling developers to seamlessly deploy inter-chain smart contracts crafted in Rust. The network’s inclusive design ensures that anyone can innovate and engage with inter-chain applications.
pub const JUNO_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "juno",
pub_address_prefix: "juno",
coin_type: 118u32,
};
pub const UNI_6: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "uni-6",
gas_denom: "ujunox",
gas_price: 0.025,
grpc_urls: &["http://juno-testnet-grpc.polkachu.com:12690"],
network_info: JUNO_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const JUNO_1: ChainInfo = ChainInfo {
kind: ChainKind::Mainnet,
chain_id: "juno-1",
gas_denom: "ujuno",
gas_price: 0.0750,
grpc_urls: &["http://juno-grpc.polkachu.com:12690"],
network_info: JUNO_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const LOCAL_JUNO: ChainInfo = ChainInfo {
kind: ChainKind::Local,
chain_id: "testing",
gas_denom: "ujunox",
gas_price: 0.0,
grpc_urls: &["http://localhost:9090"],
network_info: JUNO_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::JUNO_1
.
References
Kujira
A decentralized ecosystem for protocols, builders and web3 users seeking sustainable FinTech.
pub const KUJIRA_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "kujira",
pub_address_prefix: "kujira",
coin_type: 118u32,
};
pub const HARPOON_4: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "harpoon-4",
gas_denom: "ukuji",
gas_price: 0.025,
grpc_urls: &["http://kujira-testnet-grpc.polkachu.com:11890"],
network_info: KUJIRA_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::KAIYO_1
.
References
Neutron
The most secure CosmWasm platform in Cosmos, Neutron lets smart-contracts leverage bleeding-edge Interchain technology with minimal overhead.
pub const NEUTRON_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "neutron",
pub_address_prefix: "neutron",
coin_type: 118u32,
};
/// <https://github.com/cosmos/chain-registry/blob/master/testnets/neutrontestnet/chain.json>
pub const PION_1: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "pion-1",
gas_denom: "untrn",
gas_price: 0.075,
grpc_urls: &["http://grpc-palvus.pion-1.ntrn.tech:80"],
network_info: NEUTRON_NETWORK,
lcd_url: Some("https://rest-palvus.pion-1.ntrn.tech"),
fcd_url: None,
};
/// <https://github.com/cosmos/chain-registry/blob/master/neutron/chain.json>
pub const NEUTRON_1: ChainInfo = ChainInfo {
kind: ChainKind::Mainnet,
chain_id: "neutron-1",
gas_denom: "untrn",
gas_price: 0.075,
grpc_urls: &["http://grpc-kralum.neutron-1.neutron.org:80"],
network_info: NEUTRON_NETWORK,
lcd_url: Some("https://rest-kralum.neutron-1.neutron.org"),
fcd_url: None,
};
pub const LOCAL_NEUTRON: ChainInfo = ChainInfo {
kind: ChainKind::Local,
chain_id: "test-1",
gas_denom: "untrn",
gas_price: 0.0025,
grpc_urls: &["http://localhost:8090"],
network_info: NEUTRON_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::NEUTRON_1
.
References
Nibiru
Nibiru is a breakthrough smart contract platform providing superior throughput, reduced latency, and improved security, all driven by Web Assembly (Wasm) smart contracts. Nibiru simplifies the existing DeFi trading solutions through vertical integration.
pub const NIBIRU_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "nibiru",
pub_address_prefix: "nibi",
coin_type: 118u32,
};
pub const NIBIRU_ITN_2: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "nibiru-itn-2",
gas_denom: "unibi",
gas_price: 0.025,
grpc_urls: &["https://nibiru-testnet.grpc.kjnodes.com:443"],
network_info: NIBIRU_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::NIBIRU_ITN_2
.
References
Osmosis
Osmosis is a cutting-edge decentralized exchange built on the Cosmos network, designed for seamless asset swaps across various interconnected blockchains. As a trailblazer in AMM protocols, Osmosis empowers users with fast, secure, and efficient cross-chain liquidity solutions. The platform’s innovative approach to DeFi positions it as a cornerstone in the Cosmos ecosystem, enabling anyone to effortlessly tap into the vast potential of inter-chain finance.
pub const OSMO_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "osmosis",
pub_address_prefix: "osmo",
coin_type: 118u32,
};
pub const OSMOSIS_1: ChainInfo = ChainInfo {
kind: ChainKind::Mainnet,
chain_id: "osmosis-1",
gas_denom: "uosmo",
gas_price: 0.025,
grpc_urls: &["https://grpc.osmosis.zone:443"],
network_info: OSMO_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const OSMO_5: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "osmo-test-5",
gas_denom: "uosmo",
gas_price: 0.025,
grpc_urls: &["https://grpc.osmotest5.osmosis.zone:443"],
network_info: OSMO_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const LOCAL_OSMO: ChainInfo = ChainInfo {
kind: ChainKind::Local,
chain_id: "localosmosis",
gas_denom: "uosmo",
gas_price: 0.0026,
grpc_urls: &["http://65.108.235.46:9094"],
network_info: OSMO_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::OSMO_5
.
References
Sei
Sei is the fastest Layer 1 blockchain, designed to scale with the industry.
pub const SEI_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "sei",
pub_address_prefix: "sei",
coin_type: 118u32,
};
pub const LOCAL_SEI: ChainInfo = ChainInfo {
kind: ChainKind::Local,
chain_id: "sei-chain",
gas_denom: "usei",
gas_price: 0.1,
grpc_urls: &["http://localhost:9090"],
network_info: SEI_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const SEI_DEVNET_3: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "sei-devnet-3",
gas_denom: "usei",
gas_price: 0.1,
grpc_urls: &["http://sei_devnet-testnet-grpc.polkachu.com:11990"],
network_info: SEI_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const ATLANTIC_2: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "atlantic-2",
gas_denom: "usei",
gas_price: 0.1,
grpc_urls: &["http://sei-testnet-grpc.polkachu.com:11990"],
network_info: SEI_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const PACIFIC_1: ChainInfo = ChainInfo {
kind: ChainKind::Mainnet,
chain_id: "pacific-1",
gas_denom: "usei",
gas_price: 0.1,
grpc_urls: &["http://sei-grpc.polkachu.com:11990"],
network_info: SEI_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::SEI_DEVNET_3
.
References
Terra
Fueled by a passionate community and deep developer talent pool, the Terra blockchain is built to enable the next generation of Web3 products and services.
pub const TERRA_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "terra2",
pub_address_prefix: "terra",
coin_type: 330u32,
};
/// Terra testnet network.
/// <https://docs.terra.money/develop/endpoints>
pub const PISCO_1: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "pisco-1",
gas_denom: "uluna",
gas_price: 0.015,
grpc_urls: &["http://terra-testnet-grpc.polkachu.com:11790"],
network_info: TERRA_NETWORK,
lcd_url: None,
fcd_url: None,
};
/// Terra mainnet network.
///<https://docs.terra.money/develop/endpoints>
pub const PHOENIX_1: ChainInfo = ChainInfo {
kind: ChainKind::Mainnet,
chain_id: "phoenix-1",
gas_denom: "uluna",
gas_price: 0.015,
grpc_urls: &["http://terra-grpc.polkachu.com:11790"],
network_info: TERRA_NETWORK,
lcd_url: None,
fcd_url: None,
};
/// Terra local network.
/// <https://docs.terra.money/develop/guides/initial/#next-steps-localterra-or-testnet>
pub const LOCAL_TERRA: ChainInfo = ChainInfo {
kind: ChainKind::Local,
chain_id: "localterra",
gas_denom: "uluna",
gas_price: 0.15,
grpc_urls: &["http://localhost:9090"],
network_info: TERRA_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::PHOENIX_1
.
References
Rollkit
Rollkit is the open modular framework for sovereign rollups. Their mission is to empower developers to quickly innovate and create entire new classes of rollups with minimal tradeoffs.
pub const ROLLKIT_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "rollkit",
pub_address_prefix: "wasm",
coin_type: 118u32,
};
pub const LOCAL_ROLLKIT: ChainInfo = ChainInfo {
kind: ChainKind::Local,
chain_id: "celeswasm",
gas_denom: "uwasm",
gas_price: 0.025,
grpc_urls: &["http://localhost:9290"],
network_info: ROLLKIT_NETWORK,
lcd_url: None,
fcd_url: None,
};
pub const ROLLKIT_TESTNET: ChainInfo = ChainInfo {
kind: ChainKind::Local,
chain_id: "rosm",
gas_denom: "urosm",
gas_price: 0.025,
grpc_urls: &["http://grpc.rosm.rollkit.dev:9290"],
network_info: ROLLKIT_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::LOCAL_ROLLKIT
.
Compatibility
This integration has been tested using the Rollkit Setup with CosmWasm. The following versions were used:
- Mocha testnet node:
celestia/v0.15.0.beta.1
- Rollup node:
wasmd/v0.50.0
(which opens up port 9290 locally) - In order to use
cw-orchestrator
with this setup, theLOCAL_MNEMONIC
environment variable needs to be assigned. You can find the right value for this mnemonic in the beginning of the console logs generated by thebash init.sh
command in the official RollKit docs.
Please contact us on Discord if you have any issues.
References
XION
XION empowers developers and brands to create frictionless Web3 experiences, with a Generalized Abstraction layer that removes technical barriers for all users.
pub const XION_NETWORK: NetworkInfo = NetworkInfo {
chain_name: "xion",
pub_address_prefix: "xion",
coin_type: 118u32,
};
pub const XION_TESTNET_1: ChainInfo = ChainInfo {
kind: ChainKind::Testnet,
chain_id: "xion-testnet-1",
gas_denom: "uxion",
gas_price: 0.0,
grpc_urls: &["http://xion-testnet-grpc.polkachu.com:22390"],
network_info: XION_NETWORK,
lcd_url: None,
fcd_url: None,
};
Usage
See how to setup your main function in the main function section. Update the network passed into the Daemon
builder to be networks::XION_TESTNET_1
.
References
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 of the cw-orchestrator Github repository 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: "./state.json" # Optional
ARTIFACTS_DIR: "./target/wasm32-unknown-unknown/release" # Optional
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
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-name
command. - 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.