diff --git a/.gitignore b/.gitignore index 3455d2f..ae83267 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,9 @@ # Build results /target /schema +artifacts +/out +script.sh # Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) .cargo-ok diff --git a/Cargo.lock b/Cargo.lock index 4a53dbd..c030e71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,12 +13,30 @@ dependencies = [ "version_check", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "anybuf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f14538dd5b6befdc12b11aae1dddee64b670534d4fdbd28057bcc7c976c0e4eb" + [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "base16ct" version = "0.1.1" @@ -31,12 +49,24 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" + [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "block-buffer" version = "0.9.0" @@ -79,6 +109,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "num-traits", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -137,7 +177,7 @@ version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da78abcf059181e8cb01e95e5003cf64fe95dde6c72b3fe37e5cabc75cdba32a" dependencies = [ - "base64", + "base64 0.13.1", "bnum", "cosmwasm-crypto", "cosmwasm-derive", @@ -218,7 +258,7 @@ dependencies = [ "derivative", "itertools", "k256", - "prost", + "prost 0.9.0", "schemars", "serde", "thiserror", @@ -264,6 +304,19 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw20" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011c45920f8200bd5d32d4fe52502506f64f2f75651ab408054d4cfc75ca3a9b" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cw-utils", + "schemars", + "serde", +] + [[package]] name = "der" version = "0.6.1" @@ -364,6 +417,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "entrypoint" +version = "0.1.0" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus", + "cw2", + "osmosis-std", + "prost 0.11.9", + "schemars", + "serde", + "sha2 0.10.7", + "thiserror", +] + [[package]] name = "ff" version = "0.12.1" @@ -469,6 +540,37 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "limit" +version = "0.1.0" +dependencies = [ + "anybuf", + "base64 0.20.0", + "bech32", + "cosmwasm-schema", + "cosmwasm-std", + "cosmwasm-storage", + "cw-multi-test", + "cw-storage-plus", + "cw2", + "cw20", + "osmosis-std", + "osmosis-std-derive", + "prost 0.11.9", + "schemars", + "serde", + "thiserror", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -481,6 +583,35 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "osmosis-std" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75895e4db1a81ca29118e366365744f64314938327e4eedba8e6e462fb15e94f" +dependencies = [ + "chrono", + "cosmwasm-std", + "osmosis-std-derive", + "prost 0.11.9", + "prost-types", + "schemars", + "serde", + "serde-cw-value", +] + +[[package]] +name = "osmosis-std-derive" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47f0b2f22adb341bb59e5a3a1b464dde033181954bd055b9ae86d6511ba465b" +dependencies = [ + "itertools", + "proc-macro2", + "prost-types", + "quote", + "syn 1.0.109", +] + [[package]] name = "pkcs8" version = "0.9.0" @@ -507,7 +638,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", ] [[package]] @@ -523,6 +664,28 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + [[package]] name = "quote" version = "1.0.33" @@ -617,6 +780,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-cw-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75d32da6b8ed758b7d850b6c3c08f1d7df51a4df3cb201296e63e34a78e99d4" +dependencies = [ + "serde", +] + [[package]] name = "serde-json-wasm" version = "0.5.1" @@ -775,21 +947,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wise-wallet" -version = "0.1.0" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "cw-multi-test", - "cw-storage-plus", - "cw2", - "schemars", - "serde", - "thiserror", -] - [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index ef846dc..48c051c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,53 +1,40 @@ -[package] -name = "wise-wallet" -version = "0.1.0" -authors = ["fuzious "] -edition = "2021" - -exclude = [ - # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", +[workspace] +members = [ + "contracts/limit", + "contracts/entrypoint" ] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -crate-type = ["cdylib", "rlib"] - -[profile.release] -opt-level = 3 -debug = false -rpath = false -lto = true -debug-assertions = false -codegen-units = 1 -panic = 'abort' -incremental = false -overflow-checks = true - -[features] -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] -# use library feature to disable all instantiate/execute/query exports -library = [] +# [workspace.package] +# version = "0.0.1" +# license = "GPL-3.0-or-later" +# edition = "2021" -[package.metadata.scripts] +[workspace.metadata.scripts] optimize = """docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.10 + cosmwasm/workspace-optimizer:0.12.10 """ -[dependencies] +[workspace.dependencies] cosmwasm-schema = "1.1.3" -cosmwasm-std = "1.1.3" +cosmwasm-std = "1.2.1" cosmwasm-storage = "1.1.3" cw-storage-plus = "1.0.1" cw2 = "1.0.1" -schemars = "0.8.10" -serde = { version = "1.0.145", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.31" } +cw-utils = "1.0.1" +anyhow = "1.0.70" +thiserror = "1.0.40" +serde = { version = "1.0.103", default-features = false, features = ["derive"] } +semver = "1" -[dev-dependencies] -cw-multi-test = "0.16.2" +[profile.release] +codegen-units = 1 +debug = false +debug-assertions = false +incremental = false +lto = true +overflow-checks = true +opt-level = 3 +panic = "abort" +rpath = false diff --git a/contracts/entrypoint/Cargo.toml b/contracts/entrypoint/Cargo.toml new file mode 100644 index 0000000..6fc5499 --- /dev/null +++ b/contracts/entrypoint/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "entrypoint" +version = "0.1.0" +authors = ["bataotaku"] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.10 +""" + +[dependencies] +cosmwasm-schema = "1.1.3" +cosmwasm-std = "1.1.3" +cosmwasm-storage = "1.3.2" +cw-storage-plus = "1.0.1" +cw2 = "1.0.1" +osmosis-std = "0.16.1" +schemars = "0.8.10" +prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]} +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } +sha2 = "0.10" + +[dev-dependencies] +cw-multi-test = "0.16.2" \ No newline at end of file diff --git a/contracts/entrypoint/src/bin/schema.rs b/contracts/entrypoint/src/bin/schema.rs new file mode 100644 index 0000000..2684cda --- /dev/null +++ b/contracts/entrypoint/src/bin/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; + +use entrypoint::msg::{ExecuteMsg, InstantiateMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + } +} diff --git a/contracts/entrypoint/src/contract.rs b/contracts/entrypoint/src/contract.rs new file mode 100644 index 0000000..0464f0f --- /dev/null +++ b/contracts/entrypoint/src/contract.rs @@ -0,0 +1,90 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint64, +}; +use cw2::set_contract_version; + +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, UserOp}; +use crate::state::USER_NONCE; +use sha2::{Digest, Sha256}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:wise-wallet"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::HandleUserOps { UserOps } => execute::handle_user_op(deps, UserOps), + } +} + +pub mod execute { + use std::vec; + + use cosmwasm_std::CosmosMsg; + + use super::*; + pub fn handle_user_op(deps: DepsMut, ops: Vec) -> Result { + let mut msgs: Vec = Vec::new(); + + for op in ops { + // check for signature and nonce + let usernonce = USER_NONCE + .load(deps.storage, op.Sender.clone()) + .unwrap_or_default(); + if usernonce + 1 != op.Nonce.u128() { + return Err(ContractError::InvalidNonce { + user: op.Sender.to_string(), + }); + } + + let mut msg: CosmosMsg<_> = CosmosMsg::Wasm(cosmwasm_std::WasmMsg::Execute { + contract_addr: op.To.into_string(), + msg: op.Calldata, + funds: vec![], + }); + + let bin_msg = to_binary(&msg)?; + let hash = sha256(&bin_msg); + let sig = op.Signature.unwrap(); + + if !deps + .api + .secp256k1_verify(&hash, &sig, &op.Pubkey) + .unwrap_or_default() + { + return Err(ContractError::Unauthorized {}); + } + + msgs.push(msg); + } + Ok(Response::new()) + } +} + +pub fn sha256(msg: &[u8]) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(msg); + hasher.finalize().to_vec() +} diff --git a/src/error.rs b/contracts/entrypoint/src/error.rs similarity index 62% rename from src/error.rs rename to contracts/entrypoint/src/error.rs index 4a69d8f..cd85a3a 100644 --- a/src/error.rs +++ b/contracts/entrypoint/src/error.rs @@ -8,6 +8,7 @@ pub enum ContractError { #[error("Unauthorized")] Unauthorized {}, - // Add any other custom errors you like here. - // Look at https://docs.rs/thiserror/1.0.21/thiserror/ for details. + + #[error("Invalid Nonce for user {user}")] + InvalidNonce { user: String }, } diff --git a/contracts/entrypoint/src/lib.rs b/contracts/entrypoint/src/lib.rs new file mode 100644 index 0000000..12cd652 --- /dev/null +++ b/contracts/entrypoint/src/lib.rs @@ -0,0 +1,5 @@ +pub mod contract; +mod error; +pub mod msg; +pub mod state; +pub use crate::error::ContractError; diff --git a/contracts/entrypoint/src/msg.rs b/contracts/entrypoint/src/msg.rs new file mode 100644 index 0000000..438194c --- /dev/null +++ b/contracts/entrypoint/src/msg.rs @@ -0,0 +1,20 @@ +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary, Uint128, Uint64}; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + HandleUserOps { UserOps: Vec }, +} + +#[cw_serde] +pub struct UserOp { + pub Sender: Addr, + pub To: Addr, + pub Nonce: Uint128, + pub Calldata: Binary, + pub Signature: Option, + pub Pubkey: Binary, +} diff --git a/contracts/entrypoint/src/state.rs b/contracts/entrypoint/src/state.rs new file mode 100644 index 0000000..7dc3d28 --- /dev/null +++ b/contracts/entrypoint/src/state.rs @@ -0,0 +1,7 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use cosmwasm_std::Addr; +use cw_storage_plus::{Item, Map}; + +pub const USER_NONCE: Map = Map::new("USER_NONCE"); diff --git a/contracts/limit/Cargo.toml b/contracts/limit/Cargo.toml new file mode 100644 index 0000000..eb03377 --- /dev/null +++ b/contracts/limit/Cargo.toml @@ -0,0 +1,60 @@ +[package] +name = "limit" +version = "0.1.0" +authors = ["bataotaku"] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.12.10 +""" + +[dependencies] +cosmwasm-schema = "1.1.3" +cosmwasm-std = {version = "1.2", features = ["stargate"]} +cosmwasm-storage = "1.3.2" +cw-storage-plus = "1.0.1" +cw2 = "1.0.1" +osmosis-std = "0.16.1" +schemars = "0.8.10" +serde = { version = "1.0.145", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.31" } +osmosis-std-derive = "0.16.1" +prost = {version = "0.11.2", default-features = false, features = ["prost-derive"]} +anybuf = "0.1.0" +base64 = "0.20.0" +bech32 = "0.9.1" +cw20 = "1.1.0" + +[dev-dependencies] +cw-multi-test = "0.16.2" \ No newline at end of file diff --git a/src/bin/schema.rs b/contracts/limit/src/bin/schema.rs similarity index 72% rename from src/bin/schema.rs rename to contracts/limit/src/bin/schema.rs index 719c3cb..4670044 100644 --- a/src/bin/schema.rs +++ b/contracts/limit/src/bin/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use wise_wallet::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use limit::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/limit/src/contract.rs b/contracts/limit/src/contract.rs new file mode 100644 index 0000000..00f5fc0 --- /dev/null +++ b/contracts/limit/src/contract.rs @@ -0,0 +1,255 @@ +#[cfg(not(feature = "library"))] +use cosmwasm_std::entry_point; +use cosmwasm_std::{ + to_binary, to_vec, Binary, ContractResult, Decimal, Deps, DepsMut, Empty, Env, MessageInfo, + QueryRequest, Response, StdError, StdResult, SystemError, SystemResult, Uint64, +}; +use cw2::set_contract_version; +use cw20::Cw20ExecuteMsg::Transfer; + +use self::execute::execute_order; +use crate::error::ContractError; +use crate::msg::{ExecuteMsg, InstantiateMsg, NibiruQuerier, QueryMsg, QueryperpMsg}; +use crate::state::ORDER_ID_COUNTER; +use anybuf::Anybuf; +use base64::decode; +use bech32::encode; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:wise-wallet"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: InstantiateMsg, +) -> Result { + set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; + ORDER_ID_COUNTER.save(deps.storage, &0u64)?; + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + _env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + ExecuteMsg::StoreSwapOrder { + to, + order_requester, + token_sell, + token_bought, + quantity_order, + swap_upper_usd, + swap_lower_usd, + minimum_result_accepted_usd, + max_in_sell_usd, + is_token_out_order, + } => execute::store_swap_order( + deps, + info, + to, + order_requester.to_string(), + token_sell.to_string(), + token_bought.to_string(), + quantity_order, + swap_upper_usd, + swap_lower_usd, + minimum_result_accepted_usd, + max_in_sell_usd, + is_token_out_order, + ), + + ExecuteMsg::ExecuteSwapOrder { order_id } => execute_order(deps, order_id.u64()), + } +} + +pub mod execute { + use std::{default, io::Stderr}; + + use cosmwasm_std::{CosmosMsg, CustomMsg, WasmMsg}; + use prost::Message; + use serde::de; + + use super::*; + use crate::{ + msg::{OraclePricesResponse, QueryperpMsg}, + state::{SwapOrder, ORDER_ID_COUNTER, SWAP_ORDER_STORE}, + }; + + pub fn store_swap_order( + deps: DepsMut, + info: MessageInfo, + to: String, + order_requester: String, + token_sell: String, + token_bought: String, + quantity_order: u128, + swap_upper_usd: u128, + swap_lower_usd: u128, + minimum_result_accepted_usd: u128, + max_in_sell_usd: u128, + is_token_out_order: bool, + ) -> Result { + let x = info.sender.to_string(); + let swap_order = SwapOrder { + to, + order_requester, + token_sell, + token_bought, + quantity_order, + swap_upper_usd, + swap_lower_usd, + minimum_result_accepted_usd, + max_in_sell_usd, + is_token_out_order, + }; + let order_id = + ORDER_ID_COUNTER.update(deps.storage, |count| -> StdResult<_> { Ok(count + 1) })?; + SWAP_ORDER_STORE.save(deps.storage, order_id, &swap_order)?; + + // execute if possible + Ok(Response::new().add_attribute("orderId", order_id.to_string())) + } + + pub fn execute_order( + deps: DepsMut, + order_id: u64, + ) -> Result { + let mut swap_order = SWAP_ORDER_STORE.load(deps.storage, order_id)?; + let mut pair = swap_order.token_sell.clone(); + pair.push_str(":"); + pair.push_str(&swap_order.token_bought.clone()); + let querier = NibiruQuerier::new(&deps.querier); + let price_map: OraclePricesResponse = querier.oracle_prices(Some(vec![pair.clone()]))?; + let decimaldefault = Decimal::zero(); + let price = price_map.get(&pair).unwrap_or(&decimaldefault); + let swap_lower = Decimal::new(swap_order.swap_lower_usd.into()); + let swap_upper = Decimal::new(swap_order.swap_upper_usd.into()); + let mut msg: CosmosMsg; + if price.le(&swap_lower) { + msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: swap_order.token_sell, + msg: to_binary(&Transfer { + recipient: swap_order.to, + amount: swap_upper.to_uint_floor(), + })?, + funds: vec![], + }); + } else { + msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: swap_order.token_bought, + msg: to_binary(&Transfer { + recipient: swap_order.to, + amount: swap_upper.to_uint_floor(), + })?, + funds: vec![], + }); + } + + Ok(Response::new().add_message(msg)) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetOrder { orderId } => to_binary(&query::get_order(deps, orderId)?), + } +} + +pub mod query { + use super::*; + use crate::msg::GetOrderResponse; + use crate::state::{SwapOrder, SWAP_ORDER_STORE}; + + pub fn get_order(deps: Deps, orderId: u64) -> StdResult { + let state = SWAP_ORDER_STORE.load(deps.storage, orderId)?; + + Ok(GetOrderResponse { order: state }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::msg::GetOrderResponse; + use crate::state::SwapOrder; + use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; + use cosmwasm_std::{coins, from_binary, Addr}; + + #[test] + fn test_instantiate_store_and_query_order() { + let mut deps = mock_dependencies(); + let env = mock_env(); + let sender = "alice".to_string(); + let info = mock_info(&sender, &coins(2, "token")); + + // Instantiate + let instantiate_msg = InstantiateMsg {}; // Adjust the InstantiateMsg accordingly + let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), instantiate_msg).unwrap(); + + // Check attributes from instantiate response + assert_eq!( + _res.attributes[0], + cosmwasm_std::Attribute { + key: "method".to_string(), + value: "instantiate".to_string(), + } + ); + assert_eq!( + _res.attributes[1], + cosmwasm_std::Attribute { + key: "owner".to_string(), + value: sender.clone(), + } + ); + + // Store Swap Order + let execute_msg = ExecuteMsg::StoreSwapOrder { + order_requester: Addr::unchecked(sender.clone()), + token_sell: Addr::unchecked("token_sell".to_string()), + token_bought: Addr::unchecked("token_bought".to_string()), + quantity_order: 100, + swap_upper_usd: 200, + swap_lower_usd: 150, + minimum_result_accepted_usd: 180, + max_in_sell_usd: 190, + is_token_out_order: true, + }; + + let res = execute(deps.as_mut(), env.clone(), info.clone(), execute_msg).unwrap(); + + // Assert orderId attribute (you can extend this to check other things as well) + assert_eq!( + res.attributes[0], + cosmwasm_std::Attribute { + key: "orderId".to_string(), + value: "1".to_string(), + } + ); + + // Query the stored order + let query_msg = QueryMsg::GetOrder { orderId: 1 }; + let bin_res = query(deps.as_ref(), env, query_msg).unwrap(); + let order_response: GetOrderResponse = from_binary(&bin_res).unwrap(); + + // Check the order details + assert_eq!(order_response.order.order_requester, sender); + assert_eq!(order_response.order.token_sell, "token_sell"); + assert_eq!(order_response.order.token_bought, "token_bought"); + assert_eq!(order_response.order.quantity_order, 100); + assert_eq!(order_response.order.swap_upper_usd, 200); + assert_eq!(order_response.order.swap_lower_usd, 150); + assert_eq!(order_response.order.minimum_result_accepted_usd, 180); + assert_eq!(order_response.order.max_in_sell_usd, 190); + assert_eq!(order_response.order.is_token_out_order, true); + } +} diff --git a/contracts/limit/src/error.rs b/contracts/limit/src/error.rs new file mode 100644 index 0000000..dc19f10 --- /dev/null +++ b/contracts/limit/src/error.rs @@ -0,0 +1,11 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + + #[error("Unauthorized")] + Unauthorized {}, +} diff --git a/src/lib.rs b/contracts/limit/src/lib.rs similarity index 100% rename from src/lib.rs rename to contracts/limit/src/lib.rs diff --git a/contracts/limit/src/msg.rs b/contracts/limit/src/msg.rs new file mode 100644 index 0000000..f668124 --- /dev/null +++ b/contracts/limit/src/msg.rs @@ -0,0 +1,80 @@ +use crate::state::SwapOrder; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, CustomQuery, Decimal, QuerierWrapper, StdResult, Uint64}; +use std::collections::HashMap; + +#[cw_serde] +pub struct InstantiateMsg {} + +#[cw_serde] +pub enum ExecuteMsg { + StoreSwapOrder { + to: String, + order_requester: Addr, + token_sell: Addr, + token_bought: Addr, + quantity_order: u128, + swap_upper_usd: u128, + swap_lower_usd: u128, + minimum_result_accepted_usd: u128, + max_in_sell_usd: u128, + is_token_out_order: bool, + }, + + ExecuteSwapOrder { + order_id: Uint64, + }, +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + #[returns(GetOrderResponse)] + GetOrder { orderId: u64 }, +} + +#[cw_serde] +pub enum QueryperpMsg { + OraclePrices {}, +} + +impl CustomQuery for QueryperpMsg {} +pub struct NibiruQuerier<'a> { + querier: &'a QuerierWrapper<'a, QueryperpMsg>, +} + +impl<'a> NibiruQuerier<'a> { + pub fn new(querier: &'a QuerierWrapper) -> Self { + NibiruQuerier { querier } + } + + pub fn oracle_prices(&self, pairs: Option>) -> StdResult { + let request = QueryperpMsg::OraclePrices {}; + let price_map: OraclePricesResponse = self.querier.query(&request.into())?; + + let mut out_price_map: OraclePricesResponse; + if pairs.is_none() || pairs.as_ref().unwrap().is_empty() { + out_price_map = price_map; + } else { + let pair_vec: Vec = pairs.unwrap(); + out_price_map = HashMap::new(); + for p in &pair_vec { + match price_map.get(p) { + Some(rate) => out_price_map.insert(p.clone(), *rate), + None => continue, + }; + } + } + + Ok(out_price_map) + } +} + +pub type OraclePricesResponse = HashMap; + +// We define a custom struct for each query response +#[cw_serde] +pub struct GetOrderResponse { + pub order: SwapOrder, +} diff --git a/src/state.rs b/contracts/limit/src/state.rs similarity index 80% rename from src/state.rs rename to contracts/limit/src/state.rs index 60fa9f1..90c8ebc 100644 --- a/src/state.rs +++ b/contracts/limit/src/state.rs @@ -6,10 +6,11 @@ use cw_storage_plus::{Item, Map}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] pub struct SwapOrder { + pub to: String, pub order_requester: String, pub token_sell: String, pub token_bought: String, - pub quantityOrder: u128, + pub quantity_order: u128, pub swap_upper_usd: u128, pub swap_lower_usd: u128, pub minimum_result_accepted_usd: u128, @@ -18,4 +19,4 @@ pub struct SwapOrder { } pub const ORDER_ID_COUNTER: Item = Item::new("ORDER_ID_COUNTER"); -pub const SwapOrderStore: Map = Map::new("SwapOrderStore"); +pub const SWAP_ORDER_STORE: Map = Map::new("SWAP_ORDER_STORE"); diff --git a/src/contract.rs b/src/contract.rs deleted file mode 100644 index 408d130..0000000 --- a/src/contract.rs +++ /dev/null @@ -1,150 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; -use cw2::set_contract_version; - -use crate::error::ContractError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{ORDER_ID_COUNTER}; - -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:wise-wallet"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; - ORDER_ID_COUNTER.save(deps.storage,&0u64)?; - Ok(Response::new() - .add_attribute("method", "instantiate") - .add_attribute("owner", info.sender)) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::StoreSwapOrder { order_requester, token_sell, token_bought, quantityOrder, swap_upper_usd, swap_lower_usd, minimum_result_accepted_usd, max_in_sell_usd, is_token_out_order } => execute::storeSwapOrder(deps, info, order_requester.to_string(), token_sell.to_string(), token_bought.to_string(), quantityOrder, swap_upper_usd, swap_lower_usd, minimum_result_accepted_usd, max_in_sell_usd, is_token_out_order), - } -} - -pub mod execute { - use crate::state::{ORDER_ID_COUNTER, SwapOrder, SwapOrderStore}; - use super::*; - - - pub fn storeSwapOrder(deps: DepsMut, info: MessageInfo, order_requester: String, token_sell: String, token_bought: String, quantityOrder: u128, swap_upper_usd: u128, swap_lower_usd: u128, minimum_result_accepted_usd: u128, max_in_sell_usd: u128, is_token_out_order: bool) -> Result { - let x = info.sender.to_string(); - let swap_order = SwapOrder { - order_requester, - token_sell, - token_bought, - quantityOrder, - swap_upper_usd, - swap_lower_usd, - minimum_result_accepted_usd, - max_in_sell_usd, - is_token_out_order - }; - let order_id = ORDER_ID_COUNTER.update(deps.storage, |count| -> StdResult<_> {Ok(count + 1)})?; - - SwapOrderStore.save(deps.storage, order_id,&swap_order)?; - Ok(Response::new().add_attribute("orderId", order_id.to_string())) - } -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetOrder { orderId } => to_binary(&query::getOrder(deps, orderId)?), - } -} - -pub mod query { - use crate::msg::GetOrderResponse; - use crate::state::{SwapOrder, SwapOrderStore}; - use super::*; - - pub fn getOrder(deps: Deps, orderId: u64) -> StdResult { - let state = SwapOrderStore.load(deps.storage, orderId)?; - Ok(GetOrderResponse { order: state }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use cosmwasm_std::testing::{mock_dependencies, mock_env, mock_info}; - use cosmwasm_std::{Addr, coins, from_binary}; - use crate::msg::GetOrderResponse; - use crate::state::SwapOrder; - - - #[test] - fn test_instantiate_store_and_query_order() { - let mut deps = mock_dependencies(); - let env = mock_env(); - let sender = "alice".to_string(); - let info = mock_info(&sender, &coins(2, "token")); - - // Instantiate - let instantiate_msg = InstantiateMsg { }; // Adjust the InstantiateMsg accordingly - let _res = instantiate(deps.as_mut(), env.clone(), info.clone(), instantiate_msg).unwrap(); - - // Check attributes from instantiate response - assert_eq!(_res.attributes[0], cosmwasm_std::Attribute { - key: "method".to_string(), - value: "instantiate".to_string(), - }); - assert_eq!(_res.attributes[1], cosmwasm_std::Attribute { - key: "owner".to_string(), - value: sender.clone(), - }); - - // Store Swap Order - let execute_msg = ExecuteMsg::StoreSwapOrder { - order_requester: Addr::unchecked(sender.clone()), - token_sell: Addr::unchecked("token_sell".to_string()), - token_bought: Addr::unchecked("token_bought".to_string()), - quantityOrder: 100, - swap_upper_usd: 200, - swap_lower_usd: 150, - minimum_result_accepted_usd: 180, - max_in_sell_usd: 190, - is_token_out_order: true - }; - - let res = execute(deps.as_mut(), env.clone(), info.clone(), execute_msg).unwrap(); - - // Assert orderId attribute (you can extend this to check other things as well) - assert_eq!(res.attributes[0], cosmwasm_std::Attribute { - key: "orderId".to_string(), - value: "1".to_string(), - }); - - // Query the stored order - let query_msg = QueryMsg::GetOrder { orderId: 1 }; - let bin_res = query(deps.as_ref(), env, query_msg).unwrap(); - let order_response: GetOrderResponse = from_binary(&bin_res).unwrap(); - - // Check the order details - assert_eq!(order_response.order.order_requester, sender); - assert_eq!(order_response.order.token_sell, "token_sell"); - assert_eq!(order_response.order.token_bought, "token_bought"); - assert_eq!(order_response.order.quantityOrder, 100); - assert_eq!(order_response.order.swap_upper_usd, 200); - assert_eq!(order_response.order.swap_lower_usd, 150); - assert_eq!(order_response.order.minimum_result_accepted_usd, 180); - assert_eq!(order_response.order.max_in_sell_usd, 190); - assert_eq!(order_response.order.is_token_out_order, true); - } -} diff --git a/src/msg.rs b/src/msg.rs deleted file mode 100644 index f9473cd..0000000 --- a/src/msg.rs +++ /dev/null @@ -1,37 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; -use crate::state::SwapOrder; - -#[cw_serde] -pub struct InstantiateMsg {} - -#[cw_serde] -pub enum ExecuteMsg { - StoreSwapOrder { - order_requester: Addr, - token_sell: Addr, - token_bought: Addr, - quantityOrder: u128, - swap_upper_usd: u128, - swap_lower_usd: u128, - minimum_result_accepted_usd: u128, - max_in_sell_usd: u128, - is_token_out_order: bool - }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - // GetCount returns the current count as a json-encoded number - #[returns(GetOrderResponse)] - GetOrder { - orderId: u64 - }, -} - -// We define a custom struct for each query response -#[cw_serde] -pub struct GetOrderResponse { - pub order: SwapOrder, -}