Skip to content

Commit

Permalink
feat(00-hello-world-counter): implement the classic, counter contract…
Browse files Browse the repository at this point in the history
… for testing
  • Loading branch information
Unique-Divine committed Sep 27, 2024
1 parent 28e952f commit ec3ab9f
Show file tree
Hide file tree
Showing 10 changed files with 331 additions and 1 deletion.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions contracts/00-hello-world-counter/.cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[alias]
wasm = "build --release --lib --target wasm32-unknown-unknown"
wasm-debug = "build --lib --target wasm32-unknown-unknown"
schema = "run --bin schema"

# OLDER:
# wasm = "build --release --target wasm32-unknown-unknown"
# wasm-debug = "build --target wasm32-unknown-unknown"
# schema = "run --example schema"
31 changes: 31 additions & 0 deletions contracts/00-hello-world-counter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "hello-world-counter"
version = "0.1.0"
edition = "2021"
homepage = { workspace = true }
repository = { workspace = true }

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]

[features]
# features.library: Use the library feature to disable all
# instantiate/execute/query exports. This is necessary use this as a dependency
# for another smart contract crate.
library = []

[dependencies]
cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true }
cw-storage-plus = { workspace = true }
schemars = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
nibiru-std = { workspace = true }
cw2 = { workspace = true }
serde_json = { workspace = true }
anyhow = { workspace = true }

[dev-dependencies]
easy-addr = { workspace = true }
4 changes: 4 additions & 0 deletions contracts/00-hello-world-counter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# contracts/00-hello-world-counter

The classic "counter" smart contract. It serves as a hello-world example on
how to implement execute messages and queries in Wasm.
185 changes: 185 additions & 0 deletions contracts/00-hello-world-counter/src/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
use cosmwasm_std::{
to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response,
};
use cw2::set_contract_version;

use crate::{
msg::{ExecuteMsg, InstantiateMsg, QueryMsg},
state::{State, STATE},
};

type ContractError = anyhow::Error;

#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> Result<Response, ContractError> {
set_contract_version(
deps.storage,
format!("nibiru-wasm/contracts/{CONTRACT_NAME}"),
CONTRACT_VERSION,
)?;

STATE.save(
deps.storage,
&State {
count: msg.count,
owner: info.sender.clone(),
},
)?;
Ok(Response::default())
}

#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::Increment {} => {
STATE.update(
deps.storage,
|mut state| -> Result<_, anyhow::Error> {
state.count += 1;
Ok(state)
},
)?;
Ok(Response::default())
}
ExecuteMsg::Reset { count } => {
STATE.update(
deps.storage,
|mut state| -> Result<_, anyhow::Error> {
let owner = state.owner.clone();
if info.sender != owner {
return Err(anyhow::anyhow!(
"Unauthorized: only the owner ({owner}) can use reset",
));
}
state.count = count;
Ok(state)
},
)?;
Ok(Response::default())
}
}
}

#[cfg_attr(not(feature = "library"), cosmwasm_std::entry_point)]
pub fn query(
deps: Deps,
_env: Env,
msg: QueryMsg,
) -> Result<Binary, ContractError> {
match msg {
QueryMsg::Count {} => {
let state = STATE.load(deps.storage)?;
Ok(to_json_binary(&state)?)
}
}
}

pub const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME");
pub const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");

#[cfg(test)]
pub mod tests {

use easy_addr;
use nibiru_std::errors::TestResult;

use crate::{
contract::{execute, query},
msg::{ExecuteMsg, QueryMsg},
state::State,
tutil::{mock_info_for_sender, setup_contract, TEST_OWNER},
};

struct TestCaseExec<'a> {
exec_msg: ExecuteMsg,
sender: &'a str,
err: Option<&'a str>,
start_count: i64,
want_count_after: i64,
}

