From 80d1498a671c714d1b433d89fd2c4703ad0e0245 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Tue, 14 Nov 2023 07:30:48 -0800 Subject: [PATCH 01/35] 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"); From 43c97925e25822d85f16b75dfd2c35a682dd2cee Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Tue, 14 Nov 2023 16:16:14 -0800 Subject: [PATCH 02/35] Add scripts --- bisenzone-cw-mvp/scripts/build-contract.sh | 23 ++++++++ bisenzone-cw-mvp/scripts/deploy-contract.sh | 60 +++++++++++++++++++++ bisenzone-cw-mvp/scripts/init-node.sh | 26 +++++++++ bisenzone-cw-mvp/scripts/keygen.sh | 10 ++++ bisenzone-cw-mvp/scripts/run-node.sh | 26 +++++++++ 5 files changed, 145 insertions(+) create mode 100755 bisenzone-cw-mvp/scripts/build-contract.sh create mode 100755 bisenzone-cw-mvp/scripts/deploy-contract.sh create mode 100755 bisenzone-cw-mvp/scripts/init-node.sh create mode 100755 bisenzone-cw-mvp/scripts/keygen.sh create mode 100755 bisenzone-cw-mvp/scripts/run-node.sh diff --git a/bisenzone-cw-mvp/scripts/build-contract.sh b/bisenzone-cw-mvp/scripts/build-contract.sh new file mode 100755 index 0000000..eec2412 --- /dev/null +++ b/bisenzone-cw-mvp/scripts/build-contract.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Build and optimize the contract to output a WASM binary that can be deployed to a CosmWasm chain. + +set -euo pipefail + +if ! [ -f "Cargo.toml" ]; then + echo "❌ Error: Cannot find 'Cargo.toml' in current directory. Make sure this command is run from the contract's source directory" + exit 1 +fi + +echo "👷 Building and optimizing the contract..." +echo "===========================================" + +RUSTFLAGS='-C link-arg=-s' cargo 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/rust-optimizer:0.15.0 + +echo "" +echo "🎉 Contract build and optimization completed successfully!" diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh new file mode 100755 index 0000000..ab8db9e --- /dev/null +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Deploy the specified contract's `WASM_BIN` to the chain specified by `CHAIN_ID` using the `USER_ADDR` account. + +set -eo pipefail + +usage() { + echo "Usage: $0 WASM_BIN [COUNT]" + echo "Example: $0 artifacts/cw_mtcs.wasm" + exit 1 +} + +if [ -z "$1" ]; then + echo "❌ Error: Missing WASM_BIN parameter. Please check if all parameters were specified." + usage +fi + +if [ "$#" -gt 9 ]; then + echo "❌ Error: Incorrect number of parameters." + usage +fi + +USER_ADDR=${USER_ADDR:-$(wasmd keys show -a admin)} +WASM_BIN="$1" +CHAIN_ID=${CHAIN_ID:-testing} +LABEL=${LABEL:-bisenzone-mvp} +COUNT=${COUNT:-0} + +TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.0025ucosm --gas auto --gas-adjustment 1.3" + +echo "🚀 Deploying WASM contract '${WASM_BIN}' on chain '${CHAIN_ID}' using account '${USER_ADDR}'..." +echo "====================================================================" + +RES=$(wasmd tx wasm store "$WASM_BIN" --from "$USER_ADDR" $TXFLAG -y --output json) +sleep 8 +TX_HASH=$(echo $RES | jq -r '.["txhash"]') +RES=$(wasmd query tx "$TX_HASH" --output json) +CODE_ID=$(echo $RES | jq -r '.logs[0].events[1].attributes[1].value') + +echo "" +echo "🚀 Instantiating contract with the following parameters:" +echo "--------------------------------------------------------" +echo "Label: ${LABEL}" +echo "Count: ${COUNT}" +echo "--------------------------------------------------------" + +wasmd tx wasm instantiate "$CODE_ID" "{\"count\":${COUNT}}" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null + +echo "" +echo "🕐 Waiting for contract to be queryable..." +sleep 5 + +CONTRACT=$(wasmd query wasm list-contract-by-code "$CODE_ID" --output json | jq -r '.contracts[0]') +echo "🚀 Successfully deployed and instantiated contract!" +echo "🔗 Chain ID: ${CHAIN_ID}" +echo "🆔 Code ID: ${CODE_ID}" +echo "📌 Contract Address: ${CONTRACT}" +echo "🔑 Contract Key: ${KEY}" +echo "🔖 Contract Label: ${LABEL}" +echo "🏷️ Count: ${COUNT}" diff --git a/bisenzone-cw-mvp/scripts/init-node.sh b/bisenzone-cw-mvp/scripts/init-node.sh new file mode 100755 index 0000000..445b2fd --- /dev/null +++ b/bisenzone-cw-mvp/scripts/init-node.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Initialize a wasmd node that can host the MVP CosmWasm smart contract. +# Also creates a validator account and adds default genesis accounts with sufficient tokens for testing (stake and fees) + +set -euo pipefail + +ADMIN=${ADMIN:-$(wasmd keys show -a admin)} +ALICE=${ALICE:-$(wasmd keys show -a alice)} +BOB=${BOB:-$(wasmd keys show -a bob)} +CHARLIE=${CHARLIE:-$(wasmd keys show -a charlie)} + +echo "Remove old docker volume (if it exists)..." +docker volume rm -f wasmd_data + + +echo "" +echo "Setup wasmd (with validator and default genesis accounts)..." +docker run --rm -it \ + --mount type=volume,source=wasmd_data,target=/root \ + --name wasmd \ + cosmwasm/wasmd:v0.44.0 \ + /bin/sh -c "sed -i 's/1000000000/12000000000000/g' /opt/setup_wasmd.sh; + /opt/setup_wasmd.sh "$ADMIN" "$ALICE" "$BOB" "$CHARLIE";" \ + + diff --git a/bisenzone-cw-mvp/scripts/keygen.sh b/bisenzone-cw-mvp/scripts/keygen.sh new file mode 100755 index 0000000..00a7789 --- /dev/null +++ b/bisenzone-cw-mvp/scripts/keygen.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Generate keys for testing. + +set -euo pipefail + +wasmd keys add admin +wasmd keys add alice +wasmd keys add bob +wasmd keys add charlie diff --git a/bisenzone-cw-mvp/scripts/run-node.sh b/bisenzone-cw-mvp/scripts/run-node.sh new file mode 100755 index 0000000..61c62bf --- /dev/null +++ b/bisenzone-cw-mvp/scripts/run-node.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Run a previously initialized wasmd node. + +set -uo pipefail + +docker volume inspect wasmd_data >/dev/null 2>&1 +RESULT=$? +if [ $RESULT -eq 1 ]; then + echo "wasmd isn't initialized - run 'init-node.sh' first" + exit 1 +fi + +echo "Starting wasmd node" + +docker run --rm -it -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 9090:9090 \ + --mount type=volume,source=wasmd_data,target=/root \ + --name wasmd \ + cosmwasm/wasmd:v0.44.0 \ + /bin/sh -c "sed -i 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/g' /root/.wasmd/config/app.toml; + sed -i 's/enable = false/enable = true/g' /root/.wasmd/config/app.toml; + sed -i 's/rpc-max-body-bytes = 1000000$/rpc-max-body-bytes = 1000000000/g' /root/.wasmd/config/app.toml; + sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \[\"*\"\]/g' /root/.wasmd/config/config.toml; + sed -i 's/max_body_bytes = 1000000$/max_body_bytes = 1000000000/g' /root/.wasmd/config/config.toml; + sed -i 's/max_tx_bytes = 1048576$/max_tx_bytes = 104857600/g' /root/.wasmd/config/config.toml; + /opt/run_wasmd.sh" From 855c955ffe6d0c7ed68cf4d078eace8b22f85eee Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Tue, 14 Nov 2023 16:16:58 -0800 Subject: [PATCH 03/35] Add arrtifacts/ to .gitignore --- bisenzone-cw-mvp/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/bisenzone-cw-mvp/.gitignore b/bisenzone-cw-mvp/.gitignore index 9095dea..c7ca55f 100644 --- a/bisenzone-cw-mvp/.gitignore +++ b/bisenzone-cw-mvp/.gitignore @@ -1,4 +1,5 @@ # Build results +/artifacts /target /schema From 4a916357a66f850a8303be8c1d6282a0ec47a048 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 00:54:43 -0800 Subject: [PATCH 04/35] Implement core logic --- bisenzone-cw-mvp/src/contract.rs | 100 ++++++++++++++++++++++++------- bisenzone-cw-mvp/src/error.rs | 10 +++- bisenzone-cw-mvp/src/lib.rs | 10 +++- bisenzone-cw-mvp/src/msg.rs | 26 +++++--- bisenzone-cw-mvp/src/state.rs | 7 ++- 5 files changed, 116 insertions(+), 37 deletions(-) diff --git a/bisenzone-cw-mvp/src/contract.rs b/bisenzone-cw-mvp/src/contract.rs index 2a460b9..9109826 100644 --- a/bisenzone-cw-mvp/src/contract.rs +++ b/bisenzone-cw-mvp/src/contract.rs @@ -1,10 +1,11 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; use cw2::set_contract_version; +use itertools::Itertools; use crate::error::ContractError; -use crate::msg::{ExecuteMsg, GetCountResponse, InstantiateMsg, QueryMsg}; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{State, STATE}; // version info for migration info @@ -16,10 +17,10 @@ pub fn instantiate( deps: DepsMut, _env: Env, info: MessageInfo, - msg: InstantiateMsg, + _msg: InstantiateMsg, ) -> Result { let state = State { - count: msg.count, + utilization: Default::default(), owner: info.sender.clone(), }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; @@ -27,8 +28,7 @@ pub fn instantiate( Ok(Response::new() .add_attribute("method", "instantiate") - .add_attribute("owner", info.sender) - .add_attribute("count", msg.count.to_string())) + .add_attribute("owner", info.sender)) } #[cfg_attr(not(feature = "library"), entry_point)] @@ -39,48 +39,106 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::Increment {} => execute::increment(deps), - ExecuteMsg::Reset { count } => execute::reset(deps, info, count), + ExecuteMsg::UploadObligation { + creditor, + amount, + memo, + } => execute::upload_obligation(deps, info, creditor, amount, memo), + ExecuteMsg::ApplyCycle { path, amount } => execute::apply_cycle(deps, path, amount), } } pub mod execute { use super::*; - pub fn increment(deps: DepsMut) -> Result { + pub fn upload_obligation( + deps: DepsMut, + info: MessageInfo, + creditor: Addr, + amount: u64, + memo: String, + ) -> Result { STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { - state.count += 1; + // Uncomment if we want to only allow ourselves to add obligations + // if info.sender != state.owner { + // return Err(ContractError::Unauthorized); + // } + + *state + .utilization + .get_mut(&creditor) + .unwrap() + .get_mut(&info.sender) + .unwrap() += amount; Ok(state) })?; - Ok(Response::new().add_attribute("action", "increment")) + Ok(Response::new() + .add_attribute("action", "upload_obligation") + .add_attribute("memo", memo)) } - pub fn reset(deps: DepsMut, info: MessageInfo, count: i32) -> Result { + pub fn apply_cycle( + deps: DepsMut, + path: Vec, + amount: u64, + ) -> Result { + let mut volume_cleared = 0; + STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { - if info.sender != state.owner { - return Err(ContractError::Unauthorized {}); + validate_cycle(&path, amount, &state)?; + + for (from, to) in path.into_iter().tuples() { + *state + .utilization + .get_mut(&to) + .unwrap() + .get_mut(&from) + .unwrap() -= amount; + volume_cleared += amount; } - state.count = count; + Ok(state) })?; - Ok(Response::new().add_attribute("action", "reset")) + Ok(Response::new() + .add_attribute("action", "apply_cycle") + .add_attribute("volume_cleared", format!("{}", volume_cleared))) + } + + fn validate_cycle(path: &[Addr], amount: u64, state: &State) -> Result<(), ContractError> { + if path.first() != path.last() { + return Err(ContractError::PathNotCycle); + } + + for (from, to) in path.iter().tuples() { + if amount > state.utilization[to][from] { + return Err(ContractError::ClearingTooMuch); + } + } + + Ok(()) } } #[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)?), + QueryMsg::GetObligations { creditor } => { + to_json_binary(&query::get_obligations(deps, creditor)?) + } } } pub mod query { use super::*; - pub fn count(deps: Deps) -> StdResult { + use crate::msg::GetObligationsResponse; + + pub fn get_obligations(deps: Deps, creditor: Addr) -> StdResult { let state = STATE.load(deps.storage)?; - Ok(GetCountResponse { count: state.count }) + Ok(GetObligationsResponse { + obligations: state.utilization[&creditor].clone(), + }) } } diff --git a/bisenzone-cw-mvp/src/error.rs b/bisenzone-cw-mvp/src/error.rs index 4a69d8f..6989d01 100644 --- a/bisenzone-cw-mvp/src/error.rs +++ b/bisenzone-cw-mvp/src/error.rs @@ -7,7 +7,11 @@ pub enum ContractError { 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. + Unauthorized, + + #[error("Specified path does not form a cycle")] + PathNotCycle, + + #[error("Amount is greater than utilization")] + ClearingTooMuch, } diff --git a/bisenzone-cw-mvp/src/lib.rs b/bisenzone-cw-mvp/src/lib.rs index d6185c4..e3f498e 100644 --- a/bisenzone-cw-mvp/src/lib.rs +++ b/bisenzone-cw-mvp/src/lib.rs @@ -1,6 +1,14 @@ +#![deny( + warnings, + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications +)] +#![forbid(unsafe_code)] + pub mod contract; mod error; -pub mod helpers; pub mod integration_tests; pub mod msg; pub mod state; diff --git a/bisenzone-cw-mvp/src/msg.rs b/bisenzone-cw-mvp/src/msg.rs index 0edfa32..261f5b7 100644 --- a/bisenzone-cw-mvp/src/msg.rs +++ b/bisenzone-cw-mvp/src/msg.rs @@ -1,26 +1,34 @@ +use std::collections::HashMap; + use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::Addr; #[cw_serde] -pub struct InstantiateMsg { - pub count: i32, -} +pub struct InstantiateMsg; #[cw_serde] pub enum ExecuteMsg { - Increment {}, - Reset { count: i32 }, + UploadObligation { + creditor: Addr, + amount: u64, + memo: String, + }, + ApplyCycle { + path: Vec, + amount: u64, + }, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { // GetCount returns the current count as a json-encoded number - #[returns(GetCountResponse)] - GetCount {}, + #[returns(GetObligationsResponse)] + GetObligations { creditor: Addr }, } // We define a custom struct for each query response #[cw_serde] -pub struct GetCountResponse { - pub count: i32, +pub struct GetObligationsResponse { + pub obligations: HashMap, } diff --git a/bisenzone-cw-mvp/src/state.rs b/bisenzone-cw-mvp/src/state.rs index bad9202..2aede68 100644 --- a/bisenzone-cw-mvp/src/state.rs +++ b/bisenzone-cw-mvp/src/state.rs @@ -1,12 +1,13 @@ -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use cosmwasm_std::Addr; use cw_storage_plus::Item; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct State { - pub count: i32, + pub utilization: HashMap>, pub owner: Addr, } From c58ec93f08e9e06ea15440a0e5e9062c7edee397 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 00:55:14 -0800 Subject: [PATCH 05/35] Update scripts --- bisenzone-cw-mvp/scripts/deploy-contract.sh | 4 +- bisenzone-cw-mvp/src/helpers.rs | 47 --------------------- 2 files changed, 1 insertion(+), 50 deletions(-) delete mode 100644 bisenzone-cw-mvp/src/helpers.rs diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index ab8db9e..d8bfdab 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -41,10 +41,9 @@ echo "" echo "🚀 Instantiating contract with the following parameters:" echo "--------------------------------------------------------" echo "Label: ${LABEL}" -echo "Count: ${COUNT}" echo "--------------------------------------------------------" -wasmd tx wasm instantiate "$CODE_ID" "{\"count\":${COUNT}}" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null +wasmd tx wasm instantiate "$CODE_ID" "null" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null echo "" echo "🕐 Waiting for contract to be queryable..." @@ -57,4 +56,3 @@ echo "🆔 Code ID: ${CODE_ID}" echo "📌 Contract Address: ${CONTRACT}" echo "🔑 Contract Key: ${KEY}" echo "🔖 Contract Label: ${LABEL}" -echo "🏷️ Count: ${COUNT}" diff --git a/bisenzone-cw-mvp/src/helpers.rs b/bisenzone-cw-mvp/src/helpers.rs deleted file mode 100644 index 6d988cc..0000000 --- a/bisenzone-cw-mvp/src/helpers.rs +++ /dev/null @@ -1,47 +0,0 @@ -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) - } -} From 4bb6dfbeff3828b930c92dfcb311f0eb51933221 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 02:01:39 -0800 Subject: [PATCH 06/35] Use entry API for HashMap --- bisenzone-cw-mvp/src/contract.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/bisenzone-cw-mvp/src/contract.rs b/bisenzone-cw-mvp/src/contract.rs index 9109826..77ad58f 100644 --- a/bisenzone-cw-mvp/src/contract.rs +++ b/bisenzone-cw-mvp/src/contract.rs @@ -66,10 +66,10 @@ pub mod execute { *state .utilization - .get_mut(&creditor) - .unwrap() - .get_mut(&info.sender) - .unwrap() += amount; + .entry(creditor) + .or_default() + .entry(info.sender) + .or_default() += amount; Ok(state) })?; @@ -131,13 +131,18 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { pub mod query { use super::*; + use std::collections::HashMap; use crate::msg::GetObligationsResponse; pub fn get_obligations(deps: Deps, creditor: Addr) -> StdResult { let state = STATE.load(deps.storage)?; Ok(GetObligationsResponse { - obligations: state.utilization[&creditor].clone(), + obligations: state + .utilization + .get(&creditor) + .unwrap_or(&HashMap::new()) + .clone(), }) } } From 4b343ea6ffa15fd1142422a01b4eb66e9fbfa70d Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 02:01:53 -0800 Subject: [PATCH 07/35] Cleanup deploy script --- bisenzone-cw-mvp/scripts/deploy-contract.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index d8bfdab..b00e9ea 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -32,8 +32,10 @@ echo "🚀 Deploying WASM contract '${WASM_BIN}' on chain '${CHAIN_ID}' using ac echo "====================================================================" RES=$(wasmd tx wasm store "$WASM_BIN" --from "$USER_ADDR" $TXFLAG -y --output json) -sleep 8 TX_HASH=$(echo $RES | jq -r '.["txhash"]') + +sleep 8 + RES=$(wasmd query tx "$TX_HASH" --output json) CODE_ID=$(echo $RES | jq -r '.logs[0].events[1].attributes[1].value') @@ -49,7 +51,9 @@ echo "" echo "🕐 Waiting for contract to be queryable..." sleep 5 -CONTRACT=$(wasmd query wasm list-contract-by-code "$CODE_ID" --output json | jq -r '.contracts[0]') +RES=$(wasmd query wasm list-contract-by-code "$CODE_ID" --output json) +CONTRACT=$(echo $RES | | jq -r '.contracts[0]') + echo "🚀 Successfully deployed and instantiated contract!" echo "🔗 Chain ID: ${CHAIN_ID}" echo "🆔 Code ID: ${CODE_ID}" From cdb09737a22d5678fe1fe32c80985e2c80cce2a6 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 07:16:42 -0800 Subject: [PATCH 08/35] Use Map to store utilization --- bisenzone-cw-mvp/src/contract.rs | 109 ++++++++++++++++++------------- bisenzone-cw-mvp/src/msg.rs | 16 ++--- bisenzone-cw-mvp/src/state.rs | 15 ++--- 3 files changed, 76 insertions(+), 64 deletions(-) diff --git a/bisenzone-cw-mvp/src/contract.rs b/bisenzone-cw-mvp/src/contract.rs index 77ad58f..f22cc2f 100644 --- a/bisenzone-cw-mvp/src/contract.rs +++ b/bisenzone-cw-mvp/src/contract.rs @@ -2,11 +2,10 @@ use cosmwasm_std::{ entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, }; use cw2::set_contract_version; -use itertools::Itertools; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{State, STATE}; +use crate::state::{State, STATE, UTILIZATION}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw-mtcs"; @@ -20,8 +19,7 @@ pub fn instantiate( _msg: InstantiateMsg, ) -> Result { let state = State { - utilization: Default::default(), - owner: info.sender.clone(), + owner: info.sender.to_string(), }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; STATE.save(deps.storage, &state)?; @@ -49,29 +47,32 @@ pub fn execute( } pub mod execute { + use cosmwasm_std::Uint128; + use super::*; pub fn upload_obligation( deps: DepsMut, info: MessageInfo, - creditor: Addr, - amount: u64, + creditor: String, + amount: Uint128, memo: String, ) -> Result { - STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { - // Uncomment if we want to only allow ourselves to add obligations - // if info.sender != state.owner { - // return Err(ContractError::Unauthorized); - // } + let creditor = deps.api.addr_validate(&creditor)?; - *state - .utilization - .entry(creditor) - .or_default() - .entry(info.sender) - .or_default() += amount; - Ok(state) - })?; + UTILIZATION.update( + deps.storage, + (&creditor, &info.sender), + |utilization| -> Result<_, ContractError> { + // Uncomment if we want to only allow ourselves to add obligations + // if info.sender != state.owner { + // return Err(ContractError::Unauthorized); + // } + + let utilization = utilization.unwrap_or_default() + amount; + Ok(utilization) + }, + )?; Ok(Response::new() .add_attribute("action", "upload_obligation") @@ -80,38 +81,46 @@ pub mod execute { pub fn apply_cycle( deps: DepsMut, - path: Vec, - amount: u64, + path: Vec, + amount: Uint128, ) -> Result { - let mut volume_cleared = 0; + let mut volume_cleared = Uint128::zero(); - STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { - validate_cycle(&path, amount, &state)?; + let path = path + .into_iter() + .map(|addr| deps.api.addr_validate(&addr)) + .collect::>>()?; - for (from, to) in path.into_iter().tuples() { - *state - .utilization - .get_mut(&to) - .unwrap() - .get_mut(&from) - .unwrap() -= amount; - volume_cleared += amount; - } + validate_cycle(&path, amount, &deps)?; - Ok(state) - })?; + for from_to in path.windows(2) { + let (from, to) = (&from_to[0], &from_to[1]); + + UTILIZATION.update( + deps.storage, + (&to, &from), + |utilization| -> Result<_, ContractError> { + let utilization = utilization.unwrap_or_default() - amount; + volume_cleared += amount; + + Ok(utilization) + }, + )?; + } Ok(Response::new() .add_attribute("action", "apply_cycle") .add_attribute("volume_cleared", format!("{}", volume_cleared))) } - fn validate_cycle(path: &[Addr], amount: u64, state: &State) -> Result<(), ContractError> { + fn validate_cycle(path: &[Addr], amount: Uint128, deps: &DepsMut) -> Result<(), ContractError> { if path.first() != path.last() { return Err(ContractError::PathNotCycle); } - for (from, to) in path.iter().tuples() { - if amount > state.utilization[to][from] { + for from_to in path.windows(2) { + let (from, to) = (&from_to[0], &from_to[1]); + + if amount > UTILIZATION.load(deps.storage, (to, from))? { return Err(ContractError::ClearingTooMuch); } } @@ -131,18 +140,26 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { pub mod query { use super::*; - use std::collections::HashMap; + use cosmwasm_std::Order; use crate::msg::GetObligationsResponse; + use crate::state::UTILIZATION; - pub fn get_obligations(deps: Deps, creditor: Addr) -> StdResult { - let state = STATE.load(deps.storage)?; + pub fn get_obligations(deps: Deps, creditor: String) -> StdResult { + let creditor = deps.api.addr_validate(&creditor)?; + + let keys = UTILIZATION + .keys(deps.storage, None, None, Order::Ascending) + .collect::>>()? + .into_iter() + .filter(|(from, _)| from == creditor); Ok(GetObligationsResponse { - obligations: state - .utilization - .get(&creditor) - .unwrap_or(&HashMap::new()) - .clone(), + obligations: keys + .map(|(from, to)| { + let utilization = UTILIZATION.load(deps.storage, (&from, &to)).unwrap(); + (to.to_string(), utilization) + }) + .collect(), }) } } diff --git a/bisenzone-cw-mvp/src/msg.rs b/bisenzone-cw-mvp/src/msg.rs index 261f5b7..c02a3fa 100644 --- a/bisenzone-cw-mvp/src/msg.rs +++ b/bisenzone-cw-mvp/src/msg.rs @@ -1,7 +1,5 @@ -use std::collections::HashMap; - use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; +use cosmwasm_std::Uint128; #[cw_serde] pub struct InstantiateMsg; @@ -9,13 +7,13 @@ pub struct InstantiateMsg; #[cw_serde] pub enum ExecuteMsg { UploadObligation { - creditor: Addr, - amount: u64, + creditor: String, + amount: Uint128, memo: String, }, ApplyCycle { - path: Vec, - amount: u64, + path: Vec, + amount: Uint128, }, } @@ -24,11 +22,11 @@ pub enum ExecuteMsg { pub enum QueryMsg { // GetCount returns the current count as a json-encoded number #[returns(GetObligationsResponse)] - GetObligations { creditor: Addr }, + GetObligations { creditor: String }, } // We define a custom struct for each query response #[cw_serde] pub struct GetObligationsResponse { - pub obligations: HashMap, + pub obligations: Vec<(String, Uint128)>, } diff --git a/bisenzone-cw-mvp/src/state.rs b/bisenzone-cw-mvp/src/state.rs index 2aede68..752d93b 100644 --- a/bisenzone-cw-mvp/src/state.rs +++ b/bisenzone-cw-mvp/src/state.rs @@ -1,14 +1,11 @@ -use std::collections::HashMap; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Addr, Uint128}; +use cw_storage_plus::{Item, Map}; -use cosmwasm_std::Addr; -use cw_storage_plus::Item; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[cw_serde] pub struct State { - pub utilization: HashMap>, - pub owner: Addr, + pub owner: String, } pub const STATE: Item = Item::new("state"); +pub const UTILIZATION: Map<(&Addr, &Addr), Uint128> = Map::new("utilization"); From 931334aa46febbb8cbb22a2d18b840479e70e120 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 07:16:57 -0800 Subject: [PATCH 09/35] Fix deploy script --- bisenzone-cw-mvp/scripts/deploy-contract.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index b00e9ea..6215ab9 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -52,7 +52,7 @@ echo "🕐 Waiting for contract to be queryable..." sleep 5 RES=$(wasmd query wasm list-contract-by-code "$CODE_ID" --output json) -CONTRACT=$(echo $RES | | jq -r '.contracts[0]') +CONTRACT=$(echo $RES | jq -r '.contracts[0]') echo "🚀 Successfully deployed and instantiated contract!" echo "🔗 Chain ID: ${CHAIN_ID}" From f331b99d5c927af3af778d797bfdae3d2e0d7666 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 12:48:28 -0800 Subject: [PATCH 10/35] Add CW20 karma mint/balance --- bisenzone-cw-mvp/src/contract.rs | 42 ++++++++++++++++++++++++++++---- bisenzone-cw-mvp/src/error.rs | 10 ++++++++ bisenzone-cw-mvp/src/msg.rs | 4 +++ 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/bisenzone-cw-mvp/src/contract.rs b/bisenzone-cw-mvp/src/contract.rs index f22cc2f..98f718b 100644 --- a/bisenzone-cw-mvp/src/contract.rs +++ b/bisenzone-cw-mvp/src/contract.rs @@ -1,7 +1,12 @@ use cosmwasm_std::{ - entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, + StdResult, Uint128, }; use cw2::set_contract_version; +use cw20_base::{ + contract::{execute_mint, query_balance}, + state::{MinterData, TokenInfo, TOKEN_INFO}, +}; use crate::error::ContractError; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; @@ -14,7 +19,7 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, _msg: InstantiateMsg, ) -> Result { @@ -24,6 +29,20 @@ pub fn instantiate( set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; STATE.save(deps.storage, &state)?; + // store token info using cw20-base format + let data = TokenInfo { + name: "liquidity savings".to_string(), + symbol: "!$".to_string(), + decimals: 0, + total_supply: Uint128::zero(), + // set self as minter, so we can properly execute mint and burn + mint: Some(MinterData { + minter: env.contract.address, + cap: None, + }), + }; + TOKEN_INFO.save(deps.storage, &data)?; + Ok(Response::new() .add_attribute("method", "instantiate") .add_attribute("owner", info.sender)) @@ -32,7 +51,7 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { @@ -42,7 +61,9 @@ pub fn execute( amount, memo, } => execute::upload_obligation(deps, info, creditor, amount, memo), - ExecuteMsg::ApplyCycle { path, amount } => execute::apply_cycle(deps, path, amount), + ExecuteMsg::ApplyCycle { path, amount } => { + execute::apply_cycle(deps, env, info, path, amount) + } } } @@ -81,6 +102,8 @@ pub mod execute { pub fn apply_cycle( deps: DepsMut, + env: Env, + info: MessageInfo, path: Vec, amount: Uint128, ) -> Result { @@ -98,7 +121,7 @@ pub mod execute { UTILIZATION.update( deps.storage, - (&to, &from), + (to, from), |utilization| -> Result<_, ContractError> { let utilization = utilization.unwrap_or_default() - amount; volume_cleared += amount; @@ -107,6 +130,14 @@ pub mod execute { }, )?; } + + // call into cw20-base to mint the token, call as self as no one else is allowed + let sub_info = MessageInfo { + sender: env.contract.address.clone(), + funds: vec![], + }; + execute_mint(deps, env, sub_info, info.sender.to_string(), volume_cleared)?; + Ok(Response::new() .add_attribute("action", "apply_cycle") .add_attribute("volume_cleared", format!("{}", volume_cleared))) @@ -135,6 +166,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { QueryMsg::GetObligations { creditor } => { to_json_binary(&query::get_obligations(deps, creditor)?) } + QueryMsg::Balance { address } => to_json_binary(&query_balance(deps, address)?), } } diff --git a/bisenzone-cw-mvp/src/error.rs b/bisenzone-cw-mvp/src/error.rs index 6989d01..ce3c93c 100644 --- a/bisenzone-cw-mvp/src/error.rs +++ b/bisenzone-cw-mvp/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::StdError; +use cw20_base::ContractError as Cw20ContractError; use thiserror::Error; #[derive(Error, Debug)] @@ -14,4 +15,13 @@ pub enum ContractError { #[error("Amount is greater than utilization")] ClearingTooMuch, + + #[error("Cw20 error: {0}")] + Cw20(Cw20ContractError), +} + +impl From for ContractError { + fn from(e: Cw20ContractError) -> Self { + Self::Cw20(e) + } } diff --git a/bisenzone-cw-mvp/src/msg.rs b/bisenzone-cw-mvp/src/msg.rs index c02a3fa..54c8b47 100644 --- a/bisenzone-cw-mvp/src/msg.rs +++ b/bisenzone-cw-mvp/src/msg.rs @@ -1,5 +1,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::Uint128; +#[allow(unused)] +use cw20::BalanceResponse; #[cw_serde] pub struct InstantiateMsg; @@ -23,6 +25,8 @@ pub enum QueryMsg { // GetCount returns the current count as a json-encoded number #[returns(GetObligationsResponse)] GetObligations { creditor: String }, + #[returns(BalanceResponse)] + Balance { address: String }, } // We define a custom struct for each query response From 4b8d055c7728d763fc9ac686c44de622e355e46a Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 14:35:54 -0800 Subject: [PATCH 11/35] Fix tests --- bisenzone-cw-mvp/src/contract.rs | 144 +++++++++++++++------- bisenzone-cw-mvp/src/integration_tests.rs | 71 ----------- bisenzone-cw-mvp/src/lib.rs | 1 - 3 files changed, 99 insertions(+), 117 deletions(-) delete mode 100644 bisenzone-cw-mvp/src/integration_tests.rs diff --git a/bisenzone-cw-mvp/src/contract.rs b/bisenzone-cw-mvp/src/contract.rs index 98f718b..a0027df 100644 --- a/bisenzone-cw-mvp/src/contract.rs +++ b/bisenzone-cw-mvp/src/contract.rs @@ -199,70 +199,124 @@ pub mod query { #[cfg(test)] mod tests { use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{coins, from_json}; + use cosmwasm_std::testing::{ + mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, + }; + use cosmwasm_std::{coins, from_json, OwnedDeps}; + use cw20::BalanceResponse; + + use crate::msg::GetObligationsResponse; + + const ALICE_ADDRESS: &str = "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv"; + const BOB_ADDRESS: &str = "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw"; + const CHARLIE_ADDRESS: &str = "wasm12r9t5wmre89rwakr0e5nyhfmaf4kdleyltsm9f"; #[test] - fn proper_initialization() { + fn test_initialization() { let mut deps = mock_dependencies(); - let msg = InstantiateMsg { count: 17 }; + let msg = InstantiateMsg; 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() { + fn test_upload_obligation() { let mut deps = mock_dependencies(); - let msg = InstantiateMsg { count: 17 }; + let msg = InstantiateMsg; 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 {}; + create_obligation(&mut deps, ALICE_ADDRESS, BOB_ADDRESS, 100, "alice -> bob"); + + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetObligations { + creditor: BOB_ADDRESS.to_string(), + }, + ) + .unwrap(); + let value: GetObligationsResponse = from_json(&res).unwrap(); + assert_eq!(&100u32.into(), value.obligations[0].1); + } + + fn create_obligation( + deps: &mut OwnedDeps, + debtor: &str, + creditor: &str, + amount: u32, + memo: &str, + ) { + let info = mock_info(debtor, &coins(2, "token")); + let msg = ExecuteMsg::UploadObligation { + creditor: creditor.to_string(), + amount: amount.into(), + memo: memo.to_string(), + }; + let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + } + + #[test] + fn test_apply_cycle() { + let mut deps = mock_dependencies(); + + let msg = InstantiateMsg; + let info = mock_info("creator", &coins(2, "token")); + let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); + + create_obligation(&mut deps, ALICE_ADDRESS, BOB_ADDRESS, 100, "alice -> bob"); + create_obligation( + &mut deps, + BOB_ADDRESS, + CHARLIE_ADDRESS, + 80, + "bob -> charlie", + ); + create_obligation( + &mut deps, + CHARLIE_ADDRESS, + ALICE_ADDRESS, + 70, + "charlie -> alice", + ); + + let info = mock_info(ALICE_ADDRESS, &coins(2, "token")); + let msg = ExecuteMsg::ApplyCycle { + path: [ALICE_ADDRESS, BOB_ADDRESS, CHARLIE_ADDRESS, ALICE_ADDRESS] + .into_iter() + .map(ToString::to_string) + .collect(), + amount: 70u32.into(), + }; 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); - } + // Cycle should be cleared and only `30` should remain in `alice -> bob` + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::GetObligations { + creditor: BOB_ADDRESS.to_string(), + }, + ) + .unwrap(); + let value: GetObligationsResponse = from_json(&res).unwrap(); + assert_eq!(&30u32.into(), value.obligations[0].1); - #[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); + // Check that alice received her karma tokens + let res = query( + deps.as_ref(), + mock_env(), + QueryMsg::Balance { + address: ALICE_ADDRESS.to_string(), + }, + ) + .unwrap(); + let value: BalanceResponse = from_json(&res).unwrap(); + assert_eq!(&210u32.into(), value.balance); } } diff --git a/bisenzone-cw-mvp/src/integration_tests.rs b/bisenzone-cw-mvp/src/integration_tests.rs deleted file mode 100644 index 4c50784..0000000 --- a/bisenzone-cw-mvp/src/integration_tests.rs +++ /dev/null @@ -1,71 +0,0 @@ -#[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 index e3f498e..f71330c 100644 --- a/bisenzone-cw-mvp/src/lib.rs +++ b/bisenzone-cw-mvp/src/lib.rs @@ -9,7 +9,6 @@ pub mod contract; mod error; -pub mod integration_tests; pub mod msg; pub mod state; From 45b362317897755b3e0d52407348a6c50660d30d Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 15 Nov 2023 14:46:48 -0800 Subject: [PATCH 12/35] Fix CI (use newer rustc) --- bisenzone-cw-mvp/.github/workflows/Basic.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bisenzone-cw-mvp/.github/workflows/Basic.yml b/bisenzone-cw-mvp/.github/workflows/Basic.yml index 3890a07..a96a08a 100644 --- a/bisenzone-cw-mvp/.github/workflows/Basic.yml +++ b/bisenzone-cw-mvp/.github/workflows/Basic.yml @@ -17,7 +17,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.60.0 + toolchain: 1.73.0 target: wasm32-unknown-unknown override: true @@ -48,7 +48,7 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.60.0 + toolchain: 1.73.0 override: true components: rustfmt, clippy From f63ae7683233cb91e2004701d4b803fbb17fff31 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 29 Nov 2023 08:34:24 -0800 Subject: [PATCH 13/35] Update README.md --- bisenzone-cw-mvp/README.md | 139 +++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/bisenzone-cw-mvp/README.md b/bisenzone-cw-mvp/README.md index 6264428..ac586cc 100644 --- a/bisenzone-cw-mvp/README.md +++ b/bisenzone-cw-mvp/README.md @@ -1,85 +1,88 @@ -# CosmWasm Starter Pack +# Bisenzone CosmWasm MVP -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. +CosmWasm smart contracts used in the Bisenzone MVP. -## Creating a new repo from template +## Testing instructions -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: +* Deploy the smart contract on a local wasmd instance + (see [Obligation Clearing Mvp: Local setup](https://github.com/informalsystems/obligation-clearing-mvp#local-setup)) + * Should normally be as simple as running the following scripts -> + ```bash + # terminal-1 + ./scripts/keygen.sh + ./scripts/init-node.sh + ./scripts/run-node.sh + ``` + ```bash + # terminal-2 + ./scripts/build-contract.sh + ./scripts/deploy-contract.sh artifacts/cw_mtcs.wasm + ``` +* Set contract env var (using the output of the `deploy.sh` script) - -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 +``` +export CONTRACT="wasm13we0myxwzlpx8l5ark8elw5gj5d59dl6cjkzmt80c5q5cv5rt54qhmta7s" ``` -Now, use it to create your new contract. -Go to the folder in which you want to place it and run: +* Upload a cycle of obligations - -**Latest** +``` +export EXECUTE='{ + "upload_obligation": { + "creditor": "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw", + "amount": "100", + "memo": "alice -> bob" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y -```sh -cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME +export EXECUTE='{ + "upload_obligation": { + "creditor": "wasm12r9t5wmre89rwakr0e5nyhfmaf4kdleyltsm9f", + "amount": "80", + "memo": "bob -> charlie" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from bob --chain-id testing -y + +export EXECUTE='{ + "upload_obligation": { + "creditor": "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv", + "amount": "70", + "memo": "charlie -> alice" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from charlie --chain-id testing -y ``` -For cloning minimal code repo: +* Clear cycle - -```sh -cargo generate --git https://github.com/CosmWasm/cw-template.git --name PROJECT_NAME -d minimal=true +``` +export EXECUTE='{ + "apply_cycle": { + "path": ["wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv", "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw", "wasm12r9t5wmre89rwakr0e5nyhfmaf4kdleyltsm9f", "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv"], + "amount": "70" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y ``` -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. +* Query obligations - -## 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 +``` +* wasmd query wasm contract-state smart "$CONTRACT" '{ + "get_obligations": { + "creditor": "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw" + } +}' ``` -## CI Support +* Check balance of the solver (to confirm increase in karma) - -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. +``` +wasmd query wasm contract-state smart "$CONTRACT" '{ + "balance": { + "address": "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv" + } +}' +``` From f9f7a81cb0bed5ceb222f6d56bbb4a65ee6a2880 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 29 Nov 2023 09:13:29 -0800 Subject: [PATCH 14/35] Setup cargo workspace to support multiple contracts --- bisenzone-cw-mvp/README.md | 2 +- bisenzone-cw-mvp/scripts/deploy-contract.sh | 2 +- bisenzone-cw-mvp/src/bin/schema.rs | 11 - bisenzone-cw-mvp/src/contract.rs | 322 -------------------- bisenzone-cw-mvp/src/error.rs | 27 -- bisenzone-cw-mvp/src/lib.rs | 15 - bisenzone-cw-mvp/src/msg.rs | 36 --- bisenzone-cw-mvp/src/state.rs | 11 - 8 files changed, 2 insertions(+), 424 deletions(-) delete mode 100644 bisenzone-cw-mvp/src/bin/schema.rs delete mode 100644 bisenzone-cw-mvp/src/contract.rs delete mode 100644 bisenzone-cw-mvp/src/error.rs delete mode 100644 bisenzone-cw-mvp/src/lib.rs delete mode 100644 bisenzone-cw-mvp/src/msg.rs delete mode 100644 bisenzone-cw-mvp/src/state.rs diff --git a/bisenzone-cw-mvp/README.md b/bisenzone-cw-mvp/README.md index ac586cc..1f7c83b 100644 --- a/bisenzone-cw-mvp/README.md +++ b/bisenzone-cw-mvp/README.md @@ -16,7 +16,7 @@ CosmWasm smart contracts used in the Bisenzone MVP. ```bash # terminal-2 ./scripts/build-contract.sh - ./scripts/deploy-contract.sh artifacts/cw_mtcs.wasm + ./scripts/deploy-contract.sh artifacts/cofi_karma_game.wasm ``` * Set contract env var (using the output of the `deploy.sh` script) - diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index 6215ab9..9deca3e 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -6,7 +6,7 @@ set -eo pipefail usage() { echo "Usage: $0 WASM_BIN [COUNT]" - echo "Example: $0 artifacts/cw_mtcs.wasm" + echo "Example: $0 artifacts/cofi_karma_game.wasm" exit 1 } diff --git a/bisenzone-cw-mvp/src/bin/schema.rs b/bisenzone-cw-mvp/src/bin/schema.rs deleted file mode 100644 index 55a2a8e..0000000 --- a/bisenzone-cw-mvp/src/bin/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index a0027df..0000000 --- a/bisenzone-cw-mvp/src/contract.rs +++ /dev/null @@ -1,322 +0,0 @@ -use cosmwasm_std::{ - entry_point, to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, - StdResult, Uint128, -}; -use cw2::set_contract_version; -use cw20_base::{ - contract::{execute_mint, query_balance}, - state::{MinterData, TokenInfo, TOKEN_INFO}, -}; - -use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{State, STATE, UTILIZATION}; - -// 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 { - owner: info.sender.to_string(), - }; - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - STATE.save(deps.storage, &state)?; - - // store token info using cw20-base format - let data = TokenInfo { - name: "liquidity savings".to_string(), - symbol: "!$".to_string(), - decimals: 0, - total_supply: Uint128::zero(), - // set self as minter, so we can properly execute mint and burn - mint: Some(MinterData { - minter: env.contract.address, - cap: None, - }), - }; - TOKEN_INFO.save(deps.storage, &data)?; - - Ok(Response::new() - .add_attribute("method", "instantiate") - .add_attribute("owner", info.sender)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::UploadObligation { - creditor, - amount, - memo, - } => execute::upload_obligation(deps, info, creditor, amount, memo), - ExecuteMsg::ApplyCycle { path, amount } => { - execute::apply_cycle(deps, env, info, path, amount) - } - } -} - -pub mod execute { - use cosmwasm_std::Uint128; - - use super::*; - - pub fn upload_obligation( - deps: DepsMut, - info: MessageInfo, - creditor: String, - amount: Uint128, - memo: String, - ) -> Result { - let creditor = deps.api.addr_validate(&creditor)?; - - UTILIZATION.update( - deps.storage, - (&creditor, &info.sender), - |utilization| -> Result<_, ContractError> { - // Uncomment if we want to only allow ourselves to add obligations - // if info.sender != state.owner { - // return Err(ContractError::Unauthorized); - // } - - let utilization = utilization.unwrap_or_default() + amount; - Ok(utilization) - }, - )?; - - Ok(Response::new() - .add_attribute("action", "upload_obligation") - .add_attribute("memo", memo)) - } - - pub fn apply_cycle( - deps: DepsMut, - env: Env, - info: MessageInfo, - path: Vec, - amount: Uint128, - ) -> Result { - let mut volume_cleared = Uint128::zero(); - - let path = path - .into_iter() - .map(|addr| deps.api.addr_validate(&addr)) - .collect::>>()?; - - validate_cycle(&path, amount, &deps)?; - - for from_to in path.windows(2) { - let (from, to) = (&from_to[0], &from_to[1]); - - UTILIZATION.update( - deps.storage, - (to, from), - |utilization| -> Result<_, ContractError> { - let utilization = utilization.unwrap_or_default() - amount; - volume_cleared += amount; - - Ok(utilization) - }, - )?; - } - - // call into cw20-base to mint the token, call as self as no one else is allowed - let sub_info = MessageInfo { - sender: env.contract.address.clone(), - funds: vec![], - }; - execute_mint(deps, env, sub_info, info.sender.to_string(), volume_cleared)?; - - Ok(Response::new() - .add_attribute("action", "apply_cycle") - .add_attribute("volume_cleared", format!("{}", volume_cleared))) - } - - fn validate_cycle(path: &[Addr], amount: Uint128, deps: &DepsMut) -> Result<(), ContractError> { - if path.first() != path.last() { - return Err(ContractError::PathNotCycle); - } - - for from_to in path.windows(2) { - let (from, to) = (&from_to[0], &from_to[1]); - - if amount > UTILIZATION.load(deps.storage, (to, from))? { - return Err(ContractError::ClearingTooMuch); - } - } - - Ok(()) - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetObligations { creditor } => { - to_json_binary(&query::get_obligations(deps, creditor)?) - } - QueryMsg::Balance { address } => to_json_binary(&query_balance(deps, address)?), - } -} - -pub mod query { - use super::*; - use cosmwasm_std::Order; - - use crate::msg::GetObligationsResponse; - use crate::state::UTILIZATION; - - pub fn get_obligations(deps: Deps, creditor: String) -> StdResult { - let creditor = deps.api.addr_validate(&creditor)?; - - let keys = UTILIZATION - .keys(deps.storage, None, None, Order::Ascending) - .collect::>>()? - .into_iter() - .filter(|(from, _)| from == creditor); - Ok(GetObligationsResponse { - obligations: keys - .map(|(from, to)| { - let utilization = UTILIZATION.load(deps.storage, (&from, &to)).unwrap(); - (to.to_string(), utilization) - }) - .collect(), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{ - mock_dependencies, mock_env, mock_info, MockApi, MockQuerier, MockStorage, - }; - use cosmwasm_std::{coins, from_json, OwnedDeps}; - use cw20::BalanceResponse; - - use crate::msg::GetObligationsResponse; - - const ALICE_ADDRESS: &str = "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv"; - const BOB_ADDRESS: &str = "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw"; - const CHARLIE_ADDRESS: &str = "wasm12r9t5wmre89rwakr0e5nyhfmaf4kdleyltsm9f"; - - #[test] - fn test_initialization() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg; - 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()); - } - - #[test] - fn test_upload_obligation() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg; - let info = mock_info("creator", &coins(2, "token")); - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - - create_obligation(&mut deps, ALICE_ADDRESS, BOB_ADDRESS, 100, "alice -> bob"); - - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::GetObligations { - creditor: BOB_ADDRESS.to_string(), - }, - ) - .unwrap(); - let value: GetObligationsResponse = from_json(&res).unwrap(); - assert_eq!(&100u32.into(), value.obligations[0].1); - } - - fn create_obligation( - deps: &mut OwnedDeps, - debtor: &str, - creditor: &str, - amount: u32, - memo: &str, - ) { - let info = mock_info(debtor, &coins(2, "token")); - let msg = ExecuteMsg::UploadObligation { - creditor: creditor.to_string(), - amount: amount.into(), - memo: memo.to_string(), - }; - let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - } - - #[test] - fn test_apply_cycle() { - let mut deps = mock_dependencies(); - - let msg = InstantiateMsg; - let info = mock_info("creator", &coins(2, "token")); - let _res = instantiate(deps.as_mut(), mock_env(), info, msg).unwrap(); - - create_obligation(&mut deps, ALICE_ADDRESS, BOB_ADDRESS, 100, "alice -> bob"); - create_obligation( - &mut deps, - BOB_ADDRESS, - CHARLIE_ADDRESS, - 80, - "bob -> charlie", - ); - create_obligation( - &mut deps, - CHARLIE_ADDRESS, - ALICE_ADDRESS, - 70, - "charlie -> alice", - ); - - let info = mock_info(ALICE_ADDRESS, &coins(2, "token")); - let msg = ExecuteMsg::ApplyCycle { - path: [ALICE_ADDRESS, BOB_ADDRESS, CHARLIE_ADDRESS, ALICE_ADDRESS] - .into_iter() - .map(ToString::to_string) - .collect(), - amount: 70u32.into(), - }; - let _res = execute(deps.as_mut(), mock_env(), info, msg).unwrap(); - - // Cycle should be cleared and only `30` should remain in `alice -> bob` - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::GetObligations { - creditor: BOB_ADDRESS.to_string(), - }, - ) - .unwrap(); - let value: GetObligationsResponse = from_json(&res).unwrap(); - assert_eq!(&30u32.into(), value.obligations[0].1); - - // Check that alice received her karma tokens - let res = query( - deps.as_ref(), - mock_env(), - QueryMsg::Balance { - address: ALICE_ADDRESS.to_string(), - }, - ) - .unwrap(); - let value: BalanceResponse = from_json(&res).unwrap(); - assert_eq!(&210u32.into(), value.balance); - } -} diff --git a/bisenzone-cw-mvp/src/error.rs b/bisenzone-cw-mvp/src/error.rs deleted file mode 100644 index ce3c93c..0000000 --- a/bisenzone-cw-mvp/src/error.rs +++ /dev/null @@ -1,27 +0,0 @@ -use cosmwasm_std::StdError; -use cw20_base::ContractError as Cw20ContractError; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("Unauthorized")] - Unauthorized, - - #[error("Specified path does not form a cycle")] - PathNotCycle, - - #[error("Amount is greater than utilization")] - ClearingTooMuch, - - #[error("Cw20 error: {0}")] - Cw20(Cw20ContractError), -} - -impl From for ContractError { - fn from(e: Cw20ContractError) -> Self { - Self::Cw20(e) - } -} diff --git a/bisenzone-cw-mvp/src/lib.rs b/bisenzone-cw-mvp/src/lib.rs deleted file mode 100644 index f71330c..0000000 --- a/bisenzone-cw-mvp/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -#![deny( - warnings, - trivial_casts, - trivial_numeric_casts, - unused_import_braces, - unused_qualifications -)] -#![forbid(unsafe_code)] - -pub mod contract; -mod error; -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 deleted file mode 100644 index 54c8b47..0000000 --- a/bisenzone-cw-mvp/src/msg.rs +++ /dev/null @@ -1,36 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Uint128; -#[allow(unused)] -use cw20::BalanceResponse; - -#[cw_serde] -pub struct InstantiateMsg; - -#[cw_serde] -pub enum ExecuteMsg { - UploadObligation { - creditor: String, - amount: Uint128, - memo: String, - }, - ApplyCycle { - path: Vec, - amount: Uint128, - }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - // GetCount returns the current count as a json-encoded number - #[returns(GetObligationsResponse)] - GetObligations { creditor: String }, - #[returns(BalanceResponse)] - Balance { address: String }, -} - -// We define a custom struct for each query response -#[cw_serde] -pub struct GetObligationsResponse { - pub obligations: Vec<(String, Uint128)>, -} diff --git a/bisenzone-cw-mvp/src/state.rs b/bisenzone-cw-mvp/src/state.rs deleted file mode 100644 index 752d93b..0000000 --- a/bisenzone-cw-mvp/src/state.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::{Item, Map}; - -#[cw_serde] -pub struct State { - pub owner: String, -} - -pub const STATE: Item = Item::new("state"); -pub const UTILIZATION: Map<(&Addr, &Addr), Uint128> = Map::new("utilization"); From 5855077d307232c52717108d4ec3033026f9f883 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 29 Nov 2023 09:22:23 -0800 Subject: [PATCH 15/35] Add contract-specific README --- bisenzone-cw-mvp/README.md | 64 ++------------------------------------ 1 file changed, 2 insertions(+), 62 deletions(-) diff --git a/bisenzone-cw-mvp/README.md b/bisenzone-cw-mvp/README.md index 1f7c83b..590fd28 100644 --- a/bisenzone-cw-mvp/README.md +++ b/bisenzone-cw-mvp/README.md @@ -24,65 +24,5 @@ CosmWasm smart contracts used in the Bisenzone MVP. export CONTRACT="wasm13we0myxwzlpx8l5ark8elw5gj5d59dl6cjkzmt80c5q5cv5rt54qhmta7s" ``` -* Upload a cycle of obligations - - -``` -export EXECUTE='{ - "upload_obligation": { - "creditor": "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw", - "amount": "100", - "memo": "alice -> bob" - } -}' -wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y - -export EXECUTE='{ - "upload_obligation": { - "creditor": "wasm12r9t5wmre89rwakr0e5nyhfmaf4kdleyltsm9f", - "amount": "80", - "memo": "bob -> charlie" - } -}' -wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from bob --chain-id testing -y - -export EXECUTE='{ - "upload_obligation": { - "creditor": "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv", - "amount": "70", - "memo": "charlie -> alice" - } -}' -wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from charlie --chain-id testing -y -``` - -* Clear cycle - - -``` -export EXECUTE='{ - "apply_cycle": { - "path": ["wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv", "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw", "wasm12r9t5wmre89rwakr0e5nyhfmaf4kdleyltsm9f", "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv"], - "amount": "70" - } -}' -wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y -``` - -* Query obligations - - -``` -* wasmd query wasm contract-state smart "$CONTRACT" '{ - "get_obligations": { - "creditor": "wasm19u72czh0w4jraan8esalv48nrwemh8kgax69yw" - } -}' -``` - -* Check balance of the solver (to confirm increase in karma) - - -``` -wasmd query wasm contract-state smart "$CONTRACT" '{ - "balance": { - "address": "wasm19xlctyn7ha6pqg7pk9lnk8y60rk8646dm86qgv" - } -}' -``` +For further instructions, refer to the contract specific READMEs +(e.g. [cofi-karma-game README.md](contracts/cofi-karma-game/README.md)) \ No newline at end of file From 595b0e02a87390ffab127d6feff2dd99dffa7beb Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 29 Nov 2023 13:13:20 -0800 Subject: [PATCH 16/35] Init cw-tee-mtcs contract --- .../contracts/cw-tee-mtcs/Cargo.toml | 56 +++++++++++++++++++ .../contracts/cw-tee-mtcs/README.md | 4 ++ .../contracts/cw-tee-mtcs/src/bin/schema.rs | 11 ++++ .../contracts/cw-tee-mtcs/src/contract.rs | 50 +++++++++++++++++ .../contracts/cw-tee-mtcs/src/error.rs | 11 ++++ .../contracts/cw-tee-mtcs/src/lib.rs | 15 +++++ .../contracts/cw-tee-mtcs/src/msg.rs | 11 ++++ .../contracts/cw-tee-mtcs/src/state.rs | 9 +++ 8 files changed, 167 insertions(+) create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/lib.rs create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs create mode 100644 bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml new file mode 100644 index 0000000..d18bf27 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "cw-tee-mtcs" +version = "0.1.0" +authors = ["hu55a1n1 "] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.14.0 +""" + +[dependencies] +cosmwasm-schema = "1.5.0" +cosmwasm-std = { version = "1.5.0", features = [ + "cosmwasm_1_3", + # Enable this if you only deploy to chains that have CosmWasm 1.4 or higher + # "cosmwasm_1_4", +] } +cw-storage-plus = "1.1.0" +cw2 = "1.1.1" +schemars = "0.8.15" +serde = { version = "1.0.189", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.49" } + +[dev-dependencies] +cw-multi-test = "0.17.0" diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md new file mode 100644 index 0000000..f2a1561 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md @@ -0,0 +1,4 @@ +# CosmWasm smart contract to support MTCS on TEE + +An implementation of the on-chain component of +the [Key managers proposal v1](https://github.com/informalsystems/tee-mtcs/issues/26). diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs new file mode 100644 index 0000000..353dd79 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::write_api; + +use cw_tee_mtcs::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs new file mode 100644 index 0000000..45c62ea --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -0,0 +1,50 @@ +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use crate::state::{State, STATE}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw-tee-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 { + owner: info.sender.to_string(), + }; + 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)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + _deps: DepsMut, + _env: Env, + _info: MessageInfo, + _msg: ExecuteMsg, +) -> Result { + todo!() +} + +pub mod execute {} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { + todo!() +} + +pub mod query {} + +#[cfg(test)] +mod tests {} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs new file mode 100644 index 0000000..840de74 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized, +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/lib.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/lib.rs new file mode 100644 index 0000000..f71330c --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/lib.rs @@ -0,0 +1,15 @@ +#![deny( + warnings, + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications +)] +#![forbid(unsafe_code)] + +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs new file mode 100644 index 0000000..642f348 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; + +#[cw_serde] +pub struct InstantiateMsg; + +#[cw_serde] +pub enum ExecuteMsg {} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg {} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs new file mode 100644 index 0000000..79a7e97 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -0,0 +1,9 @@ +use cosmwasm_schema::cw_serde; +use cw_storage_plus::Item; + +#[cw_serde] +pub struct State { + pub owner: String, +} + +pub const STATE: Item = Item::new("state"); From 8603c9753471cc2d46e302ca8cbcae2f04b9563d Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 29 Nov 2023 13:37:02 -0800 Subject: [PATCH 17/35] Store requests in a map --- .../contracts/cw-tee-mtcs/src/msg.rs | 19 +++++++++++++++++++ .../contracts/cw-tee-mtcs/src/state.rs | 10 +++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index 642f348..0e4d9b3 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -6,6 +6,25 @@ pub struct InstantiateMsg; #[cw_serde] pub enum ExecuteMsg {} +pub mod execute { + use super::*; + + #[cw_serde] + pub struct Nonce([u8; 32]); + + #[cw_serde] + pub struct JoinComputeNodeMsg { + compute_node_pub_key: String, + nonce: Nonce, + } + + #[cw_serde] + pub struct ShareEpochKeyMsg { + compute_node_pub_key: String, + nonce: Nonce, + } +} + #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg {} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index 79a7e97..14d4ec4 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,9 +1,17 @@ +use crate::msg::execute::{JoinComputeNodeMsg, Nonce, ShareEpochKeyMsg}; use cosmwasm_schema::cw_serde; -use cw_storage_plus::Item; +use cw_storage_plus::{Item, Map}; #[cw_serde] pub struct State { pub owner: String, } +#[cw_serde] +pub enum Request { + JoinComputeNode(JoinComputeNodeMsg), + ShareEpochKey(ShareEpochKeyMsg), +} + pub const STATE: Item = Item::new("state"); +pub const REQUESTS: Map<&Nonce, &Request> = Map::new("requests"); From bca2adbfc421a5b5badfc67c5ecf3c957c4f57e5 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 29 Nov 2023 14:09:57 -0800 Subject: [PATCH 18/35] Implement join request --- .../contracts/cw-tee-mtcs/Cargo.toml | 1 + .../contracts/cw-tee-mtcs/src/contract.rs | 39 +++++++++++++++++-- .../contracts/cw-tee-mtcs/src/error.rs | 10 +++++ .../contracts/cw-tee-mtcs/src/msg.rs | 17 +++----- .../contracts/cw-tee-mtcs/src/state.rs | 9 +++-- 5 files changed, 57 insertions(+), 19 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml index d18bf27..ac389f6 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -48,6 +48,7 @@ cosmwasm-std = { version = "1.5.0", features = [ ] } cw-storage-plus = "1.1.0" cw2 = "1.1.1" +ecies = "0.2.6" schemars = "0.8.15" serde = { version = "1.0.189", default-features = false, features = ["derive"] } thiserror = { version = "1.0.49" } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 45c62ea..cfd795b 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -2,6 +2,7 @@ use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Respons use cw2::set_contract_version; use crate::error::ContractError; +use crate::msg::execute::JoinComputeNodeMsg; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{State, STATE}; @@ -29,15 +30,45 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - _deps: DepsMut, + deps: DepsMut, _env: Env, _info: MessageInfo, - _msg: ExecuteMsg, + msg: ExecuteMsg, ) -> Result { - todo!() + match msg { + ExecuteMsg::JoinComputeNode(JoinComputeNodeMsg { + compute_node_pub_key, + nonce, + }) => execute::enqueue_join_request(deps, compute_node_pub_key, nonce), + } } -pub mod execute {} +pub mod execute { + use cosmwasm_std::{DepsMut, Response}; + use ecies::PublicKey; + + use crate::state::Nonce; + use crate::state::{Request, REQUESTS}; + use crate::ContractError; + + pub fn enqueue_join_request( + deps: DepsMut, + compute_node_pub_key: String, + nonce: Nonce, + ) -> Result { + let _ = PublicKey::parse_slice(compute_node_pub_key.as_bytes(), None)?; + + REQUESTS.save( + deps.storage, + &nonce, + &Request::JoinComputeNode(compute_node_pub_key.clone()), + )?; + + Ok(Response::new() + .add_attribute("action", "enqueue_request") + .add_attribute("compute_node_pub_key", compute_node_pub_key)) + } +} #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs index 840de74..d05eb20 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::StdError; +use ecies::SecpError; use thiserror::Error; #[derive(Error, Debug)] @@ -8,4 +9,13 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized, + + #[error("Invalid pubkey")] + InvalidPubKey(SecpError), +} + +impl From for ContractError { + fn from(e: SecpError) -> Self { + Self::InvalidPubKey(e) + } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index 0e4d9b3..abb0171 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -4,24 +4,19 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; pub struct InstantiateMsg; #[cw_serde] -pub enum ExecuteMsg {} +pub enum ExecuteMsg { + JoinComputeNode(execute::JoinComputeNodeMsg), +} pub mod execute { use super::*; - #[cw_serde] - pub struct Nonce([u8; 32]); + use crate::state::Nonce; #[cw_serde] pub struct JoinComputeNodeMsg { - compute_node_pub_key: String, - nonce: Nonce, - } - - #[cw_serde] - pub struct ShareEpochKeyMsg { - compute_node_pub_key: String, - nonce: Nonce, + pub compute_node_pub_key: String, + pub nonce: Nonce, } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index 14d4ec4..6b204f3 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,7 +1,9 @@ -use crate::msg::execute::{JoinComputeNodeMsg, Nonce, ShareEpochKeyMsg}; use cosmwasm_schema::cw_serde; use cw_storage_plus::{Item, Map}; +pub type Nonce = [u8; 32]; +pub type RawPublicKey = String; + #[cw_serde] pub struct State { pub owner: String, @@ -9,9 +11,8 @@ pub struct State { #[cw_serde] pub enum Request { - JoinComputeNode(JoinComputeNodeMsg), - ShareEpochKey(ShareEpochKeyMsg), + JoinComputeNode(RawPublicKey), } pub const STATE: Item = Item::new("state"); -pub const REQUESTS: Map<&Nonce, &Request> = Map::new("requests"); +pub const REQUESTS: Map<&Nonce, Request> = Map::new("requests"); From efb9aae66b873bf0d65dd80940438973da87817e Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Mon, 4 Dec 2023 09:18:25 -0800 Subject: [PATCH 19/35] Remove ecies dep; use libsecp256k1 instead --- bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml | 2 +- bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs | 2 +- bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml index ac389f6..036ff5d 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -48,7 +48,7 @@ cosmwasm-std = { version = "1.5.0", features = [ ] } cw-storage-plus = "1.1.0" cw2 = "1.1.1" -ecies = "0.2.6" +libsecp256k1 = { version = "0.7.1", default-features = false } schemars = "0.8.15" serde = { version = "1.0.189", default-features = false, features = ["derive"] } thiserror = { version = "1.0.49" } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index cfd795b..5b1e832 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -45,7 +45,7 @@ pub fn execute( pub mod execute { use cosmwasm_std::{DepsMut, Response}; - use ecies::PublicKey; + use libsecp256k1::PublicKey; use crate::state::Nonce; use crate::state::{Request, REQUESTS}; diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs index d05eb20..a965913 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::StdError; -use ecies::SecpError; +use libsecp256k1::Error as SecpError; use thiserror::Error; #[derive(Error, Debug)] From 973700e6f47a43e4846d699b1de736bb79479bb0 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Tue, 5 Dec 2023 09:03:43 -0800 Subject: [PATCH 20/35] Be consistent with MTCS TEE request --- .../contracts/cw-tee-mtcs/Cargo.toml | 3 +- .../contracts/cw-tee-mtcs/src/contract.rs | 47 ++++++++++++++----- .../contracts/cw-tee-mtcs/src/error.rs | 30 ++++++++++-- .../contracts/cw-tee-mtcs/src/msg.rs | 23 +++++++-- .../contracts/cw-tee-mtcs/src/state.rs | 7 +-- 5 files changed, 84 insertions(+), 26 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml index 036ff5d..73c5a16 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -48,7 +48,8 @@ cosmwasm-std = { version = "1.5.0", features = [ ] } cw-storage-plus = "1.1.0" cw2 = "1.1.1" -libsecp256k1 = { version = "0.7.1", default-features = false } +hex = { version = "0.4.3", default-features = false } +k256 = { version = "0.13.2", default-features = false, features = ["ecdsa"] } schemars = "0.8.15" serde = { version = "1.0.189", default-features = false, features = ["derive"] } thiserror = { version = "1.0.49" } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 5b1e832..0bc5c76 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -1,4 +1,6 @@ -use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, +}; use cw2::set_contract_version; use crate::error::ContractError; @@ -37,45 +39,64 @@ pub fn execute( ) -> Result { match msg { ExecuteMsg::JoinComputeNode(JoinComputeNodeMsg { - compute_node_pub_key, + io_exchange_key, + address, nonce, - }) => execute::enqueue_join_request(deps, compute_node_pub_key, nonce), + }) => execute::enqueue_join_request(deps, io_exchange_key, address, nonce), } } pub mod execute { use cosmwasm_std::{DepsMut, Response}; - use libsecp256k1::PublicKey; + use k256::ecdsa::VerifyingKey; - use crate::state::Nonce; + use crate::state::{RawAddress, RawNonce, RawPublicKey}; use crate::state::{Request, REQUESTS}; use crate::ContractError; pub fn enqueue_join_request( deps: DepsMut, - compute_node_pub_key: String, - nonce: Nonce, + io_exchange_key: RawPublicKey, + address: RawAddress, + nonce: RawNonce, ) -> Result { - let _ = PublicKey::parse_slice(compute_node_pub_key.as_bytes(), None)?; + let _ = VerifyingKey::from_sec1_bytes(&hex::decode(&io_exchange_key)?)?; + let _ = deps.api.addr_validate(&address)?; + let _ = hex::decode(&nonce); REQUESTS.save( deps.storage, &nonce, - &Request::JoinComputeNode(compute_node_pub_key.clone()), + &Request::JoinComputeNode((io_exchange_key.clone(), address)), )?; Ok(Response::new() .add_attribute("action", "enqueue_request") - .add_attribute("compute_node_pub_key", compute_node_pub_key)) + .add_attribute("io_exchange_key", io_exchange_key)) } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(_deps: Deps, _env: Env, _msg: QueryMsg) -> StdResult { - todo!() +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetRequests {} => to_json_binary(&query::get_requests(deps)?), + } } -pub mod query {} +pub mod query { + use cosmwasm_std::{Deps, Order, StdResult}; + + use crate::msg::query::GetRequestsResponse; + use crate::state::{RawNonce, Request, REQUESTS}; + + pub fn get_requests(deps: Deps) -> StdResult { + Ok(GetRequestsResponse { + requests: REQUESTS + .range(deps.storage, None, None, Order::Ascending) + .collect::>>()?, + }) + } +} #[cfg(test)] mod tests {} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs index a965913..06cf7ce 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -1,5 +1,6 @@ use cosmwasm_std::StdError; -use libsecp256k1::Error as SecpError; +use hex::FromHexError; +use k256::ecdsa::Error as K256Error; use thiserror::Error; #[derive(Error, Debug)] @@ -11,11 +12,32 @@ pub enum ContractError { Unauthorized, #[error("Invalid pubkey")] - InvalidPubKey(SecpError), + InvalidPubKey(PublicKeyError), } -impl From for ContractError { - fn from(e: SecpError) -> Self { +#[derive(Error, Debug)] +pub enum PublicKeyError { + #[error("Not Secp256K1")] + K256(K256Error), + #[error("Invalid hex")] + Hex(FromHexError), +} + +impl> From for ContractError { + fn from(e: T) -> Self { + let e = e.into(); Self::InvalidPubKey(e) } } + +impl From for PublicKeyError { + fn from(e: K256Error) -> Self { + PublicKeyError::K256(e) + } +} + +impl From for PublicKeyError { + fn from(e: FromHexError) -> Self { + PublicKeyError::Hex(e) + } +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index abb0171..81508df 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -11,15 +11,28 @@ pub enum ExecuteMsg { pub mod execute { use super::*; - use crate::state::Nonce; - #[cw_serde] pub struct JoinComputeNodeMsg { - pub compute_node_pub_key: String, - pub nonce: Nonce, + pub io_exchange_key: String, + pub address: String, + pub nonce: String, } } #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg {} +pub enum QueryMsg { + #[returns(query::GetRequestsResponse)] + GetRequests {}, +} + +pub mod query { + use super::*; + + use crate::state::{RawNonce, Request}; + + #[cw_serde] + pub struct GetRequestsResponse { + pub requests: Vec<(RawNonce, Request)>, + } +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index 6b204f3..415b20f 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,8 +1,9 @@ use cosmwasm_schema::cw_serde; use cw_storage_plus::{Item, Map}; -pub type Nonce = [u8; 32]; +pub type RawNonce = String; pub type RawPublicKey = String; +pub type RawAddress = String; #[cw_serde] pub struct State { @@ -11,8 +12,8 @@ pub struct State { #[cw_serde] pub enum Request { - JoinComputeNode(RawPublicKey), + JoinComputeNode((RawPublicKey, RawAddress)), } pub const STATE: Item = Item::new("state"); -pub const REQUESTS: Map<&Nonce, Request> = Map::new("requests"); +pub const REQUESTS: Map<&RawNonce, Request> = Map::new("requests"); From bd0742cd84a387f915d31469b4b2c13cbaffa33e Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Tue, 5 Dec 2023 09:04:16 -0800 Subject: [PATCH 21/35] Update README.md(s) --- .../contracts/cw-tee-mtcs/README.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md index f2a1561..99f6be2 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md @@ -2,3 +2,26 @@ An implementation of the on-chain component of the [Key managers proposal v1](https://github.com/informalsystems/tee-mtcs/issues/26). + +## Testing instructions + +* Upload a cycle of obligations - + +``` +export EXECUTE='{ + "join_compute_node": { + "io_exchange_key": "03E67EF09213633074FB4FBF338643F4F0C574ED60EF11D03422EEB06FA38C8F3F", + "address": "wasm10n4dsljyyfp2k2hy6e8vuc9ry32px2egwt5e0m", + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y +``` + +* Query requests - + +``` +wasmd query wasm contract-state smart "$CONTRACT" '{ + "get_requests": { } +}' +``` From 25116842566407e2bfc139e963fc963b69241d4e Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Tue, 5 Dec 2023 09:08:45 -0800 Subject: [PATCH 22/35] Fix CI --- bisenzone-cw-mvp/.github/workflows/Basic.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bisenzone-cw-mvp/.github/workflows/Basic.yml b/bisenzone-cw-mvp/.github/workflows/Basic.yml index a96a08a..4b0121f 100644 --- a/bisenzone-cw-mvp/.github/workflows/Basic.yml +++ b/bisenzone-cw-mvp/.github/workflows/Basic.yml @@ -64,11 +64,17 @@ jobs: command: clippy args: -- -D warnings - - name: Generate Schema + - name: Generate Schema (for cofi-karma-game) uses: actions-rs/cargo@v1 with: command: schema - args: --locked + args: --locked -p cofi-karma-game + + - name: Generate Schema (for cw-tee-mtcs) + uses: actions-rs/cargo@v1 + with: + command: schema + args: --locked -p cw-tee-mtcs - name: Schema Changes # fails if any changes not committed From 1533bb8cd2811db18b668dfaddf4cc26e77957e3 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Fri, 22 Dec 2023 05:10:51 -0800 Subject: [PATCH 23/35] Key manager bootstrap --- bisenzone-cw-mvp/.gitignore | 4 +- .../contracts/cw-tee-mtcs/README.md | 25 ++++++- .../contracts/cw-tee-mtcs/src/contract.rs | 65 +++++++++++++++++-- .../contracts/cw-tee-mtcs/src/error.rs | 25 +++---- .../contracts/cw-tee-mtcs/src/msg.rs | 18 ++++- .../contracts/cw-tee-mtcs/src/state.rs | 12 ++++ 6 files changed, 124 insertions(+), 25 deletions(-) diff --git a/bisenzone-cw-mvp/.gitignore b/bisenzone-cw-mvp/.gitignore index c7ca55f..52990d7 100644 --- a/bisenzone-cw-mvp/.gitignore +++ b/bisenzone-cw-mvp/.gitignore @@ -1,7 +1,7 @@ # Build results /artifacts -/target -/schema +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 diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md index 99f6be2..b278a07 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md @@ -5,7 +5,30 @@ the [Key managers proposal v1](https://github.com/informalsystems/tee-mtcs/issue ## Testing instructions -* Upload a cycle of obligations - +* Submit a bootstrap key manager request - + +``` +export EXECUTE='{ + "bootstrap_key_manager": { + "compute_mrenclave": "dc43f8c42d8e5f52c8bbd68f426242153f0be10630ff8cca255129a3ca03d273", + "key_manager_mrenclave": "1cf2e52911410fbf3f199056a98d58795a559a2e800933f7fcd13d048462271c", + "tcb_info": "" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y +``` + +* Query the bootstrap state - + +``` +wasmd query wasm contract-state raw "$CONTRACT" 7367787374617465 # BIN_HEX('sgx_state') +# OR ---- +wasmd query wasm contract-state smart "$CONTRACT" '{ + "get_sgx_state": { } +}' +``` + +* Submit a join compute node request - ``` export EXECUTE='{ diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 0bc5c76..2e7793b 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ use cw2::set_contract_version; use crate::error::ContractError; -use crate::msg::execute::JoinComputeNodeMsg; +use crate::msg::execute::{BootstrapKeyManagerMsg, JoinComputeNodeMsg}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{State, STATE}; @@ -38,6 +38,13 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::BootstrapKeyManager(BootstrapKeyManagerMsg { + compute_mrenclave, + key_manager_mrenclave, + tcb_info, + }) => { + execute::bootstrap_key_manger(deps, compute_mrenclave, key_manager_mrenclave, tcb_info) + } ExecuteMsg::JoinComputeNode(JoinComputeNodeMsg { io_exchange_key, address, @@ -50,9 +57,46 @@ pub mod execute { use cosmwasm_std::{DepsMut, Response}; use k256::ecdsa::VerifyingKey; - use crate::state::{RawAddress, RawNonce, RawPublicKey}; + use crate::state::{ + Mrenclave, RawAddress, RawMrenclave, RawNonce, RawPublicKey, RawTcbInfo, SgxState, + SGX_STATE, + }; use crate::state::{Request, REQUESTS}; use crate::ContractError; + use crate::ContractError::BadLength; + + pub fn bootstrap_key_manger( + deps: DepsMut, + compute_mrenclave: RawMrenclave, + key_manager_mrenclave: RawMrenclave, + tcb_info: RawTcbInfo, + ) -> Result { + let _: Mrenclave = hex::decode(&compute_mrenclave)? + .try_into() + .map_err(|_| BadLength)?; + let _: Mrenclave = hex::decode(&key_manager_mrenclave)? + .try_into() + .map_err(|_| BadLength)?; + // TODO(hu55a1n1): validate TcbInfo + + let sgx_state = SgxState { + compute_mrenclave: compute_mrenclave.clone(), + key_manager_mrenclave: key_manager_mrenclave.clone(), + tcb_info: tcb_info.clone(), + }; + + if SGX_STATE.exists(deps.storage) { + return Err(ContractError::Unauthorized); + } + + SGX_STATE.save(deps.storage, &sgx_state)?; + + Ok(Response::new() + .add_attribute("action", "bootstrap_key_manger") + .add_attribute("compute_mrenclave", compute_mrenclave) + .add_attribute("key_manager_mrenclave", key_manager_mrenclave) + .add_attribute("tcb_info", tcb_info)) + } pub fn enqueue_join_request( deps: DepsMut, @@ -79,6 +123,7 @@ pub mod execute { #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { + QueryMsg::GetSgxState {} => to_json_binary(&query::get_sgx_state(deps)?), QueryMsg::GetRequests {} => to_json_binary(&query::get_requests(deps)?), } } @@ -86,8 +131,20 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { pub mod query { use cosmwasm_std::{Deps, Order, StdResult}; - use crate::msg::query::GetRequestsResponse; - use crate::state::{RawNonce, Request, REQUESTS}; + use crate::msg::query::{GetRequestsResponse, GetSgxStateResponse}; + use crate::state::{RawNonce, Request, SgxState, REQUESTS, SGX_STATE}; + + pub fn get_sgx_state(deps: Deps) -> StdResult { + let SgxState { + compute_mrenclave, + key_manager_mrenclave, + .. + } = SGX_STATE.load(deps.storage)?; + Ok(GetSgxStateResponse { + compute_mrenclave, + key_manager_mrenclave, + }) + } pub fn get_requests(deps: Deps) -> StdResult { Ok(GetRequestsResponse { diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs index 06cf7ce..f482c7b 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -11,33 +11,24 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized, - #[error("Invalid pubkey")] - InvalidPubKey(PublicKeyError), -} - -#[derive(Error, Debug)] -pub enum PublicKeyError { #[error("Not Secp256K1")] K256(K256Error), + #[error("Invalid hex")] Hex(FromHexError), + + #[error("Invalid length")] + BadLength, } -impl> From for ContractError { - fn from(e: T) -> Self { - let e = e.into(); - Self::InvalidPubKey(e) - } -} - -impl From for PublicKeyError { +impl From for ContractError { fn from(e: K256Error) -> Self { - PublicKeyError::K256(e) + ContractError::K256(e) } } -impl From for PublicKeyError { +impl From for ContractError { fn from(e: FromHexError) -> Self { - PublicKeyError::Hex(e) + ContractError::Hex(e) } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index 81508df..b8b69c4 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -5,12 +5,20 @@ pub struct InstantiateMsg; #[cw_serde] pub enum ExecuteMsg { + BootstrapKeyManager(execute::BootstrapKeyManagerMsg), JoinComputeNode(execute::JoinComputeNodeMsg), } pub mod execute { use super::*; + #[cw_serde] + pub struct BootstrapKeyManagerMsg { + pub compute_mrenclave: String, + pub key_manager_mrenclave: String, + pub tcb_info: String, + } + #[cw_serde] pub struct JoinComputeNodeMsg { pub io_exchange_key: String, @@ -22,6 +30,8 @@ pub mod execute { #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(query::GetSgxStateResponse)] + GetSgxState {}, #[returns(query::GetRequestsResponse)] GetRequests {}, } @@ -29,7 +39,13 @@ pub enum QueryMsg { pub mod query { use super::*; - use crate::state::{RawNonce, Request}; + use crate::state::{RawMrenclave, RawNonce, Request}; + + #[cw_serde] + pub struct GetSgxStateResponse { + pub compute_mrenclave: RawMrenclave, + pub key_manager_mrenclave: RawMrenclave, + } #[cw_serde] pub struct GetRequestsResponse { diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index 415b20f..56a0bc6 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -4,6 +4,10 @@ use cw_storage_plus::{Item, Map}; pub type RawNonce = String; pub type RawPublicKey = String; pub type RawAddress = String; +pub type RawMrenclave = String; +pub type RawTcbInfo = String; + +pub type Mrenclave = [u8; 32]; #[cw_serde] pub struct State { @@ -15,5 +19,13 @@ pub enum Request { JoinComputeNode((RawPublicKey, RawAddress)), } +#[cw_serde] +pub struct SgxState { + pub compute_mrenclave: RawMrenclave, + pub key_manager_mrenclave: RawMrenclave, + pub tcb_info: RawTcbInfo, +} + pub const STATE: Item = Item::new("state"); pub const REQUESTS: Map<&RawNonce, Request> = Map::new("requests"); +pub const SGX_STATE: Item = Item::new("sgxstate"); From de5314122ab1654896c6f61ff396f45486e38673 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Mon, 1 Jan 2024 12:37:08 -0800 Subject: [PATCH 24/35] Use Deque for REQUESTS storage --- .../contracts/cw-tee-mtcs/src/contract.rs | 16 ++++++++-------- .../contracts/cw-tee-mtcs/src/state.rs | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 2e7793b..3c076a3 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -108,10 +108,12 @@ pub mod execute { let _ = deps.api.addr_validate(&address)?; let _ = hex::decode(&nonce); - REQUESTS.save( + REQUESTS.push_back( deps.storage, - &nonce, - &Request::JoinComputeNode((io_exchange_key.clone(), address)), + &( + nonce, + Request::JoinComputeNode((io_exchange_key.clone(), address)), + ), )?; Ok(Response::new() @@ -129,10 +131,10 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } pub mod query { - use cosmwasm_std::{Deps, Order, StdResult}; + use cosmwasm_std::{Deps, StdResult}; use crate::msg::query::{GetRequestsResponse, GetSgxStateResponse}; - use crate::state::{RawNonce, Request, SgxState, REQUESTS, SGX_STATE}; + use crate::state::{SgxState, REQUESTS, SGX_STATE}; pub fn get_sgx_state(deps: Deps) -> StdResult { let SgxState { @@ -148,9 +150,7 @@ pub mod query { pub fn get_requests(deps: Deps) -> StdResult { Ok(GetRequestsResponse { - requests: REQUESTS - .range(deps.storage, None, None, Order::Ascending) - .collect::>>()?, + requests: REQUESTS.iter(deps.storage)?.flat_map(|r| r.ok()).collect(), }) } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index 56a0bc6..3fd17e7 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cw_storage_plus::{Item, Map}; +use cw_storage_plus::{Deque, Item}; pub type RawNonce = String; pub type RawPublicKey = String; @@ -27,5 +27,5 @@ pub struct SgxState { } pub const STATE: Item = Item::new("state"); -pub const REQUESTS: Map<&RawNonce, Request> = Map::new("requests"); +pub const REQUESTS: Deque<(RawNonce, Request)> = Deque::new("requests"); pub const SGX_STATE: Item = Item::new("sgxstate"); From 57c14e3ef934e9ed8e9fffb0a344bb4d8b73193f Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Mon, 1 Jan 2024 12:49:32 -0800 Subject: [PATCH 25/35] Use Item for REQUESTS storage --- .../contracts/cw-tee-mtcs/src/contract.rs | 15 +++++++-------- .../contracts/cw-tee-mtcs/src/state.rs | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 3c076a3..5063f10 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -108,13 +108,12 @@ pub mod execute { let _ = deps.api.addr_validate(&address)?; let _ = hex::decode(&nonce); - REQUESTS.push_back( - deps.storage, - &( - nonce, - Request::JoinComputeNode((io_exchange_key.clone(), address)), - ), - )?; + let mut requests = REQUESTS.may_load(deps.storage)?.unwrap_or_default(); + requests.push(( + nonce, + Request::JoinComputeNode((io_exchange_key.clone(), address)), + )); + REQUESTS.save(deps.storage, &requests)?; Ok(Response::new() .add_attribute("action", "enqueue_request") @@ -150,7 +149,7 @@ pub mod query { pub fn get_requests(deps: Deps) -> StdResult { Ok(GetRequestsResponse { - requests: REQUESTS.iter(deps.storage)?.flat_map(|r| r.ok()).collect(), + requests: REQUESTS.load(deps.storage)?, }) } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index 3fd17e7..98acc2b 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cw_storage_plus::{Deque, Item}; +use cw_storage_plus::Item; pub type RawNonce = String; pub type RawPublicKey = String; @@ -27,5 +27,5 @@ pub struct SgxState { } pub const STATE: Item = Item::new("state"); -pub const REQUESTS: Deque<(RawNonce, Request)> = Deque::new("requests"); +pub const REQUESTS: Item> = Item::new("requests"); pub const SGX_STATE: Item = Item::new("sgxstate"); From 976c1cacf05108b85006937d055babdd5b0cf507 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 10 Jan 2024 07:10:24 -0800 Subject: [PATCH 26/35] Impl RegisterEpochKeyMsg and handler --- .../contracts/cw-tee-mtcs/src/contract.rs | 25 ++++++++++++++++--- .../contracts/cw-tee-mtcs/src/msg.rs | 6 +++++ .../contracts/cw-tee-mtcs/src/state.rs | 8 +++++- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 5063f10..55f68d4 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -4,7 +4,7 @@ use cosmwasm_std::{ use cw2::set_contract_version; use crate::error::ContractError; -use crate::msg::execute::{BootstrapKeyManagerMsg, JoinComputeNodeMsg}; +use crate::msg::execute::{BootstrapKeyManagerMsg, JoinComputeNodeMsg, RegisterEpochKeyMsg}; use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::state::{State, STATE}; @@ -45,6 +45,9 @@ pub fn execute( }) => { execute::bootstrap_key_manger(deps, compute_mrenclave, key_manager_mrenclave, tcb_info) } + ExecuteMsg::RegisterEpochKey(RegisterEpochKeyMsg { epoch_key }) => { + execute::register_epoch_key(deps, epoch_key) + } ExecuteMsg::JoinComputeNode(JoinComputeNodeMsg { io_exchange_key, address, @@ -58,8 +61,8 @@ pub mod execute { use k256::ecdsa::VerifyingKey; use crate::state::{ - Mrenclave, RawAddress, RawMrenclave, RawNonce, RawPublicKey, RawTcbInfo, SgxState, - SGX_STATE, + EpochState, Mrenclave, RawAddress, RawMrenclave, RawNonce, RawPublicKey, RawTcbInfo, + SgxState, EPOCH_STATE, SGX_STATE, }; use crate::state::{Request, REQUESTS}; use crate::ContractError; @@ -98,6 +101,22 @@ pub mod execute { .add_attribute("tcb_info", tcb_info)) } + pub fn register_epoch_key( + deps: DepsMut, + epoch_key: RawPublicKey, + ) -> Result { + let _ = VerifyingKey::from_sec1_bytes(&hex::decode(&epoch_key)?)?; + + let epoch_state = EpochState { + epoch_key: epoch_key.clone(), + }; + EPOCH_STATE.save(deps.storage, &epoch_state)?; + + Ok(Response::new() + .add_attribute("action", "register_epoch_key") + .add_attribute("epoch_key", epoch_key)) + } + pub fn enqueue_join_request( deps: DepsMut, io_exchange_key: RawPublicKey, diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index b8b69c4..f442b26 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -6,6 +6,7 @@ pub struct InstantiateMsg; #[cw_serde] pub enum ExecuteMsg { BootstrapKeyManager(execute::BootstrapKeyManagerMsg), + RegisterEpochKey(execute::RegisterEpochKeyMsg), JoinComputeNode(execute::JoinComputeNodeMsg), } @@ -19,6 +20,11 @@ pub mod execute { pub tcb_info: String, } + #[cw_serde] + pub struct RegisterEpochKeyMsg { + pub epoch_key: String, + } + #[cw_serde] pub struct JoinComputeNodeMsg { pub io_exchange_key: String, diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index 98acc2b..c618384 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -26,6 +26,12 @@ pub struct SgxState { pub tcb_info: RawTcbInfo, } +#[cw_serde] +pub struct EpochState { + pub epoch_key: RawPublicKey, +} + pub const STATE: Item = Item::new("state"); pub const REQUESTS: Item> = Item::new("requests"); -pub const SGX_STATE: Item = Item::new("sgxstate"); +pub const SGX_STATE: Item = Item::new("sgx_state"); +pub const EPOCH_STATE: Item = Item::new("epoch_state"); From a52ba7626526d3911935869874bef429b1e69de6 Mon Sep 17 00:00:00 2001 From: hu55a1n1 Date: Wed, 10 Jan 2024 07:26:41 -0800 Subject: [PATCH 27/35] Impl get_epoch_state query --- bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs | 10 ++++++++-- bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs | 9 ++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 55f68d4..85d7db4 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -144,6 +144,7 @@ pub mod execute { pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetSgxState {} => to_json_binary(&query::get_sgx_state(deps)?), + QueryMsg::GetEpochState {} => to_json_binary(&query::get_epoch_state(deps)?), QueryMsg::GetRequests {} => to_json_binary(&query::get_requests(deps)?), } } @@ -151,8 +152,8 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { pub mod query { use cosmwasm_std::{Deps, StdResult}; - use crate::msg::query::{GetRequestsResponse, GetSgxStateResponse}; - use crate::state::{SgxState, REQUESTS, SGX_STATE}; + use crate::msg::query::{GetEpochStateResponse, GetRequestsResponse, GetSgxStateResponse}; + use crate::state::{EpochState, SgxState, EPOCH_STATE, REQUESTS, SGX_STATE}; pub fn get_sgx_state(deps: Deps) -> StdResult { let SgxState { @@ -166,6 +167,11 @@ pub mod query { }) } + pub fn get_epoch_state(deps: Deps) -> StdResult { + let EpochState { epoch_key } = EPOCH_STATE.load(deps.storage)?; + Ok(GetEpochStateResponse { epoch_key }) + } + pub fn get_requests(deps: Deps) -> StdResult { Ok(GetRequestsResponse { requests: REQUESTS.load(deps.storage)?, diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index f442b26..701b249 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -38,6 +38,8 @@ pub mod execute { pub enum QueryMsg { #[returns(query::GetSgxStateResponse)] GetSgxState {}, + #[returns(query::GetEpochStateResponse)] + GetEpochState {}, #[returns(query::GetRequestsResponse)] GetRequests {}, } @@ -45,7 +47,7 @@ pub enum QueryMsg { pub mod query { use super::*; - use crate::state::{RawMrenclave, RawNonce, Request}; + use crate::state::{RawMrenclave, RawNonce, RawPublicKey, Request}; #[cw_serde] pub struct GetSgxStateResponse { @@ -53,6 +55,11 @@ pub mod query { pub key_manager_mrenclave: RawMrenclave, } + #[cw_serde] + pub struct GetEpochStateResponse { + pub epoch_key: RawPublicKey, + } + #[cw_serde] pub struct GetRequestsResponse { pub requests: Vec<(RawNonce, Request)>, From b4e5f01cd158896520f9c5dcca8ebd574f5f2ced Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 19 Feb 2024 21:25:46 +0530 Subject: [PATCH 28/35] Quartz base impl (#10) --- .../packages/quartz-cw/.cargo/config | 5 + .../packages/quartz-cw/Cargo.toml | 19 ++ bisenzone-cw-mvp/packages/quartz-cw/README.md | 1 + .../packages/quartz-cw/src/error.rs | 22 ++ .../packages/quartz-cw/src/handler.rs | 208 ++++++++++++++++++ .../packages/quartz-cw/src/handler/execute.rs | 18 ++ .../quartz-cw/src/handler/execute/attested.rs | 49 +++++ .../src/handler/execute/session_create.rs | 17 ++ .../handler/execute/session_set_pub_key.rs | 25 +++ .../quartz-cw/src/handler/instantiate.rs | 27 +++ .../packages/quartz-cw/src/lib.rs | 22 ++ .../packages/quartz-cw/src/msg.rs | 12 + .../packages/quartz-cw/src/msg/execute.rs | 63 ++++++ .../quartz-cw/src/msg/execute/attested.rs | 146 ++++++++++++ .../src/msg/execute/session_create.rs | 51 +++++ .../src/msg/execute/session_set_pub_key.rs | 65 ++++++ .../packages/quartz-cw/src/msg/instantiate.rs | 89 ++++++++ .../packages/quartz-cw/src/msg/query.rs | 1 + .../packages/quartz-cw/src/prelude.rs | 3 + .../packages/quartz-cw/src/state.rs | 52 +++++ .../packages/quartz-tee-ra/Cargo.toml | 15 ++ .../packages/quartz-tee-ra/src/intel_sgx.rs | 13 ++ .../quartz-tee-ra/src/intel_sgx/epid.rs | 17 ++ .../quartz-tee-ra/src/intel_sgx/epid/types.rs | 49 +++++ .../src/intel_sgx/epid/verifier.rs | 172 +++++++++++++++ .../packages/quartz-tee-ra/src/lib.rs | 22 ++ 26 files changed, 1183 insertions(+) create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/.cargo/config create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/README.md create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/error.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-cw/src/state.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs diff --git a/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config b/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config new file mode 100644 index 0000000..76fb435 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +wasm-debug = "build --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" + diff --git a/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml new file mode 100644 index 0000000..f09e59a --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "quartz-cw" +version = "0.1.0" +authors = ["hu55a1n1 "] +edition = "2021" + +[dependencies] +cw-storage-plus = "1.1.0" +cosmwasm-schema = "1.4.0" +cosmwasm-std = "1.4.0" +k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] } +serde = { version = "1.0.188", default-features = false, features = ["derive"] } +sha2 = "0.10.8" +thiserror = "1.0.57" + +quartz-tee-ra = { path = "../../packages/quartz-tee-ra" } + +[dev-dependencies] +serde_json = "1.0.113" diff --git a/bisenzone-cw-mvp/packages/quartz-cw/README.md b/bisenzone-cw-mvp/packages/quartz-cw/README.md new file mode 100644 index 0000000..f4a6bbc --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/README.md @@ -0,0 +1 @@ +# Quartz CosmWasm spec diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs new file mode 100644 index 0000000..228d20d --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs @@ -0,0 +1,22 @@ +use cosmwasm_std::StdError; +use k256::ecdsa::Error as K256Error; +use quartz_tee_ra::Error as RaVerificationError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Std(#[from] StdError), + #[error("{0}")] + RaVerification(#[from] RaVerificationError), + #[error("Not Secp256K1")] + K256(K256Error), + #[error("invalid session nonce or attempt to reset pub_key")] + BadSessionTransition, +} + +impl From for Error { + fn from(e: K256Error) -> Self { + Self::K256(e) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs new file mode 100644 index 0000000..c7acbf8 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs @@ -0,0 +1,208 @@ +pub mod execute; +pub mod instantiate; + +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::error::Error; +use crate::msg::HasDomainType; + +pub trait Handler { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result; +} + +pub trait RawHandler: HasDomainType { + fn handle_raw( + self, + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result; +} + +impl RawHandler for RM +where + RM: HasDomainType, + RM::DomainType: Handler, +{ + fn handle_raw( + self, + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result { + let execute: RM::DomainType = self.try_into()?; + execute.handle(deps, env, info) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::DepsMut; + use serde::Deserialize; + + use crate::handler::Handler; + use crate::msg::{HasDomainType, RawExecuteMsg, RawInstantiateMsg}; + use crate::state::SESSION; + + fn parse_msg<'a, R>(msg_str: &'a str) -> R::DomainType + where + R: HasDomainType + Deserialize<'a>, + { + let raw_msg: R = + serde_json::from_str(msg_str).expect("deserialization failure for hard-coded RawMsg"); + raw_msg.try_into().expect("invalid hard-coded RawMsg") + } + + fn handle_msg<'a, R>(mut deps: DepsMut<'_>, msg_str: &'a str) + where + R: HasDomainType + Deserialize<'a>, + R::DomainType: Handler, + { + let msg = parse_msg::(msg_str); + let info = mock_info("creator", &[]); + let env = mock_env(); + let res = msg + .handle(deps.branch(), &env, &info) + .expect("msg handler failure"); + + assert_eq!(0, res.messages.len()); + } + + fn instantiate(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "msg": { + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ); + } + + fn session_create(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "session_create": { + "msg": { + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c27770000000000000000000000000000000000000000000000000000000000000000" + } + } + }"#, + ); + } + + fn session_set_pub_key(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "session_set_pub_key": { + "msg": { + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + "pub_key": "03E67EF09213633074FB4FBF338643F4F0C574ED60EF11D03422EEB06FA38C8F3F" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c27770000000000000000000000000000000000000000000000000000000000000000" + } + } + }"#, + ); + } + + #[test] + #[ignore] + fn test_instantiate_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + } + + #[test] + #[ignore] + fn test_session_create_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + session_create(deps.as_mut()); + SESSION.load(&deps.storage).expect("Session not created"); + } + + #[test] + #[ignore] + fn test_session_set_pub_key_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + session_create(deps.as_mut()); + session_set_pub_key(deps.as_mut()); + SESSION.load(&deps.storage).expect("Session not created"); + // TODO(hu55a1n1): check that nonce & pub_key match, etc. + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs new file mode 100644 index 0000000..7d6ad47 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs @@ -0,0 +1,18 @@ +pub mod attested; +pub mod session_create; +pub mod session_set_pub_key; + +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::Execute; + +impl Handler for Execute { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { + match self { + Execute::SessionCreate(msg) => msg.handle(deps, env, info), + Execute::SessionSetPubKey(msg) => msg.handle(deps, env, info), + } + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs new file mode 100644 index 0000000..f2dc5e0 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs @@ -0,0 +1,49 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::attested::{Attestation, Attested, EpidAttestation, HasUserData}; +use crate::state::CONFIG; + +impl Handler for EpidAttestation { + fn handle( + self, + _deps: DepsMut<'_>, + _env: &Env, + _info: &MessageInfo, + ) -> Result { + let (report, mr_enclave, user_data) = self.into_tuple(); + verify_epid_attestation(report, mr_enclave, user_data) + .map(|_| Response::default()) + .map_err(Error::RaVerification) + } +} + +impl Handler for Attested +where + M: Handler + HasUserData, + A: Handler + HasUserData + Attestation, +{ + fn handle( + self, + mut deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result { + let (msg, attestation) = self.into_tuple(); + if msg.user_data() != attestation.user_data() { + return Err(RaVerificationError::UserDataMismatch.into()); + } + + if let Some(config) = CONFIG.may_load(deps.storage)? { + // if we weren't able to load then the context was from InstantiateMsg so we don't fail + if *config.mr_enclave() != attestation.mr_enclave() { + return Err(RaVerificationError::MrEnclaveMismatch.into()); + } + } + + Handler::handle(attestation, deps.branch(), env, info)?; + Handler::handle(msg, deps, env, info) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs new file mode 100644 index 0000000..5144734 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs @@ -0,0 +1,17 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::session_create::SessionCreate; +use crate::state::{Session, SESSION}; + +impl Handler for SessionCreate { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + // TODO(hu55a1n1): overwrite previous session? + SESSION + .save(deps.storage, &Session::create(self.into_nonce())) + .map_err(Error::Std)?; + + Ok(Response::new().add_attribute("action", "session_create")) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs new file mode 100644 index 0000000..865dff0 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs @@ -0,0 +1,25 @@ +use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response}; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::session_set_pub_key::SessionSetPubKey; +use crate::state::SESSION; + +impl Handler for SessionSetPubKey { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + let session = SESSION.load(deps.storage).map_err(Error::Std)?; + let (nonce, pub_key) = self.into_tuple(); + let session = session + .with_pub_key(nonce, pub_key) + .ok_or(Error::BadSessionTransition)?; + + SESSION.save(deps.storage, &session).map_err(Error::Std)?; + + Ok(Response::new() + .add_attribute("action", "session_set_pub_key") + .add_attribute( + "pub_key", + HexBinary::from(pub_key.to_sec1_bytes().into_vec()).to_hex(), + )) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs new file mode 100644 index 0000000..ab25d04 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs @@ -0,0 +1,27 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use quartz_tee_ra::Error as RaVerificationError; + +use crate::error::Error; +use crate::handler::Handler; +use crate::msg::execute::attested::{Attestation, EpidAttestation}; +use crate::msg::instantiate::{CoreInstantiate, Instantiate}; +use crate::state::Config; +use crate::state::CONFIG; + +impl Handler for Instantiate { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { + if self.0.msg().mr_enclave() != self.0.attestation().mr_enclave() { + return Err(RaVerificationError::MrEnclaveMismatch.into()); + } + self.0.handle(deps, env, info) + } +} + +impl Handler for CoreInstantiate { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + CONFIG + .save(deps.storage, &Config::new(self.mr_enclave())) + .map_err(Error::Std)?; + Ok(Response::new().add_attribute("action", "instantiate")) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs new file mode 100644 index 0000000..4438a11 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs @@ -0,0 +1,22 @@ +#![warn( + clippy::checked_conversions, + clippy::panic, + clippy::panic_in_result_fn, + clippy::unwrap_used, + rust_2018_idioms, + unused_lifetimes +)] +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + warnings +)] +#![forbid(unsafe_code)] + +pub mod error; +pub mod handler; +pub mod msg; +pub mod prelude; +pub mod state; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs new file mode 100644 index 0000000..8b8864b --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs @@ -0,0 +1,12 @@ +pub mod execute; +pub mod instantiate; +pub mod query; + +pub use execute::{Execute as ExecuteMsg, RawExecute as RawExecuteMsg}; +pub use instantiate::{Instantiate as InstantiateMsg, RawInstantiate as RawInstantiateMsg}; + +use cosmwasm_std::StdError; + +pub trait HasDomainType: From { + type DomainType: TryFrom; +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs new file mode 100644 index 0000000..f21dafc --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs @@ -0,0 +1,63 @@ +pub mod attested; +pub mod session_create; +pub mod session_set_pub_key; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::StdError; + +use crate::msg::execute::attested::{Attested, EpidAttestation}; +use crate::msg::execute::attested::{RawAttested, RawEpidAttestation}; +use crate::msg::execute::session_create::{RawSessionCreate, SessionCreate}; +use crate::msg::execute::session_set_pub_key::{RawSessionSetPubKey, SessionSetPubKey}; +use crate::msg::HasDomainType; + +#[derive(Clone, Debug, PartialEq)] +pub enum Execute { + SessionCreate(Attested), + SessionSetPubKey(Attested), +} + +#[cw_serde] +pub enum RawExecute { + #[serde(rename = "session_create")] + RawSessionCreate(RawAttested), + #[serde(rename = "session_set_pub_key")] + RawSessionSetPubKey(RawAttested), +} + +impl TryFrom> for Execute +where + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawExecute) -> Result { + match value { + RawExecute::RawSessionCreate(msg) => { + Ok(Execute::SessionCreate(TryFrom::try_from(msg)?)) + } + RawExecute::RawSessionSetPubKey(msg) => { + Ok(Execute::SessionSetPubKey(TryFrom::try_from(msg)?)) + } + } + } +} + +impl From> for RawExecute +where + RA: HasDomainType, +{ + fn from(value: Execute) -> Self { + match value { + Execute::SessionCreate(msg) => RawExecute::RawSessionCreate(From::from(msg)), + Execute::SessionSetPubKey(msg) => RawExecute::RawSessionSetPubKey(From::from(msg)), + } + } +} + +impl HasDomainType for RawExecute +where + A: HasDomainType, +{ + type DomainType = Execute; +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs new file mode 100644 index 0000000..1e252fe --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs @@ -0,0 +1,146 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; +use quartz_tee_ra::IASReport; + +use crate::msg::HasDomainType; +use crate::state::{MrEnclave, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Attested { + msg: M, + attestation: A, +} + +impl Attested { + pub fn into_tuple(self) -> (M, A) { + let Attested { msg, attestation } = self; + (msg, attestation) + } + + pub fn msg(&self) -> &M { + &self.msg + } + + pub fn attestation(&self) -> &A { + &self.attestation + } +} + +#[cw_serde] +pub struct RawAttested { + pub msg: RM, + pub attestation: RA, +} + +impl TryFrom> for Attested +where + RM: HasDomainType, + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawAttested) -> Result { + Ok(Self { + msg: value.msg.try_into()?, + attestation: value.attestation.try_into()?, + }) + } +} + +impl From> for RawAttested +where + RM: HasDomainType, + RA: HasDomainType, +{ + fn from(value: Attested) -> Self { + Self { + msg: value.msg.into(), + attestation: value.attestation.into(), + } + } +} + +impl HasDomainType for RawAttested +where + RM: HasDomainType, + RA: HasDomainType, +{ + type DomainType = Attested; +} + +pub trait HasUserData { + fn user_data(&self) -> UserData; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct EpidAttestation { + report: IASReport, + mr_enclave: MrEnclave, + user_data: UserData, +} + +impl EpidAttestation { + pub fn into_tuple(self) -> (IASReport, MrEnclave, UserData) { + let EpidAttestation { + report, + mr_enclave, + user_data, + } = self; + (report, mr_enclave, user_data) + } + + pub fn report(&self) -> &IASReport { + &self.report + } +} + +#[cw_serde] +pub struct RawEpidAttestation { + report: IASReport, + mr_enclave: HexBinary, + user_data: HexBinary, +} + +impl TryFrom for EpidAttestation { + type Error = StdError; + + fn try_from(value: RawEpidAttestation) -> Result { + let mr_enclave = value.mr_enclave.to_array()?; + let user_data = value.user_data.to_array()?; + Ok(Self { + report: value.report, + mr_enclave, + user_data, + }) + } +} + +impl From for RawEpidAttestation { + fn from(value: EpidAttestation) -> Self { + Self { + report: value.report, + mr_enclave: value.mr_enclave.into(), + user_data: value.user_data.into(), + } + } +} + +impl HasDomainType for RawEpidAttestation { + type DomainType = EpidAttestation; +} + +impl HasUserData for EpidAttestation { + fn user_data(&self) -> UserData { + self.user_data + } +} + +pub trait Attestation { + fn mr_enclave(&self) -> MrEnclave; +} + +impl Attestation for EpidAttestation { + fn mr_enclave(&self) -> MrEnclave { + self.report().report.isv_enclave_quote_body.mrenclave() + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs new file mode 100644 index 0000000..d515421 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs @@ -0,0 +1,51 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; + +use crate::msg::execute::attested::HasUserData; +use crate::msg::HasDomainType; +use crate::state::{Nonce, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SessionCreate { + nonce: Nonce, +} + +impl SessionCreate { + pub fn into_nonce(self) -> Nonce { + self.nonce + } +} + +#[cw_serde] +pub struct RawSessionCreate { + nonce: HexBinary, +} + +impl TryFrom for SessionCreate { + type Error = StdError; + + fn try_from(value: RawSessionCreate) -> Result { + let nonce = value.nonce.to_array()?; + Ok(Self { nonce }) + } +} + +impl From for RawSessionCreate { + fn from(value: SessionCreate) -> Self { + Self { + nonce: value.nonce.into(), + } + } +} + +impl HasDomainType for RawSessionCreate { + type DomainType = SessionCreate; +} + +impl HasUserData for SessionCreate { + fn user_data(&self) -> UserData { + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&self.nonce); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs new file mode 100644 index 0000000..9a0db70 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs @@ -0,0 +1,65 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; +use k256::ecdsa::VerifyingKey; +use sha2::{Digest, Sha256}; + +use crate::error::Error; +use crate::msg::execute::attested::HasUserData; +use crate::msg::HasDomainType; +use crate::state::{Nonce, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SessionSetPubKey { + nonce: Nonce, + pub_key: VerifyingKey, +} + +impl SessionSetPubKey { + pub fn into_tuple(self) -> (Nonce, VerifyingKey) { + (self.nonce, self.pub_key) + } +} + +#[cw_serde] +pub struct RawSessionSetPubKey { + nonce: HexBinary, + pub_key: HexBinary, +} + +impl TryFrom for SessionSetPubKey { + type Error = StdError; + + fn try_from(value: RawSessionSetPubKey) -> Result { + let nonce = value.nonce.to_array()?; + let pub_key = VerifyingKey::from_sec1_bytes(&value.pub_key) + .map_err(Error::from) + .map_err(|e| StdError::generic_err(e.to_string()))?; + Ok(Self { nonce, pub_key }) + } +} + +impl From for RawSessionSetPubKey { + fn from(value: SessionSetPubKey) -> Self { + Self { + nonce: value.nonce.into(), + pub_key: value.pub_key.to_sec1_bytes().into_vec().into(), + } + } +} + +impl HasDomainType for RawSessionSetPubKey { + type DomainType = SessionSetPubKey; +} + +impl HasUserData for SessionSetPubKey { + fn user_data(&self) -> UserData { + let mut hasher = Sha256::new(); + hasher.update(self.nonce); + hasher.update(self.pub_key.to_sec1_bytes()); + let digest: [u8; 32] = hasher.finalize().into(); + + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&digest); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs new file mode 100644 index 0000000..03659c7 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs @@ -0,0 +1,89 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; +use sha2::{Digest, Sha256}; + +use crate::msg::execute::attested::{ + Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, +}; +use crate::msg::HasDomainType; +use crate::state::{MrEnclave, UserData}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Instantiate(pub(crate) Attested); + +#[cw_serde] +pub struct RawInstantiate(RawAttested); + +impl TryFrom> for Instantiate +where + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawInstantiate) -> Result { + Ok(Self(TryFrom::try_from(value.0)?)) + } +} + +impl From> for RawInstantiate +where + RA: HasDomainType, +{ + fn from(value: Instantiate) -> Self { + Self(From::from(value.0)) + } +} + +impl HasDomainType for RawInstantiate { + type DomainType = Instantiate; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CoreInstantiate { + mr_enclave: MrEnclave, + // TODO(hu55a1n1): config - e.g. Epoch duration, light client opts +} + +impl CoreInstantiate { + pub fn mr_enclave(&self) -> MrEnclave { + self.mr_enclave + } +} + +#[cw_serde] +pub struct RawCoreInstantiate { + mr_enclave: HexBinary, +} + +impl TryFrom for CoreInstantiate { + type Error = StdError; + + fn try_from(value: RawCoreInstantiate) -> Result { + let mr_enclave = value.mr_enclave.to_array()?; + Ok(Self { mr_enclave }) + } +} + +impl From for RawCoreInstantiate { + fn from(value: CoreInstantiate) -> Self { + Self { + mr_enclave: value.mr_enclave.into(), + } + } +} + +impl HasDomainType for RawCoreInstantiate { + type DomainType = CoreInstantiate; +} + +impl HasUserData for CoreInstantiate { + fn user_data(&self) -> UserData { + let mut hasher = Sha256::new(); + hasher.update(self.mr_enclave); + let digest: [u8; 32] = hasher.finalize().into(); + + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&digest); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs @@ -0,0 +1 @@ + diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs new file mode 100644 index 0000000..b998726 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs @@ -0,0 +1,3 @@ +pub use crate::handler::RawHandler; +pub use crate::msg::execute::RawExecute as QuartzExecuteMsg; +pub use crate::msg::instantiate::RawInstantiate as QuartzInstantiateMsg; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs new file mode 100644 index 0000000..5e3cb06 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs @@ -0,0 +1,52 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::HexBinary; +use cw_storage_plus::Item; +use k256::ecdsa::VerifyingKey; + +pub type MrEnclave = [u8; 32]; +pub type Nonce = [u8; 32]; +pub type UserData = [u8; 64]; + +#[cw_serde] +pub struct Config { + mr_enclave: HexBinary, +} + +impl Config { + pub fn new(mr_enclave: MrEnclave) -> Self { + Self { + mr_enclave: mr_enclave.into(), + } + } + + pub fn mr_enclave(&self) -> &HexBinary { + &self.mr_enclave + } +} + +#[cw_serde] +pub struct Session { + nonce: HexBinary, + pub_key: Option, +} + +impl Session { + pub fn create(nonce: Nonce) -> Self { + Self { + nonce: nonce.into(), + pub_key: None, + } + } + + pub fn with_pub_key(mut self, nonce: Nonce, pub_key: VerifyingKey) -> Option { + if self.nonce == nonce && self.pub_key.is_none() { + self.pub_key = Some(pub_key.to_sec1_bytes().into_vec().into()); + Some(self) + } else { + None + } + } +} + +pub const CONFIG: Item<'_, Config> = Item::new("quartz_config"); +pub const SESSION: Item<'_, Session> = Item::new("quartz_session"); diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml new file mode 100644 index 0000000..e0160db --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "quartz-tee-ra" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-schema = "1.4.0" +cosmwasm-std = "1.4.0" +hex-literal = "0.4.1" +num-bigint = "0.4.4" +serde_json = "1.0.108" +sha2 = "0.10.8" +thiserror = { version = "1.0.49" } diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs new file mode 100644 index 0000000..e5cefa4 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs @@ -0,0 +1,13 @@ +use thiserror::Error; + +pub mod epid; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Specified user data does not match the report")] + UserDataMismatch, + #[error("Specified MRENCLAVE does not match the report")] + MrEnclaveMismatch, + #[error("EPID specific error: {0}")] + Epid(#[from] epid::Error), +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs new file mode 100644 index 0000000..f1d11f4 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs @@ -0,0 +1,17 @@ +use hex_literal::hex; +use thiserror::Error; + +pub const INTEL_ROOT_MODULUS: &[u8] = &hex!("a97a2de0e66ea6147c9ee745ac0162686c7192099afc4b3f040fad6de093511d74e802f510d716038157dcaf84f4104bd3fed7e6b8f99c8817fd1ff5b9b864296c3d81fa8f1b729e02d21d72ffee4ced725efe74bea68fbc4d4244286fcdd4bf64406a439a15bcb4cf67754489c423972b4a80df5c2e7c5bc2dbaf2d42bb7b244f7c95bf92c75d3b33fc5410678a89589d1083da3acc459f2704cd99598c275e7c1878e00757e5bdb4e840226c11c0a17ff79c80b15c1ddb5af21cc2417061fbd2a2da819ed3b72b7efaa3bfebe2805c9b8ac19aa346512d484cfc81941e15f55881cc127e8f7aa12300cd5afb5742fa1d20cb467a5beb1c666cf76a368978b5"); + +pub const INTEL_ROOT_EXPONENT: &[u8] = + &hex!("0000000000000000000000000000000000000000000000000000000000010001"); + +pub mod types; + +pub mod verifier; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Recovered digest from signature does not match the specified report")] + RecoveredDigestMismatch, +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs new file mode 100644 index 0000000..b80a100 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs @@ -0,0 +1,49 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Binary; + +#[cw_serde] +pub struct IASReport { + pub report: ReportBody, + #[serde(rename = "reportsig")] + pub report_sig: Binary, +} + +#[cw_serde] +pub struct ReportBody { + pub id: String, + pub timestamp: String, + pub version: u64, + #[serde(rename = "epidPseudonym")] + pub epid_pseudonym: Binary, + #[serde(rename = "advisoryURL")] + pub advisory_url: String, + #[serde(rename = "advisoryIDs")] + pub advisory_ids: Vec, + #[serde(rename = "isvEnclaveQuoteStatus")] + pub isv_enclave_quote_status: String, + #[serde(rename = "platformInfoBlob")] + pub platform_info_blob: String, + #[serde(rename = "isvEnclaveQuoteBody")] + pub isv_enclave_quote_body: IsvEnclaveQuoteBody, +} + +#[cw_serde] +#[serde(transparent)] +pub struct IsvEnclaveQuoteBody(Binary); + +impl IsvEnclaveQuoteBody { + pub fn mrenclave(&self) -> [u8; 32] { + Self::array_chunk(self.0.as_slice(), 112) + } + + pub fn user_data(&self) -> [u8; 64] { + Self::array_chunk(self.0.as_slice(), 368) + } + + fn array_chunk(quote_body: &[u8], offset: usize) -> [u8; N] { + assert!(offset + N <= quote_body.len()); + quote_body[offset..offset + N] + .try_into() + .expect("array length mismatch") + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs new file mode 100644 index 0000000..2d09248 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs @@ -0,0 +1,172 @@ +use cosmwasm_std::ensure_eq; +use num_bigint::BigUint; +use sha2::{Digest, Sha256}; + +use crate::intel_sgx::epid::types::IASReport; +use crate::intel_sgx::epid::Error as EpidError; +use crate::intel_sgx::epid::{INTEL_ROOT_EXPONENT, INTEL_ROOT_MODULUS}; +use crate::intel_sgx::Error; + +/// Given an RSA signature and the signer's exponent + modulus we recover the digest that was signed by the signature. +pub fn recover_signature_digest(signature: &[u8], exponent: &[u8], modulus: &[u8]) -> Vec { + let sig_as_bignum_be = BigUint::from_bytes_be(signature); + let intel_modulus_be = BigUint::from_bytes_be(modulus); + let intel_exponent_be = BigUint::from_bytes_be(exponent); + + let digest_be = sig_as_bignum_be.modpow(&intel_exponent_be, &intel_modulus_be); + + // last 32 bytes contain the digest + let digest_bytes = digest_be.to_bytes_be(); + let n = digest_bytes.len(); + digest_bytes[n - 32..n].to_vec() +} + +pub fn verify( + ias_report: IASReport, + mrenclave: impl AsRef<[u8]>, + user_data: impl AsRef<[u8]>, +) -> Result<(), Error> { + // Extract the payload from the quote body + let user_data_in_quote = ias_report.report.isv_enclave_quote_body.user_data(); + + // check user_report_data + ensure_eq!( + user_data_in_quote, + user_data.as_ref(), + Error::UserDataMismatch + ); + + // Extract the mrenclave from the quote body + let mrenclave_in_quote = ias_report.report.isv_enclave_quote_body.mrenclave(); + + // check mrenclave + ensure_eq!( + mrenclave_in_quote, + mrenclave.as_ref(), + Error::MrEnclaveMismatch + ); + + // Recover the RSA signature's digest + let recovered_digest = recover_signature_digest( + ias_report.report_sig.as_slice(), + INTEL_ROOT_EXPONENT, + INTEL_ROOT_MODULUS, + ); + // Convert the recovered digest into a byte slice + let recovered_digest = recovered_digest.as_slice(); + + // Convert the ias report as a json string, removing all the backslashes to escape stuff + let ias_report_asjson = serde_json::to_string(&ias_report.report) + .expect("infallible serializer for IASReport") + .replace('\\', ""); + // Convert the ias report without the back slashes to bytes + let ias_report_asbytes = ias_report_asjson.as_bytes(); + // We are going to calculate our own digest of the ias report + let mut hasher = Sha256::default(); + // Update the hasher contents with that of the ias report json + hasher.update(ias_report_asbytes); + // finalize the sha256 hasher so that it produces a byte slice of the digest, should be 32 bytes + let ias_report_digest = &hasher.finalize()[..]; + + // ensure that the recovered digest from the signature matches the digest of the report + ensure_eq!( + ias_report_digest, + recovered_digest, + EpidError::RecoveredDigestMismatch + ); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::HexBinary; + + use super::*; + + #[cw_serde] + pub enum QueryMsg { + /// Verify an attestation + VerifyEpidAttestation { + // The report that is generated by an enclave + report: IASReport, + // The MRENCLAVE of this enclave (as hex string) + mrenclave: HexBinary, + // User data - whose commitment is in the `user_report_data` (as hex string) + user_data: HexBinary, + }, + } + + fn parse_attestation(query_verifier: &str) -> (IASReport, HexBinary, HexBinary) { + let query_verifier: QueryMsg = + serde_json::from_str(query_verifier).expect("deserialize query"); + match query_verifier { + QueryMsg::VerifyEpidAttestation { + report, + mrenclave, + user_data, + } => (report, mrenclave, user_data), + } + } + + fn verify_attestation(query_verifier: &str) { + let (report, mrenclave, user_data) = parse_attestation(query_verifier); + verify(report, mrenclave, user_data).expect("RA verification failure"); + } + + #[test] + fn test_verifier_ok() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_mrenclave() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "f3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_user_data() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_platform_info_blob() { + verify_attestation( + r#"{ + "verify_epid_attestation": { + "report": {"report":{"id":"5246688123689513540899231107533660789","timestamp":"2024-02-07T17:06:23.913745","version":4,"epidPseudonym":"+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=","advisoryURL":"https://security-center.intel.com","advisoryIDs":["INTEL-SA-00161","INTEL-SA-00219","INTEL-SA-00289","INTEL-SA-00334","INTEL-SA-00615"],"isvEnclaveQuoteStatus":"CONFIGURATION_AND_SW_HARDENING_NEEDED","platformInfoBlob":"150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1","isvEnclaveQuoteBody":"AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"},"reportsig":"YaY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs new file mode 100644 index 0000000..908a4cd --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs @@ -0,0 +1,22 @@ +#![warn( + clippy::checked_conversions, + clippy::panic, + clippy::panic_in_result_fn, + clippy::unwrap_used, + rust_2018_idioms, + unused_lifetimes +)] +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + warnings +)] +#![forbid(unsafe_code)] + +pub mod intel_sgx; + +pub use intel_sgx::epid::types::IASReport; +pub use intel_sgx::epid::verifier::verify as verify_epid_attestation; +pub use intel_sgx::Error; From 57b2a050ff394186080bb355c52e170fc80bebf3 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 20 Mar 2024 02:49:38 +0530 Subject: [PATCH 29/35] Adapt cw-tee-mtcs to use quartz (#12) --- .../contracts/cw-tee-mtcs/Cargo.toml | 3 + .../contracts/cw-tee-mtcs/src/contract.rs | 181 +++++----------- .../contracts/cw-tee-mtcs/src/error.rs | 17 +- .../contracts/cw-tee-mtcs/src/msg.rs | 149 ++++++++++---- .../contracts/cw-tee-mtcs/src/state.rs | 40 ++-- .../packages/quartz-cw/Cargo.toml | 1 + .../packages/quartz-cw/src/handler/execute.rs | 7 +- .../quartz-cw/src/handler/execute/attested.rs | 38 +++- .../quartz-cw/src/handler/instantiate.rs | 17 +- .../packages/quartz-cw/src/msg/execute.rs | 6 +- .../quartz-cw/src/msg/execute/attested.rs | 69 +++++-- .../src/msg/execute/session_create.rs | 4 + .../src/msg/execute/session_set_pub_key.rs | 4 + .../packages/quartz-cw/src/msg/instantiate.rs | 38 ++-- .../packages/quartz-cw/src/state.rs | 194 +++++++++++++++++- bisenzone-cw-mvp/scripts/deploy-contract.sh | 3 +- 16 files changed, 507 insertions(+), 264 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml index 73c5a16..0e33e50 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -54,5 +54,8 @@ schemars = "0.8.15" serde = { version = "1.0.189", default-features = false, features = ["derive"] } thiserror = { version = "1.0.49" } +quartz-cw = { path = "../../packages/quartz-cw" } + [dev-dependencies] cw-multi-test = "0.17.0" +serde_json = "1.0.113" diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 85d7db4..46b9ba8 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -1,12 +1,12 @@ -use cosmwasm_std::{ - entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; +use quartz_cw::handler::RawHandler; use crate::error::ContractError; -use crate::msg::execute::{BootstrapKeyManagerMsg, JoinComputeNodeMsg, RegisterEpochKeyMsg}; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{State, STATE}; +use crate::msg::execute::{SubmitObligationMsg, SubmitSetoffsMsg}; +use crate::msg::QueryMsg; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use crate::state::{current_epoch_key, ObligationsItem, State, OBLIGATIONS_KEY, STATE}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw-tee-mtcs"; @@ -14,17 +14,23 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, - _env: Env, + mut deps: DepsMut, + env: Env, info: MessageInfo, - _msg: InstantiateMsg, + msg: InstantiateMsg, ) -> Result { + // must be the handled first! + msg.0.handle_raw(deps.branch(), &env, &info)?; + let state = State { owner: info.sender.to_string(), }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; STATE.save(deps.storage, &state)?; + ObligationsItem::new(¤t_epoch_key(OBLIGATIONS_KEY, deps.storage)?) + .save(deps.storage, &Default::default())?; + Ok(Response::new() .add_attribute("method", "instantiate") .add_attribute("owner", info.sender)) @@ -33,151 +39,70 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, - _info: MessageInfo, + env: Env, + info: MessageInfo, msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::BootstrapKeyManager(BootstrapKeyManagerMsg { - compute_mrenclave, - key_manager_mrenclave, - tcb_info, - }) => { - execute::bootstrap_key_manger(deps, compute_mrenclave, key_manager_mrenclave, tcb_info) + ExecuteMsg::Quartz(msg) => msg.handle_raw(deps, &env, &info).map_err(Into::into), + ExecuteMsg::SubmitObligation(SubmitObligationMsg { ciphertext, digest }) => { + execute::submit_obligation(deps, ciphertext, digest) } - ExecuteMsg::RegisterEpochKey(RegisterEpochKeyMsg { epoch_key }) => { - execute::register_epoch_key(deps, epoch_key) + ExecuteMsg::SubmitSetoffs(SubmitSetoffsMsg { setoffs_enc }) => { + execute::submit_setoffs(deps, setoffs_enc) } - ExecuteMsg::JoinComputeNode(JoinComputeNodeMsg { - io_exchange_key, - address, - nonce, - }) => execute::enqueue_join_request(deps, io_exchange_key, address, nonce), } } pub mod execute { - use cosmwasm_std::{DepsMut, Response}; - use k256::ecdsa::VerifyingKey; + use std::collections::BTreeMap; + + use cosmwasm_std::{DepsMut, HexBinary, Response}; + use quartz_cw::state::Hash; use crate::state::{ - EpochState, Mrenclave, RawAddress, RawMrenclave, RawNonce, RawPublicKey, RawTcbInfo, - SgxState, EPOCH_STATE, SGX_STATE, + current_epoch_key, ObligationsItem, RawCipherText, RawHash, SetoffsItem, OBLIGATIONS_KEY, + SETOFFS_KEY, }; - use crate::state::{Request, REQUESTS}; use crate::ContractError; - use crate::ContractError::BadLength; - pub fn bootstrap_key_manger( + pub fn submit_obligation( deps: DepsMut, - compute_mrenclave: RawMrenclave, - key_manager_mrenclave: RawMrenclave, - tcb_info: RawTcbInfo, + ciphertext: HexBinary, + digest: HexBinary, ) -> Result { - let _: Mrenclave = hex::decode(&compute_mrenclave)? - .try_into() - .map_err(|_| BadLength)?; - let _: Mrenclave = hex::decode(&key_manager_mrenclave)? - .try_into() - .map_err(|_| BadLength)?; - // TODO(hu55a1n1): validate TcbInfo + let _: Hash = digest.to_array()?; - let sgx_state = SgxState { - compute_mrenclave: compute_mrenclave.clone(), - key_manager_mrenclave: key_manager_mrenclave.clone(), - tcb_info: tcb_info.clone(), - }; - - if SGX_STATE.exists(deps.storage) { - return Err(ContractError::Unauthorized); - } - - SGX_STATE.save(deps.storage, &sgx_state)?; + // store the `(digest, ciphertext)` tuple + ObligationsItem::new(¤t_epoch_key(OBLIGATIONS_KEY, deps.storage)?).update( + deps.storage, + |mut obligations| { + if let Some(_duplicate) = obligations.insert(digest.clone(), ciphertext.clone()) { + return Err(ContractError::DuplicateEntry); + } + Ok(obligations) + }, + )?; Ok(Response::new() - .add_attribute("action", "bootstrap_key_manger") - .add_attribute("compute_mrenclave", compute_mrenclave) - .add_attribute("key_manager_mrenclave", key_manager_mrenclave) - .add_attribute("tcb_info", tcb_info)) + .add_attribute("action", "submit_obligation") + .add_attribute("digest", digest.to_string()) + .add_attribute("ciphertext", ciphertext.to_string())) } - pub fn register_epoch_key( + pub fn submit_setoffs( deps: DepsMut, - epoch_key: RawPublicKey, + setoffs_enc: BTreeMap, ) -> Result { - let _ = VerifyingKey::from_sec1_bytes(&hex::decode(&epoch_key)?)?; + // store the `BTreeMap` + SetoffsItem::new(¤t_epoch_key(SETOFFS_KEY, deps.storage)?) + .save(deps.storage, &setoffs_enc)?; - let epoch_state = EpochState { - epoch_key: epoch_key.clone(), - }; - EPOCH_STATE.save(deps.storage, &epoch_state)?; - - Ok(Response::new() - .add_attribute("action", "register_epoch_key") - .add_attribute("epoch_key", epoch_key)) - } - - pub fn enqueue_join_request( - deps: DepsMut, - io_exchange_key: RawPublicKey, - address: RawAddress, - nonce: RawNonce, - ) -> Result { - let _ = VerifyingKey::from_sec1_bytes(&hex::decode(&io_exchange_key)?)?; - let _ = deps.api.addr_validate(&address)?; - let _ = hex::decode(&nonce); - - let mut requests = REQUESTS.may_load(deps.storage)?.unwrap_or_default(); - requests.push(( - nonce, - Request::JoinComputeNode((io_exchange_key.clone(), address)), - )); - REQUESTS.save(deps.storage, &requests)?; - - Ok(Response::new() - .add_attribute("action", "enqueue_request") - .add_attribute("io_exchange_key", io_exchange_key)) + Ok(Response::new().add_attribute("action", "submit_setoffs")) } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetSgxState {} => to_json_binary(&query::get_sgx_state(deps)?), - QueryMsg::GetEpochState {} => to_json_binary(&query::get_epoch_state(deps)?), - QueryMsg::GetRequests {} => to_json_binary(&query::get_requests(deps)?), - } +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg {} } - -pub mod query { - use cosmwasm_std::{Deps, StdResult}; - - use crate::msg::query::{GetEpochStateResponse, GetRequestsResponse, GetSgxStateResponse}; - use crate::state::{EpochState, SgxState, EPOCH_STATE, REQUESTS, SGX_STATE}; - - pub fn get_sgx_state(deps: Deps) -> StdResult { - let SgxState { - compute_mrenclave, - key_manager_mrenclave, - .. - } = SGX_STATE.load(deps.storage)?; - Ok(GetSgxStateResponse { - compute_mrenclave, - key_manager_mrenclave, - }) - } - - pub fn get_epoch_state(deps: Deps) -> StdResult { - let EpochState { epoch_key } = EPOCH_STATE.load(deps.storage)?; - Ok(GetEpochStateResponse { epoch_key }) - } - - pub fn get_requests(deps: Deps) -> StdResult { - Ok(GetRequestsResponse { - requests: REQUESTS.load(deps.storage)?, - }) - } -} - -#[cfg(test)] -mod tests {} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs index f482c7b..9683c23 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -1,6 +1,7 @@ use cosmwasm_std::StdError; use hex::FromHexError; use k256::ecdsa::Error as K256Error; +use quartz_cw::error::Error as QuartzError; use thiserror::Error; #[derive(Error, Debug)] @@ -8,14 +9,20 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + Quartz(#[from] QuartzError), + #[error("Unauthorized")] Unauthorized, + #[error("Duplicate entry found")] + DuplicateEntry, + #[error("Not Secp256K1")] K256(K256Error), #[error("Invalid hex")] - Hex(FromHexError), + Hex(#[from] FromHexError), #[error("Invalid length")] BadLength, @@ -23,12 +30,6 @@ pub enum ContractError { impl From for ContractError { fn from(e: K256Error) -> Self { - ContractError::K256(e) - } -} - -impl From for ContractError { - fn from(e: FromHexError) -> Self { - ContractError::Hex(e) + Self::K256(e) } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index 701b249..fff496e 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -1,67 +1,134 @@ +use std::collections::BTreeMap; + use cosmwasm_schema::{cw_serde, QueryResponses}; +use quartz_cw::prelude::*; + +use crate::state::{RawCipherText, RawHash}; #[cw_serde] -pub struct InstantiateMsg; +#[serde(transparent)] +pub struct InstantiateMsg(pub QuartzInstantiateMsg); #[cw_serde] +#[allow(clippy::large_enum_variant)] pub enum ExecuteMsg { - BootstrapKeyManager(execute::BootstrapKeyManagerMsg), - RegisterEpochKey(execute::RegisterEpochKeyMsg), - JoinComputeNode(execute::JoinComputeNodeMsg), + Quartz(QuartzExecuteMsg), + SubmitObligation(execute::SubmitObligationMsg), + SubmitSetoffs(execute::SubmitSetoffsMsg), } pub mod execute { + use cosmwasm_std::HexBinary; + use super::*; #[cw_serde] - pub struct BootstrapKeyManagerMsg { - pub compute_mrenclave: String, - pub key_manager_mrenclave: String, - pub tcb_info: String, + pub struct SubmitObligationMsg { + pub ciphertext: HexBinary, + pub digest: HexBinary, + // pub signatures: [HexBinary; 2], + // pub proof: π } #[cw_serde] - pub struct RegisterEpochKeyMsg { - pub epoch_key: String, - } - - #[cw_serde] - pub struct JoinComputeNodeMsg { - pub io_exchange_key: String, - pub address: String, - pub nonce: String, + pub struct SubmitSetoffsMsg { + pub setoffs_enc: BTreeMap, + // pub proof: π, } } - #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(query::GetSgxStateResponse)] - GetSgxState {}, - #[returns(query::GetEpochStateResponse)] - GetEpochState {}, - #[returns(query::GetRequestsResponse)] - GetRequests {}, -} +pub enum QueryMsg {} -pub mod query { +#[cfg(test)] +mod tests { use super::*; - use crate::state::{RawMrenclave, RawNonce, RawPublicKey, Request}; - - #[cw_serde] - pub struct GetSgxStateResponse { - pub compute_mrenclave: RawMrenclave, - pub key_manager_mrenclave: RawMrenclave, + #[test] + fn test_serde_instantiate_msg() { + let _: InstantiateMsg = serde_json::from_str( + r#"{ + "msg": { + "config": { + "mr_enclave": "1bfb949d235f61e5dc40f874ba3e9c36adef1e7a521b4b5f70e10fb1dc803251", + "epoch_duration": { + "secs": 43200, + "nanos": 0 + }, + "light_client_opts": { + "chain_id": "testing", + "trusted_height": 1, + "trusted_hash": "a1d115ba3a5e9fcc12ed68a9d8669159e9085f6f96ec26619f5c7ceb4ee02869", + "trust_threshold": [ + 2, + 3 + ], + "trusting_period": 1209600, + "max_clock_drift": 5, + "max_block_lag": 5 + } + } + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + } + } + }"#, + ).expect("failed to deserialize hardcoded quartz instantiate msg"); } - #[cw_serde] - pub struct GetEpochStateResponse { - pub epoch_key: RawPublicKey, - } - - #[cw_serde] - pub struct GetRequestsResponse { - pub requests: Vec<(RawNonce, Request)>, + #[test] + fn test_serde_execute_msg() { + let _: ExecuteMsg = serde_json::from_str( + r#"{ + "quartz": { + "session_create": { + "msg": { + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + } + } + } + } + }"#, + ).expect("failed to deserialize hardcoded quartz msg"); } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index c618384..f49ac64 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,37 +1,25 @@ +use std::collections::BTreeMap; + use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError, Storage}; use cw_storage_plus::Item; +use quartz_cw::state::EPOCH_COUNTER; -pub type RawNonce = String; -pub type RawPublicKey = String; -pub type RawAddress = String; -pub type RawMrenclave = String; -pub type RawTcbInfo = String; +pub type RawHash = HexBinary; +pub type RawCipherText = HexBinary; -pub type Mrenclave = [u8; 32]; +pub type ObligationsItem<'a> = Item<'a, BTreeMap>; +pub type SetoffsItem<'a> = Item<'a, BTreeMap>; #[cw_serde] pub struct State { pub owner: String, } -#[cw_serde] -pub enum Request { - JoinComputeNode((RawPublicKey, RawAddress)), -} - -#[cw_serde] -pub struct SgxState { - pub compute_mrenclave: RawMrenclave, - pub key_manager_mrenclave: RawMrenclave, - pub tcb_info: RawTcbInfo, -} - -#[cw_serde] -pub struct EpochState { - pub epoch_key: RawPublicKey, -} - pub const STATE: Item = Item::new("state"); -pub const REQUESTS: Item> = Item::new("requests"); -pub const SGX_STATE: Item = Item::new("sgx_state"); -pub const EPOCH_STATE: Item = Item::new("epoch_state"); +pub const OBLIGATIONS_KEY: &str = "obligations"; +pub const SETOFFS_KEY: &str = "setoffs"; + +pub fn current_epoch_key(key: &str, storage: &dyn Storage) -> Result { + Ok(format!("{}/{key}", EPOCH_COUNTER.load(storage)?)) +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml index f09e59a..b1b0459 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml +++ b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml @@ -10,6 +10,7 @@ cosmwasm-schema = "1.4.0" cosmwasm-std = "1.4.0" k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] } serde = { version = "1.0.188", default-features = false, features = ["derive"] } +serde_json = "1.0.94" sha2 = "0.10.8" thiserror = "1.0.57" diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs index 7d6ad47..b0d4d99 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs @@ -6,9 +6,14 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use crate::error::Error; use crate::handler::Handler; +use crate::msg::execute::attested::Attestation; +use crate::msg::execute::attested::HasUserData; use crate::msg::execute::Execute; -impl Handler for Execute { +impl Handler for Execute +where + A: Handler + HasUserData + Attestation, +{ fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { match self { Execute::SessionCreate(msg) => msg.handle(deps, env, info), diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs index f2dc5e0..3da23da 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs @@ -3,7 +3,9 @@ use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError}; use crate::error::Error; use crate::handler::Handler; -use crate::msg::execute::attested::{Attestation, Attested, EpidAttestation, HasUserData}; +use crate::msg::execute::attested::{ + Attestation, Attested, EpidAttestation, HasUserData, MockAttestation, +}; use crate::state::CONFIG; impl Handler for EpidAttestation { @@ -13,10 +15,25 @@ impl Handler for EpidAttestation { _env: &Env, _info: &MessageInfo, ) -> Result { - let (report, mr_enclave, user_data) = self.into_tuple(); - verify_epid_attestation(report, mr_enclave, user_data) - .map(|_| Response::default()) - .map_err(Error::RaVerification) + // attestation handler MUST verify that the user_data and mr_enclave match the config/msg + verify_epid_attestation( + self.clone().into_report(), + self.mr_enclave(), + self.user_data(), + ) + .map(|_| Response::default()) + .map_err(Error::RaVerification) + } +} + +impl Handler for MockAttestation { + fn handle( + self, + _deps: DepsMut<'_>, + _env: &Env, + _info: &MessageInfo, + ) -> Result { + Ok(Response::default()) } } @@ -38,12 +55,17 @@ where if let Some(config) = CONFIG.may_load(deps.storage)? { // if we weren't able to load then the context was from InstantiateMsg so we don't fail - if *config.mr_enclave() != attestation.mr_enclave() { + // in such cases, the InstantiateMsg handler will verify that the mr_enclave matches + if config.mr_enclave() != attestation.mr_enclave() { return Err(RaVerificationError::MrEnclaveMismatch.into()); } } - Handler::handle(attestation, deps.branch(), env, info)?; - Handler::handle(msg, deps, env, info) + // handle message first, this has 2 benefits - + // 1. we avoid (the more expensive) attestation verification if the message handler fails + // 2. we allow the message handler to make changes to the config so that the attestation + // handler can use those changes, e.g. InstantiateMsg + Handler::handle(msg, deps.branch(), env, info)?; + Handler::handle(attestation, deps, env, info) } } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs index ab25d04..e59419b 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs @@ -3,25 +3,34 @@ use quartz_tee_ra::Error as RaVerificationError; use crate::error::Error; use crate::handler::Handler; -use crate::msg::execute::attested::{Attestation, EpidAttestation}; +use crate::msg::execute::attested::{Attestation, EpidAttestation, MockAttestation}; use crate::msg::instantiate::{CoreInstantiate, Instantiate}; -use crate::state::Config; use crate::state::CONFIG; +use crate::state::{RawConfig, EPOCH_COUNTER}; impl Handler for Instantiate { fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { - if self.0.msg().mr_enclave() != self.0.attestation().mr_enclave() { + if self.0.msg().config().mr_enclave() != self.0.attestation().mr_enclave() { return Err(RaVerificationError::MrEnclaveMismatch.into()); } self.0.handle(deps, env, info) } } +impl Handler for Instantiate { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { + self.0.handle(deps, env, info) + } +} + impl Handler for CoreInstantiate { fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { CONFIG - .save(deps.storage, &Config::new(self.mr_enclave())) + .save(deps.storage, &RawConfig::from(self.config().clone())) .map_err(Error::Std)?; + + EPOCH_COUNTER.save(deps.storage, &1).map_err(Error::Std)?; + Ok(Response::new().add_attribute("action", "instantiate")) } } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs index f21dafc..f6f1ed1 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs @@ -55,9 +55,9 @@ where } } -impl HasDomainType for RawExecute +impl HasDomainType for RawExecute where - A: HasDomainType, + RA: HasDomainType, { - type DomainType = Execute; + type DomainType = Execute; } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs index 1e252fe..31de243 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, StdError}; +use cosmwasm_std::StdError; use quartz_tee_ra::IASReport; use crate::msg::HasDomainType; @@ -12,6 +12,10 @@ pub struct Attested { } impl Attested { + pub fn new(msg: M, attestation: A) -> Self { + Self { msg, attestation } + } + pub fn into_tuple(self) -> (M, A) { let Attested { msg, attestation } = self; (msg, attestation) @@ -75,42 +79,29 @@ pub trait HasUserData { #[derive(Clone, Debug, PartialEq)] pub struct EpidAttestation { report: IASReport, - mr_enclave: MrEnclave, - user_data: UserData, } impl EpidAttestation { - pub fn into_tuple(self) -> (IASReport, MrEnclave, UserData) { - let EpidAttestation { - report, - mr_enclave, - user_data, - } = self; - (report, mr_enclave, user_data) + pub fn new(report: IASReport) -> Self { + Self { report } } - pub fn report(&self) -> &IASReport { - &self.report + pub fn into_report(self) -> IASReport { + self.report } } #[cw_serde] pub struct RawEpidAttestation { report: IASReport, - mr_enclave: HexBinary, - user_data: HexBinary, } impl TryFrom for EpidAttestation { type Error = StdError; fn try_from(value: RawEpidAttestation) -> Result { - let mr_enclave = value.mr_enclave.to_array()?; - let user_data = value.user_data.to_array()?; Ok(Self { report: value.report, - mr_enclave, - user_data, }) } } @@ -119,8 +110,6 @@ impl From for RawEpidAttestation { fn from(value: EpidAttestation) -> Self { Self { report: value.report, - mr_enclave: value.mr_enclave.into(), - user_data: value.user_data.into(), } } } @@ -131,7 +120,7 @@ impl HasDomainType for RawEpidAttestation { impl HasUserData for EpidAttestation { fn user_data(&self) -> UserData { - self.user_data + self.report.report.isv_enclave_quote_body.user_data() } } @@ -141,6 +130,42 @@ pub trait Attestation { impl Attestation for EpidAttestation { fn mr_enclave(&self) -> MrEnclave { - self.report().report.isv_enclave_quote_body.mrenclave() + self.report.report.isv_enclave_quote_body.mrenclave() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MockAttestation; + +#[cw_serde] +pub struct RawMockAttestation; + +impl TryFrom for MockAttestation { + type Error = StdError; + + fn try_from(_value: RawMockAttestation) -> Result { + Ok(Self) + } +} + +impl From for RawMockAttestation { + fn from(_value: MockAttestation) -> Self { + Self + } +} + +impl HasDomainType for RawMockAttestation { + type DomainType = MockAttestation; +} + +impl HasUserData for MockAttestation { + fn user_data(&self) -> UserData { + unimplemented!("MockAttestation handler is a noop") + } +} + +impl Attestation for MockAttestation { + fn mr_enclave(&self) -> MrEnclave { + unimplemented!("MockAttestation handler is a noop") } } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs index d515421..7603555 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs @@ -11,6 +11,10 @@ pub struct SessionCreate { } impl SessionCreate { + pub fn new(nonce: Nonce) -> Self { + Self { nonce } + } + pub fn into_nonce(self) -> Nonce { self.nonce } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs index 9a0db70..47b3c34 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs @@ -15,6 +15,10 @@ pub struct SessionSetPubKey { } impl SessionSetPubKey { + pub fn new(nonce: Nonce, pub_key: VerifyingKey) -> Self { + Self { nonce, pub_key } + } + pub fn into_tuple(self) -> (Nonce, VerifyingKey) { (self.nonce, self.pub_key) } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs index 03659c7..6f2c2ef 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs @@ -1,15 +1,15 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, StdError}; +use cosmwasm_std::StdError; use sha2::{Digest, Sha256}; use crate::msg::execute::attested::{ Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, }; use crate::msg::HasDomainType; -use crate::state::{MrEnclave, UserData}; +use crate::state::{Config, RawConfig, UserData}; #[derive(Clone, Debug, PartialEq)] -pub struct Instantiate(pub(crate) Attested); +pub struct Instantiate(pub Attested); #[cw_serde] pub struct RawInstantiate(RawAttested); @@ -34,40 +34,47 @@ where } } -impl HasDomainType for RawInstantiate { - type DomainType = Instantiate; +impl HasDomainType for RawInstantiate +where + RA: HasDomainType, +{ + type DomainType = Instantiate; } #[derive(Clone, Debug, PartialEq)] pub struct CoreInstantiate { - mr_enclave: MrEnclave, - // TODO(hu55a1n1): config - e.g. Epoch duration, light client opts + config: Config, } impl CoreInstantiate { - pub fn mr_enclave(&self) -> MrEnclave { - self.mr_enclave + pub fn new(config: Config) -> Self { + Self { config } + } + + pub fn config(&self) -> &Config { + &self.config } } #[cw_serde] pub struct RawCoreInstantiate { - mr_enclave: HexBinary, + config: RawConfig, } impl TryFrom for CoreInstantiate { type Error = StdError; fn try_from(value: RawCoreInstantiate) -> Result { - let mr_enclave = value.mr_enclave.to_array()?; - Ok(Self { mr_enclave }) + Ok(Self { + config: value.config.try_into()?, + }) } } impl From for RawCoreInstantiate { fn from(value: CoreInstantiate) -> Self { Self { - mr_enclave: value.mr_enclave.into(), + config: value.config.into(), } } } @@ -79,7 +86,10 @@ impl HasDomainType for RawCoreInstantiate { impl HasUserData for CoreInstantiate { fn user_data(&self) -> UserData { let mut hasher = Sha256::new(); - hasher.update(self.mr_enclave); + hasher.update( + serde_json::to_string(&RawConfig::from(self.config.clone())) + .expect("infallible serializer"), + ); let digest: [u8; 32] = hasher.finalize().into(); let mut user_data = [0u8; 64]; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs index 5e3cb06..b7edf88 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs @@ -1,26 +1,199 @@ +use core::time::Duration; + use cosmwasm_schema::cw_serde; -use cosmwasm_std::HexBinary; +use cosmwasm_std::{HexBinary, StdError}; use cw_storage_plus::Item; use k256::ecdsa::VerifyingKey; pub type MrEnclave = [u8; 32]; pub type Nonce = [u8; 32]; pub type UserData = [u8; 64]; +pub type Hash = [u8; 32]; +pub type Height = u64; +pub type TrustThreshold = (u64, u64); -#[cw_serde] +#[derive(Clone, Debug, PartialEq)] pub struct Config { - mr_enclave: HexBinary, + mr_enclave: MrEnclave, + epoch_duration: Duration, + light_client_opts: LightClientOpts, } impl Config { - pub fn new(mr_enclave: MrEnclave) -> Self { + pub fn new( + mr_enclave: MrEnclave, + epoch_duration: Duration, + light_client_opts: LightClientOpts, + ) -> Self { Self { - mr_enclave: mr_enclave.into(), + mr_enclave, + epoch_duration, + light_client_opts, } } - pub fn mr_enclave(&self) -> &HexBinary { - &self.mr_enclave + pub fn light_client_opts(&self) -> &LightClientOpts { + &self.light_client_opts + } + + pub fn mr_enclave(&self) -> MrEnclave { + self.mr_enclave + } +} + +#[cw_serde] +pub struct RawConfig { + mr_enclave: HexBinary, + epoch_duration: Duration, + light_client_opts: RawLightClientOpts, +} + +impl RawConfig { + pub fn mr_enclave(&self) -> &[u8] { + self.mr_enclave.as_slice() + } +} + +impl TryFrom for Config { + type Error = StdError; + + fn try_from(value: RawConfig) -> Result { + Ok(Self { + mr_enclave: value.mr_enclave.to_array()?, + epoch_duration: value.epoch_duration, + light_client_opts: value + .light_client_opts + .try_into() + .map_err(|e| StdError::parse_err("light_client_opts", e))?, + }) + } +} + +impl From for RawConfig { + fn from(value: Config) -> Self { + Self { + mr_enclave: value.mr_enclave.into(), + epoch_duration: value.epoch_duration, + light_client_opts: value.light_client_opts.into(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct LightClientOpts { + chain_id: String, + trusted_height: Height, + trusted_hash: Hash, + trust_threshold: TrustThreshold, + trusting_period: u64, + max_clock_drift: u64, + max_block_lag: u64, +} + +impl LightClientOpts { + #[allow(clippy::too_many_arguments)] + pub fn new( + chain_id: String, + trusted_height: Height, + trusted_hash: Hash, + trust_threshold: TrustThreshold, + trusting_period: u64, + max_clock_drift: u64, + max_block_lag: u64, + ) -> Result { + let (numerator, denominator) = (trust_threshold.0, trust_threshold.1); + if numerator > denominator { + return Err(StdError::generic_err("trust_threshold_too_large")); + } + if denominator == 0 { + return Err(StdError::generic_err("undefined_trust_threshold")); + } + if 3 * numerator < denominator { + return Err(StdError::generic_err("trust_threshold_too_small")); + } + + let _trusted_height: i64 = trusted_height + .try_into() + .map_err(|_| StdError::generic_err("trusted_height too large"))?; + + Ok(Self { + chain_id, + trusted_height, + trusted_hash, + trust_threshold, + trusting_period, + max_clock_drift, + max_block_lag, + }) + } + + pub fn chain_id(&self) -> &String { + &self.chain_id + } + + pub fn trusted_height(&self) -> Height { + self.trusted_height + } + + pub fn trusted_hash(&self) -> &Hash { + &self.trusted_hash + } + + pub fn trust_threshold(&self) -> &TrustThreshold { + &self.trust_threshold + } + + pub fn trusting_period(&self) -> u64 { + self.trusting_period + } + + pub fn max_clock_drift(&self) -> u64 { + self.max_clock_drift + } + + pub fn max_block_lag(&self) -> u64 { + self.max_block_lag + } +} + +#[cw_serde] +pub struct RawLightClientOpts { + chain_id: String, + trusted_height: u64, + trusted_hash: HexBinary, + trust_threshold: (u64, u64), + trusting_period: u64, + max_clock_drift: u64, + max_block_lag: u64, +} + +impl TryFrom for LightClientOpts { + type Error = StdError; + + fn try_from(value: RawLightClientOpts) -> Result { + Self::new( + value.chain_id, + value.trusted_height, + value.trusted_hash.to_array()?, + (value.trust_threshold.0, value.trust_threshold.1), + value.trusting_period, + value.max_clock_drift, + value.max_block_lag, + ) + } +} + +impl From for RawLightClientOpts { + fn from(value: LightClientOpts) -> Self { + Self { + chain_id: value.chain_id, + trusted_height: value.trusted_height, + trusted_hash: Vec::::from(value.trusted_hash).into(), + trust_threshold: (value.trust_threshold.0, value.trust_threshold.1), + trusting_period: value.trusting_period, + max_clock_drift: value.max_clock_drift, + max_block_lag: value.max_block_lag, + } } } @@ -46,7 +219,12 @@ impl Session { None } } + + pub fn nonce(&self) -> Nonce { + self.nonce.to_array().expect("correct by construction") + } } -pub const CONFIG: Item<'_, Config> = Item::new("quartz_config"); +pub const CONFIG: Item<'_, RawConfig> = Item::new("quartz_config"); pub const SESSION: Item<'_, Session> = Item::new("quartz_session"); +pub const EPOCH_COUNTER: Item<'_, usize> = Item::new("epoch_counter"); diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index 9deca3e..30d6aec 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -25,6 +25,7 @@ WASM_BIN="$1" CHAIN_ID=${CHAIN_ID:-testing} LABEL=${LABEL:-bisenzone-mvp} COUNT=${COUNT:-0} +INSTANTIATE_MSG=${INSTANTIATE_MSG:-"{}"} TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.0025ucosm --gas auto --gas-adjustment 1.3" @@ -45,7 +46,7 @@ echo "--------------------------------------------------------" echo "Label: ${LABEL}" echo "--------------------------------------------------------" -wasmd tx wasm instantiate "$CODE_ID" "null" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null +wasmd tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null echo "" echo "🕐 Waiting for contract to be queryable..." From aa438c0d621d57ff75e3d1d86b04000a76820c26 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 7 May 2024 15:06:09 -0700 Subject: [PATCH 30/35] Obligato liquidity prototype (#21) --- .../contracts/cw-tee-mtcs/Cargo.toml | 2 + .../contracts/cw-tee-mtcs/src/contract.rs | 143 ++++++++++++++++-- .../contracts/cw-tee-mtcs/src/error.rs | 10 ++ .../contracts/cw-tee-mtcs/src/msg.rs | 35 ++++- .../contracts/cw-tee-mtcs/src/state.rs | 20 ++- 5 files changed, 190 insertions(+), 20 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml index 0e33e50..13529be 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -47,6 +47,8 @@ cosmwasm-std = { version = "1.5.0", features = [ # "cosmwasm_1_4", ] } cw-storage-plus = "1.1.0" +cw20-base = { version = "1.1.1", features = ["library"] } +cw20 = "1.1.1" cw2 = "1.1.1" hex = { version = "0.4.3", default-features = false } k256 = { version = "0.13.2", default-features = false, features = ["ecdsa"] } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 46b9ba8..a6049f6 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -1,9 +1,16 @@ -use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; +use cosmwasm_std::{ + entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + Uint128, +}; use cw2::set_contract_version; +use cw20_base::contract::execute_mint; +use cw20_base::contract::query_balance as cw20_query_balance; +use cw20_base::state::{MinterData, TokenInfo, TOKEN_INFO}; use quartz_cw::handler::RawHandler; +use quartz_cw::state::EPOCH_COUNTER; use crate::error::ContractError; -use crate::msg::execute::{SubmitObligationMsg, SubmitSetoffsMsg}; +use crate::msg::execute::{SubmitObligationMsg, SubmitObligationsMsg, SubmitSetoffsMsg}; use crate::msg::QueryMsg; use crate::msg::{ExecuteMsg, InstantiateMsg}; use crate::state::{current_epoch_key, ObligationsItem, State, OBLIGATIONS_KEY, STATE}; @@ -28,9 +35,54 @@ pub fn instantiate( set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; STATE.save(deps.storage, &state)?; + EPOCH_COUNTER.save(deps.storage, &1)?; + ObligationsItem::new(¤t_epoch_key(OBLIGATIONS_KEY, deps.storage)?) .save(deps.storage, &Default::default())?; + // store token info using cw20-base format + let data = TokenInfo { + name: "USD".to_string(), + symbol: "!$".to_string(), + decimals: 0, + total_supply: Uint128::zero(), + // set self as minter, so we can properly execute mint and burn + mint: Some(MinterData { + minter: env.contract.address.clone(), + cap: None, + }), + }; + TOKEN_INFO.save(deps.storage, &data)?; + + let info = MessageInfo { + sender: env.contract.address.clone(), + funds: vec![], + }; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + "wasm1qv9nel6lwtrq5jmwruxfndqw7ejskn5ysz53hp".to_owned(), + Uint128::new(1000), + )?; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + "wasm1tfxrdcj5kk6rewzmmkku4d9htpjqr0kk6lcftv".to_owned(), + Uint128::new(1000), + )?; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + "wasm1gjg72awjl7jvtmq4kjqp3al9p6crstpar8wgn5".to_owned(), + Uint128::new(1000), + )?; + Ok(Response::new() .add_attribute("method", "instantiate") .add_attribute("owner", info.sender)) @@ -38,7 +90,7 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( - deps: DepsMut, + mut deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg, @@ -48,21 +100,30 @@ pub fn execute( ExecuteMsg::SubmitObligation(SubmitObligationMsg { ciphertext, digest }) => { execute::submit_obligation(deps, ciphertext, digest) } - ExecuteMsg::SubmitSetoffs(SubmitSetoffsMsg { setoffs_enc }) => { - execute::submit_setoffs(deps, setoffs_enc) + ExecuteMsg::SubmitObligations(SubmitObligationsMsg(obligations)) => { + for o in obligations { + execute::submit_obligation(deps.branch(), o.ciphertext, o.digest)?; + } + Ok(Response::new()) } + ExecuteMsg::SubmitSetoffs(SubmitSetoffsMsg { setoffs_enc }) => { + execute::submit_setoffs(deps, env, info, setoffs_enc) + } + ExecuteMsg::InitClearing => execute::init_clearing(deps), } } pub mod execute { use std::collections::BTreeMap; - use cosmwasm_std::{DepsMut, HexBinary, Response}; + use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response, StdResult}; + use cw20_base::contract::{execute_burn, execute_mint}; use quartz_cw::state::Hash; + use quartz_cw::state::EPOCH_COUNTER; use crate::state::{ - current_epoch_key, ObligationsItem, RawCipherText, RawHash, SetoffsItem, OBLIGATIONS_KEY, - SETOFFS_KEY, + current_epoch_key, previous_epoch_key, ObligationsItem, RawHash, SetoffsItem, SettleOff, + OBLIGATIONS_KEY, SETOFFS_KEY, }; use crate::ContractError; @@ -91,18 +152,74 @@ pub mod execute { } pub fn submit_setoffs( - deps: DepsMut, - setoffs_enc: BTreeMap, + mut deps: DepsMut, + env: Env, + _info: MessageInfo, + setoffs_enc: BTreeMap, ) -> Result { // store the `BTreeMap` - SetoffsItem::new(¤t_epoch_key(SETOFFS_KEY, deps.storage)?) + SetoffsItem::new(&previous_epoch_key(SETOFFS_KEY, deps.storage)?) .save(deps.storage, &setoffs_enc)?; + for (_, so) in setoffs_enc { + if let SettleOff::Transfer(t) = so { + let info = MessageInfo { + sender: env.contract.address.clone(), + funds: vec![], + }; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + t.payee.to_string(), + t.amount.into(), + )?; + + let payer = deps.api.addr_validate(&t.payer.to_string())?; + let info = MessageInfo { + sender: payer, + funds: vec![], + }; + + execute_burn(deps.branch(), env.clone(), info, t.amount.into())?; + } + } + Ok(Response::new().add_attribute("action", "submit_setoffs")) } + + pub fn init_clearing(deps: DepsMut) -> Result { + EPOCH_COUNTER.update(deps.storage, |mut counter| -> StdResult<_> { + counter += 1; + Ok(counter) + })?; + Ok(Response::new().add_attribute("action", "init_clearing")) + } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg {} +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetAllSetoffs => to_json_binary(&query::get_all_setoffs(deps)?), + QueryMsg::Balance { address } => to_json_binary(&cw20_query_balance(deps, address)?), + } +} + +pub mod query { + use cosmwasm_std::Deps; + use cosmwasm_std::StdResult; + + use crate::msg::GetAllSetoffsResponse; + use crate::state::previous_epoch_key; + use crate::state::SetoffsItem; + use crate::state::SETOFFS_KEY; + + pub fn get_all_setoffs(deps: Deps) -> StdResult { + let setoffs = SetoffsItem::new(&previous_epoch_key(SETOFFS_KEY, deps.storage)?) + .load(deps.storage)? + .into_iter() + .collect(); + Ok(GetAllSetoffsResponse { setoffs }) + } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs index 9683c23..ff82711 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -1,4 +1,5 @@ use cosmwasm_std::StdError; +use cw20_base::ContractError as Cw20ContractError; use hex::FromHexError; use k256::ecdsa::Error as K256Error; use quartz_cw::error::Error as QuartzError; @@ -26,6 +27,9 @@ pub enum ContractError { #[error("Invalid length")] BadLength, + + #[error("Cw20 error: {0}")] + Cw20(Cw20ContractError), } impl From for ContractError { @@ -33,3 +37,9 @@ impl From for ContractError { Self::K256(e) } } + +impl From for ContractError { + fn from(e: Cw20ContractError) -> Self { + Self::Cw20(e) + } +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index fff496e..b2a356a 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -1,12 +1,13 @@ use std::collections::BTreeMap; use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::HexBinary; use quartz_cw::prelude::*; -use crate::state::{RawCipherText, RawHash}; +use crate::state::RawHash; +use crate::state::SettleOff; #[cw_serde] -#[serde(transparent)] pub struct InstantiateMsg(pub QuartzInstantiateMsg); #[cw_serde] @@ -14,12 +15,12 @@ pub struct InstantiateMsg(pub QuartzInstantiateMsg); pub enum ExecuteMsg { Quartz(QuartzExecuteMsg), SubmitObligation(execute::SubmitObligationMsg), + SubmitObligations(execute::SubmitObligationsMsg), SubmitSetoffs(execute::SubmitSetoffsMsg), + InitClearing, } pub mod execute { - use cosmwasm_std::HexBinary; - use super::*; #[cw_serde] @@ -30,15 +31,37 @@ pub mod execute { // pub proof: π } + #[cw_serde] + #[serde(transparent)] + pub struct SubmitObligationsMsg(pub Vec); + + #[cw_serde] + pub struct SubmitTenderMsg { + pub ciphertext: HexBinary, + pub digest: HexBinary, + // pub proof: π + } + #[cw_serde] pub struct SubmitSetoffsMsg { - pub setoffs_enc: BTreeMap, + pub setoffs_enc: BTreeMap, // pub proof: π, } } #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg {} +pub enum QueryMsg { + #[returns(GetAllSetoffsResponse)] + GetAllSetoffs, + #[returns(cw20::BalanceResponse)] + Balance { address: String }, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct GetAllSetoffsResponse { + pub setoffs: Vec<(HexBinary, SettleOff)>, +} #[cfg(test)] mod tests { diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index f49ac64..c8a62a5 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -9,13 +9,27 @@ pub type RawHash = HexBinary; pub type RawCipherText = HexBinary; pub type ObligationsItem<'a> = Item<'a, BTreeMap>; -pub type SetoffsItem<'a> = Item<'a, BTreeMap>; +pub type SetoffsItem<'a> = Item<'a, BTreeMap>; #[cw_serde] pub struct State { pub owner: String, } +#[cw_serde] +pub struct Transfer { + pub payer: String, + pub payee: String, + pub amount: u64, +} + +#[cw_serde] +#[serde(untagged)] +pub enum SettleOff { + SetOff(Vec), + Transfer(Transfer), +} + pub const STATE: Item = Item::new("state"); pub const OBLIGATIONS_KEY: &str = "obligations"; pub const SETOFFS_KEY: &str = "setoffs"; @@ -23,3 +37,7 @@ pub const SETOFFS_KEY: &str = "setoffs"; pub fn current_epoch_key(key: &str, storage: &dyn Storage) -> Result { Ok(format!("{}/{key}", EPOCH_COUNTER.load(storage)?)) } + +pub fn previous_epoch_key(key: &str, storage: &dyn Storage) -> Result { + Ok(format!("{}/{key}", EPOCH_COUNTER.load(storage)? - 1)) +} From 703b47ab1eaa28b3becb330867e9864975aab95b Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 7 May 2024 15:34:09 -0700 Subject: [PATCH 31/35] Reformat repo with nightly rustfmt (#25) --- bisenzone-cw-mvp/.github/workflows/Basic.yml | 24 ++++++---- .../contracts/cw-tee-mtcs/src/bin/schema.rs | 1 - .../contracts/cw-tee-mtcs/src/contract.rs | 47 ++++++++++--------- .../contracts/cw-tee-mtcs/src/msg.rs | 3 +- .../packages/quartz-cw/src/handler.rs | 17 ++++--- .../packages/quartz-cw/src/handler/execute.rs | 13 +++-- .../quartz-cw/src/handler/execute/attested.rs | 12 +++-- .../src/handler/execute/session_create.rs | 10 ++-- .../handler/execute/session_set_pub_key.rs | 8 ++-- .../quartz-cw/src/handler/instantiate.rs | 15 +++--- .../packages/quartz-cw/src/msg.rs | 3 +- .../packages/quartz-cw/src/msg/execute.rs | 13 +++-- .../quartz-cw/src/msg/execute/attested.rs | 6 ++- .../src/msg/execute/session_create.rs | 7 +-- .../src/msg/execute/session_set_pub_key.rs | 9 ++-- .../packages/quartz-cw/src/msg/instantiate.rs | 12 +++-- .../packages/quartz-cw/src/prelude.rs | 10 ++-- .../src/intel_sgx/epid/verifier.rs | 8 ++-- .../packages/quartz-tee-ra/src/lib.rs | 7 +-- 19 files changed, 131 insertions(+), 94 deletions(-) diff --git a/bisenzone-cw-mvp/.github/workflows/Basic.yml b/bisenzone-cw-mvp/.github/workflows/Basic.yml index 4b0121f..7b69ff3 100644 --- a/bisenzone-cw-mvp/.github/workflows/Basic.yml +++ b/bisenzone-cw-mvp/.github/workflows/Basic.yml @@ -1,6 +1,6 @@ # Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml -on: [push, pull_request] +on: [ push, pull_request ] name: Basic @@ -48,16 +48,10 @@ jobs: uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: 1.73.0 + toolchain: stable 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: @@ -79,3 +73,17 @@ jobs: - name: Schema Changes # fails if any changes not committed run: git diff --exit-code schema + + - name: Install nighly toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: rustfmt + + - name: Run cargo nightly fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check \ No newline at end of file diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs index 353dd79..c80c69f 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs @@ -1,5 +1,4 @@ use cosmwasm_schema::write_api; - use cw_tee_mtcs::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index a6049f6..79d9dc2 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -3,17 +3,20 @@ use cosmwasm_std::{ Uint128, }; use cw2::set_contract_version; -use cw20_base::contract::execute_mint; -use cw20_base::contract::query_balance as cw20_query_balance; -use cw20_base::state::{MinterData, TokenInfo, TOKEN_INFO}; -use quartz_cw::handler::RawHandler; -use quartz_cw::state::EPOCH_COUNTER; +use cw20_base::{ + contract::{execute_mint, query_balance as cw20_query_balance}, + state::{MinterData, TokenInfo, TOKEN_INFO}, +}; +use quartz_cw::{handler::RawHandler, state::EPOCH_COUNTER}; -use crate::error::ContractError; -use crate::msg::execute::{SubmitObligationMsg, SubmitObligationsMsg, SubmitSetoffsMsg}; -use crate::msg::QueryMsg; -use crate::msg::{ExecuteMsg, InstantiateMsg}; -use crate::state::{current_epoch_key, ObligationsItem, State, OBLIGATIONS_KEY, STATE}; +use crate::{ + error::ContractError, + msg::{ + execute::{SubmitObligationMsg, SubmitObligationsMsg, SubmitSetoffsMsg}, + ExecuteMsg, InstantiateMsg, QueryMsg, + }, + state::{current_epoch_key, ObligationsItem, State, OBLIGATIONS_KEY, STATE}, +}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw-tee-mtcs"; @@ -118,14 +121,15 @@ pub mod execute { use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response, StdResult}; use cw20_base::contract::{execute_burn, execute_mint}; - use quartz_cw::state::Hash; - use quartz_cw::state::EPOCH_COUNTER; + use quartz_cw::state::{Hash, EPOCH_COUNTER}; - use crate::state::{ - current_epoch_key, previous_epoch_key, ObligationsItem, RawHash, SetoffsItem, SettleOff, - OBLIGATIONS_KEY, SETOFFS_KEY, + use crate::{ + state::{ + current_epoch_key, previous_epoch_key, ObligationsItem, RawHash, SetoffsItem, + SettleOff, OBLIGATIONS_KEY, SETOFFS_KEY, + }, + ContractError, }; - use crate::ContractError; pub fn submit_obligation( deps: DepsMut, @@ -207,13 +211,12 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } pub mod query { - use cosmwasm_std::Deps; - use cosmwasm_std::StdResult; + use cosmwasm_std::{Deps, StdResult}; - use crate::msg::GetAllSetoffsResponse; - use crate::state::previous_epoch_key; - use crate::state::SetoffsItem; - use crate::state::SETOFFS_KEY; + use crate::{ + msg::GetAllSetoffsResponse, + state::{previous_epoch_key, SetoffsItem, SETOFFS_KEY}, + }; pub fn get_all_setoffs(deps: Deps) -> StdResult { let setoffs = SetoffsItem::new(&previous_epoch_key(SETOFFS_KEY, deps.storage)?) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index b2a356a..4592184 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -4,8 +4,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_std::HexBinary; use quartz_cw::prelude::*; -use crate::state::RawHash; -use crate::state::SettleOff; +use crate::state::{RawHash, SettleOff}; #[cw_serde] pub struct InstantiateMsg(pub QuartzInstantiateMsg); diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs index c7acbf8..386ba8b 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs @@ -3,8 +3,7 @@ pub mod instantiate; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; -use crate::error::Error; -use crate::msg::HasDomainType; +use crate::{error::Error, msg::HasDomainType}; pub trait Handler { fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result; @@ -37,13 +36,17 @@ where #[cfg(test)] mod tests { - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::DepsMut; + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + DepsMut, + }; use serde::Deserialize; - use crate::handler::Handler; - use crate::msg::{HasDomainType, RawExecuteMsg, RawInstantiateMsg}; - use crate::state::SESSION; + use crate::{ + handler::Handler, + msg::{HasDomainType, RawExecuteMsg, RawInstantiateMsg}, + state::SESSION, + }; fn parse_msg<'a, R>(msg_str: &'a str) -> R::DomainType where diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs index b0d4d99..c332ac9 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs @@ -4,11 +4,14 @@ pub mod session_set_pub_key; use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; -use crate::error::Error; -use crate::handler::Handler; -use crate::msg::execute::attested::Attestation; -use crate::msg::execute::attested::HasUserData; -use crate::msg::execute::Execute; +use crate::{ + error::Error, + handler::Handler, + msg::execute::{ + attested::{Attestation, HasUserData}, + Execute, + }, +}; impl Handler for Execute where diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs index 3da23da..f49d1aa 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs @@ -1,12 +1,14 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError}; -use crate::error::Error; -use crate::handler::Handler; -use crate::msg::execute::attested::{ - Attestation, Attested, EpidAttestation, HasUserData, MockAttestation, +use crate::{ + error::Error, + handler::Handler, + msg::execute::attested::{ + Attestation, Attested, EpidAttestation, HasUserData, MockAttestation, + }, + state::CONFIG, }; -use crate::state::CONFIG; impl Handler for EpidAttestation { fn handle( diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs index 5144734..cd182a7 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs @@ -1,9 +1,11 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; -use crate::error::Error; -use crate::handler::Handler; -use crate::msg::execute::session_create::SessionCreate; -use crate::state::{Session, SESSION}; +use crate::{ + error::Error, + handler::Handler, + msg::execute::session_create::SessionCreate, + state::{Session, SESSION}, +}; impl Handler for SessionCreate { fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs index 865dff0..ddebe9e 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs @@ -1,9 +1,9 @@ use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response}; -use crate::error::Error; -use crate::handler::Handler; -use crate::msg::execute::session_set_pub_key::SessionSetPubKey; -use crate::state::SESSION; +use crate::{ + error::Error, handler::Handler, msg::execute::session_set_pub_key::SessionSetPubKey, + state::SESSION, +}; impl Handler for SessionSetPubKey { fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs index e59419b..3804bc0 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs @@ -1,12 +1,15 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use quartz_tee_ra::Error as RaVerificationError; -use crate::error::Error; -use crate::handler::Handler; -use crate::msg::execute::attested::{Attestation, EpidAttestation, MockAttestation}; -use crate::msg::instantiate::{CoreInstantiate, Instantiate}; -use crate::state::CONFIG; -use crate::state::{RawConfig, EPOCH_COUNTER}; +use crate::{ + error::Error, + handler::Handler, + msg::{ + execute::attested::{Attestation, EpidAttestation, MockAttestation}, + instantiate::{CoreInstantiate, Instantiate}, + }, + state::{RawConfig, CONFIG, EPOCH_COUNTER}, +}; impl Handler for Instantiate { fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs index 8b8864b..4b440f8 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs @@ -2,11 +2,10 @@ pub mod execute; pub mod instantiate; pub mod query; +use cosmwasm_std::StdError; pub use execute::{Execute as ExecuteMsg, RawExecute as RawExecuteMsg}; pub use instantiate::{Instantiate as InstantiateMsg, RawInstantiate as RawInstantiateMsg}; -use cosmwasm_std::StdError; - pub trait HasDomainType: From { type DomainType: TryFrom; } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs index f6f1ed1..b95fc4e 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs @@ -5,11 +5,14 @@ pub mod session_set_pub_key; use cosmwasm_schema::cw_serde; use cosmwasm_std::StdError; -use crate::msg::execute::attested::{Attested, EpidAttestation}; -use crate::msg::execute::attested::{RawAttested, RawEpidAttestation}; -use crate::msg::execute::session_create::{RawSessionCreate, SessionCreate}; -use crate::msg::execute::session_set_pub_key::{RawSessionSetPubKey, SessionSetPubKey}; -use crate::msg::HasDomainType; +use crate::msg::{ + execute::{ + attested::{Attested, EpidAttestation, RawAttested, RawEpidAttestation}, + session_create::{RawSessionCreate, SessionCreate}, + session_set_pub_key::{RawSessionSetPubKey, SessionSetPubKey}, + }, + HasDomainType, +}; #[derive(Clone, Debug, PartialEq)] pub enum Execute { diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs index 31de243..125abf6 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs @@ -2,8 +2,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::StdError; use quartz_tee_ra::IASReport; -use crate::msg::HasDomainType; -use crate::state::{MrEnclave, UserData}; +use crate::{ + msg::HasDomainType, + state::{MrEnclave, UserData}, +}; #[derive(Clone, Debug, PartialEq)] pub struct Attested { diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs index 7603555..99aa545 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs @@ -1,9 +1,10 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, StdError}; -use crate::msg::execute::attested::HasUserData; -use crate::msg::HasDomainType; -use crate::state::{Nonce, UserData}; +use crate::{ + msg::{execute::attested::HasUserData, HasDomainType}, + state::{Nonce, UserData}, +}; #[derive(Clone, Debug, PartialEq)] pub struct SessionCreate { diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs index 47b3c34..9f857f2 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs @@ -3,10 +3,11 @@ use cosmwasm_std::{HexBinary, StdError}; use k256::ecdsa::VerifyingKey; use sha2::{Digest, Sha256}; -use crate::error::Error; -use crate::msg::execute::attested::HasUserData; -use crate::msg::HasDomainType; -use crate::state::{Nonce, UserData}; +use crate::{ + error::Error, + msg::{execute::attested::HasUserData, HasDomainType}, + state::{Nonce, UserData}, +}; #[derive(Clone, Debug, PartialEq)] pub struct SessionSetPubKey { diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs index 6f2c2ef..6b47169 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs @@ -2,11 +2,15 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::StdError; use sha2::{Digest, Sha256}; -use crate::msg::execute::attested::{ - Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, +use crate::{ + msg::{ + execute::attested::{ + Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, + }, + HasDomainType, + }, + state::{Config, RawConfig, UserData}, }; -use crate::msg::HasDomainType; -use crate::state::{Config, RawConfig, UserData}; #[derive(Clone, Debug, PartialEq)] pub struct Instantiate(pub Attested); diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs index b998726..d938635 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs @@ -1,3 +1,7 @@ -pub use crate::handler::RawHandler; -pub use crate::msg::execute::RawExecute as QuartzExecuteMsg; -pub use crate::msg::instantiate::RawInstantiate as QuartzInstantiateMsg; +pub use crate::{ + handler::RawHandler, + msg::{ + execute::RawExecute as QuartzExecuteMsg, + instantiate::RawInstantiate as QuartzInstantiateMsg, + }, +}; diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs index 2d09248..7234836 100644 --- a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs @@ -2,10 +2,10 @@ use cosmwasm_std::ensure_eq; use num_bigint::BigUint; use sha2::{Digest, Sha256}; -use crate::intel_sgx::epid::types::IASReport; -use crate::intel_sgx::epid::Error as EpidError; -use crate::intel_sgx::epid::{INTEL_ROOT_EXPONENT, INTEL_ROOT_MODULUS}; -use crate::intel_sgx::Error; +use crate::intel_sgx::{ + epid::{types::IASReport, Error as EpidError, INTEL_ROOT_EXPONENT, INTEL_ROOT_MODULUS}, + Error, +}; /// Given an RSA signature and the signer's exponent + modulus we recover the digest that was signed by the signature. pub fn recover_signature_digest(signature: &[u8], exponent: &[u8], modulus: &[u8]) -> Vec { diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs index 908a4cd..75fac91 100644 --- a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs @@ -17,6 +17,7 @@ pub mod intel_sgx; -pub use intel_sgx::epid::types::IASReport; -pub use intel_sgx::epid::verifier::verify as verify_epid_attestation; -pub use intel_sgx::Error; +pub use intel_sgx::{ + epid::{types::IASReport, verifier::verify as verify_epid_attestation}, + Error, +}; From 71139ab81923494ccb699dc352523fd41df84c3a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Mon, 13 May 2024 07:24:56 -0400 Subject: [PATCH 32/35] scripts/deploy-contract: reduce sleep by querying txhash (#26) --- bisenzone-cw-mvp/scripts/deploy-contract.sh | 29 +++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index 30d6aec..f218d44 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -5,19 +5,19 @@ set -eo pipefail usage() { - echo "Usage: $0 WASM_BIN [COUNT]" - echo "Example: $0 artifacts/cofi_karma_game.wasm" - exit 1 + echo "Usage: $0 WASM_BIN [COUNT]" + echo "Example: $0 artifacts/cofi_karma_game.wasm" + exit 1 } if [ -z "$1" ]; then - echo "❌ Error: Missing WASM_BIN parameter. Please check if all parameters were specified." - usage + echo "❌ Error: Missing WASM_BIN parameter. Please check if all parameters were specified." + usage fi if [ "$#" -gt 9 ]; then - echo "❌ Error: Incorrect number of parameters." - usage + echo "❌ Error: Incorrect number of parameters." + usage fi USER_ADDR=${USER_ADDR:-$(wasmd keys show -a admin)} @@ -35,7 +35,10 @@ echo "====================================================================" RES=$(wasmd tx wasm store "$WASM_BIN" --from "$USER_ADDR" $TXFLAG -y --output json) TX_HASH=$(echo $RES | jq -r '.["txhash"]') -sleep 8 +while ! wasmd query tx $TX_HASH &> /dev/null; do + echo "... 🕐 waiting for contract to deploy" + sleep 1 +done RES=$(wasmd query tx "$TX_HASH" --output json) CODE_ID=$(echo $RES | jq -r '.logs[0].events[1].attributes[1].value') @@ -46,11 +49,15 @@ echo "--------------------------------------------------------" echo "Label: ${LABEL}" echo "--------------------------------------------------------" -wasmd tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null +RES=$(wasmd tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin --output json) +TX_HASH=$(echo $RES | jq -r '.["txhash"]') + echo "" -echo "🕐 Waiting for contract to be queryable..." -sleep 5 +while ! wasmd query tx $TX_HASH &> /dev/null; do + echo "... 🕐 waiting for contract to be queryable" + sleep 1 +done RES=$(wasmd query wasm list-contract-by-code "$CODE_ID" --output json) CONTRACT=$(echo $RES | jq -r '.contracts[0]') From 103a4631661395697040175b6ced532b9f1524d6 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 14 May 2024 12:10:43 -0400 Subject: [PATCH 33/35] deploy-contract.sh uses $NODE for address (#31) Co-authored-by: hu55a1n1 --- bisenzone-cw-mvp/scripts/deploy-contract.sh | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index f218d44..c720f20 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -23,24 +23,27 @@ fi USER_ADDR=${USER_ADDR:-$(wasmd keys show -a admin)} WASM_BIN="$1" CHAIN_ID=${CHAIN_ID:-testing} +NODE_URL=${NODE_URL:-127.0.0.1:26657} LABEL=${LABEL:-bisenzone-mvp} COUNT=${COUNT:-0} INSTANTIATE_MSG=${INSTANTIATE_MSG:-"{}"} TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.0025ucosm --gas auto --gas-adjustment 1.3" +CMD="wasmd --node http://$NODE_URL" + echo "🚀 Deploying WASM contract '${WASM_BIN}' on chain '${CHAIN_ID}' using account '${USER_ADDR}'..." echo "====================================================================" -RES=$(wasmd tx wasm store "$WASM_BIN" --from "$USER_ADDR" $TXFLAG -y --output json) +RES=$($CMD tx wasm store "$WASM_BIN" --from "$USER_ADDR" $TXFLAG -y --output json) TX_HASH=$(echo $RES | jq -r '.["txhash"]') -while ! wasmd query tx $TX_HASH &> /dev/null; do +while ! $CMD query tx $TX_HASH &> /dev/null; do echo "... 🕐 waiting for contract to deploy" sleep 1 done -RES=$(wasmd query tx "$TX_HASH" --output json) +RES=$($CMD query tx "$TX_HASH" --output json) CODE_ID=$(echo $RES | jq -r '.logs[0].events[1].attributes[1].value') echo "" @@ -49,17 +52,17 @@ echo "--------------------------------------------------------" echo "Label: ${LABEL}" echo "--------------------------------------------------------" -RES=$(wasmd tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin --output json) +RES=$($CMD tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin --output json) TX_HASH=$(echo $RES | jq -r '.["txhash"]') echo "" -while ! wasmd query tx $TX_HASH &> /dev/null; do +while ! $CMD query tx $TX_HASH &> /dev/null; do echo "... 🕐 waiting for contract to be queryable" sleep 1 done -RES=$(wasmd query wasm list-contract-by-code "$CODE_ID" --output json) +RES=$($CMD query wasm list-contract-by-code "$CODE_ID" --output json) CONTRACT=$(echo $RES | jq -r '.contracts[0]') echo "🚀 Successfully deployed and instantiated contract!" From a32a03602866270107e81ca24dd4203abe30b9dc Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 15 May 2024 09:00:14 -0700 Subject: [PATCH 34/35] On-chain DCAP verifier without cert validation (#24) --- .../packages/quartz-tee-ra/Cargo.toml | 10 ++ .../quartz-tee-ra/data/DcapRootCACert.pem | 16 +++ .../packages/quartz-tee-ra/src/intel_sgx.rs | 1 + .../quartz-tee-ra/src/intel_sgx/dcap.rs | 130 ++++++++++++++++++ .../src/intel_sgx/dcap/certificate_chain.rs | 17 +++ .../src/intel_sgx/dcap/mc_attest_verifier.rs | 4 + .../intel_sgx/dcap/mc_attest_verifier/dcap.rs | 82 +++++++++++ .../dcap/mc_attest_verifier_types.rs | 4 + .../mc_attest_verifier_types/verification.rs | 60 ++++++++ 9 files changed, 324 insertions(+) create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/data/DcapRootCACert.pem create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/certificate_chain.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier/dcap.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types.rs create mode 100644 bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types/verification.rs diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml index e0160db..2bc9d3f 100644 --- a/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml @@ -13,3 +13,13 @@ num-bigint = "0.4.4" serde_json = "1.0.108" sha2 = "0.10.8" thiserror = { version = "1.0.49" } + +der = { version = "0.7.9", default-features = false } +displaydoc = { version = "0.2.4", default-features = false } +mc-sgx-core-types = { git = "https://github.com/hu55a1n1/sgx" } +mc-sgx-dcap-types = { git = "https://github.com/hu55a1n1/sgx" } +mc-sgx-dcap-sys-types = { git = "https://github.com/hu55a1n1/sgx" } +mc-attestation-verifier = { git = "https://github.com/hu55a1n1/attestation" } +serde = { version = "1.0.198", default-features = false } +x509-cert = { version = "0.2.5", default-features = false } +zeroize = { version = "1.7.0", default-features = false } diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/data/DcapRootCACert.pem b/bisenzone-cw-mvp/packages/quartz-tee-ra/data/DcapRootCACert.pem new file mode 100644 index 0000000..408bd93 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/data/DcapRootCACert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 +1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB +uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ +MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV +Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI +KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg +AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs index e5cefa4..3e7bd02 100644 --- a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs @@ -1,5 +1,6 @@ use thiserror::Error; +pub mod dcap; pub mod epid; #[derive(Error, Debug)] diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap.rs new file mode 100644 index 0000000..0668ca9 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap.rs @@ -0,0 +1,130 @@ +pub mod certificate_chain; +pub mod mc_attest_verifier; +pub mod mc_attest_verifier_types; + +/// Root anchor PEM file for use with DCAP +pub const DCAP_ROOT_ANCHOR: &str = include_str!("../../data/DcapRootCACert.pem"); + +pub use mc_attest_verifier::dcap::DcapVerifier; +pub use mc_attest_verifier_types::verification::EnclaveReportDataContents; +pub use mc_attestation_verifier::*; +pub use mc_sgx_dcap_sys_types::sgx_ql_qve_collateral_t; +pub use mc_sgx_dcap_types::{CertificationData, Collateral}; +pub use x509_cert::Certificate; + +#[cfg(test)] +mod tests { + use hex_literal::hex; + use mc_sgx_dcap_types::Quote3; + + #[test] + fn test_quote_parse() { + let quote_bytes = hex!( + "03000200000000000a000f00939a7233f79c4ca9940a0db3957f0607ce4\ + 8836fd48a951172fe155220a719bd00000000141402070180010000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000005000000000000000700000000000000dc43f8c42d8e5f52c\ + 8bbd68f426242153f0be10630ff8cca255129a3ca03d27300000000000000000000000000000000000000000000\ + 000000000000000000001cf2e52911410fbf3f199056a98d58795a559a2e800933f7fcd13d048462271c0000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000009113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd\ + 5405e4d54a682000000000000000000000000000000000000000000000000000000000000000044100000552c9b\ + 321744cf259b8c239213413ca4226ea8a705ad6eabb505bb4f3a8850f1ed9f2ff3d9e17ba8be8cc69a0d911575a\ + 813392202bddaa7b971d406704989e0be6177e039634cfbca4739ac246fda7df8c312a98f30f57b63f3c8921fce\ + 51d90a93031f97f769637be9b028e7b007a4e458d4fa717befbd81b069050825801314140207018001000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000150000000000000007\ + 0000000000000096b347a64e5a045e27369c26e6dcda51fd7c850e9b3a3a79e718f43261dee1e40000000000000\ + 0000000000000000000000000000000000000000000000000008c4f5775d796503e96137f77c68a829a0056ac8d\ + ed70140b081b094490c57bff0000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000001000a000000000000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000000000000000000000000000000000b0f056c5355f6c770413\ + 938c2a41ed1b5c34ecb35f85fa539f16cca7a30d6da900000000000000000000000000000000000000000000000\ + 00000000000000000010a4d552582e428f8fd138cbb5f8af51050776f2c487996147d4ebfb5c817ba733905f30a\ + f7b4f340eaf0fdf2bb64dcb045d56f1609a1b042e6f5aeed09175c2000000102030405060708090a0b0c0d0e0f1\ + 01112131415161718191a1b1c1d1e1f0500dc0d00002d2d2d2d2d424547494e2043455254494649434154452d2d\ + 2d2d2d0a4d4949456a544343424453674177494241674956414a5172493559365a78484836785034424941715a4\ + e6b326e4c7a594d416f4743437147534d343942414d430a4d484578497a416842674e5642414d4d476b6c756447\ + 567349464e48574342515130736755484a765932567a6332397949454e424d526f77474159445651514b0a44424\ + 64a626e526c6243424462334a7762334a6864476c76626a45554d424947413155454277774c5532467564474567\ + 51327868636d4578437a414a42674e560a4241674d416b4e424d517377435159445651514745774a56557a41654\ + 67730794d7a45784d5441784e7a45334d4452614677307a4d4445784d5441784e7a45330a4d4452614d48417849\ + 6a416742674e5642414d4d47556c756447567349464e4857434251513073675132567964476c6d61574e6864475\ + 578476a415942674e560a42416f4d45556c756447567349454e76636e4276636d4630615739754d525177456759\ + 4456515148444174545957353059534244624746795954454c4d416b470a413155454341774351304578437a414\ + a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451\ + 6741450a77625468574d583134443163657835317875614958456771517a69636e744b7a48454a32536f31336e3\ + 84a427050314a67383673764263462f7070715a554e710a68524b4642667469584c6d4f536c614955784e6e5136\ + 4f434171677767674b6b4d42384741315564497751594d426141464e446f71747031312f6b75535265590a50487\ + 3555a644456386c6c4e4d477747413155644877526c4d474d77596142666f463247573268306448427a4f693876\ + 595842704c6e527964584e305a57527a0a5a584a3261574e6c63793570626e526c6243356a62323076633264344\ + c324e6c636e52705a6d6c6a5958527062323476646a517663474e7259334a7350324e680a5058427962324e6c63\ + 334e7663695a6c626d4e765a476c755a7a316b5a584977485159445652304f42425945464a57566e41395a63736\ + f3753356b6a2f647a4a0a594f3034534175644d41344741315564447745422f775145417749477744414d42674e\ + 5648524d4241663845416a41414d4949423141594a4b6f5a496876684e0a4151304242494942785443434163457\ + 74867594b4b6f5a496876684e41513042415151514d6d5867725757774c59554164456d6c766c36615344434341\ + 5751470a43697147534962345451454e41514977676746554d42414743797147534962345451454e41514942416\ + 745554d42414743797147534962345451454e415149430a416745554d42414743797147534962345451454e4151\ + 4944416745434d42414743797147534962345451454e41514945416745454d42414743797147534962340a54514\ + 54e41514946416745424d42454743797147534962345451454e41514947416749416744415142677371686b6947\ + 2b4530424451454342774942414441510a42677371686b69472b453042445145434341494241444151426773716\ + 86b69472b45304244514543435149424144415142677371686b69472b453042445145430a436749424144415142\ + 677371686b69472b45304244514543437749424144415142677371686b69472b453042445145434441494241444\ + 15142677371686b69470a2b45304244514543445149424144415142677371686b69472b45304244514543446749\ + 424144415142677371686b69472b4530424451454344774942414441510a42677371686b69472b4530424451454\ + 3454149424144415142677371686b69472b45304244514543455149424454416642677371686b69472b45304244\ + 5145430a4567515146425143424147414141414141414141414141414144415142676f71686b69472b453042445\ + 14544424149414144415542676f71686b69472b4530420a44514545424159416b473668414141774477594b4b6f\ + 5a496876684e4151304242516f424144414b42676771686b6a4f5051514441674e4841444245416942560a6e584\ + 667364277466a6945474230417162424e702b4b56734a477245744f4f49666e6f365450387031414967536c4430\ + 574e39595261575968346534656835330a314637434537664964724f55414c5177757632735948513d0a2d2d2d2\ + d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d\ + 2d2d2d2d0a4d4949436d444343416a36674177494241674956414e446f71747031312f6b7553526559504873555\ + a644456386c6c4e4d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c7564\ + 47567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a623\ + 34a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a41\ + 4a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a457\ + 84d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484578497a41680a42674e5642414d4d\ + 476b6c756447567349464e48574342515130736755484a765932567a6332397949454e424d526f7747415944565\ + 1514b4442464a626e526c0a6243424462334a7762334a6864476c76626a45554d424947413155454277774c5532\ + 46756447456751327868636d4578437a414a42674e564241674d416b4e420a4d517377435159445651514745774\ + a56557a425a4d424d4742797147534d34394167454743437147534d34394177454841304941424c39712b4e4d70\ + 32494f670a74646c31626b2f75575a352b5447516d38614369387a373866732b664b435133642b75447a586e565\ + 44154325a68444369667949754a77764e33774e427039690a484253534d4a4d4a72424f6a676273776762677748\ + 7759445652306a42426777466f4155496d554d316c71644e496e7a6737535655723951477a6b6e427177770a556\ + 759445652306642457377535442486f45576751345a426148523063484d364c79396a5a584a3061575a70593246\ + 305a584d7564484a316333526c5a484e6c0a636e5a705932567a4c6d6c75644756734c6d4e766253394a626e526\ + c62464e4857464a76623352445153356b5a584977485159445652304f42425945464e446f0a71747031312f6b75\ + 53526559504873555a644456386c6c4e4d41344741315564447745422f77514541774942426a415342674e56485\ + 24d4241663845434441470a4151482f416745414d416f4743437147534d343942414d43413067414d4555434951\ + 434a6754627456714f795a316d336a716941584d365159613672357357530a34792f4737793875494a477864774\ + 9675271507642534b7a7a516167424c517135733541373070646f6961524a387a2f3075447a344e675639316b3d\ + 0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e20434552544946494\ + 34154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a67375356\ + 55723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d42674741315545417777525\ + 35735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c75644756734945\ + 4e760a636e4276636d4630615739754d52517745675944565151484441745459573530595342446247467959544\ + 54c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455\ + 794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554\ + 541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c7564\ + 47567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624\ + 746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b774577\ + 59484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505\ + 773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f\ + 346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d454\ + 7444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a\ + 424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e5\ + 67a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d39\ + 7664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a5572395\ + 1477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d\ + 4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b5\ + 33943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c7248\ + 6f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454\ + e442043455254494649434154452d2d2d2d2d0a00" + ); + let _quote: Quote3> = Quote3::try_from(quote_bytes.to_vec()) + .expect("Failed to parse quote") + .into(); + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/certificate_chain.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/certificate_chain.rs new file mode 100644 index 0000000..7955730 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/certificate_chain.rs @@ -0,0 +1,17 @@ +use der::DateTime; +use mc_attestation_verifier::{CertificateChainVerifier, CertificateChainVerifierError}; +use x509_cert::{crl::CertificateList, Certificate}; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] +pub struct TlsCertificateChainVerifier; + +impl CertificateChainVerifier for TlsCertificateChainVerifier { + fn verify_certificate_chain<'a, 'b>( + &self, + _certificate_chain: impl IntoIterator, + _crls: impl IntoIterator, + _time: impl Into>, + ) -> Result<(), CertificateChainVerifierError> { + todo!() + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier.rs new file mode 100644 index 0000000..10d02c2 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier.rs @@ -0,0 +1,4 @@ +// Trimmed down version of the `mc_attest_verifier` crate (for DCAP verification only) -> +// https://github.com/hu55a1n1/mobilecoin/blob/a1eba594f5b1ebe3d2b0736cf76d38120f84a4a0/attest/verifier + +pub mod dcap; diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier/dcap.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier/dcap.rs new file mode 100644 index 0000000..11698b6 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier/dcap.rs @@ -0,0 +1,82 @@ +// Copyright (c) 2023 The MobileCoin Foundation + +//! Verify the contents of a Quote3. + +use der::DateTime; +use mc_attestation_verifier::{ + Accessor, And, AndOutput, Evidence, EvidenceValue, EvidenceVerifier, ReportDataVerifier, + TrustedIdentity, VerificationOutput, Verifier, +}; +use mc_sgx_core_types::ReportData; + +// use super::DCAP_ROOT_ANCHOR; +use super::super::certificate_chain::TlsCertificateChainVerifier; +use super::super::mc_attest_verifier_types::verification::EnclaveReportDataContents; + +#[derive(Debug)] +pub struct DcapVerifier { + verifier: And, ReportDataHashVerifier>, +} + +type DcapVerifierOutput = AndOutput; + +impl DcapVerifier { + /// Create a new instance of the DcapVerifier. + /// + /// # Arguments + /// * `trusted_identities` - The allowed identities that can be used in an + /// enclave. Verification will succeed if any of these match. + /// * `time` - The time to use to verify the validity of the certificates + /// and collateral. If time is provided, verification will fail if this + /// time is before or after any of the validity periods. Otherwise, time + /// validation of certificates will be skipped. + pub fn new( + trusted_identities: I, + time: impl Into>, + report_data: EnclaveReportDataContents, + ) -> Self + where + I: IntoIterator, + ID: Into, + { + let certificate_verifier = TlsCertificateChainVerifier; + let verifier = And::new( + EvidenceVerifier::new(certificate_verifier, trusted_identities, time), + ReportDataHashVerifier::new(report_data), + ); + Self { verifier } + } + + /// Verify the `evidence` + pub fn verify(&self, evidence: &Evidence>) -> VerificationOutput { + self.verifier.verify(evidence) + } +} + +#[derive(Debug, Clone)] +pub struct ReportDataHashVerifier { + report_data_verifier: ReportDataVerifier, +} + +impl ReportDataHashVerifier { + pub fn new(report_data: EnclaveReportDataContents) -> Self { + let mut expected_report_data_bytes = [0u8; 64]; + expected_report_data_bytes[..32].copy_from_slice(report_data.sha256().as_ref()); + let mut mask = [0u8; 64]; + mask[..32].copy_from_slice([0xffu8; 32].as_ref()); + let report_data_verifier = + ReportDataVerifier::new(expected_report_data_bytes.into(), mask.into()); + + Self { + report_data_verifier, + } + } +} + +impl> Verifier for ReportDataHashVerifier { + type Value = ReportData; + + fn verify(&self, evidence: &E) -> VerificationOutput { + self.report_data_verifier.verify(evidence) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types.rs new file mode 100644 index 0000000..a40c715 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types.rs @@ -0,0 +1,4 @@ +// Heavily trimmed down version of the `mc-attest-verifier-types` crate +// https://github.com/hu55a1n1/mobilecoin/blob/89d90c427bf9cf637a124c0afad266d52b573dc8/attest/verifier/types/src/verification.rs + +pub mod verification; diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types/verification.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types/verification.rs new file mode 100644 index 0000000..266001c --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types/verification.rs @@ -0,0 +1,60 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Attestation Verification Report type. + +use core::fmt::Debug; + +use mc_sgx_core_types::QuoteNonce; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +/// Structure for holding the contents of the Enclave's Report Data. +/// The Enclave Quote's ReportData member contains a SHA256 hash of this +/// structure's contents. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct EnclaveReportDataContents { + nonce: QuoteNonce, + custom_identity: Option<[u8; 32]>, +} + +impl EnclaveReportDataContents { + /// Create a new EnclaveReportDataContents. + /// + /// # Arguments + /// * `nonce` - The nonce provided from the enclave when generating the + /// Report. + /// * `key` - The public key of the enclave. Previously this was bytes 0..32 + /// of the enclave's [`ReportData`](mc-sgx-core-types::ReportData). + /// * `custom_identity` - The custom identity of the enclave. Previously + /// this was bytes 32..64 of the enclave's + /// [`ReportData`](mc-sgx-core-types::ReportData). + pub fn new(nonce: QuoteNonce, custom_identity: impl Into>) -> Self { + Self { + nonce, + custom_identity: custom_identity.into(), + } + } + + /// Get the nonce + pub fn nonce(&self) -> &QuoteNonce { + &self.nonce + } + + /// Get the custom identity + pub fn custom_identity(&self) -> Option<&[u8; 32]> { + self.custom_identity.as_ref() + } + + /// Returns a SHA256 hash of the contents of this structure. + /// + /// This is the value that is stored in bytes 0..32 of the enclave's + /// [`ReportData`](mc-sgx-core-types::ReportData). + pub fn sha256(&self) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(&self.nonce); + if let Some(custom_identity) = &self.custom_identity { + hasher.update(custom_identity); + } + hasher.finalize().into() + } +} From 6b5b0e53ffecd842a1ec96143d6f1b0da5d7af2d Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Thu, 16 May 2024 02:05:41 -0700 Subject: [PATCH 35/35] Add liquidity sources to the contract (#32) --- .../contracts/cw-tee-mtcs/src/contract.rs | 62 +++++++++++++++++-- .../contracts/cw-tee-mtcs/src/msg.rs | 14 ++++- .../contracts/cw-tee-mtcs/src/state.rs | 12 +++- 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 79d9dc2..e218a95 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -15,7 +15,10 @@ use crate::{ execute::{SubmitObligationMsg, SubmitObligationsMsg, SubmitSetoffsMsg}, ExecuteMsg, InstantiateMsg, QueryMsg, }, - state::{current_epoch_key, ObligationsItem, State, OBLIGATIONS_KEY, STATE}, + state::{ + current_epoch_key, LiquiditySourcesItem, ObligationsItem, State, LIQUIDITY_SOURCES_KEY, + OBLIGATIONS_KEY, STATE, + }, }; // version info for migration info @@ -43,6 +46,9 @@ pub fn instantiate( ObligationsItem::new(¤t_epoch_key(OBLIGATIONS_KEY, deps.storage)?) .save(deps.storage, &Default::default())?; + LiquiditySourcesItem::new(¤t_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?) + .save(deps.storage, &Default::default())?; + // store token info using cw20-base format let data = TokenInfo { name: "USD".to_string(), @@ -103,10 +109,14 @@ pub fn execute( ExecuteMsg::SubmitObligation(SubmitObligationMsg { ciphertext, digest }) => { execute::submit_obligation(deps, ciphertext, digest) } - ExecuteMsg::SubmitObligations(SubmitObligationsMsg(obligations)) => { + ExecuteMsg::SubmitObligations(SubmitObligationsMsg { + obligations, + liquidity_sources, + }) => { for o in obligations { execute::submit_obligation(deps.branch(), o.ciphertext, o.digest)?; } + execute::append_liquidity_sources(deps, liquidity_sources)?; Ok(Response::new()) } ExecuteMsg::SubmitSetoffs(SubmitSetoffsMsg { setoffs_enc }) => { @@ -125,8 +135,8 @@ pub mod execute { use crate::{ state::{ - current_epoch_key, previous_epoch_key, ObligationsItem, RawHash, SetoffsItem, - SettleOff, OBLIGATIONS_KEY, SETOFFS_KEY, + current_epoch_key, previous_epoch_key, LiquiditySourcesItem, ObligationsItem, RawHash, + SetoffsItem, SettleOff, LIQUIDITY_SOURCES_KEY, OBLIGATIONS_KEY, SETOFFS_KEY, }, ContractError, }; @@ -155,6 +165,24 @@ pub mod execute { .add_attribute("ciphertext", ciphertext.to_string())) } + pub fn append_liquidity_sources( + deps: DepsMut, + liquidity_sources: Vec, + ) -> Result<(), ContractError> { + liquidity_sources + .iter() + .try_for_each(|ls| deps.api.addr_validate(ls).map(|_| ()))?; + + // store the liquidity sources + LiquiditySourcesItem::new(¤t_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?) + .update(deps.storage, |mut ls| { + ls.extend(liquidity_sources); + Ok::<_, ContractError>(ls) + })?; + + Ok(()) + } + pub fn submit_setoffs( mut deps: DepsMut, env: Env, @@ -206,6 +234,9 @@ pub mod execute { pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::GetAllSetoffs => to_json_binary(&query::get_all_setoffs(deps)?), + QueryMsg::GetLiquiditySources { epoch } => { + to_json_binary(&query::get_liquidity_sources(deps, epoch)?) + } QueryMsg::Balance { address } => to_json_binary(&cw20_query_balance(deps, address)?), } } @@ -214,8 +245,11 @@ pub mod query { use cosmwasm_std::{Deps, StdResult}; use crate::{ - msg::GetAllSetoffsResponse, - state::{previous_epoch_key, SetoffsItem, SETOFFS_KEY}, + msg::{GetAllSetoffsResponse, GetLiquiditySourcesResponse}, + state::{ + current_epoch_key, epoch_key, previous_epoch_key, LiquiditySourcesItem, SetoffsItem, + LIQUIDITY_SOURCES_KEY, SETOFFS_KEY, + }, }; pub fn get_all_setoffs(deps: Deps) -> StdResult { @@ -225,4 +259,20 @@ pub mod query { .collect(); Ok(GetAllSetoffsResponse { setoffs }) } + + pub fn get_liquidity_sources( + deps: Deps, + epoch: Option, + ) -> StdResult { + let epoch_key = match epoch { + None => current_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?, + Some(e) => epoch_key(LIQUIDITY_SOURCES_KEY, e)?, + }; + + let liquidity_sources = LiquiditySourcesItem::new(&epoch_key) + .load(deps.storage)? + .into_iter() + .collect(); + Ok(GetLiquiditySourcesResponse { liquidity_sources }) + } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index 4592184..3e0bfe6 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -31,8 +31,10 @@ pub mod execute { } #[cw_serde] - #[serde(transparent)] - pub struct SubmitObligationsMsg(pub Vec); + pub struct SubmitObligationsMsg { + pub obligations: Vec, + pub liquidity_sources: Vec, + } #[cw_serde] pub struct SubmitTenderMsg { @@ -47,11 +49,14 @@ pub mod execute { // pub proof: π, } } + #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { #[returns(GetAllSetoffsResponse)] GetAllSetoffs, + #[returns(GetLiquiditySourcesResponse)] + GetLiquiditySources { epoch: Option }, // `None` means latest #[returns(cw20::BalanceResponse)] Balance { address: String }, } @@ -62,6 +67,11 @@ pub struct GetAllSetoffsResponse { pub setoffs: Vec<(HexBinary, SettleOff)>, } +#[cw_serde] +pub struct GetLiquiditySourcesResponse { + pub liquidity_sources: Vec, +} + #[cfg(test)] mod tests { use super::*; diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index c8a62a5..dcdb30b 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, BTreeSet}; use cosmwasm_schema::cw_serde; use cosmwasm_std::{HexBinary, StdError, Storage}; @@ -10,6 +10,7 @@ pub type RawCipherText = HexBinary; pub type ObligationsItem<'a> = Item<'a, BTreeMap>; pub type SetoffsItem<'a> = Item<'a, BTreeMap>; +pub type LiquiditySourcesItem<'a> = Item<'a, BTreeSet>; #[cw_serde] pub struct State { @@ -33,11 +34,16 @@ pub enum SettleOff { pub const STATE: Item = Item::new("state"); pub const OBLIGATIONS_KEY: &str = "obligations"; pub const SETOFFS_KEY: &str = "setoffs"; +pub const LIQUIDITY_SOURCES_KEY: &str = "liquidity_sources"; pub fn current_epoch_key(key: &str, storage: &dyn Storage) -> Result { - Ok(format!("{}/{key}", EPOCH_COUNTER.load(storage)?)) + epoch_key(key, EPOCH_COUNTER.load(storage)?) } pub fn previous_epoch_key(key: &str, storage: &dyn Storage) -> Result { - Ok(format!("{}/{key}", EPOCH_COUNTER.load(storage)? - 1)) + epoch_key(key, EPOCH_COUNTER.load(storage)? - 1) +} + +pub fn epoch_key(key: &str, epoch: usize) -> Result { + Ok(format!("{}/{key}", epoch)) }