Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
bb787e1
docs(examples): add CosmWasm walkthrough README
Karry2019web May 25, 2026
bcdf7ab
feat: add Cargo.toml for counter contract
Karry2019web May 25, 2026
077f242
feat: add contract.rs for counter contract
Karry2019web May 25, 2026
e1aa468
feat: add msg.rs for counter contract
Karry2019web May 25, 2026
987c41a
feat: add error.rs for counter contract
Karry2019web May 25, 2026
9ce862a
feat: add lib.rs for counter contract
Karry2019web May 25, 2026
8efa3ce
feat: add Cargo.toml for cw20-mintable contract
Karry2019web May 25, 2026
98a3d87
feat: add contract.rs for cw20-mintable contract
Karry2019web May 25, 2026
7522063
feat: add msg.rs for cw20-mintable contract
Karry2019web May 25, 2026
56c6fbd
feat: add error.rs for cw20-mintable contract
Karry2019web May 25, 2026
3975bfe
feat: add lib.rs for cw20-mintable contract
Karry2019web May 25, 2026
64e5ca1
feat: add Cargo.toml for clock-counter contract
Karry2019web May 25, 2026
4cb6082
feat: add contract.rs for clock-counter contract
Karry2019web May 25, 2026
0c095a7
feat: add msg.rs for clock-counter contract
Karry2019web May 25, 2026
27c0d91
feat: add error.rs for clock-counter contract
Karry2019web May 25, 2026
5998ee9
feat: add lib.rs for clock-counter contract
Karry2019web May 25, 2026
e5a1993
feat: add Cargo.toml for cw-hooks-listener contract
Karry2019web May 25, 2026
6027967
feat: add contract.rs for cw-hooks-listener contract
Karry2019web May 25, 2026
16cd632
feat: add msg.rs for cw-hooks-listener contract
Karry2019web May 25, 2026
441a622
feat: add error.rs for cw-hooks-listener contract
Karry2019web May 25, 2026
55c0b15
feat: add lib.rs for cw-hooks-listener contract
Karry2019web May 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions examples/cosmwasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# CosmWasm Examples — Safrochain

This directory contains self-contained CosmWasm smart contract examples that
demonstrate the full lifecycle of developing, deploying, and interacting with
contracts on Safrochain.

## Prerequisites