/// Test that all owner-gated execute calls fail when the tx sender is not
/// the smart contract owner.
#[test]
pub fn test_exec() -> TestResult {
let not_owner = easy_addr::addr!("not-owner");

let test_cases: Vec<TestCaseExec> = vec![
TestCaseExec {
sender: not_owner,
exec_msg: ExecuteMsg::Increment {},
err: None,
start_count: 0,
want_count_after: 1,
},
TestCaseExec {
sender: not_owner,
exec_msg: ExecuteMsg::Increment {},
err: None,
start_count: -70,
want_count_after: -69,
},
TestCaseExec {
sender: TEST_OWNER,
exec_msg: ExecuteMsg::Reset { count: 25 },
err: None,
start_count: std::i64::MAX,
want_count_after: 25,
},
TestCaseExec {
sender: TEST_OWNER,
exec_msg: ExecuteMsg::Reset { count: -25 },
err: None,
start_count: 0,
want_count_after: -25,
},
TestCaseExec {
sender: not_owner,
exec_msg: ExecuteMsg::Reset { count: 25 },
err: Some("Unauthorized: only the owner"),
start_count: 0, // unused
want_count_after: 0, // unused
},
];

for tc in &test_cases {
// instantiate smart contract from the owner
let (mut deps, env, _info) = setup_contract(tc.start_count)?;

// send the exec msg and it should fail.
let info = mock_info_for_sender(tc.sender);
let res =
execute(deps.as_mut(), env.clone(), info, tc.exec_msg.clone());

if let Some(want_err) = tc.err {
let err = res.expect_err("err should be defined");
let is_contained = err.to_string().contains(want_err);
assert!(is_contained, "got error {}", err);
continue;
}

let res = res?;
assert_eq!(res.messages.len(), 0);

let query_req = QueryMsg::Count {};
let binary = query(deps.as_ref(), env, query_req)?;
let query_resp: State = cosmwasm_std::from_json(binary)?;

let state = query_resp;
let got_count_after = state.count;
assert_eq!(got_count_after, tc.want_count_after);
assert_eq!(state.owner.as_str(), TEST_OWNER);
}
Ok(())
}
}
5 changes: 5 additions & 0 deletions contracts/00-hello-world-counter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub mod contract;
pub mod msg;
pub mod state;
#[cfg(test)]
pub mod tutil;
21 changes: 21 additions & 0 deletions contracts/00-hello-world-counter/src/msg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::state;
use cosmwasm_schema::cw_serde;

#[cw_serde]
pub enum ExecuteMsg {
Increment {}, // Increase count by 1
Reset { count: i64 }, // Reset to any i64 value
}

#[cw_serde]
#[derive(cosmwasm_schema::QueryResponses)]
pub enum QueryMsg {
// Count returns the JSON-encoded state
#[returns(state::State)]
Count {},
}

#[cw_serde]
pub struct InstantiateMsg {
pub count: i64,
}
11 changes: 11 additions & 0 deletions contracts/00-hello-world-counter/src/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use cw_storage_plus::Item;

pub const STATE: Item<State> = Item::new("state");

#[cw_serde]
pub struct State {
pub count: i64,
pub owner: Addr,
}
47 changes: 47 additions & 0 deletions contracts/00-hello-world-counter/src/tutil.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! tutil.rs: Test helpers for the contract
#![cfg(not(target_arch = "wasm32"))]

use cosmwasm_std::{Env, MessageInfo, OwnedDeps};

#[cfg(not(target_arch = "wasm32"))]
use cosmwasm_std::testing::{
mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage,
};

use crate::{contract::instantiate, msg::InstantiateMsg};

pub const TEST_OWNER: &str = easy_addr::addr!("owner");

pub fn setup_contract(
count: i64,
) -> anyhow::Result<(
OwnedDeps<MockStorage, MockApi, MockQuerier>,
Env,
MessageInfo,
)> {
let mut deps = mock_dependencies();
let env = mock_env();
let info = mock_info(TEST_OWNER, &[]);
let msg = InstantiateMsg { count };
let res = instantiate(deps.as_mut(), env.clone(), info.clone(), msg)?;
assert_eq!(0, res.messages.len());
Ok((deps, env, info))
}

pub fn setup_contract_defaults() -> anyhow::Result<(
OwnedDeps<MockStorage, MockApi, MockQuerier>,
Env,
MessageInfo,
)> {
setup_contract(0)
}

pub fn mock_info_for_sender(sender: &str) -> MessageInfo {
mock_info(sender, &[])
}

pub fn mock_env_height(height: u64) -> Env {
let mut env = mock_env();
env.block.height = height;
env
}
2 changes: 1 addition & 1 deletion contracts/broker-bank/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "broker-bank"
version = "0.1.0"
edition = "2021"
homepage = { workspace = true }
repository = "https://github.com/NibiruChain/nibiru-wasm"
repository = { workspace = true }

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
Expand Down

0 comments on commit ec3ab9f

Please sign in to comment.