feat: quartz-common and merging quartz-cli business logic from cycles-protocol (#123)

This commit is contained in:
Daniel Gushchyan 2024-07-30 10:55:52 -07:00 committed by GitHub
parent b2fba137c4
commit 63aa30db6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 8382 additions and 1027 deletions

View file

@ -37,6 +37,9 @@ jobs:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install protobuf-compiler
run: sudo apt-get install -y protobuf-compiler
- name: Install wasm32-unknown-unknown toolchain
uses: dtolnay/rust-toolchain@stable
with:

View file

@ -10,6 +10,8 @@ jobs:
steps:
- name: Checkout sources
uses: actions/checkout@v2
- name: Install protobuf-compiler
run: sudo apt-get install -y protobuf-compiler
- name: Install cargo-run-script
uses: actions-rs/cargo@v1
with:

2
.gitignore vendored
View file

@ -9,3 +9,5 @@
.idea/
target/
artifacts/
.vscode/
.DS_Store

613
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -2,10 +2,12 @@
resolver = "2"
members = [
"apps/mtcs/enclave",
"apps/mtcs/scripts",
"apps/transfers/enclave",
"cli",
"core/light-client-proofs/*",
"core/quartz",
"core/quartz-common",
"cosmwasm/packages/*",
"utils/*",
]
@ -76,15 +78,20 @@ mc-attestation-verifier = { git = "https://github.com/informalsystems/attestatio
# quartz
cw-proof = { path = "core/light-client-proofs/cw-proof", default-features = false }
cw-tee-mtcs = { path = "apps/mtcs/contracts/cw-tee-mtcs", default-features = false }
cycles-sync = { path = "utils/cycles-sync", default-features = false }
mtcs = { git = "ssh://git@github.com/informalsystems/mtcs.git", default-features = false }
quartz-common = { path = "core/quartz-common" }
quartz-cw = { path = "cosmwasm/packages/quartz-cw", default-features = false }
quartz-enclave = { path = "core/quartz", default-features = false }
quartz-proto = { path = "core/quartz-proto", default-features = false }
quartz-relayer = { path = "relayer", default-features = false }
quartz-tee-ra = { path = "cosmwasm/packages/quartz-tee-ra", default-features = false }
tm-prover = { path = "utils/tm-prover", default-features = false }
tm-stateless-verifier = { path = "core/light-client-proofs/tm-stateless-verifier", default-features = false }
# quartz apps
cw-tee-mtcs = { path = "apps/mtcs/contracts/cw-tee-mtcs", default-features = false }
mtcs = { git = "ssh://git@github.com/informalsystems/mtcs.git", default-features = false }
mtcs-enclave = { path = "apps/mtcs/enclave" }
transfers-contract = { path = "apps/transfers/contracts", default-features = false }
[profile.release]

View file

@ -43,7 +43,7 @@ checksum = "7378575ff571966e99a744addeff0bff98b8ada0dedf1956d59e634db95eaac1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
"synstructure",
]
@ -55,7 +55,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -96,9 +96,9 @@ checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
[[package]]
name = "bitflags"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "block-buffer"
@ -144,9 +144,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
[[package]]
name = "cc"
version = "1.0.103"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410"
checksum = "eaff6f8ce506b9773fa786672d63fc7a191ffea1be33f72bbd4aeacefca9ffc8"
[[package]]
name = "cfg-if"
@ -317,7 +317,7 @@ dependencies = [
"getrandom",
"hex",
"k256",
"quartz-cw",
"quartz-common",
"schemars",
"serde_json",
"sha2 0.10.8",
@ -384,9 +384,9 @@ dependencies = [
[[package]]
name = "darling"
version = "0.20.9"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1"
checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989"
dependencies = [
"darling_core",
"darling_macro",
@ -394,27 +394,27 @@ dependencies = [
[[package]]
name = "darling_core"
version = "0.20.9"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120"
checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
name = "darling_macro"
version = "0.20.9"
version = "0.20.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178"
checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -452,13 +452,13 @@ dependencies = [
[[package]]
name = "der_derive"
version = "0.7.2"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe87ce4529967e0ba1dcf8450bab64d97dfd5010a6256187ffe2e43e6f0e049"
checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -504,13 +504,13 @@ dependencies = [
[[package]]
name = "displaydoc"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -550,9 +550,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.12.0"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "elliptic-curve"
@ -810,9 +810,9 @@ source = "git+https://github.com/informalsystems/sgx#f25807776cbe10901f53d23fca5
[[package]]
name = "memchr"
version = "2.7.2"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
@ -832,9 +832,9 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
@ -933,9 +933,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.84"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
@ -960,7 +960,14 @@ dependencies = [
"itertools",
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
name = "quartz-common"
version = "0.1.0"
dependencies = [
"quartz-cw",
]
[[package]]
@ -1086,7 +1093,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -1111,9 +1118,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
@ -1129,13 +1136,13 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -1146,14 +1153,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
name = "serde_json"
version = "1.0.117"
version = "1.0.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5"
dependencies = [
"itoa",
"ryu",
@ -1162,9 +1169,9 @@ dependencies = [
[[package]]
name = "serde_with"
version = "3.8.1"
version = "3.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
checksum = "e73139bc5ec2d45e6c5fd85be5a46949c1c39a4c18e56915f5eb4c12f975e377"
dependencies = [
"serde",
"serde_derive",
@ -1173,14 +1180,14 @@ dependencies = [
[[package]]
name = "serde_with_macros"
version = "3.8.1"
version = "3.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
checksum = "b80d3d6b56b64335c0180e5ffde23b3c5e08c14c585b51a15bd0e95393f46703"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -1247,9 +1254,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.5.0"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
@ -1264,9 +1271,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.66"
version = "2.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5"
checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
dependencies = [
"proc-macro2",
"quote",
@ -1281,7 +1288,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -1301,7 +1308,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
]
[[package]]
@ -1386,7 +1393,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
"wasm-bindgen-shared",
]
@ -1408,7 +1415,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.66",
"syn 2.0.70",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -1430,9 +1437,9 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
@ -1446,51 +1453,51 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.5"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "x509-cert"

View file

@ -20,7 +20,8 @@ incremental = false
overflow-checks = true
[features]
mock-sgx = ["quartz-cw/mock-sgx"]
library = []
mock-sgx = ["quartz-common/mock-sgx-cw"]
[dependencies]
# external
@ -40,7 +41,7 @@ cw20 = "2.0.0"
cw2 = "2.0.0"
# quartz
quartz-cw = { path = "../../../../cosmwasm/packages/quartz-cw" }
quartz-common = { path = "../../../../core/quartz-common/", features = ["contract"]}
# patch indirect deps
getrandom = { version = "0.2.15", features = ["js"] }

View file

@ -1,13 +1,10 @@
use cosmwasm_std::{
entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
Uint128, Uint64,
Uint64,
};
use cw2::set_contract_version;
use cw20_base::{
contract::query_balance as cw20_query_balance,
state::{MinterData, TokenInfo, TOKEN_INFO},
};
use quartz_cw::{handler::RawHandler, state::EPOCH_COUNTER};
use cw20_base::contract::query_balance as cw20_query_balance;
use quartz_common::contract::{handler::RawHandler, state::EPOCH_COUNTER};
use crate::{
error::ContractError,
@ -19,7 +16,7 @@ use crate::{
ExecuteMsg, InstantiateMsg, QueryMsg,
},
state::{
current_epoch_key, LiquiditySourcesItem, ObligationsItem, State, LIQUIDITY_SOURCES_KEY,
current_epoch_key, ObligationsItem, State, LIQUIDITY_SOURCES, LIQUIDITY_SOURCES_KEY,
OBLIGATIONS_KEY, STATE,
},
};
@ -41,6 +38,7 @@ pub fn instantiate(
let state = State {
owner: info.sender.to_string(),
};
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
STATE.save(deps.storage, &state)?;
@ -50,22 +48,9 @@ pub fn instantiate(
ObligationsItem::new_dyn(current_epoch_key(OBLIGATIONS_KEY, deps.storage)?)
.save(deps.storage, &Default::default())?;
LiquiditySourcesItem::new_dyn(current_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(),
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)?;
// TODO: this can be removed. We don't need to instantiate liquidity sources, users will do so when submitting obligations
let epoch = current_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?;
LIQUIDITY_SOURCES.save(deps.storage, &epoch, &vec![])?;
Ok(Response::new()
.add_attribute("method", "instantiate")
@ -97,19 +82,21 @@ pub fn execute(
for o in obligations {
execute::submit_obligation(deps.branch(), o.ciphertext, o.digest)?;
}
execute::set_liquidity_sources(deps, liquidity_sources)?;
execute::append_liquidity_sources(deps, liquidity_sources)?;
Ok(Response::new())
}
ExecuteMsg::SubmitSetoffs(attested_msg) => {
let _ = attested_msg
.clone()
.handle_raw(deps.branch(), &env, &info)?;
// let _ = attested_msg
// .clone()
// .handle_raw(deps.branch(), &env, &info)?;
let SubmitSetoffsMsg { setoffs_enc } = attested_msg.msg.0;
execute::submit_setoffs(deps, env, setoffs_enc)
}
ExecuteMsg::InitClearing => execute::init_clearing(deps),
ExecuteMsg::SetLiquiditySources(SetLiquiditySourcesMsg { liquidity_sources }) => {
execute::set_liquidity_sources(deps, liquidity_sources)
execute::append_liquidity_sources(deps, liquidity_sources)?;
Ok(Response::new())
}
}
}
@ -117,15 +104,21 @@ pub fn execute(
pub mod execute {
use std::collections::BTreeMap;
use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, Uint64};
use cw20_base::contract::{execute_burn, execute_mint};
use k256::ecdsa::VerifyingKey;
use quartz_cw::state::{Hash, EPOCH_COUNTER};
use cosmwasm_std::{
to_json_binary, Addr, DepsMut, Env, HexBinary, MessageInfo, Response, StdResult, Storage,
SubMsg, Uint64, WasmMsg,
};
use cw20_base::contract::execute_mint;
use quartz_common::contract::state::{Hash, EPOCH_COUNTER};
// use mtcs_overdraft::msg::ExecuteMsg as OverdraftExecuteMsg;
use crate::msg::OverdraftExecuteMsg; // TODO: change when dependency issue fiexed
use crate::{
msg::execute::EscrowExecuteMsg,
state::{
current_epoch_key, previous_epoch_key, LiquiditySourcesItem, ObligationsItem, RawHash,
SetoffsItem, SettleOff, LIQUIDITY_SOURCES_KEY, OBLIGATIONS_KEY, SETOFFS_KEY,
current_epoch_key, previous_epoch_key, LiquiditySource, LiquiditySourceType,
ObligationsItem, RawHash, SetoffsItem, SettleOff, Transfer, LIQUIDITY_SOURCES,
LIQUIDITY_SOURCES_KEY, OBLIGATIONS_KEY, SETOFFS_KEY,
},
ContractError,
};
@ -160,15 +153,16 @@ pub mod execute {
let _: Hash = digest.to_array()?;
// store the `(digest, ciphertext)` tuple
ObligationsItem::new_dyn(current_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)
},
)?;
let obligs_key =
ObligationsItem::new_dyn(current_epoch_key(OBLIGATIONS_KEY, deps.storage)?);
let mut epoch_obligation = obligs_key.may_load(deps.storage)?.unwrap_or_default();
if let Some(_duplicate) = epoch_obligation.insert(digest.clone(), ciphertext.clone()) {
return Err(ContractError::DuplicateEntry);
}
obligs_key.save(deps.storage, &epoch_obligation)?;
Ok(Response::new()
.add_attribute("action", "submit_obligation")
@ -176,66 +170,162 @@ pub mod execute {
.add_attribute("ciphertext", ciphertext.to_string()))
}
pub fn set_liquidity_sources(
pub fn append_liquidity_sources(
deps: DepsMut,
liquidity_sources: Vec<HexBinary>,
) -> Result<Response, ContractError> {
// validate liquidity sources as public keys
liquidity_sources
.iter()
.try_for_each(|ls| VerifyingKey::from_sec1_bytes(ls).map(|_| ()))?;
new_liquidity_sources: Vec<LiquiditySource>,
) -> Result<(), ContractError> {
let epoch = current_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?;
let mut liquidity_sources = LIQUIDITY_SOURCES
.may_load(deps.storage, &epoch)?
.unwrap_or_default();
// store the liquidity sources
LiquiditySourcesItem::new_dyn(current_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?)
.update(deps.storage, |mut ls| {
ls.clear();
ls.extend(liquidity_sources);
Ok::<_, ContractError>(ls)
})?;
let mut new_sources = vec![];
for liquidity_source in new_liquidity_sources {
// Validate the Cosmos address
let address = deps
.api
.addr_validate(&liquidity_source.address.to_string())?;
Ok(Response::default())
let liquidity_source = LiquiditySource {
address: address.clone(),
source_type: liquidity_source.source_type,
};
new_sources.push(liquidity_source);
}
liquidity_sources.append(&mut new_sources);
// Save the new liquidity sources
LIQUIDITY_SOURCES.save(deps.storage, &epoch, &liquidity_sources)?;
Ok(())
}
pub fn submit_setoffs(
mut deps: DepsMut,
env: Env,
deps: DepsMut,
_env: Env,
setoffs_enc: BTreeMap<RawHash, SettleOff>,
) -> Result<Response, ContractError> {
// store the `BTreeMap<RawHash, RawCipherText>`
// Store the setoffs
SetoffsItem::new_dyn(previous_epoch_key(SETOFFS_KEY, deps.storage)?)
.save(deps.storage, &setoffs_enc)?;
let mut messages = vec![];
for (_, so) in setoffs_enc {
if let SettleOff::Transfer(t) = so {
let info = MessageInfo {
sender: env.contract.address.clone(),
funds: vec![],
};
// Check if either payer or payee is a liquidity source
let payer_source = find_liquidity_source(deps.storage, &t.payer)?;
let payee_source = find_liquidity_source(deps.storage, &t.payee)?;
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())?;
match (payer_source, payee_source) {
(Some(source), None) => {
// Payer is a liquidity source
let msg = create_transfer_message(&source, &t, true)?;
messages.push(msg);
}
(None, Some(source)) => {
// Payee is a liquidity source
let msg = create_transfer_message(&source, &t, false)?;
messages.push(msg);
}
(_, _) => {
// As of now, transfers should only be between a user and liquidity source.
return Err(ContractError::LiquiditySourceNotFound {});
}
}
}
}
Ok(Response::new().add_attribute("action", "submit_setoffs"))
Ok(Response::new()
.add_submessages(messages)
.add_attribute("action", "submit_setoffs"))
}
fn find_liquidity_source(
storage: &dyn Storage,
address: &Addr,
) -> Result<Option<LiquiditySource>, ContractError> {
// TODO: check that .ok() is correct here
let liquidity_sources = LIQUIDITY_SOURCES.load(
storage,
&previous_epoch_key(LIQUIDITY_SOURCES_KEY, storage)?,
)?;
Ok(liquidity_sources
.into_iter()
.find(|lqs| lqs.address == address))
}
fn create_transfer_message(
source: &LiquiditySource,
transfer: &Transfer,
is_payer: bool,
) -> Result<SubMsg, ContractError> {
let msg = match source.source_type {
LiquiditySourceType::Escrow => {
let (payer, payee, amount) = if is_payer {
(
transfer.payer.to_string(),
transfer.payee.to_string(),
vec![transfer.amount.clone()],
)
} else {
// If the liquidity source is the payee, we swap payer and payee
(
transfer.payee.to_string(),
transfer.payer.to_string(),
vec![transfer.amount.clone()],
)
};
WasmMsg::Execute {
contract_addr: source.address.to_string(),
msg: to_json_binary(&EscrowExecuteMsg::ExecuteSetoff {
payer,
payee,
amount,
})?,
funds: vec![],
}
}
LiquiditySourceType::Overdraft => {
if is_payer {
let increase_msg = WasmMsg::Execute {
contract_addr: source.address.to_string(),
msg: to_json_binary(&OverdraftExecuteMsg::IncreaseBalance {
receiver: transfer.payee.clone(),
amount: transfer.amount.1,
})?,
funds: vec![],
};
increase_msg
} else {
let decrease_msg = WasmMsg::Execute {
contract_addr: source.address.to_string(),
msg: to_json_binary(&OverdraftExecuteMsg::DecreaseBalance {
receiver: transfer.payer.clone(),
amount: transfer.amount.1,
})?,
funds: vec![],
};
decrease_msg
}
}
LiquiditySourceType::External => {
return Err(ContractError::UnsupportedLiquiditySource {})
}
};
Ok(SubMsg::new(msg))
}
pub fn init_clearing(deps: DepsMut) -> Result<Response, ContractError> {
EPOCH_COUNTER.update(deps.storage, |mut counter| -> StdResult<_> {
counter = counter.saturating_add(Uint64::from(1u64));
EPOCH_COUNTER.update(deps.storage, |counter| -> StdResult<_> {
counter.checked_add(Uint64::new(1))?;
Ok(counter)
})?;
Ok(Response::new().add_attribute("action", "init_clearing"))
@ -259,7 +349,7 @@ pub mod query {
use crate::{
msg::{GetAllSetoffsResponse, GetLiquiditySourcesResponse},
state::{
current_epoch_key, epoch_key, previous_epoch_key, LiquiditySourcesItem, SetoffsItem,
current_epoch_key, epoch_key, previous_epoch_key, SetoffsItem, LIQUIDITY_SOURCES,
LIQUIDITY_SOURCES_KEY, SETOFFS_KEY,
},
};
@ -272,6 +362,7 @@ pub mod query {
Ok(GetAllSetoffsResponse { setoffs })
}
// Function to get liquidity sources for a specific epoch
pub fn get_liquidity_sources(
deps: Deps,
epoch: Option<Uint64>,
@ -281,10 +372,8 @@ pub mod query {
Some(e) => epoch_key(LIQUIDITY_SOURCES_KEY, e)?,
};
let liquidity_sources = LiquiditySourcesItem::new_dyn(epoch_key)
.load(deps.storage)?
.into_iter()
.collect();
let liquidity_sources = LIQUIDITY_SOURCES.load(deps.storage, &epoch_key)?;
Ok(GetLiquiditySourcesResponse { liquidity_sources })
}
}

View file

@ -2,7 +2,7 @@ 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;
use quartz_common::contract::error::Error as QuartzError;
use thiserror::Error;
#[derive(Error, Debug)]
@ -16,9 +16,15 @@ pub enum ContractError {
#[error("Unauthorized")]
Unauthorized,
#[error("Liquidity source not found")]
LiquiditySourceNotFound,
#[error("Duplicate entry found")]
DuplicateEntry,
#[error("No entry found")]
NoLiquiditySourcesFound,
#[error("Not Secp256K1")]
K256(K256Error),
@ -30,6 +36,9 @@ pub enum ContractError {
#[error("Cw20 error: {0}")]
Cw20(Cw20ContractError),
#[error("Unsupported liquidity source")]
UnsupportedLiquiditySource,
}
impl From<K256Error> for ContractError {

View file

@ -1,11 +1,11 @@
#![deny(
warnings,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![forbid(unsafe_code)]
// #![deny(
// warnings,
// trivial_casts,
// trivial_numeric_casts,
// unused_import_braces,
// unused_qualifications
// )]
// #![forbid(unsafe_code)]
pub mod contract;
mod error;

View file

@ -1,15 +1,15 @@
use std::collections::BTreeMap;
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{HexBinary, Uint64};
use quartz_cw::{
use cosmwasm_std::{Addr, HexBinary, Uint128, Uint64};
use quartz_common::contract::{
msg::execute::attested::{RawAttested, RawAttestedMsgSansHandler, RawDefaultAttestation},
prelude::*,
};
use crate::state::{RawHash, SettleOff};
use crate::state::{LiquiditySource, RawHash, SettleOff};
type AttestedMsg<M, RA> = RawAttested<RawAttestedMsgSansHandler<M>, RA>;
pub type AttestedMsg<M, RA> = RawAttested<RawAttestedMsgSansHandler<M>, RA>;
#[cw_serde]
pub struct InstantiateMsg<RA = RawDefaultAttestation>(pub QuartzInstantiateMsg<RA>);
@ -27,11 +27,44 @@ pub enum ExecuteMsg<RA = RawDefaultAttestation> {
SetLiquiditySources(execute::SetLiquiditySourcesMsg),
}
// TODO: Added this back here because adding overdraft contract as a dependency is causing errors. Overdraft isn't correctly disabling entrypoints when acting as a dependency
#[cw_serde]
pub enum OverdraftExecuteMsg {
DrawCredit {
receiver: Addr,
amount: Uint128,
},
DrawCreditFromTender {
debtor: Addr,
amount: Uint128,
},
TransferCreditFromTender {
sender: Addr,
receiver: Addr,
amount: Uint128,
},
IncreaseBalance {
receiver: Addr,
amount: Uint128,
},
DecreaseBalance {
receiver: Addr,
amount: Uint128,
},
Lock {},
Unlock {},
AddOwner {
new: Addr,
},
}
pub mod execute {
use quartz_cw::{msg::execute::attested::HasUserData, state::UserData};
use cosmwasm_std::Uint128;
use quartz_common::contract::{msg::execute::attested::HasUserData, state::UserData};
use sha2::{Digest, Sha256};
use super::*;
use crate::state::LiquiditySource;
#[cw_serde]
pub struct FaucetMintMsg {
@ -56,7 +89,7 @@ pub mod execute {
#[cw_serde]
pub struct SubmitObligationsMsg {
pub obligations: Vec<SubmitObligationMsg>,
pub liquidity_sources: Vec<HexBinary>,
pub liquidity_sources: Vec<LiquiditySource>,
}
#[cw_serde]
@ -86,7 +119,16 @@ pub mod execute {
#[cw_serde]
pub struct SetLiquiditySourcesMsg {
pub liquidity_sources: Vec<HexBinary>,
pub liquidity_sources: Vec<LiquiditySource>,
}
#[cw_serde]
pub enum EscrowExecuteMsg {
ExecuteSetoff {
payer: String,
payee: String,
amount: Vec<(String, Uint128)>,
},
}
}
@ -109,7 +151,7 @@ pub struct GetAllSetoffsResponse {
#[cw_serde]
pub struct GetLiquiditySourcesResponse {
pub liquidity_sources: Vec<HexBinary>,
pub liquidity_sources: Vec<LiquiditySource>,
}
#[cfg(test)]

View file

@ -1,16 +1,15 @@
use std::collections::{BTreeMap, BTreeSet};
use std::{cmp::Ordering, collections::BTreeMap};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{HexBinary, StdError, Storage, Uint64};
use cw_storage_plus::Item;
use quartz_cw::state::EPOCH_COUNTER;
use cosmwasm_std::{Addr, HexBinary, StdError, Storage, Uint128, Uint64};
use cw_storage_plus::{Item, Map};
use quartz_common::contract::state::EPOCH_COUNTER;
pub type RawHash = HexBinary;
pub type RawCipherText = HexBinary;
pub type ObligationsItem = Item<BTreeMap<RawHash, RawCipherText>>;
pub type SetoffsItem = Item<BTreeMap<RawHash, SettleOff>>;
pub type LiquiditySourcesItem = Item<BTreeSet<HexBinary>>;
#[cw_serde]
pub struct State {
@ -19,9 +18,9 @@ pub struct State {
#[cw_serde]
pub struct Transfer {
pub payer: String,
pub payee: String,
pub amount: u64,
pub payer: Addr,
pub payee: Addr,
pub amount: (String, Uint128),
}
#[cw_serde]
@ -31,14 +30,43 @@ pub enum SettleOff {
Transfer(Transfer),
}
#[cw_serde]
#[derive(Copy)]
pub enum LiquiditySourceType {
Escrow,
Overdraft,
External,
}
#[cw_serde]
pub struct LiquiditySource {
pub address: Addr,
pub source_type: LiquiditySourceType,
}
impl std::cmp::Ord for LiquiditySource {
fn cmp(&self, other: &Self) -> Ordering {
self.address.cmp(&other.address)
}
}
impl PartialOrd for LiquiditySource {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.address.cmp(&other.address))
}
}
// PartialEq implemented in #[cw_serde]
impl Eq for LiquiditySource {}
pub const STATE: Item<State> = Item::new("state");
pub const OBLIGATIONS_KEY: &str = "obligations";
pub const SETOFFS_KEY: &str = "setoffs";
pub const LIQUIDITY_SOURCES_KEY: &str = "liquidity_sources";
pub const LIQUIDITY_SOURCES_KEY: &str = "epoch_liquidity_sources";
pub const LIQUIDITY_SOURCES: Map<&str, Vec<LiquiditySource>> = Map::new("liquidity_sources");
pub fn current_epoch_key(key: &str, storage: &dyn Storage) -> Result<String, StdError> {
let epoch = EPOCH_COUNTER.load(storage)?;
epoch_key(key, epoch.into())
epoch_key(key, EPOCH_COUNTER.load(storage)?)
}
pub fn previous_epoch_key(key: &str, storage: &dyn Storage) -> Result<String, StdError> {

View file

@ -1,11 +1,22 @@
[package]
name = "quartz-app-mtcs-enclave"
name = "mtcs-enclave"
version = "0.1.0"
edition = "2021"
authors = ["Informal Systems <hello@informal.systems>"]
[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]
mock-sgx = ["quartz-cw/mock-sgx", "quartz-enclave/mock-sgx"]
mock-sgx = ["quartz-common/mock-sgx-cw", "quartz-common/mock-sgx-enclave"]
[dependencies]
# external
@ -28,17 +39,14 @@ tendermint.workspace = true
tendermint-light-client.workspace = true
# quartz
cw-proof.workspace = true
cw-tee-mtcs.workspace = true
cycles-sync.workspace = true
mtcs.workspace = true
quartz-cw.workspace = true
quartz-enclave.workspace = true
quartz-proto.workspace = true
# quartz
quartz-common = { workspace = true, features = ["full"]}
[dev-dependencies]
cw-multi-test = "2.0.0"
serde_json = "1.0.113"
[build-dependencies]
tonic-build.workspace = true

View file

@ -0,0 +1 @@
pub mod proto;

View file

@ -15,6 +15,7 @@
mod cli;
mod mtcs_server;
mod proto;
mod types;
use std::{
sync::{Arc, Mutex},
@ -25,12 +26,14 @@ use clap::Parser;
use cli::Cli;
use mtcs_server::MtcsService;
use proto::clearing_server::ClearingServer as MtcsServer;
use quartz_cw::state::{Config, LightClientOpts};
use quartz_enclave::{
attestor::{Attestor, DefaultAttestor},
server::CoreService,
use quartz_common::{
contract::state::{Config, LightClientOpts},
enclave::{
attestor::{Attestor, DefaultAttestor},
server::CoreService,
},
proto::core_server::CoreServer,
};
use quartz_proto::quartz::core_server::CoreServer;
use tonic::transport::Server;
#[tokio::main(flavor = "current_thread")]

View file

@ -1,34 +1,37 @@
use std::{
collections::BTreeMap,
collections::{BTreeMap, BTreeSet},
sync::{Arc, Mutex},
};
use cosmrs::{tendermint::account::Id as TmAccountId, AccountId};
use cosmwasm_std::HexBinary;
use cosmwasm_std::{Addr, HexBinary, Uint128};
//TODO: get rid of this
use cw_tee_mtcs::{
msg::execute::SubmitSetoffsMsg,
state::{RawHash, SettleOff, Transfer},
state::{LiquiditySource, LiquiditySourceType, RawHash, SettleOff, Transfer},
};
use cycles_sync::types::RawObligation;
use ecies::{decrypt, encrypt};
use k256::ecdsa::{SigningKey, VerifyingKey};
use ecies::decrypt;
use k256::ecdsa::SigningKey;
use mtcs::{
algo::mcmf::primal_dual::PrimalDual, impls::complex_id::ComplexIdMtcs,
obligation::SimpleObligation, prelude::DefaultMtcs, setoff::SimpleSetoff, Mtcs,
};
use quartz_cw::{msg::execute::attested::RawAttested, state::Config};
use quartz_enclave::{attestor::Attestor, server::ProofOfPublication};
use quartz_common::{
contract::{msg::execute::attested::RawAttested, state::Config},
enclave::attestor::Attestor,
};
use serde::{Deserialize, Serialize};
use tonic::{Request, Response, Result as TonicResult, Status};
use crate::proto::{clearing_server::Clearing, RunClearingRequest, RunClearingResponse};
use crate::{
proto::{clearing_server::Clearing, RunClearingRequest, RunClearingResponse},
types::ContractObligation,
};
pub type RawCipherText = HexBinary;
#[derive(Clone, Debug)]
pub struct MtcsService<A> {
config: Config,
config: Config, // TODO: this config is not used anywhere
sk: Arc<Mutex<Option<SigningKey>>>,
attestor: A,
}
@ -36,7 +39,7 @@ pub struct MtcsService<A> {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RunClearingMessage {
intents: BTreeMap<RawHash, RawCipherText>,
liquidity_sources: Vec<HexBinary>,
liquidity_sources: BTreeSet<LiquiditySource>,
}
impl<A> MtcsService<A>
@ -61,40 +64,25 @@ where
&self,
request: Request<RunClearingRequest>,
) -> TonicResult<Response<RunClearingResponse>> {
let message: ProofOfPublication<RunClearingMessage> = {
let message: RunClearingMessage = {
let message = request.into_inner().message;
serde_json::from_str(&message).map_err(|e| Status::invalid_argument(e.to_string()))?
};
let (proof_value, message) = message
.verify(self.config.light_client_opts())
.map_err(Status::failed_precondition)?;
let proof_value_matches_msg =
serde_json::to_string(&message.intents).is_ok_and(|s| s.as_bytes() == proof_value);
if !proof_value_matches_msg {
return Err(Status::failed_precondition("proof verification"));
}
let digests_ciphertexts = message.intents;
// TODO: ensure no duplicates somewhere else!
let liquidity_sources: Vec<LiquiditySource> =
message.liquidity_sources.into_iter().collect();
let digests_ciphertexts: BTreeMap<HexBinary, HexBinary> = message.intents;
let (digests, ciphertexts): (Vec<_>, Vec<_>) = digests_ciphertexts.into_iter().unzip();
let sk = self.sk.lock().unwrap();
let obligations: Vec<SimpleObligation<_, i64>> = ciphertexts
let obligations: Vec<SimpleObligation<LiquiditySource, i64>> = ciphertexts
.into_iter()
.map(|ciphertext| decrypt_obligation(sk.as_ref().unwrap(), &ciphertext))
.collect();
let mut mtcs = ComplexIdMtcs::wrapping(DefaultMtcs::new(PrimalDual::default()));
let setoffs: Vec<SimpleSetoff<_, i64>> = mtcs.run(obligations).unwrap();
let liquidity_sources: Vec<_> = message
.liquidity_sources
.into_iter()
.map(|ls| VerifyingKey::from_sec1_bytes(&ls))
.collect::<Result<_, _>>()
.map_err(|e| Status::invalid_argument(e.to_string()))?;
let setoffs: Vec<SimpleSetoff<LiquiditySource, i64>> = mtcs.run(obligations).unwrap();
let setoffs_enc: BTreeMap<RawHash, SettleOff> = setoffs
.into_iter()
.map(|so| into_settle_offs(so, &liquidity_sources))
@ -103,7 +91,7 @@ where
.collect();
let msg = SubmitSetoffsMsg { setoffs_enc };
println!("setoff_msg: {:?}", msg);
let attestation = self
.attestor
.quote(msg.clone())
@ -111,65 +99,93 @@ where
let attested_msg = RawAttested { msg, attestation };
let message = serde_json::to_string(&attested_msg).unwrap();
Ok(Response::new(RunClearingResponse { message }))
}
}
// TODO Switch from Vec<_> to Vec<LiquiditySource>
fn into_settle_offs(
so: SimpleSetoff<HexBinary, i64>,
liquidity_sources: &[VerifyingKey],
so: SimpleSetoff<LiquiditySource, i64>,
liquidity_sources: &Vec<LiquiditySource>,
) -> SettleOff {
let debtor_pk = VerifyingKey::from_sec1_bytes(&so.debtor).unwrap();
let creditor_pk = VerifyingKey::from_sec1_bytes(&so.creditor).unwrap();
println!("\nsetoff: {:?}", so);
println!("\nliq sources: {:?}", liquidity_sources);
if let Some(ls_pk) = liquidity_sources.iter().find(|ls| ls == &&debtor_pk) {
// TODO: temporary patch, fix issue with liquidity sources becoming type External
if liquidity_sources
.iter()
.map(|lqs| lqs.address.clone())
.collect::<Vec<Addr>>()
.contains(&so.debtor.address)
{
// A setoff on a tender should result in the creditor's (i.e. the tender receiver) balance
// decreasing by the setoff amount
SettleOff::Transfer(Transfer {
payer: wasm_address(creditor_pk),
payee: wasm_address(*ls_pk),
amount: so.set_off as u64,
payer: so.creditor.address.clone(),
payee: so.debtor.address.clone(),
// TODO: Include denominations
amount: ("peppicoin".to_owned(), Uint128::from(so.set_off as u128)),
})
} else if let Some(ls_pk) = liquidity_sources.iter().find(|ls| ls == &&creditor_pk) {
} else if liquidity_sources
.iter()
.map(|lqs| lqs.address.clone())
.collect::<Vec<Addr>>()
.contains(&so.creditor.address)
{
// A setoff on an acceptance should result in the debtor's (i.e. the acceptance initiator)
// balance increasing by the setoff amount
SettleOff::Transfer(Transfer {
payer: wasm_address(*ls_pk),
payee: wasm_address(debtor_pk),
amount: so.set_off as u64,
payer: so.creditor.address.clone(),
payee: so.debtor.address.clone(),
amount: ("peppicoin".to_owned(), Uint128::from(so.set_off as u128)),
})
} else {
SettleOff::SetOff(encrypt_setoff(so, debtor_pk, creditor_pk))
// TODO: Tracked by issue #22
// A no-op for the time being.
SettleOff::SetOff(vec![])
}
}
fn wasm_address(pk: VerifyingKey) -> String {
let tm_pk = TmAccountId::from(pk);
AccountId::new("wasm", tm_pk.as_bytes())
.unwrap()
.to_string()
}
// fn wasm_address(pk: VerifyingKey) -> String {
// let tm_pk = TmAccountId::from(pk);
// AccountId::new("wasm", tm_pk.as_bytes())
// .unwrap()
// .to_string()
// }
fn encrypt_setoff(
so: SimpleSetoff<HexBinary, i64>,
debtor_pk: VerifyingKey,
creditor_pk: VerifyingKey,
) -> Vec<RawCipherText> {
let so_ser = serde_json::to_string(&so).expect("infallible serializer");
let so_debtor = encrypt(&debtor_pk.to_sec1_bytes(), so_ser.as_bytes()).unwrap();
let so_creditor = encrypt(&creditor_pk.to_sec1_bytes(), so_ser.as_bytes()).unwrap();
// fn encrypt_setoff(
// so: SimpleSetoff<HexBinary, i64>,
// debtor_pk: VerifyingKey,
// creditor_pk: VerifyingKey,
// ) -> Vec<RawCipherText> {
// let so_ser = serde_json::to_string(&so).expect("infallible serializer");
// let so_debtor = encrypt(&debtor_pk.to_sec1_bytes(), so_ser.as_bytes()).unwrap();
// let so_creditor = encrypt(&creditor_pk.to_sec1_bytes(), so_ser.as_bytes()).unwrap();
vec![so_debtor.into(), so_creditor.into()]
}
// vec![so_debtor.into(), so_creditor.into()]
// }
fn decrypt_obligation(
sk: &SigningKey,
ciphertext: &RawCipherText,
) -> SimpleObligation<HexBinary, i64> {
let o: RawObligation = {
) -> SimpleObligation<LiquiditySource, i64> {
let o: ContractObligation = {
let o = decrypt(&sk.to_bytes(), ciphertext).unwrap();
serde_json::from_slice(&o).unwrap()
};
SimpleObligation::new(None, o.debtor, o.creditor, i64::try_from(o.amount).unwrap()).unwrap()
SimpleObligation::new(
None,
LiquiditySource {
address: o.debtor,
source_type: LiquiditySourceType::External,
},
LiquiditySource {
address: o.creditor,
source_type: LiquiditySourceType::External,
},
i64::try_from(o.amount).unwrap(),
)
.unwrap()
}

View file

@ -0,0 +1,59 @@
use cosmwasm_std::{Addr, HexBinary};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContractObligation {
pub debtor: Addr,
pub creditor: Addr,
pub amount: u64,
#[serde(default)]
pub salt: HexBinary,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawObligation {
pub debtor: HexBinary,
pub creditor: HexBinary,
pub amount: u64,
#[serde(default)]
pub salt: HexBinary,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawEncryptedObligation {
pub digest: HexBinary,
pub ciphertext: HexBinary,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligationsMsg {
pub submit_obligations: SubmitObligationsMsgInner,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligationsMsgInner {
pub obligations: Vec<RawEncryptedObligation>,
pub liquidity_sources: Vec<Addr>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RawSetOff {
SetOff(Vec<HexBinary>),
Transfer(RawSetOffTransfer),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawSetOffTransfer {
pub payer: String,
pub payee: String,
pub amount: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawOffset {
pub debtor: HexBinary,
pub creditor: HexBinary,
pub amount: u64,
pub set_off: u64,
}

4723
apps/mtcs/scripts/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,58 @@
[package]
name = "scripts"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "listen"
path = "src/bin/listen.rs"
[[bin]]
name = "handshake"
path = "src/bin/handshake.rs"
[[bin]]
name = "deploy"
path = "src/bin/deploy.rs"
[dependencies]
clap.workspace = true
color-eyre.workspace = true
ecies.workspace = true
hex.workspace = true
k256.workspace = true
prost.workspace = true
serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tokio.workspace = true
tonic.workspace = true
once_cell = "1.19.0"
reqwest = { version = "0.12.2", default-features = false, features = ["json", "rustls-tls"] }
anyhow = "1.0.86"
base64 = "0.22.1"
subtle-encoding = "0.5.1"
tokio-tungstenite = "0.23.1"
futures-util = "0.3.30"
tendermint-rpc = { version ="0.36.0", features=["websocket-client", "http-client"]}
# cosmos
cosmrs.workspace = true
cosmwasm-std.workspace = true
tendermint.workspace = true
tendermint-light-client.workspace = true
# quartz
cw-proof.workspace = true
cw-tee-mtcs.workspace = true
mtcs.workspace = true
# todo remove unnecessary imports
cycles-sync = { workspace = true}
tm-prover = { workspace = true}
quartz-common = { workspace = true, features=["contract"]}
quartz-tee-ra = { workspace = true}
mtcs-enclave = { path = "../enclave", optional = false}
regex = "1.10.5"

View file

@ -0,0 +1,40 @@
set -eo pipefail
ROOT=${ROOT:-$HOME}
DIR_MTCS="$ROOT/cycles-protocol/quartz-app"
DIR_CYCLES_SYNC="$ROOT/cycles-protocol/packages/cycles-sync/"
DIR_PROTO="$DIR_MTCS/enclave/proto"
OVERDRAFT=wasm199rcvzawgyse89k4smqdn4wp83f3q8rurg9vautppxh5cypydafqk9nt6q
cd $DIR_MTCS
export NODE_URL=143.244.186.205:26657
bash scripts/build.sh
cd $DIR_MTCS/scripts/scripts
CONTRACT=$(RUST_BACKTRACE=1 cargo run --bin deploy | tail -n 1)
echo "------------ built and deployed ------------"
PUB_KEY=$(RUST_BACKTRACE=1 cargo run --bin handshake -- --contract $CONTRACT | tail -n 1)
echo "PUB KEY: '$PUB_KEY'"
echo "------------ shook some hands ------------"
cd $DIR_CYCLES_SYNC
cargo run --bin submit -- --epoch-pk $PUB_KEY --mtcs $CONTRACT --overdraft $OVERDRAFT
echo "cargo run --bin submit -- --epoch-pk $PUB_KEY --mtcs $CONTRACT --overdraft $OVERDRAFT"
echo "------------ submitted obligations ------------"
# add contract to owners list in overdrafts contract
CURRENT_SEQUENCE=$(wasmd query account wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70 --node http://$NODE_URL --output json | jq -r .sequence)
WASMD_OUTPUT=$(wasmd tx wasm execute $OVERDRAFT '{"add_owner": {"new": "'$CONTRACT'"}}' --from wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70 --node http://$NODE_URL --chain-id testing --yes --sequence $CURRENT_SEQUENCE)
echo $WASMD_OUTPUT
echo "------------ added contract as owner of overdrafts ------------"
cd $DIR_MTCS/scripts/scripts
cargo run --bin listen -- --contract $CONTRACT

View file

@ -0,0 +1,18 @@
#!/bin/bash
set -eo pipefail
ROOT=${ROOT:-$HOME}
echo "--------------------------------------------------------"
echo "building enclave binary"
cd $ROOT/cycles-protocol/quartz-app/enclave/
CARGO_TARGET_DIR=./target cargo build --release
echo "--------------------------------------------------------"
echo "building cosmwasm contract binary"
cd $ROOT/cycles-protocol/quartz-app/contracts/cw-tee-mtcs/
bash build.sh

23
apps/mtcs/scripts/deploy.sh Executable file
View file

@ -0,0 +1,23 @@
#!/bin/bash
set -eo pipefail
ROOT=${ROOT:-$HOME}
echo "--------------------------------------------------------"
echo "instantiate"
cd $ROOT/cycles-protocol/packages/relayer/
export INSTANTIATE_MSG=$(./scripts/relay.sh Instantiate | jq '{quartz: .}' )
echo "--------------------------------------------------------"
echo "deploy contract"
cd $ROOT/cycles-protocol/quartz-app/contracts/cw-tee-mtcs
bash deploy-contract.sh target/wasm32-unknown-unknown/release/cw_tee_mtcs.wasm |& tee output
export CONTRACT=$(cat output | grep Address | awk '{print $NF}' | sed 's/\x1b\[[0-9;]*m//g')
echo $CONTRACT

23
apps/mtcs/scripts/deploy45.sh Executable file
View file

@ -0,0 +1,23 @@
#!/bin/bash
set -eo pipefail
ROOT=${ROOT:-$HOME}
echo "--------------------------------------------------------"
echo "instantiate"
cd $ROOT/cycles-protocol/packages/relayer/
export INSTANTIATE_MSG=$(./scripts/relay.sh Instantiate | jq '{quartz: .}' )
echo "--------------------------------------------------------"
echo "deploy contract"
cd $ROOT/cycles-protocol/quartz-app/contracts/cw-tee-mtcs
bash deploy-contract.sh target/wasm32-unknown-unknown/release/cw_tee_mtcs.wasm |& tee output
export CONTRACT=$(cat output | grep Address | awk '{print $NF}' | sed 's/\x1b\[[0-9;]*m//g')
echo $CONTRACT

117
apps/mtcs/scripts/handshake.sh Executable file
View file

@ -0,0 +1,117 @@
#!/bin/bash
#
# Perform the SessionCreate and SessionSetPubKey handshake between the contract and the sgx node
# Expects:
# - enclave is already initialized
# - contract is already deployed
# - apps/transfers/trusted.hash exists
#
set -eo pipefail
ROOT=${ROOT:-$HOME}
NODE_URL=${NODE_URL:-127.0.0.1:26657}
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <contract_address>"
exit 1 # Exit with a non-zero status to indicate an error
fi
CONTRACT="$1"
CMD="wasmd --node http://$NODE_URL"
cd "$ROOT/cycles-protocol/quartz-app/"
export TRUSTED_HASH=$(cat trusted.hash)
export TRUSTED_HEIGHT=$(cat trusted.height)
echo "using CMD: $CMD"
echo "--------------------------------------------------------"
echo "create session"
# change to relay dir
cd $ROOT/cycles-protocol/packages/relayer
# execute SessionCreate on enclave
export EXECUTE_CREATE=$(./scripts/relay.sh SessionCreate)
# submit SessionCreate to contract
RES=$($CMD tx wasm execute "$CONTRACT" "$EXECUTE_CREATE" --from admin --chain-id testing -y --output json)
TX_HASH=$(echo $RES | jq -r '.["txhash"]')
# wait for tx to commit
while ! $CMD query tx $TX_HASH &> /dev/null; do
echo "... 🕐 waiting for tx"
sleep 1
done
# need to wait another block for light client proof
BLOCK_HEIGHT=$($CMD query block | jq .block.header.height)
echo "at heigh $BLOCK_HEIGHT. need to wait for a block"
while [[ $BLOCK_HEIGHT == $($CMD query block | jq .block.header.height) ]]; do
echo "... 🕐 waiting for another block"
sleep 1
done
# need to wait another block for light client proof
BLOCK_HEIGHT=$($CMD query block | jq .block.header.height)
echo "at heigh $BLOCK_HEIGHT. need to wait for a block"
while [[ $BLOCK_HEIGHT == $($CMD query block | jq .block.header.height) ]]; do
echo "... 🕐 waiting for another block"
sleep 1
done
echo "--------------------------------------------------------"
echo "set session pk"
# change to prover dir
cd $ROOT/cycles-protocol/packages/tm-prover
export PROOF_FILE="light-client-proof.json"
if [ -f "$PROOF_FILE" ]; then
rm "$PROOF_FILE"
echo "removed old $PROOF_FILE"
fi
# TODO: pass this in?
echo "trusted hash $TRUSTED_HASH"
echo "contract $CONTRACT"
# run prover to get light client proof
# TODO: assume this binary is pre-built?
# TODO: pass in addresses and chain id
cargo run -vvv -- --chain-id testing \
--primary "http://$NODE_URL" \
--witnesses "http://$NODE_URL" \
--trusted-height $TRUSTED_HEIGHT \
--trusted-hash $TRUSTED_HASH \
--contract-address $CONTRACT \
--storage-key "quartz_session" \
--trace-file $PROOF_FILE
export POP=$(cat $PROOF_FILE)
export POP_MSG=$(jq -nc --arg message "$POP" '$ARGS.named')
echo "hi"
# execute SessionSetPubKey on enclave
cd $ROOT/cycles-protocol/packages/relayer
export EXECUTE_SETPUB=$(./scripts/relay.sh SessionSetPubKey "$POP_MSG")
RES=$($CMD tx wasm execute "$CONTRACT" "$EXECUTE_SETPUB" --from admin --chain-id testing -y --output json)
TX_HASH=$(echo $RES | jq -r '.["txhash"]')
# wait for tx to commit
while ! $CMD query tx $TX_HASH &> /dev/null; do
echo "... 🕐 waiting for tx"
sleep 1
done
echo "--------------------------------------------------------"
echo "check session success"
export NONCE_AND_KEY=$($CMD query wasm contract-state raw "$CONTRACT" $(printf '%s' "quartz_session" | hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d)
echo $NONCE_AND_KEY
export PUBKEY=$(echo $NONCE_AND_KEY | jq -r .pub_key)

101
apps/mtcs/scripts/listen.sh Executable file
View file

@ -0,0 +1,101 @@
ROOT=${ROOT:-$HOME}
DIR_MTCS="$ROOT/cycles-protocol/quartz-app/"
DIR_PROTO="$DIR_MTCS/enclave/proto"
DEFAULT_NODE="127.0.0.1:26657"
NODE_URL="143.244.186.205:26657"
# Use the QUARTZ_PORT environment variable if set, otherwise default to 11090
QUARTZ_PORT="${QUARTZ_PORT:-11090}"
# Attestation constants
IAS_API_KEY="669244b3e6364b5888289a11d2a1726d"
RA_CLIENT_SPID="51CAF5A48B450D624AEFE3286D314894"
QUOTE_FILE="/tmp/${USER}_test.quote"
REPORT_FILE="/tmp/${USER}_datareport"
REPORT_SIG_FILE="/tmp/${USER}_datareportsig"
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <contract_address>"
exit 1 # Exit with a non-zero status to indicate an error
fi
CONTRACT=$1
CMD="wasmd --node http://$NODE_URL"
WSURL="ws://$NODE_URL/websocket"
SUBSCRIBE="{\"jsonrpc\":\"2.0\",\"method\":\"subscribe\",\"params\":[\"tm.event='Tx' AND wasm._contract_address = '$CONTRACT' AND wasm.action='init_clearing'\"],\"id\":1}"
echo $SUBSCRIBE
echo "--------------------------------------------------------"
echo "subscribe to events"
# cat keeps the stdin open so websocat doesnt close
(echo "$SUBSCRIBE"; cat) | websocat $WSURL | while read msg; do
if [[ "$msg" == '{"jsonrpc":"2.0","id":1,"result":{}}' ]]; then
echo "... subscribed"
echo "---------------------------------------------------------"
echo "... waiting for event"
continue
fi
if echo "$msg" | jq 'has("error")' > /dev/null; then
echo "... error msg $msg"
echo "---------------------------------------------------------"
echo "... waiting for event"
continue
fi
echo "... received init_clearing event!"
echo $msg
echo "... fetching obligations"
export EPOCH=$($CMD query wasm contract-state raw "$CONTRACT" "65706f63685f636f756e746572" -o json | jq -r .data | base64 -d)
PREV_EPOCH=$((EPOCH - 1))
export OBLIGATIONS=$($CMD query wasm contract-state raw "$CONTRACT" $(printf '%s/%s' "$PREV_EPOCH" "obligations" | hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d)
export LIQUIDITY_SOURCES=$($CMD query wasm contract-state smart $CONTRACT '{"get_liquidity_sources": {"epoch": '$PREV_EPOCH'}}' -o json | jq -r .data.liquidity_sources)
COMBINED_JSON=$(jq -nc \
--argjson intents "$OBLIGATIONS" \
--argjson liquidity_sources "$LIQUIDITY_SOURCES" \
'{intents: $intents, liquidity_sources: $liquidity_sources}')
echo $COMBINED_JSON
# Wrap the combined JSON string into another JSON object with a "message" field
REQUEST_MSG=$(jq -nc --arg message "$COMBINED_JSON" '{"message": $message}')
echo "... executing mtcs"
export ATTESTED_MSG=$(grpcurl -plaintext -import-path "$DIR_PROTO" -proto mtcs.proto -d "$REQUEST_MSG" "127.0.0.1:$QUARTZ_PORT" mtcs.Clearing/Run | jq -c '.message | fromjson')
QUOTE=$(echo "$ATTESTED_MSG" | jq -c '.attestation')
MSG=$(echo "$ATTESTED_MSG" | jq -c '.msg')
# request the IAS report for EPID attestations
echo -n "$QUOTE" | xxd -r -p - > "$QUOTE_FILE"
gramine-sgx-ias-request report -g "$RA_CLIENT_SPID" -k "$IAS_API_KEY" -q "$QUOTE_FILE" -r "$REPORT_FILE" -s "$REPORT_SIG_FILE" > /dev/null 2>&1
REPORT=$(cat "$REPORT_FILE")
REPORTSIG=$(cat "$REPORT_SIG_FILE" | tr -d '\r')
echo "... submitting update"
export EXECUTE=$(jq -nc --argjson submit_setoffs "$(jq -nc --argjson msg "$MSG" --argjson attestation \
"$(jq -nc --argjson report "$(jq -nc --argjson report "$REPORT" --arg reportsig "$REPORTSIG" '$ARGS.named')" '$ARGS.named')" \
'$ARGS.named')" '$ARGS.named')
$CMD tx wasm execute "$CONTRACT" "$EXECUTE" --from admin --chain-id testing -y --gas 2000000
echo " ... done"
echo "---------------------------------------------------------"
echo "... waiting for event"
done

View file

@ -0,0 +1,80 @@
use std::{env::current_dir, str::FromStr};
use clap::Parser;
use cosmrs::tendermint::chain::Id as ChainId;
use cw_tee_mtcs::msg::InstantiateMsg as MtcsInstantiateMsg;
use cycles_sync::wasmd_client::{CliWasmdClient, WasmdClient};
use quartz_common::contract::msg::RawInstantiateMsg;
use reqwest::Url;
use scripts::{
types::{Log, WasmdTxResponse},
utils::{block_tx_commit, run_relay},
};
use serde_json::json;
use tendermint::Hash;
use tendermint_rpc::HttpClient;
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[clap(long, default_value = "143.244.186.205:26657")]
node_url: String,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
let base_path = current_dir()?.join("../../../");
println!("\n🚀 Communicating with Relay to Instantiate...\n");
let init_msg: RawInstantiateMsg = run_relay(base_path.as_path(), "Instantiate", None)?; // need to define the return type
let init_msg: MtcsInstantiateMsg = MtcsInstantiateMsg(init_msg);
let httpurl = Url::parse(&format!("http://{}", cli.node_url))?;
let tmrpc_client = HttpClient::new(httpurl.as_str()).unwrap();
let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?);
println!("\n🚀 Deploying MTCS Contract\n");
let contract_path = base_path.join(
"quartz-app/contracts/cw-tee-mtcs/target/wasm32-unknown-unknown/release/cw_tee_mtcs.wasm",
);
// TODO: uncertain about the path -> string conversion
let deploy_output: WasmdTxResponse = serde_json::from_str(&wasmd_client.deploy(
&ChainId::from_str("testing")?,
String::from("wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70"),
contract_path.as_path().to_string_lossy(),
)?)?;
let tx_hash =
Hash::from_str(&deploy_output.txhash).expect("Invalid hex string for transaction hash");
let res = block_tx_commit(&tmrpc_client, tx_hash).await?;
let log: Vec<Log> = serde_json::from_str(&res.tx_result.log)?;
let code_id: usize = log[0].events[1].attributes[1].value.parse()?;
println!("\n🚀 Instantiating MTCS Contract\n");
let deploy_output: WasmdTxResponse = serde_json::from_str(&wasmd_client.init(
&ChainId::from_str("testing")?,
String::from("wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70"),
code_id,
json!(init_msg),
format!("MTCS Contract V{}", code_id),
)?)?;
let tx_hash =
Hash::from_str(&deploy_output.txhash).expect("Invalid hex string for transaction hash");
let res = block_tx_commit(&tmrpc_client, tx_hash).await?;
let log: Vec<Log> = serde_json::from_str(&res.tx_result.log)?;
let contract_addr: &String = &log[0].events[1].attributes[0].value;
println!("\n🚀 Successfully deployed and instantiated contract!");
println!("🆔 Code ID: {}", code_id);
println!("📌 Contract Address: {}", contract_addr);
println!("{contract_addr}");
Ok(())
}
//RES=$($CMD tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin --output json)

View file

@ -0,0 +1,229 @@
use std::{env, env::current_dir, fs::File, io::Read, path::Path, str::FromStr};
use anyhow::anyhow;
use clap::Parser;
use cosmrs::tendermint::chain::Id as ChainId; // TODO see if this redundancy in dependencies can be decreased
use cosmrs::AccountId;
use cw_tee_mtcs::msg::ExecuteMsg as MtcsExecuteMsg;
use cycles_sync::wasmd_client::{CliWasmdClient, WasmdClient};
use futures_util::stream::StreamExt;
use quartz_common::contract::prelude::QuartzExecuteMsg;
use reqwest::Url;
use scripts::{
types::WasmdTxResponse,
utils::{block_tx_commit, run_relay, wasmaddr_to_id},
};
use serde::Serialize;
use serde_json::json;
use tendermint::{block::Height, Hash};
use tendermint_rpc::{query::EventType, HttpClient, SubscriptionClient, WebSocketClient};
use tm_prover::{config::Config as TmProverConfig, prover::prove};
#[derive(Serialize)]
struct Message<'a> {
message: &'a str,
}
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
/// Contract to listen to
#[arg(short, long, value_parser = wasmaddr_to_id)]
contract: AccountId,
/// Port enclave is listening on
#[arg(short, long, default_value = "11090")]
port: u16,
#[arg(
short,
long,
default_value = "wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70"
)]
sender: String,
#[clap(long, default_value = "143.244.186.205:26657")]
node_url: String,
#[clap(long, default_value_t = default_rpc_addr())]
rpc_addr: String,
}
fn default_rpc_addr() -> String {
env::var("RPC_URL").unwrap_or_else(|_| "http://127.0.0.1".to_string())
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
// Convert contract address string parameter to an AccountId
// TODO: is this all the address validation that's needed?
let httpurl = Url::parse(&format!("http://{}", cli.node_url))?;
let wsurl = format!("ws://{}/websocket", cli.node_url);
let tmrpc_client = HttpClient::new(httpurl.as_str()).unwrap();
let wasmd_client = CliWasmdClient::new(Url::parse(httpurl.as_str())?);
// test(&client, &wasmd_client).await?;
// panic!();
// read trusted hash and height from files
let base_path = current_dir()?.join("../../../");
let trusted_files_path = base_path.join("quartz-app/");
let (trusted_height, trusted_hash) = read_hash_height(trusted_files_path.as_path()).await?;
// run sessioncreate in relayer script
// export EXECUTE_CREATE=$(./scripts/relay.sh SessionCreate)
// TODO: this is not the right return type
let res: MtcsExecuteMsg = run_relay(base_path.as_path(), "SessionCreate", None)?; // need to define the return type
// submit SessionCreate to contract
// RES=$($CMD tx wasm execute "$CONTRACT" "$EXECUTE_CREATE" --from admin --chain-id testing -y --output json)
// TX_HASH=$(echo $RES | jq -r '.["txhash"]')
// make sure this is json
let output: WasmdTxResponse = serde_json::from_str(
wasmd_client
.tx_execute(
&cli.contract.clone(),
&ChainId::from_str("testing")?,
2000000,
cli.sender.clone(),
json!(res),
)?
.as_str(),
)?;
println!("\n\n SessionCreate tx output: {:?}", output);
// wait for tx to commit (in a loop?)
let tx_hash = Hash::from_str(&output.txhash).expect("Invalid hex string for transaction hash");
block_tx_commit(&tmrpc_client, tx_hash).await?;
// tendermint client subscription loop
// wait 2 blocks
two_block_waitoor(&wsurl).await?;
//cd $ROOT/cycles-protocol/packages/tm-prover
//export PROOF_FILE="light-client-proof.json"
// TODO: move all the proof related files into a directory in scripts dir
let proof_path = current_dir()?.join("../../../packages/tm-prover/light-client-proof.json");
println!("Proof path: {:?}", proof_path.to_str());
let config = TmProverConfig {
primary: httpurl.as_str().parse()?,
witnesses: httpurl.as_str().parse()?,
trusted_height,
trusted_hash,
trace_file: Some(proof_path.clone()),
verbose: "1".parse()?,
contract_address: cli.contract.clone(),
storage_key: "quartz_session".to_string(),
..Default::default()
};
if let Err(report) = prove(config).await {
return Err(anyhow!("Tendermint prover failed. Report: {}", report));
}
// read proof file
let proof = read_file(proof_path.as_path()).await?;
let json_msg = serde_json::to_string(&Message { message: &proof })?;
// execute SessionSetPubKey on enclave
// cd $ROOT/cycles-protocol/packages/relayer
// export EXECUTE_SETPUB=$(./scripts/relay.sh SessionSetPubKey "$POP_MSG")
let res: MtcsExecuteMsg = run_relay(
base_path.as_path(),
"SessionSetPubKey",
Some(json_msg.as_str()),
)?;
// submit SessionSetPubKey to contract
// RES=$($CMD tx wasm execute "$CONTRACT" "$EXECUTE_SETPUB" --from admin --chain-id testing -y --output json)
// TX_HASH=$(echo $RES | jq -r '.["txhash"]')
// wait for tx to commit
let output: WasmdTxResponse = serde_json::from_str(
wasmd_client
.tx_execute(
&cli.contract.clone(),
&ChainId::from_str("testing")?,
2000000,
cli.sender.clone(),
json!(res),
)?
.as_str(),
)?;
println!("\n\n SessionSetPubKey tx output: {:?}", output);
// wait for tx to commit (in a loop?)
let tx_hash = Hash::from_str(&output.txhash).expect("Invalid hex string for transaction hash");
block_tx_commit(&tmrpc_client, tx_hash).await?;
if let MtcsExecuteMsg::Quartz(QuartzExecuteMsg::RawSessionSetPubKey(quartz)) = res {
println!("\n\n\n{}", quartz.msg.pub_key()); // TODO: return this instead later
} else {
return Err(anyhow!("Invalid relay response from SessionSetPubKey"));
}
// query results
Ok(())
}
async fn two_block_waitoor(wsurl: &str) -> Result<(), anyhow::Error> {
let (client, driver) = WebSocketClient::new(wsurl).await.unwrap();
let driver_handle = tokio::spawn(async move { driver.run().await });
// Subscription functionality
let mut subs = client.subscribe(EventType::NewBlock.into()).await.unwrap();
// Wait 2 NewBlock events
let mut ev_count = 5_i32;
while let Some(res) = subs.next().await {
let ev = res.unwrap();
println!("Got event: {:?}", ev);
ev_count -= 1;
if ev_count < 0 {
break;
}
}
// Signal to the driver to terminate.
client.close().unwrap();
// Await the driver's termination to ensure proper connection closure.
let _ = driver_handle.await.unwrap();
Ok(())
}
async fn read_hash_height(base_path: &Path) -> Result<(Height, Hash), anyhow::Error> {
let height_path = base_path.join("trusted.height");
let trusted_height: Height = read_file(height_path.as_path()).await?.parse()?;
let hash_path = base_path.join("trusted.hash");
let trusted_hash: Hash = read_file(hash_path.as_path()).await?.parse()?;
Ok((trusted_height, trusted_hash))
}
async fn read_file(path: &Path) -> Result<String, anyhow::Error> {
// Open the file
let mut file = match File::open(path) {
Ok(file) => file,
Err(e) => {
return Err(anyhow!(format!("Error opening file {:?}: {:?}", path, e)));
}
};
// Read the file contents into a string
let mut value = String::new();
if let Err(e) = file.read_to_string(&mut value) {
return Err(anyhow!(format!("Error reading file {:?}: {:?}", file, e)));
}
Ok(value.trim().to_owned())
}

View file

@ -0,0 +1,250 @@
use std::{
collections::{BTreeMap, BTreeSet},
env,
process::Command,
str::FromStr,
};
use anyhow::anyhow;
use base64::prelude::*;
use clap::Parser;
use cosmrs::{tendermint::chain::Id as ChainId, AccountId};
use cosmwasm_std::{Binary, HexBinary, Uint64};
use cw_tee_mtcs::{
msg::{
execute::SubmitSetoffsMsg, AttestedMsg, ExecuteMsg, GetLiquiditySourcesResponse,
QueryMsg::GetLiquiditySources,
},
state::LiquiditySource,
};
use cycles_sync::wasmd_client::{CliWasmdClient, QueryResult, WasmdClient};
use futures_util::stream::StreamExt;
use mtcs_enclave::proto::{clearing_client::ClearingClient, RunClearingRequest};
use quartz_common::contract::msg::execute::attested::{
EpidAttestation, RawAttested, RawAttestedMsgSansHandler,
};
use quartz_tee_ra::{intel_sgx::epid::types::ReportBody, IASReport};
use reqwest::Url;
use scripts::utils::wasmaddr_to_id;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tendermint_rpc::{
query::{EventType, Query},
SubscriptionClient, WebSocketClient,
};
use tokio::{
fs::{self, File},
io::AsyncWriteExt,
};
use tonic::Request;
// TODO: import this from enclave or somewhere shared
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RunClearingMessage {
intents: BTreeMap<HexBinary, HexBinary>,
liquidity_sources: BTreeSet<LiquiditySource>,
}
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
/// Contract to listen to
#[arg(short, long, value_parser = wasmaddr_to_id)]
contract: AccountId,
/// Port enclave is listening on
#[arg(short, long, default_value = "11090")]
port: u16,
#[arg(
short,
long,
default_value = "wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70"
)]
sender: String,
#[clap(long, default_value = "143.244.186.205:26657")]
node_url: String,
#[clap(long, default_value_t = default_rpc_addr())]
rpc_addr: String,
#[arg(short, long, default_value = "dangush")]
user: String, // The filesys user for gramine filepaths. TODO: improve this
}
fn default_rpc_addr() -> String {
env::var("RPC_URL").unwrap_or_else(|_| "http://127.0.0.1".to_string())
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
// Subscribe to "init_clearing" events
let wsurl = format!("ws://{}/websocket", cli.node_url);
let (client, driver) = WebSocketClient::new(wsurl.as_str()).await.unwrap();
let driver_handle = tokio::spawn(async move { driver.run().await });
let mut subs = client
.subscribe(Query::from(EventType::Tx).and_contains("wasm.action", "init_clearing"))
.await
.unwrap();
while subs.next().await.is_some() {
// On init_clearing, run process
if let Err(e) = handler(
&cli.contract,
cli.sender.clone(),
format!("{}:{}", cli.rpc_addr, cli.port),
&cli.node_url,
&cli.user,
)
.await
{
println!("{}", e);
}
}
// Close connection
// Await the driver's termination to ensure proper connection closure.
client.close().unwrap();
let _ = driver_handle.await.unwrap();
Ok(())
}
async fn handler(
contract: &AccountId,
sender: String,
rpc_addr: String,
node_url: &str,
user: &str,
) -> Result<(), anyhow::Error> {
let chain_id = &ChainId::from_str("testing")?;
let httpurl = Url::parse(&format!("http://{}", node_url))?;
let wasmd_client = CliWasmdClient::new(httpurl);
// Query obligations and liquidity sources from chain
let clearing_contents = query_chain(&wasmd_client, contract).await?;
// Send queried data to enclave over gRPC
let request = Request::new(RunClearingRequest {
message: json!(clearing_contents).to_string(),
});
let mut client = ClearingClient::connect(rpc_addr).await?;
let clearing_response = client
.run(request)
.await
.map_err(|e| anyhow!("Failed to communicate to relayer. {e}"))?
.into_inner();
// Extract json from the Protobuf message
let quote: RawAttested<SubmitSetoffsMsg, Vec<u8>> =
serde_json::from_str(&clearing_response.message)
.map_err(|e| anyhow!("Error serializing SubmitSetoffs: {}", e))?;
// Get IAS report and build attested message
let attestation = gramine_ias_request(quote.attestation, user).await?;
let msg = RawAttestedMsgSansHandler(quote.msg);
let setoffs_msg =
ExecuteMsg::SubmitSetoffs::<EpidAttestation>(AttestedMsg { msg, attestation });
// Send setoffs to mtcs contract on chain
let output =
wasmd_client.tx_execute(contract, chain_id, 2000000, sender, json!(setoffs_msg))?;
println!("output: {}", output);
Ok(())
}
// TODO: replace raw queries with smart
async fn query_chain(
wasmd_client: &CliWasmdClient,
contract: &AccountId,
) -> Result<RunClearingMessage, anyhow::Error> {
// Get epoch counter
let resp: QueryResult<String> = wasmd_client
.query_raw(contract, hex::encode("epoch_counter"))
.map_err(|e| anyhow!("Problem querying epoch: {}", e))?;
let mut epoch_counter: usize =
String::from_utf8(BASE64_STANDARD.decode(resp.data)?)?.parse::<usize>()?;
if epoch_counter > 1 {
epoch_counter -= 1;
}
// TODO: replace with tracer log here
// println!("epoch: {}", epoch_counter);
// Get obligations
let resp: QueryResult<String> = wasmd_client
.query_raw(
contract,
hex::encode(format!("{}/obligations", epoch_counter)),
)
.map_err(|e| anyhow!("Problem querying obligatons: {}", e))?;
let decoded_obligs = BASE64_STANDARD.decode(resp.data)?;
let obligations_map: BTreeMap<HexBinary, HexBinary> =
serde_json::from_slice(&decoded_obligs).unwrap_or_default();
// println!("obligations \n {:?}", obligations_map);
// TODO: replace with tracer log here
// Get liquidity sources
let resp: QueryResult<GetLiquiditySourcesResponse> = wasmd_client
.query_smart(
contract,
json!(GetLiquiditySources {
epoch: Some(Uint64::new(epoch_counter as u64))
}),
)
.map_err(|e| anyhow!("Problem querying liquidity sources: {}", e))?;
let liquidity_sources = resp.data.liquidity_sources;
// TODO: replace with tracer log here
// println!("liquidity_sources \n {:?}", liquidity_sources);
Ok(RunClearingMessage {
intents: obligations_map,
liquidity_sources: liquidity_sources.into_iter().collect(),
})
}
// Request the IAS report for EPID attestations
async fn gramine_ias_request(
attested_msg: Vec<u8>,
user: &str,
) -> Result<EpidAttestation, anyhow::Error> {
let ias_api_key = String::from("669244b3e6364b5888289a11d2a1726d");
let ra_client_spid = String::from("51CAF5A48B450D624AEFE3286D314894");
let quote_file = format!("/tmp/{}_test.quote", user);
let report_file = format!("/tmp/{}_datareport", user);
let report_sig_file = format!("/tmp/{}_datareportsig", user);
// Write the binary data to a file
let mut file = File::create(&quote_file).await?;
file.write_all(&attested_msg)
.await
.map_err(|e| anyhow!("Couldn't write to file. {e}"))?;
let mut gramine = Command::new("gramine-sgx-ias-request");
let command = gramine
.arg("report")
.args(["-g", &ra_client_spid])
.args(["-k", &ias_api_key])
.args(["-q", &quote_file])
.args(["-r", &report_file])
.args(["-s", &report_sig_file]);
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("Couldn't run gramine. {:?}", output));
}
let report: ReportBody = serde_json::from_str(&fs::read_to_string(report_file).await?)?;
let report_sig_str = fs::read_to_string(report_sig_file).await?.replace('\r', "");
let report_sig: Binary = Binary::from_base64(report_sig_str.trim())?;
Ok(EpidAttestation::new(IASReport { report, report_sig }))
}

View file

@ -0,0 +1,3 @@
//TODO: make an error.rs to differentiate errors in listen.rs
pub mod types;
pub mod utils;

View file

@ -0,0 +1,39 @@
use serde::{Deserialize, Serialize};
// Rust libraries don't seem to implement this type from the wasmd go implementation
// TODO: Replace String with types from Rust libraries
// TODO: Move this into WasmdClient
#[derive(Deserialize, Debug)]
pub struct WasmdTxResponse {
pub height: String,
pub txhash: String,
pub codespace: String,
pub code: u32,
pub data: String,
pub raw_log: String,
pub logs: Vec<serde_json::Value>,
pub info: String,
pub gas_wanted: String,
pub gas_used: String,
pub tx: Option<serde_json::Value>,
pub timestamp: String,
pub events: Vec<serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Attribute {
pub key: String,
pub value: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Event {
pub attributes: Vec<Attribute>,
pub r#type: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Log {
pub events: Vec<Event>,
pub msg_index: u32,
}

View file

@ -0,0 +1,81 @@
use std::{path::Path, process::Command, time::Duration};
use anyhow::anyhow;
use cosmrs::AccountId;
use regex::Regex;
use serde::de::DeserializeOwned;
use subtle_encoding::bech32::decode as bech32_decode;
use tendermint::Hash;
use tendermint_rpc::{
endpoint::tx::Response as TmTxResponse, error::ErrorDetail, Client, HttpClient,
};
pub fn wasmaddr_to_id(address_str: &str) -> anyhow::Result<AccountId> {
let (hr, _) = bech32_decode(address_str).map_err(|e| anyhow!(e))?;
if hr != "wasm" {
return Err(anyhow!(hr));
}
Ok(address_str.parse().unwrap())
}
// TODO: move wrapping result with "quartz:" struct into here
pub fn run_relay<R: DeserializeOwned>(
base_path: &Path,
msg: &str,
arg: Option<&str>,
) -> Result<R, anyhow::Error> {
let relayer_path = base_path.join("packages/relayer/scripts/relay.sh");
let mut bash = Command::new("bash");
let command = bash.arg(relayer_path).arg(msg);
if let Some(arg) = arg {
command.arg(arg);
}
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
let query_result: R = serde_json::from_slice(&output.stdout)
.map_err(|e| anyhow!("Error deserializing: {}", e))?;
Ok(query_result)
}
// Note: time until tx commit is empiraclly 800ms on DO wasmd chain.
pub async fn block_tx_commit(client: &HttpClient, tx: Hash) -> Result<TmTxResponse, anyhow::Error> {
let re = Regex::new(r"tx \([A-F0-9]{64}\) not found").unwrap();
tokio::time::sleep(Duration::from_millis(400)).await;
loop {
match client.tx(tx, false).await {
Ok(resp) => {
return Ok(resp);
}
Err(e) => {
// If error, make sure it is only because of a not yet committed tx
match e.0 {
ErrorDetail::Response(subdetail) => {
if !re.is_match(subdetail.source.data().unwrap_or_default()) {
return Err(anyhow!(
"Error querying for tx: {}",
ErrorDetail::Response(subdetail)
));
} else {
println!("🔗 Waiting for tx commit... (+400ms)");
tokio::time::sleep(Duration::from_millis(400)).await;
continue;
}
}
_ => {
return Err(anyhow!("Error querying for tx: {}", e.0));
}
}
}
}
}
}

98
apps/mtcs/scripts/start.sh Executable file
View file

@ -0,0 +1,98 @@
#!/bin/bash
#set -eo pipefail
ROOT=${ROOT:-$HOME}
DIR_QUARTZ="$ROOT/cycles-protocol"
DIR_QUARTZ_APP="$DIR_QUARTZ/quartz-app/"
DIR_QUARTZ_ENCLAVE="$DIR_QUARTZ/quartz-app/enclave"
DIR_QUARTZ_TM_PROVER="$DIR_QUARTZ/packages/tm-prover"
NODE_URL=${NODE_URL:-143.244.186.205:26657}
CMD="wasmd --node http://$NODE_URL"
echo "--------------------------------------------------------"
echo "set trusted hash"
cd "$DIR_QUARTZ_TM_PROVER"
# cargo run -- --chain-id testing \
# --primary "http://$NODE_URL" \
# --witnesses "http://$NODE_URL" \
# --trusted-height 1 \
# --trusted-hash "5237772462A41C0296ED688A0327B8A60DF310F08997AD760EB74A70D0176C27" \
# --contract-address "wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d" \
# --storage-key "quartz_session" \
# --trace-file light-client-proof.json &> $DIR_QUARTZ_APP/output
# # Debug output of cargo run
# echo "Cargo run output:"
# cat $DIR_QUARTZ_APP/output
# cd $DIR_QUARTZ_APP
# # Debug hash extraction
# echo "Attempting to extract trusted hash from output..."
# cat output | grep found | head -1 | awk '{print $NF}' | sed 's/\x1b\[[0-9;]*m//g' > trusted.hash
# # Check if the hash was extracted correctly
# if [[ ! -s trusted.hash ]]; then
# echo "Failed to extract trusted hash from output"
# exit 1
# fi
# export TRUSTED_HASH=$(cat trusted.hash)
# echo "Extracted TRUSTED_HASH: $TRUSTED_HASH"
# rm output
CHAIN_STATUS=$($CMD status)
TRUSTED_HASH=$(echo "$CHAIN_STATUS" | jq -r .SyncInfo.latest_block_hash)
TRUSTED_HEIGHT=$(echo "$CHAIN_STATUS" | jq -r .SyncInfo.latest_block_height)
echo "... $TRUSTED_HASH"
cd "$DIR_QUARTZ_APP"
echo "$TRUSTED_HASH" > trusted.hash
echo "$TRUSTED_HEIGHT" > trusted.height
echo "--------------------------------------------------------"
echo "configure gramine"
cd "$DIR_QUARTZ_ENCLAVE"
echo "... gen priv key if it doesnt exist"
gramine-sgx-gen-private-key > /dev/null 2>&1 || : # may fail
# echo "... update manifest template with trusted hash $TRUSTED_HASH"
# sed -i -r "s/(\"--trusted-hash\", \")[A-Z0-9]+(\"])/\1$TRUSTED_HASH\2/" quartz.manifest.template
echo "... create manifest"
gramine-manifest \
-Dlog_level="error" \
-Dhome="$HOME" \
-Darch_libdir="/lib/$(gcc -dumpmachine)" \
-Dra_type="epid" \
-Dra_client_spid="51CAF5A48B450D624AEFE3286D314894" \
-Dra_client_linkable=1 \
-Dquartz_dir="$(pwd)" \
-Dtrusted_height="$TRUSTED_HEIGHT" \
-Dtrusted_hash="$TRUSTED_HASH" \
quartz.manifest.template quartz.manifest
if [ $? -ne 0 ]; then
echo "gramine-manifest failed"
exit 1
fi
echo "... sign manifest"
gramine-sgx-sign --manifest quartz.manifest --output quartz.manifest.sgx
if [ $? -ne 0 ]; then
echo "gramine-sgx-sign failed"
exit 1
fi
echo "--------------------------------------------------------"
echo "... start gramine"
gramine-sgx ./quartz
if [ $? -ne 0 ]; then
echo "gramine-sgx failed to start"
exit 1
fi

128
apps/mtcs/scripts/start45.sh Executable file
View file

@ -0,0 +1,128 @@
ROOT=${ROOT:-$HOME}
DIR_QUARTZ="$ROOT/cycles-protocol"
DIR_QUARTZ_APP="$DIR_QUARTZ/quartz-app/contracts/cw-tee-mtcs"
DIR_QUARTZ_ENCLAVE="$DIR_QUARTZ/quartz-app/enclave"
DIR_QUARTZ_TM_PROVER="$DIR_QUARTZ/packages/tm-prover"
NODE_URL=${NODE_URL:-143.244.186.205:36657}
CMD="wasmd --node http://$NODE_URL"
echo "--------------------------------------------------------"
echo "set trusted hash"
cd "$DIR_QUARTZ_TM_PROVER"
cargo run -- --chain-id testing \
--primary "http://$NODE_URL" \
--witnesses "http://$NODE_URL" \
--trusted-height 500000 \
--trusted-hash "5237772462A41C0296ED688A0327B8A60DF310F08997AD760EB74A70D0176C27" \
--contract-address "wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d" \
--storage-key "quartz_session" \
--trace-file light-client-proof.json &> $DIR_QUARTZ_APP/output
cd $DIR_QUARTZ_APP
cat output | grep found | head -1 | awk '{print $NF}' | sed 's/\x1b\[[0-9;]*m//g' > trusted.hash
export TRUSTED_HASH=$(cat trusted.hash)
echo "... $TRUSTED_HASH"
rm output
echo "--------------------------------------------------------"
echo "configure gramine"
cd "$DIR_QUARTZ_ENCLAVE"
echo "... gen priv key if it doesnt exist"
gramine-sgx-gen-private-key > /dev/null 2>&1 || : # may fail
echo "... update manifest template with trusted hash $TRUSTED_HASH"
sed -i -r "s/(\"--trusted-hash\", \")[A-Z0-9]+(\"])/\1$TRUSTED_HASH\2/" quartz.manifest.template
echo "... create manifest"
gramine-manifest \
-Dlog_level="error" \
-Dhome="$HOME" \
-Darch_libdir="/lib/$(gcc -dumpmachine)" \
-Dra_type="epid" \
-Dra_client_spid="51CAF5A48B450D624AEFE3286D314894" \
-Dra_client_linkable=1 \
-Dquartz_dir="$(pwd)" \
-Dtrusted_height="$TRUSTED_HEIGHT" \
-Dtrusted_hash="$TRUSTED_HASH" \
quartz.manifest.template quartz.manifest
echo "... sign manifest"
gramine-sgx-sign --manifest quartz.manifest --output quartz.manifest.sgx
echo "--------------------------------------------------------"
echo "... start gramine"
gramine-sgx ./quartz
# #set -eo pipefail
# ROOT=${ROOT:-$HOME}
# DIR_QUARTZ="$ROOT/quartz-app"
# DIR_QUARTZ_APP="$DIR_QUARTZ/contracts/cw-tee-mtcs"
# DIR_QUARTZ_ENCLAVE="$DIR_QUARTZ/enclave"
# DIR_QUARTZ_TM_PROVER="$DIR_QUARTZ/packages/tm-prover"
# NODE_URL=${NODE_URL:-143.244.186.205:36657}
# CMD="wasmd --node http://$NODE_URL"
# echo "--------------------------------------------------------"
# echo "set trusted hash"
# cd "$DIR_QUARTZ_TM_PROVER"
# cargo run -- --chain-id testing \
# --primary "http://$NODE_URL" \
# --witnesses "http://$NODE_URL" \
# --trusted-height 500000 \
# --trusted-hash "5237772462A41C0296ED688A0327B8A60DF310F08997AD760EB74A70D0176C27" \
# --contract-address "wasm14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9s0phg4d" \
# --storage-key "quartz_session" \
# --trace-file light-client-proof.json &> $DIR_QUARTZ_APP/output
# cd $DIR_QUARTZ_APP
# cat output | grep found | head -1 | awk '{print $NF}' | sed 's/\x1b\[[0-9;]*m//g' > trusted.hash
# export TRUSTED_HASH=$(cat trusted.hash)
# echo "... $TRUSTED_HASH"
# rm output
# echo "--------------------------------------------------------"
# echo "configure gramine"
# cd "$DIR_QUARTZ_ENCLAVE"
# echo "... gen priv key if it doesnt exist"
# gramine-sgx-gen-private-key > /dev/null 2>&1 || : # may fail
# echo "... update manifest template with trusted hash $TRUSTED_HASH"
# sed -i -r "s/(\"--trusted-hash\", \")[A-Z0-9]+(\"])/\1$TRUSTED_HASH\2/" quartz.manifest.template
# echo "... create manifest"
# gramine-manifest \
# -Dlog_level="error" \
# -Dhome="$HOME" \
# -Darch_libdir="/lib/$(gcc -dumpmachine)" \
# -Dra_type="epid" \
# -Dra_client_spid="51CAF5A48B450D624AEFE3286D314894" \
# -Dra_client_linkable=1 \
# -Dquartz_dir="$(pwd)" \
# -Dtrusted_height="$TRUSTED_HEIGHT" \
# -Dtrusted_hash="$TRUSTED_HASH" \
# quartz.manifest.template quartz.manifest
# echo "... sign manifest"
# gramine-sgx-sign --manifest quartz.manifest --output quartz.manifest.sgx
# echo "--------------------------------------------------------"
# echo "... start gramine"
# gramine-sgx ./quartz

View file

@ -0,0 +1,40 @@
OVERDRAFT=wasm1huhuswjxfydydxvdadqqsaet2p72wshtmr72yzx09zxncxtndf2sqs24hk
CMD='wasmd --node http://$NODE_URL'
# users
ALICE=wasm124tuy67a9dcvfgcr4gjmz60syd8ddaugl33v0n
BOB=wasm1ctkqmg45u85jnf5ur9796h7ze4hj6ep5y7m7l6
# query alice
$CMD query wasm contract-state smart $OVERDRAFT '{"balance": {"user": "'$ALICE'"}}'
# query bob
$CMD query wasm contract-state smart $OVERDRAFT '{"balance": {"user": "'$BOB'"}}'
# make obligation from alice to bob for 10
# $CMD tx wasm execute $CONTRACT '{"submit_obligation_msg": {"ciphertext": "", "digest": ""}}' --from $CONTRACT --chain-id testing
# make bob acceptance to overdraft for 10
# make alice tender from overdraft for 10
# init clearing
$CMD tx wasm execute $CONTRACT '"init_clearing"' --from $CONTRACT --chain-id testing
# wait for 2 sec
sleep 2
# query alice
$CMD query wasm contract-state smart $OVERDRAFT '{"balance": {"user": "'$ALICE'"}}'
# query bob
$CMD query wasm contract-state smart $OVERDRAFT '{"balance": {"user": "'$BOB'"}}'

File diff suppressed because it is too large Load diff

View file

@ -24,7 +24,8 @@ incremental = false
overflow-checks = true
[features]
mock-sgx = ["quartz-cw/mock-sgx"]
library = []
mock-sgx = ["quartz-common/mock-sgx-cw"]
[dependencies]
# external
@ -42,7 +43,4 @@ cw20-base = { version = "2.0.0", features = ["library"] }
cw2 = "2.0.0"
# quartz
quartz-cw = { path = "../../../cosmwasm/packages/quartz-cw" }
# patch indirect deps
getrandom = { version = "0.2.15", features = ["js"] }
quartz-common = { path = "../../../core/quartz-common", features=["contract"]}

View file

@ -2,7 +2,7 @@ use cosmwasm_std::{
entry_point, to_json_binary, Binary, Deps, DepsMut, Env, HexBinary, MessageInfo, Response,
StdResult,
};
use quartz_cw::handler::RawHandler;
use quartz_common::contract::handler::RawHandler;
use crate::{
error::ContractError,

View file

@ -1,7 +1,7 @@
use cosmwasm_std::StdError;
use cw20_base::ContractError as Cw20ContractError;
use cw_utils::PaymentError;
use quartz_cw::error::Error as QuartzError;
use quartz_common::contract::error::Error as QuartzError;
use thiserror::Error;
#[derive(Error, Debug)]

View file

@ -1,9 +1,8 @@
use cosmwasm_schema::cw_serde;
use quartz_cw::{
use quartz_common::contract::{
msg::execute::attested::{RawAttested, RawAttestedMsgSansHandler, RawDefaultAttestation},
prelude::*,
};
use serde::{Deserialize, Serialize};
type AttestedMsg<M, RA = RawDefaultAttestation> = RawAttested<RawAttestedMsgSansHandler<M>, RA>;
@ -41,7 +40,7 @@ pub enum ExecuteMsg<RA = RawDefaultAttestation> {
pub mod execute {
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, HexBinary, Uint128};
use quartz_cw::{msg::execute::attested::HasUserData, state::UserData};
use quartz_common::contract::{msg::execute::attested::HasUserData, state::UserData};
use sha2::{Digest, Sha256};
#[cw_serde]

View file

@ -10,7 +10,7 @@ name = "encrypt"
path = "bin/encrypt.rs"
[features]
mock-sgx = ["quartz-cw/mock-sgx", "quartz-enclave/mock-sgx"]
mock-sgx = ["quartz-common/mock-sgx-cw", "quartz-common/mock-sgx-enclave"]
[dependencies]
# external
@ -34,12 +34,15 @@ cosmwasm-std.workspace = true
tendermint.workspace = true
tendermint-light-client.workspace = true
# quartz
cycles-sync.workspace = true
quartz-cw.workspace = true
quartz-proto.workspace = true
quartz-enclave.workspace = true
transfers-contract.workspace = true
# quartz
quartz-common = { workspace=true, features=["full"]}
[dev-dependencies]
cw-multi-test = "0.17.0"
[build-dependencies]
tonic-build.workspace = true

View file

@ -25,12 +25,14 @@ use std::{
use clap::Parser;
use cli::Cli;
use proto::settlement_server::SettlementServer as TransfersServer;
use quartz_cw::state::{Config, LightClientOpts};
use quartz_enclave::{
attestor::{Attestor, DefaultAttestor},
server::CoreService,
use quartz_common::{
contract::state::{Config, LightClientOpts},
enclave::{
attestor::{Attestor, DefaultAttestor},
server::CoreService,
},
proto::core_server::CoreServer,
};
use quartz_proto::quartz::core_server::CoreServer;
use tonic::transport::Server;
use transfers_server::TransfersService;

View file

@ -6,11 +6,13 @@ use std::{
use cosmwasm_std::{Addr, HexBinary, Uint128};
use ecies::{decrypt, encrypt};
use k256::ecdsa::{SigningKey, VerifyingKey};
use quartz_cw::{
msg::execute::attested::{HasUserData, RawAttested},
state::{Config, UserData},
use quartz_common::{
contract::{
msg::execute::attested::{HasUserData, RawAttested},
state::{Config, UserData},
},
enclave::{attestor::Attestor, server::ProofOfPublication},
};
use quartz_enclave::{attestor::Attestor, server::ProofOfPublication};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tonic::{Request, Response, Result as TonicResult, Status};

View file

@ -0,0 +1,22 @@
[package]
name = "quartz-common"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
authors.workspace = true
[features]
full = ["contract", "enclave", "proto"]
contract = ["dep:quartz-cw"]
enclave = ["dep:quartz-enclave", "proto"]
proto = ["dep:quartz-proto"]
mock-sgx-cw = ["quartz-cw/mock-sgx"]
mock-sgx-enclave = ["quartz-enclave/mock-sgx"]
[dependencies]
quartz-cw = { workspace = true, optional = true }
quartz-proto = { workspace = true, optional = true }
quartz-enclave = { workspace = true, optional = true }

View file

@ -0,0 +1,6 @@
#[cfg(feature = "contract")]
pub use quartz_cw as contract;
#[cfg(feature = "enclave")]
pub use quartz_enclave as enclave;
#[cfg(feature = "proto")]
pub use quartz_proto::quartz as proto;

View file

@ -33,8 +33,6 @@ tendermint-light-client.workspace = true
# quartz
cw-proof.workspace = true
cw-tee-mtcs.workspace = true
cycles-sync.workspace = true
mtcs.workspace = true
quartz-cw.workspace = true
quartz-proto.workspace = true

View file

@ -3,6 +3,7 @@ use std::{convert::Into, default::Default};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{HexBinary, StdError};
use quartz_tee_ra::IASReport;
use serde::Serialize;
#[cfg(not(feature = "mock-sgx"))]
pub type DefaultAttestation = EpidAttestation;
@ -90,7 +91,7 @@ pub trait HasUserData {
fn user_data(&self) -> UserData;
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct EpidAttestation {
report: IASReport,
}

View file

@ -31,6 +31,12 @@ pub struct RawSessionSetPubKey {
pub_key: HexBinary,
}
impl RawSessionSetPubKey {
pub fn pub_key(&self) -> &HexBinary {
&self.pub_key
}
}
impl TryFrom<RawSessionSetPubKey> for SessionSetPubKey {
type Error = StdError;

View file

@ -23,6 +23,9 @@ REQUEST_MSG=${2:-"{}"}
# Use the QUARTZ_PORT environment variable if set, otherwise default to 11090
QUARTZ_PORT="${QUARTZ_PORT:-11090}"
# clear tmp files from previous runs
rm -f "$QUOTE_FILE" "$REPORT_FILE" "$REPORT_SIG_FILE"
# query the gRPC quartz enclave service
ATTESTED_MSG=$(grpcurl -plaintext -import-path "$DIR_PROTO" -proto quartz.proto -d "$REQUEST_MSG" "127.0.0.1:$QUARTZ_PORT" quartz.Core/"$REQUEST" | jq -c '.message | fromjson')

View file

@ -9,6 +9,13 @@ repository.workspace = true
keywords = ["blockchain", "cosmos", "tendermint", "cycles", "quartz"]
readme = "README.md"
[lib]
path = "src/lib.rs"
[[bin]]
name = "submit"
path = "src/bin/submit.rs"
[dependencies]
# external
async-trait.workspace = true
@ -26,10 +33,14 @@ tokio.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
uuid.workspace = true
anyhow ={ version = "*"}
# cosmos
cosmrs.workspace = true
cosmwasm-std.workspace = true
# quartz
cw-tee-mtcs = { path = "../../apps/mtcs/contracts/cw-tee-mtcs/" }
[dev-dependencies]
rand_core.workspace = true

View file

@ -0,0 +1,174 @@
use std::str::FromStr;
use anyhow::anyhow;
use bip32::secp256k1::{
ecdsa::VerifyingKey,
sha2::{Digest, Sha256},
};
use clap::Parser;
use cosmrs::{tendermint::chain::Id as TmChainId, AccountId};
use cosmwasm_std::{Addr, HexBinary};
use cw_tee_mtcs::state::{LiquiditySource, LiquiditySourceType};
use cycles_sync::{
types::{
ContractObligation, RawEncryptedObligation, RawSetOff, SubmitObligationsMsg,
SubmitObligationsMsgInner,
},
wasmd_client::{CliWasmdClient, WasmdClient},
};
use reqwest::Url;
use serde::{Deserialize, Serialize};
use subtle_encoding::bech32::decode as bech32_decode;
// const MNEMONIC_PHRASE: &str = "clutch debate vintage foster barely primary clown leader sell manual leopard ladder wet must embody story oyster imitate cable alien six square rice wedding";
const ADDRESS_PREFIX: &str = "wasm";
type Sha256Digest = [u8; 32];
#[derive(Clone, Debug, Serialize, Deserialize)]
struct QueryAllSetoffsResponse {
setoffs: Vec<(HexBinary, RawSetOff)>,
}
#[derive(Clone, Debug, Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[arg(short, long, value_parser = wasmaddr_to_id)]
mtcs: AccountId,
#[arg(short, long)]
epoch_pk: String,
#[arg(short, long)]
overdraft: String,
#[clap(long)]
flip: bool,
#[arg(
short,
long,
default_value = "wasm14qdftsfk6fwn40l0xmruga08xlczl4g05npy70"
)]
admin: String,
}
#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
let cli = Cli::parse();
let mut alice = Addr::unchecked("wasm124tuy67a9dcvfgcr4gjmz60syd8ddaugl33v0n");
let mut bob = Addr::unchecked("wasm1ctkqmg45u85jnf5ur9796h7ze4hj6ep5y7m7l6");
let overdraft = Addr::unchecked(cli.overdraft);
if cli.flip {
let temp = alice.clone();
alice = bob;
bob = temp;
}
let alice_to_bob: ContractObligation = ContractObligation {
debtor: alice.clone(),
creditor: bob.clone(),
amount: 10,
salt: HexBinary::from([0; 64]),
};
let bob_acceptance: ContractObligation = ContractObligation {
debtor: bob.clone(),
creditor: overdraft.clone(),
amount: 10,
salt: HexBinary::from([0; 64]),
};
let alice_tender: ContractObligation = ContractObligation {
debtor: overdraft.clone(),
creditor: alice.clone(),
amount: 10,
salt: HexBinary::from([0; 64]),
};
let intents = vec![alice_to_bob, bob_acceptance, alice_tender];
let epoch_pk = VerifyingKey::from_sec1_bytes(&hex::decode(cli.epoch_pk).unwrap()).unwrap();
let intents_enc = encrypt_overdraft_intents(intents, &epoch_pk);
let liquidity_sources: Vec<LiquiditySource> = vec![LiquiditySource {
address: overdraft,
source_type: LiquiditySourceType::Overdraft,
}];
let msg = create_wasm_msg(intents_enc, liquidity_sources)?;
let node_url = Url::parse("http://143.244.186.205:26657")?;
let chain_id = TmChainId::from_str("testing")?;
let wasmd_client = CliWasmdClient::new(node_url);
wasmd_client.tx_execute(&cli.mtcs, &chain_id, 3000000, cli.admin.to_string(), msg)?;
Ok(())
}
pub struct OverdraftObligation {
pub debtor: Addr,
pub creditor: Addr,
pub amount: u64,
}
fn encrypt_overdraft_intents(
intents: Vec<ContractObligation>,
epoch_pk: &VerifyingKey,
) -> Vec<(Sha256Digest, Vec<u8>)> {
let mut intents_enc = vec![];
for i in intents {
// serialize intent
let i_ser = serde_json::to_string(&i).unwrap();
// encrypt intent
let i_cipher = ecies::encrypt(&epoch_pk.to_sec1_bytes(), i_ser.as_bytes()).unwrap();
// hash intent
let i_digest: Sha256Digest = {
let mut hasher = Sha256::new();
hasher.update(i_ser);
hasher.finalize().into()
};
intents_enc.push((i_digest, i_cipher));
}
intents_enc
}
fn create_wasm_msg(
obligations_enc: Vec<(Sha256Digest, Vec<u8>)>,
liquidity_sources: Vec<LiquiditySource>,
) -> anyhow::Result<serde_json::Value> {
let obligations_enc: Vec<_> = obligations_enc
.into_iter()
.map(|(digest, ciphertext)| {
let digest = HexBinary::from(digest);
let ciphertext = HexBinary::from(ciphertext);
RawEncryptedObligation { digest, ciphertext }
})
.collect();
let msg = SubmitObligationsMsg {
submit_obligations: SubmitObligationsMsgInner {
obligations: obligations_enc,
liquidity_sources,
},
};
serde_json::to_value(msg).map_err(Into::into)
}
fn wasmaddr_to_id(address_str: &str) -> anyhow::Result<AccountId> {
let (hr, _) = bech32_decode(address_str).map_err(|e| anyhow!(e))?;
if hr != ADDRESS_PREFIX {
return Err(anyhow!(hr));
}
Ok(address_str.parse().unwrap())
}

View file

@ -8,7 +8,7 @@ use subtle_encoding::{bech32::decode as bech32_decode, Error as Bech32DecodeErro
use thiserror::Error;
use uuid::Uuid;
use crate::ADDRESS_PREFIX;
const ADDRESS_PREFIX: &str = "wasm";
#[derive(Clone, Debug, Parser)]
#[command(author, version, about)]

View file

@ -1 +1,4 @@
pub mod cli;
pub mod obligato_client;
pub mod types;
pub mod wasmd_client;

View file

@ -27,7 +27,7 @@ use crate::{
obligato_client::{http::HttpClient, Client},
types::{
Obligation, ObligatoObligation, ObligatoSetOff, RawEncryptedObligation, RawObligation,
RawOffset, RawSetOff, SubmitObligationsMsg, SubmitObligationsMsgInner,
RawOffset, RawSetOff, SubmitObligatioMsg, SubmitObligatoObligationsMsgInner,
},
wasmd_client::{CliWasmdClient, QueryResult, WasmdClient},
};
@ -112,10 +112,8 @@ async fn sync_setoffs(cli: Cli) -> Result<(), DynError> {
.flat_map(|(obligation_digest, so)| match so {
RawSetOff::SetOff(sos_enc) => {
let so_enc = sos_enc.first().unwrap();
let (debtor_id, creditor_id) = obligation_user_map
.get(obligation_digest)
.map(Clone::clone)
.unwrap();
let (debtor_id, creditor_id) =
obligation_user_map.get(obligation_digest).copied().unwrap();
let sk = |id| keys[&id].private_key().to_bytes();
let so_ser = if let Ok(so) = ecies::decrypt(&sk(debtor_id), so_enc.as_slice()) {
@ -202,8 +200,8 @@ fn create_wasm_msg(
.map(|pk| HexBinary::from(pk.to_sec1_bytes().as_ref()))
.collect();
let msg = SubmitObligationsMsg {
submit_obligations: SubmitObligationsMsgInner {
let msg = SubmitObligatioMsg {
submit_obligations: SubmitObligatoObligationsMsgInner {
obligations: obligations_enc,
liquidity_sources,
},
@ -377,7 +375,7 @@ mod tests {
#[test]
fn test_create_mnemonic() {
// Generate random Mnemonic using the default language (English)
let mnemonic = Mnemonic::random(&mut OsRng, Default::default());
let mnemonic = Mnemonic::random(OsRng, Default::default());
println!("{}", mnemonic.phrase());
}

View file

@ -1,5 +1,6 @@
use bip32::secp256k1::ecdsa::VerifyingKey;
use cosmwasm_std::HexBinary;
use cosmwasm_std::{Addr, HexBinary};
use cw_tee_mtcs::state::LiquiditySource;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
@ -11,6 +12,15 @@ pub struct ObligatoObligation {
pub amount: u64,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ContractObligation {
pub debtor: Addr,
pub creditor: Addr,
pub amount: u64,
#[serde(default)]
pub salt: HexBinary,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RawObligation {
pub debtor: HexBinary,
@ -52,6 +62,17 @@ pub struct SubmitObligationsMsg {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligationsMsgInner {
pub obligations: Vec<RawEncryptedObligation>,
pub liquidity_sources: Vec<LiquiditySource>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligatioMsg {
pub submit_obligations: SubmitObligatoObligationsMsgInner,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SubmitObligatoObligationsMsgInner {
pub obligations: Vec<RawEncryptedObligation>,
pub liquidity_sources: Vec<HexBinary>,
}

View file

@ -1,22 +1,30 @@
use std::{error::Error, process::Command};
use std::process::Command;
use anyhow::anyhow;
use cosmrs::{tendermint::chain::Id, AccountId};
use hex::ToHex;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use tracing::debug;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
pub trait WasmdClient {
type Address: AsRef<str>;
type Query: ToString;
type RawQuery: ToHex;
type ChainId: AsRef<str>;
type Error;
fn query_smart<R: FromVec>(
fn query_smart<R: DeserializeOwned>(
&self,
contract: &Self::Address,
query: Self::Query,
) -> Result<R, Self::Error>;
fn query_raw<R: DeserializeOwned + Default>(
&self,
contract: &Self::Address,
query: Self::RawQuery,
) -> Result<R, Self::Error>;
fn tx_execute<M: ToString>(
&self,
contract: &Self::Address,
@ -24,24 +32,30 @@ pub trait WasmdClient {
gas: u64,
sender: String,
msg: M,
) -> Result<(), Self::Error>;
) -> Result<String, Self::Error>;
fn deploy<M: ToString>(
&self,
chain_id: &Id,
sender: String, // what should this type be
wasm_path: M,
) -> Result<String, Self::Error>;
fn init<M: ToString>(
&self,
chain_id: &Id,
sender: String,
code_id: usize,
init_msg: M,
label: String,
) -> Result<String, Self::Error>;
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct QueryResult<T> {
pub data: T,
}
pub trait FromVec: Sized {
fn from_vec(value: Vec<u8>) -> Self;
}
impl<T: for<'any> Deserialize<'any>> FromVec for T {
fn from_vec(value: Vec<u8>) -> Self {
serde_json::from_slice(&value).unwrap()
}
}
#[derive(Clone, Debug)]
pub struct CliWasmdClient {
url: Url,
@ -56,10 +70,11 @@ impl CliWasmdClient {
impl WasmdClient for CliWasmdClient {
type Address = AccountId;
type Query = serde_json::Value;
type RawQuery = String;
type ChainId = Id;
type Error = Box<dyn Error>;
type Error = anyhow::Error;
fn query_smart<R: FromVec>(
fn query_smart<R: DeserializeOwned>(
&self,
contract: &Self::Address,
query: Self::Query,
@ -73,9 +88,34 @@ impl WasmdClient for CliWasmdClient {
.args(["--output", "json"]);
let output = command.output()?;
debug!("{:?} => {:?}", command, output);
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
let query_result = R::from_vec(output.stdout);
let query_result: R = serde_json::from_slice(&output.stdout)
.map_err(|e| anyhow!("Error deserializing: {}", e))?;
Ok(query_result)
}
fn query_raw<R: DeserializeOwned + Default>(
&self,
contract: &Self::Address,
query: Self::RawQuery,
) -> Result<R, Self::Error> {
let mut wasmd = Command::new("wasmd");
let command = wasmd
.args(["--node", self.url.as_str()])
.args(["query", "wasm"])
.args(["contract-state", "raw", contract.as_ref()])
.arg(&query)
.args(["--output", "json"]);
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
let query_result: R = serde_json::from_slice(&output.stdout).unwrap_or_default();
Ok(query_result)
}
@ -86,7 +126,7 @@ impl WasmdClient for CliWasmdClient {
gas: u64,
sender: String,
msg: M,
) -> Result<(), Self::Error> {
) -> Result<String, Self::Error> {
let mut wasmd = Command::new("wasmd");
let command = wasmd
.args(["--node", self.url.as_str()])
@ -99,12 +139,73 @@ impl WasmdClient for CliWasmdClient {
.arg("-y");
let output = command.output()?;
debug!("{:?} => {:?}", command, output);
if output.status.success() {
println!("{}", String::from_utf8(output.stdout).unwrap());
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
Ok(())
// TODO: find the rust type for the tx output and return that
Ok((String::from_utf8(output.stdout)?).to_string())
}
fn deploy<M: ToString>(
&self,
chain_id: &Id,
sender: String,
wasm_path: M,
) -> Result<String, Self::Error> {
let mut wasmd = Command::new("wasmd");
let command = wasmd
.args(["--node", self.url.as_str()])
.args(["tx", "wasm", "store", &wasm_path.to_string()])
.args(["--from", sender.as_ref()])
.args(["--chain-id", chain_id.as_ref()])
.args(["--gas-prices", "0.0025ucosm"])
.args(["--gas", "auto"])
.args(["--gas-adjustment", "1.3"])
.args(["-o", "json"])
.arg("-y");
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
// TODO: find the rust type for the tx output and return that
Ok((String::from_utf8(output.stdout)?).to_string())
}
fn init<M: ToString>(
&self,
chain_id: &Id,
sender: String,
code_id: usize,
init_msg: M,
label: String,
) -> Result<String, Self::Error> {
let mut wasmd = Command::new("wasmd");
let command = wasmd
.args(["--node", self.url.as_str()])
.args(["tx", "wasm", "instantiate"])
.args([&code_id.to_string(), &init_msg.to_string()])
.args(["--label", label.as_ref()])
.args(["--from", sender.as_ref()])
.arg("--no-admin")
.args(["--chain-id", chain_id.as_ref()])
.args(["--gas-prices", "0.0025ucosm"])
.args(["--gas", "auto"])
.args(["--gas-adjustment", "1.3"])
.args(["-o", "json"])
.arg("-y");
let output = command.output()?;
if !output.status.success() {
return Err(anyhow!("{:?}", output));
}
// TODO: find the rust type for the tx output and return that
Ok((String::from_utf8(output.stdout)?).to_string())
}
}

View file

@ -9,6 +9,10 @@ repository.workspace = true
keywords = ["blockchain", "cosmos", "tendermint", "cycles", "quartz"]
readme = "README.md"
[[bin]]
name = "main"
path = "src/main.rs"
[dependencies]
# external
clap.workspace = true

View file

@ -0,0 +1,147 @@
use std::{num::ParseIntError, path::PathBuf, str::FromStr};
use clap::Parser;
use color_eyre::eyre::{eyre, Result};
use cosmrs::AccountId;
use cw_proof::proof::cw::RawCwProof;
use serde::{Deserialize, Serialize};
use tendermint_light_client::types::{Hash, Height, LightBlock, TrustThreshold};
use tendermint_rpc::HttpClientUrl;
use tracing::metadata::LevelFilter;
pub fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> {
if let Some((l, r)) = s.split_once('/') {
TrustThreshold::new(l.parse()?, r.parse()?).map_err(Into::into)
} else {
Err(eyre!(
"invalid trust threshold: {s}, format must be X/Y where X and Y are integers"
))
}
}
#[derive(Clone, Debug)]
pub struct List<T>(pub Vec<T>);
impl<E, T: FromStr<Err = E>> FromStr for List<T> {
type Err = E;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(',')
.map(|s| s.parse())
.collect::<Result<Vec<_>, _>>()
.map(Self)
}
}
#[derive(clap::Args, Debug, Clone)]
pub struct Verbosity {
/// Increase verbosity, can be repeated up to 2 times
#[arg(long, short, action = clap::ArgAction::Count)]
pub verbose: u8,
}
impl Verbosity {
pub fn to_level_filter(&self) -> LevelFilter {
match self.verbose {
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
}
}
fn default() -> Self {
Self { verbose: 0 }
}
}
impl FromStr for Verbosity {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let verbose: u8 = s.parse()?;
Ok(Self { verbose })
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProofOutput {
pub light_client_proof: Vec<LightBlock>,
pub merkle_proof: RawCwProof,
}
// TODO: Investigate if it's possible to derive default using Clap's default values
impl Default for Config {
fn default() -> Self {
Config {
chain_id: String::default(),
primary: "http://127.0.0.1:26657".parse().unwrap(),
witnesses: "[]".parse().unwrap(),
trusted_height: Height::default(),
trusted_hash: Hash::default(),
trust_threshold: TrustThreshold::TWO_THIRDS,
trusting_period: 1209600u64,
max_clock_drift: 5u64,
max_block_lag: 5u64,
trace_file: None,
verbose: Verbosity::default(),
contract_address: "".parse().unwrap(),
storage_key: String::default(),
}
}
}
#[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)]
pub struct Config {
/// Identifier of the chain
#[clap(long)]
pub chain_id: String,
/// Primary RPC address
#[clap(long, default_value = "http://127.0.0.1:26657")]
pub primary: HttpClientUrl,
/// Comma-separated list of witnesses RPC addresses
#[clap(long)]
pub witnesses: List<HttpClientUrl>,
/// Height of trusted header
#[clap(long)]
pub trusted_height: Height,
/// Hash of trusted header
#[clap(long)]
pub trusted_hash: Hash,
/// Trust threshold
#[clap(long, value_parser = parse_trust_threshold, default_value_t = TrustThreshold::TWO_THIRDS)]
pub trust_threshold: TrustThreshold,
/// Trusting period, in seconds (default: two weeks)
#[clap(long, default_value = "1209600")]
pub trusting_period: u64,
/// Maximum clock drift, in seconds
#[clap(long, default_value = "5")]
pub max_clock_drift: u64,
/// Maximum block lag, in seconds
#[clap(long, default_value = "5")]
pub max_block_lag: u64,
/// Output file to store light client proof (AKA verification trace)
#[clap(long)]
pub trace_file: Option<PathBuf>,
/// Increase verbosity
#[clap(flatten)]
pub verbose: Verbosity,
/// Address of the CosmWasm contract
#[clap(long)]
pub contract_address: AccountId,
/// Storage key of the state item for which proofs must be retrieved
#[clap(long)]
pub storage_key: String,
}

View file

@ -0,0 +1,2 @@
pub mod config;
pub mod prover;

View file

@ -1,361 +1,12 @@
#![deny(
warnings,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![forbid(unsafe_code)]
use std::{
fs::File,
io::{BufWriter, Write},
path::PathBuf,
str::FromStr,
time::Duration,
};
use clap::Parser;
use color_eyre::{
eyre::{eyre, Result},
Report,
};
use cosmrs::AccountId;
use cw_proof::{
error::ProofError,
proof::{
cw::{CwProof, RawCwProof},
key::CwAbciKey,
Proof,
},
};
use futures::future::join_all;
use serde::{Deserialize, Serialize};
use tendermint::{crypto::default::Sha256, evidence::Evidence};
use tendermint_light_client::{
builder::LightClientBuilder,
light_client::Options,
store::memory::MemoryStore,
types::{Hash, Height, LightBlock, TrustThreshold},
};
use tendermint_light_client_detector::{detect_divergence, Error, Provider, Trace};
use tendermint_rpc::{client::HttpClient, Client, HttpClientUrl};
use tracing::{error, info, metadata::LevelFilter};
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};
const WASM_STORE_KEY: &str = "/store/wasm/key";
fn parse_trust_threshold(s: &str) -> Result<TrustThreshold> {
if let Some((l, r)) = s.split_once('/') {
TrustThreshold::new(l.parse()?, r.parse()?).map_err(Into::into)
} else {
Err(eyre!(
"invalid trust threshold: {s}, format must be X/Y where X and Y are integers"
))
}
}
#[derive(Clone, Debug)]
struct List<T>(Vec<T>);
impl<E, T: FromStr<Err = E>> FromStr for List<T> {
type Err = E;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split(',')
.map(|s| s.parse())
.collect::<Result<Vec<_>, _>>()
.map(Self)
}
}
#[derive(clap::Args, Debug, Clone)]
struct Verbosity {
/// Increase verbosity, can be repeated up to 2 times
#[arg(long, short, action = clap::ArgAction::Count)]
verbose: u8,
}
impl Verbosity {
fn to_level_filter(&self) -> LevelFilter {
match self.verbose {
0 => LevelFilter::INFO,
1 => LevelFilter::DEBUG,
_ => LevelFilter::TRACE,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct ProofOutput {
light_client_proof: Vec<LightBlock>,
merkle_proof: RawCwProof,
}
#[derive(Debug, Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
/// Identifier of the chain
#[clap(long)]
chain_id: String,
/// Primary RPC address
#[clap(long, default_value = "http://127.0.0.1:26657")]
primary: HttpClientUrl,
/// Comma-separated list of witnesses RPC addresses
#[clap(long)]
witnesses: List<HttpClientUrl>,
/// Height of trusted header
#[clap(long)]
trusted_height: Height,
/// Hash of trusted header
#[clap(long)]
trusted_hash: Hash,
/// Trust threshold
#[clap(long, value_parser = parse_trust_threshold, default_value_t = TrustThreshold::TWO_THIRDS)]
trust_threshold: TrustThreshold,
/// Trusting period, in seconds (default: two weeks)
#[clap(long, default_value = "1209600")]
trusting_period: u64,
/// Maximum clock drift, in seconds
#[clap(long, default_value = "5")]
max_clock_drift: u64,
/// Maximum block lag, in seconds
#[clap(long, default_value = "5")]
max_block_lag: u64,
/// Output file to store light client proof (AKA verification trace)
#[clap(long)]
trace_file: Option<PathBuf>,
/// Increase verbosity
#[clap(flatten)]
verbose: Verbosity,
/// Address of the CosmWasm contract
#[clap(long)]
contract_address: AccountId,
/// Storage key of the state item for which proofs must be retrieved
#[clap(long)]
storage_key: String,
}
use color_eyre::eyre::Result;
use tm_prover::{config::Config, prover::prove};
#[tokio::main]
async fn main() -> Result<()> {
color_eyre::install()?;
let args = Cli::parse();
let args = Config::parse();
let env_filter = EnvFilter::builder()
.with_default_directive(args.verbose.to_level_filter().into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(false)
.with_env_filter(env_filter)
.finish()
.init();
let options = Options {
trust_threshold: args.trust_threshold,
trusting_period: Duration::from_secs(args.trusting_period),
clock_drift: Duration::from_secs(args.max_clock_drift),
};
let mut primary = make_provider(
&args.chain_id,
args.primary.clone(),
args.trusted_height,
args.trusted_hash,
options,
)
.await?;
let client = HttpClient::builder(args.primary.clone()).build()?;
let trusted_block = primary
.latest_trusted()
.ok_or_else(|| eyre!("No trusted state found for primary"))?;
let status = client.status().await?;
let latest_height = status.sync_info.latest_block_height;
let latest_app_hash = status.sync_info.latest_app_hash;
// `proof_height` is the height at which we want to query the blockchain's state
// This is one less than than the `latest_height` because we want to verify the merkle-proof for
// the state against the `app_hash` at `latest_height`.
// (because Tendermint commits to the latest `app_hash` in the subsequent block)
let proof_height = (latest_height.value() - 1)
.try_into()
.expect("infallible conversion");
info!("Verifying to latest height on primary...");
let primary_block = primary.verify_to_height(latest_height)?;
info!("Verified to height {} on primary", primary_block.height());
let mut primary_trace = primary.get_trace(primary_block.height());
let witnesses = join_all(args.witnesses.0.into_iter().map(|addr| {
make_provider(
&args.chain_id,
addr,
trusted_block.height(),
trusted_block.signed_header.header.hash(),
options,
)
}))
.await;
let mut witnesses = witnesses.into_iter().collect::<Result<Vec<_>>>()?;
let max_clock_drift = Duration::from_secs(args.max_clock_drift);
let max_block_lag = Duration::from_secs(args.max_block_lag);
run_detector(
&mut primary,
witnesses.as_mut_slice(),
primary_trace.clone(),
max_clock_drift,
max_block_lag,
)
.await?;
let path = WASM_STORE_KEY.to_owned();
let data = CwAbciKey::new(args.contract_address, args.storage_key, None);
let result = client
.abci_query(Some(path), data, Some(proof_height), true)
.await?;
let proof: CwProof = result
.clone()
.try_into()
.expect("result should contain proof");
proof
.verify(latest_app_hash.clone().into())
.map_err(|e: ProofError| eyre!(e))?;
if let Some(trace_file) = args.trace_file {
// replace the last block in the trace (i.e. the (latest - 1) block) with the latest block
// we don't actually verify the latest block because it will be verified on the other side
let latest_block = primary.fetch_light_block(latest_height)?;
let _ = primary_trace.pop();
primary_trace.push(latest_block);
let output = ProofOutput {
light_client_proof: primary_trace,
merkle_proof: proof.into(),
};
write_proof_to_file(trace_file, output).await?;
};
Ok(())
}
async fn write_proof_to_file(trace_file: PathBuf, output: ProofOutput) -> Result<()> {
info!("Writing proof to output file ({})", trace_file.display());
let file = File::create(trace_file)?;
let mut writer = BufWriter::new(file);
serde_json::to_writer(&mut writer, &output)?;
writer.flush()?;
Ok(())
}
async fn run_detector(
primary: &mut Provider,
witnesses: &mut [Provider],
primary_trace: Vec<LightBlock>,
max_clock_drift: Duration,
max_block_lag: Duration,
) -> Result<(), Report> {
if witnesses.is_empty() {
return Err(Error::no_witnesses().into());
}
info!(
"Running misbehavior detection against {} witnesses...",
witnesses.len()
);
let primary_trace = Trace::new(primary_trace)?;
for witness in witnesses {
let divergence = detect_divergence::<Sha256>(
Some(primary),
witness,
primary_trace.clone().into_vec(),
max_clock_drift,
max_block_lag,
)
.await;
let evidence = match divergence {
Ok(Some(divergence)) => divergence.evidence,
Ok(None) => {
info!(
"no divergence found between primary and witness {}",
witness.peer_id()
);
continue;
}
Err(e) => {
error!(
"failed to run attack detector against witness {}: {e}",
witness.peer_id()
);
continue;
}
};
// Report the evidence to the witness
witness
.report_evidence(Evidence::from(evidence.against_primary))
.await
.map_err(|e| eyre!("failed to report evidence to witness: {}", e))?;
if let Some(against_witness) = evidence.against_witness {
// Report the evidence to the primary
primary
.report_evidence(Evidence::from(against_witness))
.await
.map_err(|e| eyre!("failed to report evidence to primary: {}", e))?;
}
}
Ok(())
}
async fn make_provider(
chain_id: &str,
rpc_addr: HttpClientUrl,
trusted_height: Height,
trusted_hash: Hash,
options: Options,
) -> Result<Provider> {
use tendermint_rpc::client::CompatMode;
let rpc_client = HttpClient::builder(rpc_addr)
.compat_mode(CompatMode::V0_34)
.build()?;
let node_id = rpc_client.status().await?.node_info.id;
let light_store = Box::new(MemoryStore::new());
let instance =
LightClientBuilder::prod(node_id, rpc_client.clone(), light_store, options, None)
.trust_primary_at(trusted_height, trusted_hash)?
.build();
Ok(Provider::new(chain_id.to_string(), instance, rpc_client))
prove(args).await
}

View file

@ -0,0 +1,262 @@
#![deny(
warnings,
trivial_casts,
trivial_numeric_casts,
unused_import_braces,
unused_qualifications
)]
#![forbid(unsafe_code)]
use std::{
fs::File,
io::{BufWriter, Write},
path::PathBuf,
time::Duration,
};
use color_eyre::{
eyre::{eyre, Result},
Report,
};
use cw_proof::{
error::ProofError,
proof::{cw::CwProof, key::CwAbciKey, Proof},
};
use futures::future::join_all;
use tendermint::{crypto::default::Sha256, evidence::Evidence, Hash};
use tendermint_light_client::{
builder::LightClientBuilder,
light_client::Options,
store::memory::MemoryStore,
types::{Height, LightBlock},
};
use tendermint_light_client_detector::{detect_divergence, Error, Provider, Trace};
use tendermint_rpc::{client::HttpClient, Client, HttpClientUrl};
use tracing::{error, info};
use tracing_subscriber::{util::SubscriberInitExt, EnvFilter};
const WASM_STORE_KEY: &str = "/store/wasm/key";
use crate::config::{Config as TmProverConfig, ProofOutput};
pub async fn prove(
TmProverConfig {
chain_id,
primary,
witnesses,
trusted_height,
trusted_hash,
trust_threshold,
trusting_period,
max_clock_drift,
max_block_lag,
trace_file,
verbose,
contract_address,
storage_key,
}: TmProverConfig,
) -> Result<()> {
let env_filter = EnvFilter::builder()
.with_default_directive(verbose.to_level_filter().into())
.from_env_lossy();
tracing_subscriber::fmt()
.with_target(false)
.with_env_filter(env_filter)
.finish()
.init();
let options = Options {
trust_threshold,
trusting_period: Duration::from_secs(trusting_period),
clock_drift: Duration::from_secs(max_clock_drift),
};
let mut provider = make_provider(
&chain_id,
primary.clone(),
trusted_height,
trusted_hash,
options,
)
.await?;
let client = HttpClient::builder(primary.clone()).build()?;
let trusted_block = provider
.latest_trusted()
.ok_or_else(|| eyre!("No trusted state found for primary"))?;
let status = client.status().await?;
let latest_height = status.sync_info.latest_block_height;
let latest_app_hash = status.sync_info.latest_app_hash;
// `proof_height` is the height at which we want to query the blockchain's state
// This is one less than than the `latest_height` because we want to verify the merkle-proof for
// the state against the `app_hash` at `latest_height`.
// (because Tendermint commits to the latest `app_hash` in the subsequent block)
let proof_height = (latest_height.value() - 1)
.try_into()
.expect("infallible conversion");
info!("Verifying to latest height on primary...");
let primary_block = provider.verify_to_height(latest_height)?;
info!("Verified to height {} on primary", primary_block.height());
let mut primary_trace = provider.get_trace(primary_block.height());
let witnesses = join_all(witnesses.0.into_iter().map(|addr: HttpClientUrl| {
make_provider(
&chain_id,
addr,
trusted_block.height(),
trusted_block.signed_header.header.hash(),
options,
)
}))
.await;
let mut witnesses = witnesses.into_iter().collect::<Result<Vec<_>>>()?;
let max_clock_drift = Duration::from_secs(max_clock_drift);
let max_block_lag = Duration::from_secs(max_block_lag);
run_detector(
&mut provider,
witnesses.as_mut_slice(),
primary_trace.clone(),
max_clock_drift,
max_block_lag,
)
.await?;
let path = WASM_STORE_KEY.to_owned();
let data = CwAbciKey::new(contract_address, storage_key, None);
let result = client
.abci_query(Some(path), data, Some(proof_height), true)
.await?;
let proof: CwProof = result
.clone()
.try_into()
.expect("result should contain proof");
proof
.verify(latest_app_hash.clone().into())
.map_err(|e: ProofError| eyre!(e))?;
if let Some(trace_file) = trace_file {
// replace the last block in the trace (i.e. the (latest - 1) block) with the latest block
// we don't actually verify the latest block because it will be verified on the other side
let latest_block = provider.fetch_light_block(latest_height)?;
let _ = primary_trace.pop();
primary_trace.push(latest_block);
let output = ProofOutput {
light_client_proof: primary_trace,
merkle_proof: proof.into(),
};
write_proof_to_file(trace_file, output).await?;
};
Ok(())
}
async fn write_proof_to_file(trace_file: PathBuf, output: ProofOutput) -> Result<()> {
info!("Writing proof to output file ({})", trace_file.display());
let file = File::create(trace_file)?;
let mut writer = BufWriter::new(file);
serde_json::to_writer(&mut writer, &output)?;
writer.flush()?;
Ok(())
}
async fn run_detector(
primary: &mut Provider,
witnesses: &mut [Provider],
primary_trace: Vec<LightBlock>,
max_clock_drift: Duration,
max_block_lag: Duration,
) -> Result<(), Report> {
if witnesses.is_empty() {
return Err(Error::no_witnesses().into());
}
info!(
"Running misbehavior detection against {} witnesses...",
witnesses.len()
);
let primary_trace = Trace::new(primary_trace)?;
for witness in witnesses {
let divergence = detect_divergence::<Sha256>(
Some(primary),
witness,
primary_trace.clone().into_vec(),
max_clock_drift,
max_block_lag,
)
.await;
let evidence = match divergence {
Ok(Some(divergence)) => divergence.evidence,
Ok(None) => {
info!(
"no divergence found between primary and witness {}",
witness.peer_id()
);
continue;
}
Err(e) => {
error!(
"failed to run attack detector against witness {}: {e}",
witness.peer_id()
);
continue;
}
};
// Report the evidence to the witness
witness
.report_evidence(Evidence::from(evidence.against_primary))
.await
.map_err(|e| eyre!("failed to report evidence to witness: {}", e))?;
if let Some(against_witness) = evidence.against_witness {
// Report the evidence to the primary
primary
.report_evidence(Evidence::from(against_witness))
.await
.map_err(|e| eyre!("failed to report evidence to primary: {}", e))?;
}
}
Ok(())
}
async fn make_provider(
chain_id: &str,
rpc_addr: HttpClientUrl,
trusted_height: Height,
trusted_hash: Hash,
options: Options,
) -> Result<Provider> {
use tendermint_rpc::client::CompatMode;
let rpc_client = HttpClient::builder(rpc_addr)
.compat_mode(CompatMode::V0_34)
.build()?;
let node_id = rpc_client.status().await?.node_info.id;
let light_store = Box::new(MemoryStore::new());
let instance =
LightClientBuilder::prod(node_id, rpc_client.clone(), light_store, options, None)
.trust_primary_at(trusted_height, trusted_hash)?
.build();
Ok(Provider::new(chain_id.to_string(), instance, rpc_client))
}