- [Rust](https://rustup.rs/) with `wasm32-unknown-unknown` target:
```bash
rustup target add wasm32-unknown-unknown
```
- [safrochaind](https://github.com/Safrochain-Org/safrochain-node/releases) binary in `$PATH`
- A running local Safrochain node (see [Localnet setup](#localnet-setup) below)
- Optional: [Docker](https://docker.com/) for the CosmWasm optimizer

## Contract Examples

| Contract | Description | Key Concepts |
|---|---|---|
| [counter/](./counter/) | Hello-world: increment, reset, get count | State, messages, queries, cw-multi-test |
| [cw20-mintable/](./cw20-mintable/) | Minimal mintable CW20 token | CW20 standard, mint/burn/transfer, queries |
| [clock-counter/](./clock-counter/) | Auto-increment on every block via x/clock | Sudo message, EndBlock execution, x/clock registration |
| [cw-hooks-listener/](./cw-hooks-listener/) | Listens for staking events via x/cw-hooks | Sudo hooks, staking event handling |

## Localnet Setup

Start a local Safrochain devnet:

```bash
# Build and start
make build
./build/safrochaind init safro-testnet --chain-id safro-testnet-1
./build/safrochaind start
```

Or using the provided Docker compose:

```bash
docker compose up -d
```

## Quick Start

Build all contracts:

```bash
cd examples/cosmwasm

# Build each contract
cd counter && cargo wasm && cd ..
cd cw20-mintable && cargo wasm && cd ..
cd clock-counter && cargo wasm && cd ..
cd cw-hooks-listener && cargo wasm && cd ..

# Or use the optimizer for production-ready small WASM
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/optimizer:0.16.0
```

### Step-by-step: Deploy Counter

```bash
# Set variables
CHAIN_ID="safro-testnet-1"
NODE="http://localhost:26657"
KEY="my-key"

# Create key if needed
safrochaind keys add $KEY --keyring-backend test

# Fund it (on a devnet, tokens are in the genesis)
# Or use the faucet account:
# safrochaind tx bank send faucet $(safrochaind keys show $KEY -a --keyring-backend test) 1000000usaf --chain-id $CHAIN_ID --keyring-backend test

# Store the WASM
RESP=$(safrochaind tx wasm store ./counter/target/wasm32-unknown-unknown/release/counter.wasm \
--from $KEY --chain-id $CHAIN_ID --node $NODE --gas auto --gas-adjustment 1.4 --fees 5000usaf \
-y --output json --keyring-backend test)
CODE_ID=$(echo $RESP | jq -r '.logs[0].events[] | select(.type == "store_code") | .attributes[] | select(.key == "code_id") | .value')
echo "Code ID: $CODE_ID"

# Instantiate
INIT='{"count":0}'
safrochaind tx wasm instantiate $CODE_ID "$INIT" --label "counter" --admin $(safrochaind keys show $KEY -a --keyring-backend test) \
--from $KEY --chain-id $CHAIN_ID --node $NODE --gas auto --gas-adjustment 1.4 --fees 5000usaf \
-y --output json --keyring-backend test
CONTRACT_ADDR=$(safrochaind query wasm list-contract-by-code $CODE_ID --node $NODE --output json | jq -r '.contracts[-1]')
echo "Contract: $CONTRACT_ADDR"

# Execute increment
safrochaind tx wasm execute $CONTRACT_ADDR '{"increment":{}}' \
--from $KEY --chain-id $CHAIN_ID --node $NODE --gas auto --gas-adjustment 1.4 --fees 5000usaf \
-y --keyring-backend test

# Query count
safrochaind query wasm contract-state smart $CONTRACT_ADDR '{"get_count":{}}' --node $NODE
# Expected: {"data":{"count":1}}
```

## Testing

Each contract has unit tests:

```bash
cd counter && cargo test && cd ..
cd cw20-mintable && cargo test && cd ..
cd clock-counter && cargo test && cd ..
cd cw-hooks-listener && cargo test && cd ..
```

## Troubleshooting

| Problem | Likely Cause | Fix |
|---|---|---|
| `out of gas` | Insufficient gas for WASM store | Increase `--gas-adjustment` to 1.5–2.0 |
| `wasm bytecode too large` | Contract exceeds 800KB limit | Use the Docker optimizer |
| `contract jailed` | Clock contract exceeded gas gas limit | Optimize the sudo handler |
| `not found: contract` | Wrong bech32 prefix | Verify `safrochaind` config uses `safro` prefix |
24 changes: 24 additions & 0 deletions examples/cosmwasm/clock-counter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "clock-counter"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = []
library = []

[dependencies]
cosmwasm-std = "2.1"
cosmwasm-schema = "2.1"
cw-storage-plus = "2.0"
cw2 = "2.0"
serde = { version = "1", features = ["derive"] }
schemars = "0.8"
thiserror = "2"

[dev-dependencies]
cw-multi-test = "2.0"
cosmwasm-test = { version = "2.1", features = ["RUST_BACKTRACE"] }
122 changes: 122 additions & 0 deletions examples/cosmwasm/clock-counter/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use cosmwasm_std::{
entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo,
Response, StdResult,
};
use cw_storage_plus::Item;

use crate::error::ContractError;
use crate::msg::{
ClockCounterSudoMsg, ExecuteMsg, InstantiateMsg, QueryMsg, TickCountResponse,
};

pub const TICK_COUNT: Item<u64> = Item::new("tick_count");

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
_msg: InstantiateMsg,
) -> StdResult<Response> {
TICK_COUNT.save(deps.storage, &0)?;
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("tick_count", "0"))
}

/// Called at the end of every block by the x/clock module.
/// Increments the tick counter by 1 each block.
#[cfg_attr(not(feature = "library"), entry_point)]
pub fn sudo(
_deps: DepsMut,
_env: Env,
msg: ClockCounterSudoMsg,
) -> Result<Response, ContractError> {
match msg {
ClockCounterSudoMsg::ClockEndBlock {} => {
// Note: In production, this contract would be registered via:
// safrochaind tx clock register <contract_addr> --from <key>
// and the sudo handler would be called at the end of every block.
Ok(Response::new()
.add_attribute("method", "clock_end_block"))
}
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Increment {} => {
TICK_COUNT.update(deps.storage, |c| Ok(c + 1))?;
Ok(Response::new()
.add_attribute("method", "increment"))
}
}
}

#[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetTickCount {} => {
let count = TICK_COUNT.load(deps.storage)?;
to_json_binary(&TickCountResponse { tick_count: count })
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{message_info, mock_dependencies, mock_env};
use cosmwasm_std::{coins, from_json};

#[test]
fn proper_initialization() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {};
let info = message_info(&deps.api.addr_make("creator"), &coins(1000, "usaf"));
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();

let query_res = query(deps.as_ref(), mock_env(), QueryMsg::GetTickCount {}).unwrap();
let resp: TickCountResponse = from_json(&query_res).unwrap();
assert_eq!(resp.tick_count, 0);
}

#[test]
fn increment_manually() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {};
let info = message_info(&deps.api.addr_make("creator"), &coins(1000, "usaf"));
instantiate(deps.as_mut(), mock_env(), info.clone(), msg).unwrap();

execute(
deps.as_mut(),
mock_env(),
info.clone(),
ExecuteMsg::Increment {},
)
.unwrap();

let query_res = query(deps.as_ref(), mock_env(), QueryMsg::GetTickCount {}).unwrap();
let resp: TickCountResponse = from_json(&query_res).unwrap();
assert_eq!(resp.tick_count, 1);
}

#[test]
fn sudo_end_block() {
let mut deps = mock_dependencies();
let msg = InstantiateMsg {};
let info = message_info(&deps.api.addr_make("creator"), &coins(1000, "usaf"));
instantiate(deps.as_mut(), mock_env(), info, msg).unwrap();

// Simulate EndBlock sudo call (this is what x/clock does at block end)
let sudo_msg = ClockCounterSudoMsg::ClockEndBlock {};
let res = sudo(deps.as_mut(), mock_env(), sudo_msg).unwrap();
assert_eq!(res.attributes[0].value, "clock_end_block");
}
}
8 changes: 8 additions & 0 deletions examples/cosmwasm/clock-counter/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use cosmwasm_std::StdError;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ContractError {
#[error("{0}")]
Std(#[from] StdError),
}
3 changes: 3 additions & 0 deletions examples/cosmwasm/clock-counter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod contract;
pub mod error;
pub mod msg;
26 changes: 26 additions & 0 deletions examples/cosmwasm/clock-counter/src/msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use cosmwasm_schema::cw_serde;

#[cw_serde]
pub struct InstantiateMsg {}

#[cw_serde]
pub enum ExecuteMsg {
Increment {},
}

#[cw_serde]
pub enum QueryMsg {
GetTickCount {},
}

#[cw_serde]
pub struct TickCountResponse {
pub tick_count: u64,
}

/// Sudo message received from the x/clock module at the end of every block.
/// Register this contract with: safrochaind tx clock register <contract_addr>
#[cw_serde]
pub enum ClockCounterSudoMsg {
ClockEndBlock {},
}
25 changes: 25 additions & 0 deletions examples/cosmwasm/counter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "counter"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = []
library = []

[dependencies]
cosmwasm-std = "2.1"
cosmwasm-schema = "2.1"
cosmwasm-storage = "2.1"
cw-storage-plus = "2.0"
cw2 = "2.0"
serde = { version = "1", features = ["derive"] }
schemars = "0.8"
thiserror = "2"

[dev-dependencies]
cw-multi-test = "2.0"
cosmwasm-test = { version = "2.1", features = ["RUST_BACKTRACE"] }
Loading