Prerequisites
Before starting, make sure you haverustup along with recent versions of rustc and cargo installed. Currently, we are testing on Rust v1.58.1+.
You also need to have the wasm32-unknown-unknown target installed as well as the cargo-generate Rust crate.
You can check versions via the following commands:
Objectives
- Create and interact with a smart contract that increases and resets a counter to a given value
- Understand the basics of a CosmWasm smart contract, learn how to deploy it on Injective, and interact with it using Injective tools
CosmWasm Contract Basics
A smart contract can be considered an instance of a singleton object whose internal state is persisted on the blockchain. Users can trigger state changes by sending the smart contract JSON messages, and users can also query its state by sending a request formatted as a JSON message. These JSON messages are different than Injective blockchain messages such asMsgSend and MsgExecuteContract.
As a smart contract writer, your job is to define 3 functions that compose your smart contract’s interface:
instantiate(): a constructor which is called during contract instantiation to provide initial stateexecute(): gets called when a user wants to invoke a method on the smart contractquery(): gets called when a user wants to get data out of a smart contract
instantiate, one query, and two execute methods.
Start with a Template
In your working directory, quickly launch your smart contract with the recommended folder structure and build options by running the following commands:src/contract.rs file you will find that the standard CosmWasm entrypoints instantiate(), execute(), and query() are properly exposed and hooked up.
State
You can learn more about CosmWasm State on their documentation.
State handles the state of the database where smart contract data is stored and accessed.
The starting template has the following basic state, a singleton struct State containing:
count, a 32-bit integer with whichexecute()messages will interact by increasing or resetting it.owner, the senderaddressof theMsgInstantiateContract, which will determine if certain execution messages are permitted.
cosmwasm-storage, which provides convenient high-level abstractions for data containers such as a “singleton” and “bucket”, which automatically provide serialization and deserialization for commonly-used types, such as structs and Rust numbers. Additionally, thecw-storage-plus crate can be used for a more efficient storage mechanism.
Notice how the State struct holds both count and owner. In addition, the derive attribute is applied to auto-implement some useful traits:
Serialize: provides serializationDeserialize: provides deserializationClone: makes the struct copyableDebug: enables the struct to be printed to stringPartialEq: provides equality comparisonJsonSchema: auto-generates a JSON schema
Addr refers to a human-readable Injective address prefixed with inj, e.g. inj1clw20s2uxeyxtam6f7m84vgae92s9eh7vygagt.
InstantiateMsg
You can learn more about CosmWasm InstantiateMsg on their documentation
InstantiateMsg is provided to the contract when a user instantiates a contract on the blockchain through a MsgInstantiateContract. This provides the contract with its configuration as well as its initial state.
On the Injective blockchain, the uploading of a contract’s code and the instantiation of a contract are regarded as separate events, unlike on Ethereum. This is to allow a small set of vetted contract archetypes to exist as multiple instances sharing the same base code, but be configured with different parameters (imagine one canonical ERC20, and multiple tokens that use its code).
Example
For your contract, the contract creator is expected to supply the initial state in a JSON message. We can see in the message definition below that the message holds one parametercount, which represents the initial count.
Message Definition
Contract Logic
Incontract.rs, you will define your first entry-point, instantiate(), or where the contract is instantiated and passed its InstantiateMsg. Extract the count from the message and set up your initial state where:
countis assigned the count from the messageowneris assigned to the sender of theMsgInstantiateContract
ExecuteMsg
You can learn more about CosmWasm ExecuteMsg in their documentation.
ExecuteMsg is a JSON message passed to the execute() function through a MsgExecuteContract. Unlike the InstantiateMsg, the ExecuteMsg can exist as several different types of messages to account for the different types of functions that a smart contract can expose to a user. The execute() function demultiplexes these different types of messages to its appropriate message handler logic.
We have two ExecuteMsg: Increment and Reset.
Incrementhas no input parameter and increases the value of count by 1.Resettakes a 32-bit integer as a parameter and resets the value ofcountto the input parameter.
Example
Increment Any user can increment the current count by 1.Reset
Only the owner can reset the count to a specific number. See Logic below for the implementation details.Message Definition
ForExecuteMsg, an enum can be used to multiplex over the different types of messages that your contract can understand. The serde attribute rewrites your attribute keys in snake case and lower case, so you’ll have increment and reset instead of Increment and Reset when serializing and deserializing across JSON.
Logic
execute() method, which uses Rust’s pattern matching to route the received ExecuteMsg to the appropriate handling logic, either dispatching a try_increment() or a try_reset() call depending on the message received.
state. It then updates the state’s count by returning an Ok result with the new state. Finally, it terminates the contract’s execution with an acknowledgement of success by returning an Ok result with the Response.
QueryMsg
You can learn more about CosmWasm QueryMsg in their documentation
GetCount query message has no parameters and returns the count value.
See the implementation details in Logic below.
Example
The template contract only supports one type ofQueryMsg:
GetCount
The request:
Message Definition
To support data queries in the contract, you’ll have to define both aQueryMsg format (which represents requests), as well as provide the structure of the query’s output—CountResponse in this case. You must do this because query() will send information back to the user through structured JSON, so you must make the shape of your response known. See Generating JSON Schema for more info.
Add the following to your src/msg.rs:
Logic
The logic forquery() is similar to that of execute(); however, since query() is called without the end-user making a transaction, the env argument is omitted as no information is necessary.
Unit test
Unit tests should be run as the first line of assurance before deploying the code on chain. They are quick to execute and can provide helpful backtraces on failures with theRUST_BACKTRACE=1 flag:
src/contract.rs
Building the Contract
Now that we understand and have tested the contract, we can run the following command to build the contract. This will check for any preliminary errors before we optimize the contract in the next step.Read more details on preparing the Wasm bytecode for production
/code and optimize the output (you can use an absolute path instead of $(pwd) if you don’t want to cd to the directory first):
CosmWasm does not recommend using the ARM64 version of the compiler because it produces different Wasm artifacts from the Intel/AMD version. For release/production, only contracts built with Intel/AMD optimizers are recommended for use. See here for the note from CosmWasm.
You may receive an See The Cargo Book for more information.
Unable to update registry `crates-io` error while running the command. Try adding the following lines to the Cargo.toml file located within the contract directory and running the command again:artifacts directory with a PROJECT_NAME.wasm, as well as checksums.txt, containing the Sha256 hash of the Wasm file. The Wasm file is compiled deterministically (anyone else running the same docker on the same git commit should get the identical file with the same Sha256 hash).
Install injectived
injectived is the command-line interface and daemon that connects to Injective and enables you to interact with the Injective blockchain.
If you want to interact with your Smart Contract locally using CLI, you have to have injectived installed. To do so, you can follow the installation guidelines here #install-injectived.
Alternatively, a Docker image has been prepared to make this tutorial easier.
If you install
injectived from the binary, ignore the docker commands.
In the public endpoints section you can find the right —node info to interact with Mainnet and Testnet.directory_to_which_you_cloned_cw-template must be an absolute path. The absolute path can easily be found by running the pwd command from inside the CosmWasm/cw-counter directory.
Open a new terminal and go into the Docker container to initialize the chain:
jq dependency, which will be needed later on:
testuser (when prompted use 12345678 as password). We will only use the test user to generate a new private key that will later on be used to sign messages on the testnet:
You can request testnet funds for your recently generated test address using the Injective test faucet.
testuser an Injective Testnet. It should also hold some funds after requesting testnet funds from the faucet.
To confirm, search for your address on the Injective Testnet Explorer to check your balance.
Alternatively, you can verify it by querying the bank balance or with curl:
Upload the Wasm Contract
Now it’s time to upload the.wasm file that you compiled in the previous steps to the Injective Testnet. Please note that the procedure for mainnet is different and requires a governance proposal.
txhash returned from storing the code on chain. The transaction type should be MsgStoreCode.
You can see all stored codes on Injective Testnet under Code.
There are different ways to find the code that you just stored:
- Look for the TxHash on the Injective Explorer codes list; it is most likely the most recent.
- Use
injectivedto query transaction info.
txhash and verify the contract was deployed.
code_id of 290 for the uploaded contract:
code_id as an ENV variable—we’ll need it to instantiate the contract. You can skip this step and manually add it later, but keep note of the ID.
Generating JSON Schema
While the Wasm callsinstantiate, execute, and query accept JSON, this is not enough information to use them. We need to expose the schema for the expected messages to the clients.
In order to make use of JSON-schema auto-generation, you should register each of the data structures that you need schemas for.
./schema, corresponding to the 3 message types the contract accepts, the query response message, and the internal State.
These files are in standard JSON Schema format, which should be usable by various client side tools, either to auto-generate codecs, or just to validate incoming JSON with respect to the defined schema.
Take a minute to generate the schema (take a look at it here) and get familiar with it, as you will need to it for the next steps.
Instantiate the Contract
Now that we have the code on Injective, it is time to instantiate the contract to interact with it.Reminder On CosmWasm, the upload of a contract’s code and the instantiation of a contract are regarded as separate events
You can find the contract address and metadata by:
- Looking on the Testnet Explorer
- Querying the ContractsByCode and ContractInfo APIs
- Querying through the CLI
Querying the Contract
As we know from earlier, the only QueryMsg we have isget_count.
count is 99, as set when we instantiated the contract.
If you query the same contract, you may receive a different response as others may have interacted with the contract and incremented or reset the count.
Execute the Contract
Let’s now interact with the contract by incrementing the counter.yes 12345678 | automatically pipes (passes) the passphrase to the input of injectived tx wasm execute so you do not need to enter it manually.
Cosmos Messages
In addition to defining custom smart contract logic, CosmWasm also allows contracts to interact with the underlying Cosmos SDK functionalities. One common use case is to send tokens from the contract to a specified address using the Cosmos SDK’s bank module.Example: Bank Send
TheBankMsg::Send message allows the contract to transfer tokens to another address. This can be useful in various scenarios, such as distributing rewards or returning funds to users.
Note: If you want to send funds and execute a function on another contract at the same time, don’t use BankMsg::Send. Instead, use WasmMsg::Execute and set the respective funds field.
Constructing the Message
You can construct aBankMsg::Send message within your contract’s execute function. This message requires specifying the recipient address and the amount to send. Here’s an example of how to construct this message:
Usage in a Smart Contract
In your contract, you can add a new variant to your ExecuteMsg enum to handle this bank send functionality. For example:execute function, you can add a case to handle this message: