- Docs
- Smart Contracts
- Integrate with smart contracts
Integrate with smart contracts
Integrating with other CosmWasm contracts involves understanding and utilizing entry points effectively. Entry points in a smart contract are the methods through which external actors can interact with the contract. See more details here.
In CosmWasm, inter-contract calls are facilitated using the CosmosMsg::Wasm(WasmMsg::Execute{}) message type. This allows your contract to send messages to other contracts during execution, enabling complex interactions and integrations. Additionally, you can use SubMsg for more advanced control over inter-contract calls, including handling responses from the called contracts. See the following for more details on the use of submessages.
Structure of WasmMsg::Execute
The WasmMsg::Execute type requires the following arguments:
- contract_addr: The address of the target contract.
- msg: The message to be sent to the target contract, formatted as a JSON object that conforms to the target contract’s schema.
- funds: (Optional) The coins to be transferred to the target contract as part of the call.
Example: making calls to a CW20 contract
In this example, we demonstrate how to interact with a CW20 contract, specifically calling its Transfer
method. CW20 is a standard for fungible tokens in the CosmWasm ecosystem, similar to ERC-20 on Ethereum. For more details on the CW20 contract, including its available methods and message schemas, you can refer to the CW20 Core Contract Documentation.
Using WasmMsg::Execute
Below is an example of how to use CosmosMsg::Wasm(WasmMsg::Execute{}) to interact with a CW20 contract’s execute
method:
// Step 1: Define the Transfer message for the CW20 contractlet transfer_msg = to_binary(&Transfer { recipient: deps.api.addr_humanize(&recipient_raw)?, amount, // The amount of tokens to transfer})?;// Step 2: Construct the WasmMsg::Execute messagelet msg = WasmMsg::Execute { contract_addr: other_contract_addr.to_string(), // Address of the CW20 contract msg: transfer_msg, // The message to send, encoded in binary format funds: vec![], // Optional funds to transfer alongside the message, left empty here};// Step 3: Create a response that includes the message to be executedOk(Response::new() .add_attribute("action", "call_cw20_transfer") // Add custom attributes for logging .add_message(msg)) // Add the constructed message to the response
Explanation:
- Step 1: Define the Transfer Message
Thetransfer_msg
is created by encoding aTransfer
struct into a binary format that the target contract can understand. Therecipient
field is obtained by converting a raw address into a human-readable format, andamount
represents the tokens to be transferred. The use ofto_binary
ensures that the message is properly formatted according to the target contract’s expectations. - Step 2: Construct the
WasmMsg::Execute
Message
TheWasmMsg::Execute
struct is created to encapsulate the inter-contract call. Thecontract_addr
specifies the address of the contract you want to interact with. Themsg
field contains the binary-encoded transfer message, which is sent to the target contract. Thefunds
field allows you to send native tokens (like ATOM or ARCH) with the message, but in this case, it’s an empty vector, meaning no funds are transferred. - Step 3: Create the Response
TheResponse::new()
function is used to construct the response that your contract will return after executing the logic. The response includes attributes (key-value pairs) that can be used for logging or analytics. The.add_message(msg)
method appends the previously constructedWasmMsg::Execute
message to the response. This ensures that after your contract's execution, the message will be dispatched to the target contract for execution.
Using SubMsg
for enhanced control
SubMsg is an advanced feature in CosmWasm that allows you to send messages to other contracts and handle their responses. This is particularly useful when you need to ensure that certain actions are only performed if the inter-contract call is successful, or when you want to handle failures differently.
Below is an example of how to use SubMsg with the same CW20 contract’s Transfer
method:
use cosmwasm_std::{to_binary, Response, SubMsg, WasmMsg};// Define a unique ID for the submessage replyconst CW20_TRANSFER_REPLY_ID: u64 = 1u64;// Step 1: Define the Transfer message for the CW20 contractlet transfer_msg = to_binary(&Transfer { recipient: deps.api.addr_humanize(&recipient_raw)?, amount, // The amount of tokens to transfer})?;// Step 2: Construct the WasmMsg::Execute messagelet msg = WasmMsg::Execute { contract_addr: other_contract_addr.to_string(), // Address of the CW20 contract msg: transfer_msg, // The message to send, encoded in binary format funds: vec![], // Optional funds to transfer alongside the message, left empty here};// Step 3: Wrap the message in a SubMsg for enhanced controllet sub_msg = SubMsg::reply_on_success(msg, CW20_TRANSFER_REPLY_ID);// Step 4: Create a response that includes the SubMsg to be executedOk(Response::new() .add_attribute("action", "call_cw20_transfer_submsg") // Add custom attributes for logging .add_submessage(sub_msg)) // Add the SubMsg to the response
Explanation:
- Step 1: Define the Transfer Message
Thetransfer_msg
is created by encoding aTransfer
struct into a binary format that the target contract can understand. Therecipient
field is obtained by converting a raw address into a human-readable format, andamount
represents the tokens to be transferred. The use ofto_binary
ensures that the message is properly formatted according to the target contract’s expectations. - Step 2: Construct the
WasmMsg::Execute
Message
TheWasmMsg::Execute
struct is created to encapsulate the inter-contract call. Thecontract_addr
specifies the address of the CW20 contract you want to interact with. Themsg
field contains the binary-encoded transfer message, which is sent to the target contract. Thefunds
field is optional and can be used to transfer native tokens along with the message, but it’s left empty here. - Step 3: Wrap the Message in a SubMsg
TheSubMsg::reply_on_success
function is used to wrap theWasmMsg::Execute
message in aSubMsg
. This ensures that a reply will be sent back to the contract only if the message execution is successful. TheCW20_TRANSFER_REPLY_ID
is a unique identifier that helps distinguish this reply from others. - Step 4: Create the Response
TheResponse::new()
function is used to create a response that includes theSubMsg
. The.add_submessage(sub_msg)
method appends theSubMsg
to the response. When this response is returned, the CW20Transfer
will be executed, and the contract will receive a reply if it succeeds.
Handling the reply from the SubMsg
To handle the reply from a SubMsg
, you must implement a reply handler in your contract:
use cosmwasm_std::{DepsMut, Env, Reply, Response, StdError, StdResult};#[cfg_attr(not(feature = "library"), entry_point)]pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult<Response> { match msg.id { CW20_TRANSFER_REPLY_ID => handle_transfer_reply(deps, msg), _ => Err(StdError::generic_err("Unknown reply id")), }}fn handle_transfer_reply(deps: DepsMut, msg: Reply) -> StdResult<Response> { // Check if the transfer was successful if msg.result.is_err() { return Err(StdError::generic_err("CW20 transfer failed")); } // Process the success (e.g., update state or emit an event) Ok(Response::new().add_attribute("action", "transfer_success"))}
- Reply Entry Point
Thereply
function is marked as an entry point for handling submessage replies. It matches themsg.id
against theCW20_TRANSFER_REPLY_ID
to determine which reply handler to call. - Handling the Transfer Reply
Thehandle_transfer_reply
function checks if the transfer was successful. If it failed, it returns an error; otherwise, it proceeds to process the success, such as updating the contract state or emitting an event.
Advanced integration techniques
When interacting with other contracts, consider the following best practices:
- Check Contract Interfaces: Always review the target contract’s message schemas and entry points. Contracts may have different versions or upgrades, so ensuring compatibility is critical.
- Handle Errors Gracefully: Ensure that your contract properly handles errors that may arise from inter-contract calls. This might include setting up retries or fallbacks in case of failed calls.
- Security Considerations: Validate all input data and avoid exposing sensitive contract states through queries. Implement proper access controls on critical execute functions.
Execution flow and failure handling
It’s important to remember that WasmMsg::Execute messages are only executed after your contract’s execution completes successfully. If your contract’s execution fails, the WasmMsg::Execute message will not be sent, ensuring atomicity in transaction execution.