diff --git a/Cargo.lock b/Cargo.lock index 8fc8a4e2..7ed0f98b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -197,9 +208,11 @@ name = "atomic-order-example" version = "0.2.0" dependencies = [ "cosmwasm-schema 1.5.7", - "cosmwasm-std", + "cosmwasm-std 2.1.3", + "cosmwasm-storage", "cw-multi-test", "cw-storage-plus", + "cw-utils", "cw2", "injective-cosmwasm 0.3.0", "injective-math 0.3.0", @@ -266,6 +279,12 @@ 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 = "bech32" version = "0.11.0" @@ -341,6 +360,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bnum" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56953345e39537a3e18bdaeba4cb0c58a78c1f61f361dc0fa7c5c7340ae87c5f" + [[package]] name = "bnum" version = "0.11.0" @@ -514,6 +539,19 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d905990ef3afb5753bb709dc7de88e9e370aa32bcc2f31731d4b533b63e82490" +[[package]] +name = "cosmwasm-crypto" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f862b355f7e47711e0acfe6af92cb3fd8fd5936b66a9eaa338b51edabd1e77d" +dependencies = [ + "digest 0.10.7", + "ed25519-zebra 3.1.0", + "k256", + "rand_core 0.6.4", + "thiserror", +] + [[package]] name = "cosmwasm-crypto" version = "2.1.3" @@ -527,7 +565,7 @@ dependencies = [ "cosmwasm-core", "digest 0.10.7", "ecdsa", - "ed25519-zebra", + "ed25519-zebra 4.0.3", "k256", "num-traits", "p256", @@ -537,6 +575,15 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cosmwasm-derive" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd85de6467cd1073688c86b39833679ae6db18cf4771471edd9809f15f1679f1" +dependencies = [ + "syn 1.0.109", +] + [[package]] name = "cosmwasm-derive" version = "2.1.3" @@ -596,6 +643,28 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "cosmwasm-std" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2685c2182624b2e9e17f7596192de49a3f86b7a0c9a5f6b25c1df5e24592e836" +dependencies = [ + "base64 0.21.7", + "bech32 0.9.1", + "bnum 0.10.0", + "cosmwasm-crypto 1.5.7", + "cosmwasm-derive 1.5.7", + "derivative", + "forward_ref", + "hex", + "schemars", + "serde 1.0.210", + "serde-json-wasm 0.5.2", + "sha2 0.10.8", + "static_assertions 1.1.0", + "thiserror", +] + [[package]] name = "cosmwasm-std" version = "2.1.3" @@ -603,22 +672,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51dec99a2e478715c0a4277f0dbeadbb8466500eb7dec873d0924edd086e77f1" dependencies = [ "base64 0.22.1", - "bech32", - "bnum", + "bech32 0.11.0", + "bnum 0.11.0", "cosmwasm-core", - "cosmwasm-crypto", - "cosmwasm-derive", + "cosmwasm-crypto 2.1.3", + "cosmwasm-derive 2.1.3", "derive_more", "hex", "rand_core 0.6.4", "schemars", "serde 1.0.210", - "serde-json-wasm", + "serde-json-wasm 1.0.1", "sha2 0.10.8", "static_assertions 1.1.0", "thiserror", ] +[[package]] +name = "cosmwasm-storage" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66de2ab9db04757bcedef2b5984fbe536903ada4a8a9766717a4a71197ef34f6" +dependencies = [ + "cosmwasm-std 1.5.7", + "serde 1.0.210", +] + [[package]] name = "cpufeatures" version = "0.2.14" @@ -681,6 +760,19 @@ dependencies = [ "typenum", ] +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -728,8 +820,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0ae276e7a06ad1b7e7da78a3d68aba80634cde30ee7fe8259a94e653603fef8" dependencies = [ "anyhow", - "bech32", - "cosmwasm-std", + "bech32 0.11.0", + "cosmwasm-std 2.1.3", "cw-storage-plus", "cw-utils", "derivative", @@ -747,7 +839,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f13360e9007f51998d42b1bc6b7fa0141f74feae61ed5fd1e5b0a89eec7b5de1" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 2.1.3", "schemars", "serde 1.0.210", ] @@ -759,7 +851,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07dfee7f12f802431a856984a32bce1cb7da1e6c006b5409e3981035ce562dec" dependencies = [ "cosmwasm-schema 2.1.3", - "cosmwasm-std", + "cosmwasm-std 2.1.3", "schemars", "serde 1.0.210", "thiserror", @@ -772,7 +864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b04852cd38f044c0751259d5f78255d07590d136b8a86d4e09efdd7666bd6d27" dependencies = [ "cosmwasm-schema 2.1.3", - "cosmwasm-std", + "cosmwasm-std 2.1.3", "cw-storage-plus", "schemars", "semver", @@ -862,7 +954,7 @@ checksum = "59f8e79d1fbf76bdfbde321e902714bf6c49df88a7dda6fc682fc2979226962d" name = "dummy" version = "1.1.0" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 2.1.3", "cw-storage-plus", "cw2", "injective-cosmwasm 0.3.0", @@ -914,13 +1006,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed25519-zebra" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" +dependencies = [ + "curve25519-dalek 3.2.0", + "hashbrown 0.12.3", + "hex", + "rand_core 0.6.4", + "serde 1.0.210", + "sha2 0.9.9", + "zeroize", +] + [[package]] name = "ed25519-zebra" version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 4.1.3", "ed25519", "hashbrown 0.14.5", "hex", @@ -1101,6 +1208,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forward_ref" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1245,6 +1358,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] [[package]] name = "hashbrown" @@ -1252,7 +1368,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.11", ] [[package]] @@ -1261,7 +1377,7 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash", + "ahash 0.8.11", "allocator-api2", ] @@ -1453,14 +1569,14 @@ name = "injective-cosmwasm" version = "0.3.0" dependencies = [ "cosmwasm-schema 1.5.7", - "cosmwasm-std", + "cosmwasm-std 2.1.3", "cw-storage-plus", "ethereum-types", "hex", "injective-math 0.3.0", "schemars", "serde 1.0.210", - "serde-json-wasm", + "serde-json-wasm 1.0.1", "serde_repr", "serde_test", "subtle-encoding", @@ -1473,7 +1589,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23e93e9438844b10add3eb40ed1e8c92689824ac080d207f856412a73551c221" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 2.1.3", "cw-storage-plus", "ethereum-types", "hex", @@ -1491,7 +1607,28 @@ version = "1.0.0" dependencies = [ "cosmos-sdk-proto", "cosmwasm-schema 1.5.7", - "cosmwasm-std", + "cosmwasm-std 2.1.3", + "cw-storage-plus", + "cw2", + "injective-cosmwasm 0.3.0", + "injective-math 0.3.0", + "injective-std 1.13.0", + "injective-test-tube", + "injective-testing", + "prost 0.12.6", + "schemars", + "serde 1.0.210", + "thiserror", +] + +[[package]] +name = "injective-cosmwasm-stargate-example" +version = "0.0.1" +dependencies = [ + "base64 0.21.7", + "cosmos-sdk-proto", + "cosmwasm-schema 1.5.7", + "cosmwasm-std 2.1.3", "cw-storage-plus", "cw2", "injective-cosmwasm 0.3.0", @@ -1502,6 +1639,7 @@ dependencies = [ "prost 0.12.6", "schemars", "serde 1.0.210", + "serde_json 1.0.128", "thiserror", ] @@ -1510,10 +1648,12 @@ name = "injective-math" version = "0.3.0" dependencies = [ "cosmwasm-schema 1.5.7", - "cosmwasm-std", + "cosmwasm-std 2.1.3", + "ethereum-types", "primitive-types", "schemars", "serde 1.0.210", + "subtle-encoding", ] [[package]] @@ -1522,7 +1662,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194fb5cb49537b0b9137d02a563b7019003220fb4affff05ad6cdc6fee3509c9" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 2.1.3", "ethereum-types", "primitive-types", "schemars", @@ -1535,7 +1675,7 @@ name = "injective-std" version = "1.13.0" dependencies = [ "chrono", - "cosmwasm-std", + "cosmwasm-std 2.1.3", "injective-std-derive 1.13.0", "prost 0.12.6", "prost-types 0.12.6", @@ -1551,7 +1691,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0e5193cb9520754f60b9e9af08a662ddf298d2e1a579200b9a447064b64db8b" dependencies = [ "chrono", - "cosmwasm-std", + "cosmwasm-std 2.1.3", "injective-std-derive 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "prost 0.12.6", "prost-types 0.12.6", @@ -1564,7 +1704,7 @@ dependencies = [ name = "injective-std-derive" version = "1.13.0" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 2.1.3", "itertools 0.10.5", "proc-macro2", "prost 0.12.6", @@ -1580,7 +1720,7 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2721d8c2fed1fd1dff4cd6d119711a74acf27a6eeea6bf09cd44d192119e52ea" dependencies = [ - "cosmwasm-std", + "cosmwasm-std 2.1.3", "itertools 0.10.5", "proc-macro2", "quote", @@ -1596,7 +1736,7 @@ dependencies = [ "base64 0.21.7", "bindgen", "cosmrs", - "cosmwasm-std", + "cosmwasm-std 2.1.3", "hex", "injective-cosmwasm 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "injective-std 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1612,7 +1752,8 @@ name = "injective-testing" version = "1.1.0" dependencies = [ "anyhow", - "cosmwasm-std", + "base64 0.21.7", + "cosmwasm-std 2.1.3", "cw-multi-test", "injective-cosmwasm 0.3.0", "injective-math 0.3.0", @@ -1682,7 +1823,9 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", + "once_cell", "sha2 0.10.8", + "signature", ] [[package]] @@ -1901,9 +2044,9 @@ checksum = "4eae0151b9dacf24fcc170d9995e511669a082856a91f958a2fe380bfab3fb22" [[package]] name = "once_cell" -version = "1.20.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" @@ -2233,6 +2376,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "rand_core" version = "0.6.4" @@ -2616,6 +2765,15 @@ dependencies = [ "serde 1.0.210", ] +[[package]] +name = "serde-json-wasm" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e9213a07d53faa0b8dd81e767a54a8188a242fdb9be99ab75ec576a774bfdd7" +dependencies = [ + "serde 1.0.210", +] + [[package]] name = "serde-json-wasm" version = "1.0.1" @@ -3013,7 +3171,7 @@ checksum = "3c3a4e34619e6417613fab682de9a196848f4a56653ac71b95b5860b6d87c7cd" dependencies = [ "base64 0.21.7", "cosmrs", - "cosmwasm-std", + "cosmwasm-std 2.1.3", "prost 0.12.6", "serde 1.0.210", "serde_json 1.0.128", diff --git a/Cargo.toml b/Cargo.toml index d534fddc..96216ae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ incremental = false [workspace.dependencies] anyhow = { version = "1.0.66" } -base64 = { version = "0.13.1" } +base64 = { version = "0.21.5" } chrono = { version = "0.4.27", default-features = false } cosmos-sdk-proto = { version = "0.20.0", default-features = false } cosmwasm-schema = { version = "1.5.0" } @@ -38,6 +38,7 @@ secp256k1 = { version = "0.6.2" } serde = { version = "1.0.196", default-features = false, features = [ "derive" ] } serde-cw-value = { version = "0.7.0" } serde-json-wasm = { version = "1.0.0" } +serde_json = { version = "1.0.111" } serde_repr = { version = "0.1.17" } serde_test = { version = "1.0.176" } subtle-encoding = { version = "0.5.1", features = [ "bech32-preview" ] } diff --git a/contracts/atomic-order-example/.circleci/config.yml b/contracts/atomic-order-example/.circleci/config.yml deleted file mode 100644 index 9b076696..00000000 --- a/contracts/atomic-order-example/.circleci/config.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: 2.1 - -executors: - builder: - docker: - - image: buildpack-deps:trusty - -jobs: - docker-image: - executor: builder - steps: - - checkout - - setup_remote_docker - docker_layer_caching: true - - run: - name: Build Docker artifact - command: docker build --pull -t "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" . - - run: - name: Push application Docker image to docker hub - command: | - if [ "${CIRCLE_BRANCH}" = "master" ]; then - docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" cosmwasm/cw-gitpod-base:latest - docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" - docker push cosmwasm/cw-gitpod-base:latest - docker logout - fi - - docker-tagged: - executor: builder - steps: - - checkout - - setup_remote_docker - docker_layer_caching: true - - run: - name: Push application Docker image to docker hub - command: | - docker tag "cosmwasm/cw-gitpod-base:${CIRCLE_SHA1}" "cosmwasm/cw-gitpod-base:${CIRCLE_TAG}" - docker login --password-stdin -u "$DOCKER_USER" \<<<"$DOCKER_PASS" - docker push - docker logout - -workflows: - version: 2 - test-suite: - jobs: - # this is now a slow process... let's only run on master - - docker-image: - filters: - branches: - only: - - master - - docker-tagged: - filters: - tags: - only: - - /^v.*/ - branches: - ignore: - - /.*/ - requires: - - docker-image diff --git a/contracts/atomic-order-example/Cargo.toml b/contracts/atomic-order-example/Cargo.toml index 73a5488b..247bb19a 100644 --- a/contracts/atomic-order-example/Cargo.toml +++ b/contracts/atomic-order-example/Cargo.toml @@ -28,11 +28,13 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-std = { workspace = true } +cosmwasm-storage = { workspace = true } cw-storage-plus = { workspace = true } +cw-utils = { workspace = true } cw2 = { workspace = true } -injective-cosmwasm = { workspace = true, path = "../../packages/injective-cosmwasm" } -injective-math = { workspace = true, path = "../../packages/injective-math" } -injective-std = { workspace = true, path = "../../packages/injective-std" } +injective-cosmwasm = { path = "../../packages/injective-cosmwasm" } +injective-math = { path = "../../packages/injective-math" } +injective-std = { path = "../../packages/injective-std" } prost = { workspace = true } schemars = { workspace = true } serde = { workspace = true } diff --git a/contracts/atomic-order-example/src/tests.rs b/contracts/atomic-order-example/src/tests.rs index 5c91e1a7..5d56bdb5 100644 --- a/contracts/atomic-order-example/src/tests.rs +++ b/contracts/atomic-order-example/src/tests.rs @@ -144,6 +144,7 @@ fn create_spot_market_handler() -> impl HandlesMarketIdQuery { status: MarketStatus::Active, min_price_tick_size: FPDecimal::from_str("0.000000000000001").unwrap(), min_quantity_tick_size: FPDecimal::from_str("1000000000000000").unwrap(), + min_notional: FPDecimal::ONE, }), }; SystemResult::Ok(ContractResult::from(to_json_binary(&response))) diff --git a/contracts/dummy/Cargo.toml b/contracts/dummy/Cargo.toml index fb07089b..647c5406 100644 --- a/contracts/dummy/Cargo.toml +++ b/contracts/dummy/Cargo.toml @@ -30,7 +30,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } -injective-cosmwasm = { workspace = true, path = "../../packages/injective-cosmwasm" } +injective-cosmwasm = { path = "../../packages/injective-cosmwasm" } schemars = { workspace = true } serde = { workspace = true } thiserror = { workspace = true } diff --git a/contracts/injective-cosmwasm-mock/Cargo.toml b/contracts/injective-cosmwasm-mock/Cargo.toml index 4a4274b0..f30f6d2c 100644 --- a/contracts/injective-cosmwasm-mock/Cargo.toml +++ b/contracts/injective-cosmwasm-mock/Cargo.toml @@ -26,9 +26,9 @@ cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } cw2 = { workspace = true } -injective-cosmwasm = { workspace = true, path = "../../packages/injective-cosmwasm" } -injective-math = { workspace = true, path = "../../packages/injective-math" } -injective-std = { workspace = true, path = "../../packages/injective-std" } +injective-cosmwasm = { path = "../../packages/injective-cosmwasm" } +injective-math = { path = "../../packages/injective-math" } +injective-std = { path = "../../packages/injective-std" } prost = { workspace = true } schemars = { workspace = true } serde = { workspace = true } @@ -36,4 +36,4 @@ thiserror = { workspace = true } [dev-dependencies] injective-test-tube = { workspace = true } -injective-testing = { workspace = true, path = "../../packages/injective-testing" } +injective-testing = { path = "../../packages/injective-testing" } diff --git a/contracts/injective-cosmwasm-mock/src/testing/test_exchange.rs b/contracts/injective-cosmwasm-mock/src/testing/test_exchange.rs index 091706d9..6d6a2d35 100644 --- a/contracts/injective-cosmwasm-mock/src/testing/test_exchange.rs +++ b/contracts/injective-cosmwasm-mock/src/testing/test_exchange.rs @@ -154,6 +154,7 @@ fn test_query_spot_market() { let ticker = "INJ/USDT".to_string(); let min_price_tick_size = FPDecimal::must_from_str("0.000000000000001"); let min_quantity_tick_size = FPDecimal::must_from_str("1000000000000000"); + let min_notional = FPDecimal::must_from_str("1"); exchange .instant_spot_market_launch( @@ -164,7 +165,7 @@ fn test_query_spot_market() { quote_denom: QUOTE_DENOM.to_string(), min_price_tick_size: dec_to_proto(min_price_tick_size), min_quantity_tick_size: dec_to_proto(min_quantity_tick_size), - min_notional: "1".to_string(), + min_notional: dec_to_proto(min_notional), }, &env.signer, ) diff --git a/contracts/injective-cosmwasm-mock/src/testing/test_exchange_derivative.rs b/contracts/injective-cosmwasm-mock/src/testing/test_exchange_derivative.rs index 92812450..5089b5d1 100644 --- a/contracts/injective-cosmwasm-mock/src/testing/test_exchange_derivative.rs +++ b/contracts/injective-cosmwasm-mock/src/testing/test_exchange_derivative.rs @@ -52,6 +52,7 @@ fn test_query_derivative_market() { let maintenance_margin_ratio = FPDecimal::must_from_str("0.05"); let min_price_tick_size = FPDecimal::must_from_str("1000.0"); let min_quantity_tick_size = FPDecimal::must_from_str("1000000000000000"); + let min_notional = FPDecimal::must_from_str("1"); let quote_denom = QUOTE_DENOM.to_string(); let maker_fee_rate = FPDecimal::ZERO; let taker_fee_rate = FPDecimal::ZERO; @@ -72,7 +73,7 @@ fn test_query_derivative_market() { maintenance_margin_ratio: dec_to_proto(maintenance_margin_ratio), min_price_tick_size: dec_to_proto(min_price_tick_size), min_quantity_tick_size: dec_to_proto(min_quantity_tick_size), - min_notional: "1".to_string(), + min_notional: dec_to_proto(min_notional), }, &env.signer, ) diff --git a/contracts/injective-cosmwasm-stargate-example/Cargo.toml b/contracts/injective-cosmwasm-stargate-example/Cargo.toml new file mode 100644 index 00000000..980533d3 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/Cargo.toml @@ -0,0 +1,42 @@ +[package] +authors = [ "Jose Luis Bernal Castillo " ] +edition = "2018" +name = "injective-cosmwasm-stargate-example" +version = "0.0.1" + +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" ] + +[features] +# use library feature to disable all instantiate/execute/query exports +integration = [ ] +library = [ ] + +[dependencies] +base64 = { workspace = true } +cosmos-sdk-proto = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +cw-storage-plus = { workspace = true } +cw2 = { workspace = true } +injective-cosmwasm = { path = "../../packages/injective-cosmwasm" } +injective-math = { path = "../../packages/injective-math" } +injective-std = { path = "../../packages/injective-std" } +prost = { workspace = true } +schemars = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } + +[dev-dependencies] +injective-std = { workspace = true } +injective-test-tube = { workspace = true } +injective-testing = { workspace = true } diff --git a/contracts/injective-cosmwasm-stargate-example/src/contract.rs b/contracts/injective-cosmwasm-stargate-example/src/contract.rs new file mode 100644 index 00000000..70f6f618 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/contract.rs @@ -0,0 +1,70 @@ +use crate::{ + error::ContractError, + handle::{handle_test_market_spot_order, handle_test_transient_derivative_order, handle_test_transient_spot_order}, + msg::{ExecuteMsg, InstantiateMsg, QueryMsg}, + query::{handle_query_bank_params, handle_query_spot_market, handle_query_stargate_raw}, + reply::{handle_create_derivative_order_reply_stargate, handle_create_order_reply_stargate}, +}; + +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult}; +use cw2::set_contract_version; +use injective_cosmwasm::{InjectiveMsgWrapper, InjectiveQueryWrapper}; + +const CONTRACT_NAME: &str = "crates.io:injective:dummy-stargate-contract"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const CREATE_SPOT_ORDER_REPLY_ID: u64 = 0u64; +pub const CREATE_DERIVATIVE_ORDER_REPLY_ID: u64 = 1u64; + +#[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::default()) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result, ContractError> { + match msg { + ExecuteMsg::TestTraderTransientSpotOrders { + market_id, + subaccount_id, + price, + quantity, + } => handle_test_transient_spot_order(deps, env, &info, market_id, subaccount_id, price, quantity), + ExecuteMsg::TestMarketOrderStargate { + market_id, + subaccount_id, + price, + quantity, + } => handle_test_market_spot_order(deps, env.contract.address.as_ref(), market_id, subaccount_id, price, quantity), + ExecuteMsg::TestTraderTransientDerivativeOrders { + market_id, + subaccount_id, + price, + quantity, + margin, + } => handle_test_transient_derivative_order(deps, env, &info, market_id, subaccount_id, price, quantity, margin), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::QueryStargateRaw { path, query_request } => handle_query_stargate_raw(&deps.querier, path, query_request), + QueryMsg::QueryBankParams {} => handle_query_bank_params(deps), + QueryMsg::QuerySpotMarket { market_id } => handle_query_spot_market(deps, &market_id), + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { + match msg.id { + CREATE_SPOT_ORDER_REPLY_ID => handle_create_order_reply_stargate(deps, &msg), + CREATE_DERIVATIVE_ORDER_REPLY_ID => handle_create_derivative_order_reply_stargate(deps, &msg), + _ => Err(ContractError::UnrecognizedReply(msg.id)), + } +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/encode_helper.rs b/contracts/injective-cosmwasm-stargate-example/src/encode_helper.rs new file mode 100644 index 00000000..b1ea5bc2 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/encode_helper.rs @@ -0,0 +1,9 @@ +use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; +use base64::Engine; +use prost::Message; + +pub fn encode_proto_message(msg: T) -> String { + let mut buf = vec![]; + T::encode(&msg, &mut buf).unwrap(); + BASE64_STANDARD.encode(&buf) +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/error.rs b/contracts/injective-cosmwasm-stargate-example/src/error.rs new file mode 100644 index 00000000..54dea889 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/error.rs @@ -0,0 +1,14 @@ +use cosmwasm_std::StdError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ContractError { + #[error("{0}")] + Std(#[from] StdError), + #[error("Unrecognized reply id: {0}")] + UnrecognizedReply(u64), + #[error("Invalid reply from sub-message {id}, {err}")] + ReplyParseFailure { id: u64, err: String }, + #[error("Failure response from submsg: {0}")] + SubMsgFailure(String), +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/handle.rs b/contracts/injective-cosmwasm-stargate-example/src/handle.rs new file mode 100644 index 00000000..bfbafd50 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/handle.rs @@ -0,0 +1,139 @@ +use crate::{ + contract::{CREATE_DERIVATIVE_ORDER_REPLY_ID, CREATE_SPOT_ORDER_REPLY_ID}, + msg::{MSG_CREATE_DERIVATIVE_LIMIT_ORDER_ENDPOINT, MSG_CREATE_SPOT_LIMIT_ORDER_ENDPOINT}, + order_management::{create_derivative_limit_order, create_spot_limit_order, create_stargate_msg, encode_bytes_message}, + spot_market_order_msg::create_spot_market_order_message, + state::{CacheOrderInfo, ORDER_CALL_CACHE}, + ContractError, +}; +use cosmos_sdk_proto::{cosmos::authz::v1beta1::MsgExec, traits::Message, Any}; +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, SubMsg}; +use injective_cosmwasm::{InjectiveMsgWrapper, InjectiveQuerier, InjectiveQueryWrapper, MarketId, OrderType, SubaccountId}; +use injective_math::FPDecimal; + +pub const MSG_EXEC: &str = "/cosmos.authz.v1beta1.MsgExec"; + +pub fn handle_test_market_spot_order( + deps: DepsMut, + sender: &str, + market_id: MarketId, + subaccount_id: SubaccountId, + price: String, + quantity: String, +) -> Result, ContractError> { + let querier = InjectiveQuerier::new(&deps.querier); + let spot_market = querier.query_spot_market(&market_id).unwrap().market.unwrap(); + + let order_msg = create_spot_market_order_message( + FPDecimal::must_from_str(price.as_str()), + FPDecimal::must_from_str(quantity.as_str()), + OrderType::Sell, + sender, + subaccount_id.as_str(), + "", + &spot_market, + )?; + + Ok(Response::new().add_message(order_msg)) +} + +pub fn handle_test_transient_spot_order( + deps: DepsMut, + env: Env, + info: &MessageInfo, + market_id: MarketId, + subaccount_id: SubaccountId, + price: String, + quantity: String, +) -> Result, ContractError> { + let querier = InjectiveQuerier::new(&deps.querier); + let spot_market = querier.query_spot_market(&market_id).unwrap().market.unwrap(); + + let order_msg = create_spot_limit_order( + FPDecimal::must_from_str(price.as_str()), + FPDecimal::must_from_str(quantity.as_str()), + OrderType::Sell, + info.sender.as_str(), + subaccount_id.as_str(), + &spot_market, + ); + + let order_bytes = encode_bytes_message(&order_msg).unwrap(); + + let msg_exec = MsgExec { + grantee: env.contract.address.to_string(), + msgs: vec![Any { + type_url: MSG_CREATE_SPOT_LIMIT_ORDER_ENDPOINT.to_string(), + value: order_bytes, + }], + }; + + let order_submessage = SubMsg::reply_on_success( + create_stargate_msg(MSG_EXEC, msg_exec.encode_to_vec()).unwrap(), + CREATE_SPOT_ORDER_REPLY_ID, + ); + + save_cache_info(deps, market_id, subaccount_id)?; + + Ok(Response::new().add_submessage(order_submessage)) +} + +pub fn handle_test_transient_derivative_order( + deps: DepsMut, + env: Env, + info: &MessageInfo, + market_id: MarketId, + subaccount_id: SubaccountId, + price: String, + quantity: String, + margin: String, +) -> Result, ContractError> { + let querier: InjectiveQuerier = InjectiveQuerier::new(&deps.querier); + let market = querier.query_derivative_market(&market_id).unwrap().market.unwrap(); + + let order_msg = create_derivative_limit_order( + FPDecimal::must_from_str(price.as_str()), + FPDecimal::must_from_str(quantity.as_str()), + FPDecimal::must_from_str(margin.as_str()), + OrderType::Buy, + info.sender.as_str(), + subaccount_id.as_str(), + &market, + ); + + let order_bytes = encode_bytes_message(&order_msg).unwrap(); + + let msg_exec = MsgExec { + grantee: env.contract.address.to_string(), + msgs: vec![Any { + type_url: MSG_CREATE_DERIVATIVE_LIMIT_ORDER_ENDPOINT.to_string(), + value: order_bytes, + }], + }; + + let order_submessage = SubMsg::reply_on_success( + create_stargate_msg(MSG_EXEC, msg_exec.encode_to_vec()).unwrap(), + CREATE_DERIVATIVE_ORDER_REPLY_ID, + ); + + save_cache_info(deps, market_id, subaccount_id)?; + + Ok(Response::new().add_submessage(order_submessage)) +} + +fn save_cache_info(deps: DepsMut, market_id: MarketId, subaccount_id: SubaccountId) -> Result<(), ContractError> { + let cache_order_info = CacheOrderInfo { + subaccount: subaccount_id, + market_id, + }; + + let mut order_cache = match ORDER_CALL_CACHE.may_load(deps.storage)? { + Some(order_cache) => order_cache, + None => vec![], + }; + + order_cache.push(cache_order_info); + + ORDER_CALL_CACHE.save(deps.storage, &order_cache)?; + Ok(()) +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/lib.rs b/contracts/injective-cosmwasm-stargate-example/src/lib.rs new file mode 100644 index 00000000..130ad757 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/lib.rs @@ -0,0 +1,16 @@ +pub mod contract; +mod encode_helper; +mod error; +mod handle; +pub mod msg; +mod order_management; +mod query; +mod reply; +mod spot_market_order_msg; +mod state; +#[cfg(test)] +mod testing; +#[cfg(test)] +pub mod utils; + +pub use crate::error::ContractError; diff --git a/contracts/injective-cosmwasm-stargate-example/src/msg.rs b/contracts/injective-cosmwasm-stargate-example/src/msg.rs new file mode 100644 index 00000000..b3f913ee --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/msg.rs @@ -0,0 +1,47 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use injective_cosmwasm::{MarketId, SubaccountId}; + +pub const MSG_CREATE_SPOT_LIMIT_ORDER_ENDPOINT: &str = "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder"; +pub const MSG_CREATE_DERIVATIVE_LIMIT_ORDER_ENDPOINT: &str = "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder"; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct InstantiateMsg {} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum ExecuteMsg { + TestTraderTransientSpotOrders { + market_id: MarketId, + subaccount_id: SubaccountId, + price: String, + quantity: String, + }, + TestTraderTransientDerivativeOrders { + market_id: MarketId, + subaccount_id: SubaccountId, + price: String, + quantity: String, + margin: String, + }, + TestMarketOrderStargate { + market_id: MarketId, + subaccount_id: SubaccountId, + price: String, + quantity: String, + }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum QueryMsg { + QueryStargateRaw { path: String, query_request: String }, + QueryBankParams {}, + QuerySpotMarket { market_id: String }, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct QueryStargateResponse { + pub value: String, +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/order_management.rs b/contracts/injective-cosmwasm-stargate-example/src/order_management.rs new file mode 100644 index 00000000..d58afab0 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/order_management.rs @@ -0,0 +1,74 @@ +use cosmwasm_std::{AnyMsg, CosmosMsg, StdResult}; +use injective_cosmwasm::{FullDerivativeMarket, InjectiveMsgWrapper, OrderType, SpotMarket}; +use injective_math::FPDecimal; +use injective_std::types::injective::exchange::v1beta1::{ + DerivativeOrder, MsgCreateDerivativeLimitOrder, MsgCreateSpotLimitOrder, OrderInfo, SpotOrder, +}; +use prost::Message; + +pub fn create_stargate_msg(type_url: &str, value: Vec) -> StdResult> { + Ok(CosmosMsg::Any(AnyMsg { + type_url: type_url.to_string(), + value: value.into(), + })) +} + +pub fn create_spot_limit_order( + price: FPDecimal, + quantity: FPDecimal, + order_type: OrderType, + sender: &str, + subaccount_id: &str, + market: &SpotMarket, +) -> MsgCreateSpotLimitOrder { + MsgCreateSpotLimitOrder { + sender: sender.to_string(), + order: Some(SpotOrder { + market_id: market.market_id.as_str().into(), + order_info: Some(OrderInfo { + subaccount_id: subaccount_id.to_string(), + fee_recipient: sender.to_string(), + price: price.to_string(), + quantity: quantity.to_string(), + cid: "".to_string(), + }), + order_type: order_type as i32, + trigger_price: "".to_string(), + }), + } +} + +pub fn create_derivative_limit_order( + price: FPDecimal, + quantity: FPDecimal, + margin: FPDecimal, + order_type: OrderType, + sender: &str, + subaccount_id: &str, + market: &FullDerivativeMarket, +) -> MsgCreateDerivativeLimitOrder { + let market_id = market.market.as_ref().unwrap().market_id.as_str().to_string(); + + MsgCreateDerivativeLimitOrder { + sender: sender.to_string(), + order: Some(DerivativeOrder { + market_id, + order_info: Some(OrderInfo { + subaccount_id: subaccount_id.to_string(), + fee_recipient: sender.to_string(), + price: price.to_string(), + quantity: quantity.to_string(), + cid: "".to_string(), + }), + order_type: order_type as i32, + margin: margin.to_string(), + trigger_price: "".to_string(), + }), + } +} + +pub(crate) fn encode_bytes_message(order_msg: &T) -> Result, prost::EncodeError> { + let mut buffer = Vec::new(); + order_msg.encode(&mut buffer)?; // Encode the message using prost + Ok(buffer) +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/query.rs b/contracts/injective-cosmwasm-stargate-example/src/query.rs new file mode 100644 index 00000000..cb17e7cc --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/query.rs @@ -0,0 +1,39 @@ +use crate::msg::QueryStargateResponse; + +use base64::engine::general_purpose::STANDARD as BASE64_STANDARD; +use base64::Engine as _; +use cosmwasm_std::{to_json_binary, to_json_vec, Binary, ContractResult, Deps, QuerierWrapper, QueryRequest, StdError, StdResult, SystemResult}; +use injective_cosmwasm::InjectiveQueryWrapper; +use injective_std::types::{cosmos::bank::v1beta1::BankQuerier, injective::exchange::v1beta1::ExchangeQuerier}; + +pub fn handle_query_stargate_raw(querier: &QuerierWrapper, path: String, query_request: String) -> StdResult { + let data = Binary::from_base64(&query_request)?; + + #[allow(deprecated)] + let request = &QueryRequest::::Stargate { path, data }; + let raw = to_json_vec(request).map_err(|serialize_err| StdError::generic_err(format!("Serializing QueryRequest: {}", serialize_err)))?; + + let value = match querier.raw_query(&raw) { + SystemResult::Err(system_err) => Err(StdError::generic_err(format!("Querier system error: {}", system_err))), + SystemResult::Ok(ContractResult::Err(contract_err)) => Err(StdError::generic_err(format!("Querier contract error: {}", contract_err))), + SystemResult::Ok(ContractResult::Ok(value)) => Ok(value), + }? + .to_string(); + + let decoded_value = BASE64_STANDARD + .decode(value) + .map_err(|_| StdError::generic_err("Decoding base64 value"))?; + to_json_binary(&QueryStargateResponse { + value: String::from_utf8(decoded_value)?, + }) +} + +pub fn handle_query_spot_market(deps: Deps, market_id: &str) -> StdResult { + let querier = ExchangeQuerier::new(&deps.querier); + to_json_binary(&querier.spot_market(market_id.to_string())?) +} + +pub fn handle_query_bank_params(deps: Deps) -> StdResult { + let querier = BankQuerier::new(&deps.querier); + to_json_binary(&querier.params()?) +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/reply.rs b/contracts/injective-cosmwasm-stargate-example/src/reply.rs new file mode 100644 index 00000000..598ee0cb --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/reply.rs @@ -0,0 +1,82 @@ +use crate::{encode_helper::encode_proto_message, query::handle_query_stargate_raw, state::ORDER_CALL_CACHE, ContractError}; +use cosmwasm_std::{DepsMut, Event, Reply, Response}; +use injective_cosmwasm::InjectiveQueryWrapper; + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, Eq, ::prost::Message, ::serde::Serialize, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct QueryTraderSpotOrdersRequest { + /// Market ID for the market + #[prost(string, tag = "1")] + #[serde(alias = "marketID")] + pub market_id: ::prost::alloc::string::String, + /// SubaccountID of the trader + #[prost(string, tag = "2")] + #[serde(alias = "subaccountID")] + pub subaccount_id: ::prost::alloc::string::String, +} + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, Eq, ::prost::Message, ::serde::Serialize, ::serde::Deserialize, ::schemars::JsonSchema)] +pub struct QueryTraderDerivativeOrdersRequest { + /// Market ID for the market + #[prost(string, tag = "1")] + #[serde(alias = "marketID")] + pub market_id: ::prost::alloc::string::String, + /// SubaccountID of the trader + #[prost(string, tag = "2")] + #[serde(alias = "subaccountID")] + pub subaccount_id: ::prost::alloc::string::String, +} + +pub fn handle_create_order_reply_stargate(deps: DepsMut, _msg: &Reply) -> Result { + let mut response_str = "Something went wrong".to_string(); + if let Some(mut cache) = ORDER_CALL_CACHE.may_load(deps.storage)? { + if !cache.is_empty() { + let order_info = &cache[0]; + let encode_query_message = encode_proto_message(QueryTraderSpotOrdersRequest { + market_id: order_info.market_id.clone().into(), + subaccount_id: order_info.subaccount.clone().into(), + }); + let stargate_response = handle_query_stargate_raw( + &deps.querier, + "/injective.exchange.v1beta1.Query/TraderSpotTransientOrders".to_string(), + encode_query_message, + ); + response_str = match stargate_response { + Ok(binary) => String::from_utf8(binary.to_vec()).unwrap_or_else(|e| format!("Failed to decode binary to string: {:?}", e)), + Err(e) => format!("Error: {:?}", e), + }; + cache.clear(); + ORDER_CALL_CACHE.save(deps.storage, &cache)?; + } + }; + + Ok(Response::new().add_event(Event::new("transient_order").add_attributes([("query_str", response_str)]))) +} + +pub fn handle_create_derivative_order_reply_stargate(deps: DepsMut, _msg: &Reply) -> Result { + let mut response_str = "Something went wrong".to_string(); + + if let Some(mut cache) = ORDER_CALL_CACHE.may_load(deps.storage)? { + if !cache.is_empty() { + let order_info = &cache[0]; + let encode_query_message = encode_proto_message(QueryTraderDerivativeOrdersRequest { + market_id: order_info.market_id.clone().into(), + subaccount_id: order_info.subaccount.clone().into(), + }); + let stargate_response = handle_query_stargate_raw( + &deps.querier, + "/injective.exchange.v1beta1.Query/TraderDerivativeTransientOrders".to_string(), + encode_query_message, + ); + response_str = match stargate_response { + Ok(binary) => String::from_utf8(binary.to_vec()).unwrap_or_else(|e| format!("Failed to decode binary to string: {:?}", e)), + Err(e) => format!("Error: {:?}", e), + }; + cache.clear(); + ORDER_CALL_CACHE.save(deps.storage, &cache)?; + } + }; + + Ok(Response::new().add_event(Event::new("transient_derivative_order").add_attributes([("query_str", response_str)]))) +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/spot_market_order_msg.rs b/contracts/injective-cosmwasm-stargate-example/src/spot_market_order_msg.rs new file mode 100644 index 00000000..9828d73a --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/spot_market_order_msg.rs @@ -0,0 +1,54 @@ +use cosmwasm_std::{AnyMsg, CosmosMsg, StdResult}; +use injective_cosmwasm::{InjectiveMsgWrapper, OrderType, SpotMarket}; +use injective_math::{round_to_min_tick, round_to_nearest_tick, FPDecimal}; +use injective_std::types::injective::exchange::v1beta1 as Exchange; +use prost::Message; + +pub fn create_spot_market_order_message( + price: FPDecimal, + quantity: FPDecimal, + order_type: OrderType, + sender: &str, + subaccount_id: &str, + fee_recipient: &str, + market: &SpotMarket, +) -> StdResult> { + let msg = create_spot_market_order(price, quantity, order_type, sender, subaccount_id, fee_recipient, market); + + let mut order_bytes = vec![]; + Exchange::MsgCreateSpotMarketOrder::encode(&msg, &mut order_bytes).unwrap(); + + Ok(CosmosMsg::Any(AnyMsg { + type_url: Exchange::MsgCreateSpotMarketOrder::TYPE_URL.to_string(), + value: order_bytes.into(), + })) +} + +fn create_spot_market_order( + price: FPDecimal, + quantity: FPDecimal, + order_type: OrderType, + sender: &str, + subaccount_id: &str, + fee_recipient: &str, + market: &SpotMarket, +) -> Exchange::MsgCreateSpotMarketOrder { + let rounded_quantity = round_to_min_tick(quantity, market.min_quantity_tick_size); + let rounded_price = round_to_nearest_tick(price, market.min_price_tick_size); + + Exchange::MsgCreateSpotMarketOrder { + sender: sender.to_string(), + order: Some(Exchange::SpotOrder { + market_id: market.market_id.as_str().into(), + order_info: Some(Exchange::OrderInfo { + subaccount_id: subaccount_id.to_string(), + fee_recipient: fee_recipient.to_string(), + price: rounded_price.to_string(), + quantity: rounded_quantity.to_string(), + cid: "".to_string(), + }), + order_type: order_type as i32, + trigger_price: "".to_string(), + }), + } +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/state.rs b/contracts/injective-cosmwasm-stargate-example/src/state.rs new file mode 100644 index 00000000..0107a245 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/state.rs @@ -0,0 +1,11 @@ +use cosmwasm_schema::cw_serde; +use cw_storage_plus::Item; +use injective_cosmwasm::{MarketId, SubaccountId}; + +pub const ORDER_CALL_CACHE: Item> = Item::new("order_call_cache_stargate"); + +#[cw_serde] +pub struct CacheOrderInfo { + pub subaccount: SubaccountId, + pub market_id: MarketId, +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/authz.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/authz.rs new file mode 100644 index 00000000..250932e9 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/authz.rs @@ -0,0 +1,154 @@ +use crate::{ + encode_helper::encode_proto_message, + msg::{QueryMsg, QueryStargateResponse}, + utils::{execute_all_authorizations, ExchangeType, Setup}, +}; +use cosmos_sdk_proto::cosmos::authz::v1beta1::{QueryGranteeGrantsRequest, QueryGranterGrantsRequest, QueryGrantsRequest}; +use injective_test_tube::RunnerError::QueryError; +use injective_test_tube::{Account, Module, RunnerResult, Wasm}; + +use crate::testing::type_helpers::{Authorization, Grants, StargateQueryGranteeGrantsResponse, StargateQueryGranterGrantsResponse}; +use crate::utils::get_stargate_query_result; + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_grantee_grants() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + + execute_all_authorizations(&env.app, &env.users[0].account, env.users[1].account.address().to_string()); + execute_all_authorizations(&env.app, &env.users[2].account, env.users[1].account.address().to_string()); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.authz.v1beta1.Query/GranteeGrants".to_string(), + query_request: encode_proto_message(QueryGranteeGrantsRequest { + grantee: env.users[1].account.address().to_string(), + pagination: None, + }), + }; + + let messages = vec![ + "/injective.exchange.v1beta1.MsgBatchUpdateOrders", + "/injective.exchange.v1beta1.MsgCreateDerivativeLimitOrder", + "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder", + "/injective.exchange.v1beta1.MsgCreateSpotLimitOrder", + "/injective.exchange.v1beta1.MsgWithdraw", + ]; + + let response_user0 = create_stargate_response( + messages.clone(), + env.users[0].account.address().to_string(), + env.users[1].account.address().to_string(), + ); + let response_user2 = create_stargate_response( + messages, + env.users[2].account.address().to_string(), + env.users[1].account.address().to_string(), + ); + + let combined_grants = response_user0 + .grants + .into_iter() + .chain(response_user2.grants.into_iter()) + .collect::>(); + let query_result = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + + let all_grants_present = combined_grants.iter().all(|grant| query_result.grants.contains(grant)); + let no_extra_grants = combined_grants.len() == query_result.grants.len(); + + assert!(all_grants_present); + assert!(no_extra_grants); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_granter_grants() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + + execute_all_authorizations(&env.app, &env.users[0].account, env.users[1].account.address().to_string()); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.authz.v1beta1.Query/GranterGrants".to_string(), + query_request: encode_proto_message(QueryGranterGrantsRequest { + granter: env.users[0].account.address().to_string(), + pagination: None, + }), + }; + + let query_result = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert_eq!(query_result.grants.len(), 5); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.authz.v1beta1.Query/GranterGrants".to_string(), + query_request: encode_proto_message(QueryGranterGrantsRequest { + granter: env.users[2].account.address().to_string(), + pagination: None, + }), + }; + + let query_result = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert_eq!(query_result.grants.len(), 0); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_grants() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + + execute_all_authorizations(&env.app, &env.users[0].account, env.users[1].account.address().to_string()); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.authz.v1beta1.Query/Grants".to_string(), + query_request: encode_proto_message(QueryGrantsRequest { + granter: env.users[0].account.address().to_string(), + grantee: env.users[1].account.address().to_string(), + msg_type_url: "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder".to_string(), + pagination: None, + }), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + println!("{:?}", contract_response); + // let query_result = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + // println!("{:?}", query_result); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.authz.v1beta1.Query/Grants".to_string(), + query_request: encode_proto_message(QueryGrantsRequest { + granter: env.users[2].account.address().to_string(), + grantee: env.users[1].account.address().to_string(), + msg_type_url: "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder".to_string(), + pagination: None, + }), + }; + + let contract_response: RunnerResult = wasm.query(&env.contract_address, &query_msg); + println!("{:?}", contract_response); + + if let Err(QueryError { msg }) = contract_response { + assert_eq!( + msg, "Generic error: Querier contract error: codespace: authz, code: 2: query wasm contract failed", + "The error message does not match the expected value" + ); + } else { + assert!(false, "Expected an error, but got a success: {:?}", contract_response); + } +} + +fn create_stargate_response(messages: Vec<&str>, granter: String, grantee: String) -> StargateQueryGranteeGrantsResponse { + let grants = messages + .into_iter() + .map(|msg| Grants { + granter: granter.clone(), + grantee: grantee.clone(), + authorization: Authorization { + type_str: "/cosmos.authz.v1beta1.GenericAuthorization".to_string(), + msg: msg.to_string(), + }, + }) + .collect(); + + StargateQueryGranteeGrantsResponse { grants } +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/mod.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/mod.rs new file mode 100644 index 00000000..dbeede1a --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/mod.rs @@ -0,0 +1,8 @@ +mod authz; +mod test_auction; +mod test_auth; +mod test_bank; +mod test_exchange; +mod test_exchange_derivative; +mod test_oracle; +mod type_helpers; diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/test_auction.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/test_auction.rs new file mode 100644 index 00000000..83379ea6 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/test_auction.rs @@ -0,0 +1,102 @@ +use cosmwasm_std::{from_json, Coin, Int64, Uint128, Uint64}; +use injective_math::FPDecimal; +use injective_test_tube::{Module, Wasm}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use crate::msg::QueryStargateResponse; +use crate::{ + msg::QueryMsg, + utils::{ExchangeType, Setup}, +}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct QueryCurrentAuctionBasketResponse { + pub amount: Vec, + pub auction_round: Uint64, + pub auction_closing_time: Int64, + pub highest_bidder: String, + pub highest_bid_amount: Uint128, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct QueryAuctionParamsResponse { + pub params: AuctionParams, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct AuctionParams { + pub auction_period: Int64, + pub min_next_bid_increment_rate: FPDecimal, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct QueryLastAuctionResultResponse { + pub last_auction_result: LastAuctionResult, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct LastAuctionResult { + pub winner: String, + pub amount: Coin, + pub round: Uint64, +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_current_auction_basket() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.auction.v1beta1.Query/CurrentAuctionBasket".to_string(), + query_request: "".to_string(), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: QueryCurrentAuctionBasketResponse = from_json(contract_response).unwrap(); + + assert_eq!(response.amount, vec![]); + assert_eq!(response.auction_closing_time, Int64::from(-62121081600i64)); + assert_eq!(response.highest_bid_amount, Uint128::zero()); + assert_eq!(response.auction_round, Uint64::from(23u64)); + assert_eq!(response.highest_bidder, "".to_string()); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_auction_params() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.auction.v1beta1.Query/AuctionParams".to_string(), + query_request: "".to_string(), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: QueryAuctionParamsResponse = from_json(contract_response).unwrap(); + + assert_eq!(response.params.auction_period, Int64::from(604800i64)); + assert_eq!(response.params.min_next_bid_increment_rate, FPDecimal::must_from_str("0.0025")); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_last_auction_result() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.auction.v1beta1.Query/LastAuctionResult".to_string(), + query_request: "".to_string(), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: QueryLastAuctionResultResponse = from_json(contract_response).unwrap(); + + assert_eq!(response.last_auction_result.winner, "".to_string()); + assert_eq!(response.last_auction_result.round, Uint64::zero()); + assert_eq!(response.last_auction_result.amount, Coin::new(0u128, "inj")); +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/test_auth.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/test_auth.rs new file mode 100644 index 00000000..6bf84b1a --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/test_auth.rs @@ -0,0 +1,44 @@ +use crate::{ + encode_helper::encode_proto_message, + msg::{QueryMsg, QueryStargateResponse}, + testing::type_helpers::{AuthParams, CosmosAuthQueryAccountsResponse, ParamResponse}, + utils::{ExchangeType, Setup}, +}; +use cosmos_sdk_proto::cosmos::auth::v1beta1::QueryAccountRequest; +use injective_test_tube::{Account, Module, Wasm}; + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_auth_params() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.auth.v1beta1.Query/Params".to_string(), + query_request: "".to_string(), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: ParamResponse = serde_json::from_str(&contract_response).unwrap(); + assert_eq!(response.params.max_memo_characters, "256"); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_auth_account() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + + let user_address = env.users[0].account.address().to_string(); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.auth.v1beta1.Query/Account".to_string(), + query_request: encode_proto_message(QueryAccountRequest { + address: user_address.to_owned(), + }), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: CosmosAuthQueryAccountsResponse = serde_json::from_str(&contract_response).unwrap(); + assert_eq!(response.account.base_account.address, user_address); +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/test_bank.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/test_bank.rs new file mode 100644 index 00000000..dd1fb4e6 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/test_bank.rs @@ -0,0 +1,114 @@ +use crate::{ + encode_helper::encode_proto_message, + msg::{QueryMsg, QueryStargateResponse}, + testing::type_helpers::{BankParams, ParamResponse, QueryBalanceResponse, QueryDenomMetadataResponse, QuerySupplyOffResponse}, + utils::{ExchangeType, Setup}, +}; +use cosmos_sdk_proto::cosmos::bank::v1beta1::{QueryBalanceRequest, QueryDenomMetadataRequest, QuerySupplyOfRequest}; +use cosmwasm_std::{Coin, Uint128}; +use injective_test_tube::{injective_std::types::injective::tokenfactory::v1beta1::MsgCreateDenom, Account, Module, TokenFactory, Wasm}; + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_bank_params() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let query_msg = QueryMsg::QueryBankParams {}; + + let contract_response: ParamResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + assert!(contract_response.params.default_send_enabled); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_bank_params_raw() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.bank.v1beta1.Query/Params".to_string(), + query_request: "".to_string(), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: ParamResponse = serde_json::from_str(&contract_response).unwrap(); + assert!(response.params.default_send_enabled); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_denom_metadata() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let token_factory = TokenFactory::new(&env.app); + + let create_denom_msg = MsgCreateDenom { + sender: env.users[0].account.address().to_string(), + subdenom: "cw".to_string(), + name: "CosmWasm".to_string(), + symbol: "CW".to_string(), + decimals: 6u32, + }; + + let denom = token_factory.create_denom(create_denom_msg, &env.users[0].account).unwrap(); + let denom_name = denom.data.new_token_denom; + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.bank.v1beta1.Query/DenomMetadata".to_string(), + query_request: encode_proto_message(QueryDenomMetadataRequest { + denom: denom_name.to_owned(), + }), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: QueryDenomMetadataResponse = serde_json::from_str(&contract_response).unwrap(); + assert_eq!(response.metadatas[0].denom_units[0].denom, denom_name); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_bank_balance() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let user_address = env.users[0].account.address().to_string(); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.bank.v1beta1.Query/Balance".to_string(), + query_request: encode_proto_message(QueryBalanceRequest { + address: user_address.to_owned(), + denom: "inj".to_string(), + }), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: QueryBalanceResponse = serde_json::from_str(&contract_response).unwrap(); + assert_eq!( + response.balance, + Coin { + denom: "inj".to_string(), + amount: Uint128::new(1_000_000_000_000_000_000_000_000), + } + ); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_supply_of() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/cosmos.bank.v1beta1.Query/SupplyOf".to_string(), + query_request: encode_proto_message(QuerySupplyOfRequest { denom: "inj".to_string() }), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let response: QuerySupplyOffResponse = serde_json::from_str(&contract_response).unwrap(); + assert_eq!( + response.amount, + Coin { + denom: "inj".to_string(), + amount: Uint128::new(12000004078367203674350010), + } + ); +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/test_exchange.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/test_exchange.rs new file mode 100644 index 00000000..ca6bebe0 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/test_exchange.rs @@ -0,0 +1,169 @@ +use crate::{ + encode_helper::encode_proto_message, + msg::{ExecuteMsg, QueryMsg, QueryStargateResponse}, + testing::type_helpers::{ExchangeParams, ParamResponse}, + utils::{add_spot_initial_liquidity, execute_all_authorizations, ExchangeType, Setup, BASE_DECIMALS, BASE_DENOM, QUOTE_DECIMALS}, +}; +use cosmwasm_std::{coin, from_json, Addr}; +use injective_cosmwasm::{checked_address_to_subaccount_id, MarketId, SubaccountDepositResponse}; +use injective_test_tube::{ + injective_std::types::{ + cosmos::base::v1beta1::Coin as BaseCoin, + injective::exchange::v1beta1::{Deposit, MsgDeposit, QuerySubaccountDepositRequest, QuerySubaccountDepositsRequest}, + }, + Account, Exchange, Module, Wasm, +}; +use injective_testing::utils::{human_to_dec, human_to_proto, scale_price_quantity_spot_market, str_coin}; + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_exchange_param() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/QueryExchangeParams".to_string(), + query_request: "".to_string(), + }; + + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + + let response: ParamResponse = from_json(contract_response).unwrap(); + + let listing_fee_coin = str_coin("20", BASE_DENOM, BASE_DECIMALS); + + assert_eq!(response.params.spot_market_instant_listing_fee, listing_fee_coin); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_subaccount_deposit() { + let env = Setup::new(ExchangeType::None); + let exchange = Exchange::new(&env.app); + let wasm = Wasm::new(&env.app); + + let subaccount_id = checked_address_to_subaccount_id(&Addr::unchecked(env.users[0].account.address()), 1u32); + + let make_deposit = |amount: &str, denom_key: &str| { + exchange + .deposit( + MsgDeposit { + sender: env.users[0].account.address(), + subaccount_id: subaccount_id.to_string(), + amount: Some(BaseCoin { + amount: amount.to_string(), + denom: env.denoms[denom_key].clone(), + }), + }, + &env.users[0].account, + ) + .unwrap(); + }; + + make_deposit("10000000000000000000", "base"); + make_deposit("100000000", "quote"); + + let response = exchange + .query_subaccount_deposits(&QuerySubaccountDepositsRequest { + subaccount_id: subaccount_id.to_string(), + subaccount: None, + }) + .unwrap(); + + assert_eq!( + response.deposits[&env.denoms["base"].clone()], + Deposit { + available_balance: human_to_proto("10.0", BASE_DECIMALS), + total_balance: human_to_proto("10.0", BASE_DECIMALS), + } + ); + assert_eq!( + response.deposits[&env.denoms["quote"].clone()], + Deposit { + available_balance: human_to_proto("100.0", QUOTE_DECIMALS), + total_balance: human_to_proto("100.0", QUOTE_DECIMALS), + } + ); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/SubaccountDeposit".to_string(), + query_request: encode_proto_message(QuerySubaccountDepositRequest { + subaccount_id: subaccount_id.to_string(), + denom: env.denoms["base"].clone(), + }), + }; + let contract_response: QueryStargateResponse = wasm.query(&env.contract_address, &query_msg).unwrap(); + let contract_response = contract_response.value; + let contract_response: SubaccountDepositResponse = serde_json::from_str(&contract_response).unwrap(); + let deposit = contract_response.deposits; + assert_eq!(deposit.total_balance, human_to_dec("10.0", BASE_DECIMALS)); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_trader_transient_spot_orders() { + let env = Setup::new(ExchangeType::Spot); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + + let subaccount_id = checked_address_to_subaccount_id(&Addr::unchecked(env.users[0].account.address()), 0u32); + + execute_all_authorizations(&env.app, &env.users[0].account, env.contract_address.clone()); + + add_spot_initial_liquidity(&env.app, market_id.clone()); + + let (scale_price, scale_quantity) = scale_price_quantity_spot_market("9.8", "1", &BASE_DECIMALS, "E_DECIMALS); + + let res = wasm + .execute( + &env.contract_address, + &ExecuteMsg::TestTraderTransientSpotOrders { + market_id: MarketId::new(market_id).unwrap(), + subaccount_id: subaccount_id.clone(), + price: scale_price.to_string(), + quantity: scale_quantity.to_string(), + }, + &[], + &env.users[0].account, + ) + .unwrap(); + + let transient_query = res + .events + .iter() + .find(|e| e.ty == "wasm-transient_order") + .and_then(|event| event.attributes.iter().find(|a| a.key == "query_str")); + + assert!(transient_query.is_some()); + let expected_order_info = "{\"value\":\"{\\\"orders\\\":[{\\\"price\\\":\\\"0.000000000009800000\\\",\\\"quantity\\\":\\\"1000000000000000000.000000000000000000\\\",\\\"fillable\\\":\\\"1000000000000000000.000000000000000000\\\",\\\"isBuy\\\":false,"; + assert!(transient_query.unwrap().value.contains(expected_order_info)); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_trader_spot_market_order() { + let env = Setup::new(ExchangeType::Spot); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + + let subaccount_id = checked_address_to_subaccount_id(&Addr::unchecked(env.contract_address.to_owned()), 0u32); + + execute_all_authorizations(&env.app, &env.users[0].account, env.contract_address.clone()); + add_spot_initial_liquidity(&env.app, market_id.clone()); + + let (scale_price, scale_quantity) = scale_price_quantity_spot_market("9.8", "1", &BASE_DECIMALS, "E_DECIMALS); + + wasm.execute( + &env.contract_address, + &ExecuteMsg::TestMarketOrderStargate { + market_id: MarketId::new(market_id).unwrap(), + subaccount_id: subaccount_id.clone(), + price: scale_price.to_string(), + quantity: scale_quantity.to_string(), + }, + &[coin(1000000000000000000000u128, BASE_DENOM)], + &env.users[0].account, + ) + .unwrap(); +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/test_exchange_derivative.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/test_exchange_derivative.rs new file mode 100644 index 00000000..ee44e59c --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/test_exchange_derivative.rs @@ -0,0 +1,394 @@ +use crate::{ + encode_helper::encode_proto_message, + msg::{ExecuteMsg, QueryMsg}, + testing::type_helpers::{MyDerivativeMarketResponse, MyPerpetualMarketFundingResponse, MyPerpetualMarketInfoResponse}, + utils::{ + add_derivative_order_as, add_derivative_orders, add_perp_initial_liquidity, execute_all_authorizations, + get_initial_perp_liquidity_orders_vector, get_perpetual_market_id, get_stargate_query_result, ExchangeType, HumanOrder, Setup, BASE_DENOM, + QUOTE_DECIMALS, QUOTE_DENOM, + }, +}; + +use cosmwasm_std::{coin, Addr, Int64}; +use injective_cosmwasm::{ + checked_address_to_subaccount_id, exchange::response::QueryOrderbookResponse, MarketId, MarketMidPriceAndTOBResponse, PriceLevel, + SubaccountEffectivePositionInMarketResponse, SubaccountPositionInMarketResponse, TraderDerivativeOrdersResponse, TrimmedDerivativeLimitOrder, +}; +use injective_math::FPDecimal; +use injective_test_tube::{ + injective_cosmwasm::get_default_subaccount_id_for_checked_address, + injective_std::types::injective::exchange::v1beta1::{ + MsgInstantPerpetualMarketLaunch, OrderType, QueryDerivativeMarketRequest, QueryDerivativeMidPriceAndTobRequest, + QueryDerivativeOrderbookRequest, QueryPerpetualMarketFundingRequest, QueryPerpetualMarketInfoRequest, + QuerySubaccountEffectivePositionInMarketRequest, QuerySubaccountPositionInMarketRequest, QueryTraderDerivativeOrdersRequest, + }, + Account, Exchange, Module, Wasm, +}; +use injective_testing::utils::{dec_to_proto, human_to_dec, scale_price_quantity_perp_market}; + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_perpetual_market_info() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let exchange = Exchange::new(&env.app); + + let ticker = "INJ/USDT".to_string(); + let derivative_market_id = get_perpetual_market_id(&exchange, ticker.to_owned()); + let market_id = MarketId::new(derivative_market_id.clone()).unwrap(); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/PerpetualMarketInfo".to_string(), + query_request: encode_proto_message(QueryPerpetualMarketInfoRequest { + market_id: market_id.to_owned().into(), + }), + }; + + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert!(res.info.is_some()); + let market_info = res.info.clone().unwrap(); + assert_eq!(market_info.market_id, market_id); + assert_eq!(market_info.funding_interval, Int64::new(3600)); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_derivative_market() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let exchange = Exchange::new(&env.app); + + let ticker = "INJ/USDT".to_string(); + let initial_margin_ratio = FPDecimal::must_from_str("0.195"); + let maintenance_margin_ratio = FPDecimal::must_from_str("0.05"); + let min_price_tick_size = FPDecimal::must_from_str("1000.0"); + let min_quantity_tick_size = FPDecimal::must_from_str("1000000000000000"); + let min_notional = FPDecimal::must_from_str("1000000000000000"); + + let quote_denom = QUOTE_DENOM.to_string(); + let maker_fee_rate = FPDecimal::must_from_str("-0.0001"); + let taker_fee_rate = FPDecimal::must_from_str("0.0005"); + + exchange + .instant_perpetual_market_launch( + MsgInstantPerpetualMarketLaunch { + sender: env.owner.address(), + ticker: ticker.to_owned(), + quote_denom: quote_denom.to_owned(), + oracle_base: BASE_DENOM.to_owned(), + oracle_quote: quote_denom.to_owned(), + oracle_scale_factor: 6u32, + oracle_type: 2i32, + maker_fee_rate: dec_to_proto(maker_fee_rate).to_string(), + taker_fee_rate: dec_to_proto(taker_fee_rate), + initial_margin_ratio: dec_to_proto(initial_margin_ratio), + maintenance_margin_ratio: dec_to_proto(maintenance_margin_ratio), + min_price_tick_size: dec_to_proto(min_price_tick_size), + min_quantity_tick_size: dec_to_proto(min_quantity_tick_size), + min_notional: dec_to_proto(min_notional), + }, + &env.owner, + ) + .unwrap(); + + let derivative_market_id = get_perpetual_market_id(&exchange, ticker.to_owned()); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/DerivativeMarket".to_string(), + query_request: encode_proto_message(QueryDerivativeMarketRequest { + market_id: derivative_market_id.to_owned(), + }), + }; + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + + let response_market = res.market.unwrap().market.unwrap(); + assert_eq!(response_market.market_id.as_str(), derivative_market_id); + assert_eq!(response_market.ticker, ticker); + assert_eq!(response_market.quote_denom, quote_denom); + assert_eq!(response_market.min_price_tick_size, min_price_tick_size); + assert_eq!(response_market.min_quantity_tick_size, min_quantity_tick_size); + assert_eq!(response_market.maker_fee_rate, maker_fee_rate); + assert_eq!(response_market.taker_fee_rate, taker_fee_rate); + assert_eq!(response_market.initial_margin_ratio, initial_margin_ratio); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_effective_subaccount_position() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + + add_perp_initial_liquidity(&env.app, market_id.clone()); + + let (price, quantity, margin) = scale_price_quantity_perp_market("9.7", "1", "2", "E_DECIMALS); + + let subaccount_id = get_default_subaccount_id_for_checked_address(&Addr::unchecked(env.users[1].account.address())) + .as_str() + .to_string(); + + add_derivative_order_as( + &env.app, + market_id.to_owned(), + &env.users[1].account, + price, + quantity, + OrderType::Sell, + margin, + ); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/SubaccountEffectivePositionInMarket".to_string(), + query_request: encode_proto_message(QuerySubaccountEffectivePositionInMarketRequest { + market_id: market_id.to_owned(), + subaccount_id: subaccount_id.to_owned(), + }), + }; + + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert!(res.state.is_some()); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_vanilla_subaccount_position() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + + add_perp_initial_liquidity(&env.app, market_id.clone()); + + let (price, quantity, margin) = scale_price_quantity_perp_market("9.7", "1", "2", "E_DECIMALS); + let trader = &env.users[1]; + let subaccount_id = get_default_subaccount_id_for_checked_address(&Addr::unchecked(trader.account.address())) + .as_str() + .to_string(); + + add_derivative_order_as( + &env.app, + market_id.to_owned(), + &env.users[1].account, + price, + quantity, + OrderType::Sell, + margin, + ); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/SubaccountPositionInMarket".to_string(), + query_request: encode_proto_message(QuerySubaccountPositionInMarketRequest { + subaccount_id: subaccount_id.to_string(), + market_id: market_id.to_owned(), + }), + }; + + let res: SubaccountPositionInMarketResponse = + get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + println!("{:?}", res); + assert!(res.state.is_some()); + + let liquidity_orders: Vec = vec![HumanOrder { + price: "9.7".to_string(), + quantity: "10".to_string(), + order_type: OrderType::Sell, + }]; + add_derivative_orders(&env.app, market_id.clone(), liquidity_orders.to_owned(), None); + + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + println!("{:?}", res); + assert!(res.state.is_some()); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_trader_derivative_orders() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + + let (price, quantity, margin) = scale_price_quantity_perp_market("10.1", "1", "2", "E_DECIMALS); + let subaccount_id = get_default_subaccount_id_for_checked_address(&Addr::unchecked(env.users[1].account.address())) + .as_str() + .to_string(); + + add_derivative_order_as( + &env.app, + market_id.to_owned(), + &env.users[1].account, + price, + quantity, + OrderType::Sell, + margin, + ); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/TraderDerivativeOrders".to_string(), + query_request: encode_proto_message(QueryTraderDerivativeOrdersRequest { + subaccount_id: subaccount_id.to_string(), + market_id: market_id.to_owned(), + }), + }; + + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + + assert!(res.orders.is_some()); + let orders = res.orders.clone().unwrap(); + assert_eq!(orders.len(), 1); + let expected_order = TrimmedDerivativeLimitOrder { + price: human_to_dec("10.1", QUOTE_DECIMALS), + quantity: FPDecimal::must_from_str("1"), + margin: human_to_dec("20.2", QUOTE_DECIMALS), + fillable: FPDecimal::must_from_str("1"), + isBuy: false, + order_hash: "".to_string(), + }; + assert_eq!(orders[0].price, expected_order.price); + assert_eq!(orders[0].quantity, expected_order.quantity); + assert_eq!(orders[0].fillable, expected_order.fillable); + assert_eq!(orders[0].isBuy, expected_order.isBuy); + assert_eq!(orders[0].margin, expected_order.margin); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_perpetual_market_funding() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let exchange = Exchange::new(&env.app); + let ticker = "INJ/USDT".to_string(); + let derivative_market_id = get_perpetual_market_id(&exchange, ticker.to_owned()); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/PerpetualMarketFunding".to_string(), + query_request: encode_proto_message(QueryPerpetualMarketFundingRequest { + market_id: derivative_market_id.to_owned(), + }), + }; + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert!(res.state.is_some()); + let state = res.state.unwrap(); + assert_eq!(state.cumulative_funding, FPDecimal::ZERO); + assert_eq!(state.cumulative_price, FPDecimal::ZERO); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_derivative_market_mid_price_and_tob() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + + add_perp_initial_liquidity(&env.app, market_id.to_owned()); + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/DerivativeMidPriceAndTOB".to_string(), + query_request: encode_proto_message(QueryDerivativeMidPriceAndTobRequest { + market_id: market_id.to_owned(), + }), + }; + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert_eq!(res.mid_price, Some(human_to_dec("10", QUOTE_DECIMALS))); + assert_eq!(res.best_buy_price, Some(human_to_dec("9.9", QUOTE_DECIMALS))); + assert_eq!(res.best_sell_price, Some(human_to_dec("10.1", QUOTE_DECIMALS))); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_derivative_market_orderbook() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + + let liquidity_orders = get_initial_perp_liquidity_orders_vector(); + add_derivative_orders(&env.app, market_id.clone(), liquidity_orders.to_owned(), None); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.exchange.v1beta1.Query/DerivativeOrderbook".to_string(), + query_request: encode_proto_message(QueryDerivativeOrderbookRequest { + market_id: market_id.to_owned(), + limit: 0, + limit_cumulative_notional: "100000000000000000000000000000".to_string(), + }), + }; + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + + let buys_price_level = res.buys_price_level; + let sells_price_level = res.sells_price_level; + assert_eq!(buys_price_level.len(), 2); + assert_eq!(sells_price_level.len(), 2); + assert_eq!( + buys_price_level[0], + PriceLevel { + p: human_to_dec(liquidity_orders[2].price.as_str(), QUOTE_DECIMALS), + q: FPDecimal::must_from_str(liquidity_orders[2].quantity.as_str()), + } + ); + assert_eq!( + buys_price_level[1], + PriceLevel { + p: human_to_dec(liquidity_orders[3].price.as_str(), QUOTE_DECIMALS), + q: FPDecimal::must_from_str(liquidity_orders[3].quantity.as_str()), + } + ); + assert_eq!( + sells_price_level[0], + PriceLevel { + p: human_to_dec(liquidity_orders[1].price.as_str(), QUOTE_DECIMALS), + q: FPDecimal::must_from_str(liquidity_orders[1].quantity.as_str()), + } + ); + assert_eq!( + sells_price_level[1], + PriceLevel { + p: human_to_dec(liquidity_orders[0].price.as_str(), QUOTE_DECIMALS), + q: FPDecimal::must_from_str(liquidity_orders[0].quantity.as_str()), + } + ); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_trader_transient_derivative_orders() { + let env = Setup::new(ExchangeType::Derivative); + let wasm = Wasm::new(&env.app); + let market_id = env.market_id.unwrap(); + let subaccount_id = checked_address_to_subaccount_id(&Addr::unchecked(env.users[0].account.address()), 0u32); + + execute_all_authorizations(&env.app, &env.users[0].account, env.contract_address.clone()); + + add_perp_initial_liquidity(&env.app, market_id.clone()); + + let (price, quantity, margin) = scale_price_quantity_perp_market("9.7", "0.5", "1", "E_DECIMALS); + add_derivative_order_as( + &env.app, + market_id.to_owned(), + &env.users[0].account, + price, + quantity, + OrderType::Buy, + margin, + ); + + let (scale_price, scale_quantity, scaled_margin) = scale_price_quantity_perp_market("9.7", "0.1", "0.5", "E_DECIMALS); + let res = wasm + .execute( + &env.contract_address, + &ExecuteMsg::TestTraderTransientDerivativeOrders { + market_id: MarketId::new(market_id).unwrap(), + subaccount_id: subaccount_id.clone(), + price: scale_price.to_string(), + quantity: scale_quantity.to_string(), + margin: scaled_margin.to_string(), + }, + &[coin(1000000u128, QUOTE_DENOM)], + &env.users[0].account, + ) + .unwrap(); + + let transient_query = res + .events + .iter() + .find(|e| e.ty == "wasm-transient_derivative_order") + .and_then(|event| event.attributes.iter().find(|a| a.key == "query_str")); + println!("{:?}", transient_query); + assert!(transient_query.is_some()); + let expected_order_info = "{\"value\":\"{\\\"orders\\\":[{\\\"price\\\":\\\"9700000.000000000000000000\\\",\\\"quantity\\\":\\\"0.100000000000000000\\\",\\\"margin\\\":\\\"485000.000000000000000000\\\",\\\"fillable\\\":\\\"0.100000000000000000\\\",\\\"isBuy\\\":true,"; + assert!(transient_query.unwrap().value.contains(expected_order_info)); +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/test_oracle.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/test_oracle.rs new file mode 100644 index 00000000..fa991318 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/test_oracle.rs @@ -0,0 +1,126 @@ +use crate::{ + encode_helper::encode_proto_message, + msg::QueryMsg, + testing::type_helpers::{MyOraclePriceResponse, MyPythPriceResponse, MyVolatilityResponse}, + utils::{ + create_some_inj_price_attestation, get_stargate_query_result, relay_pyth_price, set_address_of_pyth_contract, ExchangeType, Setup, + BASE_DECIMALS, BASE_DENOM, INJ_PYTH_PRICE_ID, + }, +}; +use injective_cosmwasm::OracleType; +use injective_math::{scale::Scaled, FPDecimal}; +use injective_test_tube::{ + injective_std::types::injective::oracle::v1beta1::{ + OracleHistoryOptions, OracleInfo, QueryOraclePriceRequest, QueryOracleVolatilityRequest, QueryPythPriceRequest, + }, + Module, Oracle, Wasm, +}; +use injective_testing::utils::str_coin; + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_oracle_price() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let oracle = Oracle::new(&env.app); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.oracle.v1beta1.Query/OraclePrice".to_string(), + query_request: encode_proto_message(QueryOraclePriceRequest { + oracle_type: OracleType::PriceFeed as i32, + base: env.denoms["base"].to_owned(), + quote: env.denoms["quote"].to_owned(), + scaling_options: None, + }), + }; + + let query_oracle_price_request = QueryOraclePriceRequest { + oracle_type: 2i32, + base: env.denoms["base"].to_owned(), + quote: env.denoms["quote"].to_owned(), + scaling_options: None, + }; + + let oracle_response = oracle.query_oracle_price(&query_oracle_price_request); + let contract_response = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + + let oracle_response_pair_state = oracle_response.unwrap().price_pair_state; + let contract_response_pair_state = contract_response.price_pair_state; + + assert!(contract_response_pair_state.is_some()); + assert!(oracle_response_pair_state.is_some()); + let oracle_response_pair_state = oracle_response_pair_state.unwrap(); + let contract_response_pair_state = contract_response_pair_state.unwrap(); + let oracle_response_pair_price = FPDecimal::must_from_str(oracle_response_pair_state.pair_price.as_str()); + assert_eq!(contract_response_pair_state.pair_price.scaled(18), oracle_response_pair_price); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_oracle_volatility() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + + let base_info = Some(OracleInfo { + symbol: env.denoms["base"].to_owned(), + oracle_type: OracleType::PriceFeed as i32, + }); + + let quote_info = Some(OracleInfo { + symbol: env.denoms["quote"].to_owned(), + oracle_type: OracleType::PriceFeed as i32, + }); + + let oracle_history_options = Some(OracleHistoryOptions { + max_age: 60u64, + include_raw_history: true, + include_metadata: true, + }); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.oracle.v1beta1.Query/OracleVolatility".to_string(), + query_request: encode_proto_message(QueryOracleVolatilityRequest { + base_info, + quote_info, + oracle_history_options, + }), + }; + + let res = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert!(res.volatility.is_none()); +} + +#[test] +#[cfg_attr(not(feature = "integration"), ignore)] +fn test_query_pyth_oracle_price() { + let env = Setup::new(ExchangeType::None); + let wasm = Wasm::new(&env.app); + let oracle = Oracle::new(&env.app); + + let validator = env.app.get_first_validator_signing_account(BASE_DENOM.to_string(), 1.2f64).unwrap(); + let pyth_contract = env.app.init_account(&[str_coin("1000000", BASE_DENOM, BASE_DECIMALS)]).unwrap(); + + set_address_of_pyth_contract(&env.app, &validator, &pyth_contract); + let price_attestations = vec![create_some_inj_price_attestation("7", 5, env.app.get_block_time_seconds())]; + relay_pyth_price(&oracle, price_attestations, &pyth_contract); + + let price_pyth_oracle_response = oracle + .query_pyth_price(&QueryPythPriceRequest { + price_id: INJ_PYTH_PRICE_ID.to_string(), + }) + .unwrap(); + let price_pyth_oracle_response = FPDecimal::must_from_str(price_pyth_oracle_response.price_state.unwrap().ema_price.as_str()); + + let query_msg = QueryMsg::QueryStargateRaw { + path: "/injective.oracle.v1beta1.Query/PythPrice".to_string(), + query_request: encode_proto_message(QueryPythPriceRequest { + price_id: INJ_PYTH_PRICE_ID.to_string(), + }), + }; + + let contract_response = get_stargate_query_result::(wasm.query(&env.contract_address, &query_msg)).unwrap(); + assert_eq!( + contract_response.price_state.unwrap().ema_price.scaled(BASE_DECIMALS), + price_pyth_oracle_response + ); +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/testing/type_helpers.rs b/contracts/injective-cosmwasm-stargate-example/src/testing/type_helpers.rs new file mode 100644 index 00000000..62d3c633 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/testing/type_helpers.rs @@ -0,0 +1,259 @@ +use cosmwasm_std::{Coin, Int64}; +use injective_cosmwasm::MarketId; +use injective_math::FPDecimal; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct ExchangeParams { + pub spot_market_instant_listing_fee: Coin, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct ParamResponse { + pub params: T, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct AuthParams { + pub max_memo_characters: String, + pub sig_verify_cost_ed25519: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MySpotMarketResponse { + pub market: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MySpotMarket { + pub ticker: String, + pub base_denom: String, + pub quote_denom: String, + pub maker_fee_rate: FPDecimal, + pub taker_fee_rate: FPDecimal, + pub relayer_fee_share_rate: FPDecimal, + pub market_id: MarketId, + pub status: String, + pub min_price_tick_size: FPDecimal, + pub min_quantity_tick_size: FPDecimal, + pub min_notional: FPDecimal, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyVolatilityResponse { + pub volatility: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyPerpetualMarketInfo { + pub market_id: MarketId, + #[serde(default)] + pub hourly_funding_rate_cap: FPDecimal, + #[serde(default)] + pub hourly_interest_rate: FPDecimal, + #[serde(default)] + pub next_funding_timestamp: Int64, + pub funding_interval: Int64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyPerpetualMarketInfoResponse { + pub info: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyDerivativeMarketResponse { + pub market: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyFullDerivativeMarket { + pub market: Option, + pub mark_price: FPDecimal, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyDerivativeMarket { + pub ticker: String, + pub oracle_base: String, + pub oracle_quote: String, + #[serde(default)] + pub oracle_type: String, + #[serde(default)] + pub oracle_scale_factor: u32, + pub quote_denom: String, + pub market_id: MarketId, + pub initial_margin_ratio: FPDecimal, + pub maintenance_margin_ratio: FPDecimal, + pub maker_fee_rate: FPDecimal, + pub taker_fee_rate: FPDecimal, + #[serde(rename = "isPerpetual", default)] + pub is_perpetual: bool, + #[serde(default)] + pub min_price_tick_size: FPDecimal, + pub min_quantity_tick_size: FPDecimal, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyPerpetualMarketFundingResponse { + pub state: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyPerpetualMarketFunding { + #[serde(default)] + pub cumulative_funding: FPDecimal, + #[serde(default)] + pub cumulative_price: FPDecimal, + #[serde(default)] + pub last_timestamp: Int64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyOraclePriceResponse { + pub price_pair_state: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyPricePairState { + #[serde(default)] + pub pair_price: FPDecimal, + #[serde(default)] + pub base_price: FPDecimal, + #[serde(default)] + pub quote_price: FPDecimal, + #[serde(default)] + pub base_cumulative_price: FPDecimal, + #[serde(default)] + pub quote_cumulative_price: FPDecimal, + #[serde(default)] + pub base_timestamp: Int64, + #[serde(default)] + pub quote_timestamp: Int64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct MyPythPriceResponse { + pub price_state: Option, +} +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct PythPriceState { + #[serde(default)] + pub price_id: String, + #[serde(default)] + pub ema_price: FPDecimal, + #[serde(default)] + pub ema_conf: FPDecimal, + #[serde(default)] + pub conf: FPDecimal, + #[serde(default)] + pub publish_time: Int64, + pub price_state: PriceState, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct PriceState { + #[serde(default)] + pub price: FPDecimal, + #[serde(default)] + pub cumulative_price: FPDecimal, + #[serde(default)] + pub timestamp: Int64, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct CosmosAuthQueryAccountsResponse { + pub account: AuthAccount, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct AuthAccount { + #[serde(rename = "@type")] + pub type_field: String, + pub base_account: BaseAccount, + pub code_hash: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct BaseAccount { + pub address: String, + pub pub_key: Option, + pub account_number: String, + pub sequence: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct BankParams { + pub send_enabled: Vec, + pub default_send_enabled: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct SendEnabled { + pub denom: String, + pub enabled: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct QueryBalanceResponse { + pub balance: Coin, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct QuerySupplyOffResponse { + pub amount: Coin, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct QueryDenomMetadataResponse { + pub metadatas: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct Metadata { + pub description: String, + pub denom_units: Vec, + pub base: String, + pub display: String, + pub name: String, + pub symbol: String, + pub uri: String, + pub uri_hash: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] +pub struct DenomUnit { + pub denom: String, + pub exponent: u32, + pub aliases: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Pagination { + // Define fields based on your pagination structure, if any +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct StargateQueryGranteeGrantsResponse { + pub grants: Vec, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct StargateQueryGranterGrantsResponse { + pub grants: Vec, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Grants { + pub granter: String, + pub grantee: String, + pub authorization: Authorization, +} + +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub struct Authorization { + #[serde(rename = "@type")] + pub type_str: String, + pub msg: String, +} diff --git a/contracts/injective-cosmwasm-stargate-example/src/utils.rs b/contracts/injective-cosmwasm-stargate-example/src/utils.rs new file mode 100644 index 00000000..839c0746 --- /dev/null +++ b/contracts/injective-cosmwasm-stargate-example/src/utils.rs @@ -0,0 +1,599 @@ +use crate::msg::{InstantiateMsg, QueryStargateResponse, MSG_CREATE_DERIVATIVE_LIMIT_ORDER_ENDPOINT, MSG_CREATE_SPOT_LIMIT_ORDER_ENDPOINT}; +use cosmwasm_std::Addr; +use injective_cosmwasm::{checked_address_to_subaccount_id, get_default_subaccount_id_for_checked_address, SubaccountId}; +use injective_test_tube::{ + injective_std::{ + shim::{Any, Timestamp}, + types::{ + cosmos::{ + authz::v1beta1::{GenericAuthorization, Grant, MsgGrant, MsgRevoke, MsgRevokeResponse}, + bank::v1beta1::SendAuthorization, + base::v1beta1::Coin as BaseCoin, + gov::v1::{MsgSubmitProposal, MsgVote}, + }, + injective::{ + exchange::v1beta1::{ + DerivativeOrder, MsgCreateDerivativeLimitOrder, MsgCreateSpotLimitOrder, OrderInfo, OrderType, QueryDerivativeMarketsRequest, + SpotOrder, + }, + oracle::v1beta1::{MsgRelayPythPrices, MsgUpdateParams, OracleType, Params, PriceAttestation}, + }, + }, + }, + Account, Authz, Bank, Exchange, ExecuteResponse, Gov, InjectiveTestApp, Module, Oracle, Runner, RunnerResult, SigningAccount, Wasm, +}; +use injective_testing::{ + test_tube::{ + bank::send, + exchange::{add_exchange_admin, launch_perp_market, launch_spot_market}, + insurance::launch_insurance_fund, + oracle::launch_price_feed_oracle, + utils::wasm_file, + }, + utils::{human_to_dec, human_to_i64, scale_price_quantity_perp_market, scale_price_quantity_spot_market, str_coin}, +}; +use prost::Message; +use serde::de::DeserializeOwned; +use std::{collections::HashMap, ops::Neg, str::FromStr}; + +pub const EXCHANGE_DECIMALS: i32 = 18i32; +pub const BASE_DECIMALS: i32 = 18i32; +pub const ATOM_DECIMALS: i32 = 8i32; +pub const QUOTE_DECIMALS: i32 = 6i32; + +pub const ATOM_DENOM: &str = "atom"; +pub const BASE_DENOM: &str = "inj"; +pub const QUOTE_DENOM: &str = "usdt"; +pub const INJ_PYTH_PRICE_ID: &str = "0x7a5bc1d2b56ad029048cd63964b3ad2776eadf812edc1a43a31406cb54bff592"; +pub const USDT_PYTH_PRICE_ID: &str = "0x1fc18861232290221461220bd4e2acd1dcdfbc89c84092c93c18bdc7756c1588"; +pub const GOV_MODULE_ADDRESS: &str = "inj10d07y265gmmuvt4z0w9aw880jnsr700jstypyt"; + +pub struct UserInfo { + pub account: SigningAccount, + pub subaccount_id: SubaccountId, +} +pub struct Setup { + pub app: InjectiveTestApp, + pub owner: SigningAccount, + pub signer: SigningAccount, + pub validator: SigningAccount, + pub users: Vec, + pub denoms: HashMap, + pub contract_address: String, + pub code_id: u64, + pub market_id: Option, +} + +pub enum ExchangeType { + Spot, + Derivative, + None, +} + +impl Setup { + pub fn new(exchange_type: ExchangeType) -> Self { + let app = InjectiveTestApp::new(); + + let wasm = Wasm::new(&app); + let exchange = Exchange::new(&app); + + let mut market_id = None; + + let mut denoms = HashMap::new(); + denoms.insert("atom".to_string(), ATOM_DENOM.to_string()); + denoms.insert("quote".to_string(), QUOTE_DENOM.to_string()); + denoms.insert("base".to_string(), BASE_DENOM.to_string()); + + let signer = app.init_account(&[str_coin("1000000", BASE_DENOM, BASE_DECIMALS)]).unwrap(); + + let validator = app.get_first_validator_signing_account(BASE_DENOM.to_string(), 1.2f64).unwrap(); + + let owner = app + .init_account(&[ + str_coin("1000000", ATOM_DENOM, ATOM_DECIMALS), + str_coin("1000000", BASE_DENOM, BASE_DECIMALS), + str_coin("1000000", QUOTE_DENOM, QUOTE_DECIMALS), + ]) + .unwrap(); + + let mut users: Vec = Vec::new(); + for _ in 0..10 { + let user = app + .init_account(&[ + str_coin("1000000", ATOM_DENOM, ATOM_DECIMALS), + str_coin("1000000", BASE_DENOM, BASE_DECIMALS), + str_coin("1000", QUOTE_DENOM, QUOTE_DECIMALS), + ]) + .unwrap(); + + let user_subaccount_id = checked_address_to_subaccount_id(&Addr::unchecked(user.address()), 0u32); + + users.push(UserInfo { + account: user, + subaccount_id: user_subaccount_id, + }); + } + + let wasm_byte_code = std::fs::read(wasm_file("injective_cosmwasm_stargate_example".to_string())).unwrap(); + let code_id = wasm.store_code(&wasm_byte_code, None, &owner).unwrap().data.code_id; + + // Instantiate contract + let contract_address: String = wasm + .instantiate(code_id, &InstantiateMsg {}, Some(&owner.address()), Some("mock-contract"), &[], &owner) + .unwrap() + .data + .address; + + assert!(!contract_address.is_empty(), "Contract address is empty"); + + send(&Bank::new(&app), "1000000000000000000000", BASE_DENOM, &owner, &validator); + + add_exchange_admin(&app, &validator, owner.address().to_string()); + + launch_insurance_fund( + &app, + &owner, + "INJ/USDT", + denoms["quote"].as_str(), + denoms["base"].as_str(), + denoms["quote"].as_str(), + OracleType::PriceFeed, + ); + + launch_price_feed_oracle( + &app, + &signer, + &validator, + denoms["base"].as_str(), + denoms["quote"].as_str(), + human_to_dec("10.01", BASE_DECIMALS).to_string(), + ); + + match exchange_type { + ExchangeType::Spot => { + market_id = Some(launch_spot_market(&exchange, &owner, "INJ/USDT".to_string())); + } + ExchangeType::Derivative => { + market_id = Some(launch_perp_market(&exchange, &owner, "INJ/USDT".to_string())); + } + ExchangeType::None => {} + } + + Self { + app, + owner, + signer, + validator, + users, + denoms, + contract_address, + code_id, + market_id, + } + } +} + +impl Default for Setup { + fn default() -> Self { + Self::new(ExchangeType::None) + } +} + +pub fn get_perpetual_market_id(exchange: &Exchange, ticker: String) -> String { + let perpetual_markets = exchange + .query_derivative_markets(&QueryDerivativeMarketsRequest { + status: "Active".to_string(), + market_ids: vec![], + with_mid_price_and_tob: false, + }) + .unwrap() + .markets; + + let market = perpetual_markets + .iter() + .filter(|m| m.market.is_some()) + .find(|m| m.market.as_ref().unwrap().ticker == ticker) + .unwrap() + .market + .as_ref() + .unwrap(); + + market.market_id.to_string() +} + +#[derive(Clone)] +pub struct HumanOrder { + pub price: String, + pub quantity: String, + pub order_type: OrderType, +} +pub fn add_spot_order_as(app: &InjectiveTestApp, market_id: String, trader: &UserInfo, price: String, quantity: String, order_type: OrderType) { + let exchange = Exchange::new(app); + exchange + .create_spot_limit_order( + MsgCreateSpotLimitOrder { + sender: trader.account.address().clone(), + order: Some(SpotOrder { + market_id: market_id.to_owned(), + order_info: Some(OrderInfo { + subaccount_id: trader.subaccount_id.to_string(), + fee_recipient: trader.account.address(), + price, + quantity, + cid: "".to_string(), + }), + order_type: order_type.into(), + trigger_price: "".to_string(), + }), + }, + &trader.account, + ) + .unwrap(); +} + +pub fn add_spot_orders(app: &InjectiveTestApp, market_id: String, orders: Vec) { + let account = app + .init_account(&[ + str_coin("1000000", BASE_DENOM, BASE_DECIMALS), + str_coin("1000000", QUOTE_DENOM, QUOTE_DECIMALS), + ]) + .unwrap(); + + let subaccount_id = checked_address_to_subaccount_id(&Addr::unchecked(account.address()), 0u32); + + let trader = UserInfo { account, subaccount_id }; + + for order in orders { + let (price, quantity) = scale_price_quantity_spot_market(order.price.as_str(), order.quantity.as_str(), &BASE_DECIMALS, "E_DECIMALS); + add_spot_order_as(app, market_id.to_owned(), &trader, price, quantity, order.order_type); + } +} + +pub fn get_initial_liquidity_orders_vector() -> Vec { + vec![ + HumanOrder { + price: "15".to_string(), + quantity: "10".to_string(), + order_type: OrderType::Sell, + }, + HumanOrder { + price: "12".to_string(), + quantity: "5".to_string(), + order_type: OrderType::Sell, + }, + HumanOrder { + price: "10.2".to_string(), + quantity: "5".to_string(), + order_type: OrderType::Sell, + }, + HumanOrder { + price: "10.1".to_string(), + quantity: "10".to_string(), + order_type: OrderType::Sell, + }, + HumanOrder { + price: "9.9".to_string(), + quantity: "10".to_string(), + order_type: OrderType::Buy, + }, + HumanOrder { + price: "9.8".to_string(), + quantity: "5".to_string(), + order_type: OrderType::Buy, + }, + HumanOrder { + price: "8".to_string(), + quantity: "5".to_string(), + order_type: OrderType::Buy, + }, + HumanOrder { + price: "5".to_string(), + quantity: "10".to_string(), + order_type: OrderType::Buy, + }, + ] +} + +pub fn add_spot_initial_liquidity(app: &InjectiveTestApp, market_id: String) { + add_spot_orders(app, market_id, get_initial_liquidity_orders_vector()); +} + +pub fn get_initial_perp_liquidity_orders_vector() -> Vec { + vec![ + HumanOrder { + price: "10.2".to_string(), + quantity: "2".to_string(), + order_type: OrderType::Sell, + }, + HumanOrder { + price: "10.1".to_string(), + quantity: "1".to_string(), + order_type: OrderType::Sell, + }, + HumanOrder { + price: "9.9".to_string(), + quantity: "1".to_string(), + order_type: OrderType::Buy, + }, + HumanOrder { + price: "9.8".to_string(), + quantity: "2".to_string(), + order_type: OrderType::Buy, + }, + ] +} + +pub fn add_derivative_order_as( + app: &InjectiveTestApp, + market_id: String, + trader: &SigningAccount, + price: String, + quantity: String, + order_type: OrderType, + margin: String, +) { + let exchange = Exchange::new(app); + exchange + .create_derivative_limit_order( + MsgCreateDerivativeLimitOrder { + sender: trader.address(), + order: Some(DerivativeOrder { + market_id: market_id.to_owned(), + order_info: Some(OrderInfo { + subaccount_id: get_default_subaccount_id_for_checked_address(&Addr::unchecked(trader.address())) + .as_str() + .to_string(), + fee_recipient: trader.address(), + price, + quantity, + cid: "".to_string(), + }), + margin, + order_type: order_type.into(), + trigger_price: "".to_string(), + }), + }, + trader, + ) + .unwrap(); +} + +pub fn add_derivative_orders(app: &InjectiveTestApp, market_id: String, orders: Vec, margin: Option) { + let trader = app + .init_account(&[ + str_coin("1000000", BASE_DENOM, BASE_DECIMALS), + str_coin("1000000", QUOTE_DENOM, QUOTE_DECIMALS), + ]) + .unwrap(); + + let margin = margin.unwrap_or("2".into()); + + for order in orders { + let (price, quantity, order_margin) = + scale_price_quantity_perp_market(order.price.as_str(), order.quantity.as_str(), &margin, "E_DECIMALS); + add_derivative_order_as(app, market_id.to_owned(), &trader, price, quantity, order.order_type, order_margin); + } +} + +pub fn add_perp_initial_liquidity(app: &InjectiveTestApp, market_id: String) { + add_derivative_orders(app, market_id, get_initial_perp_liquidity_orders_vector(), None); +} + +pub fn revoke_authorization(app: &InjectiveTestApp, granter: &SigningAccount, grantee: String, msg_type_url: String) { + let _res: ExecuteResponse = app + .execute_multiple( + &[( + MsgRevoke { + granter: granter.address(), + grantee, + msg_type_url, + }, + MsgRevoke::TYPE_URL, + )], + granter, + ) + .unwrap(); +} + +pub fn create_generic_authorization(app: &InjectiveTestApp, granter: &SigningAccount, grantee: String, msg: String, expiration: Option) { + let authz = Authz::new(app); + + let mut buf = vec![]; + GenericAuthorization::encode(&GenericAuthorization { msg }, &mut buf).unwrap(); + + authz + .grant( + MsgGrant { + granter: granter.address(), + grantee, + grant: Some(Grant { + authorization: Some(Any { + type_url: "/cosmos.authz.v1beta1.GenericAuthorization".to_string(), + value: buf.clone(), + }), + expiration, + }), + }, + granter, + ) + .unwrap(); +} + +pub fn create_send_authorization(app: &InjectiveTestApp, granter: &SigningAccount, grantee: String, amount: BaseCoin, expiration: Option) { + let authz = Authz::new(app); + + let mut buf = vec![]; + SendAuthorization::encode( + &SendAuthorization { + spend_limit: vec![amount], + allow_list: vec![], + }, + &mut buf, + ) + .unwrap(); + + authz + .grant( + MsgGrant { + granter: granter.address(), + grantee, + grant: Some(Grant { + authorization: Some(Any { + type_url: "/cosmos.bank.v1beta1.SendAuthorization".to_string(), + value: buf.clone(), + }), + expiration, + }), + }, + granter, + ) + .unwrap(); +} + +pub fn execute_all_authorizations(app: &InjectiveTestApp, granter: &SigningAccount, grantee: String) { + create_generic_authorization(app, granter, grantee.clone(), MSG_CREATE_SPOT_LIMIT_ORDER_ENDPOINT.to_string(), None); + + create_generic_authorization( + app, + granter, + grantee.clone(), + MSG_CREATE_DERIVATIVE_LIMIT_ORDER_ENDPOINT.to_string(), + None, + ); + + create_generic_authorization( + app, + granter, + grantee.clone(), + "/injective.exchange.v1beta1.MsgCreateDerivativeMarketOrder".to_string(), + None, + ); + + create_generic_authorization( + app, + granter, + grantee.clone(), + "/injective.exchange.v1beta1.MsgBatchUpdateOrders".to_string(), + None, + ); + + create_generic_authorization(app, granter, grantee, "/injective.exchange.v1beta1.MsgWithdraw".to_string(), None); +} + +pub fn set_address_of_pyth_contract(app: &InjectiveTestApp, validator: &SigningAccount, pyth_address: &SigningAccount) { + let gov = Gov::new(app); + + let mut buf = vec![]; + MsgUpdateParams::encode( + &MsgUpdateParams { + authority: GOV_MODULE_ADDRESS.to_string(), + params: Some(Params { + pyth_contract: pyth_address.address(), + }), + }, + &mut buf, + ) + .unwrap(); + + let res = gov + .submit_proposal( + MsgSubmitProposal { + messages: vec![Any { + type_url: "/injective.oracle.v1beta1.MsgUpdateParams".to_string(), + value: buf, + }], + initial_deposit: vec![BaseCoin { + amount: "100000000000000000000".to_string(), + denom: "inj".to_string(), + }], + proposer: validator.address(), + metadata: "".to_string(), + title: "Set Pyth contract address".to_string(), + summary: "Set Pyth contract address".to_string(), + expedited: false, + }, + validator, + ) + .unwrap(); + + let proposal_id = res.events.iter().find(|e| e.ty == "submit_proposal").unwrap().attributes[0] + .value + .to_owned(); + + gov.vote( + MsgVote { + proposal_id: u64::from_str(&proposal_id).unwrap(), + voter: validator.address(), + option: 1i32, + metadata: "".to_string(), + }, + validator, + ) + .unwrap(); + + // NOTE: increase the block time in order to move past the voting period + app.increase_time(11u64); +} + +pub fn relay_pyth_price(oracle: &Oracle, price_attestations: Vec, pyth_address: &SigningAccount) { + let pyth_price_msg = MsgRelayPythPrices { + sender: pyth_address.address(), + price_attestations, + }; + + oracle.relay_pyth_prices(pyth_price_msg, pyth_address).unwrap(); +} + +pub fn create_some_inj_price_attestation(human_price: &str, decimal_precision: i32, publish_time: i64) -> PriceAttestation { + if decimal_precision < 0 { + panic!("Desired exponent cannot be negative") + }; + + let (price_i64, exponent_to_use) = if decimal_precision == 1 { + (human_price.parse::().unwrap(), 1) + } else { + (human_to_i64(human_price, decimal_precision), decimal_precision.neg()) + }; + + PriceAttestation { + price_id: INJ_PYTH_PRICE_ID.to_string(), + price: price_i64, + conf: 500, + expo: exponent_to_use, + ema_price: price_i64, + ema_conf: 2000, + ema_expo: exponent_to_use, + publish_time, + } +} + +pub fn create_some_usdt_price_attestation(human_price: &str, decimal_precision: i32, publish_time: i64) -> PriceAttestation { + if decimal_precision < 0 { + panic!("Desired exponent cannot be negative") + }; + + let (price_i64, exponent_to_use) = if decimal_precision == 0 { + (human_price.parse::().unwrap(), 0) + } else { + (human_to_i64(human_price, decimal_precision), decimal_precision.neg()) + }; + + PriceAttestation { + price_id: USDT_PYTH_PRICE_ID.to_string(), + price: price_i64, + conf: 500, + expo: exponent_to_use, + ema_price: price_i64, + ema_conf: 2000, + ema_expo: exponent_to_use, + publish_time, + } +} + +pub fn get_stargate_query_result(contract_response: RunnerResult) -> serde_json::Result { + let contract_response = contract_response.unwrap().value; + serde_json::from_str::(&contract_response).map_err(|error| { + println!("{} \n {}", error, contract_response); + error + }) +} diff --git a/packages/injective-cosmwasm/src/exchange/spot_market.rs b/packages/injective-cosmwasm/src/exchange/spot_market.rs index 68dc19fb..4b15cd49 100644 --- a/packages/injective-cosmwasm/src/exchange/spot_market.rs +++ b/packages/injective-cosmwasm/src/exchange/spot_market.rs @@ -21,6 +21,7 @@ pub struct SpotMarket { pub status: MarketStatus, pub min_price_tick_size: FPDecimal, pub min_quantity_tick_size: FPDecimal, + pub min_notional: FPDecimal, } impl GenericMarket for SpotMarket { diff --git a/packages/injective-cosmwasm/src/exchange_mock_querier.rs b/packages/injective-cosmwasm/src/exchange_mock_querier.rs index 46fe9bc7..f4f53193 100644 --- a/packages/injective-cosmwasm/src/exchange_mock_querier.rs +++ b/packages/injective-cosmwasm/src/exchange_mock_querier.rs @@ -65,6 +65,7 @@ fn default_spot_market_response_handler(market_id: MarketId) -> QuerierResult { status: MarketStatus::Active, min_price_tick_size: FPDecimal::from_str("0.01").unwrap(), min_quantity_tick_size: FPDecimal::from_str("1000000000000000.0").unwrap(), + min_notional: FPDecimal::from_str("0.01").unwrap(), }), }; SystemResult::Ok(ContractResult::from(to_json_binary(&response))) diff --git a/packages/injective-cosmwasm/src/test_helpers.rs b/packages/injective-cosmwasm/src/test_helpers.rs index 8345e699..36a9af96 100644 --- a/packages/injective-cosmwasm/src/test_helpers.rs +++ b/packages/injective-cosmwasm/src/test_helpers.rs @@ -109,6 +109,7 @@ pub(crate) mod testing_helpers { status: MarketStatus::Active, min_price_tick_size: FPDecimal::from_str("0.01").unwrap(), min_quantity_tick_size: FPDecimal::from_str("0.01").unwrap(), + min_notional: FPDecimal::from_str("0.01").unwrap(), } } } diff --git a/packages/injective-math/Cargo.toml b/packages/injective-math/Cargo.toml index 01ba28b7..33f677bc 100644 --- a/packages/injective-math/Cargo.toml +++ b/packages/injective-math/Cargo.toml @@ -14,9 +14,11 @@ version = "0.3.0" [dependencies] cosmwasm-std = { workspace = true } +ethereum-types = { workspace = true } primitive-types = { workspace = true } schemars = { workspace = true } serde = { workspace = true } +subtle-encoding = { workspace = true } [dev-dependencies] cosmwasm-schema = { workspace = true } diff --git a/packages/injective-math/src/fp_decimal/display.rs b/packages/injective-math/src/fp_decimal/display.rs index 71d73ac4..16bbf5fd 100644 --- a/packages/injective-math/src/fp_decimal/display.rs +++ b/packages/injective-math/src/fp_decimal/display.rs @@ -1,4 +1,7 @@ -use crate::fp_decimal::FPDecimal; +use crate::{ + fp_decimal::FPDecimal, + scale::{Scaled, DEC_SCALE_FACTOR}, +}; use std::fmt; impl fmt::Display for FPDecimal { @@ -23,6 +26,16 @@ impl fmt::Display for FPDecimal { } } +pub trait ToProto { + fn to_proto_string(self) -> String; +} + +impl ToProto for FPDecimal { + fn to_proto_string(self) -> String { + self.scaled(DEC_SCALE_FACTOR).to_string() + } +} + #[cfg(test)] mod tests { use crate::FPDecimal; diff --git a/packages/injective-math/src/fp_decimal/mod.rs b/packages/injective-math/src/fp_decimal/mod.rs index 40c2bbcf..490d4648 100644 --- a/packages/injective-math/src/fp_decimal/mod.rs +++ b/packages/injective-math/src/fp_decimal/mod.rs @@ -383,7 +383,7 @@ impl FPDecimal { mod arithmetic; mod comparison; -mod display; +pub mod display; pub mod error; mod exp; mod factorial; diff --git a/packages/injective-math/src/fp_decimal/scale.rs b/packages/injective-math/src/fp_decimal/scale.rs index d2f0329f..094ee7dd 100644 --- a/packages/injective-math/src/fp_decimal/scale.rs +++ b/packages/injective-math/src/fp_decimal/scale.rs @@ -1,5 +1,7 @@ use crate::fp_decimal::FPDecimal; +pub const DEC_SCALE_FACTOR: i32 = 18; + pub trait Scaled { fn scaled(self, digits: i32) -> Self; } @@ -11,7 +13,7 @@ impl Scaled for FPDecimal { } pub fn dec_scale_factor() -> FPDecimal { - FPDecimal::ONE.scaled(18) + FPDecimal::ONE.scaled(DEC_SCALE_FACTOR) } #[cfg(test)] diff --git a/packages/injective-std/Cargo.toml b/packages/injective-std/Cargo.toml index a94f6374..ea3aaa65 100644 --- a/packages/injective-std/Cargo.toml +++ b/packages/injective-std/Cargo.toml @@ -12,7 +12,7 @@ version = "1.13.0" [dependencies] chrono = { workspace = true } cosmwasm-std = { workspace = true } -injective-std-derive = { workspace = true, path = "../injective-std-derive" } +injective-std-derive = { path = "../injective-std-derive" } prost = { workspace = true } prost-types = { workspace = true } schemars = { workspace = true } diff --git a/packages/injective-testing/Cargo.toml b/packages/injective-testing/Cargo.toml index e0a03ca0..45fb841a 100644 --- a/packages/injective-testing/Cargo.toml +++ b/packages/injective-testing/Cargo.toml @@ -9,11 +9,12 @@ version = "1.1.0" [dependencies] anyhow = { workspace = true } +base64 = { workspace = true } cosmwasm-std = { workspace = true } cw-multi-test = { workspace = true } -injective-cosmwasm = { workspace = true, path = "../injective-cosmwasm" } -injective-math = { workspace = true, path = "../injective-math" } -injective-std = { workspace = true, path = "../injective-std" } +injective-cosmwasm = { path = "../injective-cosmwasm" } +injective-math = { path = "../injective-math" } +injective-std = { path = "../injective-std" } injective-test-tube = { workspace = true } prost = { workspace = true } rand = { workspace = true } diff --git a/packages/injective-testing/src/mocks.rs b/packages/injective-testing/src/mocks.rs index acd84732..26009c2d 100644 --- a/packages/injective-testing/src/mocks.rs +++ b/packages/injective-testing/src/mocks.rs @@ -13,8 +13,7 @@ pub const MOCK_ATOM_DECIMALS: i32 = 8i32; pub const MOCK_QUOTE_DECIMALS: i32 = 6i32; pub const MOCK_ATOM_DENOM: &str = "atom"; -pub const MOCK_GAS_DENOM: &str = "inj"; -pub const MOCK_BASE_DENOM: &str = "ubase"; +pub const MOCK_BASE_DENOM: &str = "inj"; pub const MOCK_QUOTE_DENOM: &str = "usdt"; pub const MOCK_USDC_DENOM: &str = "usdc"; @@ -31,6 +30,7 @@ pub fn mock_spot_market(market_id: &str) -> SpotMarket { min_price_tick_size: FPDecimal::must_from_str("0.000000000000001000"), min_quantity_tick_size: FPDecimal::must_from_str("10000000000000.0"), // 0.00001 @ 18dp relayer_fee_share_rate: FPDecimal::must_from_str("0.4"), + min_notional: FPDecimal::ZERO, } } diff --git a/packages/injective-testing/src/test_tube/exchange.rs b/packages/injective-testing/src/test_tube/exchange.rs index d0ff0865..a2948f40 100644 --- a/packages/injective-testing/src/test_tube/exchange.rs +++ b/packages/injective-testing/src/test_tube/exchange.rs @@ -1,8 +1,5 @@ use crate::{ - mocks::{ - MOCK_ATOM_DECIMALS, MOCK_ATOM_DENOM, MOCK_BASE_DECIMALS, MOCK_BASE_DENOM, MOCK_GAS_DENOM, MOCK_QUOTE_DECIMALS, MOCK_QUOTE_DENOM, - MOCK_USDC_DENOM, - }, + mocks::{MOCK_ATOM_DECIMALS, MOCK_ATOM_DENOM, MOCK_BASE_DECIMALS, MOCK_BASE_DENOM, MOCK_QUOTE_DECIMALS, MOCK_QUOTE_DENOM, MOCK_USDC_DENOM}, utils::{dec_to_proto, scale_price_quantity_perp_market, scale_price_quantity_spot_market, str_coin}, }; @@ -438,8 +435,8 @@ pub fn launch_spot_market(exchange: &Exchange, signer: &Signin MsgInstantSpotMarketLaunch { sender: signer.address(), ticker: ticker.clone(), - base_denom: MOCK_BASE_DECIMALS.to_string(), - quote_denom: MOCK_QUOTE_DECIMALS.to_string(), + base_denom: MOCK_BASE_DENOM.to_string(), + quote_denom: MOCK_QUOTE_DENOM.to_string(), min_price_tick_size: dec_to_proto(FPDecimal::must_from_str("0.000000000000001")), min_quantity_tick_size: dec_to_proto(FPDecimal::must_from_str("1000000000000000")), min_notional: dec_to_proto(FPDecimal::must_from_str("1")), @@ -457,8 +454,8 @@ pub fn launch_spot_market_atom(exchange: &Exchange, signer: &S MsgInstantSpotMarketLaunch { sender: signer.address(), ticker: ticker.clone(), - base_denom: MOCK_ATOM_DECIMALS.to_string(), - quote_denom: MOCK_QUOTE_DECIMALS.to_string(), + base_denom: MOCK_ATOM_DENOM.to_string(), + quote_denom: MOCK_QUOTE_DENOM.to_string(), min_price_tick_size: dec_to_proto(FPDecimal::must_from_str("0.000010000000000000")), min_quantity_tick_size: dec_to_proto(FPDecimal::must_from_str("100000")), min_notional: dec_to_proto(FPDecimal::must_from_str("1")), @@ -553,7 +550,6 @@ pub fn execute_spot_limit_order(app: &InjectiveTestApp, market_id: String, price let trader = app .init_account(&[ str_coin("1000000", MOCK_ATOM_DENOM, MOCK_ATOM_DECIMALS), - str_coin("1000000", MOCK_GAS_DENOM, MOCK_BASE_DECIMALS), str_coin("1000000", MOCK_BASE_DENOM, MOCK_BASE_DECIMALS), str_coin("1000000", MOCK_QUOTE_DENOM, MOCK_QUOTE_DECIMALS), str_coin("1000000", MOCK_USDC_DENOM, MOCK_QUOTE_DECIMALS), @@ -638,7 +634,6 @@ pub fn execute_derivative_limit_order( .init_account(&[ str_coin("1000000", MOCK_ATOM_DENOM, MOCK_ATOM_DECIMALS), str_coin("1000000", MOCK_BASE_DENOM, MOCK_BASE_DECIMALS), - str_coin("1000000", MOCK_GAS_DENOM, MOCK_BASE_DECIMALS), str_coin("1000000", MOCK_QUOTE_DENOM, MOCK_QUOTE_DECIMALS), ]) .unwrap();