Interfaces
Interfaces are virtual wrappers around CosmWasm contracts. They allow you to interact with your contracts in a type-safe way, and provide a convenient way to reason about contract interactions. Interfaces are the core reason why we built cw-orchestrator and we hope that you’ll find them as useful as we do.
Reminder: You can find the code for this example in the cw-orch counter-contract folder.
If you are a fast or visual learner, you can find a Before-After view of the
cw-orch
integration process in the sample contract.
Setup
Before we can create an interface we need to add cw-orch to the contract’s Cargo.toml
file. In counter
run:
$ cargo add --optional cw-orch
> Adding cw-orch v0.17.0 to optional dependencies.
or add it manually to the counter/Cargo.toml
file:
[dependencies]
cw-orch = {version = "0.17.0", optional = true } # Latest version at time of writing
We add cw-orch
as an optional dependency to ensure that it is not included in the wasm artifact of the contract. This way there are no trust assumptions made about the code added by cw-orch
, making it safe to use for production contracts.
However, we will need a way to enable the dependency when we want to use it. To do this add an interface
feature to the Cargo.toml
and enable cw-orch
when it is enabled.
You can do this by including the following in the counter/Cargo.toml
:
[features]
interface = ["dep:cw-orch"] # Adds the dependency when the feature is enabled
Features (aka. feature flags) are a way to enable or disable parts of your code. In this case, we are including cw-orch
as a dependency when the interface
feature is enabled. This is a common pattern for feature flags.
Creating an Interface
Now that we have our dependency set up we can create the interface. cw-orch
provides a powerful interface
macro that allows you to create an interface for your contract without having to call it at the entry points, as well as the ability to specify the contract’s source more easily.
To use this macro, first create a new file in the contract/src
directory called interface.rs
. This is where we will expose our interface.
In counter/src/lib.rs
:
#[cfg(feature = "interface")]
mod interface;
This allows importing the interface.rs
file only when the interface
feature is enabled on the contract. Again, this is done because we don’t want cw-orch dependencies to end up in your resulting Wasm
contract. Using this syntax won’t change anything to the actual wasm file that you are uploading on-chain if you don’t enable this feature by default.
Then in counter/src/interface.rs
:
use cw_orch::{interface, prelude::*};
use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg};
#[interface(InstantiateMsg, ExecuteMsg, QueryMsg, MigrateMsg)]
pub struct CounterContract;
impl<Chain: CwEnv> Uploadable for CounterContract<Chain> {
/// Return the path to the wasm file corresponding to the contract
fn wasm(&self) -> WasmPath {
artifacts_dir_from_workspace!()
.find_wasm_path("counter_contract")
.unwrap()
}
/// Returns a CosmWasm contract wrapper
fn wrapper(&self) -> Box<dyn MockContract<Empty>> {
Box::new(
ContractWrapper::new_with_empty(
crate::contract::execute,
crate::contract::instantiate,
crate::contract::query,
)
.with_migrate(crate::contract::migrate),
)
}
}
This use of the interface
macro even allows you to have generic arguments in the message types. Any generics will be added to the interface under a PhantomData
attribute.
It can be beneficial to re-export the structure in our lib.rs
file.
In the counter contract we re-export in lib.rs
;
#[cfg(feature = "interface")]
pub use crate::interface::CounterContract;
NOTE: You can see that we have used the
artifacts_dir_from_workspace
macro inside 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:
#![allow(unused)] fn main() { let crate_path = env!("CARGO_MANIFEST_DIR"); let wasm_path = format!("{}/../../artifacts/counter_contract.wasm", crate_path); WasmPath::new(wasm_path).unwrap() }
Constructor
The interface
macro implements a new
function on the interface:
// Construct the counter interface
let contract = CounterContract::new(CONTRACT_NAME, chain.clone());
The constructor takes two arguments:
contract_id
: The unique identifier for this contract. This is used as the key when retrieving address and code_id information for the contract. This argument is a&str
.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
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(CONTRACT_NAME, chain.clone());
let admin = Addr::unchecked(ADMIN);
// Upload the contract
let upload_resp = contract.upload()?;
// Get the code-id from the response.
let code_id = upload_resp.uploaded_code_id()?;
// or get it from the interface.
assert_eq!(code_id, contract.code_id()?);
// Instantiate the contract
let msg = InstantiateMsg { count: 1i32 };
let init_resp = contract.instantiate(&msg, Some(&admin), None)?;
// Get the address from the response
let contract_addr = init_resp.instantiated_contract_address()?;
// or get it from the interface.
assert_eq!(contract_addr, contract.address()?);
// Return the interface
Ok(contract)
}
Entry Point Function Generation
Contract execution and querying is so common that we felt the need to improve the method of calling them. To do this we created two macros: ExecuteFns
and QueryFns
. As their name implies they can be used to automatically generate functions for executing and querying your contract through the interface.
Execution
To get started, find the ExecuteMsg
definition for your contract. In our case it’s located in counter/src/msg.rs
. Then add the following line to your ExecuteMsg
enum:
#[cw_serde]
#[cfg_attr(feature = "interface", derive(cw_orch::ExecuteFns))] // Function generation
/// Execute methods for counter
pub enum ExecuteMsg {
/// Increment count by one
Increment {},
/// Reset count
Reset {
/// Count value after reset
count: i32,
},
}
Again we feature flag the function generation to prevent cw-orchestrator entering as a dependency when building your contract.
The functions are implemented as a trait named ExecuteMsgFns
which is implemented on any interface that uses this ExecuteMsg
as an entrypoint message.
Using the trait then becomes as simple as:
// in integration_tests.rs
// Reset
use counter_contract::CounterExecuteMsgFns;
contract.reset(0)?;
Query
Generating query functions is a similar process but has the added advantage of using the cosmwasm-schema
return tags to detect the query’s return type. This allows for type-safe query functions!
#[cw_serde]
#[cfg_attr(feature = "interface", derive(cw_orch::QueryFns))] // Function generation
#[derive(QueryResponses)]
/// Query methods for counter
pub enum QueryMsg {
/// GetCount returns the current count as a json-encoded number
#[returns(GetCountResponse)]
GetCount {},
}
// Custom response for the query
#[cw_serde]
/// Response from get_count query
pub struct GetCountResponse {
/// Current count in the state
pub count: i32,
}
Using it is just as simple as the execution functions:
// in integration_tests.rs
// Get the count.
use counter_contract::CounterQueryMsgFns;
let count1 = contract.get_count()?;
// or query it manually
let count2: GetCountResponse = contract.query(&QueryMsg::GetCount {})?;
assert_eq!(count1.count, count2.count);
Just like the interface it can be beneficial to re-export the trait in your lib.rs
or interface.rs
file.
In the counter contract we re-export in lib.rs
;
#[cfg(feature = "interface")]
pub use crate::msg::{ExecuteMsgFns as CounterExecuteMsgFns, QueryMsgFns as CounterQueryMsgFns};
Additional Remarks on QueryFns
and ExecuteFns
The QueryFns
and ExecuteFns
derive macros generate traits that are implemented on any Contract structure (defined by the interface
macro) that have the matching execute and query types. Because of the nature of rust traits, you need to import the traits in your application to use the simplifying syntax. Those traits are named ExecuteMsgFns
and QueryMsgFns
.
Any variant of the ExecuteMsg
and QueryMsg
that has a #[derive(ExecuteFns)]
or #[derive(QueryFns)]
will have a function implemented on the interface (e.g. CounterContract
) through a trait. Here are the main things you need to know about the behavior of those macros:
- The function created will have the snake_case name of the variant and will take the same arguments as the variant.
- The arguments are ordered in alphabetical order to prevent attribute ordering from changing the function signature.
- If coins need to be sent along with the message you can add
#[payable]
to the variant and the function will take 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 #[payable]
attribute on your enum variant like so:
#[derive(ExecuteFns)]
enum ExecuteMsg{
UpdateConfig{
config_field: String
},
#[payable]
DepositStable{}
...
}
Be defining this attribute, you can now use:
use cosmwasm_std::coins;
money_market.deposit_stable(&coins(456, "ujunox"))?;
fn_name
Attribute
#![allow(unused)] fn main() { #[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg{ Execute{ msg: CosmoMsg } } }
The following command will error because the execute
function is reserved for contract execution. This will not even compile actually.
#![allow(unused)] fn main() { money_market.execute(message_to_execute_via_a_proxy)?; }
This can happen in numerous cases actually, when using reserved keywords of cw-orch (or even rust). If this happens, you can use the fn_name
attribute to rename a generated function.
#![allow(unused)] fn main() { #[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsg{ #[fn_name("proxy_execute")] Execute{ msg: CosmoMsg } } // This works smoothly ! money_market.proxy_execute(message_to_execute_via_a_proxy)?; }
This is also true for query functions.
impl_into
Attribute
For nested messages (execute and query) you can add an impl_into
attribute. This expects the enum to implement the Into
trait for the provided type. This is extremely useful when working with generic messages:
#![allow(unused)] fn main() { use cw_orch::interface; use cw_orch::prelude::*; // An execute message that is generic. #[cosmwasm_schema::cw_serde] pub enum GenericExecuteMsg<T> { Generic(T), Nested(NestedMessageType), } // This is the message that will be used on our contract type ExecuteMsg = GenericExecuteMsg<Foo>; #[cosmwasm_schema::cw_serde] #[derive(cw_orch::ExecuteFns)] #[impl_into(ExecuteMsg)] pub enum Foo { Bar { a: String }, } impl From<Foo> for ExecuteMsg { fn from(msg: Foo) -> Self { ExecuteMsg::Generic(msg) } } #[cosmwasm_schema::cw_serde] #[derive(cw_orch::ExecuteFns)] #[impl_into(ExecuteMsg)] pub enum NestedMessageType { Test { b: u64 }, } impl From<NestedMessageType> for ExecuteMsg { fn from(msg: NestedMessageType) -> Self { ExecuteMsg::Nested(msg) } } #[interface(Empty, ExecuteMsg, Empty, Empty)] struct Example<Chain>; impl<Chain: CwEnv> Example<Chain> { pub fn test_macro(&self) { // function `bar` is available because of the `impl_into` attribute! self.bar("hello".to_string()).unwrap(); // function `test` is available because of the `impl_into` attribute! self.test(65).unwrap(); } } }
disable_fields_sorting
Attribute
By default the ExecuteFns
and QueryFns
derived traits will sort the fields of each enum member. For instance,
#![allow(unused)] fn main() { #[cw_serde] #[derive(cw_orch::ExecuteFns)] pub enum ExecuteMsgOrdered { Test { b: u64, a: String }, } }
will generate
#![allow(unused)] fn main() { pub fn bar(a: String, b: u64) -> ...{ ... } }
You see in this example that the fields of the bar function are sorted lexicographically. We decided to put this behavior as default to prevent potential errors when rearranging the order of enum fields. If you don’t want this behavior, you can disable it by using the disable_fields_sorting
attribute. This is the resulting behavior:
#![allow(unused)] fn main() { #[cw_serde] #[derive(cw_orch::ExecuteFns)] #[disable_fields_sorting] pub enum ExecuteMsg { Test { b: u64, a: String }, } pub fn bar(b: u64, a: String) -> ...{ ... } }
NOTE: This behavior CAN be dangerous if your struct members have the same type. In that case, if you want to rearrange the order of the members inside the struct definition, you will have to be careful that you respect the orders in which you want to pass them.
Learn more
Got questions? Join the Abstract Discord and ask in the #cw-orchestrator
channel.
Learn more about Abstract at abstract.money.