-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(00-hello-world-counter): implement the classic, counter contract…
… for testing
- Loading branch information
1 parent
28e952f
commit ec3ab9f
Showing
10 changed files
with
331 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters