From 80d1498a671c714d1b433d89fd2c4703ad0e0245 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Tue, 14 Nov 2023 07:30:48 -0800 Subject: [PATCH] Initial Commit --- bisenzone-cw-mvp/.cargo/config | 4 + bisenzone-cw-mvp/.github/workflows/Basic.yml | 75 +++++++++ .../.github/workflows/Release.yml | 35 ++++ bisenzone-cw-mvp/.gitignore | 16 ++ bisenzone-cw-mvp/README.md | 85 ++++++++++ bisenzone-cw-mvp/src/bin/schema.rs | 11 ++ bisenzone-cw-mvp/src/contract.rs | 156 ++++++++++++++++++ bisenzone-cw-mvp/src/error.rs | 13 ++ bisenzone-cw-mvp/src/helpers.rs | 47 ++++++ bisenzone-cw-mvp/src/integration_tests.rs | 71 ++++++++ bisenzone-cw-mvp/src/lib.rs | 8 + bisenzone-cw-mvp/src/msg.rs | 26 +++ bisenzone-cw-mvp/src/state.rs | 13 ++ 13 files changed, 560 insertions(+) create mode 100644 bisenzone-cw-mvp/.cargo/config create mode 100644 bisenzone-cw-mvp/.github/workflows/Basic.yml create mode 100644 bisenzone-cw-mvp/.github/workflows/Release.yml create mode 100644 bisenzone-cw-mvp/.gitignore create mode 100644 bisenzone-cw-mvp/README.md create mode 100644 bisenzone-cw-mvp/src/bin/schema.rs create mode 100644 bisenzone-cw-mvp/src/contract.rs create mode 100644 bisenzone-cw-mvp/src/error.rs create mode 100644 bisenzone-cw-mvp/src/helpers.rs create mode 100644 bisenzone-cw-mvp/src/integration_tests.rs create mode 100644 bisenzone-cw-mvp/src/lib.rs create mode 100644 bisenzone-cw-mvp/src/msg.rs create mode 100644 bisenzone-cw-mvp/src/state.rs diff --git a/bisenzone-cw-mvp/.cargo/config b/bisenzone-cw-mvp/.cargo/config new file mode 100644 index 0000000..af5698e --- /dev/null +++ b/bisenzone-cw-mvp/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/bisenzone-cw-mvp/.github/workflows/Basic.yml b/bisenzone-cw-mvp/.github/workflows/Basic.yml new file mode 100644 index 0000000..3890a07 --- /dev/null +++ b/bisenzone-cw-mvp/.github/workflows/Basic.yml @@ -0,0 +1,75 @@ +# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml + +on: [push, pull_request] + +name: Basic + +jobs: + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.60.0 + target: wasm32-unknown-unknown + override: true + + - name: Run unit tests + uses: actions-rs/cargo@v1 + with: + command: unit-test + args: --locked + env: + RUST_BACKTRACE: 1 + + - name: Compile WASM contract + uses: actions-rs/cargo@v1 + with: + command: wasm + args: --locked + env: + RUSTFLAGS: "-C link-arg=-s" + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.60.0 + override: true + components: rustfmt, clippy + + - name: Run cargo fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings + + - name: Generate Schema + uses: actions-rs/cargo@v1 + with: + command: schema + args: --locked + + - name: Schema Changes + # fails if any changes not committed + run: git diff --exit-code schema diff --git a/bisenzone-cw-mvp/.github/workflows/Release.yml b/bisenzone-cw-mvp/.github/workflows/Release.yml new file mode 100644 index 0000000..a9d5979 --- /dev/null +++ b/bisenzone-cw-mvp/.github/workflows/Release.yml @@ -0,0 +1,35 @@ +name: release wasm + +on: + release: + types: [created] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install cargo-run-script + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-run-script + - name: Run cargo optimize + uses: actions-rs/cargo@v1 + with: + command: run-script + args: optimize + - name: Get release ID + id: get_release + uses: bruceadams/get-release@v1.2.3 + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Upload optimized wasm + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ./artifacts/*.wasm + tag: ${{ github.ref }} + overwrite: true + file_glob: true diff --git a/bisenzone-cw-mvp/.gitignore b/bisenzone-cw-mvp/.gitignore new file mode 100644 index 0000000..9095dea --- /dev/null +++ b/bisenzone-cw-mvp/.gitignore @@ -0,0 +1,16 @@ +# Build results +/target +/schema + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/bisenzone-cw-mvp/README.md b/bisenzone-cw-mvp/README.md new file mode 100644 index 0000000..6264428 --- /dev/null +++ b/bisenzone-cw-mvp/README.md @@ -0,0 +1,85 @@ +# CosmWasm Starter Pack + +This is a template to build smart contracts in Rust to run inside a +[Cosmos SDK](https://github.com/cosmos/cosmos-sdk) module on all chains that enable it. +To understand the framework better, please read the overview in the +[cosmwasm repo](https://github.com/CosmWasm/cosmwasm/blob/master/README.md), +and dig into the [cosmwasm docs](https://www.cosmwasm.com). +This assumes you understand the theory and just want to get coding. + +## Creating a new repo from template + +Assuming you have a recent version of Rust and Cargo installed +(via [rustup](https://rustup.rs/)), +then the following should get you a new repo to start a contract: + +Install [cargo-generate](https://github.com/ashleygwilliams/cargo-generate) and cargo-run-script. +Unless you did that before, run this line now: + +```sh +cargo install cargo-generate --features vendored-openssl +cargo install cargo-run-script +``` + +Now, use it to create your new contract. +Go to the folder in which you want to place it and run: + +**Latest** + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME +``` + +For cloning minimal code repo: + +```sh +cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME -d minimal=true +``` + +You will now have a new folder called `PROJECT_NAME` (I hope you changed that to something else) +containing a simple working contract and build system that you can customize. + +## Create a Repo + +After generating, you have a initialized local git repo, but no commits, and no remote. +Go to a server (eg. github) and create a new upstream repo (called `YOUR-GIT-URL` below). +Then run the following: + +```sh +# this is needed to create a valid Cargo.lock file (see below) +cargo check +git branch -M main +git add . +git commit -m 'Initial Commit' +git remote add origin YOUR-GIT-URL +git push -u origin main +``` + +## CI Support + +We have template configurations for both [GitHub Actions](.github/workflows/Basic.yml) +and [Circle CI](.circleci/config.yml) in the generated project, so you can +get up and running with CI right away. + +One note is that the CI runs all `cargo` commands +with `--locked` to ensure it uses the exact same versions as you have locally. This also means +you must have an up-to-date `Cargo.lock` file, which is not auto-generated. +The first time you set up the project (or after adding any dep), you should ensure the +`Cargo.lock` file is updated, so the CI will test properly. This can be done simply by +running `cargo check` or `cargo unit-test`. + +## Using your project + +Once you have your custom repo, you should check out [Developing](./Developing.md) to explain +more on how to run tests and develop code. Or go through the +[online tutorial](https://docs.cosmwasm.com/) to get a better feel +of how to develop. + +[Publishing](./Publishing.md) contains useful information on how to publish your contract +to the world, once you are ready to deploy it on a running blockchain. And +[Importing](./Importing.md) contains information about pulling in other contracts or crates +that have been published. + +Please replace this README file with information about your specific project. You can keep +the `Developing.md` and `Publishing.md` files as useful references, but please set some +proper description in the README. diff --git a/bisenzone-cw-mvp/src/bin/schema.rs b/bisenzone-cw-mvp/src/bin/schema.rs new file mode 100644 index 0000000..55a2a8e --- /dev/null +++ b/bisenzone-cw-mvp/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use cw_mtcs::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/bisenzone-cw-mvp/src/contract.rs b/bisenzone-cw-mvp/src/contract.rs new file mode 100644 index 0000000..2a460b9 --- /dev/null +++ b/bisenzone-cw-mvp/src/contract.rs @@ -0,0 +1,156 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, GetCountResponse, InstantiateMsg, QueryMsg}; +use crate::state::{State, STATE}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw-mtcs"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + let state = State { + count: msg.count, + owner: info.sender.clone(), + }; + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + STATE.save(deps.storage, &state)?; + + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender) + .add_attribute("count", msg.count.to_string())) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::Increment {} => execute::increment(deps), + ExecuteMsg::Reset { count } => execute::reset(deps, info, count), + } +} + +pub mod execute { + use super::*; + + pub fn increment(deps: DepsMut) -> Result { + STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { + state.count += 1; + Ok(state) + })?; + + Ok(Response::new().add_attribute("action", "increment")) + } + + pub fn reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result { + STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { + if info.sender != state.owner { + return Err(ContractError::Unauthorized {}); + } + state.count = count; + Ok(state) + })?; + Ok(Response::new().add_attribute("action", "reset")) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetCount {} => to_json_binary(&query::count(deps)?), + } +} + +pub mod query { + use super::*; + + pub fn count(deps: Deps) -> StdResult { + let state = STATE.load(deps.storage)?; + Ok(GetCountResponse { count: state.count }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{coins, from_json}; + + #[test] + fn proper_initialization() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(1000, "earth")); + + // we can just call .unwrap() to assert this was a success + let res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!(0, res.messages.len()); + + // it worked, let's query the state + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); + let value: GetCountResponse = from_json(&res).unwrap(); + assert_eq!(17, value.count); + } + + #[test] + fn increment() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // beneficiary can release it + let info = mock_info("anyone", &coins(2, "token")); + let msg = ExecuteMsg::Increment {}; + let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // should increase counter by 1 + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); + let value: GetCountResponse = from_json(&res).unwrap(); + assert_eq!(18, value.count); + } + + #[test] + fn reset() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg { count: 17 }; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + // beneficiary can release it + let unauth_info = mock_info("anyone", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 5 }; + let res = execute(deps.as_mut(), mock_env(), unauth_info, msg); + match res { + Err(ContractError::Unauthorized {}) => {} + _ => panic!("Must return unauthorized error"), + } + + // only the original creator can reset the counter + let auth_info = mock_info("creator", &coins(2, "token")); + let msg = ExecuteMsg::Reset { count: 5 }; + let _res = execute(deps.as_mut(), mock_env(), auth_info, msg).unwrap(); + + // should now be 5 + let res = query(deps.as_ref(), mock_env(), QueryMsg::GetCount {}).unwrap(); + let value: GetCountResponse = from_json(&res).unwrap(); + assert_eq!(5, value.count); + } +} diff --git a/bisenzone-cw-mvp/src/error.rs b/bisenzone-cw-mvp/src/error.rs new file mode 100644 index 0000000..4a69d8f --- /dev/null +++ b/bisenzone-cw-mvp/src/error.rs @@ -0,0 +1,13 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, + // Add any other custom errors you like here. + // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. +} diff --git a/bisenzone-cw-mvp/src/helpers.rs b/bisenzone-cw-mvp/src/helpers.rs new file mode 100644 index 0000000..6d988cc --- /dev/null +++ b/bisenzone-cw-mvp/src/helpers.rs @@ -0,0 +1,47 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::{ + to_json_binary, Addr, CosmosMsg, CustomQuery, Querier, QuerierWrapper, StdResult, WasmMsg, + WasmQuery, +}; + +use crate::msg::{ExecuteMsg, GetCountResponse, QueryMsg}; + +/// CwTemplateContract is a wrapper around Addr that provides a lot of helpers +/// for working with this. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CwTemplateContract(pub Addr); + +impl CwTemplateContract { + pub fn addr(&self) -> Addr { + self.0.clone() + } + + pub fn call>(&self, msg: T) -> StdResult { + let msg = to_json_binary(&msg.into())?; + Ok(WasmMsg::Execute { + contract_addr: self.addr().into(), + msg, + funds: vec![], + } + .into()) + } + + /// Get Count + pub fn count(&self, querier: &Q) -> StdResult + where + Q: Querier, + T: Into, + CQ: CustomQuery, + { + let msg = QueryMsg::GetCount {}; + let query = WasmQuery::Smart { + contract_addr: self.addr().into(), + msg: to_json_binary(&msg)?, + } + .into(); + let res: GetCountResponse = QuerierWrapper::::new(querier).query(&query)?; + Ok(res) + } +} diff --git a/bisenzone-cw-mvp/src/integration_tests.rs b/bisenzone-cw-mvp/src/integration_tests.rs new file mode 100644 index 0000000..4c50784 --- /dev/null +++ b/bisenzone-cw-mvp/src/integration_tests.rs @@ -0,0 +1,71 @@ +#[cfg(test)] +mod tests { + use crate::helpers::CwTemplateContract; + use crate::msg::InstantiateMsg; + use cosmwasm_std::{Addr, Coin, Empty, Uint128}; + use cw_multi_test::{App, AppBuilder, Contract, ContractWrapper, Executor}; + + pub fn contract_template() -> Box> { + let contract = ContractWrapper::new( + crate::contract::execute, + crate::contract::instantiate, + crate::contract::query, + ); + Box::new(contract) + } + + const USER: &str = "USER"; + const ADMIN: &str = "ADMIN"; + const NATIVE_DENOM: &str = "denom"; + + fn mock_app() -> App { + AppBuilder::new().build(|router, _, storage| { + router + .bank + .init_balance( + storage, + &Addr::unchecked(USER), + vec![Coin { + denom: NATIVE_DENOM.to_string(), + amount: Uint128::new(1), + }], + ) + .unwrap(); + }) + } + + fn proper_instantiate() -> (App, CwTemplateContract) { + let mut app = mock_app(); + let cw_template_id = app.store_code(contract_template()); + + let msg = InstantiateMsg { count: 1i32 }; + let cw_template_contract_addr = app + .instantiate_contract( + cw_template_id, + Addr::unchecked(ADMIN), + &msg, + &[], + "test", + None, + ) + .unwrap(); + + let cw_template_contract = CwTemplateContract(cw_template_contract_addr); + + (app, cw_template_contract) + } + + mod count { + use super::*; + use crate::msg::ExecuteMsg; + + #[test] + fn count() { + let (mut app, cw_template_contract) = proper_instantiate(); + + let msg = ExecuteMsg::Increment {}; + let cosmos_msg = cw_template_contract.call(msg).unwrap(); + app.execute(Addr::unchecked(USER), cosmos_msg).unwrap(); + } + } +} diff --git a/bisenzone-cw-mvp/src/lib.rs b/bisenzone-cw-mvp/src/lib.rs new file mode 100644 index 0000000..d6185c4 --- /dev/null +++ b/bisenzone-cw-mvp/src/lib.rs @@ -0,0 +1,8 @@ +pub mod contract; +mod error; +pub mod helpers; +pub mod integration_tests; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/bisenzone-cw-mvp/src/msg.rs b/bisenzone-cw-mvp/src/msg.rs new file mode 100644 index 0000000..0edfa32 --- /dev/null +++ b/bisenzone-cw-mvp/src/msg.rs @@ -0,0 +1,26 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +#[cw_serde] +pub struct InstantiateMsg { + pub count: i32, +} + +#[cw_serde] +pub enum ExecuteMsg { + Increment {}, + Reset { count: i32 }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + #[returns(GetCountResponse)] + GetCount {}, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct GetCountResponse { + pub count: i32, +} diff --git a/bisenzone-cw-mvp/src/state.rs b/bisenzone-cw-mvp/src/state.rs new file mode 100644 index 0000000..bad9202 --- /dev/null +++ b/bisenzone-cw-mvp/src/state.rs @@ -0,0 +1,13 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::Addr; +use cw_storage_plus::Item; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct State { + pub count: i32, + pub owner: Addr, +} + +pub const STATE: Item = Item::new("state");