A gentle introduction to smart contract development on Neutron
instantiate()
: a constructor which is called during contract instantiation to provide the initial stateexecute()
: gets called when a user wants to invoke a method on the smart contract, this invokes one of the execute messages defined in the contract under ExecuteMsg
enum which is essentially a list of transactions the contract supportsquery()
: gets called when a user wants to get data out of a smart contract, this invokes one of the query messages defined in the contract under QueryMsg
enum which is a list of queries the contract supportsCargo.toml
: Has contract’s rust dependencies and some configuration parameters.src/contract.rs
: Contract entry points such as instantiate()
, execute()
and query()
are defined here.src/error.rs
: The contract’s custom error messages are defined here.src/lib.rs
: This file defines the list of rust source files that are part of the contract, add the file name here when adding a new one to the contract’s source.src/msg.rs
: Defines all the messages allowed to communicate with the contract.src/state.rs
: Defines what the structure of the contract’s storage will be.src/bin/schema.rs
: Defines how to generate schema (language-agnostic format of contract messages) from the contract.State
handles the state of the database where smart contract data is stored and accessed.
The counter contract has the following basic state, a singleton struct State
containing:
count
, a 32-bit integer with which execute()
messages will interact by increasing or resetting it.owner
, the sender address
of the MsgInstantiateContract
, which will determine if certain execution messages are permitted.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 schemaAddr
refers to a human-readable Neutron address prefixed with neutron, e.g. neutron1retp3n0xl0sucqmg4qdk64h7kfflc032xg8na0
.
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.
Neutron utilizes CosmWasm as the smart contract execution layer. In CosmWasm, 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).
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
has no input parameter and increases the value of count by 1.Reset
takes a 32-bit integer as a parameter and resets the value of count
to the input parameter.QueryMsg
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.
contract.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:
count
is assigned the count from the messageowner
is assigned to the sender of the MsgInstantiateContract
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.
try_increment()
, it acquires a mutable reference to the storage to update the item located at the key 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 acknowledgment of success by returning an Ok
result with the Response
.
The logic for reset is very similar to increment, except this time, it first checks that the message sender is permitted to invoke the reset function (in this case, it must be the contract owner).
query()
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.
Deps
, not DepsMut
(Mut
stands for mutable) as in the execute()
, which implies that queries are for read-only operations and do not make any changes to contract’s storage.
neutrond
CLI: